Commit graph

3 commits

Author SHA1 Message Date
dan-claude-bot
fff45a9835 feat(users): @root seeds the admin's keys from root's own — the one source that cannot lock you out
The headline of #17: rig can verify a lot locally, but never that the
operator HOLDS the admin's private key. Seeding authorized_keys from root's
current /root/.ssh/authorized_keys turns that unprovable claim into a proven
one — the operator is connected as root right now using one of those keys.
The users file gains the literal key-field token '@root', shape-validated in
the parse pass (exit 2, pre-root-check, testable non-root); apply resolves
it once after the root check, dies with the repair when root has no keys to
seed, copies key lines verbatim (options included — rig will not silently
widen what a key can do), and writes seeded keys first with literal lines
appended, so the cmp-guard keeps re-runs convergent to root's then-current
keys plus the literals.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-18 14:53:30 +00:00
Claude
3eeab687d0 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
Claude
9bdc4db575 feat(users): declarative operators — apply/status over a users file, every class
Operators become a declared fact, not an accumulation of adduser runs: a
line-based, bash-parseable users file (no YAML, no jq — a rig box has
neither) names each user, their roles, and their keys, and apply converges
the box to exactly that. Roles map to groups (admin→rig-admin with full
NOPASSWD sudo, rig→rig sudo for the rig binary only, box→incus with no
sudo — box's setup-host owns Incus, rig only asserts the group). Every
password stays locked always; the SSH key at the door is the
authentication. A user dropped from the file is found via the /etc/rig/users
ledger and locked, never deleted — deleting frees the uid and rots
attribution. The sudoers drop-in lands only after visudo -c passes, because
a bad file under sudoers.d takes down all of sudo. Class never gates apply
(#26: a shared root login is unattributable, so operators belong on every
class); the marker only colors what root SSH does next. The whole file is
validated in one pass before the root check, every error named with its
line, so refusals are provable in the non-root harness through the sourced
parser.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-17 19:23:47 +00:00