Skip to content

Commit 3a3c078

Browse files
committed
docs: [#411] add alternative detection approach and ADR task to spec
1 parent 541765b commit 3a3c078

1 file changed

Lines changed: 46 additions & 3 deletions

File tree

docs/issues/411-bug-ssh-key-passphrase-breaks-automated-deployment.md

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -68,12 +68,47 @@ The reliable detection approach is to read the first line of the PEM file:
6868
- OpenSSH format: the body begins with `bcrypt` if passphrase-protected; the header
6969
alone is not sufficient — the first few bytes of the decoded body must be checked.
7070

71-
The simplest robust approach in Rust is to read the raw bytes of the key file and check:
71+
### Detection Approaches Considered
72+
73+
Two approaches were evaluated for detecting whether a key is passphrase-protected:
74+
75+
#### Option A — Byte inspection (chosen)
76+
77+
Read the raw bytes of the key file and check:
7278

7379
- For legacy PEM: `ENCRYPTED` appears in the header
7480
- For OpenSSH format: after the base64-decoded body, the string `bcrypt` appears near
7581
the start (OpenSSH uses bcrypt KDF for passphrase derivation)
7682

83+
| | |
84+
| --- | ------------------------------------------------------------------------------- |
85+
|| Pure Rust, no external tools required |
86+
|| Fast — reads only the first ~64 bytes of the file |
87+
|| Works in any environment, including minimal Docker images |
88+
|| No process spawning overhead |
89+
|| Must handle two PEM variants (legacy `ENCRYPTED` header, OpenSSH `bcrypt` body) |
90+
|| False-negative risk for exotic or future key formats (acceptable per spec) |
91+
92+
#### Option B — `ssh-keygen -y` probe
93+
94+
Spawn `ssh-keygen -y -f <key> < /dev/null`: this command attempts to derive the public
95+
key from the private key and exits non-zero with a passphrase prompt when the key is
96+
encrypted.
97+
98+
| | |
99+
| --- | --------------------------------------------------------------------------------------------------- |
100+
|| Handles all key formats transparently — no format-specific parsing |
101+
|| Implementation is a single `std::process::Command` call |
102+
|| Requires `ssh-keygen` to be present in the runtime environment |
103+
|| Spawns an external process just for detection |
104+
|| Must distinguish "encrypted" from "file not found" / "unsupported format" via exit codes and stderr |
105+
|| Slower than reading a few bytes from a file |
106+
107+
**Chosen approach**: Option A (byte inspection). The deployer already targets Docker
108+
containers where `ssh-keygen` may not be installed, and the detection is best-effort
109+
(a missed warning is acceptable — a false positive is not). An ADR documents this
110+
choice in full (see Implementation Plan).
111+
77112
The check only needs to be a best-effort heuristic — it is used to emit a warning, not
78113
to block the user. A false negative (missing the warning) is acceptable; a false
79114
positive (warning for an unencrypted key) would be confusing and should be avoided.
@@ -200,14 +235,22 @@ configured private key appears to be passphrase-protected.
200235
with and without passphrase if available, or by constructing the minimal PEM
201236
structure in the test)
202237

203-
### Phase 2: Documentation
238+
### Phase 2: ADR
239+
240+
- [ ] Create `docs/decisions/XXX-ssh-key-passphrase-detection.md` documenting:
241+
- Why byte inspection was chosen over the `ssh-keygen -y` probe
242+
- Pros and cons of each approach
243+
- Consequences and limitations (best-effort, false-negative acceptable)
244+
- [ ] Register the new ADR in `docs/decisions/README.md`
245+
246+
### Phase 3: Documentation
204247

205248
- [ ] Create `docs/user-guide/ssh-keys.md` covering all workflows and security notes
206249
- [ ] Update `docs/user-guide/providers/hetzner.md` with an SSH key requirements note
207250
- [ ] Update `docs/user-guide/commands/create.md` to mention the passphrase warning
208251
- [ ] Update `docs/user-guide/README.md` to link to the new `ssh-keys.md` page
209252

210-
### Phase 3: Linting and pre-commit
253+
### Phase 4: Linting and pre-commit
211254

212255
- [ ] Run linters: `cargo run --bin linter all`
213256
- [ ] Run pre-commit: `./scripts/pre-commit.sh`

0 commit comments

Comments
 (0)