-
Notifications
You must be signed in to change notification settings - Fork 594
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Alex Goodman <[email protected]>
- Loading branch information
Showing
16 changed files
with
1,541 additions
and
76 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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,156 @@ | ||
package commands | ||
|
||
import ( | ||
"encoding/json" | ||
"errors" | ||
"fmt" | ||
"io" | ||
"strings" | ||
|
||
"github.com/olekukonko/tablewriter" | ||
"github.com/spf13/cobra" | ||
|
||
"github.com/anchore/clio" | ||
"github.com/anchore/grype/cmd/grype/cli/commands/internal/dbsearch" | ||
"github.com/anchore/grype/cmd/grype/cli/options" | ||
"github.com/anchore/grype/grype/db/v6/distribution" | ||
"github.com/anchore/grype/grype/db/v6/installation" | ||
"github.com/anchore/grype/internal/bus" | ||
) | ||
|
||
type dbSearchPackageOptions struct { | ||
Format options.DBSearchFormat `yaml:",inline" mapstructure:",squash"` | ||
Vulnerability options.DBSearchVulnerabilities `yaml:",inline" mapstructure:",squash"` | ||
Package options.DBSearchPackages `yaml:",inline" mapstructure:",squash"` | ||
OS options.DBSearchOSs `yaml:",inline" mapstructure:",squash"` | ||
|
||
DBOptions `yaml:",inline" mapstructure:",squash"` | ||
} | ||
|
||
func DBSearchPackages(app clio.Application) *cobra.Command { | ||
opts := &dbSearchPackageOptions{ | ||
Format: options.DBSearchFormat{ | ||
Output: tableOutputFormat, | ||
Allowable: []string{ | ||
tableOutputFormat, | ||
jsonOutputFormat, | ||
}, | ||
}, | ||
Vulnerability: options.DBSearchVulnerabilities{ | ||
UseVulnIDFlag: true, | ||
}, | ||
DBOptions: *dbOptionsDefault(app.ID()), | ||
} | ||
|
||
return app.SetupCommand(&cobra.Command{ | ||
Use: "pkg PURL|CPE|NAME...", | ||
Aliases: []string{"package", "packages", "pkgs"}, | ||
Short: "Search for packages affected by vulnerabilities within the db (supports DB schema v6+ only)", | ||
Args: func(_ *cobra.Command, args []string) error { | ||
opts.Package.Names = args | ||
return nil | ||
}, | ||
RunE: func(_ *cobra.Command, _ []string) (err error) { | ||
if !opts.Experimental.DBv6 { | ||
return errors.New("this command only supports the v6+ database schemas") | ||
} | ||
return runDBSearchPackages(*opts) | ||
}, | ||
}, opts) | ||
} | ||
|
||
func runDBSearchPackages(opts dbSearchPackageOptions) error { | ||
client, err := distribution.NewClient(opts.DB.ToClientConfig()) | ||
if err != nil { | ||
return fmt.Errorf("unable to create distribution client: %w", err) | ||
} | ||
|
||
curator, err := installation.NewCurator(opts.DB.ToCuratorConfig(), client) | ||
if err != nil { | ||
return fmt.Errorf("unable to create curator: %w", err) | ||
} | ||
|
||
reader, err := curator.Reader() | ||
if err != nil { | ||
return fmt.Errorf("unable to get providers: %w", err) | ||
} | ||
|
||
if err := validateProvidersFilter(reader, opts.Vulnerability.Providers); err != nil { | ||
return err | ||
} | ||
|
||
rows, err := dbsearch.AffectedPackages(reader, dbsearch.AffectedPackagesOptions{ | ||
Vulnerability: opts.Vulnerability.Specs, | ||
Package: opts.Package.PkgSpecs, | ||
CPE: opts.Package.CPESpecs, | ||
OS: opts.OS.Specs, | ||
}) | ||
if err != nil { | ||
return err | ||
} | ||
|
||
if len(rows) == 0 { | ||
return errors.New("no affected packages found") | ||
} | ||
|
||
sb := &strings.Builder{} | ||
err = presentDBSearchPackages(opts.Format.Output, rows, sb) | ||
bus.Report(sb.String()) | ||
return err | ||
} | ||
|
||
func presentDBSearchPackages(outputFormat string, structuredRows []dbsearch.AffectedPackageTableRow, output io.Writer) error { | ||
if len(structuredRows) == 0 { | ||
// TODO: show a message that no results were found? | ||
return nil | ||
} | ||
|
||
switch outputFormat { | ||
case tableOutputFormat: | ||
rows := renderDBSearchPackagesTableRows(structuredRows) | ||
|
||
table := tablewriter.NewWriter(output) | ||
commonTableWriterOptions(table) | ||
|
||
table.SetHeader([]string{"ID", "Package", "Ecosystem", "Namespace", "Version Constraint"}) | ||
table.AppendBulk(rows) | ||
table.Render() | ||
case jsonOutputFormat: | ||
enc := json.NewEncoder(output) | ||
enc.SetEscapeHTML(false) | ||
enc.SetIndent("", " ") | ||
if err := enc.Encode(structuredRows); err != nil { | ||
return fmt.Errorf("failed to encode diff information: %+v", err) | ||
} | ||
default: | ||
return fmt.Errorf("unsupported output format: %s", outputFormat) | ||
} | ||
return nil | ||
} | ||
|
||
func renderDBSearchPackagesTableRows(structuredRows []dbsearch.AffectedPackageTableRow) [][]string { | ||
var rows [][]string | ||
for _, rr := range structuredRows { | ||
var pkgOrCPE, ecosystem string | ||
if rr.Package != nil { | ||
pkgOrCPE = rr.Package.Name | ||
ecosystem = rr.Package.Ecosystem | ||
} else if rr.CPE != nil { | ||
pkgOrCPE = rr.CPE.String() | ||
ecosystem = rr.CPE.TargetSoftware | ||
} | ||
|
||
namespace := rr.Vulnerability.Provider | ||
if rr.OS != nil { | ||
namespace = fmt.Sprintf("%s:%s", rr.OS.Family, rr.OS.Version) | ||
} | ||
|
||
var ranges []string | ||
for _, ra := range rr.Detail.Ranges { | ||
ranges = append(ranges, ra.Version.Constraint) | ||
} | ||
rangeStr := strings.Join(ranges, " || ") | ||
rows = append(rows, []string{rr.Vulnerability.ID, pkgOrCPE, ecosystem, namespace, rangeStr}) | ||
} | ||
return rows | ||
} |
Oops, something went wrong.