|
| 1 | +# shimkit ssh |
| 2 | + |
| 3 | +SSH key + agent + known_hosts + perms hygiene. No third-party deps — |
| 4 | +every operation shells out to baseline `ssh-keygen`, `ssh-add`, |
| 5 | +`ssh-agent`, or `ssh`. Passphrases are never handled by shimkit: |
| 6 | +`ssh-keygen` prompts the user directly. |
| 7 | + |
| 8 | +Pure scanner logic lives in `src/shimkit/tools/ssh/scanner.py` |
| 9 | +(filesystem read + known_hosts parser + perms audit). The manager |
| 10 | +owns CommandRunner shell-outs. |
| 11 | + |
| 12 | +## Commands |
| 13 | + |
| 14 | +| Command | Purpose | |
| 15 | +|--------------------------------------------------|--------------------------------------------------------| |
| 16 | +| `shimkit ssh` | Interactive menu (read-only paths only). | |
| 17 | +| `shimkit ssh keys list` | List every recognised private key. | |
| 18 | +| `shimkit ssh keys generate NAME` | Generate a new key (MODERATE). | |
| 19 | +| `shimkit ssh keys rotate NAME` | Back up the old key + regenerate (MODERATE). | |
| 20 | +| `shimkit ssh agent status` | Show ssh-agent state + loaded keys. | |
| 21 | +| `shimkit ssh agent add KEY` | Pass-through to `ssh-add`. | |
| 22 | +| `shimkit ssh known-hosts audit` | Find duplicate entries. | |
| 23 | +| `shimkit ssh known-hosts prune` | Remove later duplicates (MODERATE). | |
| 24 | +| `shimkit ssh perms audit` | Check ~/.ssh modes against the config matrix. | |
| 25 | +| `shimkit ssh perms fix` | chmod every offender (MODERATE). | |
| 26 | +| `shimkit ssh config show [HOST]` | Print ~/.ssh/config; with HOST, expand via `ssh -G`. | |
| 27 | + |
| 28 | +Universal flags (`--quiet`, `--verbose`, `--log-file`, `--no-color`, |
| 29 | +`--color`, `--no-input`) go before any subcommand. Per-command flags |
| 30 | +(`--dry-run`, `--yes`, `--force`, `--json`, `--ssh-dir`, |
| 31 | +`--type`, `--comment`) go after. |
| 32 | + |
| 33 | +## Keys |
| 34 | + |
| 35 | +```bash |
| 36 | +shimkit ssh keys list # list every recognised key |
| 37 | +shimkit ssh keys list --json # machine-readable |
| 38 | + |
| 39 | +shimkit ssh keys generate id_work --type ed25519 --yes |
| 40 | +shimkit ssh keys generate id_work -C work@example # custom comment |
| 41 | +shimkit ssh keys generate id_work --dry-run # show command without running |
| 42 | + |
| 43 | +shimkit ssh keys rotate id_ed25519 --yes # backup + regenerate |
| 44 | +``` |
| 45 | + |
| 46 | +`rotate` moves the old key to `<name>.bak-YYYYMMDDHHMMSS` and the old |
| 47 | +`.pub` alongside, then runs `ssh-keygen` for a fresh pair. You're |
| 48 | +responsible for syncing the new public key to your authorized_keys |
| 49 | +on each server — shimkit prints the steps but pushes nowhere. |
| 50 | + |
| 51 | +## ssh-agent |
| 52 | + |
| 53 | +```bash |
| 54 | +shimkit ssh agent status # what's loaded |
| 55 | +shimkit ssh agent status --json # parses cleanly |
| 56 | +shimkit ssh agent add ~/.ssh/id_ed25519 |
| 57 | +``` |
| 58 | + |
| 59 | +`status` differentiates "agent not running" (exit 1, warning) from |
| 60 | +"agent running, no keys" (exit 0, info). |
| 61 | + |
| 62 | +## known_hosts hygiene |
| 63 | + |
| 64 | +```bash |
| 65 | +shimkit ssh known-hosts audit --json |
| 66 | +shimkit ssh known-hosts prune --yes |
| 67 | +``` |
| 68 | + |
| 69 | +`audit` reports any `(host, key_blob)` pair seen more than once. |
| 70 | +`prune` keeps the first occurrence and drops the rest; comments and |
| 71 | +blank lines are preserved verbatim. |
| 72 | + |
| 73 | +## Permission matrix |
| 74 | + |
| 75 | +`audit` flags any path whose mode is **laxer** than the configured |
| 76 | +expected mode. Stricter modes pass — a `0400` key is fine even though |
| 77 | +expected is `600`. |
| 78 | + |
| 79 | +Default matrix (`tools.ssh.perms`): |
| 80 | + |
| 81 | +| Path | Expected | |
| 82 | +|----------------------------|----------| |
| 83 | +| `~/.ssh` (the dir) | `700` | |
| 84 | +| Private keys | `600` | |
| 85 | +| `.pub` files | `644` | |
| 86 | +| `~/.ssh/config` | `644` | |
| 87 | +| `~/.ssh/known_hosts` | `644` | |
| 88 | +| `~/.ssh/authorized_keys` | `644` | |
| 89 | + |
| 90 | +```bash |
| 91 | +shimkit ssh perms audit --json # report only |
| 92 | +shimkit ssh perms fix --yes # chmod each offender |
| 93 | +shimkit ssh perms fix --dry-run # what would change |
| 94 | +``` |
| 95 | + |
| 96 | +## ~/.ssh/config |
| 97 | + |
| 98 | +```bash |
| 99 | +shimkit ssh config show # print ~/.ssh/config verbatim |
| 100 | +shimkit ssh config show gh # `ssh -G gh` — effective expansion |
| 101 | +``` |
| 102 | + |
| 103 | +The effective-expansion form is useful when `Include`s + multiple |
| 104 | +`Host` blocks make it non-obvious what options actually apply. |
| 105 | + |
| 106 | +## Configuration |
| 107 | + |
| 108 | +```json |
| 109 | +{ |
| 110 | + "tools": { |
| 111 | + "ssh": { |
| 112 | + "ssh_dir": "~/.ssh", |
| 113 | + "default_key_type": "ed25519", |
| 114 | + "perms": { |
| 115 | + "dir": "700", |
| 116 | + "private_key": "600", |
| 117 | + "public_key": "644", |
| 118 | + "config": "644", |
| 119 | + "known_hosts": "644", |
| 120 | + "authorized_keys": "644" |
| 121 | + } |
| 122 | + } |
| 123 | + } |
| 124 | +} |
| 125 | +``` |
| 126 | + |
| 127 | +`--ssh-dir PATH` overrides `ssh_dir` for one invocation — used by |
| 128 | +tests, also useful for editing a chroot's keys from outside. |
| 129 | + |
| 130 | +## Exit codes |
| 131 | + |
| 132 | +| Code | Meaning | |
| 133 | +|-----:|-------------------------------------------------------------| |
| 134 | +| 0 | success / no-op | |
| 135 | +| 1 | generic failure (overwrite refused, ssh-keygen non-zero, prompt cancelled, agent unreachable) | |
| 136 | +| 2 | Typer usage error | |
| 137 | +| 69 | EX_UNAVAILABLE — wrong platform | |
| 138 | +| 130 | SIGINT | |
| 139 | + |
| 140 | +## Platform support |
| 141 | + |
| 142 | +| Platform | Status | |
| 143 | +|----------|--------| |
| 144 | +| macOS | ✓ — OpenSSH 9.x ships with macOS; `ssh-keygen` / `ssh-add` baseline. | |
| 145 | +| Linux | ✓ — `openssh-client` package, also baseline. | |
| 146 | +| WSL | ✓ (Linux path). | |
| 147 | +| Windows | ✗ — out of charter. | |
| 148 | + |
| 149 | +## Security notes |
| 150 | + |
| 151 | +- **Passphrases never pass through shimkit.** `ssh-keygen` reads them |
| 152 | + interactively from the TTY; our `CommandRunner.run` call uses |
| 153 | + `capture_output=False` for that step. Nothing involving the |
| 154 | + passphrase is logged, captured, or echoed. |
| 155 | +- **No automatic key push.** `keys rotate` does *not* upload the new |
| 156 | + public key anywhere. You sync `authorized_keys` on each server |
| 157 | + yourself. |
| 158 | +- **`config show` is read-only.** `~/.ssh/config` is your domain; |
| 159 | + shimkit reads it but doesn't write to it. |
0 commit comments