Skip to content

feat(helmExecute): generate SBOM on the publish path and build traceability - #5856

Open
fabienfritz wants to merge 1 commit into
masterfrom
ffritz/helmBuild-add-sbom
Open

feat(helmExecute): generate SBOM on the publish path and build traceability#5856
fabienfritz wants to merge 1 commit into
masterfrom
ffritz/helmBuild-add-sbom

Conversation

@fabienfritz

@fabienfritz fabienfritz commented Jul 29, 2026

Copy link
Copy Markdown
Contributor

Add CycloneDX SBOM Generation and Build Traceability to helmExecute Step

Description

This PR adds CycloneDX 1.4 SBOM generation to the helmExecute step for SLC-41 compliance, and populates buildSettingsInfo in the common pipeline environment for SLC-29 build traceability.

SBOM Generation (SLC-41)

When createBOM: true is set and the chart is published, helmExecute now emits:

  • bom-helm.xml: A chart-level SBOM (built with cyclonedx-go) whose root component carries a pkg:helm PURL. Sub-chart dependency versions are resolved using Chart.lock (winning over Chart.yaml ranges), and referenced images are included as container components.
  • bom-docker-<N>.xml: Per-image SBOMs produced by Syft. A clean, registry-free pkg:docker PURL is injected into the root component to satisfy CycloneDX validation (workaround for anchore/syft#1408).

Container images are discovered via helm template and fall back to the containerImageNameTags CPE list when templating yields no images. SBOM generation is best-effort — failures are logged but never fail the step.

Build Traceability (SLC-29)

buildSettingsInfo is now populated into commonPipelineEnvironment.custom.buildSettingsInfo, recording helm build flags for compliance processes.

New Parameters

Parameter Description
createBOM Enables CycloneDX SBOM generation
syftDownloadUrl Download URL for the Syft binary
containerImageNameTags Fallback image list from upstream kanikoExecute
buildSettingsInfo Build settings traceability info

SBOM Reports Output

SBOM files matching **/bom-*.xml are now registered as sbom reports and can be persisted to GCS.

Tests

  • Unit tests covering image discovery, chart/container SBOM assembly, PURL injection, and buildSettingsInfo population
  • Integration test (TestHelmIntegrationPublishWithSBOM) that publishes to a WebDAV sink and validates the generated bom-helm.xml against CycloneDX 1.4 schema

Checklist

  • Tests

  • Documentation

  • Inner source library needs updating

  • 🔄 Regenerate and Update Summary

PR Bot Information

Version: 1.29.6

  • Correlation ID: 76bc11a0-8b74-11f1-93c3-d9c03e937050
  • Event Trigger: issue_comment.edited

@fabienfritz

Copy link
Copy Markdown
Contributor Author

/it-go

1 similar comment
@fabienfritz

Copy link
Copy Markdown
Contributor Author

/it-go

…bility

Add CycloneDX 1.4 SBOM generation to the helmExecute step for SLC-41
compliance. When createBOM is enabled and the chart is published,
helmExecute now emits:
- bom-helm.xml: a chart-level SBOM (hand-built with cyclonedx-go) whose
  root component carries a pkg:helm PURL, with sub-chart dependencies
  (Chart.lock resolved versions winning over Chart.yaml ranges) and
  referenced images as components.
- bom-docker-<N>.xml: a per-image SBOM produced by Syft, with a clean
  registry-free docker root PURL injected to satisfy CycloneDX
  validation (works around anchore/syft#1408).

Container images are discovered via `helm template`, falling back to the
containerImageNameTags CPE list when templating yields nothing. All SBOM
generation is best-effort: a failure is logged but never fails the step.

Also populate custom/buildSettingsInfo for SLC-29 build traceability, and
expose the outputs through the reports framework (**/bom-*.xml, type:
sbom).

Metadata: add createBOM, syftDownloadUrl, containerImageNameTags and
buildSettingsInfo inputs and regenerate the step.

Tests:
- unit coverage for image discovery, chart/container SBOM assembly, PURL
  injection and buildSettingsInfo.
- a full publish+SBOM integration test (TestHelmIntegration) that
  publishes to a webdav sink and validates the generated bom-helm.xml
  against CycloneDX 1.4.
@fabienfritz
fabienfritz force-pushed the ffritz/helmBuild-add-sbom branch from e95849e to ec39ed3 Compare July 30, 2026 07:45
@fabienfritz

Copy link
Copy Markdown
Contributor Author

/it-go

@fabienfritz
fabienfritz marked this pull request as ready for review July 30, 2026 07:58
@fabienfritz
fabienfritz requested review from a team as code owners July 30, 2026 07:58

@fskhiri fskhiri left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No blocker, to findings worth look at it. Let me know what do you think ?

Comment thread cmd/helmExecute.go
// component of every bom-docker-*.xml produced by Syft. Syft does not emit a
// PURL for the parent component (anchore/syft#1408), which makes the BOM fail
// CycloneDX validation. Best-effort: individual failures are logged, not fatal.
func injectContainerBOMPurls() error {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

injectContainerBOMPurls duplicates kaniko's canonical PURL-injection block
cmd/helmExecute.go adds injectContainerBOMPurls() (diff lines ~183–217). This is a near-verbatim copy of the PURL-injection half of createDockerBuildArtifactMetadata in cmd/kanikoExecute.go:455–528

Duplicating an existing canonical helper. The right move is to extract the shared logic once and call it from both steps.

Comment thread pkg/kubernetes/helm.go
return nil
}

// RunHelmTemplate renders the chart locally (`helm template`) and returns the

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RunHelmTemplate swaps global stdout on the executor — a new, race-adjacent pattern

Why this is a smell

The problem is that h.utils is shared state — it's one object the whole struct uses. This method mutates it,
does its work, then restores it. Think of it like grabbing the office printer, secretly rerouting
everyone's print jobs to your desk for 10 seconds, then putting it back. It works if you're the only person
printing at that moment. Two concrete risks:

  1. Concurrency. If two things ever use the same executor at the same time, one call rebinding stdout would
    hijack the other's output. Today helmExecute runs single-threaded so it's fine — but it's a landmine for
    anyone who later parallelizes.

  2. The restore can be skipped. defer protects against most cases, but the pattern is fragile — the
    correctness of every other method now quietly depends on this one method having cleaned up after itself. The
    fact that the tests had to add a dedicated check "stdout is restored after the call" is the tell: you only
    write a test asserting you cleaned up your mess if the design makes messes possible.

The cleaner alternative

Instead of rewiring shared state, capture at the boundary — ask the command to give you its output directly
into a local variable, without ever touching h.stdout:

// conceptually: run helm and hand ME the output, don't touch the shared writer
output, err := h.utils.RunExecutableWithOutput("helm", helmParams...)

Now nothing shared is mutated, there's no restore step, no defer, no race, and no need for a test proving
you undid your change. The output capture is local to this one call.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants