Skip to content

Commit

Permalink
feat: snyk enrich external refs (#62)
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrosca-snyk authored Mar 5, 2024
1 parent 419eda3 commit 04ba00b
Show file tree
Hide file tree
Showing 8 changed files with 276 additions and 17 deletions.
6 changes: 3 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@ require (
github.com/edoardottt/depsdev v0.0.3
github.com/google/uuid v1.3.0
github.com/jarcoal/httpmock v1.3.0
github.com/package-url/packageurl-go v0.1.2-0.20230717211154-3587d8c2829e
github.com/package-url/packageurl-go v0.1.2
github.com/remeh/sizedwaitgroup v1.0.0
github.com/rs/zerolog v1.29.1
github.com/spdx/tools-golang v0.5.2
github.com/spdx/tools-golang v0.5.4-0.20240304222056-8baafa1a79c4
github.com/spf13/cobra v1.7.0
github.com/spf13/viper v1.15.0
github.com/stretchr/testify v1.8.4
github.com/stretchr/testify v1.9.0
)

require (
Expand Down
11 changes: 9 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/
github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
Expand Down Expand Up @@ -168,8 +169,8 @@ github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/
github.com/maxatome/go-testdeep v1.12.0 h1:Ql7Go8Tg0C1D/uMMX59LAoYK7LffeJQ6X2T04nTH68g=
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-0.20230717211154-3587d8c2829e h1:h/TWC+mVfoco4qhPEsaxkdwymlTaDe/BGnzljU8SIPw=
github.com/package-url/packageurl-go v0.1.2-0.20230717211154-3587d8c2829e/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
github.com/package-url/packageurl-go v0.1.2 h1:0H2DQt6DHd/NeRlVwW4EZ4oEI6Bn40XlNPRqegcxuo4=
github.com/package-url/packageurl-go v0.1.2/go.mod h1:uQd4a7Rh3ZsVg5j0lNyAfyxIeGde9yrlhjF78GzeW0c=
github.com/pelletier/go-toml/v2 v2.0.6 h1:nrzqCb7j9cDFj2coyLNLaZuJTLjWjlaz6nvTvIwycIU=
github.com/pelletier/go-toml/v2 v2.0.6/go.mod h1:eumQOmlWiOPt5WriQQqoM5y18pDHwha2N+QD+EUNTek=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
Expand All @@ -188,6 +189,8 @@ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQD
github.com/spdx/gordf v0.0.0-20201111095634-7098f93598fb/go.mod h1:uKWaldnbMnjsSAXRurWqqrdyZen1R7kxl8TkmWk2OyM=
github.com/spdx/tools-golang v0.5.2 h1:dtMNjJreWPe37584ajk7m/rQtfJaLpRMk7pUGgvekOg=
github.com/spdx/tools-golang v0.5.2/go.mod h1:/ETOahiAo96Ob0/RAIBmFZw6XN0yTnyr/uFZm2NTMhI=
github.com/spdx/tools-golang v0.5.4-0.20240304222056-8baafa1a79c4 h1:h1iNkxAggQH5lpDxHslTTB3Y61XN2G/rjA/n/TAIwFg=
github.com/spdx/tools-golang v0.5.4-0.20240304222056-8baafa1a79c4/go.mod h1:MVIsXx8ZZzaRWNQpUDhC4Dud34edUYJYecciXgrw5vE=
github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk=
github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y=
github.com/spf13/cast v1.5.0 h1:rj3WzYc11XZaIZMPKmwP96zkFEnnAmV8s6XbB2aY32w=
Expand All @@ -204,6 +207,7 @@ github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKk
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
Expand All @@ -214,6 +218,8 @@ github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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=
Expand Down Expand Up @@ -533,3 +539,4 @@ rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
sigs.k8s.io/yaml v1.3.0/go.mod h1:GeOyir5tyXNByN85N/dRIT9es5UQNerPYEKK56eTBm8=
sigs.k8s.io/yaml v1.4.0/go.mod h1:Ejl7/uTz7PSA4eKMyQCUTnhZYNmLIl+5c2lQPGR2BPY=
2 changes: 1 addition & 1 deletion lib/scorecard/enrich_spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func enrichSPDX(bom *spdx.Document) {
}

pkg.PackageExternalReferences = append(pkg.PackageExternalReferences, &spdx_2_3.PackageExternalReference{
Category: "OTHER",
Category: spdx.CategoryOther,
RefType: "openssfscorecard",
Locator: scURL,
})
Expand Down
4 changes: 2 additions & 2 deletions lib/scorecard/enrich_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ func TestEnrichSBOM_SPDX(t *testing.T) {
{
PackageExternalReferences: []*spdx_2_3.PackageExternalReference{
{
Category: "OTHER",
Category: spdx.CategoryOther,
RefType: "purl",
Locator: "pkg:golang/snyk/parlay",
},
Expand All @@ -191,7 +191,7 @@ func TestEnrichSBOM_SPDX(t *testing.T) {
scRef := pkg.PackageExternalReferences[1]
assert.Equal(t, scorecardURL, scRef.Locator)
assert.Equal(t, "openssfscorecard", scRef.RefType)
assert.Equal(t, "OTHER", scRef.Category)
assert.Equal(t, spdx.CategoryOther, scRef.Category)
}

func setupEcosystemsAPIMock(t *testing.T) func() {
Expand Down
43 changes: 42 additions & 1 deletion lib/snyk/enrich_cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,45 @@ import (
"github.com/snyk/parlay/snyk/issues"
)

type cdxEnricher = func(*cdx.Component, *packageurl.PackageURL)

var cdxEnrichers = []cdxEnricher{
enrichCDXSnykAdvisorData,
enrichCDXSnykVulnerabilityDBData,
}

func enrichCDXSnykVulnerabilityDBData(component *cdx.Component, purl *packageurl.PackageURL) {
url := SnykVulnURL(purl)
if url != "" {
ext := cdx.ExternalReference{
URL: url,
Comment: "Snyk Vulnerability DB",
Type: "Other",
}
if component.ExternalReferences == nil {
component.ExternalReferences = &[]cdx.ExternalReference{ext}
} else {
*component.ExternalReferences = append(*component.ExternalReferences, ext)
}
}
}

func enrichCDXSnykAdvisorData(component *cdx.Component, purl *packageurl.PackageURL) {
url := SnykAdvisorURL(purl)
if url != "" {
ext := cdx.ExternalReference{
URL: url,
Comment: "Snyk Advisor",
Type: "Other",
}
if component.ExternalReferences == nil {
component.ExternalReferences = &[]cdx.ExternalReference{ext}
} else {
*component.ExternalReferences = append(*component.ExternalReferences, ext)
}
}
}

func enrichCycloneDX(bom *cdx.BOM, logger *zerolog.Logger) *cdx.BOM {
auth, err := AuthFromToken(APIToken())
if err != nil {
Expand Down Expand Up @@ -65,7 +104,9 @@ func enrichCycloneDX(bom *cdx.BOM, logger *zerolog.Logger) *cdx.BOM {
Msg("Could not identify package")
return
}

for _, enrichFunc := range cdxEnrichers {
enrichFunc(component, &purl)
}
resp, err := GetPackageVulnerabilities(&purl, auth, orgID)
if err != nil {
l.Err(err).
Expand Down
46 changes: 45 additions & 1 deletion lib/snyk/enrich_spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import (
"net/url"
"sync"

"github.com/package-url/packageurl-go"
"github.com/remeh/sizedwaitgroup"
"github.com/rs/zerolog"
"github.com/spdx/tools-golang/spdx"
Expand All @@ -35,6 +36,47 @@ const (
snykVulnerabilityDB_URI = "https://security.snyk.io"
)

type spdxEnricher = func(*spdx_2_3.Package, *packageurl.PackageURL)

var spdxEnrichers = []spdxEnricher{
enrichSPDXSnykAdvisorData,
enrichSPDXSnykVulnerabilityDBData,
}

func enrichSPDXSnykAdvisorData(component *spdx_2_3.Package, purl *packageurl.PackageURL) {
url := SnykAdvisorURL(purl)
if url != "" {
ext := &spdx_2_3.PackageExternalReference{
Locator: url,
RefType: "advisory",
Category: spdx.CategoryOther,
ExternalRefComment: "Snyk Advisor",
}
if component.PackageExternalReferences == nil {
component.PackageExternalReferences = []*spdx_2_3.PackageExternalReference{ext}
} else {
component.PackageExternalReferences = append(component.PackageExternalReferences, ext)
}
}
}

func enrichSPDXSnykVulnerabilityDBData(component *spdx_2_3.Package, purl *packageurl.PackageURL) {
url := SnykVulnURL(purl)
if url != "" {
ext := &spdx_2_3.PackageExternalReference{
Locator: url,
RefType: "url",
Category: spdx.CategoryOther,
ExternalRefComment: "Snyk Vulnerability DB",
}
if component.PackageExternalReferences == nil {
component.PackageExternalReferences = []*spdx_2_3.PackageExternalReference{ext}
} else {
component.PackageExternalReferences = append(component.PackageExternalReferences, ext)
}
}
}

func enrichSPDX(bom *spdx.Document, logger *zerolog.Logger) *spdx.Document {
auth, err := AuthFromToken(APIToken())
if err != nil {
Expand Down Expand Up @@ -71,7 +113,9 @@ func enrichSPDX(bom *spdx.Document, logger *zerolog.Logger) *spdx.Document {
l.Debug().Msg("Could not identify package")
return
}

for _, enrichFn := range spdxEnrichers {
enrichFn(pkg, purl)
}
resp, err := GetPackageVulnerabilities(purl, auth, orgID)
if err != nil {
l.Err(err).
Expand Down
121 changes: 116 additions & 5 deletions lib/snyk/enrich_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) {
},
}
doc := &sbom.SBOMDocument{BOM: bom}
logger := zerolog.Nop()

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

assert.NotNil(t, bom.Vulnerabilities)
Expand All @@ -39,6 +39,74 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities(t *testing.T) {
assert.Equal(t, "SNYK-PYTHON-NUMPY-73513", vuln.ID)
}

func TestEnrichSBOM_CycloneDXExternalRefs(t *testing.T) {
teardown := setupTestEnv(t)
defer teardown()

bom := &cdx.BOM{
Components: &[]cdx.Component{
{
BOMRef: "pkg:pypi/[email protected]",
Name: "numpy",
Version: "1.16.0",
PackageURL: "pkg:pypi/[email protected]",
},
},
}
doc := &sbom.SBOMDocument{BOM: bom}

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

assert.NotNil(t, bom.Components)
refs := (*bom.Components)[0].ExternalReferences
assert.Len(t, *refs, 2)

ref1 := (*refs)[0]
assert.Equal(t, "https://snyk.io/advisor/python/numpy", ref1.URL)
assert.Equal(t, "Snyk Advisor", ref1.Comment)
assert.Equal(t, cdx.ExternalReferenceType("Other"), ref1.Type)

ref2 := (*refs)[1]
assert.Equal(t, "https://security.snyk.io/package/pip/numpy", ref2.URL)
assert.Equal(t, "Snyk Vulnerability DB", ref2.Comment)
assert.Equal(t, cdx.ExternalReferenceType("Other"), ref2.Type)
}

func TestEnrichSBOM_CycloneDXExternalRefs_WithNamespace(t *testing.T) {
teardown := setupTestEnv(t)
defer teardown()

bom := &cdx.BOM{
Components: &[]cdx.Component{
{
BOMRef: "@emotion/[email protected]",
Name: "react",
Version: "11.11.3",
PackageURL: "pkg:npm/%40emotion/[email protected]",
},
},
}
doc := &sbom.SBOMDocument{BOM: bom}

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

assert.NotNil(t, bom.Components)
refs := (*bom.Components)[0].ExternalReferences
assert.Len(t, *refs, 2)

ref1 := (*refs)[0]
assert.Equal(t, "https://snyk.io/advisor/npm-package/@emotion/react", ref1.URL)
assert.Equal(t, "Snyk Advisor", ref1.Comment)
assert.Equal(t, cdx.ExternalReferenceType("Other"), ref1.Type)

ref2 := (*refs)[1]
assert.Equal(t, "https://security.snyk.io/package/npm/@emotion%2Freact", ref2.URL)
assert.Equal(t, "Snyk Vulnerability DB", ref2.Comment)
assert.Equal(t, cdx.ExternalReferenceType("Other"), ref2.Type)
}

func TestEnrichSBOM_CycloneDXWithVulnerabilities_NestedComponents(t *testing.T) {
teardown := setupTestEnv(t)
defer teardown()
Expand All @@ -62,8 +130,8 @@ func TestEnrichSBOM_CycloneDXWithVulnerabilities_NestedComponents(t *testing.T)
},
}
doc := &sbom.SBOMDocument{BOM: bom}
logger := zerolog.Nop()

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

assert.NotNil(t, bom.Vulnerabilities)
Expand All @@ -85,8 +153,8 @@ func TestEnrichSBOM_CycloneDXWithoutVulnerabilities(t *testing.T) {
},
}
doc := &sbom.SBOMDocument{BOM: bom}
logger := zerolog.Nop()

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

assert.Nil(t, bom.Vulnerabilities, "should not extend vulnerabilities if there are none")
Expand All @@ -113,17 +181,60 @@ func TestEnrichSBOM_SPDXWithVulnerabilities(t *testing.T) {
},
}
doc := &sbom.SBOMDocument{BOM: bom}
logger := zerolog.Nop()

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

vulnRef := bom.Packages[0].PackageExternalReferences[1]
vulnRef := bom.Packages[0].PackageExternalReferences[3]
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 TestEnrichSBOM_SPDXExternalRefs(t *testing.T) {
teardown := setupTestEnv(t)
defer teardown()

bom := &spdx_2_3.Document{
Packages: []*spdx_2_3.Package{
{
PackageSPDXIdentifier: "pkg:pypi/[email protected]",
PackageName: "numpy",
PackageVersion: "1.16.0",
PackageExternalReferences: []*spdx_2_3.PackageExternalReference{
{
Category: spdx.CategoryPackageManager,
RefType: "purl",
Locator: "pkg:pypi/[email protected]",
},
},
},
},
}

doc := &sbom.SBOMDocument{BOM: bom}

logger := zerolog.Nop()
EnrichSBOM(doc, &logger)

assert.NotNil(t, bom.Packages)
refs := (*bom.Packages[0]).PackageExternalReferences
assert.Len(t, refs, 4)

ref1 := refs[1]
assert.Equal(t, "https://snyk.io/advisor/python/numpy", ref1.Locator)
assert.Equal(t, "Snyk Advisor", ref1.ExternalRefComment)
assert.Equal(t, "advisory", ref1.RefType)
assert.Equal(t, spdx.CategoryOther, ref1.Category)

ref2 := refs[2]
assert.Equal(t, "https://security.snyk.io/package/pip/numpy", ref2.Locator)
assert.Equal(t, "Snyk Vulnerability DB", ref2.ExternalRefComment)
assert.Equal(t, "url", ref2.RefType)
assert.Equal(t, spdx.CategoryOther, ref2.Category)
}

func setupTestEnv(t *testing.T) func() {
t.Helper()

Expand Down
Loading

0 comments on commit 04ba00b

Please sign in to comment.