From bd8cff90edd96563564e07f78a005aa74b54e5f9 Mon Sep 17 00:00:00 2001 From: "RingOfStorms (Joshua Bell)" Date: Mon, 5 Jan 2026 22:43:44 -0600 Subject: [PATCH] secrets-bao: inline configchanges, remove file, make configChanges attrs --- flakes/secrets-bao/flake.nix | 31 +- flakes/secrets-bao/nixos-configchanges.nix | 10 - flakes/secrets-bao/nixos-module.nix | 378 +++++++++++---------- hosts/juni/flake.nix | 107 +++--- 4 files changed, 269 insertions(+), 257 deletions(-) delete mode 100644 flakes/secrets-bao/nixos-configchanges.nix diff --git a/flakes/secrets-bao/flake.nix b/flakes/secrets-bao/flake.nix index 25122c4b..47e6a49b 100644 --- a/flakes/secrets-bao/flake.nix +++ b/flakes/secrets-bao/flake.nix @@ -5,13 +5,32 @@ outputs = { ... }: { + lib = { + applyConfigChanges = secrets: + let + substitute = secretPath: value: + if builtins.isAttrs value then + builtins.mapAttrs (_: v: substitute secretPath v) value + else if builtins.isList value then + map (v: substitute secretPath v) value + else if builtins.isString value then + builtins.replaceStrings [ "$SECRET_PATH" ] [ secretPath ] value + else + value; + + fragments = builtins.attrValues (builtins.mapAttrs ( + name: s: + let + secretPath = s.path or ("/run/secrets/" + name); + in + substitute secretPath (s.configChanges or { }) + ) secrets); + in + builtins.foldl' (acc: v: acc // v) { } fragments; + }; + nixosModules = { - default = { - imports = [ - (import ./nixos-module.nix) - (import ./nixos-configchanges.nix) - ]; - }; + default = import ./nixos-module.nix; }; }; } diff --git a/flakes/secrets-bao/nixos-configchanges.nix b/flakes/secrets-bao/nixos-configchanges.nix deleted file mode 100644 index 52be8f36..00000000 --- a/flakes/secrets-bao/nixos-configchanges.nix +++ /dev/null @@ -1,10 +0,0 @@ -{ config, lib, ... }: -let - cfg = config.ringofstorms.secretsBao; - secrets = cfg.secrets or { }; -in -{ - config = lib.mkIf cfg.enable ( - lib.mkMerge (lib.mapAttrsToList (_: s: s.configChanges { path = s.path; }) secrets) - ); -} diff --git a/flakes/secrets-bao/nixos-module.nix b/flakes/secrets-bao/nixos-module.nix index 15df8f84..f0b8f7bf 100644 --- a/flakes/secrets-bao/nixos-module.nix +++ b/flakes/secrets-bao/nixos-module.nix @@ -371,9 +371,9 @@ in }; configChanges = lib.mkOption { - type = lib.types.functionTo lib.types.attrs; - default = { path, ... }: { }; - description = "Function that returns extra config given { path = secret.path; }."; + type = lib.types.attrs; + default = { }; + description = "Extra config applied when enabled; supports '$SECRET_PATH' string substitution."; }; template = lib.mkOption { @@ -389,210 +389,214 @@ in }; }; - config = lib.mkIf cfg.enable (lib.mkMerge [ - { - assertions = lib.mapAttrsToList (name: s: { - assertion = (s.template != null) || (s.kvPath != null); - message = "ringofstorms.secretsBao.secrets.${name} must set either template or kvPath"; - }) cfg.secrets; - - environment.systemPackages = [ - pkgs.jq - pkgs.curl - pkgs.openssl - pkgs.openbao - zitadelMintJwt - ]; - - systemd.tmpfiles.rules = [ - "d /run/openbao 0700 root root - -" - "f /run/openbao/zitadel.jwt 0400 root root - -" - "d /run/secrets 0711 root root - -" - ]; - - systemd.services = lib.mkMerge [ + config = lib.mkIf cfg.enable ( + lib.mkMerge [ { - zitadel-mint-jwt = { - description = "Mint Zitadel access token (JWT) for OpenBao"; - wantedBy = [ "multi-user.target" ]; - after = [ - "network-online.target" - "nss-lookup.target" - "NetworkManager-wait-online.service" - "systemd-resolved.service" - "time-sync.target" - ]; - wants = [ - "network-online.target" - "NetworkManager-wait-online.service" - "systemd-resolved.service" - ]; + assertions = lib.mapAttrsToList (name: s: { + assertion = (s.template != null) || (s.kvPath != null); + message = "ringofstorms.secretsBao.secrets.${name} must set either template or kvPath"; + }) cfg.secrets; - serviceConfig = { - Type = "oneshot"; - User = "root"; - Group = "root"; - Restart = "on-failure"; - RestartSec = "30s"; - TimeoutStartSec = "2min"; - UMask = "0077"; - ExecStart = pkgs.writeShellScript "zitadel-mint-jwt-service" '' - #!/usr/bin/env bash - set -euo pipefail + environment.systemPackages = [ + pkgs.jq + pkgs.curl + pkgs.openssl + pkgs.openbao + zitadelMintJwt + ]; - if [ ! -d "/run/openbao" ]; then - ${pkgs.coreutils}/bin/mkdir -p /run/openbao - ${pkgs.coreutils}/bin/chmod 0700 /run/openbao - fi + systemd.tmpfiles.rules = [ + "d /run/openbao 0700 root root - -" + "f /run/openbao/zitadel.jwt 0400 root root - -" + "d /run/secrets 0711 root root - -" + ]; - if [ ! -f "${cfg.zitadelKeyPath}" ]; then - echo "Missing Zitadel key JSON at ${cfg.zitadelKeyPath}" >&2 - exit 1 - fi + systemd.services = lib.mkMerge [ + { + zitadel-mint-jwt = { + description = "Mint Zitadel access token (JWT) for OpenBao"; + wantedBy = [ "multi-user.target" ]; + after = [ + "network-online.target" + "nss-lookup.target" + "NetworkManager-wait-online.service" + "systemd-resolved.service" + "time-sync.target" + ]; + wants = [ + "network-online.target" + "NetworkManager-wait-online.service" + "systemd-resolved.service" + ]; - echo "zitadel-mint-jwt: starting (host=${zitadelHost})" >&2 + serviceConfig = { + Type = "oneshot"; + User = "root"; + Group = "root"; + Restart = "on-failure"; + RestartSec = "30s"; + TimeoutStartSec = "2min"; + UMask = "0077"; - # Best-effort: wait briefly for time sync + DNS. - for i in {1..10}; do - if ${pkgs.systemd}/bin/timedatectl show -p NTPSynchronized --value 2>/dev/null | ${pkgs.gnugrep}/bin/grep -qi true; then - break - fi - sleep 1 - done + ExecStart = pkgs.writeShellScript "zitadel-mint-jwt-service" '' + #!/usr/bin/env bash + set -euo pipefail - for i in {1..10}; do - if ${pkgs.systemd}/bin/resolvectl query ${zitadelHost} >/dev/null 2>&1; then - break - fi - sleep 1 - done + if [ ! -d "/run/openbao" ]; then + ${pkgs.coreutils}/bin/mkdir -p /run/openbao + ${pkgs.coreutils}/bin/chmod 0700 /run/openbao + fi - jwt_is_valid() { - local token="$1" - local payload_b64 payload_json exp now + if [ ! -f "${cfg.zitadelKeyPath}" ]; then + echo "Missing Zitadel key JSON at ${cfg.zitadelKeyPath}" >&2 + exit 1 + fi - payload_b64="$(${pkgs.coreutils}/bin/printf '%s' "$token" | ${pkgs.coreutils}/bin/cut -d. -f2)" - payload_b64="$(${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.gnused}/bin/sed -e 's/-/+/g' -e 's/_/\//g')" + echo "zitadel-mint-jwt: starting (host=${zitadelHost})" >&2 - case $((${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.coreutils}/bin/wc -c)) in - *1) payload_b64="$payload_b64=" ;; - *2) payload_b64="$payload_b64==" ;; - *3) : ;; - *0) : ;; - esac + # Best-effort: wait briefly for time sync + DNS. + for i in {1..10}; do + if ${pkgs.systemd}/bin/timedatectl show -p NTPSynchronized --value 2>/dev/null | ${pkgs.gnugrep}/bin/grep -qi true; then + break + fi + sleep 1 + done - payload_json="$(${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.coreutils}/bin/base64 -d 2>/dev/null || true)" - exp="$(${pkgs.jq}/bin/jq -r '.exp // empty' <<<"$payload_json" 2>/dev/null || true)" - if [ -z "$exp" ]; then - return 1 - fi + for i in {1..10}; do + if ${pkgs.systemd}/bin/resolvectl query ${zitadelHost} >/dev/null 2>&1; then + break + fi + sleep 1 + done - now="$(${pkgs.coreutils}/bin/date +%s)" - if [ "$exp" -gt $(( now + 60 )) ]; then - return 0 - fi - return 1 - } + jwt_is_valid() { + local token="$1" + local payload_b64 payload_json exp now - if [ -s "${cfg.zitadelJwtPath}" ] && jwt_is_valid "$(cat "${cfg.zitadelJwtPath}")"; then - echo "zitadel-mint-jwt: existing token still valid; skipping" >&2 - exit 0 - fi + payload_b64="$(${pkgs.coreutils}/bin/printf '%s' "$token" | ${pkgs.coreutils}/bin/cut -d. -f2)" + payload_b64="$(${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.gnused}/bin/sed -e 's/-/+/g' -e 's/_/\//g')" - jwt="$(${zitadelMintJwt}/bin/zitadel-mint-jwt)" + case $((${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.coreutils}/bin/wc -c)) in + *1) payload_b64="$payload_b64=" ;; + *2) payload_b64="$payload_b64==" ;; + *3) : ;; + *0) : ;; + esac - if [ -z "$jwt" ] || [ "$jwt" = "null" ]; then - echo "Failed to mint Zitadel access token" >&2 - exit 1 - fi + payload_json="$(${pkgs.coreutils}/bin/printf '%s' "$payload_b64" | ${pkgs.coreutils}/bin/base64 -d 2>/dev/null || true)" + exp="$(${pkgs.jq}/bin/jq -r '.exp // empty' <<<"$payload_json" 2>/dev/null || true)" + if [ -z "$exp" ]; then + return 1 + fi - tmp="$(${pkgs.coreutils}/bin/mktemp)" - trap '${pkgs.coreutils}/bin/rm -f "$tmp"' EXIT - ${pkgs.coreutils}/bin/printf '%s' "$jwt" > "$tmp" + now="$(${pkgs.coreutils}/bin/date +%s)" + if [ "$exp" -gt $(( now + 60 )) ]; then + return 0 + fi + return 1 + } - # In-place update so the agent's file watcher sees changes. - ${pkgs.coreutils}/bin/cat "$tmp" > "${cfg.zitadelJwtPath}" - ${pkgs.coreutils}/bin/chmod 0400 "${cfg.zitadelJwtPath}" || true - ''; - }; - }; + if [ -s "${cfg.zitadelJwtPath}" ] && jwt_is_valid "$(cat "${cfg.zitadelJwtPath}")"; then + echo "zitadel-mint-jwt: existing token still valid; skipping" >&2 + exit 0 + fi - vault-agent = { - description = "OpenBao agent for rendering secrets"; - wantedBy = [ "multi-user.target" ]; - after = [ - "network-online.target" - "zitadel-mint-jwt.service" - ]; - wants = [ - "network-online.target" - "zitadel-mint-jwt.service" - ]; + jwt="$(${zitadelMintJwt}/bin/zitadel-mint-jwt)" - serviceConfig = { - Type = "simple"; - User = "root"; - Group = "root"; - Restart = "on-failure"; - RestartSec = "10s"; - TimeoutStartSec = "30s"; - UMask = "0077"; - ExecStart = "${pkgs.openbao}/bin/bao agent -log-level=debug -config=${mkAgentConfig}"; - }; - }; + if [ -z "$jwt" ] || [ "$jwt" = "null" ]; then + echo "Failed to mint Zitadel access token" >&2 + exit 1 + fi + + tmp="$(${pkgs.coreutils}/bin/mktemp)" + trap '${pkgs.coreutils}/bin/rm -f "$tmp"' EXIT + ${pkgs.coreutils}/bin/printf '%s' "$jwt" > "$tmp" + + # In-place update so the agent's file watcher sees changes. + ${pkgs.coreutils}/bin/cat "$tmp" > "${cfg.zitadelJwtPath}" + ${pkgs.coreutils}/bin/chmod 0400 "${cfg.zitadelJwtPath}" || true + ''; + }; + }; + + vault-agent = { + description = "OpenBao agent for rendering secrets"; + wantedBy = [ "multi-user.target" ]; + after = [ + "network-online.target" + "zitadel-mint-jwt.service" + ]; + wants = [ + "network-online.target" + "zitadel-mint-jwt.service" + ]; + + serviceConfig = { + Type = "simple"; + User = "root"; + Group = "root"; + Restart = "on-failure"; + RestartSec = "10s"; + TimeoutStartSec = "30s"; + UMask = "0077"; + ExecStart = "${pkgs.openbao}/bin/bao agent -log-level=debug -config=${mkAgentConfig}"; + }; + }; + } + + (lib.mapAttrs' ( + name: secret: + lib.nameValuePair "openbao-secret-${name}" { + description = "Wait for OpenBao secret ${name}"; + after = [ "vault-agent.service" ]; + requires = [ "vault-agent.service" ]; + wantedBy = map (svc: "${svc}.service") secret.dependencies; + startLimitIntervalSec = 300; + startLimitBurst = 3; + + serviceConfig = { + Type = "oneshot"; + User = "root"; + Group = "root"; + UMask = "0077"; + ExecStart = pkgs.writeShellScript "openbao-wait-secret-${name}" '' + #!/usr/bin/env bash + set -euo pipefail + + p=${lib.escapeShellArg secret.path} + + for i in {1..60}; do + if [ -s "$p" ]; then + break + fi + sleep 1 + done + + if [ ! -s "$p" ]; then + echo "Secret file not rendered: $p" >&2 + exit 1 + fi + + ${lib.concatStringsSep "\n" (map (svc: '' + echo "Restarting ${svc} due to secret ${name}" >&2 + systemctl try-restart ${lib.escapeShellArg (svc + ".service")} || true + '') secret.dependencies)} + ''; + }; + } + ) cfg.secrets) + ]; + + age.secrets = lib.mapAttrs' ( + name: secret: + lib.nameValuePair name { + file = null; + path = secret.path; + } + ) cfg.secrets; } - (lib.mapAttrs' ( - name: secret: - lib.nameValuePair "openbao-secret-${name}" { - description = "Wait for OpenBao secret ${name}"; - after = [ "vault-agent.service" ]; - requires = [ "vault-agent.service" ]; - wantedBy = map (svc: "${svc}.service") secret.dependencies; - startLimitIntervalSec = 300; - startLimitBurst = 3; - - serviceConfig = { - Type = "oneshot"; - User = "root"; - Group = "root"; - UMask = "0077"; - ExecStart = pkgs.writeShellScript "openbao-wait-secret-${name}" '' - #!/usr/bin/env bash - set -euo pipefail - - p=${lib.escapeShellArg secret.path} - - for i in {1..60}; do - if [ -s "$p" ]; then - break - fi - sleep 1 - done - - if [ ! -s "$p" ]; then - echo "Secret file not rendered: $p" >&2 - exit 1 - fi - - ${lib.concatStringsSep "\n" (map (svc: '' - echo "Restarting ${svc} due to secret ${name}" >&2 - systemctl try-restart ${lib.escapeShellArg (svc + ".service")} || true - '') secret.dependencies)} - ''; - }; - } - ) cfg.secrets) - ]; - - age.secrets = lib.mapAttrs' ( - name: secret: - lib.nameValuePair name { - file = null; - path = secret.path; - } - ) cfg.secrets; - } - ]); + ] + ); } diff --git a/hosts/juni/flake.nix b/hosts/juni/flake.nix index 1c9f61ba..9449070e 100644 --- a/hosts/juni/flake.nix +++ b/hosts/juni/flake.nix @@ -43,8 +43,9 @@ { nixosConfigurations = { "${configuration_name}" = ( - lib.nixosSystem { - modules = [ + lib.nixosSystem { + specialArgs = { inherit inputs; }; + modules = [ inputs.nixos-hardware.nixosModules.framework-12-13th-gen-intel inputs.impermanence.nixosModules.impermanence ({ @@ -68,7 +69,7 @@ }) inputs.common.nixosModules.jetbrains_font - inputs.secrets-bao.nixosModules.default + inputs.secrets-bao.nixosModules.default inputs.ros_neovim.nixosModules.default ({ ringofstorms-nvim.includeAllRuntimeDependencies = true; @@ -89,57 +90,55 @@ inputs.common.nixosModules.tailnet inputs.common.nixosModules.remote_lio_builds - ( - { config, ... }: - { - - ringofstorms.secretsBao = { - enable = true; - zitadelKeyPath = "/machine-key.json"; - openBaoAddr = "https://sec.joshuabell.xyz"; - jwtAuthMountPath = "auth/zitadel-jwt"; - openBaoRole = "machines"; - zitadelIssuer = "https://sso.joshuabell.xyz"; - zitadelProjectId = "344379162166820867"; - debugMint = true; - secrets = { - headscale_auth = { - kvPath = "kv/data/machines/home_roaming/headscale_auth"; - dependencies = [ "tailscaled" ]; - configChanges = { path, ... }: { - services.tailscale.authKeyFile = path; - }; - }; - - nix2github = { - owner = "josh"; - group = "users"; - kvPath = "kv/data/machines/home_roaming/nix2github"; - }; - nix2bitbucket = { - owner = "josh"; - group = "users"; - kvPath = "kv/data/machines/home_roaming/nix2bitbucket"; - }; - nix2gitforgejo = { - owner = "josh"; - group = "users"; - kvPath = "kv/data/machines/home_roaming/nix2gitforgejo"; - }; - nix2lio = { - owner = "josh"; - group = "users"; - kvPath = "kv/data/machines/home_roaming/nix2lio"; - }; - }; - }; - - systemd.services.tailscaled = { - after = [ "openbao-secret-headscale_auth.service" ]; - requires = [ "openbao-secret-headscale_auth.service" ]; - }; - } - ) + ( + { inputs, lib, ... }: + let + secrets = { + headscale_auth = { + kvPath = "kv/data/machines/home_roaming/headscale_auth"; + dependencies = [ "tailscaled" ]; + configChanges = { + services.tailscale.authKeyFile = "$SECRET_PATH"; + }; + }; + nix2github = { + owner = "josh"; + group = "users"; + kvPath = "kv/data/machines/home_roaming/nix2github"; + }; + nix2bitbucket = { + owner = "josh"; + group = "users"; + kvPath = "kv/data/machines/home_roaming/nix2bitbucket"; + }; + nix2gitforgejo = { + owner = "josh"; + group = "users"; + kvPath = "kv/data/machines/home_roaming/nix2gitforgejo"; + }; + nix2lio = { + owner = "josh"; + group = "users"; + kvPath = "kv/data/machines/home_roaming/nix2lio"; + }; + }; + in + lib.mkMerge [ + { + ringofstorms.secretsBao = { + enable = true; + zitadelKeyPath = "/machine-key.json"; + openBaoAddr = "https://sec.joshuabell.xyz"; + jwtAuthMountPath = "auth/zitadel-jwt"; + openBaoRole = "machines"; + zitadelIssuer = "https://sso.joshuabell.xyz"; + zitadelProjectId = "344379162166820867"; + inherit secrets; + }; + } + (inputs.secrets-bao.lib.applyConfigChanges secrets) + ] + ) # inputs.beszel.nixosModules.agent # ({