feat: add ssh-agent flow
This commit is contained in:
+56
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user