pkg/vul/convert/grype/grype.go (188 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 grype
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("matches").Exists() {
return nil, errors.New("unable to find vulnerabilities in source data")
}
list := make(types.NoteOccurrencesMap, 0)
for _, v := range s.Data.Search("matches").Children() {
// create note
n := convertNote(s, v)
// don't add notes with no CVSS score
if n == nil || n.GetVulnerability().CvssScore == 0 {
continue
}
noteID := utils.GetPrefixNoteName(n.GetShortDescription())
// 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)
if occ != nil {
nocc.Occurrences = append(nocc.Occurrences, occ)
}
list[noteID] = nocc
}
return list, nil
}
func convertOccurrence(s *utils.Source, v *gabs.Container, noteID string) *g.Occurrence {
noteName := fmt.Sprintf("projects/%s/notes/%s", s.Project, noteID)
// nvd vulnerability
rvList := v.Search("relatedVulnerabilities").Children()
var rv *gabs.Container
for _, rvNode := range rvList {
if rvNode.Search("namespace").Data().(string) == "nvd:cpe" {
rv = rvNode
break
}
}
if rv == nil {
return nil
}
cve := rv.Search("id").Data().(string)
// cvssv2
cvssList := rv.Search("cvss").Children()
var cvss2, cvss3 *gabs.Container
for _, cvss := range cvssList {
switch cvss.Search("version").Data().(string) {
case "2.0":
cvss2 = cvss
case "3.0", "3.1":
cvss3 = cvss
}
}
if cvss2 == 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: rv.Search("description").Data().(string),
RelatedUrls: []*g.RelatedUrl{
{
Label: "Registry",
Url: s.URI,
},
},
CvssVersion: g.CVSSVersion_CVSS_VERSION_2,
CvssScore: utils.ToFloat32(cvss2.Search("metrics", "baseScore").Data()),
Severity: utils.ToGrafeasSeverity(rv.Search("severity").Data().(string)),
// TODO: What is the difference between severity and effective severity?
EffectiveSeverity: utils.ToGrafeasSeverity(rv.Search("severity").Data().(string)),
}},
}
// PackageIssues
if len(v.Search("vulnerability", "fix", "versions").Children()) == 0 {
o.GetVulnerability().PackageIssue = append(
o.GetVulnerability().PackageIssue,
getBasePackageIssue(v))
} else {
for _, version := range v.Search("vulnerability", "fix", "versions").Children() {
pi := getBasePackageIssue(v)
pi.FixedVersion = &g.Version{
Name: version.Data().(string),
Kind: g.Version_NORMAL,
}
o.GetVulnerability().PackageIssue = append(o.GetVulnerability().PackageIssue, pi)
}
}
// CVSSv3
if cvss3 != nil {
o.GetVulnerability().Cvssv3 = utils.ToCVSS(
utils.ToFloat32(cvss3.Search("metrics", "baseScore").Data()),
cvss3.Search("vector").Data().(string),
)
}
// References
for _, r := range rv.Search("urls").Children() {
o.GetVulnerability().RelatedUrls = append(o.GetVulnerability().RelatedUrls, &g.RelatedUrl{
Url: r.Data().(string),
Label: "Url",
})
}
return &o
}
func convertNote(s *utils.Source, v *gabs.Container) *g.Note {
// nvd vulnerability
rvList := v.Search("relatedVulnerabilities").Children()
var rv *gabs.Container
for _, rvNode := range rvList {
if rvNode.Search("namespace").Data().(string) == "nvd:cpe" {
rv = rvNode
break
}
}
if rv == nil {
return nil
}
cve := rv.Search("id").Data().(string)
// cvssv2
cvssList := rv.Search("cvss").Children()
var cvss2, cvss3 *gabs.Container
for _, cvss := range cvssList {
switch cvss.Search("version").Data().(string) {
case "2.0":
cvss2 = cvss
case "3.0", "3.1":
cvss3 = cvss
}
}
if cvss2 == nil {
return nil
}
// create note
n := g.Note{
ShortDescription: cve,
LongDescription: rv.Search("description").Data().(string),
RelatedUrl: []*g.RelatedUrl{
{
Label: "Registry",
Url: s.URI,
},
},
Type: &g.Note_Vulnerability{
Vulnerability: &g.VulnerabilityNote{
CvssVersion: g.CVSSVersion_CVSS_VERSION_2,
CvssScore: utils.ToFloat32(cvss2.Search("metrics", "baseScore").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(rv.Search("severity").Data().(string)),
},
},
} // end note
// CVSSv3
if cvss3 != nil {
n.GetVulnerability().CvssV3 = utils.ToCVSSv3(
utils.ToFloat32(cvss3.Search("metrics", "baseScore").Data()),
cvss3.Search("vector").Data().(string),
)
}
// References
for _, r := range rv.Search("urls").Children() {
n.RelatedUrl = append(n.RelatedUrl, &g.RelatedUrl{
Url: r.Data().(string),
Label: "Url",
})
}
return &n
}
func getBasePackageIssue(v *gabs.Container) *g.VulnerabilityOccurrence_PackageIssue {
return &g.VulnerabilityOccurrence_PackageIssue{
PackageType: utils.ParsePackageType(v.Search("artifact", "language").Data().(string)),
AffectedCpeUri: v.Search("artifact", "cpes").Index(0).Data().(string),
AffectedPackage: v.Search("artifact", "name").Data().(string),
AffectedVersion: &g.Version{
Name: v.Search("artifact", "version").Data().(string),
Kind: g.Version_NORMAL,
},
FixedCpeUri: v.Search("artifact", "cpes").Index(0).Data().(string),
FixedPackage: v.Search("artifact", "name").Data().(string),
FixedVersion: &g.Version{
Kind: g.Version_MAXIMUM,
},
}
}