diff --git a/compliance-agent/Cargo.toml b/compliance-agent/Cargo.toml index 5d1f8b0..e0a129f 100644 --- a/compliance-agent/Cargo.toml +++ b/compliance-agent/Cargo.toml @@ -25,7 +25,7 @@ uuid = { workspace = true } secrecy = { workspace = true } regex = { workspace = true } axum = "0.8" -tower-http = { version = "0.6", features = ["cors", "trace"] } +tower-http = { version = "0.6", features = ["cors", "trace", "set-header"] } git2 = "0.20" octocrab = "0.44" tokio-cron-scheduler = "0.13" diff --git a/compliance-agent/src/api/server.rs b/compliance-agent/src/api/server.rs index 3038083..9b89714 100644 --- a/compliance-agent/src/api/server.rs +++ b/compliance-agent/src/api/server.rs @@ -1,8 +1,10 @@ use std::sync::Arc; +use axum::http::HeaderValue; use axum::{middleware, Extension}; use tokio::sync::RwLock; use tower_http::cors::CorsLayer; +use tower_http::set_header::SetResponseHeaderLayer; use tower_http::trace::TraceLayer; use crate::agent::ComplianceAgent; @@ -14,7 +16,24 @@ pub async fn start_api_server(agent: ComplianceAgent, port: u16) -> Result<(), A let mut app = routes::build_router() .layer(Extension(Arc::new(agent.clone()))) .layer(CorsLayer::permissive()) - .layer(TraceLayer::new_for_http()); + .layer(TraceLayer::new_for_http()) + // Security headers (defense-in-depth, primary enforcement via Traefik) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::STRICT_TRANSPORT_SECURITY, + HeaderValue::from_static("max-age=31536000; includeSubDomains"), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::X_FRAME_OPTIONS, + HeaderValue::from_static("DENY"), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::X_CONTENT_TYPE_OPTIONS, + HeaderValue::from_static("nosniff"), + )) + .layer(SetResponseHeaderLayer::overriding( + axum::http::header::REFERRER_POLICY, + HeaderValue::from_static("strict-origin-when-cross-origin"), + )); if let (Some(kc_url), Some(kc_realm)) = (&agent.config.keycloak_url, &agent.config.keycloak_realm)