diff --git a/cliv2/go.mod b/cliv2/go.mod index 7fb24485a51..b9b4d6aa2e0 100644 --- a/cliv2/go.mod +++ b/cliv2/go.mod @@ -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 diff --git a/cliv2/go.sum b/cliv2/go.sum index 457963e0e20..4fced89d027 100644 --- a/cliv2/go.sum +++ b/cliv2/go.sum @@ -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= diff --git a/test/acceptance/fake-server.ts b/test/acceptance/fake-server.ts index 524733491e2..fa11825cb81 100644 --- a/test/acceptance/fake-server.ts +++ b/test/acceptance/fake-server.ts @@ -553,33 +553,57 @@ export const fakeServer = (basePath: string, snykToken: string): FakeServer => { (req, res) => { const depGraph: void | Record = req.body.depGraph; const depGraphs: void | Record[] = req.body.depGraphs; - const tools: void | Record[] = req.body.tools; - let bom: Record = { bomFormat: 'CycloneDX' }; + const tools = req.body.tools || []; + let name = ''; + let components; + + let bom: Record = { 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); diff --git a/test/jest/acceptance/snyk-container/container.spec.ts b/test/jest/acceptance/snyk-container/container.spec.ts index 6935d66bed0..19122973fd5 100644 --- a/test/jest/acceptance/snyk-container/container.spec.ts +++ b/test/jest/acceptance/snyk-container/container.spec.ts @@ -185,7 +185,7 @@ DepGraph end`, }); }); - it('should print sbom for image', async () => { + it('should print sbom for image - spdx', async () => { const { code, stdout, @@ -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, ); diff --git a/test/jest/acceptance/snyk-sbom/sbom.spec.ts b/test/jest/acceptance/snyk-sbom/sbom.spec.ts index c444e22333e..e9ecc5138e9 100644 --- a/test/jest/acceptance/snyk-sbom/sbom.spec.ts +++ b/test/jest/acceptance/snyk-sbom/sbom.spec.ts @@ -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( @@ -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( @@ -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), + }, + ]), + ); + }); });