diff --git a/CHANGELOG.md b/CHANGELOG.md index 81c7d4b..98f0fa7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ - Bump Nextcloud default version from 27 to 28. Add support for version 29. - Deluge config breaks the authFile into an attrset of user to password file. Also deluge has tests now. - Nextcloud now configures the LDAP app to use the `user_id` from LLDAP as the user ID used in Nextcloud. This makes all source of user - internal, LDAP and SSO - agree on the user ID. +- Authelia options changed: + - `shb.authelia.oidcClients.id` -> `shb.authelia.oidcClients.client_id` + - `shb.authelia.oidcClients.description` -> `shb.authelia.oidcClients.client_name` + - `shb.authelia.oidcClients.secret` -> `shb.authelia.oidcClients.client_secret` +- Vaultwarden data folder changed to `/var/lib/vaultwarden`. ## User Facing Backwards Compatible Changes diff --git a/docs/contributing.md b/docs/contributing.md index d26e794..a3e4220 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -21,8 +21,6 @@ gets updated. I intend to upstream to nixpkgs as much of those as makes sense. Run all tests: ```bash -$ nix build .#checks.${system}.all -# or $ nix flake check # or $ nix run github:Mic92/nix-fast-build -- --skip-cached --flake ".#checks.$(nix eval --raw --impure --expr builtins.currentSystem)" diff --git a/flake.lock b/flake.lock index 5431f5e..ab10c2b 100644 --- a/flake.lock +++ b/flake.lock @@ -35,11 +35,11 @@ }, "nixpkgs": { "locked": { - "lastModified": 1716769173, - "narHash": "sha256-7EXDb5WBw+d004Agt+JHC/Oyh/KTUglOaQ4MNjBbo5w=", + "lastModified": 1724819573, + "narHash": "sha256-GnR7/ibgIH1vhoy8cYdmXE6iyZqKqFxQSVkFgosBh6w=", "owner": "nixos", "repo": "nixpkgs", - "rev": "9ca3f649614213b2aaf5f1e16ec06952fe4c2632", + "rev": "71e91c409d1e654808b2621f28a327acfdad8dc2", "type": "github" }, "original": { diff --git a/flake.nix b/flake.nix index fd69f8a..41b831b 100644 --- a/flake.nix +++ b/flake.nix @@ -15,14 +15,11 @@ let originPkgs = nixpkgs.legacyPackages.${system}; patches = [ - (originPkgs.fetchpatch { - url = "https://patch-diff.githubusercontent.com/raw/NixOS/nixpkgs/pull/315018.patch"; - hash = "sha256-8jcGyO/d+htfv/ZajxXh89S3OiDZAr7/fsWC1JpGczM="; - }) - (originPkgs.fetchpatch { - url = "https://github.com/NixOS/nixpkgs/pull/317107.patch"; - hash = "sha256-hoLrqV7XtR1hP/m0rV9hjYUBtrSjay0qcPUYlKKuVWk="; - }) + # Leaving commented out for an example. + # (originPkgs.fetchpatch { + # url = "https://github.com/NixOS/nixpkgs/pull/317107.patch"; + # hash = "sha256-hoLrqV7XtR1hP/m0rV9hjYUBtrSjay0qcPUYlKKuVWk="; + # }) ]; patchedNixpkgs = originPkgs.applyPatches { name = "nixpkgs-patched"; @@ -99,10 +96,6 @@ shblib = pkgs.callPackage ./lib {}; in (rec { - all = mergeTests [ - modules - ]; - modules = shblib.check { inherit pkgs; tests = diff --git a/modules/blocks/authelia.nix b/modules/blocks/authelia.nix index 5669fa8..4f9acf5 100644 --- a/modules/blocks/authelia.nix +++ b/modules/blocks/authelia.nix @@ -41,8 +41,8 @@ in ldapEndpoint = lib.mkOption { type = lib.types.str; - description = "Endpoint for LDAP authentication backend."; - example = "ldap.example.com"; + description = "Endpoint of the LDAP authentication backend."; + example = "ldap://ldap.example.com:389"; }; dcdomain = lib.mkOption { @@ -97,9 +97,9 @@ in description = "OIDC clients"; default = [ { - id = "dummy_client"; - description = "Dummy Client so Authelia can start"; - secret.source = pkgs.writeText "dummy.secret" "dummy_client_secret"; + client_id = "dummy_client"; + client_name = "Dummy Client so Authelia can start"; + client_secret.source = pkgs.writeText "dummy.secret" "dummy_client_secret"; public = false; authorization_policy = "one_factor"; redirect_uris = []; @@ -109,20 +109,33 @@ in freeformType = lib.types.attrsOf lib.types.anything; options = { - id = lib.mkOption { + client_id = lib.mkOption { type = lib.types.str; description = "Unique identifier of the OIDC client."; }; - description = lib.mkOption { + client_name = lib.mkOption { type = lib.types.nullOr lib.types.str; description = "Human readable description of the OIDC client."; default = null; }; - secret = lib.mkOption { + client_secret = lib.mkOption { type = shblib.secretFileType; - description = "File containing the shared secret with the OIDC client."; + description = '' + File containing the shared secret with the OIDC client. + + Generate with: + + ``` + nix run nixpkgs#authelia -- \ + crypto hash generate pbkdf2 \ + --variant sha512 \ + --random \ + --random.length 72 \ + --random.charset rfc3986 + ``` + ''; }; public = lib.mkOption { @@ -278,8 +291,7 @@ in AUTHELIA_NOTIFIER_SMTP_PASSWORD_FILE = lib.mkIf (!(builtins.isString cfg.smtp)) (toString cfg.smtp.passwordFile); }; settings = { - server.host = "127.0.0.1"; - server.port = 9091; + server.address = "tcp://127.0.0.1:9091"; # Inspired from https://github.com/lldap/lldap/blob/7d1f5abc137821c500de99c94f7579761fc949d8/example_configs/authelia_config.yml authentication_backend = { @@ -289,20 +301,22 @@ in }; ldap = { implementation = "custom"; - url = cfg.ldapEndpoint; + address = 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}"; + attributes = { + username = "uid"; + group_name = "cn"; + mail = "mail"; + display_name = "displayName"; + }; }; }; totp = { @@ -317,11 +331,14 @@ in # Inspired from https://www.authelia.com/configuration/session/introduction/ and https://www.authelia.com/configuration/session/redis session = { name = "authelia_session"; - domain = if isNull cfg.port then cfg.domain else "${cfg.domain}:${toString cfg.port}"; + cookies = [{ + domain = if isNull cfg.port then cfg.domain else "${cfg.domain}:${toString cfg.port}"; + authelia_url = "https://${cfg.subdomain}.${cfg.domain}"; + }]; same_site = "lax"; expiration = "1h"; inactivity = "5m"; - remember_me_duration = "1M"; + remember_me = "1M"; redis = { host = config.services.redis.servers.authelia.unixSocket; port = 0; @@ -329,10 +346,9 @@ in }; storage = { postgres = { - host = "/run/postgresql"; + address = "unix:///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"; }; @@ -416,7 +432,7 @@ in proxy_set_header Connection "upgrade"; proxy_cache_bypass $http_upgrade; - proxy_pass http://127.0.0.1:${toString autheliaCfg.settings.server.port}; + proxy_pass http://127.0.0.1:9091; proxy_intercept_errors on; if ($request_method !~ ^(POST)$){ error_page 401 = /error/401; @@ -435,7 +451,7 @@ in 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}; + proxy_pass http://127.0.0.1:9091; ''; }; diff --git a/modules/blocks/monitoring.nix b/modules/blocks/monitoring.nix index ca2630f..f59c4a6 100644 --- a/modules/blocks/monitoring.nix +++ b/modules/blocks/monitoring.nix @@ -288,6 +288,14 @@ in hash = "sha256-79hK7axHf6soku5DvdXkE/0K4WKc4pnS9VMbVc1FS2I="; }; + subPackages = [ + "cmd/loki" + "cmd/loki-canary" + "clients/cmd/promtail" + "cmd/logcli" + # Removes "cmd/lokitool" + ]; + ldflags = let t = "github.com/grafana/loki/pkg/util/build"; in [ "-s" "-w" diff --git a/modules/services/audiobookshelf.nix b/modules/services/audiobookshelf.nix index 969a87b..ffa06f5 100644 --- a/modules/services/audiobookshelf.nix +++ b/modules/services/audiobookshelf.nix @@ -152,9 +152,9 @@ in shb.authelia.oidcClients = [ { - id = cfg.oidcClientID; - description = "Audiobookshelf"; - secret.source = cfg.ssoSecretFile; + client_id = cfg.oidcClientID; + client_name = "Audiobookshelf"; + client_secret.source = cfg.ssoSecretFile; public = false; authorization_policy = "one_factor"; redirect_uris = [ diff --git a/modules/services/jellyfin.nix b/modules/services/jellyfin.nix index 3a9511c..c5cda7a 100644 --- a/modules/services/jellyfin.nix +++ b/modules/services/jellyfin.nix @@ -415,9 +415,9 @@ in shb.authelia.oidcClients = lib.lists.optionals (!(isNull cfg.sso)) [ { - id = cfg.sso.clientID; - description = "Jellyfin"; - secret.source = cfg.sso.secretFile; + client_id = cfg.sso.clientID; + client_name = "Jellyfin"; + client_secret.source = cfg.sso.secretFile; public = false; authorization_policy = "one_factor"; redirect_uris = [ "https://${cfg.subdomain}.${cfg.domain}/sso/OID/r/${cfg.sso.provider}" ]; diff --git a/modules/services/nextcloud-server.nix b/modules/services/nextcloud-server.nix index e7b6917..ee0208b 100644 --- a/modules/services/nextcloud-server.nix +++ b/modules/services/nextcloud-server.nix @@ -977,9 +977,9 @@ in shb.authelia.oidcClients = lib.mkIf (cfg.apps.sso.provider == "Authelia") [ { - id = cfg.apps.sso.clientID; - description = "Nextcloud"; - secret.source = cfg.apps.sso.secretFileForAuthelia; + client_id = cfg.apps.sso.clientID; + client_name = "Nextcloud"; + client_secret.source = cfg.apps.sso.secretFileForAuthelia; public = false; authorization_policy = cfg.apps.sso.authorization_policy; redirect_uris = [ "${protocol}://${fqdnWithPort}/apps/oidc_login/oidc" ]; diff --git a/modules/services/vaultwarden.nix b/modules/services/vaultwarden.nix index 3117148..391f36b 100644 --- a/modules/services/vaultwarden.nix +++ b/modules/services/vaultwarden.nix @@ -8,7 +8,7 @@ let fqdn = "${cfg.subdomain}.${cfg.domain}"; - dataFolder = "/var/lib/bitwarden_rs"; + dataFolder = "/var/lib/vaultwarden"; in { options.shb.vaultwarden = { @@ -152,7 +152,6 @@ in enable = true; dbBackend = "postgresql"; config = { - DATA_FOLDER = dataFolder; IP_HEADER = "X-Real-IP"; SIGNUPS_ALLOWED = false; # Disabled because the /admin path is protected by SSO @@ -182,6 +181,8 @@ in "d ${dataFolder} 0750 vaultwarden vaultwarden" "f ${dataFolder}/vaultwarden.env 0640 vaultwarden vaultwarden" ]; + # Needed to be able to write template config. + systemd.services.vaultwarden.serviceConfig.ProtectHome = lib.mkForce false; systemd.services.vaultwarden.preStart = shblib.replaceSecrets { userConfig = { diff --git a/test/blocks/authelia.nix b/test/blocks/authelia.nix index d1c9df0..07b6928 100644 --- a/test/blocks/authelia.nix +++ b/test/blocks/authelia.nix @@ -17,11 +17,21 @@ in ../../modules/blocks/postgresql.nix ]; + networking.hosts = { + "127.0.0.1" = [ + "machine.com" + "client1.machine.com" + "client2.machine.com" + "ldap.machine.com" + "authelia.machine.com" + ]; + }; + shb.ldap = { enable = true; dcdomain = "dc=example,dc=com"; subdomain = "ldap"; - domain = "machine"; + domain = "machine.com"; ldapUserPasswordFile = pkgs.writeText "user_password" ldapAdminPassword; jwtSecretFile = pkgs.writeText "jwt_secret" "securejwtsecret"; }; @@ -29,8 +39,8 @@ in shb.authelia = { enable = true; subdomain = "authelia"; - domain = "machine"; - ldapEndpoint = "ldap://127.0.0.1:${builtins.toString config.shb.ldap.ldapPort}"; + domain = "machine.com"; + ldapEndpoint = "ldap://${config.shb.ldap.subdomain}.${config.shb.ldap.domain}:${toString config.shb.ldap.ldapPort}"; dcdomain = config.shb.ldap.dcdomain; secrets = { jwtSecretFile = pkgs.writeText "jwtSecretFile" "jwtSecretFile"; @@ -45,20 +55,20 @@ in oidcClients = [ { - id = "client1"; - description = "My Client 1"; - secret.source = pkgs.writeText "secret" "mysecuresecret"; + client_id = "client1"; + client_name = "My Client 1"; + client_secret.source = pkgs.writeText "secret" "$pbkdf2-sha512$310000$LR2wY11djfLrVQixdlLJew$rPByqFt6JfbIIAITxzAXckwh51QgV8E5YZmA8rXOzkMfBUcMq7cnOKEXF6MAFbjZaGf3J/B1OzLWZTCuZtALVw"; public = false; authorization_policy = "one_factor"; - redirect_uris = [ "http://client1.machine/redirect" ]; + redirect_uris = [ "http://client1.machine.com/redirect" ]; } { - id = "client2"; - description = "My Client 2"; - secret.source = pkgs.writeText "secret" "myothersecret"; + client_id = "client2"; + client_name = "My Client 2"; + client_secret.source = pkgs.writeText "secret" "$pbkdf2-sha512$310000$76EqVU1N9K.iTOvD4WJ6ww$hqNJU.UHphiCjMChSqk27lUTjDqreuMuyV/u39Esc6HyiRXp5Ecx89ypJ5M0xk3Na97vbgDpwz7il5uwzQ4bfw"; public = false; authorization_policy = "one_factor"; - redirect_uris = [ "http://client2.machine/redirect" ]; + redirect_uris = [ "http://client2.machine.com/redirect" ]; } ]; }; @@ -69,17 +79,17 @@ in start_all() machine.wait_for_unit("lldap.service") - machine.wait_for_unit("authelia-authelia.machine.service") - machine.wait_for_open_port(${toString nodes.machine.services.authelia.instances."authelia.machine".settings.server.port}) + machine.wait_for_unit("authelia-authelia.machine.com.service") + machine.wait_for_open_port(9091) - endpoints = json.loads(machine.succeed("curl -s http://machine/.well-known/openid-configuration")) + endpoints = json.loads(machine.succeed("curl -s http://machine.com/.well-known/openid-configuration")) auth_endpoint = endpoints['authorization_endpoint'] machine.succeed( "curl -f -s '" + auth_endpoint + "?client_id=other" - + "&redirect_uri=http://client1.machine/redirect" + + "&redirect_uri=http://client1.machine.com/redirect" + "&scope=openid%20profile%20email" + "&response_type=code" + "&state=99999999'" @@ -89,7 +99,7 @@ in "curl -f -s '" + auth_endpoint + "?client_id=client1" - + "&redirect_uri=http://client1.machine/redirect" + + "&redirect_uri=http://client1.machine.com/redirect" + "&scope=openid%20profile%20email" + "&response_type=code" + "&state=11111111'" @@ -99,7 +109,7 @@ in "curl -f -s '" + auth_endpoint + "?client_id=client2" - + "&redirect_uri=http://client2.machine/redirect" + + "&redirect_uri=http://client2.machine.com/redirect" + "&scope=openid%20profile%20email" + "&response_type=code" + "&state=22222222'" diff --git a/test/blocks/ssl.nix b/test/blocks/ssl.nix index 63d2303..fbfa728 100644 --- a/test/blocks/ssl.nix +++ b/test/blocks/ssl.nix @@ -103,8 +103,20 @@ in "multi3.example.com" = mkVirtualHost "multi3" config.shb.certs.certs.selfsigned.multi; }; systemd.services.nginx = { - after = [ config.shb.certs.certs.selfsigned.top.systemdService config.shb.certs.certs.selfsigned.subdomain.systemdService ]; - requires = [ config.shb.certs.certs.selfsigned.top.systemdService config.shb.certs.certs.selfsigned.subdomain.systemdService ]; + after = [ + config.shb.certs.certs.selfsigned.top.systemdService + config.shb.certs.certs.selfsigned.subdomain.systemdService + config.shb.certs.certs.selfsigned.multi.systemdService + config.shb.certs.certs.selfsigned.cert1.systemdService + config.shb.certs.certs.selfsigned.cert2.systemdService + ]; + requires = [ + config.shb.certs.certs.selfsigned.top.systemdService + config.shb.certs.certs.selfsigned.subdomain.systemdService + config.shb.certs.certs.selfsigned.multi.systemdService + config.shb.certs.certs.selfsigned.cert1.systemdService + config.shb.certs.certs.selfsigned.cert2.systemdService + ]; }; }; diff --git a/test/common.nix b/test/common.nix index d1ed745..3533b26 100644 --- a/test/common.nix +++ b/test/common.nix @@ -41,7 +41,8 @@ in ) + lib.strings.concatMapStrings (p: ''server.wait_for_open_port(${toString p})'' + "\n") ( waitForPorts args - ++ (lib.optionals redirectSSO [ nodes.server.services.authelia.instances."auth.${domain}".settings.server.port ] ) + # TODO: when the SSO block exists, replace this hardcoded port. + ++ (lib.optionals redirectSSO [ 9091 /* nodes.server.services.authelia.instances."auth.${domain}".settings.server.port */ ] ) ) + lib.strings.concatMapStrings (u: ''server.wait_for_open_unix_socket("${u}")'' + "\n") (waitForUnixSocket args) + '' diff --git a/test/services/vaultwarden.nix b/test/services/vaultwarden.nix index 9398b07..f79d2df 100644 --- a/test/services/vaultwarden.nix +++ b/test/services/vaultwarden.nix @@ -31,7 +31,7 @@ let {"email": "me@example.com"} """)) print(response) - if 'Kdf' not in response: + if 'kdf' not in response: raise Exception("Unrecognized response: {}".format(response)) with subtest("get token"): @@ -45,7 +45,7 @@ let &password=mypassword """)) print(response) - if response["Message"] != "Username or password is incorrect. Try again": + if response["message"] != "Username or password is incorrect. Try again": raise Exception("Unrecognized response: {}".format(response)) ''; }; @@ -174,6 +174,11 @@ in nodes.client = {}; testScript = commonTestScript.override { + waitForPorts = { node, ... }: [ + 8222 + 5432 + 9091 + ]; extraScript = { proto_fqdn, ... }: '' with subtest("unauthenticated access is not granted to /admin"): response = curl(client, """{"code":%{response_code},"auth_host":"%{urle.host}","auth_query":"%{urle.query}","all":%{json}}""", "${proto_fqdn}/admin")