From 9b60eef4bdcb63b0a51e260d6fec79ff53f9d67b Mon Sep 17 00:00:00 2001 From: 06kellyjac Date: Mon, 27 Mar 2023 12:00:07 +0100 Subject: authelia: move module under security and minor fixes Fixed test access to lib Added nixos test to passthru --- nixos/modules/module-list.nix | 2 +- nixos/modules/services/security/authelia.nix | 401 ++++++++++++++++++++++++ nixos/modules/services/web-servers/authelia.nix | 401 ------------------------ nixos/tests/authelia.nix | 2 +- 4 files changed, 403 insertions(+), 403 deletions(-) create mode 100644 nixos/modules/services/security/authelia.nix delete mode 100644 nixos/modules/services/web-servers/authelia.nix (limited to 'nixos') diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 23ffb81b6a4ee..abd88d285a998 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -1063,6 +1063,7 @@ ./services/search/opensearch.nix ./services/search/qdrant.nix ./services/security/aesmd.nix + ./services/security/authelia.nix ./services/security/certmgr.nix ./services/security/cfssl.nix ./services/security/clamav.nix @@ -1210,7 +1211,6 @@ ./services/web-apps/zabbix.nix ./services/web-servers/agate.nix ./services/web-servers/apache-httpd/default.nix - ./services/web-servers/authelia.nix ./services/web-servers/caddy/default.nix ./services/web-servers/darkhttpd.nix ./services/web-servers/fcgiwrap.nix diff --git a/nixos/modules/services/security/authelia.nix b/nixos/modules/services/security/authelia.nix new file mode 100644 index 0000000000000..143c441c7e153 --- /dev/null +++ b/nixos/modules/services/security/authelia.nix @@ -0,0 +1,401 @@ +{ lib +, pkgs +, config +, ... +}: + +let + cfg = config.services.authelia; + + format = pkgs.formats.yaml { }; + configFile = format.generate "config.yml" cfg.settings; + + autheliaOpts = with lib; { name, ... }: { + options = { + enable = mkEnableOption (mdDoc "Authelia instance"); + + name = mkOption { + type = types.str; + default = name; + description = mdDoc '' + Name is used as a suffix for the service name, user, and group. + By default it takes the value you use for `` in: + {option}`services.authelia.` + ''; + }; + + package = mkOption { + default = pkgs.authelia; + type = types.package; + defaultText = literalExpression "pkgs.authelia"; + description = mdDoc "Authelia derivation to use."; + }; + + user = mkOption { + default = "authelia-${name}"; + type = types.str; + description = mdDoc "The name of the user for this authelia instance."; + }; + + group = mkOption { + default = "authelia-${name}"; + type = types.str; + description = mdDoc "The name of the group for this authelia instance."; + }; + + secrets = mkOption { + description = mdDoc '' + It is recommended you keep your secrets separate from the configuration. + It's especially important to keep the raw secrets out of your nix configuration, + as the values will be preserved in your nix store. + This attribute allows you to configure the location of secret files to be loaded at runtime. + + https://www.authelia.com/configuration/methods/secrets/ + ''; + default = { }; + type = types.submodule { + options = { + manual = mkOption { + default = false; + example = true; + description = mdDoc '' + Configuring authelia's secret files via the secrets attribute set + is intended to be convenient and help catch cases where values are required + to run at all. + If a user wants to set these values themselves and bypass the validation they can set this value to true. + ''; + type = types.bool; + }; + + # required + jwtSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your JWT secret used during identity verificaiton. + ''; + }; + + oidcIssuerPrivateKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your private key file used to encrypt OIDC JWTs. + ''; + }; + + oidcHmacSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your HMAC secret used to sign OIDC JWTs. + ''; + }; + + sessionSecretFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your session secret. Only used when redis is used as session storage. + ''; + }; + + # required + storageEncryptionKeyFile = mkOption { + type = types.nullOr types.path; + default = null; + description = mdDoc '' + Path to your storage encryption key. + ''; + }; + }; + }; + }; + + environmentVariables = mkOption { + type = types.attrsOf types.str; + description = mdDoc '' + Additional environment variables to provide to authelia. + If you are providing secrets please consider the options under {option}`services.authelia..secrets` + or make sure you use the `_FILE` suffix. + If you provide the raw secret rather than the location of a secret file that secret will be preserved in the nix store. + For more details: https://www.authelia.com/configuration/methods/secrets/ + ''; + default = { }; + }; + + settings = mkOption { + description = mdDoc '' + Your Authelia config.yml as a Nix attribute set. + There are several values that are defined and documented in nix such as `default_2fa_method`, + but additional items can also be included. + + https://github.com/authelia/authelia/blob/master/config.template.yml + ''; + default = { }; + example = '' + { + theme = "light"; + default_2fa_method = "totp"; + log.level = "debug"; + server.disable_healthcheck = true; + } + ''; + type = types.submodule { + freeformType = format.type; + options = { + theme = mkOption { + type = types.enum [ "light" "dark" "grey" "auto" ]; + default = "light"; + example = "dark"; + description = mdDoc "The theme to display."; + }; + + default_2fa_method = mkOption { + type = types.enum [ "" "totp" "webauthn" "mobile_push" ]; + default = ""; + example = "webauthn"; + description = mdDoc '' + Default 2FA method for new users and fallback for preferred but disabled methods. + ''; + }; + + server = { + host = mkOption { + type = types.str; + default = "localhost"; + example = "0.0.0.0"; + description = mdDoc "The address to listen on."; + }; + + port = mkOption { + type = types.port; + default = 9091; + description = mdDoc "The port to listen on."; + }; + }; + + log = { + level = mkOption { + type = types.enum [ "info" "debug" "trace" ]; + default = "debug"; + example = "info"; + description = mdDoc "Level of verbosity for logs: info, debug, trace."; + }; + + format = mkOption { + type = types.enum [ "json" "text" ]; + default = "json"; + example = "text"; + description = mdDoc "Format the logs are written as."; + }; + + file_path = mkOption { + type = types.nullOr types.path; + default = null; + example = "/var/log/authelia/authelia.log"; + description = mdDoc "File path where the logs will be written. If not set logs are written to stdout."; + }; + + keep_stdout = mkOption { + type = types.bool; + default = false; + example = true; + description = mdDoc "Whether to also log to stdout when a `file_path` is defined."; + }; + }; + + telemetry = { + metrics = { + enabled = mkOption { + type = types.bool; + default = false; + example = true; + description = mdDoc "Enable Metrics."; + }; + + address = mkOption { + type = types.str; + default = "tcp://127.0.0.1:9959"; + example = "tcp://0.0.0.0:8888"; + description = mdDoc "The address to listen on for metrics. This should be on a different port to the main `server.port` value."; + }; + }; + }; + }; + }; + }; + + settingsFiles = mkOption { + type = types.listOf types.path; + default = [ ]; + example = [ "/etc/authelia/config.yml" "/etc/authelia/access-control.yml" "/etc/authelia/config/" ]; + description = mdDoc '' + Here you can provide authelia with configuration files or directories. + It is possible to give authelia multiple files and use the nix generated configuration + file set via {option}`services.authelia..settings`. + ''; + }; + }; + }; +in +{ + options.services.authelia.instances = with lib; mkOption { + default = { }; + type = types.attrsOf (types.submodule autheliaOpts); + description = mdDoc '' + Multi-domain protection currently requires multiple instances of Authelia. + If you don't require multiple instances of Authelia you can define just the one. + + https://www.authelia.com/roadmap/active/multi-domain-protection/ + ''; + example = '' + { + main = { + enable = true; + secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile"; + secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile"; + settings = { + theme = "light"; + default_2fa_method = "totp"; + log.level = "debug"; + server.disable_healthcheck = true; + }; + }; + preprod = { + enable = false; + secrets.storageEncryptionKeyFile = "/mnt/pre-prod/authelia/storageEncryptionKeyFile"; + secrets.jwtSecretFile = "/mnt/pre-prod/jwtSecretFile"; + settings = { + theme = "dark"; + default_2fa_method = "webauthn"; + server.host = "0.0.0.0"; + }; + }; + test.enable = true; + test.secrets.manual = true; + test.settings.theme = "grey"; + test.settings.server.disable_healthcheck = true; + test.settingsFiles = [ "/mnt/test/authelia" "/mnt/test-authelia.conf" ]; + }; + } + ''; + }; + + config = + let + mkInstanceServiceConfig = instance: + let + execCommand = "${instance.package}/bin/authelia"; + configFile = format.generate "config.yml" instance.settings; + configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}"; + in + { + description = "Authelia authentication and authorization server"; + wantedBy = [ "multi-user.target" ]; + after = [ "network.target" ]; + environment = + (lib.filterAttrs (_: v: v != null) { + AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile; + AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile; + AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile; + AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile; + AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile; + }) + // instance.environmentVariables; + + preStart = "${execCommand} ${configArg} validate-config"; + serviceConfig = { + User = instance.user; + Group = instance.group; + ExecStart = "${execCommand} ${configArg}"; + Restart = "always"; + RestartSec = "5s"; + StateDirectory = "authelia-${instance.name}"; + StateDirectoryMode = "0700"; + + # Security options: + AmbientCapabilities = ""; + CapabilityBoundingSet = ""; + DeviceAllow = ""; + LockPersonality = true; + MemoryDenyWriteExecute = true; + NoNewPrivileges = true; + + PrivateTmp = true; + PrivateDevices = true; + PrivateUsers = true; + + ProtectClock = true; + ProtectControlGroups = true; + ProtectHome = "read-only"; + ProtectHostname = true; + ProtectKernelLogs = true; + ProtectKernelModules = true; + ProtectKernelTunables = true; + ProtectProc = "noaccess"; + ProtectSystem = "strict"; + + RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; + RestrictNamespaces = true; + RestrictRealtime = true; + RestrictSUIDSGID = true; + + SystemCallArchitectures = "native"; + SystemCallErrorNumber = "EPERM"; + SystemCallFilter = [ + "@system-service" + "~@cpu-emulation" + "~@debug" + "~@keyring" + "~@memlock" + "~@obsolete" + "~@privileged" + "~@setuid" + ]; + }; + }; + mkInstanceUsersConfig = instance: { + groups."authelia-${instance.name}" = + lib.mkIf (instance.group == "authelia-${instance.name}") { + name = "authelia-${instance.name}"; + }; + users."authelia-${instance.name}" = + lib.mkIf (instance.user == "authelia-${instance.name}") { + name = "authelia-${instance.name}"; + isSystemUser = true; + group = instance.group; + }; + }; + instances = lib.attrValues cfg.instances; + in + { + assertions = lib.flatten (lib.flip lib.mapAttrsToList cfg.instances (name: instance: + [ + { + assertion = instance.secrets.manual || (instance.secrets.jwtSecretFile != null && instance.secrets.storageEncryptionKeyFile != null); + message = '' + Authelia requires a JWT Secret and a Storage Encryption Key to work. + Either set them like so: + services.authelia.${name}.secrets.jwtSecretFile = /my/path/to/jwtsecret; + services.authelia.${name}.secrets.storageEncryptionKeyFile = /my/path/to/encryptionkey; + Or set services.authelia.${name}.secrets.manual = true and provide them yourself via + environmentVariables or settingsFiles. + Do not include raw secrets in nix settings. + ''; + } + ] + )); + + systemd.services = lib.mkMerge + (map + (instance: lib.mkIf instance.enable { + "authelia-${instance.name}" = mkInstanceServiceConfig instance; + }) + instances); + users = lib.mkMerge + (map + (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance)) + instances); + }; +} diff --git a/nixos/modules/services/web-servers/authelia.nix b/nixos/modules/services/web-servers/authelia.nix deleted file mode 100644 index 143c441c7e153..0000000000000 --- a/nixos/modules/services/web-servers/authelia.nix +++ /dev/null @@ -1,401 +0,0 @@ -{ lib -, pkgs -, config -, ... -}: - -let - cfg = config.services.authelia; - - format = pkgs.formats.yaml { }; - configFile = format.generate "config.yml" cfg.settings; - - autheliaOpts = with lib; { name, ... }: { - options = { - enable = mkEnableOption (mdDoc "Authelia instance"); - - name = mkOption { - type = types.str; - default = name; - description = mdDoc '' - Name is used as a suffix for the service name, user, and group. - By default it takes the value you use for `` in: - {option}`services.authelia.` - ''; - }; - - package = mkOption { - default = pkgs.authelia; - type = types.package; - defaultText = literalExpression "pkgs.authelia"; - description = mdDoc "Authelia derivation to use."; - }; - - user = mkOption { - default = "authelia-${name}"; - type = types.str; - description = mdDoc "The name of the user for this authelia instance."; - }; - - group = mkOption { - default = "authelia-${name}"; - type = types.str; - description = mdDoc "The name of the group for this authelia instance."; - }; - - secrets = mkOption { - description = mdDoc '' - It is recommended you keep your secrets separate from the configuration. - It's especially important to keep the raw secrets out of your nix configuration, - as the values will be preserved in your nix store. - This attribute allows you to configure the location of secret files to be loaded at runtime. - - https://www.authelia.com/configuration/methods/secrets/ - ''; - default = { }; - type = types.submodule { - options = { - manual = mkOption { - default = false; - example = true; - description = mdDoc '' - Configuring authelia's secret files via the secrets attribute set - is intended to be convenient and help catch cases where values are required - to run at all. - If a user wants to set these values themselves and bypass the validation they can set this value to true. - ''; - type = types.bool; - }; - - # required - jwtSecretFile = mkOption { - type = types.nullOr types.path; - default = null; - description = mdDoc '' - Path to your JWT secret used during identity verificaiton. - ''; - }; - - oidcIssuerPrivateKeyFile = mkOption { - type = types.nullOr types.path; - default = null; - description = mdDoc '' - Path to your private key file used to encrypt OIDC JWTs. - ''; - }; - - oidcHmacSecretFile = mkOption { - type = types.nullOr types.path; - default = null; - description = mdDoc '' - Path to your HMAC secret used to sign OIDC JWTs. - ''; - }; - - sessionSecretFile = mkOption { - type = types.nullOr types.path; - default = null; - description = mdDoc '' - Path to your session secret. Only used when redis is used as session storage. - ''; - }; - - # required - storageEncryptionKeyFile = mkOption { - type = types.nullOr types.path; - default = null; - description = mdDoc '' - Path to your storage encryption key. - ''; - }; - }; - }; - }; - - environmentVariables = mkOption { - type = types.attrsOf types.str; - description = mdDoc '' - Additional environment variables to provide to authelia. - If you are providing secrets please consider the options under {option}`services.authelia..secrets` - or make sure you use the `_FILE` suffix. - If you provide the raw secret rather than the location of a secret file that secret will be preserved in the nix store. - For more details: https://www.authelia.com/configuration/methods/secrets/ - ''; - default = { }; - }; - - settings = mkOption { - description = mdDoc '' - Your Authelia config.yml as a Nix attribute set. - There are several values that are defined and documented in nix such as `default_2fa_method`, - but additional items can also be included. - - https://github.com/authelia/authelia/blob/master/config.template.yml - ''; - default = { }; - example = '' - { - theme = "light"; - default_2fa_method = "totp"; - log.level = "debug"; - server.disable_healthcheck = true; - } - ''; - type = types.submodule { - freeformType = format.type; - options = { - theme = mkOption { - type = types.enum [ "light" "dark" "grey" "auto" ]; - default = "light"; - example = "dark"; - description = mdDoc "The theme to display."; - }; - - default_2fa_method = mkOption { - type = types.enum [ "" "totp" "webauthn" "mobile_push" ]; - default = ""; - example = "webauthn"; - description = mdDoc '' - Default 2FA method for new users and fallback for preferred but disabled methods. - ''; - }; - - server = { - host = mkOption { - type = types.str; - default = "localhost"; - example = "0.0.0.0"; - description = mdDoc "The address to listen on."; - }; - - port = mkOption { - type = types.port; - default = 9091; - description = mdDoc "The port to listen on."; - }; - }; - - log = { - level = mkOption { - type = types.enum [ "info" "debug" "trace" ]; - default = "debug"; - example = "info"; - description = mdDoc "Level of verbosity for logs: info, debug, trace."; - }; - - format = mkOption { - type = types.enum [ "json" "text" ]; - default = "json"; - example = "text"; - description = mdDoc "Format the logs are written as."; - }; - - file_path = mkOption { - type = types.nullOr types.path; - default = null; - example = "/var/log/authelia/authelia.log"; - description = mdDoc "File path where the logs will be written. If not set logs are written to stdout."; - }; - - keep_stdout = mkOption { - type = types.bool; - default = false; - example = true; - description = mdDoc "Whether to also log to stdout when a `file_path` is defined."; - }; - }; - - telemetry = { - metrics = { - enabled = mkOption { - type = types.bool; - default = false; - example = true; - description = mdDoc "Enable Metrics."; - }; - - address = mkOption { - type = types.str; - default = "tcp://127.0.0.1:9959"; - example = "tcp://0.0.0.0:8888"; - description = mdDoc "The address to listen on for metrics. This should be on a different port to the main `server.port` value."; - }; - }; - }; - }; - }; - }; - - settingsFiles = mkOption { - type = types.listOf types.path; - default = [ ]; - example = [ "/etc/authelia/config.yml" "/etc/authelia/access-control.yml" "/etc/authelia/config/" ]; - description = mdDoc '' - Here you can provide authelia with configuration files or directories. - It is possible to give authelia multiple files and use the nix generated configuration - file set via {option}`services.authelia..settings`. - ''; - }; - }; - }; -in -{ - options.services.authelia.instances = with lib; mkOption { - default = { }; - type = types.attrsOf (types.submodule autheliaOpts); - description = mdDoc '' - Multi-domain protection currently requires multiple instances of Authelia. - If you don't require multiple instances of Authelia you can define just the one. - - https://www.authelia.com/roadmap/active/multi-domain-protection/ - ''; - example = '' - { - main = { - enable = true; - secrets.storageEncryptionKeyFile = "/etc/authelia/storageEncryptionKeyFile"; - secrets.jwtSecretFile = "/etc/authelia/jwtSecretFile"; - settings = { - theme = "light"; - default_2fa_method = "totp"; - log.level = "debug"; - server.disable_healthcheck = true; - }; - }; - preprod = { - enable = false; - secrets.storageEncryptionKeyFile = "/mnt/pre-prod/authelia/storageEncryptionKeyFile"; - secrets.jwtSecretFile = "/mnt/pre-prod/jwtSecretFile"; - settings = { - theme = "dark"; - default_2fa_method = "webauthn"; - server.host = "0.0.0.0"; - }; - }; - test.enable = true; - test.secrets.manual = true; - test.settings.theme = "grey"; - test.settings.server.disable_healthcheck = true; - test.settingsFiles = [ "/mnt/test/authelia" "/mnt/test-authelia.conf" ]; - }; - } - ''; - }; - - config = - let - mkInstanceServiceConfig = instance: - let - execCommand = "${instance.package}/bin/authelia"; - configFile = format.generate "config.yml" instance.settings; - configArg = "--config ${builtins.concatStringsSep "," (lib.concatLists [[configFile] instance.settingsFiles])}"; - in - { - description = "Authelia authentication and authorization server"; - wantedBy = [ "multi-user.target" ]; - after = [ "network.target" ]; - environment = - (lib.filterAttrs (_: v: v != null) { - AUTHELIA_JWT_SECRET_FILE = instance.secrets.jwtSecretFile; - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = instance.secrets.storageEncryptionKeyFile; - AUTHELIA_SESSION_SECRET_FILE = instance.secrets.sessionSecretFile; - AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = instance.secrets.oidcIssuerPrivateKeyFile; - AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = instance.secrets.oidcHmacSecretFile; - }) - // instance.environmentVariables; - - preStart = "${execCommand} ${configArg} validate-config"; - serviceConfig = { - User = instance.user; - Group = instance.group; - ExecStart = "${execCommand} ${configArg}"; - Restart = "always"; - RestartSec = "5s"; - StateDirectory = "authelia-${instance.name}"; - StateDirectoryMode = "0700"; - - # Security options: - AmbientCapabilities = ""; - CapabilityBoundingSet = ""; - DeviceAllow = ""; - LockPersonality = true; - MemoryDenyWriteExecute = true; - NoNewPrivileges = true; - - PrivateTmp = true; - PrivateDevices = true; - PrivateUsers = true; - - ProtectClock = true; - ProtectControlGroups = true; - ProtectHome = "read-only"; - ProtectHostname = true; - ProtectKernelLogs = true; - ProtectKernelModules = true; - ProtectKernelTunables = true; - ProtectProc = "noaccess"; - ProtectSystem = "strict"; - - RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ]; - RestrictNamespaces = true; - RestrictRealtime = true; - RestrictSUIDSGID = true; - - SystemCallArchitectures = "native"; - SystemCallErrorNumber = "EPERM"; - SystemCallFilter = [ - "@system-service" - "~@cpu-emulation" - "~@debug" - "~@keyring" - "~@memlock" - "~@obsolete" - "~@privileged" - "~@setuid" - ]; - }; - }; - mkInstanceUsersConfig = instance: { - groups."authelia-${instance.name}" = - lib.mkIf (instance.group == "authelia-${instance.name}") { - name = "authelia-${instance.name}"; - }; - users."authelia-${instance.name}" = - lib.mkIf (instance.user == "authelia-${instance.name}") { - name = "authelia-${instance.name}"; - isSystemUser = true; - group = instance.group; - }; - }; - instances = lib.attrValues cfg.instances; - in - { - assertions = lib.flatten (lib.flip lib.mapAttrsToList cfg.instances (name: instance: - [ - { - assertion = instance.secrets.manual || (instance.secrets.jwtSecretFile != null && instance.secrets.storageEncryptionKeyFile != null); - message = '' - Authelia requires a JWT Secret and a Storage Encryption Key to work. - Either set them like so: - services.authelia.${name}.secrets.jwtSecretFile = /my/path/to/jwtsecret; - services.authelia.${name}.secrets.storageEncryptionKeyFile = /my/path/to/encryptionkey; - Or set services.authelia.${name}.secrets.manual = true and provide them yourself via - environmentVariables or settingsFiles. - Do not include raw secrets in nix settings. - ''; - } - ] - )); - - systemd.services = lib.mkMerge - (map - (instance: lib.mkIf instance.enable { - "authelia-${instance.name}" = mkInstanceServiceConfig instance; - }) - instances); - users = lib.mkMerge - (map - (instance: lib.mkIf instance.enable (mkInstanceUsersConfig instance)) - instances); - }; -} diff --git a/nixos/tests/authelia.nix b/nixos/tests/authelia.nix index 7c239ebdd5af4..679c65fea087a 100644 --- a/nixos/tests/authelia.nix +++ b/nixos/tests/authelia.nix @@ -1,5 +1,5 @@ # Test Authelia as an auth server for Traefik as a reverse proxy of a local web service -import ./make-test-python.nix ({ pkgs, ... }: { +import ./make-test-python.nix ({ lib, ... }: { name = "authelia"; meta.maintainers = with lib.maintainers; [ jk ]; -- cgit 1.4.1