ssh-client keeps ~/.ssh to what it knows: sweep every other path into a kept folder (issue 1017, ADR 0415)
mesh/merge-gate head acf075fa63cc merged into trunk 9675babf528b — pass: builds ssh-client → ace, g14, novox, shanks; no bus step; every machine composes w…
mesh/repo-check head acf075fa63cc merged into trunk 9675babf528b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
mesh/merge-gate head acf075fa63cc merged into trunk 9675babf528b — pass: builds ssh-client → ace, g14, novox, shanks; no bus step; every machine composes w…
mesh/repo-check head acf075fa63cc merged into trunk 9675babf528b — pass: its merge-check.sh passed, each part in its toolchain: merge-check.sh (go), merge-…
mesh/delivery delivered
ssh-client owns ~/.ssh, but treated every file it did not place as found, so a predecessor's drop-in and old copies stayed there for weeks. Its process now sweeps at start and every ten minutes: keys at any depth (and links to them), authorized_keys, authorized_keys2 and sockets stay, and a folder holding one is gone into; everything else moves to ~/.local/state/ssh-client/retired/<UTC time>/, a folder never shared with another sweep, with a list beside it, which ssh_client_check shows. Nothing is deleted.
This commit is contained in:
@@ -8,12 +8,15 @@ item 9). `sshd` is the machine's side.
|
||||
| path | class | how |
|
||||
|---|---|---|
|
||||
| `~/.ssh/` | owned | directory, the account's, mode 0700 |
|
||||
| `~/.ssh/config.d/` | owned | directory, the account's, mode 0700. This is ssh's own drop-in directory: another module that needs a host (a forge, a work bastion) places its own file here |
|
||||
| `~/.ssh/config.d/` | owned | directory, the account's, mode 0700. It holds only the files this module declares (ADR 0415 §6). As every module's owned path, ssh-client is the only module that writes under `~/.ssh`: another module that needs a host there provides it through a provision, and this module writes it (ADR 0349 §1; the forge's host comes from `forge-account`). A host of the operator's own goes in `~/.ssh/config` below the mesh's region |
|
||||
| `~/.ssh/config.d/10-forge` | owned | the forge's host, with the machine's own key (ADR 0399) |
|
||||
| `~/.ssh/mesh_ed25519`, `.pub` | written by the module's own process | the machine's account's key for the forge, made once (keykeeper.go) |
|
||||
| `~/.ssh/config.d/00-mesh` | owned | a Host block per other machine of the mesh (a roster fact), regenerated whenever a machine joins, leaves or is renamed. It begins with the mesh's header |
|
||||
| `~/.ssh/config` | written into, **at the start** | the mesh's region, `# BEGIN mesh ssh-client.config` … `# END …`. It holds one `Include ~/.ssh/config.d/*`. Every line below the region is the operator's, kept byte for byte |
|
||||
| `~/.ssh/authorized_keys` | found | see *The gap* |
|
||||
| `~/.ssh/known_hosts` | found | see *The gap* |
|
||||
| private keys | found | never read, never written. `ssh_client_keys` and `ssh_client_check` read a file's first line to recognise a key, and ask `ssh-keygen` about it |
|
||||
| private keys, public keys, certificates | kept | never read, never written, never moved, at any depth and whatever their name. `ssh_client_keys` and `ssh_client_check` read a file's first line to recognise a key, and ask `ssh-keygen` about it |
|
||||
| anything else | swept | moved out of `~/.ssh` by the sweep (below) |
|
||||
|
||||
### Why an include, not Host blocks in the region
|
||||
|
||||
@@ -54,23 +57,24 @@ So both files stay *found*, and this module invents nothing.
|
||||
Each region is then one more `into: block` resource here. Until then, `ssh_client_check` reads the
|
||||
files as they are, and `ssh_client_revoke` / `ssh_client_known_host` act on them by hand.
|
||||
|
||||
## The one-off clean-up (ADR 0182: the operator removes a predecessor's output, once)
|
||||
## The sweep: `~/.ssh` holds only what this module knows (ADR 0415, issue 1017)
|
||||
|
||||
The mesh removes nothing it did not make. After the first push, a machine that had the predecessor's
|
||||
layout still holds:
|
||||
The module owns `~/.ssh`, so it keeps it to what it knows. Its process sweeps when it starts and every
|
||||
ten minutes. It knows the paths in the table above, every key (a private key by its first line, a
|
||||
public key or certificate by its line, at any depth and whatever its name) and sockets (ssh's own
|
||||
runtime). Every other path is moved, whole, into
|
||||
`~/.local/state/ssh-client/retired/<UTC time>/`, keeping its path relative to `~/.ssh`, and the list
|
||||
of what moved is written beside it as `<UTC time>.json`. Nothing is deleted. A folder that holds a key
|
||||
is gone into rather than moved whole, so the key stays where it is. A file or folder that cannot be read to
|
||||
tell whether it holds a key is left and named in the log.
|
||||
|
||||
1. **The predecessor's `~/.ssh/config.d/mesh`.** Its hosts repeat the mesh's, with the same values,
|
||||
plus one forge host that no module carries yet. Move that host to your own part of
|
||||
`~/.ssh/config`, or to a work module's drop-in, then delete the file.
|
||||
2. **The predecessor's header at the top of `~/.ssh/config`**, now just below the mesh's region: the
|
||||
comment beginning *"Mesh Host blocks are GENERATED"* and its `Include ~/.ssh/config.d/mesh` line.
|
||||
3. **A predecessor's block of mesh hosts marked *managed by sshd***, on the machines that still have
|
||||
one.
|
||||
4. **Backups beside the live files** (`config.bak-*`, `known_hosts.old`, `removed-*`). `ssh_client_check`
|
||||
lists them as debris.
|
||||
So the predecessor's `config.d/mesh`, `config.bak-*`, `known_hosts.old`, `removed-*` and
|
||||
`authorized_keys.revoked-*` leave `~/.ssh` at the first sweep. Each move is logged by the process, and
|
||||
`ssh_client_check` lists every sweep that moved something, with where each path lies now.
|
||||
|
||||
Until you do, `ssh_client_hosts` and `ssh_client_check` list the repeated hosts as duplicates. The
|
||||
mesh's still win, because they are read first.
|
||||
The predecessor's lines inside `~/.ssh/config`, below the mesh's region (its header comment and its
|
||||
`Include ~/.ssh/config.d/mesh`), are the operator's part of a written-into file and stay. ssh skips an
|
||||
`Include` that matches no file.
|
||||
|
||||
## Tools
|
||||
|
||||
@@ -80,7 +84,7 @@ They run as the operator account and never escalate. No answer carries a key: fi
|
||||
|---|---|---|
|
||||
| `ssh_client_hosts` | r | every Host and Match section in the order ssh reads it, each with file, line and source (`mesh`, `drop-in`, `operator`); duplicates; what is set outside any section |
|
||||
| `ssh_client_resolve` | r | `ssh -G` for one host: what ssh would use, and which sections matched |
|
||||
| `ssh_client_check` | r | modes of the directory and every file; keys with no passphrase (`ssh-keygen -y -P ""`, output used only to compare with the `.pub`), weak, old, unused, or whose `.pub` is another key's; one key under several names; the mesh's region first with its include; duplicate hosts; `known_hosts` for the mesh's machines, missing or stale (scanned live, 5 s each in parallel, unless `scan` is false); authorized keys without a comment; debris. It says what it did not check |
|
||||
| `ssh_client_check` | r | modes of the directory and every file; keys with no passphrase (`ssh-keygen -y -P ""`, output used only to compare with the `.pub`), weak, old, unused, or whose `.pub` is another key's; one key under several names; the mesh's region first with its include; duplicate hosts; `known_hosts` for the mesh's machines, missing or stale (scanned live, 5 s each in parallel, unless `scan` is false); authorized keys without a comment; debris; what the sweep moved out of `~/.ssh` and where each lies now. It says what it did not check |
|
||||
| `ssh_client_keys` | r | every private key, by its public half: type, size, fingerprint, comment, mode, age, passphrase, whether ssh offers it by itself |
|
||||
| `ssh_client_authorized` | r | `authorized_keys` by fingerprint, type, size, comment and options |
|
||||
| `ssh_client_revoke` | a | removes every line with one fingerprint, keeping the file first as `authorized_keys.revoked-<time>`. It refuses the last key (a lockout) and a key in a mesh region (a push would write it back) |
|
||||
|
||||
Reference in New Issue
Block a user