diff --git a/docs/swarm/secrets.md b/docs/swarm/secrets.md index 43e52214..9be8f5bf 100644 --- a/docs/swarm/secrets.md +++ b/docs/swarm/secrets.md @@ -469,7 +469,8 @@ step. A store set up before the mount existed needs the `bao-bootstrap`, which covers `sys/auth/oidc`, and the granter's own policy, which covers `auth/oidc/`. Until then `swarm-bao-operator-viewer-policy` writes the viewer policy, logs the step, and -exits 0, and the UI offers token login only. +exits 0, and the UI offers token login only. `swarm-bao-granter-role` re-runs +it each time the step succeeds, so OIDC login needs no further restart. ## The constraint that decides where the root lives diff --git a/nix/host-modules/swarm-bao.nix b/nix/host-modules/swarm-bao.nix index 493749c2..3535284f 100644 --- a/nix/host-modules/swarm-bao.nix +++ b/nix/host-modules/swarm-bao.nix @@ -2717,6 +2717,17 @@ in token_ttl=15m \ token_max_ttl=15m ''; + # Runs only once the script above exits 0, which is when the granter + # may first configure `auth/oidc`; the viewer unit exits 0 without + # doing so before then, and nothing else re-runs it. A restart because + # it is `RemainAfterExit`, where a start is a no-op. + # + # `--no-block`: the viewer unit is ordered after this one, so a + # blocking restart waits on a job that cannot run until this unit is + # active, which it is not until this command returns. + postStart = '' + systemctl restart --no-block swarm-bao-operator-viewer-policy.service + ''; }; # The swarm's first grant, written from the HOST. Every API listener but @@ -3348,6 +3359,7 @@ in # has run with a bootstrap token that carries `sys/auth/oidc`. Until then # this writes the policy and stops with exit 0: nothing is broken, the UI # still takes a token, and a day of retries would change nothing. + # `swarm-bao-granter-role` restarts this unit each time it succeeds. systemd.services.swarm-bao-operator-viewer-policy = lib.mkIf haveGranter { description = "write the bao UI's OIDC login: the operator viewer policy, the oidc config and its role"; after = [ @@ -3388,9 +3400,8 @@ in *) echo "the granter may not configure auth/oidc yet (capabilities: $caps), so the UI's OIDC login stays off and token login is unchanged." >&2 echo "one-time step, as root on this host (docs/getting-started/setup.md):" >&2 - ${lib.concatMapStringsSep "\n" (l: "echo ${lib.escapeShellArg " ${l}"} >&2") ( - granterSetupSteps ++ [ "systemctl restart swarm-bao-operator-viewer-policy" ] - )} + ${lib.concatMapStringsSep "\n" (l: "echo ${lib.escapeShellArg " ${l}"} >&2") granterSetupSteps} + echo "swarm-bao-granter-role re-runs this unit once it succeeds." >&2 exit 0 ;; esac diff --git a/nix/module-eval/bao-grants.nix b/nix/module-eval/bao-grants.nix index 0c4ac164..9156f0c0 100644 --- a/nix/module-eval/bao-grants.nix +++ b/nix/module-eval/bao-grants.nix @@ -1484,7 +1484,25 @@ let && at probe < at "exit 0" && at "exit 0" < at "bao kv get" && at "bao kv get" < at "bao write auth/oidc/config" - && lib.hasInfix "systemctl restart swarm-bao-operator-viewer-policy" s; + && lib.hasInfix "swarm-bao-granter-role re-runs this unit once it succeeds." s + && !(lib.hasInfix "systemctl restart swarm-bao-operator-viewer-policy" s); + } + { + # The granter step is what lets the viewer unit configure oidc, so its + # success restarts that unit: a restart since the unit is + # `RemainAfterExit`, `--no-block` since it is ordered after the granter. + # The ordering is one-way, so the viewer's retries never reach back. + name = "a successful granter step restarts the viewer unit without blocking"; + ok = + let + s = baoGrantHere.systemd.services; + g = s.swarm-bao-granter-role; + v = s.swarm-bao-operator-viewer-policy; + in + lib.hasInfix "systemctl restart --no-block swarm-bao-operator-viewer-policy.service" g.postStart + && lib.elem "swarm-bao-granter-role.service" v.after + && !(lib.elem "swarm-bao-operator-viewer-policy.service" g.after) + && !(v ? postStart && lib.hasInfix "swarm-bao-granter-role" v.postStart); } { # The client secret is on stdin, never an argument in /proc; the rest is