feat(pki): add CRL generation, distribution endpoint, and enrollment bundle extension (#26)
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 6s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m10s
CI Pipeline / Security Audit (push) Successful in 1m26s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
All checks were successful
CI Pipeline / Rust Format Check (push) Successful in 6s
CI Pipeline / Clippy Lints (push) Successful in 52s
CI Pipeline / Rust Unit Tests (push) Successful in 1m10s
CI Pipeline / Security Audit (push) Successful in 1m26s
CI Pipeline / Frontend Lint & Type Check (push) Successful in 15s
CI Pipeline / Build .deb & Release (push) Has been skipped
* feat(pki): add CRL generation, distribution endpoint, and enrollment bundle extension Implements manager-side CRL infrastructure for issue #7: - Add CertAuthority::generate_crl() using rcgen 0.13 - Add GET /api/v1/pki/crl.pem public endpoint - Extend PkiBundle with ca_chain and crl_pem fields - Update enrollment route to include CRL in bundle - Mount pki route as public endpoint - Add proptest dev-dependency * style: fix cargo fmt in enrollment.rs --------- Co-authored-by: Draco Lunaris <331325+Draco-Lunaris@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
parent
80ffb6b62f
commit
5aec9e629c
@ -303,10 +303,17 @@ async fn approve_enrollment(
|
||||
// server memory beyond the signing operation.
|
||||
//
|
||||
// See: https://github.com/Draco-Lunaris/Linux-Patch-Manager/issues/9
|
||||
//
|
||||
// Include the full CA chain (for root mode, same as ca_crt; for sub-CA,
|
||||
// includes intermediate + root) and the current CRL.
|
||||
let ca_chain = issued.ca_root_pem.clone(); // Root mode: chain is just the root cert
|
||||
let crl_pem = state.ca.generate_crl(&state.db).await.unwrap_or_default(); // Empty string on failure: agent falls back to WebPKI-only
|
||||
let pki = PkiBundle {
|
||||
ca_crt: issued.ca_root_pem,
|
||||
ca_chain,
|
||||
server_crt: issued.server_cert_pem,
|
||||
server_key: issued.server_key_pem,
|
||||
crl_pem,
|
||||
};
|
||||
state.approved_enrollments.insert(
|
||||
enrollment_request.polling_token.clone(),
|
||||
|
||||
4
crates/pm-web/src/routes/mod.rs
Executable file → Normal file
4
crates/pm-web/src/routes/mod.rs
Executable file → Normal file
@ -8,10 +8,10 @@ pub mod health_checks;
|
||||
pub mod hosts;
|
||||
pub mod jobs;
|
||||
pub mod maintenance_windows;
|
||||
pub mod pki;
|
||||
pub mod reports;
|
||||
pub mod settings;
|
||||
pub mod sso;
|
||||
pub mod status;
|
||||
pub mod users;
|
||||
pub mod ws;
|
||||
|
||||
pub mod reports;
|
||||
|
||||
61
crates/pm-web/src/routes/pki.rs
Normal file
61
crates/pm-web/src/routes/pki.rs
Normal file
@ -0,0 +1,61 @@
|
||||
//! PKI endpoints for certificate revocation list (CRL) distribution.
|
||||
//!
|
||||
//! This module exposes the CRL endpoint that agents poll every 24 hours to
|
||||
//! check for revoked certificates. The CRL is signed by the internal CA and
|
||||
//! is publicly accessible (CRLs are self-authenticating — they carry the CA
|
||||
//! signature and do not require client authentication).
|
||||
|
||||
use crate::AppState;
|
||||
use axum::{
|
||||
extract::State,
|
||||
http::{header, StatusCode},
|
||||
response::IntoResponse,
|
||||
routing::get,
|
||||
Router,
|
||||
};
|
||||
|
||||
/// Define public PKI routes.
|
||||
///
|
||||
/// These endpoints are **unauthenticated** because CRLs are self-authenticating:
|
||||
/// the agent verifies the CRL signature against its pinned CA certificate.
|
||||
/// No client certificate or API key is required.
|
||||
pub fn router() -> Router<AppState> {
|
||||
Router::new().route("/pki/crl.pem", get(get_crl))
|
||||
}
|
||||
|
||||
/// `GET /api/v1/pki/crl.pem`
|
||||
///
|
||||
/// Returns the current Certificate Revocation List (CRL) as a PEM-encoded
|
||||
/// X.509 CRL. The CRL is signed by the internal CA and contains the serial
|
||||
/// numbers of all revoked certificates that have not yet expired.
|
||||
///
|
||||
/// # Cache headers
|
||||
///
|
||||
/// The response includes `Cache-Control: max-age=3600` (1 hour) to allow
|
||||
/// intermediate caches to serve the CRL. Agents refresh every 24 hours,
|
||||
/// so a 1-hour cache is a reasonable balance between freshness and load.
|
||||
///
|
||||
/// # CRL generation
|
||||
///
|
||||
/// The CRL is generated on demand from the `certificates` table. For our
|
||||
/// target scale (max ~2500 clients), this is a fast query and the resulting
|
||||
/// CRL is KB-range. If performance becomes a concern, the CRL can be cached
|
||||
/// in memory and regenerated on a schedule (see background task in main.rs).
|
||||
async fn get_crl(State(state): State<AppState>) -> impl IntoResponse {
|
||||
match state.ca.generate_crl(&state.db).await {
|
||||
Ok(crl_pem) => (
|
||||
StatusCode::OK,
|
||||
[(
|
||||
header::CONTENT_TYPE,
|
||||
"application/x-pem-file; charset=utf-8",
|
||||
)],
|
||||
[(header::CACHE_CONTROL, "max-age=3600")],
|
||||
crl_pem,
|
||||
)
|
||||
.into_response(),
|
||||
Err(e) => {
|
||||
tracing::error!(error = %e, "Failed to generate CRL");
|
||||
(StatusCode::INTERNAL_SERVER_ERROR, "Failed to generate CRL").into_response()
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user