Skip to content

Commit b571c17

Browse files
authored
[CRAVEX] SCA Integrations: SBOM tool (#1826)
Signed-off-by: tdruez <[email protected]>
1 parent 7515cd2 commit b571c17

File tree

3 files changed

+376
-0
lines changed

3 files changed

+376
-0
lines changed
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
name: Generate SBOM with SBOM tool and load into ScanCode.io
2+
3+
# This workflow:
4+
# 1. Generates a CycloneDX SBOM for a container image using SBOM tool.
5+
# 2. Uploads the SBOM as a GitHub artifact for future inspection.
6+
# 3. Loads the SBOM into ScanCode.io for further analysis.
7+
# 4. Runs assertions to verify that the SBOM was properly processed in ScanCode.io.
8+
#
9+
# It runs on demand, and once a week (scheduled).
10+
11+
on:
12+
workflow_dispatch:
13+
schedule:
14+
# Run once a week (every 7 days) at 00:00 UTC on Sunday
15+
- cron: "0 0 * * 0"
16+
17+
permissions:
18+
contents: read
19+
20+
env:
21+
IMAGE_REFERENCE: "python:3.13.0-slim"
22+
23+
jobs:
24+
generate-and-load-sbom:
25+
runs-on: ubuntu-24.04
26+
steps:
27+
- name: Download SBOM tool
28+
run: |
29+
curl -Lo $RUNNER_TEMP/sbom-tool https://github.com/microsoft/sbom-tool/releases/latest/download/sbom-tool-linux-x64
30+
chmod +x $RUNNER_TEMP/sbom-tool
31+
32+
- name: Generate SBOM with SBOM tool
33+
run: |
34+
mkdir -p sbom-output
35+
$RUNNER_TEMP/sbom-tool generate \
36+
-di ${{ env.IMAGE_REFERENCE }} \
37+
-pn DockerImage \
38+
-pv 1.0.0 \
39+
-ps Company \
40+
-nsb https://sbom.company.com \
41+
-m sbom-output \
42+
-V Verbose
43+
44+
- name: Upload SBOM artifact
45+
uses: actions/upload-artifact@v4
46+
with:
47+
name: sbom-output
48+
path: sbom-output
49+
50+
- name: Import SBOM into ScanCode.io
51+
uses: aboutcode-org/scancode-action@main
52+
with:
53+
pipelines: "load_sbom"
54+
inputs-path: "sbom-output/_manifest/spdx_2.2/manifest.spdx.json"
55+
scancodeio-repo-branch: "main"
56+
57+
- name: Verify SBOM Analysis Results in ScanCode.io
58+
shell: bash
59+
run: |
60+
scanpipe shell --command "from scanpipe.models import DiscoveredPackage, DiscoveredDependency; package_manager = DiscoveredPackage.objects; assert package_manager.count() > 90; assert package_manager.vulnerable().count() == 0; assert DiscoveredDependency.objects.count() > 90"
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
{
2+
"files": [],
3+
"packages": [
4+
{
5+
"name": "alpine-baselayout-data",
6+
"SPDXID": "SPDXRef-Package-2FF1344E2849EBD04203DE6480D48AA9DF318FD79C2CF23DEBBF14C63DAD7AD9",
7+
"downloadLocation": "NOASSERTION",
8+
"filesAnalyzed": false,
9+
"licenseConcluded": "GPL-2.0-only",
10+
"licenseDeclared": "NOASSERTION",
11+
"copyrightText": "NOASSERTION",
12+
"versionInfo": "3.4.0-r0",
13+
"supplier": "Organization: Natanael Copa <[email protected]>"
14+
},
15+
{
16+
"name": "busybox",
17+
"SPDXID": "SPDXRef-Package-269AF94DD8BFFCFAD5BA8BBC6E82B9365798AE4AF85B49BC3A5FE4F31DDE9488",
18+
"downloadLocation": "NOASSERTION",
19+
"filesAnalyzed": false,
20+
"licenseConcluded": "GPL-2.0-only",
21+
"licenseDeclared": "NOASSERTION",
22+
"copyrightText": "NOASSERTION",
23+
"versionInfo": "1.35.0-r29",
24+
"supplier": "Organization: Sören Tempel <[email protected]>"
25+
},
26+
{
27+
"name": "alpine-baselayout",
28+
"SPDXID": "SPDXRef-Package-BC8A0138F4AB887ACBE81B0C5FF1077477DD1F054104C9D28244F3EE1318EF29",
29+
"downloadLocation": "NOASSERTION",
30+
"filesAnalyzed": false,
31+
"licenseConcluded": "GPL-2.0-only",
32+
"licenseDeclared": "NOASSERTION",
33+
"copyrightText": "NOASSERTION",
34+
"versionInfo": "3.4.0-r0",
35+
"supplier": "Organization: Natanael Copa <[email protected]>"
36+
},
37+
{
38+
"name": "ca-certificates-bundle",
39+
"SPDXID": "SPDXRef-Package-2471353CCE204889AD89EFCEC030096363B376775C5DFAFBDBEBD99597EEA969",
40+
"downloadLocation": "NOASSERTION",
41+
"filesAnalyzed": false,
42+
"licenseConcluded": "MPL-2.0 AND MIT",
43+
"licenseDeclared": "NOASSERTION",
44+
"copyrightText": "NOASSERTION",
45+
"versionInfo": "20220614-r2",
46+
"supplier": "Organization: Natanael Copa <[email protected]>"
47+
},
48+
{
49+
"name": "libssl3",
50+
"SPDXID": "SPDXRef-Package-E8FDE8662042A1A73B88F43634ECA413A98DD1B5BA43F46455B024C794272A3C",
51+
"downloadLocation": "NOASSERTION",
52+
"filesAnalyzed": false,
53+
"licenseConcluded": "Apache-2.0",
54+
"licenseDeclared": "NOASSERTION",
55+
"copyrightText": "NOASSERTION",
56+
"versionInfo": "3.0.7-r0",
57+
"supplier": "Organization: Ariadne Conill <[email protected]>"
58+
},
59+
{
60+
"name": "ssl_client",
61+
"SPDXID": "SPDXRef-Package-A03157DF01BCE5BF4A209E6AAF611D234E1D38DD10136D6F48A0E30D4704DA2E",
62+
"downloadLocation": "NOASSERTION",
63+
"filesAnalyzed": false,
64+
"licenseConcluded": "GPL-2.0-only",
65+
"licenseDeclared": "NOASSERTION",
66+
"copyrightText": "NOASSERTION",
67+
"versionInfo": "1.35.0-r29",
68+
"supplier": "Organization: Sören Tempel <[email protected]>"
69+
},
70+
{
71+
"name": "zlib",
72+
"SPDXID": "SPDXRef-Package-00888F6C5F68DF809EB4EBE041A412FFD51276AA402850EECDC1C4A17921C5D3",
73+
"downloadLocation": "NOASSERTION",
74+
"filesAnalyzed": false,
75+
"licenseConcluded": "Zlib",
76+
"licenseDeclared": "NOASSERTION",
77+
"copyrightText": "NOASSERTION",
78+
"versionInfo": "1.2.13-r0",
79+
"supplier": "Organization: Natanael Copa <[email protected]>"
80+
},
81+
{
82+
"name": "libcrypto3",
83+
"SPDXID": "SPDXRef-Package-342117E26CA6266072A57732213BAA801F51EB280EEF5A1CE4DBB9B3D8F5F5B0",
84+
"downloadLocation": "NOASSERTION",
85+
"filesAnalyzed": false,
86+
"licenseConcluded": "Apache-2.0",
87+
"licenseDeclared": "NOASSERTION",
88+
"copyrightText": "NOASSERTION",
89+
"versionInfo": "3.0.7-r0",
90+
"supplier": "Organization: Ariadne Conill <[email protected]>"
91+
},
92+
{
93+
"name": "busybox-binsh",
94+
"SPDXID": "SPDXRef-Package-01C3FA070AC7D05FE03422B385F566584B3AF940657764A6748FCD9AACEEB807",
95+
"downloadLocation": "NOASSERTION",
96+
"filesAnalyzed": false,
97+
"licenseConcluded": "GPL-2.0-only",
98+
"licenseDeclared": "NOASSERTION",
99+
"copyrightText": "NOASSERTION",
100+
"versionInfo": "1.35.0-r29",
101+
"supplier": "Organization: Sören Tempel <[email protected]>"
102+
},
103+
{
104+
"name": "musl-utils",
105+
"SPDXID": "SPDXRef-Package-E00DB534A00A22C8CE4CA11B1FC76E8BE0E9B819067295BA0575664FC5D97D9C",
106+
"downloadLocation": "NOASSERTION",
107+
"filesAnalyzed": false,
108+
"licenseConcluded": "MIT AND BSD-2-Clause AND GPL-2.0-or-later",
109+
"licenseDeclared": "NOASSERTION",
110+
"copyrightText": "NOASSERTION",
111+
"versionInfo": "1.2.3-r4",
112+
"supplier": "Organization: Timo Teräs <[email protected]>"
113+
},
114+
{
115+
"name": "apk-tools",
116+
"SPDXID": "SPDXRef-Package-ECA17F56515573DD93ACE781F91516271AC671A1C6A71618E49ED7F26C398AF2",
117+
"downloadLocation": "NOASSERTION",
118+
"filesAnalyzed": false,
119+
"licenseConcluded": "GPL-2.0-only",
120+
"licenseDeclared": "NOASSERTION",
121+
"copyrightText": "NOASSERTION",
122+
"versionInfo": "2.12.10-r1",
123+
"supplier": "Organization: Natanael Copa <[email protected]>"
124+
},
125+
{
126+
"name": "musl",
127+
"SPDXID": "SPDXRef-Package-705013B3778D10501271BF617E996011323C4A2E28E57A79D17B6955D6888627",
128+
"downloadLocation": "NOASSERTION",
129+
"filesAnalyzed": false,
130+
"licenseConcluded": "MIT",
131+
"licenseDeclared": "NOASSERTION",
132+
"copyrightText": "NOASSERTION",
133+
"versionInfo": "1.2.3-r4",
134+
"supplier": "Organization: Timo Teräs <[email protected]>"
135+
},
136+
{
137+
"name": "libc-utils",
138+
"SPDXID": "SPDXRef-Package-275AFBF9CCA335A5E19E25A072FFEFB4E434081793EB7292B98827157A1283A2",
139+
"downloadLocation": "NOASSERTION",
140+
"filesAnalyzed": false,
141+
"licenseConcluded": "BSD-2-Clause AND BSD-3-Clause",
142+
"licenseDeclared": "NOASSERTION",
143+
"copyrightText": "NOASSERTION",
144+
"versionInfo": "0.7.2-r3",
145+
"supplier": "Organization: Natanael Copa <[email protected]>"
146+
},
147+
{
148+
"name": "alpine-keys",
149+
"SPDXID": "SPDXRef-Package-932F58BF477100E836DA6FFE2DC4AFB017DBF2D0CE6BB04467E4BB26E7EC36FC",
150+
"downloadLocation": "NOASSERTION",
151+
"filesAnalyzed": false,
152+
"licenseConcluded": "MIT",
153+
"licenseDeclared": "NOASSERTION",
154+
"copyrightText": "NOASSERTION",
155+
"versionInfo": "2.4-r1",
156+
"supplier": "Organization: Natanael Copa <[email protected]>"
157+
},
158+
{
159+
"name": "scanelf",
160+
"SPDXID": "SPDXRef-Package-76DC710B1952C21FCC403CE0DAF2FEA3FB887F06C005D10639E71D7947DE90FB",
161+
"downloadLocation": "NOASSERTION",
162+
"filesAnalyzed": false,
163+
"licenseConcluded": "GPL-2.0-only",
164+
"licenseDeclared": "NOASSERTION",
165+
"copyrightText": "NOASSERTION",
166+
"versionInfo": "1.3.5-r1",
167+
"supplier": "Organization: Natanael Copa <[email protected]>"
168+
},
169+
{
170+
"name": "DockerImage",
171+
"SPDXID": "SPDXRef-RootPackage",
172+
"downloadLocation": "NOASSERTION",
173+
"packageVerificationCode": {
174+
"packageVerificationCodeValue": "da39a3ee5e6b4b0d3255bfef95601890afd80709"
175+
},
176+
"filesAnalyzed": true,
177+
"licenseConcluded": "NOASSERTION",
178+
"licenseInfoFromFiles": [
179+
"NOASSERTION"
180+
],
181+
"licenseDeclared": "NOASSERTION",
182+
"copyrightText": "NOASSERTION",
183+
"versionInfo": "1.0.0",
184+
"externalRefs": [
185+
{
186+
"referenceCategory": "PACKAGE-MANAGER",
187+
"referenceType": "purl",
188+
"referenceLocator": "pkg:swid/Company/sbom.company.com/[email protected]?tag_id=60e3f440-f9a8-449e-b516-da3049700fff"
189+
}
190+
],
191+
"supplier": "Organization: Company",
192+
"hasFiles": []
193+
}
194+
],
195+
"externalDocumentRefs": [],
196+
"relationships": [
197+
{
198+
"relationshipType": "DESCRIBES",
199+
"relatedSpdxElement": "SPDXRef-RootPackage",
200+
"spdxElementId": "SPDXRef-DOCUMENT"
201+
},
202+
{
203+
"relationshipType": "DEPENDS_ON",
204+
"relatedSpdxElement": "SPDXRef-Package-2471353CCE204889AD89EFCEC030096363B376775C5DFAFBDBEBD99597EEA969",
205+
"spdxElementId": "SPDXRef-RootPackage"
206+
},
207+
{
208+
"relationshipType": "DEPENDS_ON",
209+
"relatedSpdxElement": "SPDXRef-Package-269AF94DD8BFFCFAD5BA8BBC6E82B9365798AE4AF85B49BC3A5FE4F31DDE9488",
210+
"spdxElementId": "SPDXRef-RootPackage"
211+
},
212+
{
213+
"relationshipType": "DEPENDS_ON",
214+
"relatedSpdxElement": "SPDXRef-Package-A03157DF01BCE5BF4A209E6AAF611D234E1D38DD10136D6F48A0E30D4704DA2E",
215+
"spdxElementId": "SPDXRef-RootPackage"
216+
},
217+
{
218+
"relationshipType": "DEPENDS_ON",
219+
"relatedSpdxElement": "SPDXRef-Package-E8FDE8662042A1A73B88F43634ECA413A98DD1B5BA43F46455B024C794272A3C",
220+
"spdxElementId": "SPDXRef-RootPackage"
221+
},
222+
{
223+
"relationshipType": "DEPENDS_ON",
224+
"relatedSpdxElement": "SPDXRef-Package-275AFBF9CCA335A5E19E25A072FFEFB4E434081793EB7292B98827157A1283A2",
225+
"spdxElementId": "SPDXRef-RootPackage"
226+
},
227+
{
228+
"relationshipType": "DEPENDS_ON",
229+
"relatedSpdxElement": "SPDXRef-Package-ECA17F56515573DD93ACE781F91516271AC671A1C6A71618E49ED7F26C398AF2",
230+
"spdxElementId": "SPDXRef-RootPackage"
231+
},
232+
{
233+
"relationshipType": "DEPENDS_ON",
234+
"relatedSpdxElement": "SPDXRef-Package-01C3FA070AC7D05FE03422B385F566584B3AF940657764A6748FCD9AACEEB807",
235+
"spdxElementId": "SPDXRef-RootPackage"
236+
},
237+
{
238+
"relationshipType": "DEPENDS_ON",
239+
"relatedSpdxElement": "SPDXRef-Package-705013B3778D10501271BF617E996011323C4A2E28E57A79D17B6955D6888627",
240+
"spdxElementId": "SPDXRef-RootPackage"
241+
},
242+
{
243+
"relationshipType": "DEPENDS_ON",
244+
"relatedSpdxElement": "SPDXRef-Package-E00DB534A00A22C8CE4CA11B1FC76E8BE0E9B819067295BA0575664FC5D97D9C",
245+
"spdxElementId": "SPDXRef-RootPackage"
246+
},
247+
{
248+
"relationshipType": "DEPENDS_ON",
249+
"relatedSpdxElement": "SPDXRef-Package-342117E26CA6266072A57732213BAA801F51EB280EEF5A1CE4DBB9B3D8F5F5B0",
250+
"spdxElementId": "SPDXRef-RootPackage"
251+
},
252+
{
253+
"relationshipType": "DEPENDS_ON",
254+
"relatedSpdxElement": "SPDXRef-Package-00888F6C5F68DF809EB4EBE041A412FFD51276AA402850EECDC1C4A17921C5D3",
255+
"spdxElementId": "SPDXRef-RootPackage"
256+
},
257+
{
258+
"relationshipType": "DEPENDS_ON",
259+
"relatedSpdxElement": "SPDXRef-Package-76DC710B1952C21FCC403CE0DAF2FEA3FB887F06C005D10639E71D7947DE90FB",
260+
"spdxElementId": "SPDXRef-RootPackage"
261+
},
262+
{
263+
"relationshipType": "DEPENDS_ON",
264+
"relatedSpdxElement": "SPDXRef-Package-932F58BF477100E836DA6FFE2DC4AFB017DBF2D0CE6BB04467E4BB26E7EC36FC",
265+
"spdxElementId": "SPDXRef-RootPackage"
266+
},
267+
{
268+
"relationshipType": "DEPENDS_ON",
269+
"relatedSpdxElement": "SPDXRef-Package-BC8A0138F4AB887ACBE81B0C5FF1077477DD1F054104C9D28244F3EE1318EF29",
270+
"spdxElementId": "SPDXRef-RootPackage"
271+
},
272+
{
273+
"relationshipType": "DEPENDS_ON",
274+
"relatedSpdxElement": "SPDXRef-Package-2FF1344E2849EBD04203DE6480D48AA9DF318FD79C2CF23DEBBF14C63DAD7AD9",
275+
"spdxElementId": "SPDXRef-RootPackage"
276+
}
277+
],
278+
"spdxVersion": "SPDX-2.2",
279+
"dataLicense": "CC0-1.0",
280+
"SPDXID": "SPDXRef-DOCUMENT",
281+
"name": "DockerImage 1.0.0",
282+
"documentNamespace": "https://sbom.company.com/DockerImage/1.0.0/YuWW1Lwcd0S5vJUf-VhClw",
283+
"creationInfo": {
284+
"created": "2025-08-26T12:03:59Z",
285+
"creators": [
286+
"Organization: Company",
287+
"Tool: Microsoft.SBOMTool-4.1.1"
288+
]
289+
},
290+
"documentDescribes": [
291+
"SPDXRef-RootPackage"
292+
]
293+
}

scanpipe/tests/test_sca_integrations.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,3 +116,26 @@ def test_scanpipe_scan_integrations_load_sbom_depscan(self):
116116
self.assertEqual(33, project1.discoveredpackages.count())
117117
self.assertEqual(3, project1.discoveredpackages.vulnerable().count())
118118
self.assertEqual(20, project1.discovereddependencies.count())
119+
120+
def test_scanpipe_scan_integrations_load_sbom_sbomtool(self):
121+
# Input file generated with:
122+
# $ sbom-tool generate -di alpine:3.17.0 \
123+
# -pn DockerImage -pv 1.0.0 -ps Company -nsb https://sbom.company.com
124+
input_location = (
125+
self.data / "sca-integrations" / "sbom-tool-alpine-3.17-sbom.spdx.json"
126+
)
127+
128+
pipeline_name = "load_sbom"
129+
project1 = make_project()
130+
project1.copy_input_from(input_location)
131+
132+
run = project1.add_pipeline(pipeline_name)
133+
pipeline = run.make_pipeline_instance()
134+
135+
exitcode, out = pipeline.execute()
136+
self.assertEqual(0, exitcode, msg=out)
137+
138+
self.assertEqual(1, project1.codebaseresources.count())
139+
self.assertEqual(16, project1.discoveredpackages.count())
140+
self.assertEqual(0, project1.discoveredpackages.vulnerable().count())
141+
self.assertEqual(16, project1.discovereddependencies.count())

0 commit comments

Comments
 (0)