-
Notifications
You must be signed in to change notification settings - Fork 585
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add initial support for embedded CycloneDX VEX documents
Signed-off-by: Sambhav Kothari <skothari44@bloomberg.net>
- Loading branch information
Showing
24 changed files
with
7,807 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package cyclonedxvex | ||
|
||
import ( | ||
"time" | ||
|
||
"github.com/CycloneDX/cyclonedx-go" | ||
|
||
"github.com/anchore/syft/syft/source" | ||
) | ||
|
||
// NewBomMetadata returns a new BomDescriptor tailored for the current time and "syft" tool details. | ||
func NewBomMetadata(name, version string, srcMetadata *source.Metadata) *cyclonedx.Metadata { | ||
metadata := cyclonedx.Metadata{ | ||
Timestamp: time.Now().Format(time.RFC3339), | ||
Tools: &[]cyclonedx.Tool{ | ||
{ | ||
Vendor: "anchore", | ||
Name: name, | ||
Version: version, | ||
}, | ||
}, | ||
} | ||
if srcMetadata != nil { | ||
switch srcMetadata.Scheme { | ||
case source.ImageScheme: | ||
metadata.Component = &cyclonedx.Component{ | ||
Type: "container", | ||
Name: srcMetadata.ImageMetadata.UserInput, | ||
Version: srcMetadata.ImageMetadata.ManifestDigest, | ||
} | ||
case source.DirectoryScheme: | ||
metadata.Component = &cyclonedx.Component{ | ||
Type: "file", | ||
Name: srcMetadata.Path, | ||
} | ||
} | ||
} | ||
return &metadata | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,88 @@ | ||
package cyclonedxvex | ||
|
||
import ( | ||
"github.com/CycloneDX/cyclonedx-go" | ||
"github.com/google/uuid" | ||
|
||
"github.com/anchore/grype/grype/match" | ||
"github.com/anchore/grype/grype/pkg" | ||
"github.com/anchore/grype/grype/vulnerability" | ||
"github.com/anchore/grype/internal" | ||
"github.com/anchore/grype/internal/version" | ||
"github.com/anchore/packageurl-go" | ||
"github.com/anchore/syft/syft/source" | ||
) | ||
|
||
// NewDocument returns a CycloneDX Document object populated with the SBOM and vulnerability findings. | ||
func NewDocument(packages []pkg.Package, matches match.Matches, srcMetadata *source.Metadata, provider vulnerability.MetadataProvider) (*cyclonedx.BOM, error) { | ||
versionInfo := version.FromBuild() | ||
doc := cyclonedx.NewBOM() | ||
doc.SerialNumber = uuid.New().URN() | ||
if srcMetadata != nil { | ||
doc.Metadata = NewBomMetadata(internal.ApplicationName, versionInfo.Version, srcMetadata) | ||
} | ||
|
||
// attach matches | ||
components := []cyclonedx.Component{} | ||
vulnerabilities := []cyclonedx.Vulnerability{} | ||
|
||
for _, p := range packages { | ||
component := getComponent(p) | ||
pkgMatches := matches.GetByPkgID(p.ID) | ||
|
||
if len(pkgMatches) > 0 { | ||
for _, m := range pkgMatches { | ||
v, err := NewVulnerability(m, provider) | ||
if err != nil { | ||
return &cyclonedx.BOM{}, err | ||
} | ||
v.Affects = &[]cyclonedx.Affects{ | ||
{ | ||
Ref: component.BOMRef, | ||
}, | ||
} | ||
vulnerabilities = append(vulnerabilities, v) | ||
} | ||
} | ||
// add a *copy* of the Component to the bom document | ||
components = append(components, component) | ||
} | ||
doc.Components = &components | ||
doc.Vulnerabilities = &vulnerabilities | ||
|
||
return doc, nil | ||
} | ||
|
||
func getComponent(p pkg.Package) cyclonedx.Component { | ||
bomRef := string(p.ID) | ||
// try and parse the PURL if possible and append syft id to it, to make | ||
// the purl unique in the BOM. | ||
// TODO: In the future we may want to dedupe by PURL and combine components with | ||
// the same PURL while preserving their unique metadata. | ||
if parsedPURL, err := packageurl.FromString(p.PURL); err == nil { | ||
parsedPURL.Qualifiers = append(parsedPURL.Qualifiers, packageurl.Qualifier{Key: "syft-id", Value: string(p.ID)}) | ||
bomRef = parsedPURL.ToString() | ||
} | ||
// make a new Component (by value) | ||
component := cyclonedx.Component{ | ||
Type: "library", // TODO: this is not accurate, syft does the same thing | ||
Name: p.Name, | ||
Version: p.Version, | ||
PackageURL: p.PURL, | ||
BOMRef: bomRef, | ||
} | ||
|
||
var licenses cyclonedx.Licenses | ||
for _, licenseName := range p.Licenses { | ||
licenses = append(licenses, cyclonedx.LicenseChoice{ | ||
License: &cyclonedx.License{ | ||
Name: licenseName, | ||
}, | ||
}) | ||
} | ||
if len(licenses) > 0 { | ||
// adding licenses to the Component | ||
component.Licenses = &licenses | ||
} | ||
return component | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package cyclonedxvex | ||
|
||
import ( | ||
"io" | ||
|
||
"github.com/CycloneDX/cyclonedx-go" | ||
|
||
"github.com/anchore/grype/grype/match" | ||
"github.com/anchore/grype/grype/pkg" | ||
"github.com/anchore/grype/grype/vulnerability" | ||
"github.com/anchore/syft/syft/source" | ||
) | ||
|
||
// Presenter writes a CycloneDX report from the given Matches and Scope contents | ||
type Presenter struct { | ||
results match.Matches | ||
packages []pkg.Package | ||
srcMetadata *source.Metadata | ||
metadataProvider vulnerability.MetadataProvider | ||
embedded bool | ||
format cyclonedx.BOMFileFormat | ||
} | ||
|
||
// NewPresenter is a *Presenter constructor | ||
func NewPresenter(results match.Matches, packages []pkg.Package, srcMetadata *source.Metadata, metadataProvider vulnerability.MetadataProvider, embedded bool, format cyclonedx.BOMFileFormat) *Presenter { | ||
return &Presenter{ | ||
results: results, | ||
packages: packages, | ||
metadataProvider: metadataProvider, | ||
srcMetadata: srcMetadata, | ||
embedded: embedded, | ||
format: format, | ||
} | ||
} | ||
|
||
// Present creates a CycloneDX-based reporting | ||
func (pres *Presenter) Present(output io.Writer) error { | ||
bom, err := NewDocument(pres.packages, pres.results, pres.srcMetadata, pres.metadataProvider) | ||
if err != nil { | ||
return err | ||
} | ||
encoder := cyclonedx.NewBOMEncoder(output, pres.format) | ||
encoder.SetPretty(true) | ||
|
||
err = encoder.Encode(bom) | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
return err | ||
} |
Oops, something went wrong.