Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1ab443457e |
@@ -498,6 +498,13 @@ impl PipelineOrchestrator {
|
|||||||
"Unified pipeline: scan plan built"
|
"Unified pipeline: scan plan built"
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Ingest + classify (tramiton for firmware) and store the detected type.
|
||||||
|
self.classify_and_store(target, &target_id, scan_run_id)
|
||||||
|
.await;
|
||||||
|
// Provision a DAST target from a LiveUrl artifact so DAST fires for
|
||||||
|
// wizard-created targets, not just migrated ones.
|
||||||
|
self.ensure_dast_target(target, &plan).await;
|
||||||
|
|
||||||
match target.code_artifact() {
|
match target.code_artifact() {
|
||||||
Some(code) if code.kind == ArtifactKind::GitRepo => {
|
Some(code) if code.kind == ArtifactKind::GitRepo => {
|
||||||
let repo = repo_view_from_target(target, code);
|
let repo = repo_view_from_target(target, code);
|
||||||
@@ -527,6 +534,110 @@ impl PipelineOrchestrator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Ingest the target's artifacts, classify (tramiton for firmware/RTOS/Yocto,
|
||||||
|
/// heuristics otherwise), and store the detected classification on the target.
|
||||||
|
/// Best-effort — never fails the scan.
|
||||||
|
async fn classify_and_store(
|
||||||
|
&self,
|
||||||
|
target: &OnboardedTarget,
|
||||||
|
target_id: &str,
|
||||||
|
scan_run_id: &str,
|
||||||
|
) {
|
||||||
|
self.update_phase(scan_run_id, "classification").await;
|
||||||
|
let ctx = crate::ingest::IngestContext::from_config(&self.config, target_id);
|
||||||
|
let ingest_set = match crate::ingest::ingest_all(target, &ctx) {
|
||||||
|
Ok(set) => set,
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(target_id, error = %e, "Unified pipeline: ingest for classification failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let working_paths = ingest_set.working_paths();
|
||||||
|
match crate::classify::classify_target(
|
||||||
|
target,
|
||||||
|
&working_paths,
|
||||||
|
&crate::classify::TramitonNative,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Ok(classification) => {
|
||||||
|
tracing::info!(
|
||||||
|
target_id,
|
||||||
|
suggested = %classification.suggested,
|
||||||
|
"Unified pipeline: classified target"
|
||||||
|
);
|
||||||
|
if let (Some(oid), Ok(bson)) = (target.id, mongodb::bson::to_bson(&classification))
|
||||||
|
{
|
||||||
|
let _ = self
|
||||||
|
.db
|
||||||
|
.onboarded_targets()
|
||||||
|
.update_one(
|
||||||
|
doc! { "_id": oid },
|
||||||
|
doc! { "$set": { "classification": bson } },
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
tracing::warn!(target_id, error = %e, "Unified pipeline: classification failed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the target has a `LiveUrl` artifact and DAST is planned, provision a
|
||||||
|
/// `DastTarget` (keyed by `repo_id` = target id) so the existing DAST trigger
|
||||||
|
/// fires for wizard-created targets. Idempotent.
|
||||||
|
async fn ensure_dast_target(
|
||||||
|
&self,
|
||||||
|
target: &OnboardedTarget,
|
||||||
|
plan: &crate::pipeline::plan::ScanPlan,
|
||||||
|
) {
|
||||||
|
if !plan.has(ScanType::Dast) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let (Some(url), Some(oid)) = (target.live_url(), target.id) else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let target_id = oid.to_hex();
|
||||||
|
if self
|
||||||
|
.db
|
||||||
|
.dast_targets()
|
||||||
|
.find_one(doc! { "repo_id": &target_id })
|
||||||
|
.await
|
||||||
|
.ok()
|
||||||
|
.flatten()
|
||||||
|
.is_some()
|
||||||
|
{
|
||||||
|
return; // already provisioned
|
||||||
|
}
|
||||||
|
let kind = url
|
||||||
|
.web
|
||||||
|
.as_ref()
|
||||||
|
.map(|w| w.target_kind.clone())
|
||||||
|
.unwrap_or(DastTargetType::WebApp);
|
||||||
|
let mut dast = DastTarget::new(target.name.clone(), url.source_ref.clone(), kind);
|
||||||
|
dast.repo_id = Some(target_id);
|
||||||
|
if let Some(web) = &url.web {
|
||||||
|
dast.excluded_paths = web.excluded_paths.clone();
|
||||||
|
dast.max_crawl_depth = web.max_crawl_depth;
|
||||||
|
dast.rate_limit = web.rate_limit;
|
||||||
|
dast.allow_destructive = web.allow_destructive;
|
||||||
|
}
|
||||||
|
if let Some(auth) = &url.auth {
|
||||||
|
dast.auth_config = Some(DastAuthConfig {
|
||||||
|
method: auth.method.clone(),
|
||||||
|
login_url: auth.login_url.clone(),
|
||||||
|
username: auth.username.clone(),
|
||||||
|
password: None,
|
||||||
|
token: auth.secret.clone(),
|
||||||
|
headers: auth.headers.clone(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Err(e) = self.db.dast_targets().insert_one(&dast).await {
|
||||||
|
tracing::warn!(error = %e, "Unified pipeline: failed to provision DAST target");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Sync the onboarded-target document after a scan: bump `findings_count`
|
/// Sync the onboarded-target document after a scan: bump `findings_count`
|
||||||
/// and advance the git artifact's `last_scanned_commit` watermark.
|
/// and advance the git artifact's `last_scanned_commit` watermark.
|
||||||
async fn finalize_target(
|
async fn finalize_target(
|
||||||
|
|||||||
Reference in New Issue
Block a user