pkg/vul/convert/snyk/snyk.go (149 lines of code) (raw):

// Copyright 2023 Google LLC // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. package snyk import ( "fmt" "github.com/GoogleCloudPlatform/aactl/pkg/types" "github.com/GoogleCloudPlatform/aactl/pkg/utils" "github.com/Jeffail/gabs/v2" "github.com/pkg/errors" g "google.golang.org/genproto/googleapis/grafeas/v1" ) // Convert converts Snyk JSON to Grafeas Note/Occurrence format. func Convert(s *utils.Source) (types.NoteOccurrencesMap, error) { if s == nil || s.Data == nil { return nil, errors.New("valid source required") } if !s.Data.Search("vulnerabilities").Exists() { return nil, errors.New("unable to find vulnerabilities in source data") } list := make(types.NoteOccurrencesMap, 0) for _, v := range s.Data.Search("vulnerabilities").Children() { cve := v.Search("identifiers", "CVE").Index(0).Data().(string) noteID := utils.GetPrefixNoteName(cve) // create note n := convertNote(s, v) // don't add notes with no CVSS score if n == nil || n.GetVulnerability().CvssScore == 0 { continue } // If cve is not found, add to map if _, ok := list[noteID]; !ok { list[noteID] = types.NoteOccurrences{Note: n} } nocc := list[noteID] occ := convertOccurrence(s, v, noteID) nocc.Occurrences = append(nocc.Occurrences, occ) list[noteID] = nocc } return list, nil } func convertNote(s *utils.Source, v *gabs.Container) *g.Note { cve := v.Search("identifiers", "CVE").Index(0).Data().(string) // Get cvss3 details from NVD var cvss3 *gabs.Container for _, detail := range v.Search("cvssDetails").Children() { if utils.ToString(detail.Search("assigner").Data()) == "NVD" { cvss3 = detail } } if cvss3 == nil { return nil } // create note n := g.Note{ ShortDescription: cve, LongDescription: utils.ToString(v.Search("CVSSv3").Data()), RelatedUrl: []*g.RelatedUrl{ { Label: "Registry", Url: s.URI, }, }, Type: &g.Note_Vulnerability{ Vulnerability: &g.VulnerabilityNote{ CvssVersion: g.CVSSVersion_CVSS_VERSION_3, CvssScore: utils.ToFloat32(cvss3.Search("cvssV3BaseScore").Data()), // Details in Notes are not populated since we will never see the full list Details: []*g.VulnerabilityNote_Detail{ { AffectedCpeUri: "N/A", AffectedPackage: "N/A", }, }, Severity: utils.ToGrafeasSeverity(v.Search("nvdSeverity").Data().(string)), SourceUpdateTime: utils.ToGRPCTime(cvss3.Search("modificationTime").Data()), }, }, } // end note // CVSSv3 if cvss3.Search("cvssV3Vector").Data() != nil { n.GetVulnerability().CvssV3 = utils.ToCVSSv3( utils.ToFloat32(cvss3.Search("cvssV3BaseScore").Data()), cvss3.Search("cvssV3Vector").Data().(string), ) } // References for _, r := range v.Search("references").Children() { n.RelatedUrl = append(n.RelatedUrl, &g.RelatedUrl{ Url: r.Search("url").Data().(string), Label: r.Search("title").Data().(string), }) } return &n } func convertOccurrence(s *utils.Source, v *gabs.Container, noteID string) *g.Occurrence { cve := v.Search("identifiers", "CVE").Index(0).Data().(string) noteName := fmt.Sprintf("projects/%s/notes/%s", s.Project, noteID) // Get cvss3 details from NVD var cvss3 *gabs.Container for _, detail := range v.Search("cvssDetails").Children() { if utils.ToString(detail.Search("assigner").Data()) == "NVD" { cvss3 = detail } } if cvss3 == nil { return nil } // Create Occurrence o := g.Occurrence{ ResourceUri: fmt.Sprintf("https://%s", s.URI), NoteName: noteName, Details: &g.Occurrence_Vulnerability{ Vulnerability: &g.VulnerabilityOccurrence{ ShortDescription: cve, LongDescription: utils.ToString(v.Search("CVSSv3").Data()), RelatedUrls: []*g.RelatedUrl{ { Label: "Registry", Url: s.URI, }, }, CvssVersion: g.CVSSVersion_CVSS_VERSION_3, CvssScore: utils.ToFloat32(cvss3.Search("cvssV3BaseScore").Data()), // TODO: Set PackageType PackageIssue: []*g.VulnerabilityOccurrence_PackageIssue{{ AffectedCpeUri: makeCPE(v), AffectedPackage: v.Search("packageName").Data().(string), AffectedVersion: &g.Version{ Name: v.Search("version").Data().(string), Kind: g.Version_NORMAL, }, FixedCpeUri: makeCPE(v), FixedPackage: v.Search("packageName").Data().(string), FixedVersion: &g.Version{ Kind: g.Version_MAXIMUM, }, }}, Severity: utils.ToGrafeasSeverity(v.Search("nvdSeverity").Data().(string)), // TODO: What is the difference between severity and effective severity? EffectiveSeverity: utils.ToGrafeasSeverity(v.Search("nvdSeverity").Data().(string)), }}, } // CVSSv3 if cvss3.Search("cvssV3Vector").Data() != nil { o.GetVulnerability().Cvssv3 = utils.ToCVSS( utils.ToFloat32(cvss3.Search("cvssV3BaseScore").Data()), cvss3.Search("cvssV3Vector").Data().(string), ) } // References for _, r := range v.Search("references").Children() { o.GetVulnerability().RelatedUrls = append(o.GetVulnerability().RelatedUrls, &g.RelatedUrl{ Url: r.Search("url").Data().(string), Label: r.Search("title").Data().(string), }) } return &o } // makeCPE creates CPE from Snyk data as the OSS CLI does not generate CPEs // NOTE: This is for demo purposes only and is not a complete CPE generator // Ref: https://en.wikipedia.org/wiki/Common_Platform_Enumeration // Schema: cpe:2.3:a:<vendor>:<product>:<version>:<update>:<edition>:<language>:<sw_edition>:<target_sw>:<target_hw>:<other> func makeCPE(v *gabs.Container) string { pkgName := v.Search("name").Data().(string) pkgVersion := v.Search("version").Data().(string) return fmt.Sprintf("cpe:2.3:a:%s:%s:%s:*:*:*:*:*:*:*", pkgName, pkgName, pkgVersion) }