feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
#!/usr/bin/env bash
# rig users close-root — shut the human-class root SSH door, once and only
# once a named admin can already get in. class decides root SSH's fate (#26):
# on class=human a root login is unattributable noise, so it goes; on
# class=server root IS the control plane's automation identity, so closing it
# would sever fleet management — this command refuses there, and no --force
# exists. Convergent: a second run is a no-op and says so.
set -euo pipefail
HERE = " $( cd " $( dirname " $( readlink -f " ${ BASH_SOURCE [0] } " ) " ) " && pwd ) "
# shellcheck source=SCRIPTDIR/lib/users-config.sh
. " $HERE /lib/users-config.sh "
log( ) { printf 'rig-users: %s\n' " $* " ; }
die( ) { printf 'rig-users: ERROR: %s\n' " $1 " >& 2; exit " ${ 2 :- 1 } " ; }
usage( ) {
cat <<'EOF'
usage: rig users close-root
Shuts the root SSH door: installs /etc/ssh/sshd_config.d/00-rig-users.conf
carrying exactly ` PermitRootLogin no` , which beats bootstrap' s drop-in by
first-wins include order.
Human class ONLY. On class = server, root SSH is the control plane's (Coolify' s)
automation identity — closing it severs fleet management — so close-root
refuses there, with no --force. It also refuses without a role marker ( re-run
rig bootstrap; never shut the root door blind) and refuses while no rig-admin
fix(users): review findings — invoker gate, real SSH revocation, StrictModes-shaped close-root gate, trait-aware box role
Seven review findings on the users family, each with the harness check that
would have caught it:
- Invoker gate (apply + close-root): %rig's sudoers rule is binary-scoped but
not argument-scoped, so `sudo rig users apply --file <me-as-admin>` made
role rig silently root-equivalent through the very command that granted it.
Identity management now refuses any sudo invoker outside rig-admin; direct
root (bring-up, a root shell) proceeds.
- Offboarding revokes SSH, not just the password: a '!'-locked password is
not a closed door under UsePAM — Debian sshd still honors the pubkey. A
dropped user's account is now expired (usermod -L -e 1, the switch PAM
actually enforces) and authorized_keys is renamed to
authorized_keys.revoked-by-rig — access revoked, data kept, convergence
never destroys. Present users get their expiry cleared idempotently, so a
re-added user comes back to life.
- The ledger remembers: two-field lines ('name active' / 'name revoked',
legacy bare names read as active), so dropped users no longer vanish from
rig's memory on the next rewrite. status now reports the ledger state
corroborated by the account's real expiry — passwd -S read L for everyone
(apply locks all passwords always), so its locked/active was meaningless —
and flags a mismatch loudly as drift.
- Perms are part of the converged state: ~/.ssh and authorized_keys ownership
and mode converge on every run, not only when content changes — StrictModes
treats them as load-bearing, so drifted perms were a broken login that
"already converged" lied about. Only the content write stays cmp-guarded.
- close-root's admin-door gate checks the StrictModes shape per candidate —
ownership, group/world-writability of home/.ssh/authorized_keys, a real
login shell, an unexpired account — and names which check failed. It proves
the door SHOULD open, not that it does; the separate-session advisory stays
load-bearing.
- Usernames are validated in the parser's one-pass refusal matrix
(^[a-z_][a-z0-9_-]{0,31}$): 'fo|o' corrupted the parser's own '|'-delimited
stream, and a leading '-' read as a useradd flag mid-convergence.
- The box role is trait-aware: on a host=no box an absent incus group skips
the role with a warning and converges everything else — one box-role user
in a fleet-wide file must not abort apply everywhere VMs don't live.
host=yes still dies pointing at box setup-host; a classless marker warns
toward a bootstrap re-run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:19 +00:00
member holds a login sshd would plausibly accept — authorized_keys present
and non-empty, home/.ssh/keys owned by the user and not group/world-writable
( sshd' s StrictModes rejects the key otherwise) , a real login shell, account
not expired. The refusal names which check failed, per candidate. Run rig
users apply first; never close the only door.
feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
Before running, verify your admin login in a SEPARATE session — ` ssh
<admin>@<box>` while this one stays open. Root SSH is the door being welded
shut; the admin door must be proven, not presumed.
Run as root. Convergent: once root is closed, a re-run is a clean no-op.
EOF
}
# --- args (validated before the root check, so errors are testable) ---------
while [ $# -gt 0 ] ; do
case " $1 " in
-h| --help) usage; exit 0 ; ;
*) die " unknown flag: $1 " 2 ; ;
esac
done
# --- guards ------------------------------------------------------------------
[ " $( id -u) " -eq 0 ] || die "must run as root"
fix(users): review findings — invoker gate, real SSH revocation, StrictModes-shaped close-root gate, trait-aware box role
Seven review findings on the users family, each with the harness check that
would have caught it:
- Invoker gate (apply + close-root): %rig's sudoers rule is binary-scoped but
not argument-scoped, so `sudo rig users apply --file <me-as-admin>` made
role rig silently root-equivalent through the very command that granted it.
Identity management now refuses any sudo invoker outside rig-admin; direct
root (bring-up, a root shell) proceeds.
- Offboarding revokes SSH, not just the password: a '!'-locked password is
not a closed door under UsePAM — Debian sshd still honors the pubkey. A
dropped user's account is now expired (usermod -L -e 1, the switch PAM
actually enforces) and authorized_keys is renamed to
authorized_keys.revoked-by-rig — access revoked, data kept, convergence
never destroys. Present users get their expiry cleared idempotently, so a
re-added user comes back to life.
- The ledger remembers: two-field lines ('name active' / 'name revoked',
legacy bare names read as active), so dropped users no longer vanish from
rig's memory on the next rewrite. status now reports the ledger state
corroborated by the account's real expiry — passwd -S read L for everyone
(apply locks all passwords always), so its locked/active was meaningless —
and flags a mismatch loudly as drift.
- Perms are part of the converged state: ~/.ssh and authorized_keys ownership
and mode converge on every run, not only when content changes — StrictModes
treats them as load-bearing, so drifted perms were a broken login that
"already converged" lied about. Only the content write stays cmp-guarded.
- close-root's admin-door gate checks the StrictModes shape per candidate —
ownership, group/world-writability of home/.ssh/authorized_keys, a real
login shell, an unexpired account — and names which check failed. It proves
the door SHOULD open, not that it does; the separate-session advisory stays
load-bearing.
- Usernames are validated in the parser's one-pass refusal matrix
(^[a-z_][a-z0-9_-]{0,31}$): 'fo|o' corrupted the parser's own '|'-delimited
stream, and a leading '-' read as a useradd flag mid-convergence.
- The box role is trait-aware: on a host=no box an absent incus group skips
the role with a warning and converges everything else — one box-role user
in a fleet-wide file must not abort apply everywhere VMs don't live.
host=yes still dies pointing at box setup-host; a classless marker warns
toward a bootstrap re-run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:19 +00:00
# Identity management gates its INVOKER, not just its uid: %rig's sudoers rule
# is binary-scoped but not argument-scoped, so without this gate a rig-role
# user could reshape who enters this box as whom — the scoped grant silently
# root-equivalent through the users family. Direct root (no SUDO_USER:
# bring-up, a root shell) proceeds.
if [ -n " ${ SUDO_USER :- } " ] && [ " $SUDO_USER " != "root" ] \
&& ! id -nG " $SUDO_USER " 2>/dev/null | tr ' ' '\n' | grep -qx rig-admin; then
die " the users family changes who holds root — only rig-admin members (or root itself) may run it; role rig grants operational rig use, not identity management (invoker: $SUDO_USER ) "
fi
feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
# Marker gate — the policy lives in assert_marker_human (lib) so the harness
# can prove its refusals against fixture markers as non-root; RIG_ROLE_MARKER
# exists for the same reason: it keeps the command's own gate pointable at
# fixtures instead of only at the real /etc/rig/role.
if ! WHY = " $( assert_marker_human " ${ RIG_ROLE_MARKER :- /etc/rig/role } " ) " ; then
die " $WHY "
fi
fix(users): review findings — invoker gate, real SSH revocation, StrictModes-shaped close-root gate, trait-aware box role
Seven review findings on the users family, each with the harness check that
would have caught it:
- Invoker gate (apply + close-root): %rig's sudoers rule is binary-scoped but
not argument-scoped, so `sudo rig users apply --file <me-as-admin>` made
role rig silently root-equivalent through the very command that granted it.
Identity management now refuses any sudo invoker outside rig-admin; direct
root (bring-up, a root shell) proceeds.
- Offboarding revokes SSH, not just the password: a '!'-locked password is
not a closed door under UsePAM — Debian sshd still honors the pubkey. A
dropped user's account is now expired (usermod -L -e 1, the switch PAM
actually enforces) and authorized_keys is renamed to
authorized_keys.revoked-by-rig — access revoked, data kept, convergence
never destroys. Present users get their expiry cleared idempotently, so a
re-added user comes back to life.
- The ledger remembers: two-field lines ('name active' / 'name revoked',
legacy bare names read as active), so dropped users no longer vanish from
rig's memory on the next rewrite. status now reports the ledger state
corroborated by the account's real expiry — passwd -S read L for everyone
(apply locks all passwords always), so its locked/active was meaningless —
and flags a mismatch loudly as drift.
- Perms are part of the converged state: ~/.ssh and authorized_keys ownership
and mode converge on every run, not only when content changes — StrictModes
treats them as load-bearing, so drifted perms were a broken login that
"already converged" lied about. Only the content write stays cmp-guarded.
- close-root's admin-door gate checks the StrictModes shape per candidate —
ownership, group/world-writability of home/.ssh/authorized_keys, a real
login shell, an unexpired account — and names which check failed. It proves
the door SHOULD open, not that it does; the separate-session advisory stays
load-bearing.
- Usernames are validated in the parser's one-pass refusal matrix
(^[a-z_][a-z0-9_-]{0,31}$): 'fo|o' corrupted the parser's own '|'-delimited
stream, and a leading '-' read as a useradd flag mid-convergence.
- The box role is trait-aware: on a host=no box an absent incus group skips
the role with a warning and converges everything else — one box-role user
in a fleet-wide file must not abort apply everywhere VMs don't live.
host=yes still dies pointing at box setup-host; a classless marker warns
toward a bootstrap re-run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:19 +00:00
# Admin-door gate — never close the only door. Root SSH goes away below, so
# at least one rig-admin member must hold a login sshd would plausibly ACCEPT
# — a non-empty authorized_keys alone proves a file exists, not a door:
# StrictModes rejects keys behind wrongly-owned or group/world-writable
# paths, a nologin shell never logs in, and an expired account fails PAM
# before the key is read. So every candidate is checked for the StrictModes
# shape, and the refusal names, per candidate, WHICH check failed — an
# operator staring at a refusal must see the repair. Honestly: this proves
# the door SHOULD open per StrictModes, not that it does — the
# verify-in-a-separate-session advisory in --help stays load-bearing.
today = $(( $( date +%s) / 86400 ))
# path_strict <path> <uid> <label> — flag the two StrictModes complaints
flag( ) { bad = " ${ bad : + $bad , } $1 " ; }
path_strict( ) {
local p = " $1 " uid = " $2 " l = " $3 " o m
o = " $( stat -c '%u' " $p " 2>/dev/null) " || return 0
m = " $( stat -c '%a' " $p " 2>/dev/null) "
if [ " $o " != " $uid " ] ; then flag " $l not owned by the user " ; fi
if [ $(( 8# $m & 8#022 )) -ne 0 ] ; then flag " $l is group/world-writable (mode $m ) " ; fi
}
feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
ADMIN_OK = 0
fix(users): review findings — invoker gate, real SSH revocation, StrictModes-shaped close-root gate, trait-aware box role
Seven review findings on the users family, each with the harness check that
would have caught it:
- Invoker gate (apply + close-root): %rig's sudoers rule is binary-scoped but
not argument-scoped, so `sudo rig users apply --file <me-as-admin>` made
role rig silently root-equivalent through the very command that granted it.
Identity management now refuses any sudo invoker outside rig-admin; direct
root (bring-up, a root shell) proceeds.
- Offboarding revokes SSH, not just the password: a '!'-locked password is
not a closed door under UsePAM — Debian sshd still honors the pubkey. A
dropped user's account is now expired (usermod -L -e 1, the switch PAM
actually enforces) and authorized_keys is renamed to
authorized_keys.revoked-by-rig — access revoked, data kept, convergence
never destroys. Present users get their expiry cleared idempotently, so a
re-added user comes back to life.
- The ledger remembers: two-field lines ('name active' / 'name revoked',
legacy bare names read as active), so dropped users no longer vanish from
rig's memory on the next rewrite. status now reports the ledger state
corroborated by the account's real expiry — passwd -S read L for everyone
(apply locks all passwords always), so its locked/active was meaningless —
and flags a mismatch loudly as drift.
- Perms are part of the converged state: ~/.ssh and authorized_keys ownership
and mode converge on every run, not only when content changes — StrictModes
treats them as load-bearing, so drifted perms were a broken login that
"already converged" lied about. Only the content write stays cmp-guarded.
- close-root's admin-door gate checks the StrictModes shape per candidate —
ownership, group/world-writability of home/.ssh/authorized_keys, a real
login shell, an unexpired account — and names which check failed. It proves
the door SHOULD open, not that it does; the separate-session advisory stays
load-bearing.
- Usernames are validated in the parser's one-pass refusal matrix
(^[a-z_][a-z0-9_-]{0,31}$): 'fo|o' corrupted the parser's own '|'-delimited
stream, and a leading '-' read as a useradd flag mid-convergence.
- The box role is trait-aware: on a host=no box an absent incus group skips
the role with a warning and converges everything else — one box-role user
in a fleet-wide file must not abort apply everywhere VMs don't live.
host=yes still dies pointing at box setup-host; a classless marker warns
toward a bootstrap re-run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:19 +00:00
CANDIDATES = 0
DETAIL = ""
feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
while IFS = read -r a; do
[ -n " $a " ] || continue
fix(users): review findings — invoker gate, real SSH revocation, StrictModes-shaped close-root gate, trait-aware box role
Seven review findings on the users family, each with the harness check that
would have caught it:
- Invoker gate (apply + close-root): %rig's sudoers rule is binary-scoped but
not argument-scoped, so `sudo rig users apply --file <me-as-admin>` made
role rig silently root-equivalent through the very command that granted it.
Identity management now refuses any sudo invoker outside rig-admin; direct
root (bring-up, a root shell) proceeds.
- Offboarding revokes SSH, not just the password: a '!'-locked password is
not a closed door under UsePAM — Debian sshd still honors the pubkey. A
dropped user's account is now expired (usermod -L -e 1, the switch PAM
actually enforces) and authorized_keys is renamed to
authorized_keys.revoked-by-rig — access revoked, data kept, convergence
never destroys. Present users get their expiry cleared idempotently, so a
re-added user comes back to life.
- The ledger remembers: two-field lines ('name active' / 'name revoked',
legacy bare names read as active), so dropped users no longer vanish from
rig's memory on the next rewrite. status now reports the ledger state
corroborated by the account's real expiry — passwd -S read L for everyone
(apply locks all passwords always), so its locked/active was meaningless —
and flags a mismatch loudly as drift.
- Perms are part of the converged state: ~/.ssh and authorized_keys ownership
and mode converge on every run, not only when content changes — StrictModes
treats them as load-bearing, so drifted perms were a broken login that
"already converged" lied about. Only the content write stays cmp-guarded.
- close-root's admin-door gate checks the StrictModes shape per candidate —
ownership, group/world-writability of home/.ssh/authorized_keys, a real
login shell, an unexpired account — and names which check failed. It proves
the door SHOULD open, not that it does; the separate-session advisory stays
load-bearing.
- Usernames are validated in the parser's one-pass refusal matrix
(^[a-z_][a-z0-9_-]{0,31}$): 'fo|o' corrupted the parser's own '|'-delimited
stream, and a leading '-' read as a useradd flag mid-convergence.
- The box role is trait-aware: on a host=no box an absent incus group skips
the role with a warning and converges everything else — one box-role user
in a fleet-wide file must not abort apply everywhere VMs don't live.
host=yes still dies pointing at box setup-host; a classless marker warns
toward a bootstrap re-run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:19 +00:00
CANDIDATES = $(( CANDIDATES + 1 ))
bad = ""
uid = " $( id -u " $a " 2>/dev/null) " || { DETAIL = " $DETAIL ; $a : no such user " ; continue ; }
ent = " $( getent passwd " $a " ) "
h = " $( printf '%s' " $ent " | cut -d: -f6) "
shell = " $( printf '%s' " $ent " | cut -d: -f7) "
if [ ! -d " $h " ] ; then
flag "home directory missing"
else
path_strict " $h " " $uid " "home"
if [ ! -d " $h /.ssh " ] ; then
flag ".ssh directory missing"
else
path_strict " $h /.ssh " " $uid " ".ssh directory"
if [ ! -s " $h /.ssh/authorized_keys " ] ; then
flag "authorized_keys missing or empty"
else
path_strict " $h /.ssh/authorized_keys " " $uid " "authorized_keys"
fi
fi
fi
case " $shell " in
*/nologin| */false) flag " login shell $shell never logs in " ; ;
esac
# shadow field 8 is the expiry in days-since-epoch; empty means never.
exp = " $( getent shadow " $a " 2>/dev/null | cut -d: -f8) "
if [ -n " $exp " ] && [ " $exp " -le " $today " ] 2>/dev/null; then
flag "account expired"
fi
if [ -z " $bad " ] ; then ADMIN_OK = 1; break; fi
DETAIL = " $DETAIL ; $a : $bad "
feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
done < <( getent group rig-admin | cut -d: -f4 | tr ',' '\n' )
fix(users): review findings — invoker gate, real SSH revocation, StrictModes-shaped close-root gate, trait-aware box role
Seven review findings on the users family, each with the harness check that
would have caught it:
- Invoker gate (apply + close-root): %rig's sudoers rule is binary-scoped but
not argument-scoped, so `sudo rig users apply --file <me-as-admin>` made
role rig silently root-equivalent through the very command that granted it.
Identity management now refuses any sudo invoker outside rig-admin; direct
root (bring-up, a root shell) proceeds.
- Offboarding revokes SSH, not just the password: a '!'-locked password is
not a closed door under UsePAM — Debian sshd still honors the pubkey. A
dropped user's account is now expired (usermod -L -e 1, the switch PAM
actually enforces) and authorized_keys is renamed to
authorized_keys.revoked-by-rig — access revoked, data kept, convergence
never destroys. Present users get their expiry cleared idempotently, so a
re-added user comes back to life.
- The ledger remembers: two-field lines ('name active' / 'name revoked',
legacy bare names read as active), so dropped users no longer vanish from
rig's memory on the next rewrite. status now reports the ledger state
corroborated by the account's real expiry — passwd -S read L for everyone
(apply locks all passwords always), so its locked/active was meaningless —
and flags a mismatch loudly as drift.
- Perms are part of the converged state: ~/.ssh and authorized_keys ownership
and mode converge on every run, not only when content changes — StrictModes
treats them as load-bearing, so drifted perms were a broken login that
"already converged" lied about. Only the content write stays cmp-guarded.
- close-root's admin-door gate checks the StrictModes shape per candidate —
ownership, group/world-writability of home/.ssh/authorized_keys, a real
login shell, an unexpired account — and names which check failed. It proves
the door SHOULD open, not that it does; the separate-session advisory stays
load-bearing.
- Usernames are validated in the parser's one-pass refusal matrix
(^[a-z_][a-z0-9_-]{0,31}$): 'fo|o' corrupted the parser's own '|'-delimited
stream, and a leading '-' read as a useradd flag mid-convergence.
- The box role is trait-aware: on a host=no box an absent incus group skips
the role with a warning and converges everything else — one box-role user
in a fleet-wide file must not abort apply everywhere VMs don't live.
host=yes still dies pointing at box setup-host; a classless marker warns
toward a bootstrap re-run.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 20:01:19 +00:00
if [ " $ADMIN_OK " -ne 1 ] ; then
if [ " $CANDIDATES " -eq 0 ] ; then
die "no admin user with a key on this box — run rig users apply first; never close the only door"
fi
die " no rig-admin member would pass sshd's door ${ DETAIL } — repair, prove the login in a separate session, then re-run; never close the only door "
fi
feat(users): close-root — shut the human-class root door once an admin key works
class decides root SSH's fate, and this is human's: install
/etc/ssh/sshd_config.d/00-rig-users.conf (PermitRootLogin no), where the NAME
is the mechanism — sshd_config is first-wins, the Include glob expands
lexically, and '-' sorts before '.', so it is read before bootstrap's
00-rig.conf and wins. Gated three ways, no --force: a marker must exist
(never shut the root door blind), it must say class=human (on a server root
is the control plane's automation identity — closing it severs fleet
management), and some rig-admin member must already hold a non-empty
authorized_keys (never close the only door). The gate's policy lives in the
lib as assert_marker_human so the harness proves every refusal against
fixture markers as non-root; RIG_ROLE_MARKER keeps the command pointable at
the same fixtures. Apply is bootstrap's validate-then-apply shape verbatim —
cmp-guard, sshd -t on the merged config before the restart with rollback,
then the sshd -T effective assertion. Bootstrap's own permitrootlogin
assertion widens to accept 'no': the closed door is strictly harder, never
broken, and by first-wins bootstrap cannot reopen it.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:28:25 +00:00
# --- the drop-in --------------------------------------------------------------
# The NAME is the entire mechanism: sshd_config is FIRST-wins ("for each
# keyword, the first obtained value will be used" — sshd_config(5)), Include
# expands its glob in lexical order, and '-' (0x2D) sorts before '.' (0x2E),
# so 00-rig-users.conf is read BEFORE bootstrap's 00-rig.conf and this
# PermitRootLogin beats its prohibit-password. Rename the file and it silently
# loses that fight — the harness asserts the comparison the glob makes.
DROPIN = /etc/ssh/sshd_config.d/00-rig-users.conf
TMP = " $( mktemp) "
printf 'PermitRootLogin no\n' > " $TMP "
if cmp -s " $TMP " " $DROPIN " 2>/dev/null; then
rm -f " $TMP "
log "root already closed; nothing to do"
exit 0
fi
BACKUP = ""
[ -e " $DROPIN " ] && { BACKUP = " $( mktemp) " ; cp -a " $DROPIN " " $BACKUP " ; }
install -m 0644 " $TMP " " $DROPIN "
rm -f " $TMP "
# Validate the MERGED config BEFORE bouncing the daemon (the bootstrap shape):
# on a box whose only door is SSH — exactly what this box is about to become —
# restarting into a config the daemon refuses to parse leaves no listener and
# no way back in. Roll back and stop rather than shut the door on a maybe.
if ! sshd -t 2>/dev/null; then
if [ -n " $BACKUP " ] ; then cp -a " $BACKUP " " $DROPIN " ; else rm -f " $DROPIN " ; fi
rm -f " $BACKUP "
die "sshd rejects the merged config; drop-in rolled back, daemon untouched. Run 'sshd -t' to see which file is bad."
fi
rm -f " $BACKUP "
systemctl restart ssh
# Assert the EFFECTIVE config, not the file's existence — a drop-in sorting
# even earlier would win the first-wins fight silently. `sshd -T` is what the
# daemon actually resolved.
eff = " $( sshd -T 2>/dev/null) " || die "sshd -T failed; refusing to claim root is closed"
echo " $eff " | grep -qx 'permitrootlogin no' \
|| die " sshd still resolves permitrootlogin != no — a drop-in is beating ${ DROPIN } ; check ls /etc/ssh/sshd_config.d/ "
log "root door closed (sshd -T resolves permitrootlogin no); humans enter as themselves now"