Check if public key is associated with the user
This commit is contained in:
@@ -4,19 +4,33 @@ use clap::Parser as _;
|
||||
use ratatui::{Terminal, TerminalOptions, Viewport, layout::Rect, prelude::CrosstermBackend};
|
||||
use russh::{
|
||||
ChannelId,
|
||||
keys::ssh_key::PublicKey,
|
||||
server::{Auth, Msg, Session},
|
||||
};
|
||||
use tracing::{debug, trace, warn};
|
||||
|
||||
use crate::{
|
||||
cli,
|
||||
Ldap, cli,
|
||||
input::Input,
|
||||
io::TerminalHandle,
|
||||
ldap::LdapError,
|
||||
tui::Renderer,
|
||||
tunnel::{Tunnel, TunnelAccess, Tunnels},
|
||||
};
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum HandlerError {
|
||||
#[error(transparent)]
|
||||
Russh(#[from] russh::Error),
|
||||
#[error(transparent)]
|
||||
Ldap(#[from] LdapError),
|
||||
#[error(transparent)]
|
||||
IO(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
pub struct Handler {
|
||||
ldap: Ldap,
|
||||
|
||||
all_tunnels: Tunnels,
|
||||
tunnels: Vec<Tunnel>,
|
||||
|
||||
@@ -31,8 +45,9 @@ pub struct Handler {
|
||||
}
|
||||
|
||||
impl Handler {
|
||||
pub fn new(all_tunnels: Tunnels) -> Self {
|
||||
pub fn new(ldap: Ldap, all_tunnels: Tunnels) -> Self {
|
||||
Self {
|
||||
ldap,
|
||||
all_tunnels,
|
||||
tunnels: Default::default(),
|
||||
user: None,
|
||||
@@ -237,7 +252,7 @@ impl Handler {
|
||||
}
|
||||
|
||||
impl russh::server::Handler for Handler {
|
||||
type Error = russh::Error;
|
||||
type Error = HandlerError;
|
||||
|
||||
async fn channel_open_session(
|
||||
&mut self,
|
||||
@@ -252,14 +267,21 @@ impl russh::server::Handler for Handler {
|
||||
async fn auth_publickey(
|
||||
&mut self,
|
||||
user: &str,
|
||||
_public_key: &russh::keys::ssh_key::PublicKey,
|
||||
public_key: &PublicKey,
|
||||
) -> Result<Auth, Self::Error> {
|
||||
debug!("Login from {user}");
|
||||
trace!("{public_key:?}");
|
||||
|
||||
self.user = Some(user.into());
|
||||
|
||||
// TODO: Get ssh keys associated with user from ldap
|
||||
Ok(Auth::Accept)
|
||||
for key in self.ldap.get_ssh_keys(user).await? {
|
||||
trace!("{key:?}");
|
||||
if key.key_data() == public_key.key_data() {
|
||||
return Ok(Auth::Accept);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Auth::reject())
|
||||
}
|
||||
|
||||
async fn data(
|
||||
@@ -322,7 +344,7 @@ impl russh::server::Handler for Handler {
|
||||
}
|
||||
}
|
||||
|
||||
session.channel_success(channel)
|
||||
Ok(session.channel_success(channel)?)
|
||||
}
|
||||
|
||||
async fn tcpip_forward(
|
||||
@@ -334,7 +356,7 @@ impl russh::server::Handler for Handler {
|
||||
trace!(address, port, "tcpip_forward");
|
||||
|
||||
let Some(user) = self.user.clone() else {
|
||||
return Err(russh::Error::Inconsistent);
|
||||
return Err(russh::Error::Inconsistent.into());
|
||||
};
|
||||
|
||||
let tunnel = self
|
||||
|
||||
74
src/ldap.rs
Normal file
74
src/ldap.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use ldap3::{LdapConnAsync, SearchEntry};
|
||||
use russh::keys::PublicKey;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Ldap {
|
||||
base: String,
|
||||
ldap: ldap3::Ldap,
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum LdapError {
|
||||
#[error(transparent)]
|
||||
Ldap(#[from] ldap3::LdapError),
|
||||
#[error("Key error: {0}")]
|
||||
FailedToParseKey(#[from] russh::Error),
|
||||
#[error("Mising environment variable: {0}")]
|
||||
MissingEnvironmentVariable(&'static str),
|
||||
#[error("Mising environment variable: {0}")]
|
||||
CouldNotReadPasswordFile(#[from] std::io::Error),
|
||||
}
|
||||
|
||||
impl Ldap {
|
||||
pub async fn start_from_env() -> Result<Ldap, LdapError> {
|
||||
let address = std::env::var("LDAP_ADDRESS")
|
||||
.map_err(|_| LdapError::MissingEnvironmentVariable("LDAP_ADDRESS"))?;
|
||||
let base = std::env::var("LDAP_BASE")
|
||||
.map_err(|_| LdapError::MissingEnvironmentVariable("LDAP_BASE"))?;
|
||||
let bind_dn = std::env::var("LDAP_BIND_DN")
|
||||
.map_err(|_| LdapError::MissingEnvironmentVariable("LDAP_BIND_DN"))?;
|
||||
|
||||
let password = std::env::var("LDAP_PASSWORD_FILE").map_or_else(
|
||||
|_| {
|
||||
std::env::var("LDAP_PASSWORD")
|
||||
.map_err(|_| LdapError::MissingEnvironmentVariable("LDAP_PASSWORD"))
|
||||
},
|
||||
|path| std::fs::read_to_string(path).map_err(|err| err.into()),
|
||||
)?;
|
||||
|
||||
let (conn, mut ldap) = LdapConnAsync::new(&address).await?;
|
||||
ldap3::drive!(conn);
|
||||
|
||||
ldap.simple_bind(&bind_dn, &password).await?.success()?;
|
||||
|
||||
Ok(Self { base, ldap })
|
||||
}
|
||||
|
||||
pub async fn get_ssh_keys(
|
||||
&mut self,
|
||||
user: impl AsRef<str>,
|
||||
) -> Result<Vec<PublicKey>, LdapError> {
|
||||
Ok(self
|
||||
.ldap
|
||||
.search(
|
||||
&self.base,
|
||||
ldap3::Scope::Subtree,
|
||||
// TODO: Make this not hardcoded
|
||||
&format!("(uid={})", user.as_ref()),
|
||||
vec!["sshkeys"],
|
||||
)
|
||||
.await?
|
||||
.success()?
|
||||
.0
|
||||
.into_iter()
|
||||
.map(SearchEntry::construct)
|
||||
.flat_map(|entry| {
|
||||
entry
|
||||
.attrs
|
||||
.into_values()
|
||||
.flat_map(|keys| keys.into_iter().map(|key| PublicKey::from_openssh(&key)))
|
||||
})
|
||||
.collect::<Result<Vec<_>, _>>()
|
||||
.map_err(russh::Error::from)?)
|
||||
}
|
||||
}
|
||||
@@ -7,9 +7,11 @@ mod handler;
|
||||
mod helper;
|
||||
mod input;
|
||||
mod io;
|
||||
mod ldap;
|
||||
mod server;
|
||||
mod tui;
|
||||
mod tunnel;
|
||||
|
||||
pub use ldap::Ldap;
|
||||
pub use server::Server;
|
||||
pub use tunnel::{Tunnel, Tunnels};
|
||||
|
||||
@@ -8,7 +8,7 @@ use rand::rngs::OsRng;
|
||||
use tokio::net::TcpListener;
|
||||
use tracing::{error, info};
|
||||
use tracing_subscriber::{EnvFilter, Registry, layer::SubscriberExt, util::SubscriberInitExt};
|
||||
use tunnel_rs::{Server, Tunnels};
|
||||
use tunnel_rs::{Ldap, Server, Tunnels};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> color_eyre::Result<()> {
|
||||
@@ -32,8 +32,10 @@ async fn main() -> color_eyre::Result<()> {
|
||||
let authz_address = std::env::var("AUTHZ_ENDPOINT")
|
||||
.unwrap_or("http://localhost:9091/api/authz/forward-auth".into());
|
||||
|
||||
let ldap = Ldap::start_from_env().await?;
|
||||
|
||||
let tunnels = Tunnels::new(domain, authz_address);
|
||||
let mut ssh = Server::new(tunnels.clone());
|
||||
let mut ssh = Server::new(ldap, tunnels.clone());
|
||||
let addr = SocketAddr::from(([0, 0, 0, 0], 2222));
|
||||
tokio::spawn(async move { ssh.run(key, addr).await });
|
||||
info!("SSH is available on {addr}");
|
||||
|
||||
@@ -1,18 +1,19 @@
|
||||
use std::{net::SocketAddr, sync::Arc, time::Duration};
|
||||
|
||||
use russh::{keys::PrivateKey, server::Server as _};
|
||||
use russh::{MethodKind, keys::PrivateKey, server::Server as _};
|
||||
use tokio::net::ToSocketAddrs;
|
||||
use tracing::{debug, warn};
|
||||
|
||||
use crate::{handler::Handler, tunnel::Tunnels};
|
||||
use crate::{Ldap, handler::Handler, tunnel::Tunnels};
|
||||
|
||||
pub struct Server {
|
||||
ldap: Ldap,
|
||||
tunnels: Tunnels,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
pub fn new(tunnels: Tunnels) -> Self {
|
||||
Server { tunnels }
|
||||
pub fn new(ldap: Ldap, tunnels: Tunnels) -> Self {
|
||||
Server { ldap, tunnels }
|
||||
}
|
||||
|
||||
pub fn tunnels(&self) -> Tunnels {
|
||||
@@ -26,13 +27,14 @@ impl Server {
|
||||
) -> impl Future<Output = Result<(), std::io::Error>> + Send {
|
||||
let config = russh::server::Config {
|
||||
inactivity_timeout: Some(Duration::from_secs(3600)),
|
||||
auth_rejection_time: Duration::from_secs(3),
|
||||
auth_rejection_time: Duration::from_secs(1),
|
||||
auth_rejection_time_initial: Some(Duration::from_secs(0)),
|
||||
keys: vec![key],
|
||||
preferred: russh::Preferred {
|
||||
..Default::default()
|
||||
},
|
||||
nodelay: true,
|
||||
methods: [MethodKind::PublicKey].as_slice().into(),
|
||||
..Default::default()
|
||||
};
|
||||
let config = Arc::new(config);
|
||||
@@ -47,7 +49,7 @@ impl russh::server::Server for Server {
|
||||
type Handler = Handler;
|
||||
|
||||
fn new_client(&mut self, _peer_addr: Option<SocketAddr>) -> Self::Handler {
|
||||
Handler::new(self.tunnels.clone())
|
||||
Handler::new(self.ldap.clone(), self.tunnels.clone())
|
||||
}
|
||||
|
||||
fn handle_session_error(&mut self, error: <Self::Handler as russh::server::Handler>::Error) {
|
||||
|
||||
Reference in New Issue
Block a user