Skip to content

Resolve ~/.ssh/config aliases and authenticate via ssh-agent - #1

Open
kirisame-meguru wants to merge 1 commit into
mixelpixx:mainfrom
kirisame-meguru:ssh-config-agent-auth
Open

Resolve ~/.ssh/config aliases and authenticate via ssh-agent#1
kirisame-meguru wants to merge 1 commit into
mixelpixx:mainfrom
kirisame-meguru:ssh-config-agent-auth

Conversation

@kirisame-meguru

Copy link
Copy Markdown

ssh_connect { "host": "prod-web" } works wherever ssh prod-web does.

src/ssh_config.rs (new), shared by ssh_connect and connect:

  • lookup() — runs ssh -G <host>, reads HostName/User/Port/IdentityFile. Host/Match, Include, wildcards, percent tokens, canonicalization and OpenSSH defaults come free. No ssh on PATH → local account name only, connect still proceeds.
  • authenticate() — password → each existing IdentityFile → every ssh-agent identity (SSH_AUTH_SOCK, or \\.\pipe\openssh-ssh-agent on Windows). RSA signed with the hash from server-sig-algs (OpenSSH 8.8+). Errors list every method tried and why.

API

  • ssh_connect: username/port/privateKeyPath now optional; useAgent (default true).
  • connect: same, session name used as alias when host is omitted. Precedence: explicit args > hosts.toml > ~/.ssh/config.
  • SshAuth.key_path: Option<&Path>identity_files: Vec<PathBuf>, plus use_agent.

Notes

  • No new dependencies; Cargo.toml/Cargo.lock unchanged.
  • 7 unit tests in src/ssh_config.rs; skip themselves when ssh is absent.
  • authenticate() returns a boxed Send future — generic async fn awaited in an rmcp #[tool] handler trips rust#102211.
  • No cargo fmt run: tree isn't rustfmt-clean, kept the diff to the change.

russh speaks the protocol only: it reads no client config and never talks to
the agent, so both connect tools demanded an explicit host, username and a
password or key path. A host that plain `ssh <alias>` reaches with no
arguments could not be reached here without restating its whole configuration,
and agent-only setups (hardware keys, forwarded agents) could not connect at
all.

Add src/ssh_config.rs, shared by the server-ops and interactive paths:

- lookup() asks the local OpenSSH client for its own answer (`ssh -G <host>`)
  and reads HostName/User/Port/IdentityFile off it, so Host *and* Match blocks,
  Includes, wildcards, percent tokens, canonicalization and OpenSSH's built-in
  defaults behave exactly as they do for ssh itself, with no config parser to
  keep in sync. No ssh on PATH, or a config ssh refuses to load, degrades to
  the local account name rather than failing the connect.
- authenticate() runs one auth ladder: password, then each identity file that
  exists on disk, then every identity the agent holds (SSH_AUTH_SOCK, or the
  OpenSSH named pipe on Windows). RSA keys are signed with the hash the server
  advertises in server-sig-algs, so they still work against OpenSSH 8.8+.
  Failures report every method tried and why.

ssh_connect now takes `host` alone (username/port/key optional, `useAgent` to
opt out); `connect` resolves the same way, using the session name as the alias
when no host is given, and keeps precedence explicit args > hosts.toml >
~/.ssh/config.

No new dependencies: resolution is a subprocess and the agent client ships
with russh.

The auth future is boxed with an explicit Send bound: awaiting a generic async
fn inside an rmcp #[tool] handler otherwise trips rust#102211.
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