docs(users): own the runner-row divergence from #17 — server class keeps root, deliberately, runner included

#17's table said runner 'can close root once an admin user is proven'; the
class model (#26) superseded the per-role call, and close-root refuses on
class=server — runner's class. The gate does not change: the refusal message
now explains itself (server-class machines are automation identities whose
management plane IS root SSH; a CI box meant to be administered like a human
machine is --class human at bootstrap, not an exception), and the README's
identity-model section records the divergence in one paragraph. README also
documents the @root seed token and close-root's reachability proofs.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
dan-claude-bot 2026-07-18 14:54:03 +00:00
parent d1b6fec5f8
commit ceceb71e34
3 changed files with 46 additions and 4 deletions

View file

@ -209,6 +209,15 @@ On `class=server` it stays open — key-only, as bootstrap left it — because
root there is the **automation** identity the control plane (Coolify) SSHes root there is the **automation** identity the control plane (Coolify) SSHes
in as. It is a machine door, never a human one. in as. It is a machine door, never a human one.
**Where this diverges from #17's original table:** that table let the
`runner` role close root ("no Coolify involved"). The class model supersedes
the per-role call: runner is `class=server` — an automation identity, not a
person's box — and on every server-class machine root SSH is the management
plane rig itself converges through, so `close-root` refuses there
deliberately, runner included. A CI box you mean to administer like a human
machine is `--class human` at bootstrap, not an exception carved out of the
gate.
**The detection side benefit:** once humans never use root, any root login **The detection side benefit:** once humans never use root, any root login
that is not the control plane is anomalous *by definition* — a cheap, that is not the control plane is anomalous *by definition* — a cheap,
high-signal alert that a shared root identity makes impossible to write. high-signal alert that a shared root identity makes impossible to write.
@ -520,6 +529,20 @@ refused as a username: this file names operators; root's fate is class policy.
`--file -` reads stdin. A bad file exits 2 with **every** error listed at `--file -` reads stdin. A bad file exits 2 with **every** error listed at
once, before anything changes — one fix cycle, not one round-trip per line. once, before anything changes — one fix cycle, not one round-trip per line.
**`@root` — seed keys from the door you came in through (#17).** A key field
of exactly `@root` means "this user's `authorized_keys` becomes root's
CURRENT `/root/.ssh/authorized_keys`". The point is lockout-avoidance: you
provably hold a root private key — you SSHed in with it to run apply at all —
so the seeded key is the one key rig can *know* opens for you; any pasted
literal can be a key you do not hold. `@root` mixes with literal lines
(seeded keys land first, literals append after), re-runs re-seed from root's
then-current file — convergent to it, so a seeded key you hand-remove from
the admin returns until you switch the line to literal keys — and apply dies
if root has no `authorized_keys` to seed. Root's key lines are copied
verbatim, options included: a `from=`/`command=` restriction follows the key,
and on a Coolify-managed box root's file also carries *Coolify's* key — on
`class=server`, prefer literal keys.
**Public tool, private state, here too.** The users file lives in *your* **Public tool, private state, here too.** The users file lives in *your*
private infra repo and is passed per invocation — rig never persists it. It private infra repo and is passed per invocation — rig never persists it. It
holds nothing secret anyway: usernames, roles, and *public* keys. holds nothing secret anyway: usernames, roles, and *public* keys.
@ -602,9 +625,15 @@ automation identity and closing it severs fleet management. Then at least one
non-empty `authorized_keys` alone proves a file, not a door: the gate checks non-empty `authorized_keys` alone proves a file, not a door: the gate checks
the `StrictModes` shape (home, `.ssh`, and `authorized_keys` owned by the the `StrictModes` shape (home, `.ssh`, and `authorized_keys` owned by the
user and not group/world-writable), a real login shell, and an unexpired user and not group/world-writable), a real login shell, and an unexpired
account, and its refusal names which check failed, per candidate. It proves account — and then two **reachability** proofs (#17): `sudo -n true` under
the door *should* open, not that it does — which is why the separate-session `runuser` must answer, so NOPASSWD sudo is effective rather than merely
verification below stays load-bearing. Never close the only door. written, and `sshd -T -C user=<admin>` must resolve a per-user effective
config that accepts the login (`pubkeyauthentication yes`, no `DenyUsers`
hit, `AllowUsers` — if set — names them), so a `Match` block elsewhere cannot
quietly exclude the admin while every file looks right. The refusal names
which check failed, per candidate. What no local check can prove is that you
*hold* the private key — which is why the separate-session verification below
stays load-bearing. Never close the only door.
Before running it, prove the admin door in a **separate** session — `ssh Before running it, prove the admin door in a **separate** session — `ssh
<admin>@<box>` while this one stays open. Root SSH is being welded shut; the <admin>@<box>` while this one stays open. Root SSH is being welded shut; the

View file

@ -131,7 +131,14 @@ assert_marker_human() {
*class=server*) *class=server*)
# Root SSH on a server IS the control plane's (Coolify's) automation # Root SSH on a server IS the control plane's (Coolify's) automation
# identity — closing it severs fleet management. No --force exists. # identity — closing it severs fleet management. No --force exists.
printf '%s\n' "class=server: root here is the control plane's automation identity — closing it severs fleet management" # Deliberately per-CLASS, not per-role: #17's original table let the
# runner role close root ("no Coolify involved"), but the class model
# (#26) supersedes that — every server-class box, runner included, is
# an automation identity whose management plane is root SSH, and rig
# itself converges through that door. A CI box someone administers
# like a human machine is class=human at bootstrap, not an exception
# carved out here.
printf '%s\n' "class=server: root here is the control plane's automation identity — closing it severs fleet management. Every server-class box (runner included) keeps root deliberately: it is an automation identity, and root SSH is its management plane; a box meant to be administered like a human machine is --class human at bootstrap, not an exception here"
return 1 ;; return 1 ;;
*) *)
printf '%s\n' "marker names no class (${marker}): re-run rig bootstrap; refusing to shut the root door blind" printf '%s\n' "marker names no class (${marker}): re-run rig bootstrap; refusing to shut the root door blind"

View file

@ -584,6 +584,12 @@ check "users close-root: absent marker refuses, names bootstrap as the repair" \
1 "no /etc/rig/role marker" marker_gate "$MARKER_DIR/absent" 1 "no /etc/rig/role marker" marker_gate "$MARKER_DIR/absent"
check "users close-root: class=server refuses, names the control plane" \ check "users close-root: class=server refuses, names the control plane" \
1 "control plane" marker_gate "$MARKER_DIR/server" 1 "control plane" marker_gate "$MARKER_DIR/server"
# #17's original table let the runner ROLE close root; the class model
# supersedes it — runner is class=server, an automation identity, and the
# refusal must SAY so or the divergence reads as a bug to anyone holding the
# old table.
check "users close-root: the server refusal owns the runner row (#17)" \
1 "runner included" marker_gate "$MARKER_DIR/server"
check "users close-root: class=human passes the gate" \ check "users close-root: class=human passes the gate" \
0 "" marker_gate "$MARKER_DIR/human" 0 "" marker_gate "$MARKER_DIR/human"
rm -rf "$MARKER_DIR" rm -rf "$MARKER_DIR"