Skip to content

M13.2: AnvilSession::connect_via_proxy_command (FR-55)#7

Closed
UnbreakableMJ wants to merge 1 commit into
feature/m13-1-proxy-tokens-stdiofrom
feature/m13-2-connect-via-proxy-command
Closed

M13.2: AnvilSession::connect_via_proxy_command (FR-55)#7
UnbreakableMJ wants to merge 1 commit into
feature/m13-1-proxy-tokens-stdiofrom
feature/m13-2-connect-via-proxy-command

Conversation

@UnbreakableMJ

Copy link
Copy Markdown
Contributor

Summary

Adds the public constructor that consumes the ProxyCommand template captured in M12 and the building blocks landed in M13.1.

Stacks on #6. Will rebase onto main after #6 merges.

Public surface (additive)

  • AnvilSession::connect_via_proxy_command(config: &AnvilConfig, template: &str, alias: &str) -> Result<Self, AnvilError>
    • Token-expands %h %p %r %n %% against config.host, config.port, config.username, and alias.
    • Spawns the expanded command via the platform shell, pipes its stdio, and hands it to russh::client::connect_stream.
    • Rejects the literal none (case-insensitive) — the FR-59 disable sentinel; callers should fall back to connect() instead.

Internal refactor

  • New private HandlerPieces struct + Self::build_handler_pieces helper centralises the russh config + GitwayHandler + auth_banner + verified_fingerprint mutex setup that connect(), connect_via_proxy_command, and (M13.4) connect_via_jump_hosts all need.
  • The existing connect() now calls the same helper; behavior is unchanged.
  • Removed the M13.1-era #![allow(dead_code)] from proxy/mod.rs since spawn_proxy_command and ChildStdio::new are now consumed.

Tests

  • 173 lib + 1 matrix + 6 integration; 0 failures.
  • The end-to-end russh + ChildStdio integration test lives in M13.7's test_proxy_jump.rs (gated by GITWAY_INTEGRATION_TESTS=1).

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.2 of let-us-plan-on-bright-cosmos.md.

🤖 Generated with Claude Code

Adds the public constructor that consumes the ProxyCommand template
captured into ResolvedSshConfig in M12 and the building blocks landed
in M13.1 (token expansion + ChildStdio adapter).

session.rs:
- New private HandlerPieces struct + Self::build_handler_pieces helper
  factor out the russh config + GitwayHandler + auth_banner +
  verified_fingerprint mutex setup that connect() and the new
  connect_via_proxy_command both need. M13.4's connect_via_jump_hosts
  will reuse the same helper. The host-key fingerprint lookup
  (with the StrictHostKeyChecking::AcceptNew tolerance shipped in
  M12.5) lives there now, in one place.

- pub async fn connect_via_proxy_command(config, template, alias):
    1. Reject the literal `none` (case-insensitive) — that's the FR-59
       disable sentinel; callers should reach for connect() instead.
       The ssh_config resolver preserves `none` as a literal so first-
       wins protects it; the dispatcher in M13.6 turns that into a
       call to connect() rather than this method.
    2. Build the handler via build_handler_pieces.
    3. Spawn the ProxyCommand child via crate::proxy::command::
       spawn_proxy_command (token-expanded against config.host /
       config.port / config.username / alias).
    4. Hand the resulting ChildStdio (AsyncRead+AsyncWrite+Unpin+Send)
       to russh::client::connect_stream. russh drives the SSH
       handshake over the child's stdio.
    5. Return the AnvilSession with the same shape as connect() —
       indistinguishable to callers thereafter.

- The existing connect() now calls build_handler_pieces too; behavior
  is identical (same russh::client::connect call, same handler).

proxy/mod.rs:
- Removed the M13.1-era #![allow(dead_code)]; spawn_proxy_command and
  ChildStdio::new are now used by session.rs.

Tests: 173 lib + 1 matrix + 6 integration; 0 failures.

Plan: M13.2 of let-us-plan-on-bright-cosmos.md. Stacked on M13.1 (PR #6).
@UnbreakableMJ
UnbreakableMJ deleted the branch feature/m13-1-proxy-tokens-stdio May 4, 2026 10:12
UnbreakableMJ added a commit that referenced this pull request May 4, 2026
Adds the public `proxy::jump` submodule:

JumpHost (struct):
- pub fields: host: String, port: u16, user: Option<String>,
  identity_files: Vec<PathBuf>.
- The chain manager (M13.4) populates identity_files by re-running
  ssh_config::resolve against each hop's name; M13.3 leaves it empty.

parse_jump_chain(raw: &str) -> Result<Vec<JumpHost>, AnvilError>:
- Accepts the OpenSSH `-J` / `ProxyJump` syntax: `[user@]host[:port]`
  comma-separated, whitespace-around-commas tolerated.
- Defaults the port to 22, leaves user as None when omitted.
- Rejects empty strings, trailing/double commas, empty user prefix,
  empty host, invalid port, and the literal `none` (case-insensitive)
  with clear actionable error messages.

MAX_JUMP_HOPS = 8:
- Matches OpenSSH's READCONF_MAX_DEPTH for ProxyJump chains (FR-57).
- Enforced at parse time, not chain-construction time, so callers
  bail out before any network I/O.

IPv6 literals (`[::1]:22`) are not in scope for M13 — none of the
listed FRs require them and the OpenSSH convention is rare in
ProxyJump strings; documented as a follow-up.

Tests: +16 unit tests (single-hop bare, single-hop user@host:port,
two-hop chain, whitespace tolerance, empty-string/whitespace-only,
trailing/double commas, empty user, empty host, invalid port,
out-of-range port, none literal in three case variants, max-length
chain accepted, length+1 chain rejected, struct PartialEq+Clone
sanity). 189 lib tests green, 0 failures.

Plan: M13.3 of let-us-plan-on-bright-cosmos.md. Stacked on M13.2 (PR
#7).
UnbreakableMJ added a commit that referenced this pull request May 4, 2026
Adds the public `proxy::jump` submodule:

JumpHost (struct):
- pub fields: host: String, port: u16, user: Option<String>,
  identity_files: Vec<PathBuf>.
- The chain manager (M13.4) populates identity_files by re-running
  ssh_config::resolve against each hop's name; M13.3 leaves it empty.

parse_jump_chain(raw: &str) -> Result<Vec<JumpHost>, AnvilError>:
- Accepts the OpenSSH `-J` / `ProxyJump` syntax: `[user@]host[:port]`
  comma-separated, whitespace-around-commas tolerated.
- Defaults the port to 22, leaves user as None when omitted.
- Rejects empty strings, trailing/double commas, empty user prefix,
  empty host, invalid port, and the literal `none` (case-insensitive)
  with clear actionable error messages.

MAX_JUMP_HOPS = 8:
- Matches OpenSSH's READCONF_MAX_DEPTH for ProxyJump chains (FR-57).
- Enforced at parse time, not chain-construction time, so callers
  bail out before any network I/O.

IPv6 literals (`[::1]:22`) are not in scope for M13 — none of the
listed FRs require them and the OpenSSH convention is rare in
ProxyJump strings; documented as a follow-up.

Tests: +16 unit tests (single-hop bare, single-hop user@host:port,
two-hop chain, whitespace tolerance, empty-string/whitespace-only,
trailing/double commas, empty user, empty host, invalid port,
out-of-range port, none literal in three case variants, max-length
chain accepted, length+1 chain rejected, struct PartialEq+Clone
sanity). 189 lib tests green, 0 failures.

Plan: M13.3 of let-us-plan-on-bright-cosmos.md. Stacked on M13.2 (PR
#7).
UnbreakableMJ added a commit that referenced this pull request May 4, 2026
Adds the public `proxy::jump` submodule:

JumpHost (struct):
- pub fields: host: String, port: u16, user: Option<String>,
  identity_files: Vec<PathBuf>.
- The chain manager (M13.4) populates identity_files by re-running
  ssh_config::resolve against each hop's name; M13.3 leaves it empty.

parse_jump_chain(raw: &str) -> Result<Vec<JumpHost>, AnvilError>:
- Accepts the OpenSSH `-J` / `ProxyJump` syntax: `[user@]host[:port]`
  comma-separated, whitespace-around-commas tolerated.
- Defaults the port to 22, leaves user as None when omitted.
- Rejects empty strings, trailing/double commas, empty user prefix,
  empty host, invalid port, and the literal `none` (case-insensitive)
  with clear actionable error messages.

MAX_JUMP_HOPS = 8:
- Matches OpenSSH's READCONF_MAX_DEPTH for ProxyJump chains (FR-57).
- Enforced at parse time, not chain-construction time, so callers
  bail out before any network I/O.

IPv6 literals (`[::1]:22`) are not in scope for M13 — none of the
listed FRs require them and the OpenSSH convention is rare in
ProxyJump strings; documented as a follow-up.

Tests: +16 unit tests (single-hop bare, single-hop user@host:port,
two-hop chain, whitespace tolerance, empty-string/whitespace-only,
trailing/double commas, empty user, empty host, invalid port,
out-of-range port, none literal in three case variants, max-length
chain accepted, length+1 chain rejected, struct PartialEq+Clone
sanity). 189 lib tests green, 0 failures.

Plan: M13.3 of let-us-plan-on-bright-cosmos.md. Stacked on M13.2 (PR
#7).
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