From 9c1e224a43da4487b0068c0f331fce89072c3941 Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 16 Feb 2023 15:37:48 +0530 Subject: [PATCH 1/5] Add OpenSSF scorecard badge --- README.md | 3 +++ pkg/parser/parser.go | 2 ++ pkg/parser/parser_test.go | 2 +- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index be67895..5788dc5 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,10 @@ helps engineering and security teams to identify potential issues in their open source dependencies and evaluate them against organizational policies. +[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/safedep/vet/badge)](https://api.securityscorecards.dev/projects/github.com/safedep/vet) [![CodeQL](https://github.com/safedep/vet/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/safedep/vet/actions/workflows/codeql.yml) +[![Scorecard supply-chain security](https://github.com/safedep/vet/actions/workflows/scorecard.yml/badge.svg)](https://github.com/safedep/vet/actions/workflows/scorecard.yml) + ## TL;DR diff --git a/pkg/parser/parser.go b/pkg/parser/parser.go index 0ed7927..a750926 100644 --- a/pkg/parser/parser.go +++ b/pkg/parser/parser.go @@ -85,6 +85,8 @@ func (pw *parserWrapper) Ecosystem() string { return models.EcosystemPub case "requirements.txt": return models.EcosystemPyPI + case "Pipfile.lock": + return models.EcosystemPyPI case "yarn.lock": return models.EcosystemNpm case "gradle.lockfile": diff --git a/pkg/parser/parser_test.go b/pkg/parser/parser_test.go index 9ab6c1e..dccc266 100644 --- a/pkg/parser/parser_test.go +++ b/pkg/parser/parser_test.go @@ -8,7 +8,7 @@ import ( func TestListParser(t *testing.T) { parsers := List() - assert.Equal(t, 9, len(parsers)) + assert.Equal(t, 10, len(parsers)) } func TestInvalidEcosystemMapping(t *testing.T) { From b4c407dddc9448b850c30e4cab426717998c9e35 Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 16 Feb 2023 16:02:53 +0530 Subject: [PATCH 2/5] Fix #14: Corrected typo --- pkg/reporter/summary.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/reporter/summary.go b/pkg/reporter/summary.go index 9961eb9..be53aba 100644 --- a/pkg/reporter/summary.go +++ b/pkg/reporter/summary.go @@ -181,8 +181,10 @@ func (r *summaryReporter) Finish() error { fmt.Println() fmt.Println(text.Faint.Sprint(summaryListPrependText, r.manifestCountStatement())) fmt.Println() + r.renderRemediationAdvice() fmt.Println() + fmt.Println("Install as a security gate in CI for incremental scan and blocking risky dependencies") fmt.Println("Run `vet ci` to generate CI scripts") fmt.Println() @@ -221,7 +223,7 @@ func (r *summaryReporter) renderRemediationAdvice() { tbl.SetOutputMirror(os.Stdout) tbl.SetStyle(table.StyleLight) - tbl.AppendHeader(table.Row{"Package", "Update To", "Risk Score"}) + tbl.AppendHeader(table.Row{"Package", "Update To", "Impact"}) for idx, sp := range sortedPackages { if idx >= summaryReportMaxUpgradeAdvice { break @@ -255,7 +257,7 @@ func (r *summaryReporter) packageNameForRemediationAdvice(pkg *models.Package) s } func (r *summaryReporter) vulnSummaryStatement() string { - return fmt.Sprintf("%d critical, %d high and %d other vulnerabilities were identifier", + return fmt.Sprintf("%d critical, %d high and %d other vulnerabilities were identified", r.summary.vulns.critical, r.summary.vulns.high, r.summary.vulns.medium+r.summary.vulns.low) } From 504598de65f35eace29d83e849525b24949d44e6 Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 16 Feb 2023 16:05:02 +0530 Subject: [PATCH 3/5] Fix #15: Return error when auth sub-command is missing --- auth.go | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/auth.go b/auth.go index 0dc0be1..143dbc1 100644 --- a/auth.go +++ b/auth.go @@ -1,6 +1,7 @@ package main import ( + "errors" "fmt" "os" "syscall" @@ -22,9 +23,7 @@ func newAuthCommand() *cobra.Command { Use: "auth", Short: "Configure and verify Insights API authentication", RunE: func(cmd *cobra.Command, args []string) error { - fmt.Printf("You must choose an appropriate command: configure, verify\n") - os.Exit(1) - return nil + return errors.New("a valid sub-command is required") }, } From e895f8a0ec838d2c16ccd7dc732f89c7745080cd Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 16 Feb 2023 16:18:26 +0530 Subject: [PATCH 4/5] #16: Refactor to use UI utils for printing msg --- internal/ui/progress.go | 66 +++++++++++++++++++++++++++++ internal/ui/spinner.go | 39 +++++++++++++++++ internal/ui/ui.go | 93 ++--------------------------------------- main.go | 3 +- 4 files changed, 110 insertions(+), 91 deletions(-) create mode 100644 internal/ui/progress.go create mode 100644 internal/ui/spinner.go diff --git a/internal/ui/progress.go b/internal/ui/progress.go new file mode 100644 index 0000000..f5ae3f7 --- /dev/null +++ b/internal/ui/progress.go @@ -0,0 +1,66 @@ +package ui + +import ( + "os" + "time" + + "github.com/jedib0t/go-pretty/v6/progress" +) + +var progressWriter progress.Writer + +func StartProgressWriter() { + pw := progress.NewWriter() + + pw.SetTrackerLength(25) + pw.SetMessageWidth(20) + pw.SetSortBy(progress.SortByPercentDsc) + pw.SetStyle(progress.StyleDefault) + pw.SetOutputWriter(os.Stderr) + pw.SetTrackerPosition(progress.PositionRight) + pw.SetUpdateFrequency(time.Millisecond * 100) + pw.Style().Colors = progress.StyleColorsExample + pw.Style().Options.PercentFormat = "%4.1f%%" + pw.Style().Visibility.Pinned = true + pw.Style().Visibility.ETA = true + pw.Style().Visibility.Value = true + + progressWriter = pw + go progressWriter.Render() +} + +func StopProgressWriter() { + if progressWriter != nil { + progressWriter.Stop() + time.Sleep(1 * time.Second) + } +} + +func TrackProgress(message string, total int) any { + tracker := progress.Tracker{Message: message, Total: int64(total), + Units: progress.UnitsDefault} + + if progressWriter != nil { + progressWriter.AppendTracker(&tracker) + } + + return &tracker +} + +func MarkTrackerAsDone(i any) { + if tracker, ok := i.(*progress.Tracker); ok { + tracker.MarkAsDone() + } +} + +func IncrementTrackerTotal(i any, count int) { + if tracker, ok := i.(*progress.Tracker); ok { + tracker.UpdateTotal(tracker.Total + int64(count)) + } +} + +func IncrementProgress(i any, count int) { + if tracker, ok := i.(*progress.Tracker); ok { + tracker.Increment(int64(count)) + } +} diff --git a/internal/ui/spinner.go b/internal/ui/spinner.go new file mode 100644 index 0000000..3147a3b --- /dev/null +++ b/internal/ui/spinner.go @@ -0,0 +1,39 @@ +package ui + +import ( + "fmt" + "time" +) + +var spinnerChan chan bool + +func StartSpinner(msg string) { + style := `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` + frames := []rune(style) + length := len(frames) + + spinnerChan = make(chan bool) + + ticker := time.NewTicker(100 * time.Millisecond) + go func() { + pos := 0 + + for { + select { + case <-spinnerChan: + ticker.Stop() + return + case <-ticker.C: + fmt.Printf("\r%s ... %s", msg, string(frames[pos%length])) + pos += 1 + } + } + }() +} + +func StopSpinner() { + spinnerChan <- true + + fmt.Printf("\r") + fmt.Println() +} diff --git a/internal/ui/ui.go b/internal/ui/ui.go index 1379ff8..dbcb131 100644 --- a/internal/ui/ui.go +++ b/internal/ui/ui.go @@ -2,96 +2,9 @@ package ui import ( "fmt" - "time" - - "github.com/jedib0t/go-pretty/v6/progress" + "os" ) -var progressWriter progress.Writer -var spinnerChan chan bool - -func StartSpinner(msg string) { - style := `⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏` - frames := []rune(style) - length := len(frames) - - spinnerChan = make(chan bool) - - ticker := time.NewTicker(100 * time.Millisecond) - go func() { - pos := 0 - - for { - select { - case <-spinnerChan: - ticker.Stop() - return - case <-ticker.C: - fmt.Printf("\r%s ... %s", msg, string(frames[pos%length])) - pos += 1 - } - } - }() -} - -func StopSpinner() { - spinnerChan <- true - - fmt.Printf("\r") - fmt.Println() -} - -func StartProgressWriter() { - pw := progress.NewWriter() - - pw.SetTrackerLength(25) - pw.SetMessageWidth(20) - pw.SetSortBy(progress.SortByPercentDsc) - pw.SetStyle(progress.StyleDefault) - pw.SetTrackerPosition(progress.PositionRight) - pw.SetUpdateFrequency(time.Millisecond * 100) - pw.Style().Colors = progress.StyleColorsExample - pw.Style().Options.PercentFormat = "%4.1f%%" - pw.Style().Visibility.Pinned = true - pw.Style().Visibility.ETA = true - pw.Style().Visibility.Value = true - - progressWriter = pw - go progressWriter.Render() -} - -func StopProgressWriter() { - if progressWriter != nil { - progressWriter.Stop() - time.Sleep(1 * time.Second) - } -} - -func TrackProgress(message string, total int) any { - tracker := progress.Tracker{Message: message, Total: int64(total), - Units: progress.UnitsDefault} - - if progressWriter != nil { - progressWriter.AppendTracker(&tracker) - } - - return &tracker -} - -func MarkTrackerAsDone(i any) { - if tracker, ok := i.(*progress.Tracker); ok { - tracker.MarkAsDone() - } -} - -func IncrementTrackerTotal(i any, count int) { - if tracker, ok := i.(*progress.Tracker); ok { - tracker.UpdateTotal(tracker.Total + int64(count)) - } -} - -func IncrementProgress(i any, count int) { - if tracker, ok := i.(*progress.Tracker); ok { - tracker.Increment(int64(count)) - } +func PrintBanner(s string) { + fmt.Fprintf(os.Stderr, s) } diff --git a/main.go b/main.go index 379f504..175d8bb 100644 --- a/main.go +++ b/main.go @@ -7,6 +7,7 @@ import ( "strconv" "github.com/safedep/dry/utils" + "github.com/safedep/vet/internal/ui" "github.com/safedep/vet/pkg/common/logger" "github.com/spf13/cobra" ) @@ -68,7 +69,7 @@ func main() { func printBanner() { bRet, err := strconv.ParseBool(os.Getenv("VET_DISABLE_BANNER")) if (err != nil) || (!bRet) { - fmt.Print(banner) + ui.PrintBanner(banner) } } From b5457c23b137f7ebce16c1d19115ed86f6dcbe83 Mon Sep 17 00:00:00 2001 From: abhisek Date: Thu, 16 Feb 2023 17:06:12 +0530 Subject: [PATCH 5/5] Fix #11: Add support for tags in summary report table --- go.mod | 2 +- go.sum | 2 ++ pkg/reporter/summary.go | 41 ++++++++++++++++++++++++++++++----------- 3 files changed, 33 insertions(+), 12 deletions(-) diff --git a/go.mod b/go.mod index 1d4a60c..2879bbd 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,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-20230203134955-367834d99b1c + github.com/safedep/dry v0.0.0-20230216112435-385c68e56634 github.com/sirupsen/logrus v1.9.0 github.com/spf13/cobra v1.6.1 github.com/stretchr/testify v1.8.1 diff --git a/go.sum b/go.sum index a22dc2e..f123b45 100644 --- a/go.sum +++ b/go.sum @@ -45,6 +45,8 @@ github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJ github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/safedep/dry v0.0.0-20230203134955-367834d99b1c h1:zbhTBT463mwcIuCq89GT8pFTU8UtGalBWFaa/wsgVXA= github.com/safedep/dry v0.0.0-20230203134955-367834d99b1c/go.mod h1:yZ8R6kv4pR0yertVoxgBmnN4bvHT8TLubE7aahpWDDk= +github.com/safedep/dry v0.0.0-20230216112435-385c68e56634 h1:JRIzwT2Xo7TFH2O1gMJpHS5Fn6jDJdF+/+2JdyhzI3A= +github.com/safedep/dry v0.0.0-20230216112435-385c68e56634/go.mod h1:yZ8R6kv4pR0yertVoxgBmnN4bvHT8TLubE7aahpWDDk= 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/summary.go b/pkg/reporter/summary.go index be53aba..f9a5fed 100644 --- a/pkg/reporter/summary.go +++ b/pkg/reporter/summary.go @@ -26,12 +26,17 @@ const ( summaryWeightUnpopular = 2 summaryWeightMajorDrift = 2 + tagVuln = "vulnerabiity" + tagUnpopular = "low popularity" + tagDrift = "drift" + summaryReportMaxUpgradeAdvice = 5 ) type summaryReporterRemediationData struct { pkg *models.Package score int + tags []string } type summaryReporter struct { @@ -100,7 +105,7 @@ func (r *summaryReporter) processForVersionDrift(pkg *models.Package) { driftType, _ := semver.Diff(version, latestVersion) if driftType.IsMajor() { r.summary.metrics.drifts += 1 - r.addPkgForRemediationAdvice(pkg, summaryWeightMajorDrift) + r.addPkgForRemediationAdvice(pkg, summaryWeightMajorDrift, tagDrift) } } @@ -121,7 +126,7 @@ func (r *summaryReporter) processForPopularity(pkg *models.Package) { if (strings.EqualFold(projectType, "github")) && (starsCount < 10) { r.summary.metrics.unpopular += 1 - r.addPkgForRemediationAdvice(pkg, summaryWeightUnpopular) + r.addPkgForRemediationAdvice(pkg, summaryWeightUnpopular, tagUnpopular) } } } @@ -141,33 +146,39 @@ func (r *summaryReporter) processForVulns(pkg *models.Package) { switch risk { case insightapi.PackageVulnerabilitySeveritiesRiskCRITICAL: r.summary.vulns.critical += 1 - r.addPkgForRemediationAdvice(pkg, summaryWeightCriticalVuln) + r.addPkgForRemediationAdvice(pkg, summaryWeightCriticalVuln, tagVuln) break case insightapi.PackageVulnerabilitySeveritiesRiskHIGH: r.summary.vulns.high += 1 - r.addPkgForRemediationAdvice(pkg, summaryWeightHighVuln) + r.addPkgForRemediationAdvice(pkg, summaryWeightHighVuln, tagVuln) break case insightapi.PackageVulnerabilitySeveritiesRiskMEDIUM: r.summary.vulns.medium += 1 - r.addPkgForRemediationAdvice(pkg, summaryWeightMediumVuln) + r.addPkgForRemediationAdvice(pkg, summaryWeightMediumVuln, tagVuln) break case insightapi.PackageVulnerabilitySeveritiesRiskLOW: r.summary.vulns.low += 1 - r.addPkgForRemediationAdvice(pkg, summaryWeightLowVuln) + r.addPkgForRemediationAdvice(pkg, summaryWeightLowVuln, tagVuln) break } } } } -func (r *summaryReporter) addPkgForRemediationAdvice(pkg *models.Package, weight int) { +func (r *summaryReporter) addPkgForRemediationAdvice(pkg *models.Package, + weight int, tag string) { if _, ok := r.remediationScores[pkg.Id()]; !ok { r.remediationScores[pkg.Id()] = &summaryReporterRemediationData{ - pkg: pkg, + pkg: pkg, + tags: []string{}, } } r.remediationScores[pkg.Id()].score += weight + + if utils.FindInSlice(r.remediationScores[pkg.Id()].tags, tag) == -1 { + r.remediationScores[pkg.Id()].tags = append(r.remediationScores[pkg.Id()].tags, tag) + } } func (r *summaryReporter) Finish() error { @@ -185,9 +196,6 @@ func (r *summaryReporter) Finish() error { r.renderRemediationAdvice() fmt.Println() - fmt.Println("Install as a security gate in CI for incremental scan and blocking risky dependencies") - fmt.Println("Run `vet ci` to generate CI scripts") - fmt.Println() fmt.Println("Run with `vet --filter=\"...\"` for custom filters to identify risky libraries") fmt.Println() fmt.Println("For more details", text.Bold.Sprint("https://github.com/safedep/vet")) @@ -236,6 +244,17 @@ func (r *summaryReporter) renderRemediationAdvice() { utils.SafelyGetValue(insight.PackageCurrentVersion), sp.score, }) + + tagText := "" + for _, t := range sp.tags { + tagText += text.BgMagenta.Sprint(" "+t+" ") + " " + } + + tbl.AppendRow(table.Row{ + tagText, "", "", + }) + + tbl.AppendSeparator() } tbl.Render()