diff --git a/Cargo.lock b/Cargo.lock index a982d22..b5014a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1931,7 +1931,7 @@ dependencies = [ [[package]] name = "linux-patch-api" -version = "1.2.0" +version = "1.3.0" dependencies = [ "actix", "actix-rt", diff --git a/src/enroll/client.rs b/src/enroll/client.rs index 9d10064..10ced56 100644 --- a/src/enroll/client.rs +++ b/src/enroll/client.rs @@ -38,8 +38,12 @@ pub enum EnrollmentStatusResponse { Pending, Approved { ca_crt: String, + #[serde(default)] + ca_chain: String, server_crt: String, server_key: String, + #[serde(default)] + crl_pem: String, }, Denied, NotFound, @@ -49,8 +53,10 @@ pub enum EnrollmentStatusResponse { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct PkiBundle { pub ca_crt: String, + pub ca_chain: String, pub server_crt: String, pub server_key: String, + pub crl_pem: String, } impl From for Option { @@ -58,12 +64,16 @@ impl From for Option { match response { EnrollmentStatusResponse::Approved { ca_crt, + ca_chain, server_crt, server_key, + crl_pem, } => Some(PkiBundle { ca_crt, + ca_chain, server_crt, server_key, + crl_pem, }), _ => None, } @@ -451,8 +461,10 @@ impl EnrollmentClient { } EnrollmentStatusResponse::Approved { ca_crt, + ca_chain, server_crt, server_key, + crl_pem, } => { tracing::info!( elapsed_seconds = start.elapsed().as_secs(), @@ -461,8 +473,10 @@ impl EnrollmentClient { ); return Ok(PkiBundle { ca_crt, + ca_chain, server_crt, server_key, + crl_pem, }); } EnrollmentStatusResponse::Denied => { @@ -566,8 +580,10 @@ mod tests { fn approved_to_pki_bundle() { let status = EnrollmentStatusResponse::Approved { ca_crt: "ca".into(), + ca_chain: String::new(), server_crt: "crt".into(), server_key: "key".into(), + crl_pem: String::new(), }; let bundle: Option = status.into(); assert!(bundle.is_some()); diff --git a/src/enroll/mod.rs b/src/enroll/mod.rs index 49060d2..baa1f72 100644 --- a/src/enroll/mod.rs +++ b/src/enroll/mod.rs @@ -160,8 +160,10 @@ pub async fn run_enrollment( // Write certificates to configured paths (or defaults) provision::provision_pki_bundle( &pki_bundle.ca_crt, + &pki_bundle.ca_chain, &pki_bundle.server_crt, &pki_bundle.server_key, + &pki_bundle.crl_pem, config.tls_config(), ) .await?; diff --git a/src/enroll/provision.rs b/src/enroll/provision.rs index 45001e3..24f4034 100644 --- a/src/enroll/provision.rs +++ b/src/enroll/provision.rs @@ -16,6 +16,8 @@ const DEFAULT_CA_CERT: &str = "/etc/linux_patch_api/certs/ca.pem"; const DEFAULT_SERVER_CERT: &str = "/etc/linux_patch_api/certs/server.pem"; /// Default server key path. const DEFAULT_SERVER_KEY: &str = "/etc/linux_patch_api/certs/server.key.pem"; +/// Default CRL path. +const DEFAULT_CRL_PATH: &str = "/etc/linux_patch_api/certs/crl.pem"; /// Validate that a PEM string has proper format (BEGIN/END markers present). /// @@ -128,12 +130,14 @@ pub fn write_pem_file(path: &str, pem_data: &str, is_key: bool) -> Result<()> { /// Provision the full PKI bundle from an approved enrollment response. /// -/// Writes CA cert, server cert, and server key to configured paths. +/// Writes CA cert, CA chain, server cert, server key, and CRL to configured paths. /// Paths are read from TLS config if available, otherwise defaults are used. pub async fn provision_pki_bundle( ca_crt: &str, + _ca_chain: &str, server_crt: &str, server_key: &str, + crl_pem: &str, tls_config: Option<&super::super::config::loader::TlsConfig>, ) -> Result<()> { // Determine target paths from config or defaults @@ -173,6 +177,19 @@ pub async fn provision_pki_bundle( write_pem_file(&key_path, server_key, true).context("Failed to write server key")?; + // Write CRL if provided (non-empty) + let crl_path = if let Some(tls) = tls_config { + tls.crl_path.clone() + } else { + DEFAULT_CRL_PATH.to_string() + }; + if !crl_pem.trim().is_empty() { + write_pem_file(&crl_path, crl_pem, false).context("Failed to write CRL")?; + tracing::info!(path = %crl_path, "CRL written from enrollment bundle"); + } else { + tracing::info!("No CRL in enrollment bundle — agent will fetch on refresh cycle"); + } + // 3. Log successful provisioning with structured fields tracing::info!( ca_cert = %ca_path, diff --git a/tests/e2e/test_enrollment_e2e.rs b/tests/e2e/test_enrollment_e2e.rs index ee8c825..7387082 100644 --- a/tests/e2e/test_enrollment_e2e.rs +++ b/tests/e2e/test_enrollment_e2e.rs @@ -178,8 +178,10 @@ async fn test_full_enrollment_flow_happy_path() { let tls_config = build_tls_config(cert_dir.path()); provision::provision_pki_bundle( &bundle.ca_crt, + &bundle.ca_chain, &bundle.server_crt, &bundle.server_key, + &bundle.crl_pem, Some(&tls_config), ) .await @@ -445,8 +447,10 @@ async fn test_certificate_permission_verification() { let tls_config = build_tls_config(cert_dir.path()); provision::provision_pki_bundle( &bundle.ca_crt, + &bundle.ca_chain, &bundle.server_crt, &bundle.server_key, + &bundle.crl_pem, Some(&tls_config), ) .await