Skip to content

Commit 740a63f

Browse files
leodidoona-agent
andcommitted
feat: store SBOM files outside tar.gz artifacts
Move SBOM files from inside the tar.gz archive to separate files alongside the artifact. This follows the same pattern as provenance bundles and maintains artifact determinism. Changes: - SBOM files now written as <artifact>.sbom.cdx.json, .sbom.spdx.json, .sbom.json - S3 cache uploads/downloads SBOM files separately (both SLSA and non-SLSA paths) - AccessSBOMInCachedArchive checks for separate files first, falls back to tar.gz - Updated integration tests to verify separate SBOM files Co-authored-by: Ona <[email protected]>
1 parent 1aa988a commit 740a63f

File tree

5 files changed

+522
-218
lines changed

5 files changed

+522
-218
lines changed

pkg/leeway/build.go

Lines changed: 16 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1215,13 +1215,6 @@ func (p *Package) build(buildctx *buildContext) (err error) {
12151215
}
12161216
}
12171217

1218-
// Generate SBOM if enabled (before packaging)
1219-
if p.C.W.SBOM.Enabled {
1220-
if err := writeSBOM(buildctx, p, builddir); err != nil {
1221-
return err
1222-
}
1223-
}
1224-
12251218
// Handle test coverage if available (before packaging - needs _deps)
12261219
if bld.TestCoverage != nil {
12271220
coverage, funcsWithoutTest, funcsWithTest, err := bld.TestCoverage()
@@ -1256,6 +1249,14 @@ func (p *Package) build(buildctx *buildContext) (err error) {
12561249
}
12571250
}
12581251

1252+
// Generate SBOM if enabled (after packaging - written alongside artifact)
1253+
// SBOM files are stored outside the tar.gz to maintain artifact determinism.
1254+
if p.C.W.SBOM.Enabled {
1255+
if err := writeSBOMToCache(buildctx, p, builddir); err != nil {
1256+
return err
1257+
}
1258+
}
1259+
12591260
// Register newly built package
12601261
return buildctx.RegisterNewlyBuilt(p)
12611262
}
@@ -1813,12 +1814,8 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac
18131814
}
18141815
packageJSONFiles = append(packageJSONFiles, pkgYarnLock)
18151816
// Note: provenance bundle is written alongside the artifact as <artifact>.provenance.jsonl
1816-
// (outside tar.gz) to maintain artifact determinism
1817-
if p.C.W.SBOM.Enabled {
1818-
packageJSONFiles = append(packageJSONFiles, sbomBaseFilename+sbomCycloneDXFileExtension)
1819-
packageJSONFiles = append(packageJSONFiles, sbomBaseFilename+sbomSPDXFileExtension)
1820-
packageJSONFiles = append(packageJSONFiles, sbomBaseFilename+sbomSyftFileExtension)
1821-
}
1817+
// (outside tar.gz) to maintain artifact determinism.
1818+
// Note: SBOM files are also written alongside the artifact (outside tar.gz) for determinism.
18221819
packageJSON["files"] = packageJSONFiles
18231820

18241821
modifiedPackageJSON = true
@@ -1912,7 +1909,7 @@ func (p *Package) buildYarn(buildctx *buildContext, wd, result string) (bld *pac
19121909
pkgCommands = append(pkgCommands, [][]string{
19131910
{"sh", "-c", fmt.Sprintf("yarn generate-lock-entry --resolved file://./%s > _mirror/content_yarn.lock", dst)},
19141911
{"sh", "-c", "cat yarn.lock >> _mirror/content_yarn.lock"},
1915-
{"sh", "-c", fmt.Sprintf("find . -name '%s*.json' -exec cp {} ./_mirror/ \\;", sbomBaseFilename)},
1912+
// Note: SBOM files are written alongside the artifact (outside tar.gz) for determinism
19161913
{"yarn", "pack", "--filename", dst},
19171914
BuildTarCommand(
19181915
WithOutputFile(result),
@@ -2483,12 +2480,8 @@ func (p *Package) buildDocker(buildctx *buildContext, wd, result string) (res *p
24832480
// Add a diagnostic command to generate a manifest of what we're packaging
24842481
pkgcmds = append(pkgcmds, []string{"sh", "-c", fmt.Sprintf("find %s -type f | sort > %s/files-manifest.txt", containerDir, containerDir)})
24852482

2483+
// Note: SBOM files are written alongside the artifact (outside tar.gz) for determinism
24862484
sourcePaths := []string{"."}
2487-
if p.C.W.SBOM.Enabled {
2488-
sourcePaths = append(sourcePaths, fmt.Sprintf("../%s", sbomBaseFilename+sbomCycloneDXFileExtension))
2489-
sourcePaths = append(sourcePaths, fmt.Sprintf("../%s", sbomBaseFilename+sbomSPDXFileExtension))
2490-
sourcePaths = append(sourcePaths, fmt.Sprintf("../%s", sbomBaseFilename+sbomSyftFileExtension))
2491-
}
24922485

24932486
// Create final tar with container files and metadata
24942487
pkgcmds = append(pkgcmds, BuildTarCommand(
@@ -2531,14 +2524,9 @@ func (p *Package) buildDocker(buildctx *buildContext, wd, result string) (res *p
25312524
pkgCommands = append(pkgCommands, []string{"sh", "-c", fmt.Sprintf("echo %s | base64 -d > %s", encodedMetadata, dockerMetadataFile)})
25322525

25332526
// Prepare for packaging
2527+
// Note: SBOM files are written alongside the artifact (outside tar.gz) for determinism
25342528
sourcePaths := []string{fmt.Sprintf("./%s", dockerImageNamesFiles), fmt.Sprintf("./%s", dockerMetadataFile)}
25352529

2536-
if p.C.W.SBOM.Enabled {
2537-
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", sbomBaseFilename+sbomCycloneDXFileExtension))
2538-
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", sbomBaseFilename+sbomSPDXFileExtension))
2539-
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", sbomBaseFilename+sbomSyftFileExtension))
2540-
}
2541-
25422530
archiveCmd := BuildTarCommand(
25432531
WithOutputFile(result),
25442532
WithSourcePaths(sourcePaths...),
@@ -2583,19 +2571,12 @@ func (p *Package) buildDocker(buildctx *buildContext, wd, result string) (res *p
25832571
}
25842572

25852573
// Package everything into final tar.gz
2574+
// Note: SBOM files are written alongside the artifact (outside tar.gz) for determinism
25862575
sourcePaths := []string{"./image.tar", fmt.Sprintf("./%s", dockerImageNamesFiles), "./docker-export-metadata.json"}
25872576
if len(cfg.Metadata) > 0 {
25882577
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", dockerMetadataFile))
25892578
}
25902579

2591-
if p.C.W.SBOM.Enabled {
2592-
sourcePaths = append(sourcePaths,
2593-
fmt.Sprintf("./%s", sbomBaseFilename+sbomCycloneDXFileExtension),
2594-
fmt.Sprintf("./%s", sbomBaseFilename+sbomSPDXFileExtension),
2595-
fmt.Sprintf("./%s", sbomBaseFilename+sbomSyftFileExtension),
2596-
)
2597-
}
2598-
25992580
archiveCmd := BuildTarCommand(
26002581
WithOutputFile(result),
26012582
WithSourcePaths(sourcePaths...),
@@ -2970,19 +2951,11 @@ func (p *Package) buildGeneric(buildctx *buildContext, wd, result string) (res *
29702951
}
29712952

29722953
// Use buildTarCommand directly which will handle compression internally
2954+
// Note: SBOM files are written alongside the artifact (outside tar.gz) for determinism
29732955
var tarCmd []string
2974-
if p.C.W.SBOM.Enabled {
2975-
var sourcePaths []string
2976-
2977-
if p.C.W.SBOM.Enabled {
2978-
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", sbomBaseFilename+sbomCycloneDXFileExtension))
2979-
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", sbomBaseFilename+sbomSPDXFileExtension))
2980-
sourcePaths = append(sourcePaths, fmt.Sprintf("./%s", sbomBaseFilename+sbomSyftFileExtension))
2981-
}
2982-
2956+
if len(commands) > 0 {
29832957
tarCmd = BuildTarCommand(
29842958
WithOutputFile(result),
2985-
WithSourcePaths(sourcePaths...),
29862959
WithCompression(!buildctx.DontCompress),
29872960
WithMtime(mtime),
29882961
)
@@ -2994,15 +2967,6 @@ func (p *Package) buildGeneric(buildctx *buildContext, wd, result string) (res *
29942967
}, nil
29952968
}
29962969

2997-
if len(commands) > 0 {
2998-
return &packageBuild{
2999-
Commands: map[PackageBuildPhase][][]string{
3000-
PackageBuildPhaseBuild: commands,
3001-
PackageBuildPhasePackage: {tarCmd},
3002-
},
3003-
}, nil
3004-
}
3005-
30062970
// Truly empty package with no dependencies
30072971
tarCmd = BuildTarCommand(
30082972
WithFilesFrom("/dev/null"),

0 commit comments

Comments
 (0)