Skip to content

Commit c1f0b62

Browse files
authored
Merge pull request #1090 from jvanz/issue1078
feat: multiple client CA.
2 parents 2094eaa + 866cff2 commit c1f0b62

File tree

4 files changed

+219
-79
lines changed

4 files changed

+219
-79
lines changed

src/cli.rs

+1
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ pub(crate) fn build_cli() -> Command {
9595

9696
Arg::new("client-ca-file")
9797
.long("client-ca-file")
98+
.value_delimiter(',')
9899
.value_name("CLIENT_CA_FILE")
99100
.env("KUBEWARDEN_CLIENT_CA_FILE")
100101
.help("Path to an CA certificate file that issued the client certificate. Required to enable mTLS"),

src/config.rs

+13-7
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ pub struct Config {
5454
pub struct TlsConfig {
5555
pub cert_file: String,
5656
pub key_file: String,
57-
pub client_ca_file: Option<String>,
57+
pub client_ca_file: Vec<PathBuf>,
5858
}
5959

6060
impl Config {
@@ -192,14 +192,20 @@ fn readiness_probe_bind_address(matches: &clap::ArgMatches) -> Result<SocketAddr
192192
fn build_tls_config(matches: &clap::ArgMatches) -> Result<Option<TlsConfig>> {
193193
let cert_file = matches.get_one::<String>("cert-file").cloned();
194194
let key_file = matches.get_one::<String>("key-file").cloned();
195-
let client_ca_file = matches.get_one::<String>("client-ca-file").cloned();
195+
let client_ca_file = matches.get_many::<String>("client-ca-file");
196196

197197
match (cert_file, key_file, &client_ca_file) {
198-
(Some(cert_file), Some(key_file), _) => Ok(Some(TlsConfig {
199-
cert_file,
200-
key_file,
201-
client_ca_file,
202-
})),
198+
(Some(cert_file), Some(key_file), _) => {
199+
let client_ca_file = client_ca_file
200+
.unwrap_or_default()
201+
.map(PathBuf::from)
202+
.collect();
203+
Ok(Some(TlsConfig {
204+
cert_file,
205+
key_file,
206+
client_ca_file,
207+
}))
208+
}
203209
// No TLS configuration provided
204210
(None, None, None) => Ok(None),
205211
// Client CA certificate provided without server certificate and key

src/lib.rs

+51-26
Original file line numberDiff line numberDiff line change
@@ -291,13 +291,11 @@ impl PolicyServer {
291291
}
292292
}
293293

294-
/// Load the ServerConfig to be used by the Policy Server configuring the server
295-
/// certificate and mTLS when necessary
296-
///
297-
/// RustlsConfig does not offer a function to load the client CA certificate together with the
298-
/// service certificates. Therefore, we need to load everything and build the ServerConfig
299-
async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::ServerConfig> {
300-
let cert_reader = &mut BufReader::new(File::open(tls_config.cert_file.clone())?);
294+
async fn load_server_cert_and_key(
295+
cert_file: String,
296+
key_file: String,
297+
) -> Result<(Vec<CertificateDer<'static>>, PrivateKeyDer<'static>)> {
298+
let cert_reader = &mut BufReader::new(File::open(cert_file)?);
301299
let cert: Vec<CertificateDer> = rustls_pemfile::certs(cert_reader)
302300
.filter_map(|it| {
303301
if let Err(ref e) = it {
@@ -311,7 +309,7 @@ async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::Serve
311309
return Err(anyhow!("Multiple certificates provided in cert file"));
312310
}
313311

314-
let key_file_reader = &mut BufReader::new(File::open(tls_config.key_file.clone())?);
312+
let key_file_reader = &mut BufReader::new(File::open(key_file)?);
315313
let mut key_vec: Vec<Vec<u8>> = rustls_pemfile::read_all(key_file_reader)
316314
.filter_map(|i| match i.ok()? {
317315
Item::Sec1Key(key) => Some(key.secret_sec1_der().to_vec()),
@@ -332,11 +330,18 @@ async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::Serve
332330
let key = PrivateKeyDer::try_from(key_vec.pop().unwrap())
333331
.map_err(|e| anyhow!("Cannot parse server key: {e}"))?;
334332

335-
if let Some(client_ca_file) = tls_config.client_ca_file.clone() {
333+
Ok((cert, key))
334+
}
335+
336+
async fn load_client_cas(
337+
client_cas: Vec<std::path::PathBuf>,
338+
) -> Result<Arc<dyn rustls::server::danger::ClientCertVerifier>> {
339+
let mut store = RootCertStore::empty();
340+
//mTLS enabled
341+
for client_ca_file in client_cas {
336342
// we have the client CA. Therefore, we should enable mTLS.
337343
let client_ca_reader = &mut BufReader::new(File::open(client_ca_file)?);
338344

339-
let mut store = RootCertStore::empty();
340345
let client_ca_certs: Vec<_> = rustls_pemfile::certs(client_ca_reader)
341346
.filter_map(|it| {
342347
if let Err(ref e) = it {
@@ -351,15 +356,30 @@ async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::Serve
351356
client_ca_certs_ignored = cert_ignored,
352357
"Loaded client CA certificates"
353358
);
354-
let client_verifier = WebPkiClientVerifier::builder(Arc::new(store)).build()?;
359+
}
360+
WebPkiClientVerifier::builder(Arc::new(store))
361+
.build()
362+
.map_err(|e| anyhow!("Cannot build client verifier: {e}"))
363+
}
355364

365+
/// Load the ServerConfig to be used by the Policy Server configuring the server
366+
/// certificate and mTLS when necessary
367+
///
368+
/// RustlsConfig does not offer a function to load the client CA certificate together with the
369+
/// service certificates. Therefore, we need to load everything and build the ServerConfig
370+
async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::ServerConfig> {
371+
let (cert, key) =
372+
load_server_cert_and_key(tls_config.cert_file.clone(), tls_config.key_file.clone()).await?;
373+
374+
if tls_config.client_ca_file.is_empty() {
356375
return Ok(ServerConfig::builder()
357-
.with_client_cert_verifier(client_verifier)
376+
.with_no_client_auth()
358377
.with_single_cert(cert, key)?);
359378
}
360379

380+
let client_verifier = load_client_cas(tls_config.client_ca_file.clone()).await?;
361381
Ok(ServerConfig::builder()
362-
.with_no_client_auth()
382+
.with_client_cert_verifier(client_verifier)
363383
.with_single_cert(cert, key)?)
364384
}
365385

@@ -385,11 +405,12 @@ async fn create_tls_config_and_watch_certificate_changes(
385405
) -> Result<RustlsConfig> {
386406
use ::tracing::error;
387407

388-
let config = build_tls_server_config(&tls_config).await?;
389-
390-
let rust_config = RustlsConfig::from_config(Arc::new(config));
408+
// Build initial TLS configuration
409+
let initial_config = build_tls_server_config(&tls_config).await?;
410+
let rust_config = RustlsConfig::from_config(Arc::new(initial_config));
391411
let reloadable_rust_config = rust_config.clone();
392412

413+
// Init inotify to watch for changes in the certificate files
393414
let inotify =
394415
inotify::Inotify::init().map_err(|e| anyhow!("Cannot initialize inotify: {e}"))?;
395416
let cert_watch = inotify
@@ -404,15 +425,18 @@ async fn create_tls_config_and_watch_certificate_changes(
404425
.add(tls_config.key_file.clone(), inotify::WatchMask::CLOSE_WRITE)
405426
.map_err(|e| anyhow!("Cannot watch key file: {e}"))?;
406427

407-
let mut client_cert_watch = None;
408-
if let Some(ref client_ca_file) = tls_config.client_ca_file {
409-
client_cert_watch = Some(
428+
let client_cert_watches = tls_config
429+
.client_ca_file
430+
.clone()
431+
.into_iter()
432+
.map(|path| {
410433
inotify
411434
.watches()
412-
.add(client_ca_file, inotify::WatchMask::CLOSE_WRITE)
413-
.map_err(|e| anyhow!("Cannot watch client certificate file: {e}"))?,
414-
);
415-
}
435+
.add(path, inotify::WatchMask::CLOSE_WRITE)
436+
.map_err(|e| anyhow!("Cannot watch client certificate file: {e}"))
437+
.unwrap()
438+
})
439+
.collect::<Vec<_>>();
416440

417441
let buffer = [0; 1024];
418442
let stream = inotify
@@ -442,7 +466,8 @@ async fn create_tls_config_and_watch_certificate_changes(
442466
info!("TLS key file has been modified");
443467
key_changed = true;
444468
}
445-
if let Some(ref client_cert_watch) = client_cert_watch {
469+
470+
for client_cert_watch in client_cert_watches.iter() {
446471
if event.wd == *client_cert_watch {
447472
info!("TLS client certificate file has been modified");
448473
client_cert_changed = true;
@@ -454,11 +479,12 @@ async fn create_tls_config_and_watch_certificate_changes(
454479
if (key_changed && cert_changed)
455480
|| (client_cert_changed && (key_changed == cert_changed))
456481
{
457-
info!("reloading TLS certificates");
482+
info!("Reloading TLS certificates");
458483

459484
cert_changed = false;
460485
key_changed = false;
461486
client_cert_changed = false;
487+
462488
let server_config = build_tls_server_config(&tls_config).await;
463489
if let Err(e) = server_config {
464490
error!("Failed to reload TLS certificate: {}", e);
@@ -468,7 +494,6 @@ async fn create_tls_config_and_watch_certificate_changes(
468494
}
469495
}
470496
});
471-
472497
Ok(rust_config)
473498
}
474499

0 commit comments

Comments
 (0)