Files
alkssh/README.md
T
glm-5.3-flash ab5236ebbb docs(release): wave 5 release readiness — crate docs, README, CHANGELOG, matrix, dry-run (w5-release-readiness)
- resolve the rustdoc warning set (9 incl. private-link class in
  client_ops.rs + redundant explicit links + a duplicated tty_map
  paragraph): cargo doc warning-free on default and --all-features
- docs honesty: the five recorded limits ride the published
  crate-root rustdoc (§Known limits: write_all no-deadline, PTY
  rule-5, port-0-only mint echo, no-reconnect policy, no-accept-all);
  crate docs state the feature graph + the fail-closed invariant
- two no_run usage doctests (server mount + dial) — the README
  examples stay compile-checked in every matrix build
- README.md lands (the door, feature graph, examples, custody
  posture, license); CHANGELOG.md carries the 0.1.0 founding entry
- feature matrix green (6 test builds, clippy x2 -D warnings, fmt,
  doc) + the fuzz corpus gate (11 invariants)
- cargo publish --dry-run --allow-dirty clean (76 files packaged +
  verified; file list checked against the package policy; manifest
  sanity recorded); the freeze inventory recorded for the gate

Verification: scripts/matrix.sh (all builds), fuzz corpus gate,
taskgraph validate
2026-10-30 19:29:23 +00:00

158 lines
5.8 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# alkssh
SSH adapter for the alk family: a thin wrapper around
[russh](https://docs.rs/russh) terminating the vanilla-ssh wire and
bridging to/from alkcall channels — the SSH counterpart of the
alkhttp adapter (`family` ADR-003). Vanilla clients terminate vanilla
ssh; family protocol halves (alktty, alktunnels, alkgit) receive their
own chunk planes; the door itself speaks no family protocol and owns
no deployment policy.
The crate is a **door, not a backend**: no binary, no front doors — a
downstream app (alkforge) embeds it, enables the features it wants,
and brings identity/ACL configuration and the family fabric handles.
It is two-directional: a server mount serves vanilla ssh into the
family, and a dial points family ops at a vanilla `sshd`.
## The feature graph — fail-closed
`default = []` by design: **a default build has no capabilities and
can serve nothing but clean declines.** Every channel-type mapping is
a cargo feature; each feature is flat and independent (no feature
implies another):
| Feature | Maps | Family protocol |
|---|---|---|
| `session` | pty / shell / exec (pipe) rows → chunk-framed session | alktty |
| `git` | the exec-only git rows (`git-{upload,receive}-pack`) | alkgit |
| `tunnel` | `direct-tcpip` / `direct-streamlocal`, the `-R` legs | alktunnels |
| `sftp` | reserved decline placeholder (OQ-13) — always declines until the alkfs mapping lands | — |
Non-mapped surface (x11, `auth-agent`, the `env` request, git *subsystem*
requests, `sftp` until alkfs) declines fail-closed in every build:
never a half-capability, never a silent fallback, and never advertised
— an unfeatured channel type is declined, not advertised. A request
whose config fields are unset declines too (an unset seam or shell
argv configures nothing — the feature alone is not a capability).
## Usage
The assembly injects the seams: an alkcall
`IdentityProvider` (verified fingerprint → identity), key sources, and
the per-feature family handles. The examples below are the crate's
compile-checked doctests (see the crate docs in `src/lib.rs`).
A server mount — per accepted TCP connection, run the door:
```rust,no_run
use std::sync::Arc;
use async_trait::async_trait;
struct Identity;
impl alkcall::core::auth::IdentityProvider for Identity {
fn resolve_from_fingerprint(&self, _: &str) -> Option<alkcall::core::auth::Identity> {
None // the assembly's registry decides
}
fn resolve_from_token(&self, _: &alkcall::core::auth::AuthToken) -> Option<alkcall::core::auth::Identity> {
None
}
}
struct HostKeys;
#[async_trait]
impl alkssh::HostKeySource for HostKeys {
async fn load(&self) -> Result<Vec<russh::keys::PrivateKey>, alkssh::KeySourceError> {
Err(alkssh::KeySourceError::Absent) // sketch; vault-backed in prod
}
}
let serve = alkssh::ServeConfig::new(Arc::new(Identity), Arc::new(HostKeys));
let adapter = alkssh::SshAdapter::new(serve).await?;
// per accepted TCP connection from the assembly's listener:
// adapter.handle(stream).await?;
```
A dial — family ops pointed upstream at a vanilla server:
```rust,no_run
use std::sync::Arc;
use async_trait::async_trait;
struct DialKey;
#[async_trait]
impl alkssh::KeySource for DialKey {
async fn load(&self) -> Result<russh::keys::PrivateKey, alkssh::KeySourceError> {
Err(alkssh::KeySourceError::Absent) // sketch; vault-backed in prod
}
}
let conn = alkssh::dial(alkssh::DialConfig::new(
"hub.example:22",
"runner",
Arc::new(DialKey),
Arc::new(alkssh::RejectAll),
))
.await?;
// op rows (under their features): conn.exec, conn.tunnel_direct,
// conn.git_service, conn.reverse_listen — see the crate docs.
conn.close().await; // clean disconnect — drop is never the clean path
```
Minimal builds compile and doctest with no features enabled —
```console
$ cargo build
$ cargo test
```
— and every capability row additionally rides its feature build (the
matrix: no features / `git` / `session` / `tunnel` / `sftp` /
`--all-features`).
## Key custody and security posture
- **Keys are sources, never static material** (alknet-main's `main`
key model, ADR-005): the dial/host key seams are
decrypt-at-use handles over an injected
[`alkvault`](https://docs.rs/alkvault) service — keys held
encrypted at rest, decrypted at use, zeroized through the seam.
The vault is a secret store on this path, not a key generator.
- **No env vars, no config-file key material, no secrets on the
wire.** The standing family posture (family ADR-002 decision 5).
- **Publickey-only authentication**, exactly one attempt — no
password, keyboard-interactive, cert, or fallback methods exist on
the surface.
- **Host-key trust is assembly policy**: the crate ships a
`HostKeyPolicy` seam with a fail-closed default (`RejectAll` — a
dial with it connects to nothing) and **no accept-all built-in**;
any permissive policy is the assembly's to write.
- **Injected identity**: every wire-exposed op is checked against
alkcall's `AccessControl::check(peer_identity)`; internal ops are
never wire-callable; there is no unauthenticated capability.
- **Bounded resources everywhere** (`Limits` — wall clock always
set, channel caps, listener caps, window/packet budgets, auth
attempt limit): the crate never constructs an unbounded session.
Known, recorded limits ride the published docs (the crate-root
rustdoc §Known limits).
- **Every disabled feature declines**: the decline matrix is contract
territory tested in every build.
## Documentation
- Crate docs: `cargo doc --open` (the crate-root docs carry the
feature graph and the known-limits records).
- `docs/architecture/` — the authoritative contract: the spec
(`overview.md`, `mapping.md`, `client.md`, `auth.md`), ADRs
001–006, and the open-questions tracker.
## License
MIT OR Apache-2.0 — see `LICENSE-MIT` / `LICENSE-APACHE`.