Skip to content

Commit

Permalink
feat: snyk enrich external refs
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrosca-snyk committed Mar 4, 2024
1 parent 3b2986a commit b409492
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 8 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ 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
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -168,8 +168,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 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 @@ -60,7 +99,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 {
logger.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: "Other",
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: "Other",
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 @@ -65,7 +107,9 @@ func enrichSPDX(bom *spdx.Document, logger zerolog.Logger) *spdx.Document {
Msg("Could not identify package.")
return
}

for _, enrichFn := range spdxEnrichers {
enrichFn(pkg, *purl)
}
resp, err := GetPackageVulnerabilities(purl, auth, orgID)
if err != nil {
logger.Err(err).
Expand Down
113 changes: 112 additions & 1 deletion lib/snyk/enrich_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 Down Expand Up @@ -117,13 +185,56 @@ func TestEnrichSBOM_SPDXWithVulnerabilities(t *testing.T) {

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, "Other", 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, "Other", ref2.Category)
}

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

Expand Down
60 changes: 58 additions & 2 deletions lib/snyk/package.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,64 @@ import (
"github.com/snyk/parlay/snyk/issues"
)

const snykServer = "https://api.snyk.io/rest"
const version = "2023-04-28"
const (
snykServer = "https://api.snyk.io/rest"
version = "2023-04-28"
snykAdvisorServer = "https://snyk.io/advisor"
snykVulnDBServer = "https://security.snyk.io/package"
)

func purlToSnykAdvisor(purl packageurl.PackageURL) string {
return map[string]string{
packageurl.TypeNPM: "npm-package",
packageurl.TypePyPi: "python",
packageurl.TypeGolang: "golang",
packageurl.TypeDocker: "docker",
}[purl.Type]
}

func SnykAdvisorURL(purl packageurl.PackageURL) string {
ecosystem := purlToSnykAdvisor(purl)
if ecosystem == "" {
return ""
}
url := snykAdvisorServer + "/" + ecosystem + "/"
if purl.Namespace != "" {
url += purl.Namespace + "/"
}
url += purl.Name
return url
}

func purlToSnykVulnDB(purl packageurl.PackageURL) string {
return map[string]string{
packageurl.TypeCargo: "cargo",
packageurl.TypeCocoapods: "cocoapods",
packageurl.TypeComposer: "composer",
packageurl.TypeGolang: "golang",
packageurl.TypeHex: "hex",
packageurl.TypeMaven: "maven",
packageurl.TypeNPM: "npm",
packageurl.TypeNuget: "nuget",
packageurl.TypePyPi: "pip",
packageurl.TypePub: "pub",
packageurl.TypeGem: "rubygems",
packageurl.TypeSwift: "swift",
}[purl.Type]
}

func SnykVulnURL(purl packageurl.PackageURL) string {
ecosystem := purlToSnykVulnDB(purl)
if ecosystem == "" {
return ""
}
url := snykVulnDBServer + "/" + ecosystem + "/"
if purl.Namespace != "" {
url += purl.Namespace + "%2F"
}
url += purl.Name
return url
}

func GetPackageVulnerabilities(purl *packageurl.PackageURL, auth *securityprovider.SecurityProviderApiKey, orgID *uuid.UUID) (*issues.FetchIssuesPerPurlResponse, error) {
client, err := issues.NewClientWithResponses(snykServer, issues.WithRequestEditorFn(auth.Intercept))
Expand Down

0 comments on commit b409492

Please sign in to comment.