diff --git a/nixos/modules/services/networking/headplane.nix b/nixos/modules/services/networking/headplane.nix index 5fa0d6a6df20..19e131fcab8d 100644 --- a/nixos/modules/services/networking/headplane.nix +++ b/nixos/modules/services/networking/headplane.nix @@ -33,37 +33,6 @@ let settingsFile = settingsFormat.generate "headplane-config.yaml" (filterSettings settings); in { - imports = [ - (lib.mkRemovedOptionModule - [ - "services.headplane" - "settings" - "oidc" - "redirect_uri" - ] - "Use services.headplane.settings.server.base_url instead; the OIDC redirect URI is now derived from it. Do not include the /admin suffix." - ) - (lib.mkRemovedOptionModule [ - "services.headplane" - "settings" - "oidc" - "user_storage_file" - ] "Users are now stored in the internal database.") - (lib.mkRemovedOptionModule [ - "services.headplane" - "settings" - "oidc" - "strict_validation" - ] "This option has no effect in Headplane 0.6.2 and later.") - (lib.mkRemovedOptionModule [ - "services.headplane" - "settings" - "integration" - "agent" - "cache_ttl" - ] "This option has no effect in Headplane 0.6.2 and later.") - ]; - options.services.headplane = { enable = mkEnableOption "Headplane"; package = mkPackageOption pkgs "headplane" { }; @@ -151,17 +120,6 @@ in example = "/var/lib/headplane"; }; - info_secret_path = mkOption { - type = types.nullOr types.path; - default = null; - description = '' - Path to a file containing the info secret. - Allows access to certain debug endpoints that may expose - sensitive information about your Headplane instance. - If not set, these endpoints will be disabled. - ''; - example = lib.literalExpression "config.sops.secrets.headplane_info_secret.path"; - }; }; }; default = { }; @@ -278,8 +236,17 @@ in description = "Optionally change the name of the agent in the Tailnet."; }; + cache_ttl = mkOption { + type = types.nullOr types.int; + default = null; + description = '' + Deprecated cache TTL for the agent. This option is accepted + by Headplane 0.6.2 but has no effect. + ''; + }; + cache_path = mkOption { - type = types.str; + type = types.path; default = "/var/lib/headplane/agent_cache.json"; description = "The path to store the agent's cache."; }; @@ -399,56 +366,22 @@ in ''; }; - use_end_session = mkOption { - type = types.bool; - default = false; - description = '' - Enable RP-initiated logout. When true, /logout redirects the user - to the IdP's end_session_endpoint. The post_logout_redirect_uri - MUST be pre-registered in your OIDC client configuration. - ''; - }; - - end_session_endpoint = mkOption { + redirect_uri = mkOption { type = types.nullOr types.str; default = null; description = '' - Override the auto-discovered end_session_endpoint, or supply - one if your provider does not advertise it via discovery. + Deprecated OIDC redirect URI. Use services.headplane.settings.server.base_url + instead; Headplane derives the callback URL from it. ''; - example = "https://provider.example.com/logout"; + example = "https://headplane.example.com/admin/oidc/callback"; }; - post_logout_redirect_uri = mkOption { - type = types.nullOr types.str; + strict_validation = mkOption { + type = types.nullOr types.bool; default = null; description = '' - Where the identity provider should redirect after RP-initiated logout. - If unset, Headplane defaults to its own login page. - ''; - example = "https://headplane.example.com/admin/login?s=logout"; - }; - - subject_claims = mkOption { - type = types.nullOr (types.listOf types.str); - default = null; - description = '' - Fallback claims to use when your provider does not return a standard - OIDC `sub` claim. Headplane always checks `sub` first, then each - claim here in order. - ''; - example = [ - "open_id" - "email" - ]; - }; - - allow_weak_rsa_keys = mkOption { - type = types.bool; - default = false; - description = '' - Allow ID token verification with legacy RSA keys smaller than 2048 bits. - Only use as a temporary compatibility workaround. + Deprecated OIDC validation setting. This option is accepted + by Headplane 0.6.2 but has no effect. ''; }; @@ -496,6 +429,16 @@ in description = "Custom userinfo endpoint URL."; example = "https://provider.example.com/userinfo"; }; + + user_storage_file = mkOption { + type = types.nullOr types.path; + default = null; + description = '' + Deprecated path to the pre-0.6.2 JSON user database. + Headplane uses this once to migrate users into its internal database. + ''; + example = "/var/lib/headplane/users.json"; + }; }; } ); @@ -509,6 +452,33 @@ in }; config = mkIf cfg.enable { + warnings = + lib.optionals (cfg.settings.oidc != null && cfg.settings.oidc.redirect_uri != null) [ + '' + services.headplane.settings.oidc.redirect_uri is deprecated by Headplane 0.6.2. + Use services.headplane.settings.server.base_url instead; Headplane derives + the OIDC callback URL from it. + '' + ] + ++ lib.optionals (cfg.settings.oidc != null && cfg.settings.oidc.strict_validation != null) [ + '' + services.headplane.settings.oidc.strict_validation is deprecated and has no effect + in Headplane 0.6.2. + '' + ] + ++ lib.optionals (cfg.settings.oidc != null && cfg.settings.oidc.user_storage_file != null) [ + '' + services.headplane.settings.oidc.user_storage_file is deprecated. Headplane 0.6.2 + uses it only to migrate the pre-0.6.2 JSON user database into the internal database. + '' + ] + ++ lib.optionals (agentSettings != null && agentSettings.cache_ttl != null) [ + '' + services.headplane.settings.integration.agent.cache_ttl is deprecated and has no + effect in Headplane 0.6.2. + '' + ]; + assertions = [ { assertion = config.services.headscale.enable; @@ -540,7 +510,8 @@ in ''; } { - assertion = agentSettings == null || !agentSettings.enabled || agentSettings.pre_authkey_path != null; + assertion = + agentSettings == null || !agentSettings.enabled || agentSettings.pre_authkey_path != null; message = '' services.headplane.settings.integration.agent.pre_authkey_path must be set when the agent is enabled. diff --git a/nixos/tests/headplane.nix b/nixos/tests/headplane.nix index d11bab00f999..331012b12fdd 100644 --- a/nixos/tests/headplane.nix +++ b/nixos/tests/headplane.nix @@ -1,6 +1,8 @@ { lib, pkgs, ... }: let cookieSecret = pkgs.writeText "cookie-secret" "01234567890123456789012345678901"; + oidcClientSecret = pkgs.writeText "oidc-client-secret" "client-secret"; + headscaleApiKey = pkgs.writeText "headscale-api-key" "headscale-api-key"; in { name = "headplane"; @@ -32,6 +34,16 @@ in cookie_max_age = 3600; cookie_domain = "127.0.0.1"; }; + settings.oidc = { + enabled = false; + issuer = "https://provider.example.com/issuer-url"; + client_id = "your-client-id"; + client_secret_path = oidcClientSecret; + headscale_api_key_path = headscaleApiKey; + redirect_uri = "http://127.0.0.1:3000/admin/oidc/callback"; + strict_validation = true; + user_storage_file = "/var/lib/headplane/users.json"; + }; }; }; @@ -40,15 +52,15 @@ in machine.wait_for_unit("headplane.service") machine.wait_until_succeeds("curl -sf http://127.0.0.1:3000/admin/login") - with subtest("rendered config contains new 0.6.2 options"): + with subtest("rendered config contains new and deprecated-compatible options"): config = machine.succeed("cat /etc/headplane/config.yaml") - for key in ["base_url:", "cookie_max_age:", "cookie_domain:"]: + for key in ["base_url:", "cookie_max_age:", "cookie_domain:", "redirect_uri:", "strict_validation:", "user_storage_file:"]: assert key in config, f"{key} missing from rendered config" - with subtest("removed options are not rendered"): + with subtest("unsupported options are not rendered"): config = machine.succeed("cat /etc/headplane/config.yaml") - for key in ["redirect_uri:", "user_storage_file:", "strict_validation:", "cache_ttl:"]: - assert key not in config, f"removed option {key} still rendered" + for key in ["info_secret_path:", "subject_claims:", "allow_weak_rsa_keys:", "use_end_session:", "end_session_endpoint:", "post_logout_redirect_uri:"]: + assert key not in config, f"unsupported option {key} still rendered" with subtest("session cookie is set on login page"): machine.succeed("curl -sf -c /tmp/cj http://127.0.0.1:3000/admin/login")