SSH Agent v2: server framework (#19119)
Adds the basic framework for handling new client connections in the server. Co-authored-by: Bernd Schoolmann <mail@quexten.com>pull/19620/merge
parent
477b856519
commit
81adf155ee
@ -0,0 +1,70 @@
|
||||
//! SSH agent client connection and connection handler
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::info;
|
||||
|
||||
use super::{auth_policy::AuthPolicy, peer_info::PeerInfo, KeyStore};
|
||||
|
||||
/// An accepted connection from an SSH agent client, bundling the I/O stream
|
||||
/// with information about the connecting peer.
|
||||
pub(crate) struct Connection<S> {
|
||||
/// The I/O stream for this connection
|
||||
pub(crate) stream: S,
|
||||
/// Information about the connected peer process
|
||||
pub(crate) peer_info: PeerInfo,
|
||||
}
|
||||
|
||||
/// Handles an individual SSH agent client connection
|
||||
pub(crate) struct ConnectionHandler<K, A, S> {
|
||||
keystore: Arc<K>,
|
||||
auth_policy: Arc<A>,
|
||||
connection: Connection<S>,
|
||||
token: CancellationToken,
|
||||
}
|
||||
|
||||
impl<K, A, S> ConnectionHandler<K, A, S>
|
||||
where
|
||||
K: KeyStore,
|
||||
A: AuthPolicy,
|
||||
S: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
/// Create a new connection handler
|
||||
pub fn new(
|
||||
keystore: Arc<K>,
|
||||
auth_policy: Arc<A>,
|
||||
connection: Connection<S>,
|
||||
token: CancellationToken,
|
||||
) -> Self {
|
||||
Self {
|
||||
keystore,
|
||||
auth_policy,
|
||||
connection,
|
||||
token,
|
||||
}
|
||||
}
|
||||
|
||||
/// Handle incoming SSH agent protocol messages from the client
|
||||
#[allow(clippy::never_loop)] // TODO PM-30755 remove
|
||||
pub async fn handle(self) {
|
||||
info!(peer_info = ?self.connection.peer_info, "Connection handler started");
|
||||
|
||||
loop {
|
||||
tokio::select! {
|
||||
() = self.token.cancelled() => {
|
||||
info!("Connection handler received cancellation signal");
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO: PM-30755
|
||||
// read SSH protocol message from self.connection.stream
|
||||
// parse message type, use auth policy and keystore to satisfy requests
|
||||
// build response and write back to self.connection.stream
|
||||
}
|
||||
}
|
||||
|
||||
info!("Connection handler shutting down");
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,60 @@
|
||||
//! SSH agent client connection listener abstraction
|
||||
|
||||
use anyhow::Result;
|
||||
use tokio::{
|
||||
io::{AsyncRead, AsyncWrite},
|
||||
sync::mpsc::Sender,
|
||||
};
|
||||
use tokio_util::sync::CancellationToken;
|
||||
use tracing::{debug, error};
|
||||
|
||||
use super::connection::Connection;
|
||||
|
||||
/// Implementors handle platform-specific socket/pipe creation and connection acceptance.
|
||||
#[async_trait::async_trait]
|
||||
pub(crate) trait Listener: Send + Sync {
|
||||
/// The stream type returned by `accept()`
|
||||
type Stream: AsyncRead + AsyncWrite + Send + Unpin + 'static;
|
||||
|
||||
/// Accept a new connection
|
||||
async fn accept(&mut self) -> Result<Connection<Self::Stream>>;
|
||||
}
|
||||
|
||||
/// Spawns an independent tokio task for each listener in `listeners`.
|
||||
///
|
||||
/// Each task loops calling `listener.accept()` and forwards accepted connections to `tx`.
|
||||
/// Tasks exit when the cancellation token is triggered or the channel receiver is dropped.
|
||||
pub(crate) fn spawn_listener_tasks<L>(
|
||||
listeners: Vec<L>,
|
||||
tx: &Sender<Connection<L::Stream>>,
|
||||
cancel_token: &CancellationToken,
|
||||
) where
|
||||
L: Listener + 'static,
|
||||
{
|
||||
for mut listener in listeners {
|
||||
let tx = tx.clone();
|
||||
let token = cancel_token.clone();
|
||||
tokio::spawn(async move {
|
||||
loop {
|
||||
tokio::select! {
|
||||
() = token.cancelled() => {
|
||||
debug!("Listener task received cancellation signal");
|
||||
break;
|
||||
}
|
||||
result = listener.accept() => match result {
|
||||
Ok(conn) => {
|
||||
// Receiver dropped; main loop has exited
|
||||
if tx.send(conn).await.is_err() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Continue to retry on transient errors
|
||||
Err(error) => {
|
||||
error!(%error, "Listener accept failed");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,5 @@
|
||||
//! Peer process information for SSH agent connections
|
||||
|
||||
/// Information about the connecting peer process
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct PeerInfo {}
|
||||
Loading…
Reference in new issue