healthcheck: check that the server's certificate is the one in the config

This commit is contained in:
Valentin Tolmer 2023-04-11 13:22:07 +02:00 committed by nitnelave
parent 558bb37354
commit 9a30cac7b0
3 changed files with 71 additions and 29 deletions

View File

@ -31,7 +31,6 @@ lber = "0.4.1"
ldap3_proto = ">=0.3.1" ldap3_proto = ">=0.3.1"
log = "*" log = "*"
orion = "0.17" orion = "0.17"
rustls = "0.20"
rustls-pemfile = "1" rustls-pemfile = "1"
serde = "*" serde = "*"
serde_bytes = "0.11" serde_bytes = "0.11"
@ -115,5 +114,9 @@ version = "0.11"
default-features = false default-features = false
features = ["rustls-tls-webpki-roots"] features = ["rustls-tls-webpki-roots"]
[dependencies.rustls]
version = "0.20"
features = ["dangerous_configuration"]
[dev-dependencies] [dev-dependencies]
mockall = "0.11" mockall = "0.11"

View File

@ -1,4 +1,4 @@
use crate::infra::configuration::LdapsOptions; use crate::infra::{configuration::LdapsOptions, ldap_server::read_certificates};
use anyhow::{anyhow, bail, ensure, Context, Result}; use anyhow::{anyhow, bail, ensure, Context, Result};
use futures_util::SinkExt; use futures_util::SinkExt;
use ldap3_proto::{ use ldap3_proto::{
@ -65,6 +65,7 @@ where
invalid_answer invalid_answer
); );
info!("Success"); info!("Success");
resp.close().await?;
Ok(()) Ok(())
} }
@ -85,15 +86,44 @@ fn get_root_certificates() -> rustls::RootCertStore {
root_store root_store
} }
fn get_tls_connector() -> Result<RustlsTlsConnector> { fn get_tls_connector(ldaps_options: &LdapsOptions) -> Result<RustlsTlsConnector> {
use rustls::ClientConfig; let mut client_config = rustls::ClientConfig::builder()
let client_config = std::sync::Arc::new(
ClientConfig::builder()
.with_safe_defaults() .with_safe_defaults()
.with_root_certificates(get_root_certificates()) .with_root_certificates(get_root_certificates())
.with_no_client_auth(), .with_no_client_auth();
); let (certs, _private_key) = read_certificates(ldaps_options)?;
Ok(client_config.into()) // Check that the server cert is the one in the config file.
struct CertificateVerifier {
certificate: rustls::Certificate,
certificate_path: String,
}
impl rustls::client::ServerCertVerifier for CertificateVerifier {
fn verify_server_cert(
&self,
end_entity: &rustls::Certificate,
_intermediates: &[rustls::Certificate],
_server_name: &rustls::ServerName,
_scts: &mut dyn Iterator<Item = &[u8]>,
_ocsp_response: &[u8],
_now: std::time::SystemTime,
) -> std::result::Result<rustls::client::ServerCertVerified, rustls::Error> {
if end_entity != &self.certificate {
return Err(rustls::Error::InvalidCertificateData(format!(
"Server certificate doesn't match the one in the config file {}",
&self.certificate_path
)));
}
Ok(rustls::client::ServerCertVerified::assertion())
}
}
let mut dangerous_config = rustls::client::DangerousClientConfig {
cfg: &mut client_config,
};
dangerous_config.set_certificate_verifier(std::sync::Arc::new(CertificateVerifier {
certificate: certs.first().expect("empty certificate chain").clone(),
certificate_path: ldaps_options.cert_file.clone(),
}));
Ok(std::sync::Arc::new(client_config).into())
} }
#[instrument(skip_all, level = "info", err)] #[instrument(skip_all, level = "info", err)]
@ -102,15 +132,20 @@ pub async fn check_ldaps(ldaps_options: &LdapsOptions) -> Result<()> {
info!("LDAPS not enabled"); info!("LDAPS not enabled");
return Ok(()); return Ok(());
}; };
let tls_connector = get_tls_connector()?; let tls_connector =
get_tls_connector(ldaps_options).context("while preparing the tls connection")?;
let url = format!("localhost:{}", ldaps_options.port); let url = format!("localhost:{}", ldaps_options.port);
check_ldap_endpoint( check_ldap_endpoint(
tls_connector tls_connector
.connect( .connect(
rustls::ServerName::try_from(url.as_str())?, rustls::ServerName::try_from("localhost")
TcpStream::connect(&url).await?, .context("while parsing the server name")?,
TcpStream::connect(&url)
.await
.context("while connecting TCP")?,
) )
.await?, .await
.context("while connecting TLS")?,
) )
.await .await
} }

View File

@ -4,7 +4,8 @@ use crate::{
opaque_handler::OpaqueHandler, opaque_handler::OpaqueHandler,
}, },
infra::{ infra::{
access_control::AccessControlledBackendHandler, configuration::Configuration, access_control::AccessControlledBackendHandler,
configuration::{Configuration, LdapsOptions},
ldap_handler::LdapHandler, ldap_handler::LdapHandler,
}, },
}; };
@ -126,20 +127,22 @@ fn read_private_key(key_file: &str) -> Result<PrivateKey> {
.map(rustls::PrivateKey) .map(rustls::PrivateKey)
} }
fn get_tls_acceptor(config: &Configuration) -> Result<RustlsTlsAcceptor> { pub fn read_certificates(
use rustls::{Certificate, ServerConfig}; ldaps_options: &LdapsOptions,
use rustls_pemfile::certs; ) -> Result<(Vec<rustls::Certificate>, rustls::PrivateKey)> {
use std::{fs::File, io::BufReader}; use std::{fs::File, io::BufReader};
// Load TLS key and cert files let certs = rustls_pemfile::certs(&mut BufReader::new(File::open(&ldaps_options.cert_file)?))?
let certs = certs(&mut BufReader::new(File::open(
&config.ldaps_options.cert_file,
)?))?
.into_iter() .into_iter()
.map(Certificate) .map(rustls::Certificate)
.collect::<Vec<_>>(); .collect::<Vec<_>>();
let private_key = read_private_key(&config.ldaps_options.key_file)?; let private_key = read_private_key(&ldaps_options.key_file)?;
Ok((certs, private_key))
}
fn get_tls_acceptor(ldaps_options: &LdapsOptions) -> Result<RustlsTlsAcceptor> {
let (certs, private_key) = read_certificates(ldaps_options)?;
let server_config = std::sync::Arc::new( let server_config = std::sync::Arc::new(
ServerConfig::builder() rustls::ServerConfig::builder()
.with_safe_defaults() .with_safe_defaults()
.with_no_client_auth() .with_no_client_auth()
.with_single_cert(certs, private_key)?, .with_single_cert(certs, private_key)?,
@ -190,7 +193,8 @@ where
if config.ldaps_options.enabled { if config.ldaps_options.enabled {
let tls_context = ( let tls_context = (
context_for_tls, context_for_tls,
get_tls_acceptor(config).context("while setting up the SSL certificate")?, get_tls_acceptor(&config.ldaps_options)
.context("while setting up the SSL certificate")?,
); );
let tls_binder = move || { let tls_binder = move || {
let tls_context = tls_context.clone(); let tls_context = tls_context.clone();