Decompose architecture into 35 atomic tasks across 10 generations for implementation

This commit is contained in:
2026-06-02 09:02:55 +00:00
parent b5c59ef3bc
commit 14dbd81195
35 changed files with 1636 additions and 0 deletions

View File

@@ -0,0 +1,40 @@
---
id: auth/client-auth-handler
name: Implement client-side SSH authentication with Ed25519 key pairs
status: pending
depends_on:
- auth/key-loading
- auth/error-types
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement the client-side SSH authentication. The client presents an Ed25519 private key during SSH handshake. This creates the `russh::client::Handler` implementation and the `russh::client::ConnectStreamConfig` that uses the loaded key.
No password auth. The client handler is simpler than the server — it just needs to provide the private key and handle the auth callback from russh.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/auth/client_auth.rs` exports `ClientAuthConfig` and client handler
- [ ] `ClientAuthConfig` holds: `private_key: KeyPair`, optional `public_key: PublicKey`
- [ ] `ClientAuthConfig::from_key_source(source: KeySource) -> Result<Self>` — loads key via key-loading module
- [ ] Implements `russh::client::Handler` with `auth_publickey()` returning the public key
- [ ] Client handler returns `russh::client::AuthResult::Accept` or appropriate auth state
- [ ] Unit tests: valid key creates handler, auth flow succeeds with mock SSH session
## References
- docs/architecture/client.md — "Authentication is Ed25519 public key or OpenSSH certificate (ADR-012)"
- docs/architecture/decisions/012-auth-ed25519-and-cert-authority.md — key-based auth only
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

47
tasks/auth/error-types.md Normal file
View File

@@ -0,0 +1,47 @@
---
id: auth/error-types
name: Define error types for transport, auth, channel, and configuration layers
status: pending
depends_on:
- setup/project-init
scope: narrow
risk: trivial
impact: phase
level: implementation
---
## Description
Define the error hierarchy per the overview.md layered error pattern:
- **Transport errors** — connection failures, TLS handshake failures, iroh endpoint errors
- **Auth errors** — key rejection, certificate validation failures, missing keys
- **Channel errors** — target unreachable, proxy failure
- **Config errors** — invalid flags, key file not found, bind failure
Use `thiserror` for structured error types propagated via `anyhow::Result` in the public API. The key design: transport/auth errors cause reconnection (client) or rejection (server). Channel-level errors close that channel without killing the session.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/error.rs` exports error types
- [ ] `TransportError` enum: `ConnectionFailed`, `HandshakeFailed`, `Timeout`, `ProxyFailed`
- [ ] `AuthError` enum: `KeyRejected`, `CertInvalid`, `CertExpired`, `CertPrincipalMismatch`, `NoMatchingKey`
- [ ] `ChannelError` enum: `TargetUnreachable`, `ProxyConnectFailed`, `ChannelClosed`
- [ ] `ConfigError` enum: `InvalidFlag`, `KeyFileNotFound`, `BindFailed`, `IncompatibleOptions`
- [ ] All error types implement `std::error::Error` via `thiserror`, `Display`, and `Debug`
- [ ] Error types have `source` chaining where appropriate (e.g., `TransportError::HandshakeFailed { source: std::io::Error }`)
- [ ] Re-exported from `crates/wraith-core/src/lib.rs`
- [ ] Unit tests for Display output of each error variant
## References
- docs/architecture/overview.md — "Error handling follows a consistent layered pattern"
- docs/architecture/client.md — error handling section (transport → reconnect, channel → close)
- docs/architecture/server.md — error handling section
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

51
tasks/auth/key-loading.md Normal file
View File

@@ -0,0 +1,51 @@
---
id: auth/key-loading
name: Implement SSH key material loading (file paths and in-memory data)
status: pending
depends_on:
- auth/error-types
- setup/project-init
scope: narrow
risk: low
impact: component
level: implementation
---
## Description
Implement key material loading that accepts both file paths and in-memory data per the programmatic-first API (ADR-011). Key inputs (`--identity`, `--authorized-keys`, `--cert-authority`, `--key`) accept either:
- **File path**: load from filesystem
- **In-memory data**: raw key bytes provided programmatically
All keys must be in **OpenSSH key format** (not PEM/PKCS#1/PKCS#8). This module handles:
- Loading private keys (OpenSSH format: `-----BEGIN OPENSSH PRIVATE KEY-----`)
- Loading public keys (OpenSSH format: `ssh-ed25519 AAAA... user@host`)
- Loading authorized_keys files (standard OpenSSH format)
- Parsing `cert-authority` entries in authorized_keys
## Acceptance Criteria
- [ ] `crates/wraith-core/src/auth/keys.rs` exports key loading functions
- [ ] `KeySource` enum: `File(PathBuf)` and `Memory(Vec<u8>)` for unified key input handling
- [ ] `load_private_key(source: KeySource) -> Result<russh::key::KeyPair>` — loads OpenSSH private key from file or memory
- [ ] `load_public_keys(source: KeySource) -> Result<Vec<russh::key::PublicKey>>` — loads one or more public keys from authorized_keys format
- [ ] Parses standard `authorized_keys` format including options (e.g., `cert-authority,permit-port-forwarding ssh-ed25519 AAAA...`)
- [ ] `CertAuthorityEntry` struct: `public_key: PublicKey, options: Vec<String>` parsed from authorized_keys cert-authority lines
- [ ] Returns `ConfigError::KeyFileNotFound` for missing file paths
- [ ] Returns `ConfigError::InvalidFlag` with clear message for PEM-encoded (non-OpenSSH) keys
- [ ] Unit tests: load Ed25519 key from file, load from memory, parse authorized_keys with multiple entries, reject PEM format
## References
- docs/architecture/client.md — Key Material Format section
- docs/architecture/server.md — Key Material Format section
- docs/architecture/decisions/012-auth-ed25519-and-cert-authority.md — authorized_keys format with cert-authority
- docs/architecture/decisions/011-no-ssh-config-programmatic-api.md — programmatic-first, file paths or in-memory
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion

View File

@@ -0,0 +1,47 @@
---
id: auth/server-auth-handler
name: Implement server-side authentication (Ed25519 keys + OpenSSH cert-authority)
status: pending
depends_on:
- auth/key-loading
- auth/error-types
scope: moderate
risk: medium
impact: component
level: implementation
---
## Description
Implement the server-side SSH authentication logic per ADR-012:
1. **Ed25519 public key**: `auth_publickey()` checks presented key against the authorized set using constant-time comparison
2. **OpenSSH certificate authority**: validates presented certificate — checks CA signature, expiry, and principal restrictions (`permit-port-forwarding`, `no-pty`, `source-address`)
No password authentication over SSH. This is the `russh::server::Handler::auth_publickey()` implementation that the server handler will call.
## Acceptance Criteria
- [ ] `crates/wraith-core/src/auth/server_auth.rs` exports `ServerAuthConfig` and auth logic
- [ ] `ServerAuthConfig` holds: `authorized_keys: HashSet<PublicKey>`, `cert_authorities: Vec<CertAuthorityEntry>`
- [ ] `ServerAuthConfig::from_keys_and_ca()` constructor: loads authorized keys and cert-authority entries from provided key sources
- [ ] Auth check function: given a presented key/certificate, return `Accept` or `Reject`
- [ ] Ed25519 key matching uses constant-time comparison (via `russh`/`ssh-key` crate builtins)
- [ ] Certificate validation checks: CA signature valid, cert not expired, principal restrictions enforced
- [ ] Certificate options respected: `permit-port-forwarding`, `no-pty`, `source-address`
- [ ] Returns `AuthError::KeyRejected` or `AuthError::CertInvalid`/`CertExpired`/`CertPrincipalMismatch` on failure
- [ ] Unit tests: valid key accepted, invalid key rejected, cert-authority signed cert accepted, expired cert rejected, wrong principal rejected
## References
- docs/architecture/server.md — Authentication section
- docs/architecture/decisions/012-auth-ed25519-and-cert-authority.md — ADR for key + cert-authority
- docs/architecture/client.md — "Authentication is Ed25519 public key or OpenSSH certificate"
## Notes
> To be filled by implementation agent
## Summary
> To be filled on completion