7e5989f574124a44cebdcece781f34198ab0a0c4
7
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
7e5989f574 |
feat(client): land the -R leg — the acceptor registry + the reverse-listen rows (w4-reverse-listen)
- src/client_accept.rs: the assembly-mutable acceptor registry (per-type fixed slots under `tunnel`; refuse-never-replace install; payload = the `TunnelChannel` duplex + the open's echo/originator fields — the harness `ForwardedAcceptor` vocabulary grown), the reverse-listen rows (bound-port surfaced; the listen/cancel forms; the runtime-Unix gate at the listen), and the accepted-opens' spawned handoff tasks. - src/client_handler.rs: the `forwarded-tcpip` / `forwarded-streamlocal` hooks route through the registry at arrival time — registered + admitted → accept + handoff; unregistered/unfeatured/cap-exhausted → the clean `AdministrativelyProhibited` decline (never accept-and-drop); pre-install arrivals decline, never buffered. - src/client.rs: the dial wires the empty registry into the handler + the connection (`SshConnection::acceptors`, the late-install accessor) + the listener budget's dedicated counter. - src/error.rs: `OpError::NoAcceptor` / `ListenRefused` / `ListenCapExceeded`; `RequestDenied` → `ListenRefused` (the listen/cancel forms' wire decline class; the port-0 mint rides alone at this pin — an explicit port's bound *is* the requested one, surfaced). - tests/common/sshd_double.rs: the fake `sshd`'s `-R` half — the listen/cancel hooks bind real sockets (the out-param mint is the real bound port), the accept loops open the server-initiated forwarded channels + pump per accepted connection, cancels match by the raw wire strings and tear the listener down. - tests/client_reverse_listen.rs: the contract rows (listen + data leg, the mint, the pre-wire gates, the declines, late-install on/off, the cancels, the ceiling, the unix twins). - tests/tunnel_mapping.rs: the full-circle client ↔ server-direction smoke (both `-R` legs through the alkcall fabric double); real- `sshd` `-R` stays wave-5 e2e (recorded). - client.md: the acceptor's landing record (shape, cap relation, gates, unix-gate placement, port-0 parity). - fix(w4-op-rows, test cfg): the test mod's `family_exit_code` import gated to the exit-consuming builds (`--features tunnel` did not compile its tests before). Verification: cargo test (default / each feature / --all-features) green; cargo clippy --all-targets -- -D warnings (none + --all-features) clean; cargo fmt --check clean; cargo doc --no-deps clean (default build; all-features carries only the pre-existing client_ops private-link warnings). |
||
|
|
2371c2e169 |
feat(client): land the op rows — exec/pty, direct-tcpip, git exec, sftp placeholder (w4-op-rows)
The family op → vanilla translation surfaces on SshConnection, per client.md §op rows: - session: exec (pipe) / pty_exec / shell → RemoteSession — the alktty chunk plane both directions, the exit consumed in-band as the family exit chunk (exit-chunk-is-last); the POC-3 §D.3 mechanics pinned (exec() + wait()-drain, split-halves pump for exit-consuming rows, typed-open-then-straight-into_stream for the exit-less tunnel rows) - tunnel: tunnel_direct / tunnel_direct_unix → TunnelChannel — typed opens, rejections mirrored to OpError::OpenDeclined (OpenDeclineReason), the connection stays usable after every decline (row-pinned per class); the OQ-11 runtime-Unix gate mirrored - git: git_service(Service, repo_id) → GitPackSession — the shared grammar's construction arm in dispatch.rs (parse round-trips unit-pinned), the raw pack duplex pumped, the exit consumed - sftp: the every-build OQ-13 placeholder (OpError::SftpNotServed, Infallible Ok type), before any wire op - shared exit-currency module src/exit_code.rs (extracted from tty_map.rs): SIGNAL_NAMES + derive_exit + sig maps two-direction, new signal_code_from_name client arm - OpError taxonomy (closed, family-mirrored) in error.rs; the fake sshd double grown with the op-row script + observations; tests/client_ops.rs — 18 rows + the alkssh↔alkssh exec smoke (real-vanilla-sshd smoke named wave-5 e2e territory) Verification: cargo test (default + per-feature + all-features) all green; clippy ×2 -D warnings clean; fmt clean; cargo doc --no-deps warning-free. |
||
|
|
0d3fd48f5d |
feat(client): land the dial surface — the authenticated connection handle + the fail-closed client handler skeleton (w4-dial)
The client direction's substrate (client.md §The dial surface), the mirror of w2-server-mount: dial(DialConfig) runs the fail-closed four-step sequence — decrypt-at-dial key load (every KeySource posture a distinct no-dial outcome), the bounded russh client Config from Limits (keepalive 15 s / 3, the wall clock always Some, window/ packet/buffer verbatim), the injected host-key policy at check_server_key (RejectAll connects to nothing; UnknownKey maps to DialError::HostKeyRejected — its only producer), and exactly one publickey-only auth attempt — and returns the authenticated SshConnection handle everything else in the wave rides. - DialConfig amendment (the sanctioned course-correction, recorded): the required `username` field — the USERAUTH_REQUEST's user string; no crate default, never identity, never logged (ADR-004 decision 5). - The client Handler skeleton (src/client_handler.rs): every server_channel_open_* hook overridden explicit-over-inherited with a clean AdministrativelyProhibited reject (never russh's accept-and-drop default, ADR-003 §4); should_accept_unknown_ server_channel the explicit false; decline events on the crate vocabulary (no-acceptor / never-feature reasons). - The in-process fake sshd (tests/common/sshd_double.rs): the client-direction contract vehicle — a plain russh server on a real loopback TCP listener, accept-set identity resolution, the auth_ succeeded handle carrier for the server-initiated-open rows, the per-session outcome channel for the clean-close rows. - tests/client_dial.rs: 10 contract rows — dial success, the five key-source no-dial postures, RejectAll / pin-mismatch / case-2 policy rows, refused auth, the malformed-address dial-time refusal, the bounded-config observable, the six-hook decline wire rows with decline events, and the smoke: alkssh-client ↔ alkssh-server over a real TCP loopback (real-sshd dial stays wave-5 e2e). Verification: cargo test + per-feature + --all-features (14 binaries, all green), cargo clippy ×2 (-D warnings), cargo fmt --check, cargo doc --no-deps — all clean. taskgraph validate: 35 tasks valid. |
||
|
|
d300095687 |
feat(tunnel): ListenRouter token-collision pre-check — refuse, never replace (w4-router-collision)
Closes the wave-3 gate's booked finding item 4 (review-wave-3 §Booked findings): register on an already-held token now refuses fail-closed instead of silently replacing the live route — a collided mint (the ADR-008 discipline's upstream failure) can never steal a live adapter's arrival routes. - ListenRouter::register returns Result<(), String>; collision → Err, the held route untouched (src/tunnel_map.rs) - Error surface decided + recorded: the wire-time collision is the request-time decline disposition (RequestDenied, reason token-collision), not a new AdapterError variant — a global-request hook's Err propagates out of russh 0.65's packet loop and kills the session, while a refused listen's vanilla contract is RequestDenied; AdapterError::ListenRegistration keeps its construction-time class (src/error.rs doc records the sibling) - listen_request_dispatch: register moved before the bound-port out-param write; on refusal the listener cap releases, the uniform decline event emits, and the minted fabric listener tears down immediately (never a half state) (src/handler.rs) - Wire row a_collided_mint_is_refused_and_never_steals_the_live_route pins the full shape through a mint-strategy construct on the fabric double (MintStrategy::WithCollisions — the test forces what the fabric's discipline prevents): second register refuses, first route still serves end to end, refused endpoint never serves, count released (tests/tunnel_mapping.rs) Verification: cargo test (default + git/session/tunnel/sftp + --all-features) · clippy ×2 -D warnings · fmt --check · cargo doc --no-deps · taskgraph validate ✓ (35 tasks) |
||
|
|
3d0253d981 |
feat(tunnel): land the -R listen flow + the forwarded-tcpip data leg (w3-tunnel-listen)
The tunnel branch's other half is served for real against published alktunnels 0.4.1 / alkcall 0.10.1, with POC-3 §C.4 as the template: - tcpip_forward/streamlocal_forward (+ both cancel forms) map the vanilla listen request family-ward through TunnelListener::open on the held ChannelClient (bounded inline dial — the global request's reply + the &mut u32 bound-port out-param are the hook's); the wire bind endpoint rides as the resource string verbatim (OpenSSH client-side normalization parity). A reply without the minted listen token/bound report declines and reaps its fabric listener. - the door serves the fabric's inbound tunnel/forwarded opens: the serve config gains TunnelListenSeam (the seam-config second course-correction — the shared ServingConfig registry the assembly wired at from_connection_with_serving); alkssh registers the forwarded openable (ChannelCore over a clone of the held client's manager) at SshAdapter::new — a failed registration is a failed construction (AdapterError::ListenRegistration). - the forwarded-tcpip data leg: token correlation through the shared ListenRouter → the in-crate per-session channel cap at the accept surface (the acceptor vocabulary) → Handle:: channel_open_forwarded_tcpip streaming the listen request's own args + the actually-bound port (sshd parity) with TunnelForwardedParams.peer as the originator → pump_bidi(ssh, fabric). Correlation-miss → teardown-at-birth, never a half-open SSH channel. Unix arrivals ride forwarded-streamlocal@openssh.com. - bounded bookkeeping: the per-session listener map packet-loop-only with a dedicated max_concurrent_channels-ceiling counter; the route map bounded by construction (mints at accepted listens, removed at cancels + session teardown — the handler's Drop). - the harness grew the client-side accept hook (server_channel_open_forwarded_tcpip/_streamlocal, acceptor-driven) + a hand-rolled tracing subscriber; 18 contract rows + both real-tool smokes (ssh -L, ssh -R + curl) green in tests/tunnel_mapping.rs; the wave-2 stub listen rows migrated per build (unfeatured / seam-unconfigured). Verified: cargo test across default/git/session/sftp/tunnel and all combos + --all-features; clippy -D warnings (default, all-features, combos); cargo fmt --check; cargo doc --no-deps clean; taskgraph validate (26 tasks). |
||
|
|
b90e89b600 |
feat(server): land the SshAdapter mount + russh Config construction (w2-server-mount)
- SshAdapter:🆕 decrypt-at-construction host-key load (ADR-005
decision 6) — locked / absent / decryption / unknown-version /
decode all refuse with AdapterError::Construction(KeySourceError
preserved), no adapter; an empty key set refuses as Absent even
from a custom source.
- russh 0.65 server Config built from ServeConfig: methods exactly
{publickey} (MethodSet from [MethodKind::PublicKey] — honest
advertisement, ADR-004 decision 4); host keys ride Config::keys;
Limits knobs (window_size, maximum_packet_size,
channel_buffer_size, max_auth_attempts) one-for-one;
inactivity_timeout always Some (bounded-resources posture). All
other knobs stay russh's default posture: rekey Limits (RFC-4253
defaults, russh-enforced), auth_rejection_time, server_id
(russh default — recorded), server keepalive (client-direction
posture only).
- SshAdapter::handle: ADR-001 mount — generic duplex stream in,
run_stream per stream, run to completion (Result<(), AdapterError>;
no ALPN/relay code; the serving loop is spawned inside russh, so
ADR-006's spawn-and-return holds without double-spawning).
- SessionHandler skeleton (pub(crate)): per-connection
russh::server::Handler with type Error = AdapterError; every hook
at the inherited fail-closed default; carries Arc<ServeConfig> for
w2-auth-bridge / w2-dispatch-decline-core.
- AdapterError (error.rs): Construction + Wire (From<russh::Error>)
— the wave-1 error-model's reserved wave-2 shape.
- Tests: advertisement-exactly-publickey; knob mapping (tuned +
default postures); key-set parity; construction failure matrix;
EOF-on-mount; wire smoke (russh client over duplex: full KEX,
fail-closed auth decline, exactly the {publickey} method list on
the wire).
Verified: cargo test (61 across suites), cargo test --all-features,
clippy --all-targets -- -D warnings (default + all-features),
fmt --check, cargo doc --no-deps, taskgraph validate.
|
||
|
|
d85299df38 |
feat(error): land the fail-closed key-source error taxonomy
- KeySourceError with exactly five variants (Locked, Absent, Decryption, UnknownKeyVersion, Decode) — ADR-005 decision 4's failure matrix as distinct shapes, the vault taxonomy reshaped never collapsed; no catch-all, no Other/Unknown - payload shapes pinned: Locked/Absent payload-free; Decryption/UnknownKeyVersion carry the vault's opaque messages; Decode carries a message so the RC-pinned ssh-key error type never enters the public API (flagged for the wave-1 gate) - each variant doc-commented with ADR-005 semantics + fail-closed consequence; module stays engine-blind (no vault/russh types) - no From<VaultServiceError>: non-mappable vault variants would force a collapse; w1-key-sources maps explicitly at its seam - exhaustive-match + Display/Debug/Clone unit tests Verified: cargo test (default + all-features), cargo clippy --all-targets --all-features -- -D warnings, cargo fmt --check, cargo doc --no-deps, taskgraph validate |