pkg/vul/convert/trivy/trivy.go (172 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 trivy
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 Trivy JSON to Grafeas Note/Occurrence format.
func Convert(s *utils.Source) (types.NoteOccurrencesMap, error) {
if s == nil || s.Data == nil {
return nil, types.ErrInvalidSource
}
if !s.Data.Search("Results").Exists() {
return nil, errors.New("unable to find Results in source data")
}
list := make(types.NoteOccurrencesMap, 0)
for _, r := range s.Data.Search("Results").Children() {
for _, v := range r.Search("Vulnerabilities").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, getPackageType(r))
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("VulnerabilityID").Data().(string)
if v.Search("CVSS", "nvd").Data() == nil {
return nil
}
nvd := v.Search("CVSS", "nvd")
n := g.Note{
ShortDescription: cve,
RelatedUrl: []*g.RelatedUrl{
{
Label: "Registry",
Url: s.URI,
},
{
Label: "PrimaryURL",
Url: v.Search("PrimaryURL").Data().(string),
},
},
Type: &g.Note_Vulnerability{
Vulnerability: &g.VulnerabilityNote{
// 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("Severity").Data().(string)),
SourceUpdateTime: utils.ToGRPCTime(v.Search("LastModifiedDate").Data()),
},
},
} // end note
// CVSSv2
if nvd.Search("V2Vector").Data() != nil {
n.LongDescription = nvd.Search("V2Vector").Data().(string)
n.GetVulnerability().CvssVersion = g.CVSSVersion_CVSS_VERSION_2
n.GetVulnerability().CvssScore = utils.ToFloat32(nvd.Search("V2Score").Data())
}
// CVSSv3, will override v2 values
if nvd.Search("V3Vector").Data() != nil {
n.LongDescription = nvd.Search("V3Vector").Data().(string)
n.GetVulnerability().CvssVersion = g.CVSSVersion_CVSS_VERSION_3
n.GetVulnerability().CvssScore = utils.ToFloat32(nvd.Search("V3Score").Data())
n.GetVulnerability().CvssV3 = utils.ToCVSSv3(
utils.ToFloat32(nvd.Search("V3Score").Data()),
nvd.Search("V3Vector").Data().(string),
)
}
// References
for _, r := range v.Search("References").Children() {
n.RelatedUrl = append(n.RelatedUrl, &g.RelatedUrl{
Url: r.Data().(string),
Label: "Url",
})
}
return &n
}
// convertOccurrence converts Trivy JSON to Grafeas Occurrence format.
func convertOccurrence(s *utils.Source, v *gabs.Container, noteID string, packageType string) *g.Occurrence {
cve := v.Search("VulnerabilityID").Data().(string)
noteName := fmt.Sprintf("projects/%s/notes/%s", s.Project, noteID)
if v.Search("CVSS", "nvd").Data() == nil {
return nil
}
nvd := v.Search("CVSS", "nvd")
// Create Occurrence
o := g.Occurrence{
ResourceUri: fmt.Sprintf("https://%s", s.URI),
NoteName: noteName,
Details: &g.Occurrence_Vulnerability{
Vulnerability: &g.VulnerabilityOccurrence{
ShortDescription: cve,
RelatedUrls: []*g.RelatedUrl{
{
Label: "Registry",
Url: s.URI,
},
{
Label: "PrimaryURL",
Url: v.Search("PrimaryURL").Data().(string),
},
},
CvssScore: utils.ToFloat32(v.Search("CVSS", "nvd", "V2Score").Data()),
Severity: utils.ToGrafeasSeverity(v.Search("Severity").Data().(string)),
// TODO: What is the difference between severity and effective severity?
EffectiveSeverity: utils.ToGrafeasSeverity(v.Search("Severity").Data().(string)),
}},
}
// PackageIssues
o.GetVulnerability().PackageIssue = append(
o.GetVulnerability().PackageIssue,
getBasePackageIssue(v, packageType))
// CVSSv2
if nvd.Search("V2Vector").Data() != nil {
o.GetVulnerability().LongDescription = nvd.Search("V2Vector").Data().(string)
o.GetVulnerability().CvssVersion = g.CVSSVersion_CVSS_VERSION_2
o.GetVulnerability().CvssScore = utils.ToFloat32(nvd.Search("V2Score").Data())
}
// CVSSv3, will override v2 values
if nvd.Search("V3Vector").Data() != nil {
o.GetVulnerability().LongDescription = nvd.Search("V3Vector").Data().(string)
o.GetVulnerability().CvssVersion = g.CVSSVersion_CVSS_VERSION_3
o.GetVulnerability().CvssScore = utils.ToFloat32(nvd.Search("V3Score").Data())
o.GetVulnerability().Cvssv3 = utils.ToCVSS(
utils.ToFloat32(nvd.Search("V3Score").Data()),
nvd.Search("V3Vector").Data().(string),
)
}
// References
for _, r := range v.Search("References").Children() {
o.GetVulnerability().RelatedUrls = append(o.GetVulnerability().RelatedUrls, &g.RelatedUrl{
Url: r.Data().(string),
Label: "Url",
})
}
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 {
src := v.Search("SeveritySource").Data().(string)
pkgName := v.Search("PkgName").Data().(string)
pkgVersion := v.Search("InstalledVersion").Data().(string)
return fmt.Sprintf("cpe:2.3:a:%s:%s:%s:*:*:*:*:*:*:*",
src,
pkgName,
pkgVersion)
}
// getPackageType returns the package type based on the Trivy Class/Type combination.
// If the Class is not lang-pkgs, then it is an OS package.
// Else, use utils.ParsePackageType to determine the package type.
func getPackageType(r *gabs.Container) string {
if r.Search("Class").Data().(string) != "lang-pkgs" {
return "OS"
}
return utils.ParsePackageType(r.Search("Type").Data().(string))
}
func getBasePackageIssue(v *gabs.Container, packageType string) *g.VulnerabilityOccurrence_PackageIssue {
return &g.VulnerabilityOccurrence_PackageIssue{
PackageType: packageType,
AffectedCpeUri: makeCPE(v),
AffectedPackage: v.Search("PkgName").Data().(string),
AffectedVersion: &g.Version{
Name: v.Search("InstalledVersion").Data().(string),
Kind: g.Version_NORMAL,
},
FixedCpeUri: makeCPE(v),
FixedPackage: v.Search("PkgName").Data().(string),
FixedVersion: &g.Version{
Kind: g.Version_MAXIMUM,
},
}
}