fix(plc): scan every PLC-source artifact, not just the first (#178)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Has been cancelled
CI / Deploy Dashboard (push) Has been cancelled
CI / Deploy Docs (push) Has been cancelled
CI / Deploy MCP (push) Has been cancelled
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Has been cancelled
CI / Deploy Dashboard (push) Has been cancelled
CI / Deploy Docs (push) Has been cancelled
CI / Deploy MCP (push) Has been cancelled
This commit was merged in pull request #178.
This commit is contained in:
@@ -459,32 +459,57 @@ impl PipelineOrchestrator {
|
|||||||
|
|
||||||
let ctx = crate::ingest::IngestContext::from_config(&self.config, target_id);
|
let ctx = crate::ingest::IngestContext::from_config(&self.config, target_id);
|
||||||
let ingest_set = crate::ingest::ingest_all(target, &ctx)?;
|
let ingest_set = crate::ingest::ingest_all(target, &ctx)?;
|
||||||
// The PLC source: a dedicated PlcProject artifact, else a code artifact
|
// Every PLC-source artifact on the target: dedicated PLC projects plus any
|
||||||
// (git repo / source archive) holding PLCopen XML / ST exports.
|
// code artifacts (git repo / source archive) holding PLCopen XML / ST
|
||||||
let Some(artifact) = target
|
// exports. A target can carry several (e.g. one POU export per file).
|
||||||
.first_of(ArtifactKind::PlcProject)
|
let sources: Vec<&Artifact> = target
|
||||||
.or_else(|| target.code_artifact())
|
.artifacts
|
||||||
else {
|
.iter()
|
||||||
|
.filter(|a| {
|
||||||
|
matches!(
|
||||||
|
a.kind,
|
||||||
|
ArtifactKind::PlcProject | ArtifactKind::GitRepo | ArtifactKind::SourceArchive
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
if sources.is_empty() {
|
||||||
tracing::warn!(target_id, "PLC scan: no PLC source artifact");
|
tracing::warn!(target_id, "PLC scan: no PLC source artifact");
|
||||||
return Ok(0);
|
return Ok(0);
|
||||||
};
|
}
|
||||||
let Some(path) = ingest_set
|
|
||||||
.get(&artifact.id)
|
|
||||||
.and_then(|ia| ia.working_path.clone())
|
|
||||||
else {
|
|
||||||
tracing::warn!(target_id, "PLC scan: no ingested PLC source path");
|
|
||||||
return Ok(0);
|
|
||||||
};
|
|
||||||
|
|
||||||
let findings = crate::pipeline::plc::analyze_tree(&path, target_id);
|
let mut all_findings = Vec::new();
|
||||||
|
let mut all_sbom: Vec<SbomEntry> = Vec::new();
|
||||||
|
let mut sbom_seen = std::collections::BTreeSet::new();
|
||||||
|
for a in &sources {
|
||||||
|
let Some(path) = ingest_set.get(&a.id).and_then(|ia| ia.working_path.clone()) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
all_findings.extend(crate::pipeline::plc::analyze_tree(&path, target_id));
|
||||||
|
// Control-application SBOM: CODESYS libraries + runtime from a
|
||||||
|
// `.projectarchive` (uploaded, or committed in the working tree).
|
||||||
|
let archive = a
|
||||||
|
.stored_path
|
||||||
|
.clone()
|
||||||
|
.unwrap_or_else(|| a.source_ref.clone());
|
||||||
|
for e in crate::pipeline::plc::sbom::collect_sbom(
|
||||||
|
std::path::Path::new(&archive),
|
||||||
|
&path,
|
||||||
|
target_id,
|
||||||
|
) {
|
||||||
|
if sbom_seen.insert((e.name.clone(), e.version.clone())) {
|
||||||
|
all_sbom.push(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
tracing::info!(
|
tracing::info!(
|
||||||
target_id,
|
target_id,
|
||||||
found = findings.len(),
|
artifacts = sources.len(),
|
||||||
|
found = all_findings.len(),
|
||||||
"PLC control-logic analysis complete"
|
"PLC control-logic analysis complete"
|
||||||
);
|
);
|
||||||
|
|
||||||
let mut new_count = 0u32;
|
let mut new_count = 0u32;
|
||||||
for mut finding in findings {
|
for mut finding in all_findings {
|
||||||
finding.scan_run_id = Some(scan_run_id.to_string());
|
finding.scan_run_id = Some(scan_run_id.to_string());
|
||||||
if self
|
if self
|
||||||
.db
|
.db
|
||||||
@@ -498,23 +523,9 @@ impl PipelineOrchestrator {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Control-application dependency SBOM: the CODESYS libraries + runtime
|
if !all_sbom.is_empty() {
|
||||||
// bundled in a `.projectarchive`, matched against known CVEs. Sourced from
|
|
||||||
// the uploaded archive *and* any `.projectarchive` committed in the working
|
|
||||||
// tree (e.g. a git repo). Empty for a bare `.st`/`.xml` or a repo of only
|
|
||||||
// PLCopen XML exports (which carry no library manifest).
|
|
||||||
let archive = artifact
|
|
||||||
.stored_path
|
|
||||||
.clone()
|
|
||||||
.unwrap_or_else(|| artifact.source_ref.clone());
|
|
||||||
let sbom = crate::pipeline::plc::sbom::collect_sbom(
|
|
||||||
std::path::Path::new(&archive),
|
|
||||||
&path,
|
|
||||||
target_id,
|
|
||||||
);
|
|
||||||
if !sbom.is_empty() {
|
|
||||||
if let Err(e) = self
|
if let Err(e) = self
|
||||||
.persist_control_app_sbom(target_id, &target.name, sbom)
|
.persist_control_app_sbom(target_id, &target.name, all_sbom)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
tracing::warn!(target_id, error = %e, "control-app SBOM persist failed");
|
tracing::warn!(target_id, error = %e, "control-app SBOM persist failed");
|
||||||
|
|||||||
Reference in New Issue
Block a user