Skip to content

Commit aa5bb78

Browse files
authored
Merge pull request #1087 from fabriziosestito/fix/tls-config
fix: TlsConfig and mTLS logic
2 parents 33c1868 + 77151ed commit aa5bb78

File tree

5 files changed

+59
-60
lines changed

5 files changed

+59
-60
lines changed

cli-docs.md

-6
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,7 @@ This document contains the help content for the `policy-server` command-line pro
2424
Default value: `0.0.0.0`
2525
* `--always-accept-admission-reviews-on-namespace <NAMESPACE>` — Always accept AdmissionReviews that target the given namespace
2626
* `--cert-file <CERT_FILE>` — Path to an X.509 certificate file for HTTPS
27-
28-
Default value: ``
2927
* `--client-ca-file <CLIENT_CA_FILE>` — Path to an CA certificate file that issued the client certificate. Required to enable mTLS
30-
31-
Default value: ``
3228
* `--daemon` — If set, runs policy-server in detached mode as a daemon
3329
* `--daemon-pid-file <DAEMON-PID-FILE>` — Path to the PID file, used only when running in daemon mode
3430

@@ -41,8 +37,6 @@ This document contains the help content for the `policy-server` command-line pro
4137
* `--enable-pprof` — Enable pprof profiling
4238
* `--ignore-kubernetes-connection-failure` — Do not exit with an error if the Kubernetes connection fails. This will cause context-aware policies to break when there's no connection with Kubernetes.
4339
* `--key-file <KEY_FILE>` — Path to an X.509 private key file for HTTPS
44-
45-
Default value: ``
4640
* `--log-fmt <LOG_FMT>` — Log output format
4741

4842
Default value: `text`

src/cli.rs

-3
Original file line numberDiff line numberDiff line change
@@ -84,21 +84,18 @@ pub(crate) fn build_cli() -> Command {
8484
Arg::new("cert-file")
8585
.long("cert-file")
8686
.value_name("CERT_FILE")
87-
.default_value("")
8887
.env("KUBEWARDEN_CERT_FILE")
8988
.help("Path to an X.509 certificate file for HTTPS"),
9089

9190
Arg::new("key-file")
9291
.long("key-file")
9392
.value_name("KEY_FILE")
94-
.default_value("")
9593
.env("KUBEWARDEN_KEY_FILE")
9694
.help("Path to an X.509 private key file for HTTPS"),
9795

9896
Arg::new("client-ca-file")
9997
.long("client-ca-file")
10098
.value_name("CLIENT_CA_FILE")
101-
.default_value("")
10299
.env("KUBEWARDEN_CLIENT_CA_FILE")
103100
.help("Path to an CA certificate file that issued the client certificate. Required to enable mTLS"),
104101

src/config.rs

+23-13
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_cert_file: Option<String>,
57+
pub client_ca_file: Option<String>,
5858
}
5959

6060
impl Config {
@@ -130,7 +130,7 @@ impl Config {
130130
.expect("clap should have assigned a default value")
131131
.to_owned();
132132

133-
let tls_config = Some(build_tls_config(matches)?);
133+
let tls_config = build_tls_config(matches)?;
134134

135135
let enable_pprof = matches
136136
.get_one::<bool>("enable-pprof")
@@ -189,18 +189,28 @@ fn readiness_probe_bind_address(matches: &clap::ArgMatches) -> Result<SocketAddr
189189
.map_err(|e| anyhow!("error parsing arguments: {}", e))
190190
}
191191

192-
fn build_tls_config(matches: &clap::ArgMatches) -> Result<TlsConfig> {
193-
let cert_file = matches.get_one::<String>("cert-file").unwrap().to_owned();
194-
let key_file = matches.get_one::<String>("key-file").unwrap().to_owned();
195-
let client_ca_cert_file = matches.get_one::<String>("client-ca-file").cloned();
196-
if cert_file.is_empty() != key_file.is_empty() {
197-
return Err(anyhow!("error parsing arguments: either both --cert-file and --key-file must be provided, or neither"));
192+
fn build_tls_config(matches: &clap::ArgMatches) -> Result<Option<TlsConfig>> {
193+
let cert_file = matches.get_one::<String>("cert-file").cloned();
194+
let key_file = matches.get_one::<String>("key-file").cloned();
195+
let client_ca_file = matches.get_one::<String>("client-ca-file").cloned();
196+
197+
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+
})),
203+
// No TLS configuration provided
204+
(None, None, None) => Ok(None),
205+
// Client CA certificate provided without server certificate and key
206+
(None, None, Some(_)) => Err(anyhow!(
207+
"client CA certificate requires server certificate and key to be specified"
208+
)),
209+
// Server certificate or key provided without the other
210+
(Some(_), None, _) | (None, Some(_), _) => Err(anyhow!(
211+
"both certificate and key must be provided together"
212+
)),
198213
}
199-
Ok(TlsConfig {
200-
cert_file,
201-
key_file,
202-
client_ca_cert_file,
203-
})
204214
}
205215

206216
fn policies(matches: &clap::ArgMatches) -> Result<HashMap<String, PolicyOrPolicyGroup>> {

src/lib.rs

+35-37
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,17 @@ use policy_evaluator::{
3333
use profiling::activate_memory_profiling;
3434
use rayon::prelude::*;
3535
use std::{fs, net::SocketAddr, sync::Arc};
36+
use std::{fs::File, io::BufReader};
3637
use tokio::{
3738
sync::{oneshot, Notify, Semaphore},
3839
time,
3940
};
4041
use tower_http::trace::{self, TraceLayer};
4142

43+
use rustls::{server::WebPkiClientVerifier, RootCertStore, ServerConfig};
44+
use rustls_pemfile::Item;
45+
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
46+
4247
// This is required by certificate hot reload when using inotify, which is available only on linux
4348
#[cfg(target_os = "linux")]
4449
use tokio_stream::StreamExt;
@@ -286,21 +291,14 @@ impl PolicyServer {
286291
}
287292
}
288293

289-
// Load the ServerConfig to be used by the Policy Server configuring the server
290-
// certificate and mTLS when necessary
291-
//
292-
// RustlsConfig does not offer a function to load the client CA certificate together with the
293-
// service certificates. Therefore, we need to load everything and build the ServerConfig
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
294299
async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::ServerConfig> {
295-
use std::{fs::File, io::BufReader};
296-
297-
use rustls::{server::WebPkiClientVerifier, RootCertStore, ServerConfig};
298-
use rustls_pemfile::Item;
299-
use rustls_pki_types::{CertificateDer, PrivateKeyDer};
300-
301-
let cert_file = &mut BufReader::new(File::open(tls_config.cert_file.clone())?);
302-
let key_file = &mut BufReader::new(File::open(tls_config.key_file.clone())?);
303-
let cert: Vec<CertificateDer> = rustls_pemfile::certs(cert_file)
300+
let cert_reader = &mut BufReader::new(File::open(tls_config.cert_file.clone())?);
301+
let cert: Vec<CertificateDer> = rustls_pemfile::certs(cert_reader)
304302
.filter_map(|it| {
305303
if let Err(ref e) = it {
306304
warn!("Cannot parse certificate: {e}");
@@ -312,7 +310,9 @@ async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::Serve
312310
if cert.len() > 1 {
313311
return Err(anyhow!("Multiple certificates provided in cert file"));
314312
}
315-
let mut key_vec: Vec<Vec<u8>> = rustls_pemfile::read_all(key_file)
313+
314+
let key_file_reader = &mut BufReader::new(File::open(tls_config.key_file.clone())?);
315+
let mut key_vec: Vec<Vec<u8>> = rustls_pemfile::read_all(key_file_reader)
316316
.filter_map(|i| match i.ok()? {
317317
Item::Sec1Key(key) => Some(key.secret_sec1_der().to_vec()),
318318
Item::Pkcs1Key(key) => Some(key.secret_pkcs1_der().to_vec()),
@@ -332,36 +332,35 @@ async fn build_tls_server_config(tls_config: &TlsConfig) -> Result<rustls::Serve
332332
let key = PrivateKeyDer::try_from(key_vec.pop().unwrap())
333333
.map_err(|e| anyhow!("Cannot parse server key: {e}"))?;
334334

335-
let config = if let Some(client_ca_cert_file_path) = tls_config.client_ca_cert_file.clone() {
335+
if let Some(client_ca_file) = tls_config.client_ca_file.clone() {
336336
// we have the client CA. Therefore, we should enable mTLS.
337-
let client_ca_cert_file = &mut BufReader::new(File::open(client_ca_cert_file_path)?);
337+
let client_ca_reader = &mut BufReader::new(File::open(client_ca_file)?);
338338

339-
let mut ca_certs = RootCertStore::empty();
340-
let client_ca_certs: Vec<_> = rustls_pemfile::certs(client_ca_cert_file)
339+
let mut store = RootCertStore::empty();
340+
let client_ca_certs: Vec<_> = rustls_pemfile::certs(client_ca_reader)
341341
.filter_map(|it| {
342342
if let Err(ref e) = it {
343343
warn!("Cannot parse client CA certificate: {e}");
344344
}
345345
it.ok()
346346
})
347347
.collect();
348-
let (cert_added, cert_ignored) = ca_certs.add_parsable_certificates(client_ca_certs);
348+
let (cert_added, cert_ignored) = store.add_parsable_certificates(client_ca_certs);
349349
info!(
350350
client_ca_certs_added = cert_added,
351351
client_ca_certs_ignored = cert_ignored,
352352
"Loaded client CA certificates"
353353
);
354-
let client_verifier = WebPkiClientVerifier::builder(Arc::new(ca_certs)).build()?;
354+
let client_verifier = WebPkiClientVerifier::builder(Arc::new(store)).build()?;
355355

356-
ServerConfig::builder()
356+
return Ok(ServerConfig::builder()
357357
.with_client_cert_verifier(client_verifier)
358-
.with_single_cert(cert, key)?
359-
} else {
360-
ServerConfig::builder()
361-
.with_no_client_auth()
362-
.with_single_cert(cert, key)?
363-
};
364-
Ok(config)
358+
.with_single_cert(cert, key)?);
359+
}
360+
361+
Ok(ServerConfig::builder()
362+
.with_no_client_auth()
363+
.with_single_cert(cert, key)?)
365364
}
366365

367366
/// There's no watching of the certificate files on non-linux platforms
@@ -386,10 +385,6 @@ async fn create_tls_config_and_watch_certificate_changes(
386385
) -> Result<RustlsConfig> {
387386
use ::tracing::error;
388387

389-
let cert_file_path = tls_config.cert_file.clone();
390-
let key_file_path = tls_config.key_file.clone();
391-
let client_ca_cert_path = tls_config.client_ca_cert_file.clone();
392-
393388
let config = build_tls_server_config(&tls_config).await?;
394389

395390
let rust_config = RustlsConfig::from_config(Arc::new(config));
@@ -399,19 +394,22 @@ async fn create_tls_config_and_watch_certificate_changes(
399394
inotify::Inotify::init().map_err(|e| anyhow!("Cannot initialize inotify: {e}"))?;
400395
let cert_watch = inotify
401396
.watches()
402-
.add(cert_file_path.clone(), inotify::WatchMask::CLOSE_WRITE)
397+
.add(
398+
tls_config.cert_file.clone(),
399+
inotify::WatchMask::CLOSE_WRITE,
400+
)
403401
.map_err(|e| anyhow!("Cannot watch certificate file: {e}"))?;
404402
let key_watch = inotify
405403
.watches()
406-
.add(key_file_path.clone(), inotify::WatchMask::CLOSE_WRITE)
404+
.add(tls_config.key_file.clone(), inotify::WatchMask::CLOSE_WRITE)
407405
.map_err(|e| anyhow!("Cannot watch key file: {e}"))?;
408406

409407
let mut client_cert_watch = None;
410-
if let Some(ref client_cert_file) = client_ca_cert_path {
408+
if let Some(ref client_ca_file) = tls_config.client_ca_file {
411409
client_cert_watch = Some(
412410
inotify
413411
.watches()
414-
.add(client_cert_file.clone(), inotify::WatchMask::CLOSE_WRITE)
412+
.add(client_ca_file, inotify::WatchMask::CLOSE_WRITE)
415413
.map_err(|e| anyhow!("Cannot watch client certificate file: {e}"))?,
416414
);
417415
}

tests/integration_test.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -688,7 +688,7 @@ async fn test_detect_certificate_rotation() {
688688
config.tls_config = Some(policy_server::config::TlsConfig {
689689
cert_file: cert_file.to_str().unwrap().to_owned(),
690690
key_file: key_file.to_str().unwrap().to_owned(),
691-
client_ca_cert_file: Some(client_ca.to_str().unwrap().to_owned()),
691+
client_ca_file: Some(client_ca.to_str().unwrap().to_owned()),
692692
});
693693
config.policies = HashMap::new();
694694

0 commit comments

Comments
 (0)