diff --git a/nix/host-modules/hive-ci.nix b/nix/host-modules/hive-ci.nix index f215e49e..7af88be9 100644 --- a/nix/host-modules/hive-ci.nix +++ b/nix/host-modules/hive-ci.nix @@ -380,17 +380,40 @@ in # (bound socket above), which is up before the container — nix's # own connect retries cover any brief window. `mkBefore` runs this # ahead of any upstream pre-steps. + # + # The `+` prefix runs this step with FULL PRIVILEGES instead of as + # the unit's (dynamic) `gitea-runner` user. The token file is 0600 + # root-owned, so an unprivileged check cannot read it — and it did + # not fail closed: `[ -s ]` succeeds on a stat alone, `grep` then + # died with EACCES, and `! grep` turned that error into `true`, so + # the gate exited 0 on an unreadable file. It reported "a real token + # is present" without ever having looked, for its entire existence. + # + # This grants the runner nothing new: `tokenFile` above becomes + # `EnvironmentFile=`, which systemd already reads AS ROOT before + # dropping privileges, so the payload never passes through an + # unprivileged reader either way. The `+` only lets the gate observe + # what systemd observes. serviceConfig.ExecStartPre = lib.mkBefore [ - (pkgs.writeShellScript "hive-ci-runner-precond" '' + "+${pkgs.writeShellScript "hive-ci-runner-precond" '' RUNNER=/var/lib/gitea-runner/hive/.runner TOKEN_FILE=/run/hive-ci/runner-token if [ -f "$RUNNER" ]; then exit 0; fi + # Readability is asserted SEPARATELY and fails loudly. Folding + # it into the test below is what hid the bug: an unreadable file + # and a file holding a real token produced the same verdict, so + # "I could not look" was indistinguishable from "I looked and it + # is fine". + if [ -e "$TOKEN_FILE" ] && [ ! -r "$TOKEN_FILE" ]; then + echo "hive-ci runner: $TOKEN_FILE exists but is unreadable by this step; the credential gate cannot run" >&2 + exit 1 + fi if [ -s "$TOKEN_FILE" ] && ! ${pkgs.gnugrep}/bin/grep -q '^TOKEN=placeholder$' "$TOKEN_FILE"; then exit 0 fi echo "hive-ci runner: not registered and no real token yet; waiting for hive-c0re to register" >&2 exit 1 - '') + ''}" ]; # Registration is out of band: hive-c0re explicitly restarts this # unit once it writes the real token, which is the primary path. As