diff --git a/Cargo.lock b/Cargo.lock index f6ec4073..8bc5d636 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3506,9 +3506,9 @@ dependencies = [ [[package]] name = "rustls-webpki" -version = "0.103.12" +version = "0.103.13" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8279bb85272c9f10811ae6a6c547ff594d6a7f3c6c6b02ee9726d1d0dcfcdd06" +checksum = "61c429a8649f110dddef65e2a5ad240f747e85f7758a6bccc7e5777bd33f756e" dependencies = [ "aws-lc-rs", "ring", diff --git a/src/main.rs b/src/main.rs index eb58963f..1ecadec0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -72,6 +72,28 @@ async fn main() -> Result<(), GatewayError> { tokio::fs::create_dir_all(cert_dir).await?; #[cfg(unix)] tokio::fs::set_permissions(cert_dir, Permissions::from_mode(0o700)).await?; + } else { + // Probe write access + let probe = cert_dir.join(".write_test"); + match tokio::fs::OpenOptions::new() + .write(true) + .create(true) + .truncate(true) + .open(&probe) + .await + { + Ok(_) => { + let _ = tokio::fs::remove_file(&probe).await; + } + Err(e) if e.kind() == std::io::ErrorKind::PermissionDenied => { + return Err(GatewayError::SetupError(format!( + "Certificate directory '{}' exists but is not writable. \ + Please check directory permissions.", + cert_dir.display() + ))); + } + Err(e) => return Err(e.into()), + } } let maybe_tls_config = load_tls_config(cert_dir)?; diff --git a/src/setup.rs b/src/setup.rs index 4775029c..2864c039 100644 --- a/src/setup.rs +++ b/src/setup.rs @@ -1,4 +1,5 @@ use std::{ + path::{Path, PathBuf}, sync::{ Arc, Mutex, atomic::{AtomicBool, Ordering}, @@ -94,42 +95,33 @@ fn validate_cert_bundle( Ok(()) } -pub async fn run_setup( - config: &Config, - logs_rx: Arc>>, -) -> Result { - let cert_dir = &config.cert_dir; - let setup_server = GatewaySetupServer::new(logs_rx); - let tls_config = setup_server.await_setup(config).await?; - - let cert_path = cert_dir.join(GRPC_CERT_NAME); - let key_path = cert_dir.join(GRPC_KEY_NAME); - // Certificate and its key will be accessed only to this process's user. +/// Persist all four TLS certificate files that the gateway needs to start with mTLS on +/// subsequent runs. This is called from within the `SendCert` gRPC handler **before** +/// returning `Ok` to Core, so any I/O failure causes Core to treat the adoption as failed +/// rather than silently leaving the gateway without certificates. +async fn save_tls_certs(cert_dir: &Path, config: &TlsConfig) -> Result<(), GatewayError> { let mut options = OpenOptions::new(); options.write(true).create(true).truncate(true); #[cfg(unix)] options.mode(0o600); // rw------- - // Write certificate to a file. + options - .open(cert_path) + .open(cert_dir.join(GRPC_CERT_NAME)) .await? - .write_all(tls_config.grpc_cert_pem.as_bytes()) + .write_all(config.grpc_cert_pem.as_bytes()) .await?; - // Write key to a file. options - .open(key_path) + .open(cert_dir.join(GRPC_KEY_NAME)) .await? - .write_all(tls_config.grpc_key_pem.as_bytes()) + .write_all(config.grpc_key_pem.as_bytes()) .await?; - // Write CA certificate to a file. options .open(cert_dir.join(GRPC_CA_CERT_NAME)) .await? - .write_all(tls_config.grpc_ca_cert_pem.as_bytes()) + .write_all(config.grpc_ca_cert_pem.as_bytes()) .await?; - // Write Core client certificate (PEM-encoded) to a file for serial pinning on restart. let core_client_cert_pem = defguard_certs::der_to_pem( - &tls_config.core_client_cert_der, + &config.core_client_cert_der, defguard_certs::PemLabel::Certificate, ) .map_err(|err| { @@ -142,11 +134,20 @@ pub async fn run_setup( .await? .write_all(core_client_cert_pem.as_bytes()) .await?; + log::info!( "Generated gRPC TLS certificates have been saved to {}", cert_dir.display() ); + Ok(()) +} +pub async fn run_setup( + config: &Config, + logs_rx: Arc>>, +) -> Result { + let setup_server = GatewaySetupServer::new(logs_rx, config.cert_dir.clone()); + let tls_config = setup_server.await_setup(config).await?; Ok(tls_config) } @@ -157,6 +158,7 @@ pub struct GatewaySetupServer { setup_tx: Arc>>>, setup_rx: Arc>>, adoption_expired: Arc, + cert_dir: Arc, } impl Clone for GatewaySetupServer { @@ -168,13 +170,14 @@ impl Clone for GatewaySetupServer { setup_tx: Arc::clone(&self.setup_tx), setup_rx: Arc::clone(&self.setup_rx), adoption_expired: Arc::clone(&self.adoption_expired), + cert_dir: Arc::clone(&self.cert_dir), } } } impl GatewaySetupServer { #[must_use] - pub fn new(logs_rx: LogsReceiver) -> Self { + pub fn new(logs_rx: LogsReceiver, cert_dir: PathBuf) -> Self { let (setup_tx, setup_rx) = oneshot::channel(); Self { key_pair: Arc::new(Mutex::new(None)), @@ -183,6 +186,7 @@ impl GatewaySetupServer { setup_tx: Arc::new(tokio::sync::Mutex::new(Some(setup_tx))), setup_rx: Arc::new(tokio::sync::Mutex::new(setup_rx)), adoption_expired: Arc::new(AtomicBool::new(false)), + cert_dir: Arc::new(cert_dir), } } @@ -531,6 +535,13 @@ impl gateway_setup_server::GatewaySetup for GatewaySetupServer { return Err(Status::internal("Setup channel sender not found")); }; + if let Err(err) = save_tls_certs(&self.cert_dir, &configuration).await { + let msg = format!("Failed to save certificates to disk: {err}"); + error!("{msg}"); + self.clear_setup_session(); + return Err(Status::internal(msg)); + } + sender.send(configuration).map_err(|_| { let msg = "Failed to send setup configuration through channel"; error!("{msg}");