Skip to content

Commit b22b166

Browse files
Daniel Ekelundpaulrosca-snyk
Daniel Ekelund
andauthored
feat: support CycloneDX v1.5 (#5123)
- Updated `snyk sbom` to accept CycloneDX 1.5 - Updated `snyk container sbom` to accept CycloneDX 1.5 Co-authored-by: Paul Rosca <[email protected]>
1 parent c55af61 commit b22b166

File tree

5 files changed

+174
-33
lines changed

5 files changed

+174
-33
lines changed

cliv2/go.mod

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,8 @@ require (
1111
github.com/rs/zerolog v1.32.0
1212
github.com/snyk/cli-extension-dep-graph v0.0.0-20230926124856-b0fdf1ee6f73
1313
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce
14-
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a
15-
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f
14+
github.com/snyk/cli-extension-sbom v0.0.0-20240314090036-46535b380426
15+
github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1
1616
github.com/snyk/go-application-framework v0.0.0-20240320144631-1935b2cb624e
1717
github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65
1818
github.com/snyk/snyk-iac-capture v0.6.5

cliv2/go.sum

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -654,12 +654,12 @@ github.com/snyk/cli-extension-dep-graph v0.0.0-20230926124856-b0fdf1ee6f73 h1:rw
654654
github.com/snyk/cli-extension-dep-graph v0.0.0-20230926124856-b0fdf1ee6f73/go.mod h1:QF3v8HBpOpyudYNCuR8LqfULutO76c91sBdLzD+pBJU=
655655
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce h1:WchwuyPX4mEr7tFCGD6EsjwTDipFWfLxs4Wps6KB3b4=
656656
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce/go.mod h1:5/IYYTgf32pST7St4GhS3KNz32WE17Ys+Hdb5Pqxex0=
657-
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a h1:oRrk9bvMXdAVhRt84Y8G06+Op7fYQYrRuslngG9BPZk=
658-
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a/go.mod h1:IwRGWjRuNkY08O7NJb7u3JuQkroEB8Qi1MlASpZVu1Q=
657+
github.com/snyk/cli-extension-sbom v0.0.0-20240314090036-46535b380426 h1:MXbip3nmiOym3/9bNWlPISVOAEAAz4FDcPvqOMPcCc4=
658+
github.com/snyk/cli-extension-sbom v0.0.0-20240314090036-46535b380426/go.mod h1:g2VgZU79btvZrAP3oHZGv3tHD9POVOx5a3DY894rS4w=
659659
github.com/snyk/code-client-go v0.3.1 h1:jCYBRJJ/qVlPRqJONwmwpMCMe7s/lulbJQE6KUe2DW0=
660660
github.com/snyk/code-client-go v0.3.1/go.mod h1:D+cfqDbuZE1S106bY3Tr+ZXLb9BR16kKBtvlf0xdyNA=
661-
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f h1:ghajT5PEiLP8XNFIdc7Yn4Th74RH/9Q++dDOp6Cb9eo=
662-
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
661+
github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1 h1:9RKY9NdX5DrJAoVXDP0JiqrXT+4Nb9NH8pjEcA0NsLA=
662+
github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
663663
github.com/snyk/go-application-framework v0.0.0-20240320144631-1935b2cb624e h1:Cu5BIVoy+s6/oiOd0OAJS02AuJ+jM8FF2RBZ+YoZs80=
664664
github.com/snyk/go-application-framework v0.0.0-20240320144631-1935b2cb624e/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
665665
github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk=

test/acceptance/fake-server.ts

Lines changed: 46 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -553,33 +553,57 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
553553
(req, res) => {
554554
const depGraph: void | Record<string, any> = req.body.depGraph;
555555
const depGraphs: void | Record<string, any>[] = req.body.depGraphs;
556-
const tools: void | Record<string, any>[] = req.body.tools;
557-
let bom: Record<string, unknown> = { bomFormat: 'CycloneDX' };
556+
const tools = req.body.tools || [];
557+
let name = '';
558+
let components;
559+
560+
let bom: Record<string, any> = { bomFormat: 'CycloneDX' };
558561

559562
if (Array.isArray(depGraphs) && req.body.subject) {
560563
// Return a fixture of an all-projects SBOM.
561-
bom = {
562-
...bom,
563-
metadata: { component: { name: req.body.subject.name } },
564-
components: depGraphs
565-
.flatMap(({ pkgs }) => pkgs)
566-
.map(({ info: { name } }) => ({ name })),
567-
};
568-
}
569-
570-
if (depGraph) {
571-
bom = {
572-
...bom,
573-
metadata: { component: { name: depGraph.pkgs[0]?.info.name } },
574-
components: depGraph.pkgs.map(({ info: { name } }) => ({ name })),
575-
};
564+
name = req.body.subject.name;
565+
components = depGraphs
566+
.flatMap(({ pkgs }) => pkgs)
567+
.map(({ info: { name } }) => ({ name }));
568+
} else if (depGraph) {
569+
name = depGraph.pkgs[0]?.info.name;
570+
components = depGraph.pkgs.map(({ info: { name } }) => ({ name }));
576571
}
577572

578-
if (Array.isArray(tools)) {
579-
bom.metadata = {
580-
...(bom.metadata as any),
581-
tools: [...tools, { name: 'fake-server' }],
582-
};
573+
switch (req.query.format) {
574+
case 'spdx2.3+json':
575+
bom = {
576+
spdxVersion: 'SPDX-2.3',
577+
name,
578+
packages: components,
579+
creators: [...tools, 'fake-server'],
580+
};
581+
break;
582+
case 'cyclonedx1.4+json':
583+
bom = {
584+
specVersion: '1.4',
585+
$schema: 'http://cyclonedx.org/schema/bom-1.4.schema.json',
586+
components,
587+
metadata: {
588+
component: { name },
589+
tools: [...tools, { name: 'fake-server', version: '42' }],
590+
},
591+
};
592+
break;
593+
case 'cyclonedx1.5+json':
594+
bom = {
595+
specVersion: '1.5',
596+
$schema: 'http://cyclonedx.org/schema/bom-1.5.schema.json',
597+
components,
598+
metadata: {
599+
component: { name },
600+
tools: {
601+
components: [...tools, { name: 'fake-server' }],
602+
services: [{ name: 'fake-server', version: '42' }],
603+
},
604+
},
605+
};
606+
break;
583607
}
584608

585609
res.status(200).send(bom);

test/jest/acceptance/snyk-container/container.spec.ts

Lines changed: 57 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ DepGraph end`,
185185
});
186186
});
187187

188-
it('should print sbom for image', async () => {
188+
it('should print sbom for image - spdx', async () => {
189189
const {
190190
code,
191191
stdout,
@@ -201,7 +201,62 @@ DepGraph end`,
201201
expect(() => {
202202
sbom = JSON.parse(stdout);
203203
}).not.toThrow();
204-
expect(sbom.metadata.component.name).toEqual('gcr.io/distroless/static');
204+
expect(sbom.name).toEqual('gcr.io/distroless/static');
205+
expect(sbom.spdxVersion).toEqual('SPDX-2.3');
206+
expect(sbom.packages).toHaveLength(
207+
TEST_DISTROLESS_STATIC_IMAGE_DEPGRAPH.pkgs.length,
208+
);
209+
});
210+
211+
it('should print sbom for image - cyclonedx 1.4', async () => {
212+
const {
213+
code,
214+
stdout,
215+
stderr,
216+
} = await runSnykCLIWithDebug(
217+
`container sbom --org=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format=cyclonedx1.4+json ${TEST_DISTROLESS_STATIC_IMAGE}`,
218+
{ env },
219+
);
220+
221+
let sbom: any;
222+
assertCliExitCode(code, 0, stderr);
223+
224+
expect(() => {
225+
sbom = JSON.parse(stdout);
226+
}).not.toThrow();
227+
228+
expect(sbom.specVersion).toEqual('1.4');
229+
expect(sbom['$schema']).toEqual(
230+
'http://cyclonedx.org/schema/bom-1.4.schema.json',
231+
);
232+
233+
expect(sbom.components).toHaveLength(
234+
TEST_DISTROLESS_STATIC_IMAGE_DEPGRAPH.pkgs.length,
235+
);
236+
});
237+
238+
it('should print sbom for image - cyclonedx 1.5', async () => {
239+
const {
240+
code,
241+
stdout,
242+
stderr,
243+
} = await runSnykCLIWithDebug(
244+
`container sbom --org=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format=cyclonedx1.5+json ${TEST_DISTROLESS_STATIC_IMAGE}`,
245+
{ env },
246+
);
247+
248+
let sbom: any;
249+
assertCliExitCode(code, 0, stderr);
250+
251+
expect(() => {
252+
sbom = JSON.parse(stdout);
253+
}).not.toThrow();
254+
255+
expect(sbom.specVersion).toEqual('1.5');
256+
expect(sbom['$schema']).toEqual(
257+
'http://cyclonedx.org/schema/bom-1.5.schema.json',
258+
);
259+
205260
expect(sbom.components).toHaveLength(
206261
TEST_DISTROLESS_STATIC_IMAGE_DEPGRAPH.pkgs.length,
207262
);

test/jest/acceptance/snyk-sbom/sbom.spec.ts

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ describe('snyk sbom (mocked server only)', () => {
3535
});
3636
});
3737

38-
test('`sbom` generates an SBOM for a single project', async () => {
38+
test('`sbom` generates an SBOM for a single project - CycloneDX 1.4', async () => {
3939
const project = await createProjectFromWorkspace('npm-package');
4040

4141
const { code, stdout } = await runSnykCLI(
@@ -45,17 +45,22 @@ describe('snyk sbom (mocked server only)', () => {
4545
env,
4646
},
4747
);
48-
let bom;
48+
let bom: any;
4949

5050
expect(code).toEqual(0);
5151
expect(() => {
5252
bom = JSON.parse(stdout);
5353
}).not.toThrow();
54+
55+
expect(bom.specVersion).toEqual('1.4');
56+
expect(bom['$schema']).toEqual(
57+
'http://cyclonedx.org/schema/bom-1.4.schema.json',
58+
);
5459
expect(bom.metadata.component.name).toEqual('npm-package');
5560
expect(bom.components).toHaveLength(3);
5661
});
5762

58-
test('`sbom` includes a tool name in the document', async () => {
63+
test('`sbom` includes a tool name in the document - CycloneDX 1.4', async () => {
5964
const project = await createProjectFromWorkspace('npm-package');
6065

6166
const { stdout } = await runSnykCLI(
@@ -77,4 +82,61 @@ describe('snyk sbom (mocked server only)', () => {
7782
]),
7883
);
7984
});
85+
86+
test('`sbom` generates an SBOM for a single project - CycloneDX 1.5', async () => {
87+
const project = await createProjectFromWorkspace('npm-package');
88+
89+
const { code, stdout } = await runSnykCLI(
90+
`sbom --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format cyclonedx1.5+json --debug`,
91+
{
92+
cwd: project.path(),
93+
env,
94+
},
95+
);
96+
let bom: any;
97+
98+
expect(code).toEqual(0);
99+
expect(() => {
100+
bom = JSON.parse(stdout);
101+
}).not.toThrow();
102+
103+
expect(bom.specVersion).toEqual('1.5');
104+
expect(bom['$schema']).toEqual(
105+
'http://cyclonedx.org/schema/bom-1.5.schema.json',
106+
);
107+
expect(bom.metadata.component.name).toEqual('npm-package');
108+
expect(bom.components).toHaveLength(3);
109+
});
110+
111+
test('`sbom` includes a tool name in the document - CycloneDX 1.5', async () => {
112+
const project = await createProjectFromWorkspace('npm-package');
113+
114+
const { stdout } = await runSnykCLI(
115+
`sbom --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format cyclonedx1.5+json --debug`,
116+
{
117+
cwd: project.path(),
118+
env,
119+
},
120+
);
121+
const bom = JSON.parse(stdout);
122+
123+
expect(bom.metadata.tools.components).toEqual(
124+
expect.arrayContaining([
125+
{
126+
vendor: 'Snyk',
127+
name: 'snyk-cli',
128+
version: expect.any(String),
129+
},
130+
]),
131+
);
132+
133+
expect(bom.metadata.tools.services).toEqual(
134+
expect.arrayContaining([
135+
{
136+
name: 'fake-server',
137+
version: expect.any(String),
138+
},
139+
]),
140+
);
141+
});
80142
});

0 commit comments

Comments
 (0)