From ca6e1e811495348540bebfb48bb351b180c20c0e Mon Sep 17 00:00:00 2001 From: ibizaman Date: Sat, 30 Dec 2023 23:54:41 -0800 Subject: [PATCH] rename authelia block to sso --- flake.nix | 4 +- modules/blocks/authelia.nix | 365 ----------------------------- modules/blocks/nginx.nix | 30 +-- modules/blocks/sso.nix | 375 ++++++++++++++++++++++++++++++ modules/services/arr.nix | 6 +- modules/services/deluge.nix | 4 +- modules/services/hledger.nix | 10 +- modules/services/jellyfin.nix | 8 +- modules/services/vaultwarden.nix | 12 +- test/modules/arr.nix | 10 +- test/vm/nextcloud.nix | 2 +- test/vm/{authelia.nix => sso.nix} | 5 +- 12 files changed, 421 insertions(+), 410 deletions(-) delete mode 100644 modules/blocks/authelia.nix create mode 100644 modules/blocks/sso.nix rename test/vm/{authelia.nix => sso.nix} (96%) diff --git a/flake.nix b/flake.nix index 37607ed..bd42ed5 100644 --- a/flake.nix +++ b/flake.nix @@ -28,7 +28,6 @@ }; allModules = [ - modules/blocks/authelia.nix modules/blocks/backup.nix modules/blocks/davfs.nix modules/blocks/ldap.nix @@ -36,6 +35,7 @@ modules/blocks/nginx.nix modules/blocks/postgresql.nix modules/blocks/ssl.nix + modules/blocks/sso.nix modules/blocks/tinyproxy.nix modules/blocks/vpn.nix @@ -91,7 +91,7 @@ ]); }; } - // (vm_test "authelia" ./test/vm/authelia.nix) + // (vm_test "sso" ./test/vm/sso.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 deleted file mode 100644 index 27ba9fb..0000000 --- a/modules/blocks/authelia.nix +++ /dev/null @@ -1,365 +0,0 @@ -{ config, pkgs, lib, ... }: - -let - cfg = config.shb.authelia; - - 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 = { - enable = lib.mkEnableOption "selfhostblocks.authelia"; - - subdomain = lib.mkOption { - type = lib.types.str; - description = "Subdomain under which Authelia will be served."; - example = "auth"; - }; - - domain = lib.mkOption { - type = lib.types.str; - description = "domain under which Authelia will be served."; - example = "mydomain.com"; - }; - - ldapEndpoint = lib.mkOption { - type = lib.types.str; - description = "Endpoint for LDAP authentication backend."; - example = "ldap.example.com"; - }; - - dcdomain = lib.mkOption { - type = lib.types.str; - description = "dc domain for ldap."; - example = "dc=mydomain,dc=com"; - }; - - autheliaUser = lib.mkOption { - type = lib.types.str; - description = "System user for this Authelia instance."; - default = "authelia"; - }; - - secrets = lib.mkOption { - description = "Secrets needed by Authelia"; - type = lib.types.submodule { - options = { - jwtSecretFile = lib.mkOption { - type = lib.types.path; - description = "File containing the JWT secret."; - }; - ldapAdminPasswordFile = lib.mkOption { - type = lib.types.path; - description = "File containing the LDAP admin user password."; - }; - sessionSecretFile = lib.mkOption { - type = lib.types.path; - description = "File containing the session secret."; - }; - storageEncryptionKeyFile = lib.mkOption { - type = lib.types.path; - description = "File containing the storage encryption key."; - }; - identityProvidersOIDCHMACSecretFile = lib.mkOption { - type = lib.types.path; - description = "File containing the identity provider OIDC HMAC secret."; - }; - identityProvidersOIDCIssuerPrivateKeyFile = lib.mkOption { - type = lib.types.path; - description = "File containing the identity provider OIDC issuer private key."; - }; - }; - }; - }; - - oidcClients = lib.mkOption { - type = lib.types.listOf lib.types.anything; - description = "OIDC clients"; - default = []; - }; - - 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 { - type = lib.types.listOf lib.types.anything; - description = "Rule based clients"; - default = []; - }; - }; - - 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} = {}; - users.${autheliaCfg.user} = { - isSystemUser = true; - group = autheliaCfg.user; - }; - }; - - services.authelia.instances.${fqdn} = { - enable = true; - user = cfg.autheliaUser; - - secrets = { - inherit (cfg.secrets) jwtSecretFile storageEncryptionKeyFile; - }; - # See https://www.authelia.com/configuration/methods/secrets/ - environmentVariables = { - 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 = 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"; - server.port = 9091; - - # Inspired from https://github.com/lldap/lldap/blob/7d1f5abc137821c500de99c94f7579761fc949d8/example_configs/authelia_config.yml - authentication_backend = { - refresh_interval = "5m"; - password_reset = { - disable = "false"; - }; - ldap = { - implementation = "custom"; - url = cfg.ldapEndpoint; - timeout = "5s"; - start_tls = "false"; - base_dn = cfg.dcdomain; - username_attribute = "uid"; - additional_users_dn = "ou=people"; - # Sign in with username or email. - users_filter = "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))"; - additional_groups_dn = "ou=groups"; - groups_filter = "(member={dn})"; - group_name_attribute = "cn"; - mail_attribute = "mail"; - display_name_attribute = "displayName"; - user = "uid=admin,ou=people,${cfg.dcdomain}"; - }; - }; - totp = { - disable = "false"; - issuer = fqdn; - algorithm = "sha1"; - digits = "6"; - period = "30"; - skew = "1"; - secret_size = "32"; - }; - # Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis - session = { - name = "authelia_session"; - domain = cfg.domain; - same_site = "lax"; - expiration = "1h"; - inactivity = "5m"; - remember_me_duration = "1M"; - redis = { - host = config.services.redis.servers.authelia.unixSocket; - port = 0; - }; - }; - storage = { - postgres = { - host = "/run/postgresql"; - username = autheliaCfg.user; - database = autheliaCfg.user; - port = config.services.postgresql.port; - # Uses peer auth for local users, so we don't need a password. - password = "test"; - }; - }; - notifier = { - 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"; - }; - }; - access_control = { - default_policy = "deny"; - networks = [ - { - name = "internal"; - networks = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/18" ]; - } - ]; - rules = [ - { - domain = fqdn; - policy = "bypass"; - resources = [ - "^/api/.*" - ]; - } - ] ++ cfg.rules; - }; - telemetry = { - metrics = { - enabled = true; - address = "tcp://127.0.0.1:9959"; - }; - }; - }; - - 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} = { - 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 = '' - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Content-Type-Options nosniff; - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - - proxy_set_header Host $host; - proxy_set_header X-Real-IP $remote_addr; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_http_version 1.1; - proxy_set_header Upgrade $http_upgrade; - proxy_set_header Connection "upgrade"; - proxy_cache_bypass $http_upgrade; - - proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port}; - proxy_intercept_errors on; - if ($request_method !~ ^(POST)$){ - error_page 401 = /error/401; - error_page 403 = /error/403; - error_page 404 = /error/404; - } - ''; - - locations."/api/verify".extraConfig = '' - add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; - add_header X-Content-Type-Options nosniff; - add_header X-Frame-Options "SAMEORIGIN"; - add_header X-XSS-Protection "1; mode=block"; - add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"; - add_header X-Download-Options noopen; - add_header X-Permitted-Cross-Domain-Policies none; - - proxy_set_header Host $http_x_forwarded_host; - proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port}; - ''; - }; - - services.redis.servers.authelia = { - enable = true; - user = autheliaCfg.user; - }; - - shb.postgresql.ensures = [ - { - username = autheliaCfg.user; - database = autheliaCfg.user; - } - ]; - - services.prometheus.scrapeConfigs = [ - { - job_name = "authelia"; - static_configs = [ - { - targets = ["127.0.0.1:9959"]; - } - ]; - } - ]; - }; -} diff --git a/modules/blocks/nginx.nix b/modules/blocks/nginx.nix index 9052b14..ff03aa8 100644 --- a/modules/blocks/nginx.nix +++ b/modules/blocks/nginx.nix @@ -5,17 +5,17 @@ let fqdn = c: "${c.subdomain}.${c.domain}"; - autheliaConfig = lib.types.submodule { + ssoConfig = lib.types.submodule { options = { subdomain = lib.mkOption { type = lib.types.str; - description = "Subdomain which must be protected."; + description = "Subdomain part of the fqdn that must be protected."; example = "subdomain"; }; domain = lib.mkOption { type = lib.types.str; - description = "Domain of the subdomain."; + description = "Domain part of the fqdn that must be protected."; example = "mydomain.com"; }; @@ -23,7 +23,7 @@ let type = lib.types.str; description = "Auth endpoint for SSO."; default = null; - example = "https://authelia.example.com"; + example = "https://auth.example.com"; }; upstream = lib.mkOption { @@ -32,9 +32,9 @@ let example = "http://127.0.0.1:1234"; }; - autheliaRules = lib.mkOption { + ssoRules = lib.mkOption { type = lib.types.listOf (lib.types.attrsOf lib.types.anything); - description = "Authelia rule configuration"; + description = "SSO rules configuration"; example = lib.literalExpression ''[{ policy = "two_factor"; subject = ["group:service_user"]; @@ -59,9 +59,9 @@ in example = true; }; - autheliaProtect = lib.mkOption { - description = "Endpoints to be protected by authelia."; - type = lib.types.listOf autheliaConfig; + ssoProtect = lib.mkOption { + description = "Endpoints to be protected by SSO."; + type = lib.types.listOf ssoConfig; default = []; }; }; @@ -125,7 +125,7 @@ in proxy_set_header Connection "upgrade"; proxy_cache_bypass $http_upgrade; - auth_request /authelia; + auth_request /sso; auth_request_set $user $upstream_http_remote_user; auth_request_set $groups $upstream_http_remote_groups; proxy_set_header X-Forwarded-User $user; @@ -150,7 +150,7 @@ in ''; # Virtual endpoint created by nginx to forward auth requests. - locations."/authelia".extraConfig = '' + locations."/sso".extraConfig = '' internal; proxy_pass ${c.authEndpoint}/api/verify; @@ -172,13 +172,13 @@ in }; }; in - lib.mkMerge (map vhostCfg cfg.autheliaProtect); + lib.mkMerge (map vhostCfg cfg.ssoProtect); - shb.authelia.rules = + shb.sso.rules = let - authConfig = c: map (r: r // { domain = fqdn c; }) c.autheliaRules; + authConfig = c: map (r: r // { domain = fqdn c; }) c.ssoRules; in - lib.flatten (map authConfig cfg.autheliaProtect); + lib.flatten (map authConfig cfg.ssoProtect); security.acme.defaults.reloadServices = [ "nginx.service" diff --git a/modules/blocks/sso.nix b/modules/blocks/sso.nix new file mode 100644 index 0000000..5ef9d2d --- /dev/null +++ b/modules/blocks/sso.nix @@ -0,0 +1,375 @@ +{ config, pkgs, lib, ... }: + +let + cfg = config.shb.sso; + + fqdn = "${cfg.subdomain}.${cfg.domain}"; + + 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.sso = { + enable = lib.mkEnableOption "SSO block"; + + backend = lib.mkOption { + type = lib.types.enum [ "authelia" ]; + description = "Backend to use for SSO."; + default = "authelia"; + }; + + subdomain = lib.mkOption { + type = lib.types.str; + description = "Subdomain under which SSO will be served."; + example = "auth"; + }; + + domain = lib.mkOption { + type = lib.types.str; + description = "domain under which SSO will be served."; + example = "mydomain.com"; + }; + + ldapEndpoint = lib.mkOption { + type = lib.types.str; + description = "Endpoint for LDAP authentication backend."; + example = "ldap.example.com"; + }; + + dcdomain = lib.mkOption { + type = lib.types.str; + description = "dc domain for ldap."; + example = "dc=mydomain,dc=com"; + }; + + unixUser = lib.mkOption { + type = lib.types.str; + description = "System user for this SSO instance."; + default = "sso"; + }; + + secrets = lib.mkOption { + description = "Secrets needed by the SSO instance."; + type = lib.types.submodule { + options = { + jwtSecretFile = lib.mkOption { + type = lib.types.path; + description = "File containing the JWT secret."; + }; + ldapAdminPasswordFile = lib.mkOption { + type = lib.types.path; + description = "File containing the LDAP admin user password."; + }; + sessionSecretFile = lib.mkOption { + type = lib.types.path; + description = "File containing the session secret."; + }; + storageEncryptionKeyFile = lib.mkOption { + type = lib.types.path; + description = "File containing the storage encryption key."; + }; + identityProvidersOIDCHMACSecretFile = lib.mkOption { + type = lib.types.path; + description = "File containing the identity provider OIDC HMAC secret."; + }; + identityProvidersOIDCIssuerPrivateKeyFile = lib.mkOption { + type = lib.types.path; + description = "File containing the identity provider OIDC issuer private key."; + }; + }; + }; + }; + + oidcClients = lib.mkOption { + type = lib.types.listOf lib.types.anything; + description = "OIDC clients"; + default = []; + }; + + 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 = "sso@mydomain.com"; + }; + from_name = lib.mkOption { + type = lib.types.str; + description = "SMTP name from which the emails originate."; + default = "SSO"; + }; + 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 { + type = lib.types.listOf lib.types.anything; + description = "Rule based clients"; + default = []; + }; + }; + + config = { + autheliaBackend = + let + autheliaCfg = config.services.authelia.instances.${fqdn}; + in + { + 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} = {}; + users.${autheliaCfg.user} = { + isSystemUser = true; + group = autheliaCfg.user; + }; + }; + + services.authelia.instances.${fqdn} = { + enable = true; + user = cfg.unixUser; + + secrets = { + inherit (cfg.secrets) jwtSecretFile storageEncryptionKeyFile; + }; + # See https://www.authelia.com/configuration/methods/secrets/ + environmentVariables = { + 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 = 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"; + server.port = 9091; + + # Inspired from https://github.com/lldap/lldap/blob/7d1f5abc137821c500de99c94f7579761fc949d8/example_configs/authelia_config.yml + authentication_backend = { + refresh_interval = "5m"; + password_reset = { + disable = "false"; + }; + ldap = { + implementation = "custom"; + url = cfg.ldapEndpoint; + timeout = "5s"; + start_tls = "false"; + base_dn = cfg.dcdomain; + username_attribute = "uid"; + additional_users_dn = "ou=people"; + # Sign in with username or email. + users_filter = "(&(|({username_attribute}={input})({mail_attribute}={input}))(objectClass=person))"; + additional_groups_dn = "ou=groups"; + groups_filter = "(member={dn})"; + group_name_attribute = "cn"; + mail_attribute = "mail"; + display_name_attribute = "displayName"; + user = "uid=admin,ou=people,${cfg.dcdomain}"; + }; + }; + totp = { + disable = "false"; + issuer = fqdn; + algorithm = "sha1"; + digits = "6"; + period = "30"; + skew = "1"; + secret_size = "32"; + }; + # Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis + session = { + name = "authelia_session"; + domain = cfg.domain; + same_site = "lax"; + expiration = "1h"; + inactivity = "5m"; + remember_me_duration = "1M"; + redis = { + host = config.services.redis.servers.authelia.unixSocket; + port = 0; + }; + }; + storage = { + postgres = { + host = "/run/postgresql"; + username = autheliaCfg.user; + database = autheliaCfg.user; + port = config.services.postgresql.port; + # Uses peer auth for local users, so we don't need a password. + password = "test"; + }; + }; + notifier = { + 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"; + }; + }; + access_control = { + default_policy = "deny"; + networks = [ + { + name = "internal"; + networks = [ "10.0.0.0/8" "172.16.0.0/12" "192.168.0.0/18" ]; + } + ]; + rules = [ + { + domain = fqdn; + policy = "bypass"; + resources = [ + "^/api/.*" + ]; + } + ] ++ cfg.rules; + }; + telemetry = { + metrics = { + enabled = true; + address = "tcp://127.0.0.1:9959"; + }; + }; + }; + + 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} = { + 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 = '' + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options nosniff; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_cache_bypass $http_upgrade; + + proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port}; + proxy_intercept_errors on; + if ($request_method !~ ^(POST)$){ + error_page 401 = /error/401; + error_page 403 = /error/403; + error_page 404 = /error/404; + } + ''; + + locations."/api/verify".extraConfig = '' + add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; + add_header X-Content-Type-Options nosniff; + add_header X-Frame-Options "SAMEORIGIN"; + add_header X-XSS-Protection "1; mode=block"; + add_header X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"; + add_header X-Download-Options noopen; + add_header X-Permitted-Cross-Domain-Policies none; + + proxy_set_header Host $http_x_forwarded_host; + proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port}; + ''; + }; + + services.redis.servers.authelia = { + enable = true; + user = autheliaCfg.user; + }; + + shb.postgresql.ensures = [ + { + username = autheliaCfg.user; + database = autheliaCfg.user; + } + ]; + + services.prometheus.scrapeConfigs = [ + { + job_name = "authelia"; + static_configs = [ + { + targets = ["127.0.0.1:9959"]; + } + ]; + } + ]; + }; + }.${cfg.backend}; +} diff --git a/modules/services/arr.nix b/modules/services/arr.nix index f995252..8d28785 100644 --- a/modules/services/arr.nix +++ b/modules/services/arr.nix @@ -162,7 +162,7 @@ let type = lib.types.nullOr lib.types.str; default = null; description = "Endpoint to the SSO provider. Leave null to not have SSO configured."; - example = "https://authelia.example.com"; + example = "https://auth.example.com"; }; backupCfg = lib.mkOption { @@ -297,7 +297,7 @@ config.xml" templatedSettings) "${config.services.radarr.dataDir}/config.xml" ( in lib.mkIf cfg.jackett.enable t; - shb.nginx.autheliaProtect = + shb.nginx.ssoProtect = let appProtectConfig = name: _defaults: let @@ -306,7 +306,7 @@ config.xml" templatedSettings) "${config.services.radarr.dataDir}/config.xml" ( lib.mkIf (c.authEndpoint != null) { inherit (c) subdomain domain authEndpoint; upstream = "http://127.0.0.1:${toString c.port}"; - autheliaRules = [ + ssoRules = [ { domain = "${c.subdomain}.${c.domain}"; policy = "bypass"; diff --git a/modules/services/deluge.nix b/modules/services/deluge.nix index 064e522..bedb04c 100644 --- a/modules/services/deluge.nix +++ b/modules/services/deluge.nix @@ -143,7 +143,7 @@ in authEndpoint = lib.mkOption { type = lib.types.str; description = "OIDC endpoint for SSO"; - example = "https://authelia.example.com"; + example = "https://auth.example.com"; }; sopsFile = lib.mkOption { @@ -254,7 +254,7 @@ in restartUnits = [ "deluged.service" "delugeweb.service" ]; }; - shb.nginx.autheliaProtect = lib.mkIf config.shb.authelia.enable [ + shb.nginx.ssoProtect = lib.mkIf config.shb.sso.enable [ { inherit (cfg) subdomain domain authEndpoint; upstream = "http://127.0.0.1:${toString config.services.deluge.web.port}"; diff --git a/modules/services/hledger.nix b/modules/services/hledger.nix index ce43f96..012331e 100644 --- a/modules/services/hledger.nix +++ b/modules/services/hledger.nix @@ -11,13 +11,13 @@ in subdomain = lib.mkOption { type = lib.types.str; - description = "Subdomain under which Authelia will be served."; + description = "Subdomain under which Hledger will be served."; example = "ha"; }; domain = lib.mkOption { type = lib.types.str; - description = "domain under which Authelia will be served."; + description = "domain under which Hledger will be served."; example = "mydomain.com"; }; @@ -37,7 +37,7 @@ in authEndpoint = lib.mkOption { type = lib.types.str; description = "OIDC endpoint for SSO"; - example = "https://authelia.example.com"; + example = "https://auth.example.com"; }; }; @@ -72,11 +72,11 @@ in serviceConfig.StateDirectory = "hledger"; }; - shb.nginx.autheliaProtect = [ + shb.nginx.ssoProtect = [ { inherit (cfg) subdomain domain authEndpoint; upstream = "http://${toString config.services.hledger-web.host}:${toString config.services.hledger-web.port}"; - autheliaRules = [{ + ssoRules = [{ domain = fqdn; policy = "two_factor"; subject = ["group:hledger_user"]; diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index 078a43b..a8325ee 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -54,13 +54,13 @@ in oidcProvider = lib.mkOption { type = lib.types.str; description = "OIDC provider name"; - default = "Authelia"; + default = "SSO"; }; authEndpoint = lib.mkOption { type = lib.types.str; description = "OIDC endpoint for SSO"; - example = "https://authelia.example.com"; + example = "https://auth.example.com"; }; oidcClientID = lib.mkOption { @@ -368,7 +368,7 @@ in } + template brandingConfig "/var/lib/jellyfin/config/branding.xml" {"%a%" = "%a%";}; - shb.authelia.oidcClients = [ + shb.sso.oidcClients = [ { id = cfg.oidcClientID; description = "Jellyfin"; @@ -382,7 +382,7 @@ in inherit (cfg) sopsFile; key = "jellyfin/sso_secret"; mode = "0400"; - owner = config.shb.authelia.autheliaUser; + owner = config.shb.sso.unixUser; }; # For backup diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 2e57bff..79efe99 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -23,13 +23,13 @@ in subdomain = lib.mkOption { type = lib.types.str; - description = "Subdomain under which Authelia will be served."; + description = "Subdomain under which Vaultwarden will be served."; example = "ha"; }; domain = lib.mkOption { type = lib.types.str; - description = "domain under which Authelia will be served."; + description = "domain under which Vaultwarden will be served."; example = "mydomain.com"; }; @@ -48,7 +48,7 @@ in authEndpoint = lib.mkOption { type = lib.types.str; description = "OIDC endpoint for SSO"; - example = "https://authelia.example.com"; + example = "https://auth.example.com"; }; databasePasswordFile = lib.mkOption { @@ -162,11 +162,11 @@ in "%SMTP_PASSWORD%" = "$(cat ${cfg.smtp.passwordFile})"; }; - shb.nginx.autheliaProtect = [ + shb.nginx.ssoProtect = [ { inherit (cfg) subdomain domain authEndpoint; upstream = "http://127.0.0.1:${toString config.services.vaultwarden.config.ROCKET_PORT}"; - autheliaRules = [ + ssoRules = [ { domain = "${fqdn}"; policy = "two_factor"; @@ -175,7 +175,7 @@ in "^/admin" ]; } - # There's no way to protect the webapp using Authelia this way, see + # There's no way to protect the webapp using SSO this way, see # https://github.com/dani-garcia/vaultwarden/discussions/3188 { domain = fqdn; diff --git a/test/modules/arr.nix b/test/modules/arr.nix index 60a3677..7044113 100644 --- a/test/modules/arr.nix +++ b/test/modules/arr.nix @@ -42,7 +42,7 @@ in systemd.services.radarr = {}; systemd.services.jackett = {}; shb.backup = {}; - shb.nginx.autheliaProtect = []; + shb.nginx.ssoProtect = []; users.users = {}; services.bazarr = {}; services.jackett = {}; @@ -67,9 +67,9 @@ in "d '/var/lib/radarr' 0750 radarr radarr - -" ]; shb.backup = {}; - shb.nginx.autheliaProtect = [ + shb.nginx.ssoProtect = [ { - autheliaRules = [ + ssoRules = [ { domain = "radarr.example.com"; policy = "bypass"; @@ -140,9 +140,9 @@ in excludePatterns = [ ".db-shm" ".db-wal" ".mono" ]; }; }; - shb.nginx.autheliaProtect = [ + shb.nginx.ssoProtect = [ { - autheliaRules = [ + ssoRules = [ { domain = "radarr.example.com"; policy = "bypass"; diff --git a/test/vm/nextcloud.nix b/test/vm/nextcloud.nix index 4df22e5..a5f1cc2 100644 --- a/test/vm/nextcloud.nix +++ b/test/vm/nextcloud.nix @@ -18,7 +18,7 @@ in shb.backup = lib.mkOption { type = lib.types.anything; }; }; } - # ../../modules/blocks/authelia.nix + # ../../modules/blocks/sso.nix # ../../modules/blocks/ldap.nix ../../modules/services/nextcloud-server.nix ]; diff --git a/test/vm/authelia.nix b/test/vm/sso.nix similarity index 96% rename from test/vm/authelia.nix rename to test/vm/sso.nix index 28257ee..77d983e 100644 --- a/test/vm/authelia.nix +++ b/test/vm/sso.nix @@ -14,7 +14,7 @@ in shb.backup = lib.mkOption { type = lib.types.anything; }; }; } - ../../modules/blocks/authelia.nix + ../../modules/blocks/sso.nix ../../modules/blocks/ldap.nix ../../modules/blocks/postgresql.nix ]; @@ -28,8 +28,9 @@ in jwtSecretFile = pkgs.writeText "jwt_secret" "securejwtsecret"; }; - shb.authelia = { + shb.sso = { enable = true; + backend = "authelia"; subdomain = "authelia"; domain = "example.com"; ldapEndpoint = "ldap://127.0.0.1:${builtins.toString config.shb.ldap.ldapPort}";