Check if public key is associated with the user
This commit is contained in:
parent
7f64ca1822
commit
1515ff236a
73
Cargo.lock
generated
73
Cargo.lock
generated
|
@ -150,6 +150,17 @@ dependencies = [
|
|||
"password-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "async-trait"
|
||||
version = "0.1.88"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e539d3fca749fcee5236ab05e93a52867dd549cc157c8cb7f99595f3cedffdb5"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
|
@ -1559,6 +1570,40 @@ dependencies = [
|
|||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lber"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2df7f9fd9f64cf8f59e1a4a0753fe7d575a5b38d3d7ac5758dcee9357d83ef0a"
|
||||
dependencies = [
|
||||
"bytes",
|
||||
"nom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ldap3"
|
||||
version = "0.11.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "166199a8207874a275144c8a94ff6eed5fcbf5c52303e4d9b4d53a0c7ac76554"
|
||||
dependencies = [
|
||||
"async-trait",
|
||||
"bytes",
|
||||
"futures",
|
||||
"futures-util",
|
||||
"lazy_static",
|
||||
"lber",
|
||||
"log",
|
||||
"native-tls",
|
||||
"nom",
|
||||
"percent-encoding",
|
||||
"thiserror 1.0.69",
|
||||
"tokio",
|
||||
"tokio-native-tls",
|
||||
"tokio-stream",
|
||||
"tokio-util",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.171"
|
||||
|
@ -1647,6 +1692,12 @@ version = "0.3.17"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a"
|
||||
|
||||
[[package]]
|
||||
name = "minimal-lexical"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "miniz_oxide"
|
||||
version = "0.7.4"
|
||||
|
@ -1706,6 +1757,16 @@ dependencies = [
|
|||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
"minimal-lexical",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nu-ansi-term"
|
||||
version = "0.46.0"
|
||||
|
@ -3077,6 +3138,17 @@ dependencies = [
|
|||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.7.14"
|
||||
|
@ -3221,6 +3293,7 @@ dependencies = [
|
|||
"http-body-util",
|
||||
"hyper",
|
||||
"hyper-util",
|
||||
"ldap3",
|
||||
"rand 0.8.5",
|
||||
"ratatui",
|
||||
"reqwest",
|
||||
|
|
|
@ -15,6 +15,7 @@ futures = "0.3.31"
|
|||
http-body-util = { version = "0.1.3", features = ["full"] }
|
||||
hyper = { version = "1.6.0", features = ["full"] }
|
||||
hyper-util = { version = "0.1.11", features = ["full"] }
|
||||
ldap3 = "0.11.5"
|
||||
rand = "0.8.5"
|
||||
ratatui = { version = "0.29.0", features = ["unstable-backend-writer"] }
|
||||
reqwest = { version = "0.12.15", features = ["rustls-tls"] }
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in New Issue
Block a user