Files
kca/ocsp-responder/src/main.rs
T
woggioni 69d78939a1
CI / Build and push docker image (push) Successful in 3m23s
CI / Build and push OCSP image (push) Successful in 7m53s
Build static musl OCSP image in Gitea Actions
2026-10-23 10:00:36 +00:00

103 lines
3.0 KiB
Rust

//! 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<dyn std::error::Error + Send + Sync>> {
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<dyn std::error::Error + Send + Sync>> {
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;
}