Skip to content

Commit

Permalink
Merge pull request #962 from imjasonh/check-sbom
Browse files Browse the repository at this point in the history
fix and continuously validate SBOMs
  • Loading branch information
imjasonh authored Nov 17, 2023
2 parents 4f9a4c6 + 0f044ab commit 7fac2ad
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 54 deletions.
86 changes: 45 additions & 41 deletions .github/workflows/build-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ on:
workflow_dispatch:

jobs:
# Build a single-arch nginx image for each arch.
build-nginx-on-all-arches:
name: build-nginx-all-arches
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
arch: [x86_64, "386", armv7, aarch64, riscv64, s390x, ppc64le]

Expand All @@ -20,16 +22,47 @@ jobs:
go-version-file: 'go.mod'
- name: Setup QEMU
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0
- run: |
make apko
./apko build ./examples/nginx.yaml nginx:build /tmp/nginx-${{ matrix.arch }}.tar --debug --arch ${{ matrix.arch }}
- name: build
- name: Check SBOM Conformance
run: |
set -euxo pipefail
if ! ls *.spdx.json; then
echo "no SBOMs found!"
exit 1
fi
for f in *.spdx.json; do
echo ::group::sbom.json
cat $f
echo ::endgroup::
docker run --rm -v $(pwd)/$f:/sbom.json cgr.dev/chainguard/ntia-conformance-checker -v --file /sbom.json
done
# Build a multi-arch nginx image for all archs.
build-nginx-multiarch:
name: build-nginx-multiarch
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v2.1.5
with:
go-version-file: 'go.mod'
- run: |
make apko
./apko version
./apko build ./examples/nginx.yaml nginx:build /tmp/nginx.tar --debug
- name: build image
timeout-minutes: 15
- name: Check SBOM Conformance
run: |
./apko build ./examples/nginx.yaml nginx:build /tmp/nginx-${{ matrix.arch }}.tar --debug --arch ${{ matrix.arch }}
set -euxo pipefail
for f in *.spdx.json; do
echo ::group::sbom.json
cat $f
echo ::endgroup::
docker run --rm -v $(pwd)/$f:/sbom.json cgr.dev/chainguard/ntia-conformance-checker -v --file /sbom.json
done
build-all-examples-one-arch:
name: build-all-examples-amd64
Expand All @@ -43,16 +76,9 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v2.1.5
with:
go-version: "1.21"
check-latest: true
- name: build
run: |
go-version-file: 'go.mod'
- run: |
make apko
./apko version
- name: build images
timeout-minutes: 15
run: |
for cfg in $(find ./examples/ -name '*.yaml'); do
name=$(basename ${cfg} .yaml)
./apko build ${cfg} ${name}:build /tmp/${name}.tar --debug --arch amd64
Expand All @@ -66,26 +92,16 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v2.1.5
with:
go-version: "1.21"
check-latest: true
- name: Setup QEMU
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0

- name: build
run: |
make apko
./apko version
go-version-file: 'go.mod'
- uses: chainguard-dev/actions/setup-registry@main
with:
port: 5000

- name: build image (w/ source date epoch)
shell: bash
timeout-minutes: 15
env:
SOURCE_DATE_EPOCH: "0"
run: |
make apko
FIRST=$(./apko publish ./examples/alpine-base.yaml localhost:5000/alpine 2> /dev/null)
for idx in {2..10}
Expand All @@ -108,24 +124,14 @@ jobs:
- uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
- uses: actions/setup-go@93397bea11091df50f3d7e59dc26a7711a8bcfbe # v2.1.5
with:
go-version: "1.21"
check-latest: true
- name: Setup QEMU
uses: docker/setup-qemu-action@68827325e0b33c7199eb31dd4e31fbe9023e06e3 # v3.0.0

- name: build
run: |
make apko
./apko version
go-version-file: 'go.mod'
- uses: chainguard-dev/actions/setup-registry@main
with:
port: 5000

- name: build image (w/ build date epoch)
shell: bash
timeout-minutes: 15
run: |
make apko
# Without SOURCE_DATE_EPOCH set, the timestamp of the image will be computed to be
# the maximum build date of the resolved APKs.
FIRST=$(./apko publish ./examples/alpine-base.yaml localhost:5000/alpine 2> /dev/null)
Expand Down Expand Up @@ -155,10 +161,8 @@ jobs:
- uses: chainguard-dev/actions/setup-registry@main
with:
port: 5000
- name: build
run: |
- run: |
make apko
./apko version
# Build image with annotations.
ref=$(./apko publish ./examples/nginx.yaml localhost:5000/nginx)
Expand Down
5 changes: 4 additions & 1 deletion internal/cli/build.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,11 @@ Along the image, apko will generate CycloneDX and SPDX SBOMs (software
bill of materials) describing the image contents.
`,
Example: ` apko build <config.yaml> <tag> <output.tar|oci-layout-dir/>`,
Args: cobra.ExactArgs(3),
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) != 3 {
return fmt.Errorf("requires 3 arg: 1 config file, a tag for the image, and an output path")
}

if len(logPolicy) == 0 {
if quietEnabled {
logPolicy = []string{"builtin:discard"}
Expand Down
6 changes: 3 additions & 3 deletions internal/cli/publish_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func TestPublish(t *testing.T) {

// This test will fail if we ever make a change in apko that changes the SBOM.
// Sometimes, this is intentional, and we need to change this and bump the version.
swant := "sha256:8dcaffc88372c0b3bf08a04d5c7bb70e59fe22c7ff781f868f3bb8ea3d093eda"
swant := "sha256:2cbdb42a7b4160cdcd44836a583fa23985532e1641f026365f653006545ad90c"
require.Equal(t, swant, got)

im, err := idx.IndexManifest()
Expand All @@ -113,8 +113,8 @@ func TestPublish(t *testing.T) {
// We also want to check the children SBOMs because the index SBOM does not have
// references to the children SBOMs, just the children!
wantBoms := []string{
"sha256:24a4f1a47dd353ca8e33b0c6bad00b7efc8cabeb27338e3288c2290fd8aaf389",
"sha256:db34ca4a2ac9a03f037edbe0e208fb546e9ccd602d918bf10191ab6056ef8413",
"sha256:a6acf3531effec2dd296834096fccff905d73f6838d9f680419c9bfbedad42f7",
"sha256:91097a5a791914cf2456e540671d47d369ae980c5376844ae978e56c15e8957c",
}

for i, m := range im.Manifests {
Expand Down
31 changes: 23 additions & 8 deletions pkg/sbom/generator/spdx/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -115,9 +115,7 @@ func (sx *SPDX) Generate(opts *options.Options, path string) error {

if opts.ImageInfo.VCSUrl != "" {
if opts.ImageInfo.ImageDigest != "" {
addSourcePackage(opts.ImageInfo.VCSUrl, doc, imagePackage)
} else {
addSourcePackage(opts.ImageInfo.VCSUrl, doc, layerPackage)
addSourcePackage(opts.ImageInfo.VCSUrl, doc, imagePackage, opts)
}
}

Expand Down Expand Up @@ -221,8 +219,6 @@ func (sx *SPDX) ProcessInternalApkSBOM(opts *options.Options, doc *Document, p *
return nil
}

// TODO: Logf("composing packages from %s into image SBOM", path)

internalDoc, err := sx.ParseInternalSBOM(opts, path)
if err != nil {
// TODO: Log error parsing apk SBOM
Expand Down Expand Up @@ -302,6 +298,8 @@ func copySBOMElements(sourceDoc, targetDoc *Document, todo map[string]struct{})

for _, f := range sourceDoc.Files {
if _, ok := todo[f.ID]; ok {
f.Name = strings.TrimPrefix(f.Name, "/") // Strip leading slashes, which SPDX doesn't like.

targetDoc.Files = append(targetDoc.Files, f)
done[f.ID] = struct{}{}
}
Expand Down Expand Up @@ -366,6 +364,8 @@ func (sx *SPDX) imagePackage(opts *options.Options) (p *Package) {
"SPDXRef-Package-%s", opts.ImageInfo.ImageDigest,
)),
Name: opts.ImageInfo.ImageDigest,
Version: opts.ImageInfo.ImageDigest,
Supplier: "Organization: " + opts.OS.Name,
DownloadLocation: NOASSERTION,
PrimaryPurpose: "CONTAINER",
FilesAnalyzed: false,
Expand Down Expand Up @@ -397,6 +397,7 @@ func (sx *SPDX) apkPackage(opts *options.Options, pkg *apk.Package) Package {
)),
Name: pkg.Name,
Version: pkg.Version,
Supplier: "Organization: " + opts.OS.Name,
FilesAnalyzed: false,
LicenseConcluded: pkg.License,
Description: pkg.Description,
Expand Down Expand Up @@ -436,6 +437,7 @@ func (sx *SPDX) layerPackage(opts *options.Options) *Package {
Description: "apko operating system layer",
DownloadLocation: NOASSERTION,
Originator: "",
Supplier: "Organization: " + opts.OS.Name,
Checksums: []Checksum{},
ExternalRefs: []ExternalRef{
{
Expand Down Expand Up @@ -500,6 +502,7 @@ type Package struct {
Description string `json:"description,omitempty"`
DownloadLocation string `json:"downloadLocation,omitempty"`
Originator string `json:"originator,omitempty"`
Supplier string `json:"supplier,omitempty"`
SourceInfo string `json:"sourceInfo,omitempty"`
CopyrightText string `json:"copyrightText,omitempty"`
PrimaryPurpose string `json:"primaryPackagePurpose,omitempty"`
Expand Down Expand Up @@ -560,6 +563,8 @@ func (sx *SPDX) GenerateIndex(opts *options.Options, path string) error {
indexPackage := Package{
ID: "SPDXRef-Package-" + stringToIdentifier(opts.ImageInfo.IndexDigest.DeepCopy().String()),
Name: opts.ImageInfo.IndexDigest.DeepCopy().String(),
Version: opts.ImageInfo.IndexDigest.DeepCopy().String(),
Supplier: "Organization: " + opts.OS.Name,
FilesAnalyzed: false,
Description: "Multi-arch image index",
SourceInfo: "Generated at image build time by apko",
Expand Down Expand Up @@ -592,6 +597,8 @@ func (sx *SPDX) GenerateIndex(opts *options.Options, path string) error {
doc.Packages = append(doc.Packages, Package{
ID: imagePackageID,
Name: fmt.Sprintf("sha256:%s", info.Digest.DeepCopy().Hex),
Version: fmt.Sprintf("sha256:%s", info.Digest.DeepCopy().Hex),
Supplier: "Organization: " + opts.OS.Name,
FilesAnalyzed: false,
DownloadLocation: NOASSERTION,
PrimaryPurpose: "CONTAINER",
Expand Down Expand Up @@ -620,7 +627,9 @@ func (sx *SPDX) GenerateIndex(opts *options.Options, path string) error {
})
}

addSourcePackage(opts.ImageInfo.VCSUrl, doc, &indexPackage)
if opts.ImageInfo.VCSUrl != "" {
addSourcePackage(opts.ImageInfo.VCSUrl, doc, &indexPackage, opts)
}

if err := renderDoc(doc, path); err != nil {
return fmt.Errorf("rendering document: %w", err)
Expand All @@ -630,7 +639,7 @@ func (sx *SPDX) GenerateIndex(opts *options.Options, path string) error {
}

// addSourcePackage creates a package describing the source code
func addSourcePackage(vcsURL string, doc *Document, parent *Package) {
func addSourcePackage(vcsURL string, doc *Document, parent *Package, opts *options.Options) {
version := ""
checksums := []Checksum{}
packageName := vcsURL
Expand All @@ -648,16 +657,22 @@ func addSourcePackage(vcsURL string, doc *Document, parent *Package) {
packageName = strings.TrimPrefix(packageName, "git://")
packageName = strings.TrimPrefix(packageName, "https://")

downloadLocation := vcsURL
if vcsURL == "" {
downloadLocation = NOASSERTION
}

sourcePackage := Package{
ID: fmt.Sprintf("SPDXRef-Package-%s", stringToIdentifier(vcsURL)),
Name: packageName,
Version: version,
Supplier: "Organization: " + opts.OS.Name,
FilesAnalyzed: false,
HasFiles: []string{},
LicenseInfoFromFiles: []string{},
PrimaryPurpose: "SOURCE",
Description: "Image configuration source",
DownloadLocation: vcsURL,
DownloadLocation: downloadLocation,
Checksums: checksums,
ExternalRefs: []ExternalRef{},
}
Expand Down
6 changes: 5 additions & 1 deletion pkg/sbom/generator/spdx/spdx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,11 @@ func TestSourcePackage(t *testing.T) {
}

// Call the function
addSourcePackage(vcsURL, &doc, &imagePackage)
addSourcePackage(vcsURL, &doc, &imagePackage, &options.Options{
OS: options.OSInfo{
Name: "Testing",
},
})

// Verify the purl
require.Len(t, doc.Packages[0].ExternalRefs, 1)
Expand Down

0 comments on commit 7fac2ad

Please sign in to comment.