Skip to content

Commit 2598c82

Browse files
committed
feat(build): attested sbom
1 parent 64c06ca commit 2598c82

2 files changed

Lines changed: 183 additions & 0 deletions

File tree

.github/workflows/nightly.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,3 +42,51 @@ jobs:
4242
TAGS: ghcr.io/edera-dev/${{ matrix.component }}:nightly
4343
COSIGN_EXPERIMENTAL: "true"
4444
run: cosign sign --yes "${TAGS}@${DIGEST}"
45+
46+
- name: Build `build` stage for source-commit capture
47+
uses: docker/build-push-action@15560696de535e4014efeff63c48f16952e52dd1 # v6.2.0
48+
with:
49+
file: ./Dockerfile.${{ matrix.component }}
50+
target: build
51+
platforms: linux/amd64
52+
load: true
53+
tags: sbom-scan-${{ matrix.component }}:latest
54+
- name: Generate and attest CycloneDX SBOM
55+
env:
56+
DIGEST: ${{ steps.push-step.outputs.digest }}
57+
TAGS: ghcr.io/edera-dev/${{ matrix.component }}:nightly
58+
COMPONENT: ${{ matrix.component }}
59+
COSIGN_EXPERIMENTAL: "true"
60+
run: |
61+
set -euo pipefail
62+
scan="sbom-scan-${COMPONENT}:latest"
63+
64+
sha() { docker run --rm "${scan}" git -C "$1" rev-parse HEAD; }
65+
desc() { docker run --rm "${scan}" git -C "$1" describe --tags --always; }
66+
case "${COMPONENT}" in
67+
squashfs-tools)
68+
SQUASHFS_TOOLS_COMMIT="$(sha /usr/src/squashfs-tools)"
69+
SQUASHFS_TOOLS_VERSION="$(desc /usr/src/squashfs-tools)"
70+
ZLIB_NG_COMMIT="$(sha /usr/src/zlib-ng)"
71+
ZLIB_NG_VERSION="$(desc /usr/src/zlib-ng)"
72+
export SQUASHFS_TOOLS_COMMIT SQUASHFS_TOOLS_VERSION \
73+
ZLIB_NG_COMMIT ZLIB_NG_VERSION
74+
;;
75+
mkfs)
76+
E2FSPROGS_COMMIT="$(sha /usr/src/e2fsprogs)"
77+
E2FSPROGS_VERSION="$(desc /usr/src/e2fsprogs)"
78+
export E2FSPROGS_COMMIT E2FSPROGS_VERSION
79+
;;
80+
esac
81+
82+
python3 generate-sbom.py
83+
84+
jq -e '.bomFormat == "CycloneDX" and ((.components // []) | length > 0)' \
85+
"${COMPONENT}.cdx.json" >/dev/null \
86+
|| { echo "::error::SBOM for ${COMPONENT} is empty or invalid"; exit 1; }
87+
jq '{component: env.COMPONENT, components: (.components | length)}' \
88+
"${COMPONENT}.cdx.json"
89+
90+
cosign attest --yes --type cyclonedx \
91+
--predicate "${COMPONENT}.cdx.json" \
92+
"${TAGS}@${DIGEST}"

generate-sbom.py

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
#!/usr/bin/env python3
2+
"""Generate a curated CycloneDX SBOM for a published linux-utils-oci image.
3+
4+
Reads from the environment (set by the SBOM workflow step, resolved from the
5+
build-stage image):
6+
COMPONENT squashfs-tools | mkfs
7+
SQUASHFS_TOOLS_COMMIT resolved commit of plougher/squashfs-tools
8+
SQUASHFS_TOOLS_VERSION git-describe of same
9+
ZLIB_NG_COMMIT resolved commit of zlib-ng/zlib-ng
10+
ZLIB_NG_VERSION git-describe of same
11+
E2FSPROGS_COMMIT resolved commit of e2fsprogs
12+
E2FSPROGS_VERSION git-describe of same (the build-discovered release tag)
13+
14+
Writes <COMPONENT>.cdx.json (CycloneDX 1.6) in the current directory.
15+
"""
16+
import json
17+
import os
18+
import sys
19+
20+
21+
def github_source(name, gh_path, commit, version, ctype="application"):
22+
"""A GitHub-hosted source component, pinned to commit in its purl."""
23+
purl = "pkg:github/%s" % gh_path
24+
if commit:
25+
purl = "%s@%s" % (purl, commit)
26+
component = {
27+
"bom-ref": purl,
28+
"type": ctype,
29+
"name": name,
30+
"purl": purl,
31+
"externalReferences": [
32+
{"type": "vcs", "url": "https://github.com/%s.git" % gh_path}
33+
],
34+
}
35+
if version:
36+
component["version"] = version
37+
return component
38+
39+
40+
def e2fsprogs_source(commit, version):
41+
"""e2fsprogs lives on git.kernel.org (not GitHub), so it is a pkg:generic
42+
component with the commit carried as a property (purls for generic sources
43+
have no standard commit qualifier)."""
44+
purl = "pkg:generic/e2fsprogs"
45+
if version:
46+
purl = "%s@%s" % (purl, version)
47+
component = {
48+
"bom-ref": purl,
49+
"type": "application",
50+
"name": "e2fsprogs",
51+
"purl": purl,
52+
"externalReferences": [
53+
{
54+
"type": "vcs",
55+
"url": "https://git.kernel.org/pub/scm/fs/ext2/e2fsprogs.git",
56+
}
57+
],
58+
}
59+
if version:
60+
component["version"] = version
61+
if commit:
62+
component["properties"] = [
63+
{"name": "dev.edera.source.commit", "value": commit}
64+
]
65+
return component
66+
67+
68+
def build_sources(comp):
69+
if comp == "squashfs-tools":
70+
return [
71+
github_source(
72+
"squashfs-tools",
73+
"plougher/squashfs-tools",
74+
os.environ.get("SQUASHFS_TOOLS_COMMIT", ""),
75+
os.environ.get("SQUASHFS_TOOLS_VERSION", ""),
76+
),
77+
# zlib-ng is built static (zlib-compat) and linked into the binaries.
78+
github_source(
79+
"zlib-ng",
80+
"zlib-ng/zlib-ng",
81+
os.environ.get("ZLIB_NG_COMMIT", ""),
82+
os.environ.get("ZLIB_NG_VERSION", ""),
83+
ctype="library",
84+
),
85+
]
86+
if comp == "mkfs":
87+
return [
88+
e2fsprogs_source(
89+
os.environ.get("E2FSPROGS_COMMIT", ""),
90+
os.environ.get("E2FSPROGS_VERSION", ""),
91+
)
92+
]
93+
print("ERROR: unknown COMPONENT %r" % comp, file=sys.stderr)
94+
sys.exit(1)
95+
96+
97+
def main():
98+
comp = os.environ["COMPONENT"]
99+
sources = build_sources(comp)
100+
101+
image_ref = "%s@nightly" % comp
102+
document = {
103+
"bomFormat": "CycloneDX",
104+
"specVersion": "1.6",
105+
"version": 1,
106+
"metadata": {
107+
"component": {
108+
"bom-ref": image_ref,
109+
"type": "container",
110+
"name": comp,
111+
}
112+
},
113+
"components": sources,
114+
"dependencies": [
115+
{
116+
"ref": image_ref,
117+
"dependsOn": [c["bom-ref"] for c in sources if c.get("bom-ref")],
118+
}
119+
],
120+
}
121+
122+
with open("%s.cdx.json" % comp, "w") as out:
123+
json.dump(document, out, indent=2)
124+
out.write("\n")
125+
126+
print(
127+
json.dumps(
128+
{"component": comp, "components": len(sources)},
129+
indent=2,
130+
)
131+
)
132+
133+
134+
if __name__ == "__main__":
135+
main()

0 commit comments

Comments
 (0)