diff --git a/go.mod b/go.mod index 8021bd0..288178f 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/google/cel-go v0.13.0 github.com/google/osv-scanner v1.1.0 github.com/jedib0t/go-pretty/v6 v6.4.4 - github.com/safedep/dry v0.0.0-20230201172119-ff40bd754419 + github.com/safedep/dry v0.0.0-20230202121135-2225c66946de github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 @@ -23,6 +23,7 @@ require ( github.com/golang/protobuf v1.5.2 // indirect github.com/google/uuid v1.3.0 // indirect github.com/inconshreveable/mousetrap v1.0.1 // indirect + github.com/masterminds/semver v1.5.0 // indirect github.com/mattn/go-runewidth v0.0.13 // indirect github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/oklog/ulid/v2 v2.1.0 // indirect diff --git a/go.sum b/go.sum index 12db76a..e69b435 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,6 @@ github.com/BurntSushi/toml v1.2.1 h1:9F2/+DoOYIOksmaJFPw1tGFy1eDnIJXg+UHjuD8lTak= github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= +github.com/Masterminds/semver v1.5.0 h1:H65muMkzWKEuNDnfl9d70GUjFniHKHRbFPGBuZ3QEww= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10 h1:yL7+Jz0jTC6yykIK/Wh74gnTJnrGr5AyrNMXuA0gves= github.com/antlr/antlr4/runtime/Go/antlr v1.4.10/go.mod h1:F7bn7fEU90QkQ3tnmaTx3LTKLEDqnwWODIYppRQ5hnY= @@ -28,6 +29,8 @@ github.com/inconshreveable/mousetrap v1.0.1/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/jedib0t/go-pretty/v6 v6.4.4 h1:N+gz6UngBPF4M288kiMURPHELDMIhF/Em35aYuKrsSc= github.com/jedib0t/go-pretty/v6 v6.4.4/go.mod h1:MgmISkTWDSFu0xOqiZ0mKNntMQ2mDgOcwOkwBEkMDJI= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= +github.com/masterminds/semver v1.5.0 h1:hTxJTTY7tjvnWMrl08O6u3G6BLlKVwxSz01lVac9P8U= +github.com/masterminds/semver v1.5.0/go.mod h1:s7KNT9fnd7edGzwwP7RBX4H0v/CYd5qdOLfkL1V75yg= github.com/mattn/go-runewidth v0.0.13 h1:lTGmDsbAYt5DmK6OnoV7EuIF1wEIFAcxld6ypU4OSgU= github.com/mattn/go-runewidth v0.0.13/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= @@ -41,8 +44,8 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN github.com/rivo/uniseg v0.2.0 h1:S1pD9weZBuJdFmowNwbpi7BJ8TNftyUImj/0WQi72jY= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/safedep/dry v0.0.0-20230201172119-ff40bd754419 h1:A41ZjwvBng3TLVZCBKXNKuheUblNnLABnZFELuznnNM= -github.com/safedep/dry v0.0.0-20230201172119-ff40bd754419/go.mod h1:BDeFh8rfhLz1H0F829C6adC7nkmoU9BfGyKlHE+ccF0= +github.com/safedep/dry v0.0.0-20230202121135-2225c66946de h1:07LwA5P5bxaVK8SXtoBfP8qZGcWAtqiFbq54UZnMcyg= +github.com/safedep/dry v0.0.0-20230202121135-2225c66946de/go.mod h1:H111d9khzpHFEqKXb9lgAZ1W5eeK7yEN7ToWu0ak+JI= github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0= github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/spf13/cobra v1.6.1 h1:o94oiPyS4KD1mPy2fmcYYHHfCxLqYjJOhGsCHFZtEzA= diff --git a/pkg/reporter/console.go b/pkg/reporter/console.go new file mode 100644 index 0000000..f47f9d0 --- /dev/null +++ b/pkg/reporter/console.go @@ -0,0 +1,122 @@ +package reporter + +import ( + "fmt" + "os" + + "github.com/jedib0t/go-pretty/v6/table" + "github.com/jedib0t/go-pretty/v6/text" + "github.com/safedep/dry/semver" + "github.com/safedep/dry/utils" + "github.com/safedep/vet/pkg/analyzer" + "github.com/safedep/vet/pkg/models" + "github.com/safedep/vet/pkg/policy" +) + +type consoleReporter struct{} + +func NewConsoleReporter() (Reporter, error) { + return &consoleReporter{}, nil +} + +func (r *consoleReporter) Name() string { + return "Console Report Generator" +} + +func (r *consoleReporter) AddManifest(manifest *models.PackageManifest) { + tbl := table.NewWriter() + tbl.SetOutputMirror(os.Stdout) + tbl.SetStyle(table.StyleLight) + + tbl.AppendHeader(table.Row{"Package", "Attribute", "Summary"}) + for _, pkg := range manifest.Packages { + r.report(tbl, pkg) + } + + fmt.Print(text.Bold.Sprint("Rendering summary report for: ", + manifest.Path)) + fmt.Print("\n") + + tbl.Render() +} + +func (r *consoleReporter) AddAnalyzerEvent(event *analyzer.AnalyzerEvent) { +} + +func (r *consoleReporter) AddPolicyEvent(event *policy.PolicyEvent) { +} + +func (r *consoleReporter) Finish() error { + return nil +} + +func (r *consoleReporter) report(tbl table.Writer, pkg *models.Package) { + insight := utils.SafelyGetValue(pkg.Insights) + + headerAppended := false + headerAppender := func() { + if headerAppended { + return + } + + // Header for this package + tbl.AppendRow(table.Row{ + fmt.Sprintf("%s/%s", pkg.PackageDetails.Name, pkg.PackageDetails.Version), + "", "", + }) + + headerAppended = true + } + + // Report vulnerabilities + sm := map[string]int{"CRITICAL": 0, "HIGH": 0} + for _, vuln := range utils.SafelyGetValue(insight.Vulnerabilities) { + for _, s := range utils.SafelyGetValue(vuln.Severities) { + risk := string(utils.SafelyGetValue(s.Risk)) + if (risk == "CRITICAL") || (risk == "HIGH") { + sm[risk] += 1 + } + } + } + + if (sm["CRITICAL"] > 0) || (sm["HIGH"] > 0) { + headerAppender() + tbl.AppendRow(table.Row{"", + text.Bold.Sprint(text.BgRed.Sprint("Vulnerability")), + fmt.Sprintf("Critical:%d High:%d", + sm["CRITICAL"], sm["HIGH"])}) + } + + // Popularity + projects := utils.SafelyGetValue(insight.Projects) + if len(projects) > 0 { + p := projects[0] + + sc := utils.SafelyGetValue(p.Stars) + ic := utils.SafelyGetValue(p.Issues) + + if (sc > 0) && (sc < 10) && (ic > 0) && (ic < 5) { + headerAppender() + tbl.AppendRow(table.Row{"", + text.Bold.Sprint("Low Popularity"), + fmt.Sprintf("Stars:%d Issues:%d", sc, ic)}) + } + } + + // High version drift + version := pkg.PackageDetails.Version + latestVersion := utils.SafelyGetValue(insight.PackageCurrentVersion) + + driftType, _ := semver.Diff(version, latestVersion) + if driftType.IsMajor() { + headerAppender() + tbl.AppendRow(table.Row{"", + text.Bold.Sprint("Version Drift"), + fmt.Sprintf("%s > %s", version, latestVersion), + }) + } + + if headerAppended { + tbl.AppendSeparator() + } +} diff --git a/scan.go b/scan.go index 3ec5bd5..76b0ca5 100644 --- a/scan.go +++ b/scan.go @@ -24,6 +24,7 @@ var ( dumpJsonManifestDir string celFilterExpression string markdownReportPath string + consoleReport bool ) func newScanCommand() *cobra.Command { @@ -61,6 +62,8 @@ func newScanCommand() *cobra.Command { "Filter and print packages using CEL") cmd.Flags().StringVarP(&markdownReportPath, "report-markdown", "", "", "Generate consolidated markdown report to file") + cmd.Flags().BoolVarP(&consoleReport, "report-console", "", true, + "Minimal summary of package manifest") cmd.AddCommand(listParsersCommand()) return cmd @@ -111,6 +114,15 @@ func internalStartScan() error { } reporters := []reporter.Reporter{} + if consoleReport { + rp, err := reporter.NewConsoleReporter() + if err != nil { + return err + } + + reporters = append(reporters, rp) + } + if !utils.IsEmptyString(markdownReportPath) { rp, err := reporter.NewMarkdownReportGenerator(reporter.MarkdownReportingConfig{ Path: markdownReportPath,