diff --git a/flake.nix b/flake.nix index 3c2afda..d884711 100644 --- a/flake.nix +++ b/flake.nix @@ -91,6 +91,7 @@ ]); }; } + // (vm_test "authelia" ./test/vm/authelia.nix) // (vm_test "ldap" ./test/vm/ldap.nix) // (vm_test "postgresql" ./test/vm/postgresql.nix) // (vm_test "monitoring" ./test/vm/monitoring.nix) diff --git a/modules/blocks/authelia.nix b/modules/blocks/authelia.nix index aa5a558..27ba9fb 100644 --- a/modules/blocks/authelia.nix +++ b/modules/blocks/authelia.nix @@ -6,6 +6,18 @@ let fqdn = "${cfg.subdomain}.${cfg.domain}"; autheliaCfg = config.services.authelia.instances.${fqdn}; + + template = file: newPath: replacements: + let + templatePath = newPath + ".template"; + + sedPatterns = lib.strings.concatStringsSep " " (lib.attrsets.mapAttrsToList (from: to: "\"s|${from}|${to}|\"") replacements); + in + '' + ln -fs ${file} ${templatePath} + rm ${newPath} || : + sed ${sedPatterns} ${templatePath} > ${newPath} + ''; in { options.shb.authelia = { @@ -46,31 +58,27 @@ in type = lib.types.submodule { options = { jwtSecretFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; description = "File containing the JWT secret."; }; ldapAdminPasswordFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; description = "File containing the LDAP admin user password."; }; sessionSecretFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; description = "File containing the session secret."; }; - notifierSMTPPasswordFile = lib.mkOption { - type = lib.types.str; - description = "File containing the STMP password for the notifier."; - }; storageEncryptionKeyFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; description = "File containing the storage encryption key."; }; identityProvidersOIDCHMACSecretFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; description = "File containing the identity provider OIDC HMAC secret."; }; identityProvidersOIDCIssuerPrivateKeyFile = lib.mkOption { - type = lib.types.str; + type = lib.types.path; description = "File containing the identity provider OIDC issuer private key."; }; }; @@ -83,22 +91,40 @@ in default = []; }; - smtpHost = lib.mkOption { - type = lib.types.str; - description = "SMTP host."; - example = "smtp.example.com"; - }; - - smtpPort = lib.mkOption { - type = lib.types.int; - description = "SMTP port."; - default = 587; - }; - - smtpUsername = lib.mkOption { - type = lib.types.str; - description = "SMTP username."; - example = "postmaster@smtp.example.com"; + smtp = lib.mkOption { + description = "SMTP options."; + default = null; + type = lib.types.nullOr (lib.types.submodule { + options = { + from_address = lib.mkOption { + type = lib.types.str; + description = "SMTP address from which the emails originate."; + example = "authelia@mydomain.com"; + }; + from_name = lib.mkOption { + type = lib.types.str; + description = "SMTP name from which the emails originate."; + default = "Authelia"; + }; + host = lib.mkOption { + type = lib.types.str; + description = "SMTP host to send the emails to."; + }; + port = lib.mkOption { + type = lib.types.port; + description = "SMTP port to send the emails to."; + default = 25; + }; + username = lib.mkOption { + type = lib.types.str; + description = "Username to connect to the SMTP host."; + }; + passwordFile = lib.mkOption { + type = lib.types.str; + description = "File containing the password to connect to the SMTP host."; + }; + }; + }); }; rules = lib.mkOption { @@ -109,6 +135,13 @@ in }; config = lib.mkIf cfg.enable { + assertions = [ + { + assertion = builtins.length cfg.oidcClients > 0; + message = "Must have at least one oidc client otherwise Authelia refuses to start."; + } + ]; + # Overriding the user name so we don't allow any weird characters anywhere. For example, postgres users do not accept the '.'. users = { groups.${autheliaCfg.user} = {}; @@ -127,14 +160,15 @@ in }; # See https://www.authelia.com/configuration/methods/secrets/ environmentVariables = { - AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = cfg.secrets.ldapAdminPasswordFile; - AUTHELIA_SESSION_SECRET_FILE = cfg.secrets.sessionSecretFile; + AUTHELIA_AUTHENTICATION_BACKEND_LDAP_PASSWORD_FILE = toString cfg.secrets.ldapAdminPasswordFile; + AUTHELIA_SESSION_SECRET_FILE = toString cfg.secrets.sessionSecretFile; # Not needed since we use peer auth. # AUTHELIA_STORAGE_POSTGRES_PASSWORD_FILE = "/run/secrets/authelia/postgres_password"; - AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = cfg.secrets.storageEncryptionKeyFile; - AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = cfg.secrets.notifierSMTPPasswordFile; - AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = cfg.secrets.identityProvidersOIDCHMACSecretFile; - AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = cfg.secrets.identityProvidersOIDCIssuerPrivateKeyFile; + AUTHELIA_STORAGE_ENCRYPTION_KEY_FILE = toString cfg.secrets.storageEncryptionKeyFile; + AUTHELIA_IDENTITY_PROVIDERS_OIDC_HMAC_SECRET_FILE = toString cfg.secrets.identityProvidersOIDCHMACSecretFile; + AUTHELIA_IDENTITY_PROVIDERS_OIDC_ISSUER_PRIVATE_KEY_FILE = toString cfg.secrets.identityProvidersOIDCIssuerPrivateKeyFile; + + AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(isNull cfg.smtp)) (toString cfg.smtp.passwordFile); }; settings = { server.host = "127.0.0.1"; @@ -197,12 +231,14 @@ in }; }; notifier = { - smtp = { - host = cfg.smtpHost; - port = cfg.smtpPort; - username = cfg.smtpUsername; - sender = "Authelia "; - # identifier = ""; + filesystem = lib.mkIf (isNull cfg.smtp) { + filename = "/tmp/authelia-notifications"; + }; + smtp = lib.mkIf (!(isNull cfg.smtp)) { + host = cfg.smtp.host; + port = cfg.smtp.port; + username = cfg.smtp.username; + sender = "${cfg.smtp.from_name} <${cfg.smtp.from_address}>"; subject = "[Authelia] {title}"; startup_check_address = "test@authelia.com"; }; @@ -225,7 +261,6 @@ in } ] ++ cfg.rules; }; - identity_providers.oidc.clients = cfg.oidcClients; telemetry = { metrics = { enabled = true; @@ -233,12 +268,34 @@ in }; }; }; + + settingsFiles = map (client: "/var/lib/authelia-${fqdn}/oidc_client_${client.id}.yaml") cfg.oidcClients; }; + systemd.services."authelia-${fqdn}".preStart = + let + mkCfg = client: + let + secretFile = client.secretFile; + clientWithTmpl = { + identity_providers.oidc.clients = [ + ((lib.attrsets.filterAttrs (name: v: name != "secretFile") client) // { + secret = "%SECRET%"; + }) + ]; + }; + tmplFile = pkgs.writeText "oidc_client_${client.id}.yaml" (lib.generators.toYAML {} clientWithTmpl); + in + template tmplFile "/var/lib/authelia-${fqdn}/oidc_client_${client.id}.yaml" { + "%SECRET%" = "$(cat ${toString secretFile})"; + }; + in + lib.mkBefore (lib.concatStringsSep "\n" (map mkCfg cfg.oidcClients)); + services.nginx.virtualHosts.${fqdn} = { - sslCertificate = "/var/lib/acme/${cfg.domain}/cert.pem"; - sslCertificateKey = "/var/lib/acme/${cfg.domain}/key.pem"; - forceSSL = true; + forceSSL = lib.mkIf config.shb.ssl.enable true; + sslCertificate = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/cert.pem"; + sslCertificateKey = lib.mkIf config.shb.ssl.enable "/var/lib/acme/${cfg.domain}/key.pem"; # Taken from https://github.com/authelia/authelia/issues/178 # TODO: merge with config from https://matwick.ca/authelia-nginx-sso/ locations."/".extraConfig = '' diff --git a/test/vm/authelia.nix b/test/vm/authelia.nix new file mode 100644 index 0000000..28257ee --- /dev/null +++ b/test/vm/authelia.nix @@ -0,0 +1,69 @@ +{ pkgs, lib, ... }: +let + ldapAdminPassword = "ldapAdminPassword"; +in +{ + basic = pkgs.nixosTest { + name = "authelia-basic"; + + nodes.machine = { config, pkgs, ... }: { + imports = [ + { + options = { + shb.ssl.enable = lib.mkEnableOption "ssl"; + shb.backup = lib.mkOption { type = lib.types.anything; }; + }; + } + ../../modules/blocks/authelia.nix + ../../modules/blocks/ldap.nix + ../../modules/blocks/postgresql.nix + ]; + + shb.ldap = { + enable = true; + dcdomain = "dc=example,dc=com"; + subdomain = "ldap"; + domain = "example.com"; + ldapUserPasswordFile = pkgs.writeText "user_password" ldapAdminPassword; + jwtSecretFile = pkgs.writeText "jwt_secret" "securejwtsecret"; + }; + + shb.authelia = { + enable = true; + subdomain = "authelia"; + domain = "example.com"; + ldapEndpoint = "ldap://127.0.0.1:${builtins.toString config.shb.ldap.ldapPort}"; + dcdomain = config.shb.ldap.dcdomain; + secrets = { + jwtSecretFile = pkgs.writeText "jwtSecretFile" "jwtSecretFile"; + ldapAdminPasswordFile = pkgs.writeText "ldapAdminPasswordFile" ldapAdminPassword; + sessionSecretFile = pkgs.writeText "sessionSecretFile" "sessionSecretFile"; + storageEncryptionKeyFile = pkgs.writeText "storageEncryptionKeyFile" "storageEncryptionKeyFile"; + identityProvidersOIDCHMACSecretFile = pkgs.writeText "identityProvidersOIDCHMACSecretFile" "identityProvidersOIDCHMACSecretFile"; + # This needs to be of the correct shape and at least 2048 bits. Generated with: + # nix run nixpkgs#openssl -- genrsa -out keypair.pem 2048 + identityProvidersOIDCIssuerPrivateKeyFile = pkgs.writeText "identityProvidersOIDCIssuerPrivateKeyFile" (builtins.readFile ./keypair.pem); + }; + + + oidcClients = [ + { + id = "myclient"; + description = "My Client"; + secretFile = pkgs.writeText "secret" "mysecuresecret"; + public = "false"; + authorization_policy = "one_factor"; + redirect_uris = [ "https://myclient.exapmle.com/redirect" ]; + } + ]; + }; + }; + + testScript = { nodes, ... }: '' + start_all() + machine.wait_for_unit("lldap.service") + machine.wait_for_unit("authelia-authelia.example.com.service") + + ''; + }; +} diff --git a/test/vm/keypair.pem b/test/vm/keypair.pem new file mode 100644 index 0000000..23264ab --- /dev/null +++ b/test/vm/keypair.pem @@ -0,0 +1,28 @@ +-----BEGIN PRIVATE KEY----- +MIIEvwIBADANBgkqhkiG9w0BAQEFAASCBKkwggSlAgEAAoIBAQC2x1rFx98p6djQ +X0mJ8nUMnS3LU7ih8UU5soW/TVhdcioe+NUevLjq0Qe/RXAz+yjhAxmJoWsFwMuy +PKQKDbnqLH6Rf2qPiOvg5NB/vINuVpnAo9mQUJlJOrWKe6/pk6FWD0YxGiwETEIX +bdARazOo/n5emQCZ7XwFy9ULlZkURIt9co/SxhMaD0Q+P1eev0lt01XG5ZLg9wL8 +CHIkasqK5huKFUnHVyq8+ApVrzsANsvjFSLwd985FpIF+DYcVQ+8ZmwVrbZTzFFC +Pjee8CrhO1ZxOAPwVm7qNTtZ4es8xJyMJvijk6grqLGcWSIWTMAwxmN7feOuEvvk +RlDf9DmpAgMBAAECggEACP51jySrDLAQ/wznTJpRi+u6loYhwFdD26dXGT8kNWHy +JGjEbPEmrMhZKB5xu18VJ4Bca/c1UdjHNTeybzu2balfl4eEbfhz+fKsd1KmiYIJ +qg7t/GHY7x9sUjqMoRLmhhp1juI9rv71JBu/WLIUnlDalUtUWh6zYwIhE0M634I8 +GjN4hCxvbVgQEyY4kMBvCcT9sixwm407qL7LfqlsT8KTGB9UU2cC1HD4B/pUKVzw +x+vN93S6KS2SrjaYhAb1xHgxU6Bl1jT1IH8yAXVlmBBmDL9dNEJtD/kuX8kfbvuC +yFY5NWVapSgyIhURkaHqJKmziaq51K1xHGCZYBDsYQKBgQDq0ovgTNBzgmwyl/ye +ZgUIWc/5tE2LlyoM8XTok9EB+8CnoBek8JFo9DVfNzTv+UCUXb7DCveuS1Jb0JY0 +Xi4gOSczVV297Lszziogxuni4ax/1Nezah/WSffVEowakPuTLK+0dst0QxWC6+Db +m4OHJY5qjS/mh3rLhFdjXcmAoQKBgQDHQ0BAg8AhlFz9fTxit1pyHuVs1EcEBjqI +UOS1ClS+BDcjERVBJ8GKiZj2/la37OLlQuguH2AXX//wVC5rZEXP38+ELemW0BZC +JFKaY5PYufMcGVd6JBDYCoEa/JERJsD87ADBAUj/kIMfvka/it9PID9jgMPaVESE +LYIsRv40CQKBgQCtdJ0yMEuCJ4L41GAcOUvaYU1JLDBjvmOnb+xlqFqpVmd26sDM +a49dsZaDIOqPoNRdQ+oXdNCEBMtvWuK5CCCWWOFl/9bg5i9aEx33XDeECiM7weMb +enbN+ZGB6NNpBFNw4X9glKew16TaMpbEYVmEyO8sMeKCLO09zCIpGiwwQQKBgQCF +++dhOfXf3mXkoOgQrJ8pazLzSY1y3ElRTatrPEYc+rKkZqE3DWdrIvhy5DQlOiia +5bE/CiPPs+JhlAkedu8mRqS/iSuvF75PvSK540kPioE4nKWgYE3fJrkHD1rwAHH1 +3y7mmFmgVmiE2Kmzs8pR5yoYWwXWcaEci4kjAp19GQKBgQDRpy4ojGUmKdDffcGU +pEpl+dGpC3YuGwEsopDTYJSjANq0p5QGcQo9L140XxBEaFd4k/jwvVh2VRx4KmkC +wyFODOk4vbq1NKljLC9yRo6UbUZuzWBsyjP62OHPR5MBg5FQgd4RI6/c3EpAhFGX +pM/CH7yZXp7Brhp4RcdbwhQnIA== +-----END PRIVATE KEY-----