Skip to content

Commit

Permalink
feat: support CycloneDX v1.5 (#5123)
Browse files Browse the repository at this point in the history
- Updated `snyk sbom` to accept CycloneDX 1.5
- Updated `snyk container sbom` to accept CycloneDX 1.5

Co-authored-by: Paul Rosca <paul.rosca@snyk.io>
  • Loading branch information
Daniel Ekelund and paulrosca-snyk authored Mar 25, 2024
1 parent c55af61 commit b22b166
Show file tree
Hide file tree
Showing 5 changed files with 174 additions and 33 deletions.
4 changes: 2 additions & 2 deletions cliv2/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,8 @@ require (
github.com/rs/zerolog v1.32.0
github.com/snyk/cli-extension-dep-graph v0.0.0-20230926124856-b0fdf1ee6f73
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f
github.com/snyk/cli-extension-sbom v0.0.0-20240314090036-46535b380426
github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1
github.com/snyk/go-application-framework v0.0.0-20240320144631-1935b2cb624e
github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65
github.com/snyk/snyk-iac-capture v0.6.5
Expand Down
8 changes: 4 additions & 4 deletions cliv2/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -654,12 +654,12 @@ github.com/snyk/cli-extension-dep-graph v0.0.0-20230926124856-b0fdf1ee6f73 h1:rw
github.com/snyk/cli-extension-dep-graph v0.0.0-20230926124856-b0fdf1ee6f73/go.mod h1:QF3v8HBpOpyudYNCuR8LqfULutO76c91sBdLzD+pBJU=
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce h1:WchwuyPX4mEr7tFCGD6EsjwTDipFWfLxs4Wps6KB3b4=
github.com/snyk/cli-extension-iac-rules v0.0.0-20230601153200-c572cfce46ce/go.mod h1:5/IYYTgf32pST7St4GhS3KNz32WE17Ys+Hdb5Pqxex0=
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a h1:oRrk9bvMXdAVhRt84Y8G06+Op7fYQYrRuslngG9BPZk=
github.com/snyk/cli-extension-sbom v0.0.0-20231123083311-52b1cecc1a7a/go.mod h1:IwRGWjRuNkY08O7NJb7u3JuQkroEB8Qi1MlASpZVu1Q=
github.com/snyk/cli-extension-sbom v0.0.0-20240314090036-46535b380426 h1:MXbip3nmiOym3/9bNWlPISVOAEAAz4FDcPvqOMPcCc4=
github.com/snyk/cli-extension-sbom v0.0.0-20240314090036-46535b380426/go.mod h1:g2VgZU79btvZrAP3oHZGv3tHD9POVOx5a3DY894rS4w=
github.com/snyk/code-client-go v0.3.1 h1:jCYBRJJ/qVlPRqJONwmwpMCMe7s/lulbJQE6KUe2DW0=
github.com/snyk/code-client-go v0.3.1/go.mod h1:D+cfqDbuZE1S106bY3Tr+ZXLb9BR16kKBtvlf0xdyNA=
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f h1:ghajT5PEiLP8XNFIdc7Yn4Th74RH/9Q++dDOp6Cb9eo=
github.com/snyk/container-cli v0.0.0-20230920093251-fe865879a91f/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1 h1:9RKY9NdX5DrJAoVXDP0JiqrXT+4Nb9NH8pjEcA0NsLA=
github.com/snyk/container-cli v0.0.0-20240322120441-6d9b9482f9b1/go.mod h1:38w+dcAQp9eG3P5t2eNS9eG0reut10AeJjLv5lJ5lpM=
github.com/snyk/go-application-framework v0.0.0-20240320144631-1935b2cb624e h1:Cu5BIVoy+s6/oiOd0OAJS02AuJ+jM8FF2RBZ+YoZs80=
github.com/snyk/go-application-framework v0.0.0-20240320144631-1935b2cb624e/go.mod h1:Yz/qxFyfhf0xbA+z8Vzr5IM9IDG+BS+2PiGaP1yAsEw=
github.com/snyk/go-httpauth v0.0.0-20240307114523-1f5ea3f55c65 h1:CEQuYv0Go6MEyRCD3YjLYM2u3Oxkx8GpCpFBd4rUTUk=
Expand Down
68 changes: 46 additions & 22 deletions test/acceptance/fake-server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -553,33 +553,57 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => {
(req, res) => {
const depGraph: void | Record<string, any> = req.body.depGraph;
const depGraphs: void | Record<string, any>[] = req.body.depGraphs;
const tools: void | Record<string, any>[] = req.body.tools;
let bom: Record<string, unknown> = { bomFormat: 'CycloneDX' };
const tools = req.body.tools || [];
let name = '';
let components;

let bom: Record<string, any> = { bomFormat: 'CycloneDX' };

if (Array.isArray(depGraphs) && req.body.subject) {
// Return a fixture of an all-projects SBOM.
bom = {
...bom,
metadata: { component: { name: req.body.subject.name } },
components: depGraphs
.flatMap(({ pkgs }) => pkgs)
.map(({ info: { name } }) => ({ name })),
};
}

if (depGraph) {
bom = {
...bom,
metadata: { component: { name: depGraph.pkgs[0]?.info.name } },
components: depGraph.pkgs.map(({ info: { name } }) => ({ name })),
};
name = req.body.subject.name;
components = depGraphs
.flatMap(({ pkgs }) => pkgs)
.map(({ info: { name } }) => ({ name }));
} else if (depGraph) {
name = depGraph.pkgs[0]?.info.name;
components = depGraph.pkgs.map(({ info: { name } }) => ({ name }));
}

if (Array.isArray(tools)) {
bom.metadata = {
...(bom.metadata as any),
tools: [...tools, { name: 'fake-server' }],
};
switch (req.query.format) {
case 'spdx2.3+json':
bom = {
spdxVersion: 'SPDX-2.3',
name,
packages: components,
creators: [...tools, 'fake-server'],
};
break;
case 'cyclonedx1.4+json':
bom = {
specVersion: '1.4',
$schema: 'http://cyclonedx.org/schema/bom-1.4.schema.json',
components,
metadata: {
component: { name },
tools: [...tools, { name: 'fake-server', version: '42' }],
},
};
break;
case 'cyclonedx1.5+json':
bom = {
specVersion: '1.5',
$schema: 'http://cyclonedx.org/schema/bom-1.5.schema.json',
components,
metadata: {
component: { name },
tools: {
components: [...tools, { name: 'fake-server' }],
services: [{ name: 'fake-server', version: '42' }],
},
},
};
break;
}

res.status(200).send(bom);
Expand Down
59 changes: 57 additions & 2 deletions test/jest/acceptance/snyk-container/container.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@ DepGraph end`,
});
});

it('should print sbom for image', async () => {
it('should print sbom for image - spdx', async () => {
const {
code,
stdout,
Expand All @@ -201,7 +201,62 @@ DepGraph end`,
expect(() => {
sbom = JSON.parse(stdout);
}).not.toThrow();
expect(sbom.metadata.component.name).toEqual('gcr.io/distroless/static');
expect(sbom.name).toEqual('gcr.io/distroless/static');
expect(sbom.spdxVersion).toEqual('SPDX-2.3');
expect(sbom.packages).toHaveLength(
TEST_DISTROLESS_STATIC_IMAGE_DEPGRAPH.pkgs.length,
);
});

it('should print sbom for image - cyclonedx 1.4', async () => {
const {
code,
stdout,
stderr,
} = await runSnykCLIWithDebug(
`container sbom --org=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format=cyclonedx1.4+json ${TEST_DISTROLESS_STATIC_IMAGE}`,
{ env },
);

let sbom: any;
assertCliExitCode(code, 0, stderr);

expect(() => {
sbom = JSON.parse(stdout);
}).not.toThrow();

expect(sbom.specVersion).toEqual('1.4');
expect(sbom['$schema']).toEqual(
'http://cyclonedx.org/schema/bom-1.4.schema.json',
);

expect(sbom.components).toHaveLength(
TEST_DISTROLESS_STATIC_IMAGE_DEPGRAPH.pkgs.length,
);
});

it('should print sbom for image - cyclonedx 1.5', async () => {
const {
code,
stdout,
stderr,
} = await runSnykCLIWithDebug(
`container sbom --org=aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format=cyclonedx1.5+json ${TEST_DISTROLESS_STATIC_IMAGE}`,
{ env },
);

let sbom: any;
assertCliExitCode(code, 0, stderr);

expect(() => {
sbom = JSON.parse(stdout);
}).not.toThrow();

expect(sbom.specVersion).toEqual('1.5');
expect(sbom['$schema']).toEqual(
'http://cyclonedx.org/schema/bom-1.5.schema.json',
);

expect(sbom.components).toHaveLength(
TEST_DISTROLESS_STATIC_IMAGE_DEPGRAPH.pkgs.length,
);
Expand Down
68 changes: 65 additions & 3 deletions test/jest/acceptance/snyk-sbom/sbom.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ describe('snyk sbom (mocked server only)', () => {
});
});

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

const { code, stdout } = await runSnykCLI(
Expand All @@ -45,17 +45,22 @@ describe('snyk sbom (mocked server only)', () => {
env,
},
);
let bom;
let bom: any;

expect(code).toEqual(0);
expect(() => {
bom = JSON.parse(stdout);
}).not.toThrow();

expect(bom.specVersion).toEqual('1.4');
expect(bom['$schema']).toEqual(
'http://cyclonedx.org/schema/bom-1.4.schema.json',
);
expect(bom.metadata.component.name).toEqual('npm-package');
expect(bom.components).toHaveLength(3);
});

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

const { stdout } = await runSnykCLI(
Expand All @@ -77,4 +82,61 @@ describe('snyk sbom (mocked server only)', () => {
]),
);
});

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

const { code, stdout } = await runSnykCLI(
`sbom --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format cyclonedx1.5+json --debug`,
{
cwd: project.path(),
env,
},
);
let bom: any;

expect(code).toEqual(0);
expect(() => {
bom = JSON.parse(stdout);
}).not.toThrow();

expect(bom.specVersion).toEqual('1.5');
expect(bom['$schema']).toEqual(
'http://cyclonedx.org/schema/bom-1.5.schema.json',
);
expect(bom.metadata.component.name).toEqual('npm-package');
expect(bom.components).toHaveLength(3);
});

test('`sbom` includes a tool name in the document - CycloneDX 1.5', async () => {
const project = await createProjectFromWorkspace('npm-package');

const { stdout } = await runSnykCLI(
`sbom --org aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee --format cyclonedx1.5+json --debug`,
{
cwd: project.path(),
env,
},
);
const bom = JSON.parse(stdout);

expect(bom.metadata.tools.components).toEqual(
expect.arrayContaining([
{
vendor: 'Snyk',
name: 'snyk-cli',
version: expect.any(String),
},
]),
);

expect(bom.metadata.tools.services).toEqual(
expect.arrayContaining([
{
name: 'fake-server',
version: expect.any(String),
},
]),
);
});
});

0 comments on commit b22b166

Please sign in to comment.