Skip to content

M13.4: AnvilSession::connect_via_jump_hosts (FR-56, NFR-17)#12

Merged
UnbreakableMJ merged 1 commit into
mainfrom
feature/m13-4-connect-via-jump-hosts
May 4, 2026
Merged

M13.4: AnvilSession::connect_via_jump_hosts (FR-56, NFR-17)#12
UnbreakableMJ merged 1 commit into
mainfrom
feature/m13-4-connect-via-jump-hosts

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

Summary

Adds the public chain manager that drives a ProxyJump chain through russh direct-tcpip channels with independent host-key verification at every hop (NFR-17). Re-opened on a clean rebase off main; previous PR #9 was auto-closed when its base branch (M13.3) was deleted by the squash-merge.

Public surface (additive)

  • AnvilSession::connect_via_jump_hosts(config: &AnvilConfig, jumps: &[proxy::JumpHost]) -> Result<Self, AnvilError>

Per-hop flow

  1. Build per-hop AnvilConfig via jump_to_config helper. Inherits strict_host_key_checking, custom_known_hosts, verbose from primary so a user's connection-wide policy applies to every hop. Per-hop user / identity_files come from the JumpHost when set, else from primary.
  2. First hop: client::connect (TCP). Subsequent hops: previous handle's channel_open_direct_tcpip -> Channel -> into_stream() -> client::connect_stream.
  3. Independent per-hop host-key verification via GitwayHandler::check_server_key (NFR-17). Mismatch at any hop aborts the whole chain via ?-propagation; partial-success is forbidden.
  4. Authenticate the bastion with authenticate_best so we can open the next hop's direct-tcpip through it.

After the loop, the last bastion's handle opens a final direct-tcpip to config.host:config.port, then connect_stream runs the SSH handshake over that channel for the target.

Per-hop ssh_config

Not done here. The CLI dispatcher (M13.6) is responsible for populating JumpHost.identity_files from per-hop ssh_config::resolve calls before invoking this method.

Tests

185 lib + 1 matrix + 6 integration; 0 failures, 5 ignored. End-to-end chain test with two russh::server instances on loopback lands in M13.7.

Test plan

  • CI passes (Linux/macOS/Windows + MSRV 1.88).
  • cargo fmt / cargo clippy --all-targets -- -D warnings clean.
  • cargo test --lib --tests --locked green.

Plan: M13.4 of let-us-plan-on-bright-cosmos.md.

🤖 Generated with Claude Code

…-17)

Adds the public chain manager that drives a ProxyJump chain through
russh `direct-tcpip` channels, with full host-key verification at
every hop.

session.rs:

- pub async fn connect_via_jump_hosts(config, jumps: &[JumpHost]):
    1. Reject empty `jumps` with a clear message — callers should use
       AnvilSession::connect when no chain is in play.
    2. For each hop:
       - Build per-hop AnvilConfig via the new jump_to_config helper
         (inherits strict_host_key_checking, custom_known_hosts,
         verbose from primary; per-hop user / identity_files from the
         JumpHost when set, else from primary).
       - Build handler pieces.
       - First hop: client::connect (TCP).
         Subsequent hops: previous handle's
         channel_open_direct_tcpip(...) -> Channel -> into_stream() ->
         client::connect_stream(...). The bastion-to-bastion transport
         is the prev hop's direct-tcpip channel; russh runs a fresh
         handshake over it.
       - Independent per-hop host-key verification via
         GitwayHandler::check_server_key (NFR-17). Mismatch at any
         hop aborts the whole chain via ?-propagation; partial-success
         path is forbidden.
       - authenticate_best on the per-hop session so we can open the
         next direct-tcpip channel through this bastion.
    3. After the loop, the LAST bastion's handle opens a final
       direct-tcpip to config.host:config.port, into_stream, then
       client::connect_stream for the target's SSH handshake. Returns
       the resulting AnvilSession indistinguishable from connect()'s
       output to callers.

- Per-hop ssh_config resolution is NOT done here. The CLI dispatcher
  (M13.6) is responsible for populating JumpHost.identity_files from
  per-hop ssh_config::resolve calls before invoking this method.
  Documented in the doc comment.

- Helper jump_to_config inherits security policy from the primary
  config so a user's connection-wide --insecure-skip-host-check
  applies to every hop (deliberate: a corporate user troubleshooting
  a mismatch at one bastion still has the same global escape hatch).

Tests: 189 lib + 1 matrix + 6 integration; 0 failures. End-to-end
chain test (with two russh::server instances on loopback) lands in
M13.7.

Plan: M13.4 of let-us-plan-on-bright-cosmos.md. Stacked on M13.3 (PR
not yet opened).
@UnbreakableMJ
UnbreakableMJ merged commit a1c01cf into main May 4, 2026
5 checks passed
@UnbreakableMJ
UnbreakableMJ deleted the feature/m13-4-connect-via-jump-hosts branch May 4, 2026 10:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant