You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Merge #421: feat: [#411] detect passphrase-protected SSH key and warn during create environment
e688a7c feat: [#411] detect passphrase-protected SSH key and warn during create environment (Jose Celano)
3a3c078 docs: [#411] add alternative detection approach and ADR task to spec (Jose Celano)
Pull request description:
## Summary
Closes#411
When a passphrase-protected SSH private key is configured in `ssh_credentials`, the deployer previously failed silently during `provision` with a misleading `Permission denied (publickey,password)` error. This PR adds an early non-blocking warning during `create environment` so users can make an informed decision before reaching `provision`.
## Changes
### Detection (`src/adapters/ssh/key_inspector.rs` — new module)
- Moved passphrase detection out of `credentials.rs` into a dedicated `key_inspector` module (cleaner separation of concerns)
- `is_passphrase_protected(path: &Path) -> bool` — best-effort heuristic:
- Legacy PEM: checks for `BEGIN ENCRYPTED PRIVATE KEY` / `Proc-Type: 4,ENCRYPTED` header
- OpenSSH format: base64-decodes the body and scans first 100 bytes for `bcrypt` KDF marker
- Uses the `base64` crate (now a direct dependency; was already transitive at v0.22.1) instead of an inline decoder
- 4 unit tests covering: unencrypted key, passphrase-protected key, missing file, legacy PEM header
### Warning (`src/presentation/cli/views/progress/mod.rs`, `handler.rs`)
- Added `ProgressReporter::warn()` for advisory user-facing messages
- `CreateEnvironmentCommandController::execute()` calls `warn_if_ssh_key_passphrase_protected()` after config load — emits a detailed warning with 3 resolution options but does **not** block environment creation
### Test fixture
- `fixtures/testing_ed25519_encrypted` — real ed25519 key protected with passphrase `"password"`, used in unit tests
### ADR
- `docs/decisions/ssh-key-passphrase-detection.md` — documents the byte inspection approach and why `ssh-keygen -y` probe was rejected
### Documentation
- `docs/user-guide/ssh-keys.md` — new page covering SSH key requirements, 3 supported workflows, how to remove a passphrase, and security notes
- `docs/user-guide/providers/hetzner.md` — added SSH Key Requirements section with warning callout
- `docs/user-guide/commands/create.md` — added passphrase warning subsection in Troubleshooting
- `docs/user-guide/README.md` — added SSH Keys subsection under Security with link
## Testing
All pre-commit checks pass:
- `cargo machete` ✅
- `cargo run --bin linter all` (clippy stable + nightly, rustfmt, cspell, shellcheck, markdownlint, yamllint, taplo) ✅
- `cargo test` (417 tests) ✅
- `cargo doc` ✅
- E2E infrastructure lifecycle tests ✅
- E2E deployment workflow tests ✅
ACKs for top commit:
josecelano:
ACK e688a7c
Tree-SHA512: 9cd13240680cf1cd248c0b34f0e2df88b302a52823d92e9f8e5b7ad0976d57a47c475d3ed765b43dda66866f0d248f2d4d7b972a5ad76d36095087b4c65fb857
| ✅ Accepted | 2026-02-26 |[SDK Package Naming](./sdk-package-naming.md)| Keep "SDK" name for packages/sdk — the modern API-wrapper meaning is industry-standard |
10
11
| ✅ Accepted | 2026-02-24 |[SDK Presentation Layer Interface Design](./sdk-presentation-layer-interface-design.md)| Return () from SDK operations; no domain types or typestate pattern in the SDK public API |
11
12
| ✅ Accepted | 2026-02-24 |[Docker Compose Local Validation Placement](./docker-compose-local-validation-placement.md)| Validate rendered docker-compose.yml in the infrastructure generator, not the app layer |
When you run the deployer inside a Docker container (the recommended approach for
153
+
Hetzner), there is no SSH agent and no interactive terminal. A passphrase-protected
154
+
private key will cause the `provision` step to fail with
155
+
`Permission denied (publickey,password)`.
156
+
157
+
**Options**:
158
+
159
+
1.**Remove the passphrase** (recommended for dedicated deployment keys):
160
+
161
+
```bash
162
+
ssh-keygen -p -f ~/.ssh/your_deployment_key
163
+
# Press Enter twice for an empty new passphrase
164
+
```
165
+
166
+
2.**Forward your SSH agent** into the container (see [SSH Keys Guide](../ssh-keys.md#workflow-2--passphrase-protected-key-with-ssh-agent-forwarding-into-docker)).
167
+
168
+
The `create environment` command will warn you if it detects a passphrase-protected key
169
+
so you can resolve this before reaching `provision`.
170
+
171
+
For more detail on generating keys, removing passphrases, and security considerations,
172
+
see the [SSH Keys Guide](../ssh-keys.md).
173
+
148
174
## SSH Key Behavior
149
175
150
176
Hetzner deployments configure SSH access through two mechanisms:
0 commit comments