diff --git a/.circleci/config.yml b/.circleci/config.yml index b69be13..02154c7 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -6,7 +6,7 @@ orbs: go_image: &go_image resource_class: small docker: - - image: cimg/go:1.21 + - image: cimg/go:1.23 jobs: security-scans: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c82d569..45116f0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,7 +14,7 @@ jobs: - name: Set up Go uses: actions/setup-go@v3 with: - go-version: 1.20.3 + go-version: 1.23.2 - name: Lint uses: golangci/golangci-lint-action@v3 diff --git a/go.mod b/go.mod index 34de795..d723db5 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/snyk/parlay -go 1.20 +go 1.23 require ( github.com/CycloneDX/cyclonedx-go v0.9.0 diff --git a/go.sum b/go.sum index 602b739..7bbbf85 100644 --- a/go.sum +++ b/go.sum @@ -49,6 +49,7 @@ github.com/avast/retry-go v3.0.0+incompatible h1:4SOWQ7Qs+oroOTQOYnAHqelpCO0biHS github.com/avast/retry-go v3.0.0+incompatible/go.mod h1:XtSnn+n/sHqQIpZ10K1qAevBhOOCWBLXXy3hyiqqBrY= github.com/bmatcuk/doublestar v1.1.1/go.mod h1:UD6OnuiIn0yFxxA2le/rnRU1G4RaI4UvFv1sNto9p6w= github.com/bradleyjkemp/cupaloy/v2 v2.8.0 h1:any4BmKE+jGIaMpnU8YgH/I2LPiLBufr6oMMlVBbn9M= +github.com/bradleyjkemp/cupaloy/v2 v2.8.0/go.mod h1:bm7JXdkRd4BHJk9HpwqAI8BoAY1lps46Enkdqw6aRX0= github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= @@ -73,6 +74,7 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.3 h1:FJKSZTDHjyhriyC81FLQ0LY93eSai0ZyR/ZIkd3ZUKE= +github.com/frankban/quicktest v1.14.3/go.mod h1:mgiwOwqx65TmIk1wJ6Q7wvnVMocbUorkibMOrVTHZps= github.com/fsnotify/fsnotify v1.6.0 h1:n+5WquG0fcWoWp6xPWfHdbskMCQaFnG6PfBrh1Ky4HY= github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -155,9 +157,11 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY= github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0= github.com/mattn/go-colorable v0.1.12/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= @@ -167,6 +171,7 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g= +github.com/maxatome/go-testdeep v1.12.0/go.mod h1:lPZc/HAcJMP92l7yI6TRz1aZN5URwUBUAfUNvrclaNM= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4= @@ -182,6 +187,7 @@ github.com/remeh/sizedwaitgroup v1.0.0 h1:VNGGFwNo/R5+MJBf6yrsr110p0m4/OX4S3DCy7 github.com/remeh/sizedwaitgroup v1.0.0/go.mod h1:3j2R4OIe/SeS6YDhICBy22RWjJC5eNCJ1V+9+NVNYlo= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rs/xid v1.4.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= github.com/rs/zerolog v1.29.1 h1:cO+d60CHkknCbvzEWxP0S9K6KqyTjrCNUy1LdQLCGPc= github.com/rs/zerolog v1.29.1/go.mod h1:Le6ESbR7hc+DP6Lt1THiV8CQSdkkNrd3R0XbEgp3ZBU= @@ -220,9 +226,13 @@ github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8 github.com/subosito/gotenv v1.4.2 h1:X1TuBLAMDFbaTAChgCBLu3DU3UPyELpnF2jjJ2cz/S8= github.com/subosito/gotenv v1.4.2/go.mod h1:ayKnFf/c6rvx/2iiLrJUk1e6plDbT3edrFNGqEflhK0= github.com/terminalstatic/go-xsd-validate v0.1.5 h1:RqpJnf6HGE2CB/lZB1A8BYguk8uRtcvYAPLCF15qguo= +github.com/terminalstatic/go-xsd-validate v0.1.5/go.mod h1:18lsvYFofBflqCrvo1umpABZ99+GneNTw2kEEc8UPJw= github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f h1:J9EGpcZtP0E/raorCMxlFGSTBrsSlaDGf3jU/qvAE2c= +github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= +github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ= github.com/xeipuuv/gojsonschema v1.2.0 h1:LhYJRs+L4fBtjZUfuSZIKGeVu0QRy8e5Xi7D17UxZ74= +github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y= github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= diff --git a/lib/snyk/enrich_test.go b/lib/snyk/enrich_test.go index 93957d3..29f29b0 100644 --- a/lib/snyk/enrich_test.go +++ b/lib/snyk/enrich_test.go @@ -1,25 +1,32 @@ package snyk import ( + _ "embed" + "net/http" + "net/http/httptest" "testing" cdx "github.com/CycloneDX/cyclonedx-go" - "github.com/jarcoal/httpmock" "github.com/rs/zerolog" 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/stretchr/testify/require" "github.com/snyk/parlay/lib/sbom" ) -func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() +var ( + //go:embed testdata/numpy_issues.json + numpyIssues []byte + //go:embed testdata/pandas_issues.json + pandasIssues []byte + //go:embed testdata/no_issues.json + noIssues []byte +) - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) +func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) { + svc := setupTestEnv(t) bom := &cdx.BOM{ Components: &[]cdx.Component{ @@ -35,7 +42,7 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) { svc.EnrichSBOM(doc) - assert.NotNil(t, bom.Vulnerabilities) + require.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) @@ -43,12 +50,7 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) { } func TestEnrichSBOM_CycloneDXExternalRefs(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() - - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) + svc := setupTestEnv(t) bom := &cdx.BOM{ Components: &[]cdx.Component{ @@ -64,7 +66,7 @@ func TestEnrichSBOM_CycloneDXExternalRefs(t *testing.T) { svc.EnrichSBOM(doc) - assert.NotNil(t, bom.Components) + require.NotNil(t, bom.Components) refs := (*bom.Components)[0].ExternalReferences assert.Len(t, *refs, 2) @@ -80,12 +82,7 @@ func TestEnrichSBOM_CycloneDXExternalRefs(t *testing.T) { } func TestEnrichSBOM_CycloneDXExternalRefs_WithNamespace(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() - - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) + svc := setupTestEnv(t) bom := &cdx.BOM{ Components: &[]cdx.Component{ @@ -101,7 +98,7 @@ func TestEnrichSBOM_CycloneDXExternalRefs_WithNamespace(t *testing.T) { svc.EnrichSBOM(doc) - assert.NotNil(t, bom.Components) + require.NotNil(t, bom.Components) refs := (*bom.Components)[0].ExternalReferences assert.Len(t, *refs, 2) @@ -117,12 +114,7 @@ func TestEnrichSBOM_CycloneDXExternalRefs_WithNamespace(t *testing.T) { } func TestEnrichSBOM_CycloneDXWithVulnerabilities_NestedComponents(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() - - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) + svc := setupTestEnv(t) bom := &cdx.BOM{ Components: &[]cdx.Component{ @@ -146,17 +138,12 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities_NestedComponents(t *testing.T) svc.EnrichSBOM(doc) - assert.NotNil(t, bom.Vulnerabilities) + require.NotNil(t, bom.Vulnerabilities) assert.Len(t, *bom.Vulnerabilities, 2) } func TestEnrichSBOM_CycloneDXWithoutVulnerabilities(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() - - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) + svc := setupTestEnv(t) bom := &cdx.BOM{ Components: &[]cdx.Component{ @@ -176,12 +163,7 @@ func TestEnrichSBOM_CycloneDXWithoutVulnerabilities(t *testing.T) { } func TestEnrichSBOM_SPDXWithVulnerabilities(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() - - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) + svc := setupTestEnv(t) bom := &spdx_2_3.Document{ Packages: []*spdx_2_3.Package{ @@ -211,12 +193,7 @@ func TestEnrichSBOM_SPDXWithVulnerabilities(t *testing.T) { } func TestEnrichSBOM_SPDXExternalRefs(t *testing.T) { - teardown := setupTestEnv(t) - defer teardown() - - cfg := newTestConfig(t) - logger := zerolog.Nop() - svc := NewService(cfg, &logger) + svc := setupTestEnv(t) bom := &spdx_2_3.Document{ Packages: []*spdx_2_3.Package{ @@ -256,39 +233,51 @@ func TestEnrichSBOM_SPDXExternalRefs(t *testing.T) { assert.Equal(t, spdx.CategoryOther, ref2.Category) } -func setupTestEnv(t *testing.T) func() { +func setupTestEnv(t *testing.T) Service { t.Helper() - 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/pkg%3Apypi%2Fpandas%400.15.0/issues`, - httpmock.NewJsonResponderOrPanic(200, httpmock.File("testdata/pandas_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() - } + mux := http.NewServeMux() + + mux.HandleFunc( + "GET /rest/self", + func(w http.ResponseWriter, r *http.Request) { + respond(w, selfBody) + }) + + mux.HandleFunc( + "GET /rest/orgs/{org_id}/packages/{purl}/issues", + func(w http.ResponseWriter, r *http.Request) { + respond(w, noIssues) + }) + + mux.HandleFunc( + "GET /rest/orgs/{org_id}/packages/pkg%3Apypi%2Fnumpy%401.16.0/issues", + func(w http.ResponseWriter, r *http.Request) { + respond(w, numpyIssues) + }) + + mux.HandleFunc( + "GET /rest/orgs/{org_id}/packages/pkg%3Apypi%2Fpandas%400.15.0/issues", + func(w http.ResponseWriter, r *http.Request) { + respond(w, pandasIssues) + }) + + srv := httptest.NewServer(mux) + t.Cleanup(srv.Close) + + cfg := DefaultConfig() + cfg.APIToken = "asdf" + cfg.SnykAPIURL = srv.URL + + logger := zerolog.Nop() + svc := NewService(cfg, &logger) + + return svc } -func newTestConfig(t *testing.T) *Config { - t.Helper() - c := DefaultConfig() - c.APIToken = "asdf" - return c +func respond(w http.ResponseWriter, data []byte) { + w.Header().Set("content-type", "application/vnd.api+json") + if _, err := w.Write(data); err != nil { + panic(err) + } } diff --git a/lib/snyk/self_test.go b/lib/snyk/self_test.go index 605f93a..7b7fd5c 100644 --- a/lib/snyk/self_test.go +++ b/lib/snyk/self_test.go @@ -17,43 +17,51 @@ package snyk import ( + _ "embed" "net/http" + "net/http/httptest" "testing" "github.com/deepmap/oapi-codegen/pkg/securityprovider" "github.com/google/uuid" - "github.com/jarcoal/httpmock" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +//go:embed testdata/self.json +var selfBody []byte + func TestSnykOrgID_Success(t *testing.T) { - expectedOrg := uuid.MustParse("00000000-0000-0000-0000-000000000000") - auth, err := securityprovider.NewSecurityProviderApiKey("header", "name", "value") + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + respond(w, selfBody) + })) + defer srv.Close() + + cfg := DefaultConfig() + cfg.SnykAPIURL = srv.URL + auth, err := securityprovider.NewSecurityProviderApiKey("header", "authorization", "asdf") require.NoError(t, err) - httpmock.Activate() - defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("GET", "https://api.snyk.io/rest/self", - httpmock.NewJsonResponderOrPanic(http.StatusOK, httpmock.File("testdata/self.json")), - ) + actualOrg, err := SnykOrgID(cfg, auth) - actualOrg, err := SnykOrgID(DefaultConfig(), auth) assert.NoError(t, err) - assert.Equal(t, expectedOrg, *actualOrg) + assert.Equal(t, uuid.MustParse("00000000-0000-0000-0000-000000000000"), *actualOrg) } func TestSnykOrgID_Unauthorized(t *testing.T) { - auth, err := securityprovider.NewSecurityProviderApiKey("header", "name", "value") + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + w.WriteHeader(http.StatusUnauthorized) + respond(w, []byte(`{"msg":"unauthorized"}`)) + })) + defer srv.Close() + + cfg := DefaultConfig() + cfg.SnykAPIURL = srv.URL + auth, err := securityprovider.NewSecurityProviderApiKey("header", "authorization", "asdf") require.NoError(t, err) - httpmock.Activate() - defer httpmock.DeactivateAndReset() - httpmock.RegisterResponder("GET", "https://api.snyk.io/rest/self", - httpmock.NewJsonResponderOrPanic(http.StatusUnauthorized, []byte(`{"msg":"unauthorized"}`)), - ) + actualOrg, err := SnykOrgID(cfg, auth) - actualOrg, err := SnykOrgID(DefaultConfig(), auth) - assert.ErrorContains(t, err, "Failed to get user info (401)") + assert.ErrorContains(t, err, "Failed to get user info (401 Unauthorized)") assert.Nil(t, actualOrg) }