103 lines
3.0 KiB
Rust
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;
|
|
}
|