feat(plc): ingest CODESYS projects from a git repo (SAST + SBOM) (#171)
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 11m7s
CI / Deploy Dashboard (push) Successful in 7m0s
CI / Deploy Docs (push) Successful in 57s
CI / Deploy MCP (push) Successful in 1m48s
CI / Check (push) Has been skipped
CI / Detect Changes (push) Successful in 3s
CI / Deploy Agent (push) Successful in 11m7s
CI / Deploy Dashboard (push) Successful in 7m0s
CI / Deploy Docs (push) Successful in 57s
CI / Deploy MCP (push) Successful in 1m48s
This commit was merged in pull request #171.
This commit is contained in:
@@ -449,23 +449,24 @@ impl PipelineOrchestrator {
|
||||
// wizard-created targets, not just migrated ones.
|
||||
self.ensure_dast_target(target, &plan).await;
|
||||
|
||||
// PLC control-logic analysis for PLC/SPS targets (a PlcProject artifact).
|
||||
// A PLC/SPS device is a composite target: after the control-logic scan we
|
||||
// fall through so a reachable device (WebVisu / exposed services) still
|
||||
// gets DAST, rather than early-returning on the PLC scan alone.
|
||||
// PLC/SPS targets: the control-logic scan consumes the PLC source (an
|
||||
// uploaded PlcProject *or* a git repo / source archive of PLCopen XML / ST
|
||||
// exports), so it takes over the code artifact — we don't also run the
|
||||
// SAST pipeline over it. A PLC device is reachable, so DAST still runs
|
||||
// against a WebVisu / exposed endpoint when one is provisioned.
|
||||
let mut new_count = 0u32;
|
||||
if plan.has(ScanType::PlcControlLogic) {
|
||||
new_count += self.run_plc_scan(target, &target_id, scan_run_id).await?;
|
||||
self.update_phase(scan_run_id, "dast_scanning").await;
|
||||
self.maybe_trigger_dast(&target_id, scan_run_id).await;
|
||||
return Ok(new_count);
|
||||
}
|
||||
|
||||
match target.code_artifact() {
|
||||
Some(code) if code.kind == ArtifactKind::GitRepo => {
|
||||
let n = {
|
||||
let repo = RepoView::from_target(target, code);
|
||||
let n = self.run_pipeline(&repo, scan_run_id).await?;
|
||||
self.finalize_target(target, &repo, n).await?;
|
||||
n
|
||||
};
|
||||
let repo = RepoView::from_target(target, code);
|
||||
let n = self.run_pipeline(&repo, scan_run_id).await?;
|
||||
self.finalize_target(target, &repo, n).await?;
|
||||
new_count += n;
|
||||
}
|
||||
Some(_) => {
|
||||
@@ -475,10 +476,10 @@ impl PipelineOrchestrator {
|
||||
);
|
||||
}
|
||||
None => {
|
||||
// No code to scan (a PLC device or a migrated DAST target).
|
||||
// Firmware/mobile static scanners land in #128/#129; DAST for a
|
||||
// running URL works when a DastTarget row exists (provisioned above
|
||||
// from a LiveUrl, or from a migrated target).
|
||||
// No code to scan (a migrated DAST target). Firmware/mobile static
|
||||
// scanners land in #128/#129; DAST for a running URL works when a
|
||||
// DastTarget row exists (provisioned above from a LiveUrl, or from
|
||||
// a migrated target).
|
||||
tracing::info!(
|
||||
target_id = %target_id,
|
||||
"Unified pipeline: no code artifact; attempting DAST"
|
||||
@@ -503,15 +504,20 @@ impl PipelineOrchestrator {
|
||||
|
||||
let ctx = crate::ingest::IngestContext::from_config(&self.config, target_id);
|
||||
let ingest_set = crate::ingest::ingest_all(target, &ctx)?;
|
||||
let Some(artifact) = target.first_of(ArtifactKind::PlcProject) else {
|
||||
tracing::warn!(target_id, "PLC scan: no PLC project artifact");
|
||||
// The PLC source: a dedicated PlcProject artifact, else a code artifact
|
||||
// (git repo / source archive) holding PLCopen XML / ST exports.
|
||||
let Some(artifact) = target
|
||||
.first_of(ArtifactKind::PlcProject)
|
||||
.or_else(|| target.code_artifact())
|
||||
else {
|
||||
tracing::warn!(target_id, "PLC scan: no PLC source artifact");
|
||||
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 project path");
|
||||
tracing::warn!(target_id, "PLC scan: no ingested PLC source path");
|
||||
return Ok(0);
|
||||
};
|
||||
|
||||
@@ -538,14 +544,17 @@ impl PipelineOrchestrator {
|
||||
}
|
||||
|
||||
// Control-application dependency SBOM: the CODESYS libraries + runtime
|
||||
// bundled in a `.projectarchive`, matched against known CVEs. Best-effort
|
||||
// and empty for a bare `.st`/`.xml` (which carries no library manifest).
|
||||
// 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::projectarchive_sbom(
|
||||
let sbom = crate::pipeline::plc::sbom::collect_sbom(
|
||||
std::path::Path::new(&archive),
|
||||
&path,
|
||||
target_id,
|
||||
);
|
||||
if !sbom.is_empty() {
|
||||
|
||||
Reference in New Issue
Block a user