Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add initial support for embedded CycloneDX VEX documents #678

Merged
merged 1 commit into from
Apr 28, 2022
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
7 changes: 6 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ all: clean static-analysis test ## Run all checks (linting, license check, unit,
@printf '$(SUCCESS)All checks pass!$(RESET)\n'

.PHONY: test
test: unit validate-cyclonedx-schema integration cli ## Run all tests (unit, integration, linux acceptance, and CLI tests)
test: unit validate-cyclonedx-schema validate-cyclonedx-vex-schema integration cli ## Run all tests (unit, integration, linux acceptance, and CLI tests)

help:
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "$(BOLD)$(CYAN)%-25s$(RESET)%s\n", $$1, $$2}'
Expand All @@ -96,6 +96,7 @@ bootstrap-tools: $(TEMPDIR)
curl -sSfL https://raw.githubusercontent.com/anchore/chronicle/main/install.sh | sh -s -- -b $(TEMPDIR)/ v0.3.0
# the only difference between goimports and gosimports is that gosimports removes extra whitespace between import blocks (see https://github.com/golang/go/issues/20818)
GOBIN="$(shell realpath $(TEMPDIR))" go install github.com/rinchsan/gosimports/cmd/gosimports@v0.1.5
GOBIN="$(shell realpath $(TEMPDIR))" go install github.com/neilpa/yajsv@v1.4.0
.github/scripts/goreleaser-install.sh -b $(TEMPDIR)/ v1.4.1

.PHONY: bootstrap-go
Expand Down Expand Up @@ -143,6 +144,10 @@ check-go-mod-tidy:
validate-cyclonedx-schema:
cd schema/cyclonedx && make

.PHONY: validate-cyclonedx-vex-schema
validate-cyclonedx-vex-schema:
cd schema/cyclonedxvex && make

.PHONY: validate-grype-db-schema
validate-grype-db-schema:
# ensure the codebase is only referencing a single grype-db schema version, multiple is not allowed
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/anchore/grype
go 1.18

require (
github.com/CycloneDX/cyclonedx-go v0.5.0
github.com/Masterminds/sprig/v3 v3.2.2
github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d
github.com/adrg/xdg v0.2.1
Expand Down Expand Up @@ -75,7 +76,6 @@ require (
github.com/Azure/go-autorest/autorest/validation v0.3.1 // indirect
github.com/Azure/go-autorest/logger v0.2.1 // indirect
github.com/Azure/go-autorest/tracing v0.6.0 // indirect
github.com/CycloneDX/cyclonedx-go v0.5.0 // indirect
github.com/Masterminds/goutils v1.1.1 // indirect
github.com/Masterminds/semver/v3 v3.1.1 // indirect
github.com/Microsoft/go-winio v0.5.1 // indirect
Expand Down
39 changes: 39 additions & 0 deletions grype/presenter/cyclonedxvex/bom_metadata.go
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
}
88 changes: 88 additions & 0 deletions grype/presenter/cyclonedxvex/document.go
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{
sambhav marked this conversation as resolved.
Show resolved Hide resolved
{
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: "package-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
}
52 changes: 52 additions & 0 deletions grype/presenter/cyclonedxvex/presenter.go
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
}
Loading