Files
LLDAP/server/src/ldap_server.rs
lyzstrik 6f94134fdc refactor(server): migrate to rustls 0.23 and centralize TLS logic (#1389)
This commit upgrades the TLS stack to Rustls 0.23

Key changes:
- Dependencies: Updated 'rustls' (v0.23), 'tokio-rustls' (v0.26), and 'actix-web' (v4.12.1).
- Build Fix: Configured 'rustls' to use the 'ring' provider (disabling default 'aws-lc-rs') to ensure ARMv7 compatibility.
- Refactor: Created 'server/src/tls.rs' to handle certificate loading (DRY).
- LDAP: Updated 'ldap_server.rs' to use the new TLS module and Rustls APIs.
- Healthcheck: Updated 'healthcheck.rs' to use Rustls 0.23 types.
2026-01-31 09:47:11 +01:00

199 lines
6.9 KiB
Rust

use crate::configuration::{Configuration, LdapsOptions};
use crate::tls;
use actix_rt::net::TcpStream;
use actix_server::ServerBuilder;
use actix_service::{ServiceFactoryExt, fn_service};
use anyhow::{Context, Result};
use ldap3_proto::{LdapCodec, control::LdapControl, proto::LdapMsg, proto::LdapOp};
use lldap_access_control::AccessControlledBackendHandler;
use lldap_domain_handlers::handler::{BackendHandler, LoginHandler};
use lldap_ldap::{LdapHandler, LdapInfo};
use lldap_opaque_handler::OpaqueHandler;
use tokio_rustls::TlsAcceptor as RustlsTlsAcceptor;
use tokio_util::codec::{FramedRead, FramedWrite};
use tracing::{debug, error, info, instrument};
use uuid::Uuid;
#[instrument(skip_all, level = "info", name = "LDAP request", fields(session_id = %session.session_uuid()))]
async fn handle_ldap_message<Backend, Writer>(
msg: Result<LdapMsg, std::io::Error>,
resp: &mut Writer,
session: &mut LdapHandler<Backend>,
) -> Result<bool>
where
Backend: BackendHandler + LoginHandler + OpaqueHandler,
Writer: futures_util::Sink<LdapMsg> + Unpin,
<Writer as futures_util::Sink<LdapMsg>>::Error: std::error::Error + Send + Sync + 'static,
{
use futures_util::SinkExt;
let msg = msg.context("while receiving LDAP op")?;
for control in msg.ctrl.iter() {
if let LdapControl::Unknown { oid, .. } = control {
info!("Received unknown control: {}, ignoring", oid);
}
}
debug!(?msg);
match session.handle_ldap_message(msg.op).await {
None => return Ok(false),
Some(result) => {
if result.is_empty() {
debug!("No response");
}
let results: i64 = result.len().try_into().unwrap();
for response in result.into_iter() {
debug!(?response);
let controls = if matches!(response, LdapOp::SearchResultDone(_)) {
vec![LdapControl::SimplePagedResults {
size: results - 1, // Avoid counting SearchResultDone as a result
cookie: vec![],
}]
} else {
vec![]
};
resp.send(LdapMsg {
msgid: msg.msgid,
op: response,
ctrl: controls,
})
.await
.context("while sending a response: {:#}")?
}
resp.flush()
.await
.context("while flushing responses: {:#}")?
}
}
Ok(true)
}
async fn handle_ldap_stream<Stream, Backend>(
stream: Stream,
backend_handler: Backend,
ldap_info: &'static LdapInfo,
) -> Result<Stream>
where
Backend: BackendHandler + LoginHandler + OpaqueHandler + 'static,
Stream: tokio::io::AsyncRead + tokio::io::AsyncWrite + std::marker::Unpin,
{
use tokio_stream::StreamExt;
let (r, w) = tokio::io::split(stream);
// Configure the codec etc.
let mut requests = FramedRead::new(r, LdapCodec::default());
let mut resp = FramedWrite::new(w, LdapCodec::default());
let session_uuid = Uuid::new_v4();
let mut session = LdapHandler::new(
AccessControlledBackendHandler::new(backend_handler),
ldap_info,
session_uuid,
);
info!("LDAP session start: {}", session_uuid);
while let Some(msg) = requests.next().await {
if !handle_ldap_message(msg, &mut resp, &mut session)
.await
.context("while handling incoming messages")?
{
break;
}
}
info!("LDAP session end: {}", session_uuid);
Ok(requests.into_inner().unsplit(resp.into_inner()))
}
fn get_tls_acceptor(ldaps_options: &LdapsOptions) -> Result<RustlsTlsAcceptor> {
let certs = tls::load_certificates(&ldaps_options.cert_file)?;
let private_key = tls::load_private_key(&ldaps_options.key_file)?;
let server_config = std::sync::Arc::new(
rustls::ServerConfig::builder_with_provider(
rustls::crypto::ring::default_provider().into(),
)
.with_safe_default_protocol_versions()
.context("Failed to set default protocol versions")?
.with_no_client_auth()
.with_single_cert(certs, private_key)?,
);
Ok(server_config.into())
}
pub fn build_ldap_server<Backend>(
config: &Configuration,
backend_handler: Backend,
server_builder: ServerBuilder,
) -> Result<ServerBuilder>
where
Backend: BackendHandler + LoginHandler + OpaqueHandler + Clone + 'static,
{
let context = (
backend_handler,
Box::leak(Box::new(
LdapInfo::new(
&config.ldap_base_dn,
config.ignored_user_attributes.clone(),
config.ignored_group_attributes.clone(),
)
.with_context(|| {
format!(
"Invalid value for ldap_base_dn in configuration: {}",
&config.ldap_base_dn
)
})?,
)) as &'static LdapInfo,
);
let context_for_tls = context.clone();
let binder = move || {
let context = context.clone();
fn_service(move |stream: TcpStream| {
let context = context.clone();
async move {
let (handler, ldap_info) = context;
handle_ldap_stream(stream, handler, ldap_info).await
}
})
.map_err(|err: anyhow::Error| error!("[LDAP] Service Error: {:#}", err))
};
info!("Starting the LDAP server on port {}", config.ldap_port);
let server_builder = server_builder
.bind("ldap", (config.ldap_host.clone(), config.ldap_port), binder)
.with_context(|| format!("while binding to the port {}", config.ldap_port));
if config.ldaps_options.enabled {
let tls_context = (
context_for_tls,
get_tls_acceptor(&config.ldaps_options)
.context("while setting up the SSL certificate")?,
);
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, ldap_info), tls_acceptor) = tls_context;
let tls_stream = tls_acceptor.accept(stream).await?;
handle_ldap_stream(tls_stream, handler, ldap_info).await
}
})
.map_err(|err: anyhow::Error| error!("[LDAPS] Service Error: {:#}", err))
};
info!(
"Starting the LDAPS server on port {}",
config.ldaps_options.port
);
server_builder.and_then(|s| {
s.bind(
"ldaps",
(config.ldap_host.clone(), config.ldaps_options.port),
tls_binder,
)
.with_context(|| format!("while binding to the port {}", config.ldaps_options.port))
})
} else {
server_builder
}
}