feat(oscal): live assessment endpoint POST /api/v1/oscal/assess (#212)
This commit was merged in pull request #212.
This commit is contained in:
@@ -10,6 +10,7 @@ pub mod issues;
|
||||
pub mod mcp_tokens;
|
||||
pub mod notifications;
|
||||
pub mod onboarding;
|
||||
pub mod oscal;
|
||||
pub mod pentest_handlers;
|
||||
pub use pentest_handlers as pentest;
|
||||
pub mod sbom;
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
//! OSCAL assessment endpoint.
|
||||
//!
|
||||
//! Assesses a target's findings against the breakpilot-compliance control
|
||||
//! catalog and returns a standard OSCAL assessment-results document. Ties
|
||||
//! together the ingest provider ([`OscalControlsProvider`]) and the assessment
|
||||
//! emitter (`compliance_core::models::oscal_assessment`).
|
||||
|
||||
use axum::extract::Extension;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::Json;
|
||||
use mongodb::bson::doc;
|
||||
use serde::Deserialize;
|
||||
|
||||
use compliance_core::models::onboarding::ComplianceFramework;
|
||||
use compliance_core::models::oscal_assessment::{assess, ControlLinker};
|
||||
use compliance_core::models::Finding;
|
||||
use compliance_core::tenant_ctx::TenantCtx;
|
||||
|
||||
use super::dto::{collect_cursor_async, tenant_db, AgentExt};
|
||||
use crate::controls::OscalControlsProvider;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct AssessRequest {
|
||||
/// The target / repo id whose findings are assessed.
|
||||
pub target_id: String,
|
||||
/// Frameworks to assess against; defaults to `[Cra]` when empty.
|
||||
#[serde(default)]
|
||||
pub frameworks: Vec<ComplianceFramework>,
|
||||
}
|
||||
|
||||
/// `POST /api/v1/oscal/assess` — pull the catalog(s), load the target's findings,
|
||||
/// and emit an OSCAL assessment-results document linking findings to controls.
|
||||
pub async fn assess_target(
|
||||
Extension(agent): AgentExt,
|
||||
tenant: TenantCtx,
|
||||
Json(req): Json<AssessRequest>,
|
||||
) -> Response {
|
||||
let cfg = &agent.config.breakpilot;
|
||||
let Some(base_url) = cfg.base_url.clone() else {
|
||||
return (
|
||||
StatusCode::SERVICE_UNAVAILABLE,
|
||||
"breakpilot base URL not configured (set BREAKPILOT_BASE_URL)",
|
||||
)
|
||||
.into_response();
|
||||
};
|
||||
|
||||
let frameworks = if req.frameworks.is_empty() {
|
||||
vec![ComplianceFramework::Cra]
|
||||
} else {
|
||||
req.frameworks.clone()
|
||||
};
|
||||
|
||||
let db = match tenant_db(&agent, &tenant).await {
|
||||
Ok(db) => db,
|
||||
Err(code) => return code.into_response(),
|
||||
};
|
||||
|
||||
let findings: Vec<Finding> = match db.findings().find(doc! { "repo_id": &req.target_id }).await
|
||||
{
|
||||
Ok(cursor) => collect_cursor_async(cursor).await,
|
||||
Err(e) => {
|
||||
tracing::warn!(error = %e, "failed to load findings for OSCAL assessment");
|
||||
return StatusCode::INTERNAL_SERVER_ERROR.into_response();
|
||||
}
|
||||
};
|
||||
|
||||
let provider = OscalControlsProvider::new(
|
||||
agent.http.clone(),
|
||||
base_url,
|
||||
cfg.token.clone(),
|
||||
&cfg.snapshot_dir,
|
||||
);
|
||||
let mut controls = Vec::new();
|
||||
for framework in &frameworks {
|
||||
match provider.load(*framework).await {
|
||||
Ok(document) => controls.extend(document.to_controls()),
|
||||
Err(e) => tracing::warn!(?framework, error = %e, "OSCAL catalog load failed"),
|
||||
}
|
||||
}
|
||||
|
||||
let assessment = assess(
|
||||
&controls,
|
||||
&findings,
|
||||
&ControlLinker::cra_seed(),
|
||||
chrono::Utc::now(),
|
||||
);
|
||||
Json(assessment).into_response()
|
||||
}
|
||||
@@ -6,6 +6,7 @@ use crate::api::handlers;
|
||||
pub fn build_router() -> Router {
|
||||
Router::new()
|
||||
.route("/api/v1/health", get(handlers::health))
|
||||
.route("/api/v1/oscal/assess", post(handlers::oscal::assess_target))
|
||||
.route("/api/v1/stats/overview", get(handlers::stats_overview))
|
||||
.route(
|
||||
"/api/v1/settings/ssh-public-key",
|
||||
|
||||
Reference in New Issue
Block a user