From f74b3a6dfdc15709a4800bc9ce8a890c95932602 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Comb=C3=BCchen?= Date: Fri, 30 Jun 2023 14:49:59 +0200 Subject: [PATCH] test: improve test coverage of snyk package --- lib/snyk/enrich_cyclonedx.go | 6 +- lib/snyk/enrich_test.go | 117 ++++++++++++++++++++++++++++ lib/snyk/self_test.go | 16 +--- lib/snyk/testdata/no_issues.json | 12 +++ lib/snyk/testdata/numpy_issues.json | 106 +++++++++++++++++++++++++ lib/snyk/testdata/self.json | 18 +++++ 6 files changed, 260 insertions(+), 15 deletions(-) create mode 100644 lib/snyk/enrich_test.go create mode 100644 lib/snyk/testdata/no_issues.json create mode 100644 lib/snyk/testdata/numpy_issues.json create mode 100644 lib/snyk/testdata/self.json diff --git a/lib/snyk/enrich_cyclonedx.go b/lib/snyk/enrich_cyclonedx.go index b6fec8c..2be3b8a 100644 --- a/lib/snyk/enrich_cyclonedx.go +++ b/lib/snyk/enrich_cyclonedx.go @@ -156,7 +156,11 @@ func enrichCycloneDX(bom *cdx.BOM) *cdx.BOM { } } } - bom.Vulnerabilities = &vulns + + if len(vulns) > 0 { + bom.Vulnerabilities = &vulns + } + return bom } diff --git a/lib/snyk/enrich_test.go b/lib/snyk/enrich_test.go new file mode 100644 index 0000000..27d4b65 --- /dev/null +++ b/lib/snyk/enrich_test.go @@ -0,0 +1,117 @@ +package snyk + +import ( + "testing" + + cdx "github.com/CycloneDX/cyclonedx-go" + "github.com/jarcoal/httpmock" + spdx "github.com/spdx/tools-golang/spdx/v2/common" + spdx_2_3 "github.com/spdx/tools-golang/spdx/v2/v2_3" + "github.com/stretchr/testify/assert" + + "github.com/snyk/parlay/lib/sbom" +) + +func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) { + teardown := setupTestEnv(t) + defer teardown() + + bom := &cdx.BOM{ + Components: &[]cdx.Component{ + { + BOMRef: "pkg:pypi/numpy@1.16.0", + Name: "numpy", + Version: "1.16.0", + PackageURL: "pkg:pypi/numpy@1.16.0", + }, + }, + } + doc := &sbom.SBOMDocument{BOM: bom} + + EnrichSBOM(doc) + + assert.NotNil(t, bom.Vulnerabilities) + assert.Len(t, *bom.Vulnerabilities, 1) + vuln := (*bom.Vulnerabilities)[0] + assert.Equal(t, "pkg:pypi/numpy@1.16.0", vuln.BOMRef) + assert.Equal(t, "SNYK-PYTHON-NUMPY-73513", vuln.ID) +} + +func TestEnrichSBOM_CycloneDXWithoutVulnerabilities(t *testing.T) { + teardown := setupTestEnv(t) + defer teardown() + + bom := &cdx.BOM{ + Components: &[]cdx.Component{ + { + BOMRef: "pkg:pypi/werkzeug@2.2.3", + Name: "werkzeug", + Version: "2.2.3", + PackageURL: "pkg:pypi/werkzeug@2.2.3", + }, + }, + } + doc := &sbom.SBOMDocument{BOM: bom} + + EnrichSBOM(doc) + + assert.Nil(t, bom.Vulnerabilities, "should not extend vulnerabilities if there are none") +} + +func TestEnrichSBOM_SPDXWithVulnerabilities(t *testing.T) { + teardown := setupTestEnv(t) + defer teardown() + + bom := &spdx_2_3.Document{ + Packages: []*spdx_2_3.Package{ + { + PackageSPDXIdentifier: "pkg:pypi/numpy@1.16.0", + PackageName: "numpy", + PackageVersion: "1.16.0", + PackageExternalReferences: []*spdx_2_3.PackageExternalReference{ + { + Category: spdx.CategoryPackageManager, + RefType: "purl", + Locator: "pkg:pypi/numpy@1.16.0", + }, + }, + }, + }, + } + doc := &sbom.SBOMDocument{BOM: bom} + + EnrichSBOM(doc) + + vulnRef := bom.Packages[0].PackageExternalReferences[1] + assert.Equal(t, "SECURITY", vulnRef.Category) + assert.Equal(t, "advisory", vulnRef.RefType) + assert.Equal(t, "https://security.snyk.io/vuln/SNYK-PYTHON-NUMPY-73513", vulnRef.Locator) + assert.Equal(t, "Arbitrary Code Execution", vulnRef.ExternalRefComment) +} + +func setupTestEnv(t *testing.T) func() { + t.Helper() + + t.Setenv("SNYK_TOKEN", "asdf") + + httpmock.Activate() + httpmock.RegisterResponder( + "GET", + `=~^https://api\.snyk\.io/rest/self`, + httpmock.NewJsonResponderOrPanic(200, httpmock.File("testdata/self.json")), + ) + httpmock.RegisterResponder( + "GET", + `=~^https://api\.snyk\.io/rest/orgs/[a-z0-9-]+/packages/pkg%3Apypi%2Fnumpy%401.16.0/issues`, + httpmock.NewJsonResponderOrPanic(200, httpmock.File("testdata/numpy_issues.json")), + ) + httpmock.RegisterResponder( + "GET", + `=~^https://api\.snyk\.io/rest/orgs/[a-z0-9-]+/packages/.*/issues`, + httpmock.NewJsonResponderOrPanic(200, httpmock.File("testdata/no_issues.json")), + ) + + return func() { + httpmock.DeactivateAndReset() + } +} diff --git a/lib/snyk/self_test.go b/lib/snyk/self_test.go index 3d67984..b874e46 100644 --- a/lib/snyk/self_test.go +++ b/lib/snyk/self_test.go @@ -17,7 +17,6 @@ package snyk import ( - "net/http" "testing" "github.com/deepmap/oapi-codegen/pkg/securityprovider" @@ -28,25 +27,14 @@ import ( ) func TestGetSnykOrg(t *testing.T) { - expectedOrg := uuid.New() + expectedOrg := uuid.MustParse("00000000-0000-0000-0000-000000000000") auth, err := securityprovider.NewSecurityProviderApiKey("header", "name", "value") require.NoError(t, err) httpmock.Activate() defer httpmock.DeactivateAndReset() httpmock.RegisterResponder("GET", "https://api.snyk.io/rest/self", - func(req *http.Request) (*http.Response, error) { - jsonBody := `{ - "data": { - "attributes": { - "default_org_context": "` + expectedOrg.String() + `" - } - } - }` - resp := httpmock.NewStringResponse(200, jsonBody) - resp.Header.Set("Content-Type", "application/json") - return resp, nil - }, + httpmock.NewJsonResponderOrPanic(200, httpmock.File("testdata/self.json")), ) actualOrg, err := getSnykOrg(auth) diff --git a/lib/snyk/testdata/no_issues.json b/lib/snyk/testdata/no_issues.json new file mode 100644 index 0000000..a167795 --- /dev/null +++ b/lib/snyk/testdata/no_issues.json @@ -0,0 +1,12 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [], + "links": { + "self": "/orgs/00000000-0000-0000-0000-000000000000/packages/pkg%3A/issues?version=2023-06-01&limit=1000&offset=0" + }, + "meta": { + "package": {} + } +} \ No newline at end of file diff --git a/lib/snyk/testdata/numpy_issues.json b/lib/snyk/testdata/numpy_issues.json new file mode 100644 index 0000000..25d1208 --- /dev/null +++ b/lib/snyk/testdata/numpy_issues.json @@ -0,0 +1,106 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": [ + { + "id": "SNYK-PYTHON-NUMPY-73513", + "type": "issue", + "attributes": { + "key": "SNYK-PYTHON-NUMPY-73513", + "title": "Arbitrary Code Execution", + "type": "package_vulnerability", + "created_at": "2019-01-16T14:11:37.000761Z", + "updated_at": "2022-09-01T16:21:50.298458Z", + "description": "## Overview\n[numpy](https://github.com/numpy/numpy) is a fundamental package needed for scientific computing with Python.\n\nAffected versions of this package are vulnerable to Arbitrary Code Execution. It uses the pickle Python module unsafely, which allows remote attackers to execute arbitrary code via a crafted serialized object, as demonstrated by a `numpy.load` call.\r\n\r\nPoC by nanshihui:\r\n```py\r\nimport numpy\r\nfrom numpy import __version__\r\nprint __version__\r\nimport os\r\nimport pickle\r\nclass Test(object):\r\n def __init__(self):\r\n self.a = 1\r\n\r\n def __reduce__(self):\r\n return (os.system,('ls',))\r\ntmpdaa = Test()\r\nwith open(\"a-file.pickle\",'wb') as f:\r\n pickle.dump(tmpdaa,f)\r\nnumpy.load('a-file.pickle')\r\n```\n## Remediation\nUpgrade `numpy` to version 1.16.3 or higher.\n## References\n- [GitHub Commit](https://github.com/numpy/numpy/commit/89b688732b37616c9d26623f81aaee1703c30ffb)\n- [GitHub Issue](https://github.com/numpy/numpy/issues/12759)\n- [GitHub PR](https://github.com/numpy/numpy/pull/13359)\n- [PoC](https://github.com/RayScri/CVE-2019-6446)\n", + "problems": [ + { + "id": "CVE-2019-6446", + "source": "CVE" + }, + { + "id": "CWE-94", + "source": "CWE" + } + ], + "coordinates": [ + { + "remedies": [ + { + "type": "indeterminate", + "description": "Upgrade the package version to 1.16.3 to fix this vulnerability", + "details": { + "upgrade_package": "1.16.3" + } + } + ], + "representation": [ + "[0,1.16.3)" + ] + } + ], + "severities": [ + { + "source": "Snyk", + "level": "critical", + "score": 9.8, + "vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H/E:P" + }, + { + "source": "NVD", + "level": "critical", + "score": 9.8, + "vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H" + }, + { + "source": "Red Hat", + "level": "high", + "score": 8.8, + "vector": "CVSS:3.0/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H" + }, + { + "source": "SUSE", + "level": "high", + "score": 7.8, + "vector": "CVSS:3.0/AV:L/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H" + } + ], + "effective_severity_level": "critical", + "slots": { + "disclosure_time": "2019-01-16T12:26:38Z", + "exploit": "Proof of Concept", + "publication_time": "2019-01-16T13:50:50Z", + "references": [ + { + "url": "https://github.com/numpy/numpy/commit/89b688732b37616c9d26623f81aaee1703c30ffb", + "title": "GitHub Commit" + }, + { + "url": "https://github.com/numpy/numpy/issues/12759", + "title": "GitHub Issue" + }, + { + "url": "https://github.com/numpy/numpy/pull/13359", + "title": "GitHub PR" + }, + { + "url": "https://github.com/RayScri/CVE-2019-6446", + "title": "PoC" + } + ] + } + } + } + ], + "links": { + "self": "/orgs/00000000-0000-0000-0000-000000000000/packages/pkg%3Apypi%2Fnumpy%401.16.0/issues?version=2023-06-01&limit=1000&offset=0" + }, + "meta": { + "package": { + "name": "numpy", + "type": "pypi", + "url": "pkg:pypi/numpy@1.16.0", + "version": "1.16.0" + } + } +} \ No newline at end of file diff --git a/lib/snyk/testdata/self.json b/lib/snyk/testdata/self.json new file mode 100644 index 0000000..5e3f43e --- /dev/null +++ b/lib/snyk/testdata/self.json @@ -0,0 +1,18 @@ +{ + "jsonapi": { + "version": "1.0" + }, + "data": { + "type": "user", + "id": "00000000-0000-0000-0000-000000000000", + "attributes": { + "name": "jane.doe@example.com", + "default_org_context": "00000000-0000-0000-0000-000000000000", + "username": "jane.doe@example.com", + "email": "jane.doe@example.com" + } + }, + "links": { + "self": "/self?version=2023-04-28~experimental" + } +} \ No newline at end of file