Skip to main content

AnvilSession

Struct AnvilSession 

Source
pub struct AnvilSession { /* private fields */ }
Expand description

An active SSH session connected to a GitHub (or GHE) host.

§Typical Usage

use anvil_ssh::{AnvilConfig, AnvilSession};

let config = AnvilConfig::github();
let mut session = AnvilSession::connect(&config).await?;
// authenticate, exec, close…

Implementations§

Source§

impl AnvilSession

Source

pub async fn connect(config: &AnvilConfig) -> Result<Self, AnvilError>

Establishes a TCP connection to the host in config and completes the SSH handshake (including host-key verification).

Does not authenticate; call authenticate or authenticate_best after this.

M18 / FR-80 / FR-81 / FR-82: the TCP connect is wrapped in a crate::retry::run loop with per-attempt tokio::time::timeout (when config.connect_timeout is Some). Transient failures (ECONNREFUSED, ETIMEDOUT, DNS NXDOMAIN, …) are retried with jittered exponential backoff; auth / host-key / protocol errors are fatal and surface immediately. See Self::retry_history for the per-attempt history captured during the loop.

§Errors

Returns an error on terminal network failure (after exhausting the retry budget) or if the server’s host key does not match any pinned fingerprint (fatal — never retried).

Source

pub fn retry_history(&self) -> &[RetryAttempt]

Returns the crate::retry::RetryAttempt history captured during the most-recent connect* call on this session.

Empty when the first attempt succeeded. Non-empty when the retry loop fired at least once; the last entry’s attempt matches the attempt number that ultimately succeeded. Surfaced by gitway --test --json’s data.retry_attempts envelope (FR-83).

Source

pub async fn connect_via_jump_hosts( config: &AnvilConfig, jumps: &[JumpHost], ) -> Result<Self, AnvilError>

Establishes the SSH session through a chain of ProxyJump bastion hops (FR-56).

For each hop in jumps:

  1. Build a per-hop AnvilConfig from the [JumpHost] fields, inheriting strict_host_key_checking, custom_known_hosts, and verbose from the primary config. Per-hop user and identity_files come from the [JumpHost] when set, else from the primary config.
  2. Connect: the first hop uses russh::client::connect over TCP; subsequent hops use the previous hop’s direct-tcpip channel as the underlying transport via russh::client::connect_stream.
  3. Run host-key verification — every hop runs the full [GitwayHandler::check_server_key] path independently (NFR-17: failure at hop n+1 aborts the entire chain; no partial-success path).
  4. Authenticate the hop with AnvilSession::authenticate_best so the chain can open direct-tcpip to the next hop.

After the loop, the last bastion’s handle is used to open direct-tcpip to the primary config.host / config.port, and the resulting [ChannelStream] becomes the SSH transport for the final session this method returns.

§Per-hop ssh_config

This method does NOT re-resolve ssh_config per hop — that requires the caller’s [SshConfigPaths], which the session module deliberately does not depend on. The CLI dispatcher (M13.6) is responsible for populating [JumpHost::identity_files] (and any other per-hop overrides) from per-hop crate::ssh_config::resolve calls before invoking this method.

§Errors

Returns the first error encountered. An empty jumps slice is rejected with a clear message — callers should use Self::connect when no chain is in play. Authentication failures at any intermediate hop terminate the whole chain. ChannelStream-based transport errors propagate via the usual russh / AnvilError mapping.

§Panics

Does not panic. An internal expect fires only on a logic bug (the empty-jumps check at the top of the function would have already returned).

Source

pub async fn connect_via_proxy_command( config: &AnvilConfig, proxy_command_template: &str, alias: &str, ) -> Result<Self, AnvilError>

Establishes the SSH session over a child process spawned from a ProxyCommand template (FR-55).

proxy_command_template is the raw template (typically from crate::ssh_config::ResolvedSshConfig::proxy_command or a CLI override). %h, %p, %r, %n, and %% are expanded against config.host, config.port, config.username, and alias respectively before the platform shell (sh -c / cmd /C) spawns the command. The child’s stdin/stdout become the SSH transport via russh::client::connect_stream.

alias is the original argument the user typed before HostName resolution — it powers the %n token. Pass config.host if you do not track the alias separately.

The literal value "none" (case-insensitive) is recognized as the FR-59 disable sentinel: this method returns an error directing the caller to use Self::connect instead. In practice the caller’s dispatcher should never invoke this method in that case, but the guard keeps the spawn path safe against accidental “none” input.

§Errors

Returns an error on shell-spawn failure, on a host-key mismatch, or on any russh handshake failure.

Source

pub async fn authenticate( &mut self, username: &str, key: PrivateKeyWithHashAlg, ) -> Result<(), AnvilError>

Authenticates with an explicit key.

Use [authenticate_best] to let the library discover the key automatically.

§Errors

Returns an error on SSH protocol failures. Returns AnvilError::is_authentication_failed when the server accepts the exchange but rejects the key.

Source

pub async fn authenticate_with_cert( &mut self, username: &str, key: PrivateKey, cert: Certificate, ) -> Result<(), AnvilError>

Authenticates with a private key and an accompanying OpenSSH certificate (FR-12).

The certificate is presented to the server in place of the raw public key. This is typically used with organisation-issued certificates that grant access without requiring the public key to be listed in authorized_keys.

§Errors

Returns an error on SSH protocol failures or if the server rejects the certificate.

Source

pub async fn authenticate_best( &mut self, config: &AnvilConfig, ) -> Result<(), AnvilError>

Discovers the best available key and authenticates using it.

Priority order (FR-9):

  1. Explicit --identity path from config.
  2. Default .ssh paths (id_ed25519id_ecdsaid_rsa).
  3. SSH agent via $SSH_AUTH_SOCK (Unix only).

If a certificate path is configured in config.cert_file, certificate authentication (FR-12) is used instead of raw public-key authentication for file-based keys.

When the chosen key requires a passphrase this method returns an error whose is_key_encrypted predicate is true; the caller (CLI layer) should then prompt and call authenticate_with_passphrase.

§Errors

Returns AnvilError::is_no_key_found when no key is available via any discovery method.

Source

pub async fn authenticate_with_passphrase( &mut self, config: &AnvilConfig, path: &Path, passphrase: &str, ) -> Result<(), AnvilError>

Loads an encrypted key with passphrase and authenticates.

Call this after [authenticate_best] returns an encrypted-key error and the CLI has collected the passphrase from the terminal.

If config.cert_file is set, certificate authentication is used (FR-12).

§Errors

Returns an error if the passphrase is wrong or authentication fails.

Source

pub async fn authenticate_with_agent( &mut self, username: &str, conn: AgentConnection, ) -> Result<(), AnvilError>

Tries each identity held in conn until one succeeds or all are exhausted.

On Unix this is called automatically by [authenticate_best] when no file-based key is found. For plain public-key identities the signing challenge is forwarded to the agent; for certificate identities the full certificate is presented alongside the agent-signed challenge.

§Errors

Returns AnvilError::is_authentication_failed if all identities are rejected, or AnvilError::is_no_key_found if the agent was empty.

Source

pub async fn exec(&mut self, command: &str) -> Result<u32, AnvilError>

Opens a session channel, executes command, and relays stdio bidirectionally until the remote process exits.

Returns the remote exit code (FR-16). Exit-via-signal returns 128 + signal_number (FR-17).

§Errors

Returns an error on channel open failure or SSH protocol errors.

Source

pub async fn close(self) -> Result<(), AnvilError>

Sends a graceful SSH_MSG_DISCONNECT and closes the connection.

§Errors

Returns an error if the disconnect message cannot be sent.

Source

pub fn auth_banner(&self) -> Option<String>

Returns the authentication banner last received from the server (if any).

For GitHub.com this contains the “Hi !” welcome message.

§Panics

Panics if the internal mutex is poisoned, which can only occur if another thread panicked while holding the lock — a programming error.

Source

pub fn verified_fingerprint(&self) -> Option<String>

Returns the SHA-256 fingerprint of the server key that was verified.

Available after a successful connect. Returns None when host-key verification was skipped (--insecure-skip-host-check).

§Panics

Panics if the internal mutex is poisoned — a programming error.

Trait Implementations§

Source§

impl Debug for AnvilSession

Manual Debug impl because client::Handle<H> does not implement Debug.

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self>

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self>

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

Source§

fn vzip(self) -> V

Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self>
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self>

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more