//! Standalone KCA OCSP responder. mod cache; mod config; mod issuer; mod ocsp; mod signer; mod snapshot; mod store; use std::sync::Arc; use axum::extract::DefaultBodyLimit; use axum::routing::{get, post}; use axum::Router; use tower_http::timeout::TimeoutLayer; use tracing_subscriber::EnvFilter; use crate::issuer::Issuer; use crate::ocsp::AppState; use crate::store::StatusStore; #[tokio::main] async fn main() -> Result<(), Box> { if std::env::args().any(|argument| argument == "--healthcheck") { return healthcheck(); } tracing_subscriber::fmt() .with_env_filter( EnvFilter::try_from_default_env().unwrap_or_else(|_| EnvFilter::new("info")), ) .init(); let config = config::load_config()?; let store = StatusStore::new(&config.s3).await; let mut issuers = Vec::with_capacity(config.issuers.len()); for issuer_config in &config.issuers { let issuer = Issuer::load(issuer_config)?; tracing::info!( issuer = %issuer.name, fingerprint = %issuer.fingerprint, "loaded OCSP issuer" ); issuers.push(issuer); } let state = AppState { issuers, store, config: Arc::new(config.clone()), }; let app = Router::new() .route("/ocsp", post(ocsp::ocsp_post)) .route("/ocsp/{request}", get(ocsp::ocsp_get)) .route("/healthz", get(health)) .route("/readyz", get(health)) .layer(DefaultBodyLimit::max(16 * 1024)) .layer(TimeoutLayer::with_status_code( axum::http::StatusCode::REQUEST_TIMEOUT, std::time::Duration::from_secs(15), )) .with_state(state); let listener = tokio::net::TcpListener::bind(config.listen).await?; tracing::info!(listen = %config.listen, "OCSP responder listening"); axum::serve(listener, app) .with_graceful_shutdown(shutdown_signal()) .await?; Ok(()) } async fn health() -> &'static str { "ok" } fn healthcheck() -> Result<(), Box> { use std::io::{Read, Write}; use std::net::{IpAddr, Ipv4Addr, SocketAddr}; let config = config::load_config()?; let host = if config.listen.ip().is_unspecified() { match config.listen.ip() { IpAddr::V4(_) => IpAddr::V4(Ipv4Addr::LOCALHOST), IpAddr::V6(_) => IpAddr::V6(std::net::Ipv6Addr::LOCALHOST), } } else { config.listen.ip() }; let socket = SocketAddr::new(host, config.listen.port()); let mut stream = std::net::TcpStream::connect(socket)?; stream.write_all(b"GET /healthz HTTP/1.0\r\nHost: localhost\r\n\r\n")?; let mut response = String::new(); stream.read_to_string(&mut response)?; if response.starts_with("HTTP/1.") && response.contains(" 200 ") { Ok(()) } else { Err("health endpoint did not return HTTP 200".into()) } } async fn shutdown_signal() { let _ = tokio::signal::ctrl_c().await; }