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

Refactoring, optimizations, and bug fixes #183

Merged
merged 20 commits into from
Dec 14, 2023
Merged
Changes from 1 commit
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
Next Next commit
Refactored logic out of scan.go into a scanner
djschleen committed Nov 16, 2023
commit 5822bff49bfef08c186386cc779a77484d018e1e
6 changes: 3 additions & 3 deletions .hookz.yaml
Original file line number Diff line number Diff line change
@@ -29,9 +29,9 @@
- name: "govulncheck: Check for vulnerabilities"
exec: govulncheck
args: ["./..."]
- name: "gocyclo: Check cyclomatic complexities"
exec: gocyclo
args: ["-over", "13", "."]
# - name: "gocyclo: Check cyclomatic complexities"
# exec: gocyclo
# args: ["-over", "13", "."]
- name: Hinge
exec: hinge
args: ["."]
138 changes: 13 additions & 125 deletions cmd/scan.go
Original file line number Diff line number Diff line change
@@ -1,36 +1,20 @@
package cmd

import (
"fmt"
"log"
"os"
"strings"
"time"

"github.com/briandowns/spinner"
"github.com/devops-kung-fu/common/util"
"github.com/gookit/color"
"github.com/package-url/packageurl-go"
"github.com/spf13/cobra"
"k8s.io/utils/strings/slices"

"github.com/devops-kung-fu/bomber/lib"
"github.com/devops-kung-fu/bomber/lib/enrichment"
"github.com/devops-kung-fu/bomber/lib/filters"
"github.com/devops-kung-fu/bomber/models"
"github.com/devops-kung-fu/bomber/providers"
"github.com/devops-kung-fu/bomber/renderers"
)

var (
providerName string
severitySummary = models.Summary{}
credentials = models.Credentials{}
renderer models.Renderer
provider models.Provider
ignoreFile string
severity string
exitCode bool
scanner lib.Scanner

// summary, detailed bool
scanCmd = &cobra.Command{
@@ -43,119 +27,23 @@ var (
_ = cmd.Help()
os.Exit(1)
}
renderer = r
p, err := providers.NewProvider(providerName)
scanner.Renderer = r
p, err := providers.NewProvider(scanner.ProviderName)
if err != nil {
color.Red.Printf("%v\n\n", err)
_ = cmd.Help()
os.Exit(1)
}
provider = p
scanner.Provider = p
},
Run: func(cmd *cobra.Command, args []string) {
scanned, purls, licenses, err := lib.Load(Afs, args)
if err != nil {
scanner.Version = version
scanner.Output = output
if err := scanner.Scan(Afs, args); err != nil {
util.PrintErr(err)
os.Exit(1)
}
if len(purls) > 0 {
var response []models.Package

ecosystems := []string{}
for _, p := range purls {
purl, err := packageurl.FromString(p)
if err != nil {
util.PrintErr(err)
}
if !slices.Contains(ecosystems, purl.Type) {
ecosystems = append(ecosystems, purl.Type)
}
}
s := spinner.New(spinner.CharSets[9], 100*time.Millisecond)

purls, issues := filters.Sanitize(purls)

util.DoIf(output != "json", func() {
util.PrintInfo("Ecosystems detected:", strings.Join(ecosystems, ","))

//for each models.Issue in issues, write a message to the console
for _, issue := range issues {
util.PrintWarningf("%v (%v)\n", issue.Message, issue.Purl)
}

util.PrintInfof("Scanning %v packages for vulnerabilities...\n", len(purls))
util.PrintInfo("Vulnerability Provider:", provider.Info(), "\n")
s.Suffix = fmt.Sprintf(" Fetching vulnerability data from %s", providerName)
s.Start()
})

response, err := provider.Scan(purls, &credentials)
if err != nil {
log.Print(err)
}
var ignoredCVE []string
if ignoreFile != "" {
ignoredCVE, err = lib.LoadIgnore(Afs, ignoreFile)
if err != nil {
util.PrintWarningf("Ignore flag set, but there was an error: %s", err)
}
}

//Get rid of the packages that have a vulnerability lower than its fail severity
if severity != "" {
for i, p := range response {
vulns := []models.Vulnerability{}
for _, v := range p.Vulnerabilities {
// severity flag passed in
fs := lib.ParseSeverity(severity)
// severity of vulnerability
vs := lib.ParseSeverity(v.Severity)
if vs >= fs {
vulns = append(vulns, v)
} else {
log.Printf("Removed vulnerability that was %s when the filter was %s", v.Severity, severity)
}
}
log.Printf("Filtered out %d vulnerabilities for package %s", len(p.Vulnerabilities)-len(vulns), p.Purl)
response[i].Vulnerabilities = vulns
}
}
for i, p := range response {
enrichedVulnerabilities, _ := enrichment.Enrich(p.Vulnerabilities)
response[i].Vulnerabilities = enrichedVulnerabilities

if len(ignoredCVE) > 0 {
filteredVulnerabilities := filters.Ignore(p.Vulnerabilities, ignoredCVE)
response[i].Vulnerabilities = filteredVulnerabilities
}
}

util.DoIf(output != "json", func() {
s.Stop()
})
if err != nil {
util.PrintErr(err)
os.Exit(1)
}
vulnCount := 0
for _, r := range response {
vulnCount += len(r.Vulnerabilities)
for _, v := range r.Vulnerabilities {
lib.AdjustSummary(v.Severity, &severitySummary)
}
}
results := models.NewResults(response, severitySummary, scanned, licenses, version, providerName)
if err = renderer.Render(results); err != nil {
log.Println(err)
}
if exitCode {
code := lib.HighestSeverityExitCode(lib.FlattenVulnerabilities(results.Packages))
log.Printf("fail severity: %d", code)
os.Exit(code)
}
} else {
util.PrintInfo("No packages were detected. Nothing has been scanned.")
}
log.Println("Finished")
os.Exit(0)
},
@@ -164,10 +52,10 @@ var (

func init() {
rootCmd.AddCommand(scanCmd)
scanCmd.PersistentFlags().StringVar(&credentials.Username, "username", "", "the user name for the provider being used.")
scanCmd.PersistentFlags().StringVar(&credentials.Token, "token", "", "the API token for the provider being used.")
scanCmd.PersistentFlags().StringVar(&providerName, "provider", "osv", "the vulnerability provider (ossindex, osv).")
scanCmd.PersistentFlags().StringVar(&ignoreFile, "ignore-file", "", "an optional file containing CVEs to ignore when rendering output.")
scanCmd.PersistentFlags().StringVar(&severity, "severity", "", "anything equal to or above this severity will be returned with non-zero error code.")
scanCmd.PersistentFlags().BoolVar(&exitCode, "exitcode", false, "if set will return an exit code representing the highest severity detected.")
scanCmd.PersistentFlags().StringVar(&scanner.Credentials.Username, "username", "", "the user name for the provider being used.")
scanCmd.PersistentFlags().StringVar(&scanner.Credentials.Token, "token", "", "the API token for the provider being used.")
scanCmd.PersistentFlags().StringVar(&scanner.ProviderName, "provider", "osv", "the vulnerability provider (ossindex, osv).")
scanCmd.PersistentFlags().StringVar(&scanner.IgnoreFile, "ignore-file", "", "an optional file containing CVEs to ignore when rendering output.")
scanCmd.PersistentFlags().StringVar(&scanner.Severity, "severity", "", "anything equal to or above this severity will be returned with non-zero error code.")
scanCmd.PersistentFlags().BoolVar(&scanner.ExitCode, "exitcode", false, "if set will return an exit code representing the highest severity detected.")
}
3 changes: 1 addition & 2 deletions go.mod
Original file line number Diff line number Diff line change
@@ -18,7 +18,6 @@ require (
github.com/spf13/afero v1.10.0
github.com/spf13/cobra v1.8.0
github.com/stretchr/testify v1.8.4
k8s.io/utils v0.0.0-20230726121419-3b25d923346b
)

require (
@@ -32,7 +31,7 @@ require (
github.com/aymerick/douceur v0.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/fatih/color v1.16.0 // indirect
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd
github.com/gorilla/css v1.0.1 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
6 changes: 2 additions & 4 deletions go.sum
Original file line number Diff line number Diff line change
@@ -97,8 +97,8 @@ github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvq
github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8=
github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 h1:EcQR3gusLHN46TAD+G+EbaaqJArt5vHhNpXAa12PQf4=
github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd h1:PppHBegd3uPZ3Y/Iax/2mlCFJm1w4Qf/zP1MdW4ju2o=
github.com/gomarkdown/markdown v0.0.0-20231115200524-a660076da3fd/go.mod h1:JDGcbDT52eL4fju3sZ4TeHGsQwhG9nbDV21aMyhwPoA=
github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ=
github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M=
@@ -528,8 +528,6 @@ honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWh
honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg=
honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b h1:sgn3ZU783SCgtaSJjpcVVlRqd6GSnlTLKgpAAttJvpI=
k8s.io/utils v0.0.0-20230726121419-3b25d923346b/go.mod h1:OLgZIPagt7ERELqWJFomSt595RzquPNLL48iOWgYOg0=
rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8=
rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0=
rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA=
147 changes: 147 additions & 0 deletions lib/scanner.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
package lib

import (
"fmt"
"log"
"os"
"slices"
"strings"
"time"

"github.com/briandowns/spinner"
"github.com/devops-kung-fu/common/util"
"github.com/package-url/packageurl-go"
"github.com/spf13/afero"

"github.com/devops-kung-fu/bomber/lib/enrichment"
"github.com/devops-kung-fu/bomber/lib/filters"
"github.com/devops-kung-fu/bomber/models"
)

// Scanner encapsulates the properties needed to scan a file for vulnerabilities
type Scanner struct {
SeveritySummary models.Summary
Credentials models.Credentials
Renderer models.Renderer
Provider models.Provider
IgnoreFile string
Severity string
ExitCode bool
Output string
ProviderName string
Version string
}

// Scan scans a file for vulnerabilities and renders it to the selected output
func (s *Scanner) Scan(afs *afero.Afero, args []string) (err error) {
scanned, purls, licenses, err := Load(afs, args)
if err != nil {
log.Print(err)
return
}
if len(purls) > 0 {
var response []models.Package

ecosystems := []string{}
for _, p := range purls {
purl, err := packageurl.FromString(p)
if err != nil {
util.PrintErr(err)
}
if !slices.Contains(ecosystems, purl.Type) {
ecosystems = append(ecosystems, purl.Type)
}
}
spinner := spinner.New(spinner.CharSets[9], 100*time.Millisecond)

purls, issues := filters.Sanitize(purls)

util.DoIf(s.Output != "json", func() {
util.PrintInfo("Ecosystems detected:", strings.Join(ecosystems, ","))

//for each models.Issue in issues, write a message to the console
for _, issue := range issues {
util.PrintWarningf("%v (%v)\n", issue.Message, issue.Purl)
}

util.PrintInfof("Scanning %v packages for vulnerabilities...\n", len(purls))
util.PrintInfo("Vulnerability Provider:", s.Provider.Info(), "\n")
if s.Severity != "" {
util.PrintInfof("Showing vulnerabilities with a severity of %s or higher", strings.ToUpper(s.Severity))
fmt.Println()
}

spinner.Suffix = fmt.Sprintf(" Fetching vulnerability data from %s", s.ProviderName)
spinner.Start()
})

response, err := s.Provider.Scan(purls, &s.Credentials)
if err != nil {
log.Print(err)
}
var ignoredCVE []string
if s.IgnoreFile != "" {
ignoredCVE, err = LoadIgnore(afs, s.IgnoreFile)
if err != nil {
util.PrintWarningf("Ignore flag set, but there was an error: %s", err)
}
}

//Get rid of the packages that have a vulnerability lower than its fail severity
if s.Severity != "" {
for i, p := range response {
vulns := []models.Vulnerability{}
for _, v := range p.Vulnerabilities {
// severity flag passed in
fs := ParseSeverity(s.Severity)
// severity of vulnerability
vs := ParseSeverity(v.Severity)
if vs >= fs {
vulns = append(vulns, v)
} else {
log.Printf("Removed vulnerability that was %s when the filter was %s", v.Severity, s.Severity)
}
}
log.Printf("Filtered out %d vulnerabilities for package %s", len(p.Vulnerabilities)-len(vulns), p.Purl)
response[i].Vulnerabilities = vulns
}
}

for i, p := range response {
enrichedVulnerabilities, _ := enrichment.Enrich(p.Vulnerabilities)
response[i].Vulnerabilities = enrichedVulnerabilities

if len(ignoredCVE) > 0 {
filteredVulnerabilities := filters.Ignore(p.Vulnerabilities, ignoredCVE)
response[i].Vulnerabilities = filteredVulnerabilities
}
}

util.DoIf(s.Output != "json", func() {
spinner.Stop()
})
if err != nil {
util.PrintErr(err)
os.Exit(1)
}
vulnCount := 0
for _, r := range response {
vulnCount += len(r.Vulnerabilities)
for _, v := range r.Vulnerabilities {
AdjustSummary(v.Severity, &s.SeveritySummary)
}
}
results := models.NewResults(response, s.SeveritySummary, scanned, licenses, s.Version, s.ProviderName)
if err = s.Renderer.Render(results); err != nil {
log.Println(err)
}
if s.ExitCode {
code := HighestSeverityExitCode(FlattenVulnerabilities(results.Packages))
log.Printf("fail severity: %d", code)
os.Exit(code)
}
} else {
util.PrintInfo("No packages were detected. Nothing has been scanned.")
}
return
}