Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions pkg/data/transformers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ type OSTransformer func(entry unmarshal.OSVulnerability) ([]Entry, error)
type MatchExclusionTransformer func(entry unmarshal.MatchExclusion) ([]Entry, error)

// all v2 transformers (schema v6+)

type GitHubTransformerV2 func(entry unmarshal.GitHubAdvisory, state provider.State) ([]Entry, error)
type MSRCTransformerV2 func(entry unmarshal.MSRCVulnerability, state provider.State) ([]Entry, error)
type NVDTransformerV2 func(entry unmarshal.NVDVulnerability, state provider.State) ([]Entry, error)
Expand All @@ -28,3 +29,4 @@ type KnownExploitedVulnerabilityTransformerV2 func(entry unmarshal.KnownExploite
type EPSSTransformerV2 func(entry unmarshal.EPSS, state provider.State) ([]Entry, error)
type OSVTransformerV2 func(entry unmarshal.OSVVulnerability, state provider.State) ([]Entry, error)
type OpenVEXTransformerV2 func(entry unmarshal.OpenVEXVulnerability, state provider.State) ([]Entry, error)
type AnnotatedOpenVEXTransformerV2 func(entry unmarshal.AnnotatedOpenVEXVulnerability, state provider.State) ([]Entry, error)
54 changes: 54 additions & 0 deletions pkg/process/processors/annotated_openvex_processor.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package processors // nolint:dupl

import (
"io"

"github.com/anchore/grype-db/internal/log"
"github.com/anchore/grype-db/pkg/data"
"github.com/anchore/grype-db/pkg/provider"
"github.com/anchore/grype-db/pkg/provider/unmarshal"
)

type annotatedOpenVEXProcessor struct {
transformer data.AnnotatedOpenVEXTransformerV2
}

func NewV2AnnotatedOpenVEXProcessor(transformer data.AnnotatedOpenVEXTransformerV2) data.Processor {
return &annotatedOpenVEXProcessor{
transformer: transformer,
}
}

func (p annotatedOpenVEXProcessor) Process(reader io.Reader, state provider.State) ([]data.Entry, error) {
var results []data.Entry

entries, err := unmarshal.AnnotatedOpenVEXVulnerabilityEntries(reader)
if err != nil {
return nil, err
}

for _, entry := range entries {
transformedEntries, err := p.transformer(entry, state)
if err != nil {
return nil, err
}

results = append(results, transformedEntries...)
}

return results, nil
}

func (p annotatedOpenVEXProcessor) IsSupported(schemaURL string) bool {
if !hasSchemaSegment(schemaURL, "annotated-openvex") {
return false
}

parsedVersion, err := parseVersion(schemaURL)
if err != nil {
log.WithFields("schema", schemaURL, "error", err).Error("failed to parse annotated OpenVEX schema version")
return false
}

return parsedVersion.Major == 1
}
2 changes: 1 addition & 1 deletion pkg/process/processors/osv_processor.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package processors
package processors // nolint:dupl

import (
"io"
Expand Down
1 change: 1 addition & 0 deletions pkg/process/v6/processors.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,5 +52,6 @@ func Processors(cfg Config) []data.Processor {
processors.NewV2KEVProcessor(kev.Transform),
processors.NewV2EPSSProcessor(epss.Transform),
processors.NewV2OpenVEXProcessor(openvex.Transform),
processors.NewV2AnnotatedOpenVEXProcessor(openvex.AnnotatedTransform),
}
}
79 changes: 49 additions & 30 deletions pkg/process/v6/transformers/openvex/transform.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,15 @@ import (
syft "github.com/anchore/syft/syft/pkg"
)

// Transform an openvex vulnerability into data entries.
//
// satisifies pkg/data/OpenVexTransformerV2
func AnnotatedTransform(wrapper unmarshal.AnnotatedOpenVEXVulnerability, state provider.State) ([]data.Entry, error) {
return transform(wrapper.Document, state, wrapper.Fixes)
}

func Transform(vulnerability unmarshal.OpenVEXVulnerability, state provider.State) ([]data.Entry, error) {
return transform(vulnerability, state, nil)
}

func transform(vulnerability unmarshal.OpenVEXVulnerability, state provider.State, fixes []unmarshal.AnnotatedOpenVEXFix) ([]data.Entry, error) {
name := getName(&vulnerability)
vulnHandle := grypeDB.VulnerabilityHandle{
Name: name,
Expand All @@ -37,7 +42,7 @@ func Transform(vulnerability unmarshal.OpenVEXVulnerability, state provider.Stat
Aliases: getAliases(&vulnerability),
},
}
pkgs, err := getPackageHandles(&vulnerability)
pkgs, err := getPackageHandles(&vulnerability, fixes)
if err != nil {
return nil, err
}
Expand All @@ -47,15 +52,20 @@ func Transform(vulnerability unmarshal.OpenVEXVulnerability, state provider.Stat
}

// getPackageHandles for all products in this advisory
func getPackageHandles(vuln *unmarshal.OpenVEXVulnerability) ([]any, error) {
func getPackageHandles(vuln *unmarshal.OpenVEXVulnerability, fixes []unmarshal.AnnotatedOpenVEXFix) ([]any, error) {
if len(vuln.Products) == 0 {
return nil, nil
}

fixesByProduct := make(map[string][]unmarshal.AnnotatedOpenVEXFix)
for _, fix := range fixes {
fixesByProduct[fix.Product] = append(fixesByProduct[fix.Product], fix)
}

var aphs []grypeDB.AffectedPackageHandle
var uaphs []grypeDB.UnaffectedPackageHandle
for _, product := range vuln.Products {
aph, uph, err := getPackageHandle(&product, vuln)
aph, uph, err := getPackageHandle(&product, vuln, fixesByProduct[product.Identifiers[govex.PURL]])
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -86,7 +96,7 @@ func getPackageHandles(vuln *unmarshal.OpenVEXVulnerability) ([]any, error) {
// PURLIdentifierType: pkg:type/name@version
// }
// }
func getPackageHandle(product *govex.Product, vuln *unmarshal.OpenVEXVulnerability) (aphs []grypeDB.AffectedPackageHandle, uphs []grypeDB.UnaffectedPackageHandle, err error) {
func getPackageHandle(product *govex.Product, vuln *unmarshal.OpenVEXVulnerability, fixes []unmarshal.AnnotatedOpenVEXFix) (aphs []grypeDB.AffectedPackageHandle, uphs []grypeDB.UnaffectedPackageHandle, err error) {
if product == nil || vuln == nil {
return nil, nil, fmt.Errorf("getAffectedPackage params cannot be nil")
}
Expand All @@ -107,17 +117,17 @@ func getPackageHandle(product *govex.Product, vuln *unmarshal.OpenVEXVulnerabili
case govex.StatusAffected:
aphs = append(aphs, grypeDB.AffectedPackageHandle{
Package: pkg,
BlobValue: getAffectedBlob(aliases, purl.Version, purl.Type),
BlobValue: getPackageBlob(aliases, purl.Version, purl.Type, "", fixes),
})
case govex.StatusNotAffected:
uphs = append(uphs, grypeDB.UnaffectedPackageHandle{
Package: pkg,
BlobValue: getUnaffectedBlob(aliases, purl.Version, purl.Type, grypeDB.NotAffectedFixStatus),
BlobValue: getPackageBlob(aliases, purl.Version, purl.Type, grypeDB.NotAffectedFixStatus, fixes),
})
case govex.StatusFixed:
uphs = append(uphs, grypeDB.UnaffectedPackageHandle{
Package: pkg,
BlobValue: getUnaffectedBlob(aliases, purl.Version, purl.Type, grypeDB.FixedStatus),
BlobValue: getPackageBlob(aliases, purl.Version, purl.Type, grypeDB.FixedStatus, fixes),
})
default:
err = fmt.Errorf("invalid vuln states %s", vuln.Status)
Expand Down Expand Up @@ -165,24 +175,36 @@ func getReferences(vuln *unmarshal.OpenVEXVulnerability) []grypeDB.Reference {
return refs
}

// getAffectedBlob creates a package blob for affected packages
func getAffectedBlob(aliases []string, ver string, ty string) *grypeDB.PackageBlob {
return &grypeDB.PackageBlob{
CVEs: aliases,
// semantic versioning
Ranges: []grypeDB.Range{
{
Version: grypeDB.Version{
Type: version.ParseFormat(ty).String(),
Constraint: fmt.Sprintf("= %s", ver),
},
},
},
func getPackageBlob(aliases []string, ver string, ty string, fixState grypeDB.FixStatus, fixes []unmarshal.AnnotatedOpenVEXFix) *grypeDB.PackageBlob {
var fix *grypeDB.Fix
if fixState != "" {
fix = &grypeDB.Fix{
State: fixState,
}

canExpressFixVersion := ver != "" && fixState == grypeDB.FixedStatus
if canExpressFixVersion {
// only express a fix version if we have a version and the state is "fixed"
fix.Version = ver
}

canExpressFixDetail := len(fixes) > 0 && canExpressFixVersion
var detail *grypeDB.FixDetail
if canExpressFixDetail {
time := internal.ParseTime(fixes[0].Available.Date)
if time != nil && !time.IsZero() {
detail = &grypeDB.FixDetail{
Available: &grypeDB.FixAvailability{
Date: time,
Kind: fixes[0].Available.Kind,
},
}
}
}

fix.Detail = detail
}
}

// getUnaffectedBlob creates a package blob for unaffected packages (has a fix)
func getUnaffectedBlob(aliases []string, ver string, ty string, fixState grypeDB.FixStatus) *grypeDB.PackageBlob {
return &grypeDB.PackageBlob{
CVEs: aliases,
Ranges: []grypeDB.Range{
Expand All @@ -191,10 +213,7 @@ func getUnaffectedBlob(aliases []string, ver string, ty string, fixState grypeDB
Type: version.ParseFormat(ty).String(),
Constraint: fmt.Sprintf("= %s", ver),
},
Fix: &grypeDB.Fix{
Version: ver,
State: fixState,
},
Fix: fix,
},
},
}
Expand Down
Loading