Enrol the keys that already have access when governance is turned on #100
Loading…
Reference in a new issue
No description provided.
Delete branch "worktree-agent-a0b36369492c01629"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Fixes
54b04c35.keydir/supersedesauthorized_keysthe instantsettings.gitexists —auth_publickeyreads one roster or the other, never both — sosetup, beinga one-key bootstrap, revoked SSH for everybody but the administrator at the
moment it ran. On the live deployment that is one of two keys.
The supersession is right; two rosters that can disagree is worse. What was
wrong is that the rest of
setupis built on the opposite principle: the filegoes to considerable lengths — an end-to-end exposure test, a run-time
verify()— to guarantee that enabling governance changes nothing, and authwas the one axis where it did.
What changed
Setup copies every well-formed
authorized_keysentry intokeydir/, leavingthe file itself untouched, and translates the
server.tomlrosters that namedthose keys by fingerprint. Previously only the admin's own key could be
translated, because it was the only fingerprint with a name to write.
Default versus flag
Enrolling is the default;
--no-enrol-existingis the flag. The two mistakesare not symmetric: an over-broad roster is a
git rmand a push, a lockout mayneed filesystem access to the box. An opt-in flag also only helps operators who
already know it exists, and the ones who do not are exactly the ones who get
locked out. On a fresh server the default and the flag do the same thing, so
defaulting to safe costs nothing where there is nothing to lose. Declining
prints how many keys it just locked out.
Naming
An
authorized_keysentry has no identity and the keydir basename is theprincipal, so this is the real work.
The entry's trailing comment names it where the comment can be a principal
name, used verbatim and never cut down to
alex@laptop's first component.Truncation is the guess that merges:
alex@laptopandalex@desktopwouldcollapse into one principal, and a rule written for one would silently cover
the other. Widening by inference is the one thing this bootstrap must not do.
For the same reason a name two entries both claim goes to neither — one of them
winning by file order puts a plausible name on the wrong key.
Everything else gets
key1,key2: obviously provisional rather than plausiblywrong, printed with the key's fingerprint so the operator can tell which key
wears which. Every derived name is reported, by
--dry-runtoo, while renamingis still a file rename and a push.
What happens to an entry that cannot be named or parsed
Nothing fatal. A malformed line, an entry carrying
command=options, aduplicate key, the admin's own key, a missing file — each is reported by
authorized_keysline number and skipped. Aborting over one bad line wouldkeep nobody's access, which is the opposite of the point. Entries with options
are noted as not authenticating today either (
is_authorizedcompares thefirst field against the key type), so skipping them is faithful rather than a
narrowing.
verify()Grows the matching half. The generated rules are asked, through the real
evaluator, whether each enrolled principal gets exactly the read, write and
rewind its
server.tomlgives it today. Rewind is compared against the writedecision deliberately: nothing before governance gated a force-push separately
from a push. A mismatch aborts before anything is written.
Tests
Both against a live server over real SSH, TDD, watched failing first.
authorized_keyswith three different reaches across threerepositories (blanket
*, a one-fingerprint roster, a read-only roster),measured before setup and after. Same shape as the existing exposure test,
applied to auth.
and a line that is not a key: setup must succeed, and the report must name
what each got.
--dry-runshows the derived names;--no-enrol-existingyields thesingle-admin bootstrap and says what it cost.
Mutation-checked both ways: dropping an enrolled key fails the first test on
exactly that key's row, and translating only the admin's roster entry is caught
by
verify()before the seed is written.Incidental
Also fixes a latent bug in
tests/common: the server's stdout and stderr arepipes nothing ever read, so the server blocked on its next log line partway
through any test making enough requests. It presented as a hang on whichever
operation crossed 64 KiB — this test was simply the first to make enough
requests to reach it. Drained now, with the buffer also serving the existing
error messages.
Verification
cargo clippy --all-targets: silent.tests/tui_review_test.rstwice: 23 passed both runs, 3.17s both runs, novariance.
Unverified
authorized_keys; the livetwo-key case is covered by the admin-key-already-present test, not by the
actual file.
PublicKey::from_opensshbut onlyed25519 is tested.
is_authorizedhas a deliberate RSA type-name leniency(
ssh-rsavsrsa-sha2-*) that enrolment does not need, since it matches onfingerprint — believed equivalent, not tested.
authorized_keysentries with options are reported under the same message asmalformed lines rather than detected as options specifically.
Migrated from git-collab patche
65c569f32b733b879f19c5997bb587522a1706f1.Original author: a73x dev@a73x.sh.
Fixes #97
Revision 1:
8bbdad3e73547570048f78f0ace837f3278a2253; basef3d6c51a310971e38d750cc70c5cae3cfbb1bfdd