- overview.md: two-doors framing, family-position table, surface categories, bounded-resources policy (max_auth_attempts 10, keepalives 15s/3, per-channel caps), non-goals, lifecycle note - mapping.md: the spine — channel/request rows (git exec-only, route-1 bridge, translation-bridge session rows), parse-only dispatch, global-request rows, spawn-and-return constraint, per-build decline matrix - client.md: dial surface (key source + host-key policy + keepalives), op rows (family types in, vanilla out), fail-closed acceptor (not russh's accept-and-drop), lifecycle primitives only, POC correctness facts carried - auth.md: the fingerprint bridge + dispositions summary (server), vault custody + two-case host-key policy (client) - README index: doc table filled, current state updated
5.3 KiB
status, last_updated
| status | last_updated |
|---|---|
| draft | 2026-10-27 |
Auth — identity is the family's, not SSH's
The model
One resolution path exists on every surface: a verified key's
fingerprint → alkcall IdentityProvider::resolve_from_fingerprint →
Identity. russh performs all cryptographic work (ADR-002 pin):
auth_publickey fires after signature and ownership verification,
and invalid/malformed signatures produce USERAUTH_FAILURE before
any handler hook runs (POC-1/POC-2 facts). alkssh never sees or
verifies a signature; its auth surface is dispositional (ADR-004).
Server direction
The bridge
auth_publickey(user, VerifiedPublicKey)fires (post-verification; 0.65.0 shape — the key is the.public_keyfield, and the client's signature.algorithmis exposed for optional hardening).- alkssh computes the fingerprint via the
ssh-keyre-export (HashAlg::Sha256) —SHA256:+ unpadded url-safe base64. The string format is a contract invariant (ADR-004): parity with assembly-registered values is proven against independent issuers (ssh-keygen -lf— POC-1 §1). resolve_from_fingerprint→Some(identity)→ accept;None→ reject — fail-closed, and the resolved-identity-Nonepath never creates a "weakly authorized" session.- On success the identity rides the session (
auth_succeeded): for mapped channels it propagates through the alkcall transport (set_identity, once) soAccessControl::check(peer_identity)gates every mapped open — the visible-surface invariant holds in the SSH direction with no SSH-shaped exception. POC-3 §C.3 observed the propagation live (the establisher witness).
Dispositions (ADR-004 — summarized)
publickey is the only implemented method; auth_publickey_offered
accepts-and-delegates (keeping max_auth_attempts honest — the hook
does not consume an attempt, POC-1 verified); auth_none /
auth_password / auth_keyboard_interactive / auth_openssh_certificate
reject explicitly (at this pin russh's defaults already reject; the
explicit implementations are the family contract — a pin bump could
change defaults silently). Config::methods advertises {publickey}
— exactly what is implemented. The username is not identity and
carries no semantics (OQ-03 resolved); log exposure is deployment
policy. max_auth_attempts defaults 10 (the OpenSSH none-probe
consumes one — POC-1 §4).
Client direction
Key custody (ADR-005)
Held keys are bearer-token-class secrets: vault-encrypted at rest
(alkvault EncryptedData, OpenSSH-PEM inner), decrypted at use,
zeroized ownership at the adapter seam; never env vars, never
plaintext at rest, never persisted after use. The vault is a secret
store on this path — not a key generator, not a custodian, and never
involved in resolving peers' fingerprints. Failure modes are all
fail-closed with distinct errors: locked / absent / decryption
failure / unknown key_version — and rotation preserves key identity.
The same model covers host keys (decrypt-at-construction into the
server's Config::keys; locked vault → no server is constructed —
POC-2 §5).
Host-key policy (OQ-05 resolution; the client-side trust contract)
Two cases, one seam — the crate is policy-blind:
- Known beforehand (anchors exist; family-built runners mint
their host key at assembly time): assembly-supplied pins —
hard-decline on mismatch at
check_server_key. - Unknown beforehand (vanilla
sshdon rental fleets; no anchor until contact): the crate surfaces (host, port, presented fingerprint) to an assembly-providedHostKeyPolicy— prompt-and-record, provider-metadata match, or learn-and-record. The OpenSSH "add to known_hosts?" moment becomes assembly policy.
Crate rules: default policy is fail-closed (reject unknown); the crate never decides trust; accept-all is never a crate option (alkminer's accept-all is the recorded anti-pattern; for ephemeral fleets the real anchor is hub-held key material — per ADR-005's custody model — not host pins).
What is out of scope
- Password / keyboard-interactive anywhere (server: declined; client: non-goal until a use case).
- OpenSSH certificate auth (
auth_openssh_certificatedeclines; OQ-04 deferred on a CA use case). - ssh-agent integration/forwarding (external signer processes are out of family scope; the vault is the custody story).
- TOFO/TOFU as crate behavior — policy lives at the assembly, the crate is policy-blind (above).
Open Questions
- OQ-04 (deferred — certificate auth, on a use case).
- OQ-07 (open — the vault
decryptplain-Stringgap; upstream ask to alkvault; adapter-sideZeroizingconvention stands meanwhile; ADR-005 decision 3 is the contract). - OQ-05 (resolved — recorded above).
References
- ADR-004 (dispositions + parity invariant), ADR-005 (custody), ADR-002 (the pinned russh/auth pipeline facts)
docs/research/auth-bridge.md(the surface mapping + host-key refinement),poc-1-auth-bridge.md(§1–4),poc-2-vault-keys.md(§2–5),reverse-runner.md(lessons 5–6)- alkcall ADRs 002/003/006 (identity surface), alkhttp ADR-014/073 analogs (secret flow, one-backend posture), family ADR-002 decision 5 / family ADR-003 decision 2