docs(review): alkssh consumer input — the post-construction identity seam (one dialed connection, many per-session identities)
This commit is contained in:
1 parent
a99cc67e76
commit
8e6aa79736
1 file changed
+137
@@ -0,0 +1,137 @@
|
||||
# Review 014 — The post-construction identity seam: consumer input from alkssh's SSH server direction
|
||||
|
||||
## Status
|
||||
|
||||
Open — filed 2026-10-31. Consumer input from alkssh 0.1.0 (published
|
||||
2026-10-31), under the family's upstream posture (file asks, never
|
||||
absorb silently). Booked alkssh-side at its wave-3 review gate (item 5)
|
||||
as the "non-detaching post-construction identity seam" note and carried
|
||||
unabsorbed through the alkssh 0.1.0 release; this is that filing.
|
||||
|
||||
This is the server-direction sibling of the identity-capture class the
|
||||
alkhttp ledger recorded as CF-005 ("no caller-identity capture on the
|
||||
connect-side serving path"): there, the transport-authenticated peer
|
||||
could not be captured *at acceptance*; here, a correctly resolved
|
||||
application-identity cannot be attached *after dialing*. Same boundary
|
||||
(transport connection identity vs. per-session/per-op truth), opposite
|
||||
end of the connection's lifecycle.
|
||||
|
||||
## The finding
|
||||
|
||||
A consumer that authenticates **after** the fabric connection exists —
|
||||
an SSH front door, where the russh auth hooks fire per-session while
|
||||
the substrate connection is dialed once by the assembly — has no
|
||||
correct surface to make its resolved identity the one the fabric's ACL
|
||||
sees.
|
||||
|
||||
The pieces, verified against 0.10.1 source:
|
||||
|
||||
- `Connection::set_identity` is once-only (`src/core/types.rs:541` —
|
||||
a second call errors `IdentityAlreadySet`). The identity slot is
|
||||
consumed at construction/establishment time; the hub template
|
||||
demonstrates the intended composition point: the establisher
|
||||
closure receives `channel0_conn` and may set identity at the
|
||||
transport-establish event (`src/channels/hub_leg.rs:206-221` —
|
||||
"transport connection's identity propagates to channel 0 …
|
||||
dispatch's fallback resolves through it").
|
||||
- The only post-construction way to reach an already-held
|
||||
`ChannelClient`'s connection is `ChannelClient::take_call_connection`
|
||||
(`src/channels/client.rs:521`), which **detaches the client's own
|
||||
calling surface** ("after this, `call_open_op` returns an error").
|
||||
For a hub that is at least arguable (the relay note flags the detach
|
||||
as wrong there too). For a long-lived door it is disqualifying:
|
||||
alkssh rejected this surface on exactly that ground — "the detach
|
||||
would strand every later session."
|
||||
|
||||
## The consumer scenario, concretely (alkssh server direction)
|
||||
|
||||
1. The assembly dials the substrate and configures the door with an
|
||||
`Arc<ChannelClient>` (alkssh `ServeConfig::channel_client`). The
|
||||
fabric connection — and its identity slot — exist and are composed
|
||||
**before any SSH client has connected**. The assembly's dial-time
|
||||
identity ("once at dial, propagated to channel 0 at
|
||||
`from_connection`") is what `AccessControl::check(peer_identity)`
|
||||
on the substrate sees from then on.
|
||||
2. Each SSH session authenticates later, via `auth_publickey`
|
||||
(post-verification; fingerprint → `Identity` resolution,
|
||||
fail-closed). alkssh ties into these hooks correctly — the
|
||||
resolution, the door-side `no-identity` guard, and the gate at the
|
||||
door all work and need no alkcall change.
|
||||
3. The gap is only the second gate: the admitted mapped open rides
|
||||
the dialed fabric connection, and the substrate's ACL re-checks
|
||||
`peer_identity` (CF-005/CF-006). There is no non-detaching,
|
||||
post-construction way to attach the per-auth resolved identity —
|
||||
and structurally there cannot be a single one for a session with
|
||||
a **shared** handle: `set_identity` is once-only, so even a
|
||||
hypothetical setter cannot represent N sessions' identities on one
|
||||
connection.
|
||||
|
||||
So this review documents two coupled facts, not one bug: **(a)** no
|
||||
seam exists to attach identity post-dialing without detaching the
|
||||
calling surface; **(b)** even with one, the once-per-connection
|
||||
identity model does not fit "one fabric connection, many
|
||||
per-session authentications."
|
||||
|
||||
## What the consumer shipped instead (the current honest posture)
|
||||
|
||||
- The no-detach posture preserved (`set_identity`/`take_call_connection`
|
||||
appear nowhere in alkssh `src/`; recorded in alkssh
|
||||
`review-wave-3.md`, item 5's disposition and `w3-tunnel-direct`'s
|
||||
mechanics record).
|
||||
- Identity gated at the door (fail-closed `no-identity` guard,
|
||||
unreachable on the wire) with the substrate gate seeing dial-time
|
||||
composition — the composed-gate arrangement is documented in
|
||||
alkssh `config.rs`'s module docs (§the identity ride) as a decided
|
||||
posture, not a drift risk. Safety degrades to the door-side gate
|
||||
plus the assembly's dial-time identity semantics; nothing
|
||||
anonymous passes at either gate unless the assembly composes it.
|
||||
- One incidental hardening alkssh took that touches this crate's
|
||||
surface in spirit: the door's listen router refuses a colliding
|
||||
token mint fail-closed (never replaces; alkssh `w4-router-collision`
|
||||
— in-depth defense, safety otherwise resting on this crate's
|
||||
ADR-008 mint discipline). No alkcall change asked; recorded for
|
||||
the trail.
|
||||
|
||||
## What a resolution decides (framing, not prescription)
|
||||
|
||||
Any resolution is an API/semantics decision in a published crate —
|
||||
ADR-class in this repo, one-way from alkssh's consumer:
|
||||
|
||||
1. **A seam**: a non-detaching post-construction identity mechanism on
|
||||
a held `ChannelClient`/connection (the hub's establisher hook has
|
||||
no consumer-side equivalent today). Whether that is connection- or
|
||||
op-scoped decides nothing else below by itself.
|
||||
2. **The identity model behind it**: what `peer_identity` *means* when
|
||||
one dialed connection serves many per-session identities —
|
||||
per-open identity carriage (op-scoped, the CF-005 payload-token
|
||||
path's shape), connection-per-auth, or an explicit ruling that
|
||||
dial-time identity is always the substrate-truth and per-session
|
||||
gates are the door's job. A ruling alone (option 3) is a valid
|
||||
resolution: it converts the current arrangement from "happened to
|
||||
work" to contract.
|
||||
3. **The shared-handle corollary**: N-authentications-per-connection
|
||||
is what doors built like alkssh will always present. Whatever
|
||||
shape lands should state this case explicitly rather than assume
|
||||
one identity per connection is the norm.
|
||||
|
||||
## References
|
||||
|
||||
- alkssh `docs/architecture/auth.md` §Server direction step 4 (the
|
||||
auth-hook resolution; the visible-surface invariant)
|
||||
- alkssh `src/config.rs` module docs §the identity ride (the shipped
|
||||
posture; why `take_call_connection` was rejected) and
|
||||
`src/tunnel_map.rs` (the no-detach posture in code: no
|
||||
`set_identity` calls)
|
||||
- alkssh `tasks/implementation/review-wave-3.md` item 5 (the original
|
||||
booking) and `tasks/implementation/w3-tunnel-direct.md` (the
|
||||
mechanics record); `tasks/implementation/review-wave-5.md` /
|
||||
`docs/plans/implementation.md` (carried unabsorbed through release)
|
||||
- `src/core/types.rs:541-549` (`set_identity` once-only),
|
||||
`src/channels/client.rs:514-523` (`take_call_connection` detach),
|
||||
`src/channels/hub_leg.rs:213-221` (the establisher's identity
|
||||
composition point)
|
||||
- `docs/reviews/consumer-findings-ledger.md` CF-005 (the
|
||||
connect-side sibling of this class; its resolution is the
|
||||
payload-token/CF-005 precedence machinery alkssh's scenario rides)
|
||||
- ADR-047/ADR-048 class precedents for how a published-crate
|
||||
semantics ruling ships (loud default precedent, ADR-048)
|
||||
Reference in new issue
Block a user