Thanks to visit codestin.com
Credit goes to github.com

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
66 changes: 42 additions & 24 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,9 @@ import (
"github.com/anchore/grype/internal/format"
"github.com/anchore/grype/internal/ui"
"github.com/anchore/grype/internal/version"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/syft/syft/source"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
"github.com/docker/docker/client"
Expand All @@ -32,6 +31,12 @@ import (
"github.com/wagoodman/go-partybus"
)

const (
scopeFlag = "scope"
outputFlag = "output"
FailOnFlag = "fail-on"
)

var rootCmd = &cobra.Command{
Use: fmt.Sprintf("%s [IMAGE]", internal.ApplicationName),
Short: "A vulnerability scanner for container images and filesystems",
Expand All @@ -46,10 +51,15 @@ You can also explicitly specify the scheme to use:
{{.appName}} oci-archive:path/to/yourimage.tar use a tarball from disk for OCI archives (from Podman or otherwise)
{{.appName}} oci-dir:path/to/yourimage read directly from a path on disk for OCI layout directories (from Skopeo or otherwise)
{{.appName}} dir:path/to/yourproject read directly from a path on disk (any directory)
{{.appName}} sbom:path/to/syft.json read Syft JSON from path on disk

You can also pipe in Syft JSON directly:
syft yourimage:tag -o json | {{.appName}}

`, map[string]interface{}{
"appName": internal.ApplicationName,
}),
Args: cobra.MaximumNArgs(1),
Args: validateRootArgs,
Run: func(cmd *cobra.Command, args []string) {
if appConfig.Dev.ProfileCPU {
f, err := os.Create("cpu.profile")
Expand All @@ -62,14 +72,7 @@ You can also explicitly specify the scheme to use:
}
}
}
if len(args) == 0 {
err := cmd.Help()
if err != nil {
log.Errorf(err.Error())
os.Exit(1)
}
os.Exit(1)
}

err := runDefaultCmd(cmd, args)

if appConfig.Dev.ProfileCPU {
Expand Down Expand Up @@ -102,22 +105,32 @@ You can also explicitly specify the scheme to use:
},
}

func validateRootArgs(cmd *cobra.Command, args []string) error {
// the user must specify at least one argument OR wait for input on stdin IF it is a pipe
if len(args) == 0 && !internal.IsPipedInput() {
// return an error with no message for the user, which will implicitly show the help text (but no specific error)
return fmt.Errorf("")
}

return cobra.MaximumNArgs(1)(cmd, args)
}

func init() {
// setup CLI options specific to scanning an image

// scan options
flag := "scope"
flag := scopeFlag
rootCmd.Flags().StringP(
"scope", "s", scope.SquashedScope.String(),
fmt.Sprintf("selection of layers to analyze, options=%v", scope.Options),
scopeFlag, "s", source.SquashedScope.String(),
fmt.Sprintf("selection of layers to analyze, options=%v", source.AllScopes),
)
if err := viper.BindPFlag(flag, rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}

// output & formatting options
flag = "output"
flag = outputFlag
rootCmd.Flags().StringP(
flag, "o", presenter.TablePresenter.String(),
fmt.Sprintf("report output formatter, options=%v", presenter.Options),
Expand All @@ -127,12 +140,13 @@ func init() {
os.Exit(1)
}

flag = FailOnFlag
rootCmd.Flags().StringP(
"fail-on", "f", "",
flag, "f", "",
fmt.Sprintf("set the return code to 1 if a vulnerability is found with a severity >= the given severity, options=%v", vulnerability.AllSeverities),
)
if err := viper.BindPFlag("fail-on-severity", rootCmd.Flags().Lookup("fail-on")); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", "fail-on", err)
if err := viper.BindPFlag("fail-on-severity", rootCmd.Flags().Lookup(flag)); err != nil {
fmt.Printf("unable to bind flag '%s': %+v", flag, err)
os.Exit(1)
}
}
Expand Down Expand Up @@ -163,8 +177,8 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
var provider vulnerability.Provider
var metadataProvider vulnerability.MetadataProvider
var catalog *pkg.Catalog
var theScope *scope.Scope
var theDistro *distro.Distro
var srcMetadata source.Metadata
var theDistro distro.Distro
var err error
var wg = &sync.WaitGroup{}

Expand All @@ -180,7 +194,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha

go func() {
defer wg.Done()
catalog, theScope, theDistro, err = syft.Catalog(userInput, appConfig.ScopeOpt)
srcMetadata, catalog, theDistro, err = grype.Catalog(userInput, appConfig.ScopeOpt)
if err != nil {
errs <- fmt.Errorf("failed to catalog: %w", err)
}
Expand All @@ -191,7 +205,7 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha
return
}

matches := grype.FindVulnerabilitiesForCatalog(provider, *theDistro, catalog)
matches := grype.FindVulnerabilitiesForCatalog(provider, theDistro, catalog)

// determine if there are any severities >= to the max allowable severity (which is optional).
// note: until the shared file lock in sqlittle is fixed the sqlite DB cannot be access concurrently,
Expand All @@ -202,14 +216,18 @@ func startWorker(userInput string, failOnSeverity *vulnerability.Severity) <-cha

bus.Publish(partybus.Event{
Type: event.VulnerabilityScanningFinished,
Value: presenter.GetPresenter(appConfig.PresenterOpt, matches, catalog, *theScope, metadataProvider),
Value: presenter.GetPresenter(appConfig.PresenterOpt, matches, catalog, theDistro, srcMetadata, metadataProvider),
})
}()
return errs
}

func runDefaultCmd(_ *cobra.Command, args []string) error {
userInput := args[0]
// we may not be provided an image if the user is piping in SBOM input
var userInput string
if len(args) > 0 {
userInput = args[0]
}
errs := startWorker(userInput, appConfig.FailOnSeverity)
ux := ui.Select(appConfig.CliOptions.Verbosity > 0, appConfig.Quiet)
return ux(errs, eventSubscription)
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/anchore/go-version v1.2.2-0.20200810141238-330bef18dbca
github.com/anchore/grype-db v0.0.0-20200929200644-6d1c82acc95e
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409
github.com/anchore/syft v0.7.1
github.com/anchore/syft v0.8.0
github.com/docker/docker v17.12.0-ce-rc1.0.20200309214505-aa6a9891b09c+incompatible
github.com/dustin/go-humanize v1.0.0
github.com/facebookincubator/nvdtools v0.1.4-0.20200622182922-aed862a62ae6
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,8 @@ github.com/anchore/grype-db v0.0.0-20200929200644-6d1c82acc95e h1:s0HmxxDuJyvgGB
github.com/anchore/grype-db v0.0.0-20200929200644-6d1c82acc95e/go.mod h1:LINmipRzG88vnJEWvgMMDVCFH1qZsj7+bjmpERlSyaA=
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409 h1:xKSpDRjmYrEFrdMeDh4AuSUAFc99pdro6YFBKxy2um0=
github.com/anchore/stereoscope v0.0.0-20201106140100-12e75c48f409/go.mod h1:2Jja/4l0zYggW52og+nn0rut4i+OYjCf9vTyrM8RT4E=
github.com/anchore/syft v0.7.1 h1:xP5EI8r1WbnrhI71AaEk5e/OSTXJKFleV+J03TTOSv8=
github.com/anchore/syft v0.7.1/go.mod h1:Uf1lxsZSo/y3HjQ0U94p3aQpHy8Ac6wLyDwYLT0dcYw=
github.com/anchore/syft v0.8.0 h1:Jq9yja9rwx6oIrPjwSpmB2VWRbrOTLtC06GJnAUz03A=
github.com/anchore/syft v0.8.0/go.mod h1:Uf1lxsZSo/y3HjQ0U94p3aQpHy8Ac6wLyDwYLT0dcYw=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883 h1:bvNMNQO63//z+xNgfBlViaCIJKLlCJ6/fmUseuG0wVQ=
github.com/andreyvit/diff v0.0.0-20170406064948-c7f18ee00883/go.mod h1:rCTlJbsFo29Kk6CurOXKm700vrz8f0KW0JNfpkRJY/8=
github.com/andybalholm/cascadia v1.1.0/go.mod h1:GsXiBklL0woXo1j/WYWtSYYC4ouU9PqHO0sqidkEA4Y=
Expand Down
54 changes: 44 additions & 10 deletions grype/lib.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,64 @@
package grype

import (
"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/internal/bus"
"github.com/wagoodman/go-partybus"
"fmt"
"os"
"strings"

"github.com/anchore/grype/grype/db"
"github.com/anchore/grype/internal"

"github.com/anchore/grype/grype/db"
"github.com/anchore/grype/grype/logger"

"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/matcher"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal/bus"
"github.com/anchore/grype/internal/log"
"github.com/anchore/syft/syft"
"github.com/anchore/syft/syft/distro"
"github.com/anchore/syft/syft/pkg"
"github.com/anchore/syft/syft/scope"
"github.com/anchore/syft/syft/source"
"github.com/wagoodman/go-partybus"
)

func FindVulnerabilities(provider vulnerability.Provider, userImageStr string, scopeOpt scope.Option) (match.Matches, *pkg.Catalog, *scope.Scope, error) {
catalog, theScope, theDistro, err := syft.Catalog(userImageStr, scopeOpt)
func Catalog(userImageStr string, scopeOpt source.Scope) (source.Metadata, *pkg.Catalog, distro.Distro, error) {
// handle explicit sbom input first
if strings.HasPrefix(userImageStr, "sbom:") {
// the user has explicitly hinted this is an sbom, if there is an issue return the error
filepath := strings.TrimPrefix(userImageStr, "sbom:")
sbomReader, err := os.Open(filepath)
if err != nil {
return source.Metadata{}, nil, distro.Distro{}, fmt.Errorf("unable to read sbom: %w", err)
}
return syft.CatalogFromJSON(sbomReader)
} else if internal.IsPipedInput() && userImageStr == "" {
// the user has not provided an image and stdin is a pipe, assume this to be an explicit sbom case
return syft.CatalogFromJSON(os.Stdin)
}

// the user has not hinted that this may be a sbom, but lets try that first... ignore failures and fallback to syft
if sbomReader, err := os.Open(userImageStr); err == nil {
sourceMetadata, catalog, theDistro, err := syft.CatalogFromJSON(sbomReader)
if err == nil {
return sourceMetadata, catalog, theDistro, nil
}
}

// attempt to parse as an image (left syft handle this)
theSource, catalog, theDistro, err := syft.Catalog(userImageStr, scopeOpt)
if err != nil {
return source.Metadata{}, nil, distro.Distro{}, err
}
return theSource.Metadata, catalog, theDistro, nil
}

func FindVulnerabilities(provider vulnerability.Provider, userImageStr string, scopeOpt source.Scope) (match.Matches, source.Metadata, *pkg.Catalog, error) {
sourceMetadata, catalog, theDistro, err := Catalog(userImageStr, scopeOpt)
if err != nil {
return match.Matches{}, nil, nil, err
return match.Matches{}, source.Metadata{}, nil, err
}

return FindVulnerabilitiesForCatalog(provider, *theDistro, catalog), catalog, theScope, nil
return FindVulnerabilitiesForCatalog(provider, theDistro, catalog), sourceMetadata, catalog, nil
}

func FindVulnerabilitiesForCatalog(provider vulnerability.Provider, d distro.Distro, catalog *pkg.Catalog) match.Matches {
Expand Down
107 changes: 17 additions & 90 deletions grype/presenter/cyclonedx/document.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,14 @@ package cyclonedx

import (
"encoding/xml"
"fmt"
"strings"

"github.com/anchore/grype/grype/match"
"github.com/anchore/grype/grype/vulnerability"
"github.com/anchore/grype/internal"
"github.com/anchore/grype/internal/version"
"github.com/anchore/syft/syft/pkg"
syftCDX "github.com/anchore/syft/syft/presenter/cyclonedx"
"github.com/anchore/syft/syft/source"
"github.com/google/uuid"
)

Expand All @@ -27,84 +28,22 @@ type Document struct {
}

// NewDocument returns an empty CycloneDX Document object.
func NewDocument() Document {
return Document{
XMLNs: "http://cyclonedx.org/schema/bom/1.2",
XMLNsBd: "http://cyclonedx.org/schema/ext/bom-descriptor/1.0",
XMLNsV: "http://cyclonedx.org/schema/ext/vulnerability/1.0",
Version: 1,
SerialNumber: uuid.New().URN(),
}
}

// NewVulnerability creates a Vulnerability document from a match and the metadata provider
func NewVulnerability(m match.Match, p vulnerability.MetadataProvider) (Vulnerability, error) {
metadata, err := p.GetMetadata(m.Vulnerability.ID, m.Vulnerability.RecordSource)
if err != nil {
return Vulnerability{}, fmt.Errorf("unable to fetch vuln=%q metadata: %+v", m.Vulnerability.ID, err)
}

// The spec allows many ratings, but we only have 1
var rating Rating
var score Score

if metadata.CvssV2 != nil {
if metadata.CvssV2.ExploitabilityScore > 0 {
score.Exploitability = metadata.CvssV2.ExploitabilityScore
}
if metadata.CvssV2.ImpactScore > 0 {
score.Impact = metadata.CvssV2.ImpactScore
}
score.Base = metadata.CvssV2.BaseScore
rating.Method = "CVSSv2"
rating.Vector = metadata.CvssV2.Vector
}

if metadata.CvssV3 != nil {
if metadata.CvssV3.ExploitabilityScore > 0 {
score.Exploitability = metadata.CvssV3.ExploitabilityScore
}
if metadata.CvssV3.ImpactScore > 0 {
score.Impact = metadata.CvssV3.ImpactScore
}
score.Base = metadata.CvssV3.BaseScore
rating.Method = "CVSSv3"
rating.Vector = metadata.CvssV3.Vector
func NewDocument(catalog *pkg.Catalog, matches match.Matches, srcMetadata source.Metadata, provider vulnerability.MetadataProvider) (Document, error) {
versionInfo := version.FromBuild()

doc := Document{
XMLNs: "http://cyclonedx.org/schema/bom/1.2",
XMLNsBd: "http://cyclonedx.org/schema/ext/bom-descriptor/1.0",
XMLNsV: "http://cyclonedx.org/schema/ext/vulnerability/1.0",
Version: 1,
SerialNumber: uuid.New().URN(),
BomDescriptor: syftCDX.NewBomDescriptor(internal.ApplicationName, versionInfo.Version, srcMetadata),
}

rating.Score = score
// attach matches

// The schema does not allow "Negligible", only allowing the following:
// 'None', 'Low', 'Medium', 'High', 'Critical', 'Unknown'
severity := metadata.Severity
if metadata.Severity == "Negligible" {
severity = "Low"
}

rating.Severity = severity

v := Vulnerability{
Ref: uuid.New().URN(),
ID: m.Vulnerability.ID,
Source: Source{
Name: m.Vulnerability.RecordSource,
URL: makeURL(m.Vulnerability.ID),
},
Ratings: []Rating{rating},
Description: metadata.Description,
Advisories: &Advisories{
Advisory: metadata.Links,
},
}

return v, nil
}

// NewDocumentFromCatalog returns a CycloneDX Document object populated with the vulnerability contents.
func NewDocumentFromCatalog(catalog *pkg.Catalog, matches match.Matches, provider vulnerability.MetadataProvider) (Document, error) {
bom := NewDocument()
for p := range catalog.Enumerate() {
// make a new compoent (by value)
// make a new component (by value)
component := Component{
Component: syftCDX.Component{
Type: "library", // TODO: this is not accurate, syft does the same thing
Expand Down Expand Up @@ -144,20 +83,8 @@ func NewDocumentFromCatalog(catalog *pkg.Catalog, matches match.Matches, provide
}

// add a *copy* of the component to the bom document
bom.Components = append(bom.Components, component)
doc.Components = append(doc.Components, component)
}

bom.BomDescriptor = NewBomDescriptor()

return bom, nil
}

func makeURL(id string) string {
if strings.HasPrefix(id, "CVE-") {
return fmt.Sprintf("http://cve.mitre.org/cgi-bin/cvename.cgi?name=%s", id)
}
if strings.HasPrefix(id, "GHSA") {
return fmt.Sprintf("https://github.com/advisories/%s", id)
}
return id
return doc, nil
}
Loading