From f689458aa2888ddb7386f6fed89ec17d8747e325 Mon Sep 17 00:00:00 2001 From: Valentin Tolmer Date: Thu, 5 May 2022 16:12:10 +0200 Subject: [PATCH] server: Implement LDAPS support --- Cargo.lock | 6 +- server/Cargo.toml | 2 + server/src/infra/ldap_server.rs | 143 ++++++++++++++++++++++---------- 3 files changed, 107 insertions(+), 44 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d9c1cad..7084b6c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1917,6 +1917,7 @@ dependencies = [ "lldap_auth", "log", "mockall", + "native-tls", "opaque-ke", "openssl-sys", "orion", @@ -1931,6 +1932,7 @@ dependencies = [ "thiserror", "time 0.2.27", "tokio", + "tokio-native-tls", "tokio-stream", "tokio-util", "tracing", @@ -2150,9 +2152,9 @@ dependencies = [ [[package]] name = "native-tls" -version = "0.2.8" +version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48ba9f7719b5a0f42f338907614285fb5fd70e53858141f69898a1fb7203b24d" +checksum = "fd7e2f3618557f980e0b17e8856252eee3c97fa12c54dff0ca290fb6266ca4a9" dependencies = [ "lazy_static", "libc", diff --git a/server/Cargo.toml b/server/Cargo.toml index bde60c4..d3ea765 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -30,6 +30,7 @@ ldap3_server = ">=0.1.9" lldap_auth = { path = "../auth" } log = "*" orion = "0.16" +native-tls = "0.2.10" serde = "*" serde_json = "1" sha2 = "0.9" @@ -37,6 +38,7 @@ sqlx-core = "=0.5.1" thiserror = "*" time = "0.2" tokio = { version = "1.2.0", features = ["full"] } +tokio-native-tls = "0.3" tokio-util = "0.6.3" tokio-stream = "*" tracing = "*" diff --git a/server/src/infra/ldap_server.rs b/server/src/infra/ldap_server.rs index 53717e6..9cafcd3 100644 --- a/server/src/infra/ldap_server.rs +++ b/server/src/infra/ldap_server.rs @@ -1,6 +1,6 @@ use crate::{ domain::{ - handler::{BackendHandler, LoginHandler}, + handler::{BackendHandler, LoginHandler, UserId}, opaque_handler::OpaqueHandler, }, infra::{configuration::Configuration, ldap_handler::LdapHandler}, @@ -9,19 +9,21 @@ use actix_rt::net::TcpStream; use actix_server::ServerBuilder; use actix_service::{fn_service, ServiceFactoryExt}; use anyhow::{Context, Result}; -use futures_util::future::ok; use ldap3_server::{proto::LdapMsg, LdapCodec}; use log::*; -use tokio::net::tcp::WriteHalf; +use native_tls::{Identity, TlsAcceptor}; +use tokio_native_tls::TlsAcceptor as NativeTlsAcceptor; use tokio_util::codec::{FramedRead, FramedWrite}; -async fn handle_incoming_message( +async fn handle_incoming_message( msg: Result, - resp: &mut FramedWrite, LdapCodec>, + resp: &mut Writer, session: &mut LdapHandler, ) -> Result where Backend: BackendHandler + LoginHandler + OpaqueHandler, + Writer: futures_util::Sink + Unpin, + >::Error: std::error::Error + Send + Sync + 'static, { use futures_util::SinkExt; let msg = msg.context("while receiving LDAP op")?; @@ -51,6 +53,56 @@ where Ok(true) } +fn get_file_as_byte_vec(filename: &str) -> Result> { + (|| -> Result> { + use std::fs::{metadata, File}; + use std::io::Read; + let mut f = File::open(&filename).context("file not found")?; + let metadata = metadata(&filename).context("unable to read metadata")?; + let mut buffer = vec![0; metadata.len() as usize]; + f.read(&mut buffer).context("buffer overflow")?; + Ok(buffer) + })() + .context(format!("while reading file {}", filename)) +} + +async fn handle_ldap_stream( + stream: Stream, + backend_handler: Backend, + ldap_base_dn: String, + ldap_user_dn: UserId, +) -> Result +where + Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static, + Stream: tokio::io::AsyncRead + tokio::io::AsyncWrite, +{ + use tokio_stream::StreamExt; + let (r, w) = tokio::io::split(stream); + // Configure the codec etc. + let mut requests = FramedRead::new(r, LdapCodec); + let mut resp = FramedWrite::new(w, LdapCodec); + + let mut session = LdapHandler::new(backend_handler, ldap_base_dn, ldap_user_dn); + + while let Some(msg) = requests.next().await { + if !handle_incoming_message(msg, &mut resp, &mut session) + .await + .context("while handling incoming messages")? + { + break; + } + } + Ok(requests.into_inner().unsplit(resp.into_inner())) +} + +fn get_tls_acceptor(config: &Configuration) -> Result { + // Load TLS key and cert files + let cert_file = get_file_as_byte_vec(&config.ldaps_options.cert_file)?; + let key_file = get_file_as_byte_vec(&config.ldaps_options.key_file)?; + let identity = Identity::from_pkcs8(&cert_file, &key_file)?; + Ok(TlsAcceptor::new(identity)?.into()) +} + pub fn build_ldap_server( config: &Configuration, backend_handler: Backend, @@ -59,44 +111,51 @@ pub fn build_ldap_server( where Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static, { - use futures_util::StreamExt; + let context = ( + backend_handler, + config.ldap_base_dn.clone(), + config.ldap_user_dn.clone(), + ); - let ldap_base_dn = config.ldap_base_dn.clone(); - let ldap_user_dn = config.ldap_user_dn.clone(); - server_builder - .bind("ldap", ("0.0.0.0", config.ldap_port), move || { - let backend_handler = backend_handler.clone(); - let ldap_base_dn = ldap_base_dn.clone(); - let ldap_user_dn = ldap_user_dn.clone(); - fn_service(move |mut stream: TcpStream| { - let backend_handler = backend_handler.clone(); - let ldap_base_dn = ldap_base_dn.clone(); - let ldap_user_dn = ldap_user_dn.clone(); - async move { - // Configure the codec etc. - let (r, w) = stream.split(); - let mut requests = FramedRead::new(r, LdapCodec); - let mut resp = FramedWrite::new(w, LdapCodec); + let tls_context = ( + context.clone(), + get_tls_acceptor(config).context("while setting up the SSL certificate")?, + ); - let mut session = LdapHandler::new(backend_handler, ldap_base_dn, ldap_user_dn); - - while let Some(msg) = requests.next().await { - if !handle_incoming_message(msg, &mut resp, &mut session) - .await - .context("while handling incoming messages")? - { - break; - } - } - - Ok(stream) - } - }) - .map_err(|err: anyhow::Error| error!("Service Error: {:#}", err)) - .and_then(move |_| { - // finally - ok(()) - }) + let binder = move || { + let context = context.clone(); + fn_service(move |stream: TcpStream| { + let context = context.clone(); + async move { + let (handler, base_dn, user_dn) = context; + handle_ldap_stream(stream, handler, base_dn, user_dn).await + } }) - .with_context(|| format!("while binding to the port {}", config.ldap_port)) + .map_err(|err: anyhow::Error| error!("[LDAP] Service Error: {:#}", err)) + }; + + let tls_binder = move || { + let tls_context = tls_context.clone(); + fn_service(move |stream: TcpStream| { + let tls_context = tls_context.clone(); + async move { + let ((handler, base_dn, user_dn), tls_acceptor) = tls_context; + let tls_stream = tls_acceptor.clone().accept(stream).await?; + handle_ldap_stream(tls_stream, handler, base_dn, user_dn).await + } + }) + .map_err(|err: anyhow::Error| error!("[LDAPS] Service Error: {:#}", err)) + }; + + let server_builder = server_builder + .bind("ldap", ("0.0.0.0", config.ldap_port), binder) + .with_context(|| format!("while binding to the port {}", config.ldap_port)); + if config.ldaps_options.enabled { + server_builder.and_then(|s| { + s.bind("ldaps", ("0.0.0.0", config.ldaps_options.port), tls_binder) + .with_context(|| format!("while binding to the port {}", config.ldaps_options.port)) + }) + } else { + server_builder + } }