feat: add ssh-agent flow

This commit is contained in:
2026-08-23 06:10:15 +05:00
parent eeb5aa9e84
commit dd7151bca3
2 changed files with 162 additions and 0 deletions
+56
View File
@@ -5,6 +5,7 @@ use std::sync::{Arc, OnceLock};
use anyhow::{Context, Result, bail};
use russh::ChannelMsg;
use russh::client::{self, AuthResult, Handle};
use russh::keys::agent::client::{AgentClient, AgentStream};
use russh::keys::known_hosts;
use russh::keys::{PrivateKeyWithHashAlg, PublicKey, load_secret_key};
use ssh2_config::{HostParams, ParseRule, SshConfig};
@@ -214,6 +215,10 @@ pub fn connect(resolved: &Resolved) -> Result<Session> {
}
async fn authenticate(handle: &mut Handle<ClientHandler>, r: &Resolved) -> Result<()> {
if try_agent_auth(handle, &r.user).await? {
return Ok(());
}
let secret = load_secret_key(&r.key_path, None).map_err(|err| {
anyhow::anyhow!(
"cannot load private key {}: {err} (passphrase-protected keys are not supported yet)",
@@ -241,6 +246,57 @@ async fn authenticate(handle: &mut Handle<ClientHandler>, r: &Resolved) -> Resul
}
}
async fn try_agent_auth(handle: &mut Handle<ClientHandler>, user: &str) -> Result<bool> {
let Some(mut agent) = connect_agent().await else {
return Ok(false);
};
let identities = match agent.request_identities().await {
Ok(identities) => identities,
Err(_) => return Ok(false),
};
for identity in identities {
let hash = handle
.best_supported_rsa_hash()
.await
.ok()
.flatten()
.flatten();
let public_key = identity.public_key().into_owned();
match handle
.authenticate_publickey_with(user, public_key, hash, &mut agent)
.await
{
Ok(AuthResult::Success) => return Ok(true),
_ => continue,
}
}
Ok(false)
}
type DynAgentClient = AgentClient<Box<dyn AgentStream + Send + Unpin>>;
async fn connect_agent() -> Option<DynAgentClient> {
#[cfg(unix)]
if let Ok(sock) = std::env::var("SSH_AUTH_SOCK") {
if let Ok(client) = AgentClient::connect_uds(sock).await {
return Some(client.dynamic());
}
}
#[cfg(windows)]
{
const OPENSSH_PIPE: &str = "\\\\.\\pipe\\openssh-ssh-agent";
if let Ok(client) = AgentClient::connect_named_pipe(OPENSSH_PIPE).await {
return Some(client.dynamic());
}
if let Ok(client) = AgentClient::connect_pageant().await {
return Some(client.dynamic());
}
}
None
}
impl Session {
pub fn exec(&mut self, cmd: &str) -> Result<()> {
runtime().block_on(async {