diff --git a/.goreleaser.yaml b/.goreleaser.yaml index 85238d9..540acd3 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -36,7 +36,7 @@ checksum: nfpms: - maintainer: DevOps Kung Fu Mafia - description: Scans SBoMs for security vulnerabilities. + description: Scans SBOMs for security vulnerabilities. homepage: https://github.com/devops-kung-fu/bomber license: MPL formats: diff --git a/.vscode/launch.json b/.vscode/launch.json index ab42517..8c18b06 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -4,10 +4,6 @@ // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 "version": "0.2.0", "configurations": [ - - - - { "name": "Debug Folder (OSV)", "type": "go", @@ -33,12 +29,36 @@ "args": ["--provider=ossindex", "--debug=true", "scan", "./_TESTDATA_/sbom"] }, { - "name": "Debug File (OSS Index - juiceshop, fail = moderate)", + "name": "Debug File (OSS Index - juiceshop, severity = moderate)", "type": "go", "request": "launch", "mode": "auto", "program": "${workspaceFolder}/main.go", - "args": ["--debug=true", "--fail=critical", "--provider=ossindex", "scan", "./_TESTDATA_/sbom/juiceshop.cyclonedx.json"] + "args": ["--debug=true", "--severity=moderate", "--provider=ossindex", "scan", "./_TESTDATA_/sbom/juiceshop.cyclonedx.json"] + }, + { + "name": "Debug File (OSS Index - juiceshop, severity = critical)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": ["--debug=true", "--exitcode","--severity=moderate", "--provider=ossindex", "scan", "./_TESTDATA_/sbom/juiceshop.cyclonedx.json"] + }, + { + "name": "> Debug File (OSS Index - railsgoat, severity = critical, exitcode)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": ["--debug=true", "--exitcode","--severity=critical", "--provider=ossindex", "scan", "./_TESTDATA_/sbom/railsgoat.cyclonedx.json"] + }, + { + "name": "Debug File (OSS Index - juiceshop, severity = moderate, exitcode)", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceFolder}/main.go", + "args": ["--debug=true", "--severity=moderate", "--provider=ossindex", "scan", "./_TESTDATA_/sbom/juiceshop.cyclonedx.json"] }, { "name": "Debug File (OSV- cargo-valid)", diff --git a/README.md b/README.md index 692bbb5..e56d6fe 100644 --- a/README.md +++ b/README.md @@ -165,6 +165,29 @@ To use the ```bomber.ignore``` file, use the syntax as follows: bomber --ignore-file=bomber.ignore scan bom.json ``` +## Filtering Output + +You may set the severity level with the ```--severity``` flag in order to return specific vulnerability severities. For example, if you set ```--severity=moderate``` only vulnerabilities with a severity of ```MODERATE``` or above will be returned. + +For example, the following command will return only high and critical vulnerabilities. + +``` bash +bomber --severity=high scan bom.json +``` +## Highest Severity Return Codes (Experimental) + +Using the flag ```--exitcode```, will return with an exit code representing the highest vulnerability severity found. Without this flag you can expect an exit code of ```0``` for success, or ```1``` if an error was encountered. + +Assuming there is no error, the following values will be returned by ```bomber``` when ```--exitcode``` + +| Severity | Return Code | +|---|---| +| UNSPECIFIED (This is a status where the provider gives us something wacky, or no info) | 10 | +| LOW | 11 | +| MODERATE | 12 | +| HIGH | 13 | +| CRITICAL | 14 | + ## Data Enrichment ```bomber``` has the ability to enrich vulnerability data it obtains from the [Providers](#providers). The first "enricher" we have implemented for is for [EPSS](https://www.first.org/epss/) diff --git a/cmd/root.go b/cmd/root.go index f41312f..bae7c43 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -16,7 +16,7 @@ import ( ) var ( - version = "0.4.6" + version = "0.4.7" output string //Afs stores a global OS Filesystem that is used throughout bomber Afs = &afero.Afero{Fs: afero.NewOsFs()} @@ -25,7 +25,7 @@ var ( rootCmd = &cobra.Command{ Use: "bomber [flags] file", Example: " bomber scan --output html test.cyclonedx.json", - Short: "Scans SBoMs for security vulnerabilities.", + Short: "Scans SBOMs for security vulnerabilities.", Version: version, PersistentPreRun: func(cmd *cobra.Command, args []string) { if !debug { diff --git a/cmd/scan.go b/cmd/scan.go index b91e46f..525bf49 100644 --- a/cmd/scan.go +++ b/cmd/scan.go @@ -29,12 +29,13 @@ var ( renderer models.Renderer provider models.Provider ignoreFile string - failSeverity string + severity string + exitCode bool // summary, detailed bool scanCmd = &cobra.Command{ Use: "scan", - Short: "Scans a provided SBoM file or folder containing SBoMs for vulnerabilities.", + Short: "Scans a provided SBOM file or folder containing SBOMs for vulnerabilities.", PreRun: func(cmd *cobra.Command, args []string) { r, err := renderers.NewRenderer(output) if err != nil { @@ -101,16 +102,21 @@ var ( } //Get rid of the packages that have a vulnerability lower than its fail severity - if failSeverity != "" { + if severity != "" { for i, p := range response { vulns := []models.Vulnerability{} for _, v := range p.Vulnerabilities { - fs := int(lib.ParseFailSeverity(failSeverity)) + // 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 } } @@ -142,15 +148,16 @@ var ( if err = renderer.Render(results); err != nil { log.Println(err) } - if failSeverity != "" { - log.Printf("fail severity: %x\n", int(lib.ParseFailSeverity(failSeverity))) - os.Exit(int(lib.ParseFailSeverity(failSeverity))) + 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) }, } ) @@ -161,5 +168,6 @@ func init() { 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(&failSeverity, "fail", "undefined", "anything above this severity will be returned with non-zero error code.") + 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.") } diff --git a/go.mod b/go.mod index 36513c3..bc8cf8c 100644 --- a/go.mod +++ b/go.mod @@ -10,13 +10,13 @@ require ( github.com/devops-kung-fu/common v0.2.6 github.com/gookit/color v1.5.4 github.com/jarcoal/httpmock v1.3.0 - github.com/jedib0t/go-pretty/v6 v6.4.8 + github.com/jedib0t/go-pretty/v6 v6.4.9 github.com/kirinlabs/HttpRequest v1.1.1 github.com/microcosm-cc/bluemonday v1.0.26 github.com/package-url/packageurl-go v0.1.2 github.com/remeh/sizedwaitgroup v1.0.0 github.com/spf13/afero v1.10.0 - github.com/spf13/cobra v1.7.0 + github.com/spf13/cobra v1.8.0 github.com/stretchr/testify v1.8.4 k8s.io/utils v0.0.0-20230726121419-3b25d923346b ) @@ -25,15 +25,15 @@ require ( github.com/kr/pretty v0.3.0 // indirect github.com/rogpeppe/go-internal v1.8.0 // indirect golang.org/x/exp v0.0.0-20230202163644-54bba9f4231b // indirect - golang.org/x/term v0.13.0 // indirect + golang.org/x/term v0.14.0 // indirect ) require ( github.com/aymerick/douceur v0.2.0 // indirect github.com/davecgh/go-spew v1.1.1 // indirect - github.com/fatih/color v1.15.0 // indirect + github.com/fatih/color v1.16.0 // indirect github.com/gomarkdown/markdown v0.0.0-20230922112808-5421fefb8386 - github.com/gorilla/css v1.0.0 // indirect + 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 github.com/mattn/go-isatty v0.0.20 // indirect @@ -42,9 +42,9 @@ require ( github.com/rivo/uniseg v0.4.4 // indirect github.com/spf13/pflag v1.0.5 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - golang.org/x/net v0.17.0 // indirect - golang.org/x/sys v0.13.0 // indirect - golang.org/x/text v0.13.0 // indirect + golang.org/x/net v0.18.0 // indirect + golang.org/x/sys v0.14.0 // indirect + golang.org/x/text v0.14.0 // indirect gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) diff --git a/go.sum b/go.sum index e66d339..a5ebddd 100644 --- a/go.sum +++ b/go.sum @@ -54,7 +54,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= -github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/cpuguy83/go-md2man/v2 v2.0.3/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= @@ -67,8 +67,8 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= -github.com/fatih/color v1.15.0 h1:kOqh6YHBtK8aywxGerMG2Eq3H6Qgoqeo13Bk2Mv/nBs= -github.com/fatih/color v1.15.0/go.mod h1:0h5ZqXfHYED7Bhv2ZJamyIOUej9KtShiJESRwBDUSsw= +github.com/fatih/color v1.16.0 h1:zmkK9Ngbjj+K0yRhTVONQh1p/HknKYSlNT+vZCzyokM= +github.com/fatih/color v1.16.0/go.mod h1:fL2Sau1YI5c0pdGEVCbKQbLXB6edEj1ZgiY4NijnWvE= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= @@ -130,8 +130,8 @@ github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5m github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= github.com/gookit/color v1.5.4 h1:FZmqs7XOyGgCAxmWyPslpiok1k05wmY3SJTytgvYFs0= github.com/gookit/color v1.5.4/go.mod h1:pZJOeOS8DM43rXbp4AZo1n9zCU2qjpcRko0b6/QJi9w= -github.com/gorilla/css v1.0.0 h1:BQqNyPTi50JCFMTw/b67hByjMVXZRwGha6wxVGkeihY= -github.com/gorilla/css v1.0.0/go.mod h1:Dn721qIggHpt4+EFCcTLTU/vk5ySda2ReITrtgBl60c= +github.com/gorilla/css v1.0.1 h1:ntNaBIghp6JmvWnxbZKANoLyuXTPZ4cAMlo6RyhlbO8= +github.com/gorilla/css v1.0.1/go.mod h1:BvnYkspnSzMmwRK+b8/xgNPLiIuNZr6vbZBTPQ2A3b0= github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= @@ -140,8 +140,8 @@ github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2 github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= github.com/jarcoal/httpmock v1.3.0 h1:2RJ8GP0IIaWwcC9Fp2BmVi8Kog3v2Hn7VXM3fTd+nuc= github.com/jarcoal/httpmock v1.3.0/go.mod h1:3yb8rc4BI7TCBhFY8ng0gjuLKJNquuDNiPaZjnENuYg= -github.com/jedib0t/go-pretty/v6 v6.4.8 h1:HiNzyMSEpsBaduKhmK+CwcpulEeBrTmxutz4oX/oWkg= -github.com/jedib0t/go-pretty/v6 v6.4.8/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= +github.com/jedib0t/go-pretty/v6 v6.4.9 h1:vZ6bjGg2eBSrJn365qlxGcaWu09Id+LHtrfDWlB2Usc= +github.com/jedib0t/go-pretty/v6 v6.4.9/go.mod h1:Ndk3ase2CkQbXLLNf5QDHoYb6J9WtVfmHZu9n8rk2xs= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= github.com/kirinlabs/HttpRequest v1.1.1 h1:eBbFzpRd/Y7vQhRY30frHK3yAJiT1wDlB31Ryzyklc0= @@ -189,8 +189,8 @@ github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6po github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/spf13/afero v1.10.0 h1:EaGW2JJh15aKOejeuJ+wpFSHnbd7GE6Wvp3TsNhb6LY= github.com/spf13/afero v1.10.0/go.mod h1:UBogFpq8E9Hx+xc5CNTTEpTnuHVmXDwZcZcE1eb/UhQ= -github.com/spf13/cobra v1.7.0 h1:hyqWnYt1ZQShIddO5kBpj3vu05/++x6tJ6dg8EC572I= -github.com/spf13/cobra v1.7.0/go.mod h1:uLxZILRyS/50WlhOIKD7W6V5bgeIt+4sICxh6uRMrb0= +github.com/spf13/cobra v1.8.0 h1:7aJaZx1B85qltLMc546zn58BxxfZdR/W22ej9CFoEf0= +github.com/spf13/cobra v1.8.0/go.mod h1:WXLWApfZ71AjXPya3WOlMsY9yMs7YeiHhFVlvLyhcho= github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= @@ -296,8 +296,8 @@ golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM= -golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE= +golang.org/x/net v0.18.0 h1:mIYleuAkSbHh0tCv7RvjL3F6ZVbLjq4+R7zbOn3Kokg= +golang.org/x/net v0.18.0/go.mod h1:/czyP5RqHAH4odGYxBJ1qz0+CE5WZ+2j1YgoEo8F2jQ= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -355,11 +355,11 @@ golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE= -golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.14.0 h1:Vz7Qs629MkJkGyHxUlRHizWJRG2j8fbQKjELVSNhy7Q= +golang.org/x/sys v0.14.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= -golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek= -golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U= +golang.org/x/term v0.14.0 h1:LGK9IlZ8T9jvdy6cTdfKUCltatMFOehAQo9SRC46UQ8= +golang.org/x/term v0.14.0/go.mod h1:TySc+nGkYR6qt8km8wUhuFRTVSMIX3XPR58y2lC8vww= golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= @@ -368,8 +368,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= -golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= -golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= diff --git a/lib/util.go b/lib/util.go index 5eca455..ebcc6ec 100644 --- a/lib/util.go +++ b/lib/util.go @@ -45,35 +45,50 @@ func AdjustSummary(severity string, summary *models.Summary) { // ParseSeverity takes a severity string and returns an int func ParseSeverity(severity string) int { severity = strings.ToUpper(severity) - switch severity { case "LOW": - return 11 + return int(models.LOW) case "MODERATE": - return 12 + return int(models.MODERATE) case "HIGH": - return 13 + return int(models.HIGH) case "CRITICAL": - return 14 + return int(models.CRITICAL) + case "UNDEFINED": + return int(models.UNDEFINED) default: - return 10 + return 0 } } -// ParseFailSeverity takes a string and returns a FailSeverity enum -func ParseFailSeverity(s string) models.FailSeverity { - s = strings.ToLower(s) +// HighestSeverityExitCode returns the exit code of the highest vulnerability +func HighestSeverityExitCode(vulnerabilities []models.Vulnerability) int { + severityExitCodes := map[string]int{ + "UNDEFINED": int(models.UNDEFINED), + "LOW": int(models.LOW), + "MODERATE": int(models.MODERATE), + "HIGH": int(models.HIGH), + "CRITICAL": int(models.CRITICAL), + } - switch s { - case "low": - return models.LOW - case "moderate": - return models.MODERATE - case "high": - return models.HIGH - case "critical": - return models.CRITICAL - default: - return models.UNDEFINED + highestSeverity := "UNDEFINED" // Initialize with the lowest severity + for _, vulnerability := range vulnerabilities { + if exitCode, ok := severityExitCodes[vulnerability.Severity]; ok { + if exitCode > severityExitCodes[highestSeverity] { + highestSeverity = vulnerability.Severity + } + } } + + return severityExitCodes[highestSeverity] +} + +func FlattenVulnerabilities(packages []models.Package) []models.Vulnerability { + var flattenedVulnerabilities []models.Vulnerability + + for _, pkg := range packages { + flattenedVulnerabilities = append(flattenedVulnerabilities, pkg.Vulnerabilities...) + } + + return flattenedVulnerabilities } diff --git a/lib/util_test.go b/lib/util_test.go index 68a2979..1ac5fa8 100644 --- a/lib/util_test.go +++ b/lib/util_test.go @@ -80,7 +80,7 @@ func TestParseSeverity(t *testing.T) { t.Run("Invalid severity: undefined", func(t *testing.T) { severity := "invalid" - expected := 10 + expected := 0 result := ParseSeverity(severity) assert.Equal(t, expected, result) }) @@ -93,46 +93,54 @@ func TestParseSeverity(t *testing.T) { }) } -func TestParseFailSeverity(t *testing.T) { - t.Run("Valid severity: low", func(t *testing.T) { - s := "low" - expected := models.LOW - result := ParseFailSeverity(s) - assert.Equal(t, expected, result) - }) - - t.Run("Valid severity: moderate", func(t *testing.T) { - s := "moderate" - expected := models.MODERATE - result := ParseFailSeverity(s) - assert.Equal(t, expected, result) - }) - - t.Run("Valid severity: high", func(t *testing.T) { - s := "high" - expected := models.HIGH - result := ParseFailSeverity(s) - assert.Equal(t, expected, result) - }) - - t.Run("Valid severity: critical", func(t *testing.T) { - s := "critical" - expected := models.CRITICAL - result := ParseFailSeverity(s) - assert.Equal(t, expected, result) - }) - - t.Run("Invalid severity: undefined", func(t *testing.T) { - s := "invalid" - expected := models.UNDEFINED - result := ParseFailSeverity(s) - assert.Equal(t, expected, result) - }) +func TestHighestSeverityExitCode(t *testing.T) { + // Sample vulnerabilities with different severities + vulnerabilities := []models.Vulnerability{ + {Severity: "LOW"}, + {Severity: "CRITICAL"}, + {Severity: "MODERATE"}, + {Severity: "HIGH"}, + {Severity: "UNDEFINED"}, + } + + // Calculate the expected exit code based on the highest severity + expectedExitCode := 14 // CRITICAL has the highest severity + + // Call the function and check the result using assert + actualExitCode := HighestSeverityExitCode(vulnerabilities) + assert.Equal(t, expectedExitCode, actualExitCode) +} - t.Run("Mixed case severity: moderate", func(t *testing.T) { - s := "MoDerAte" - expected := models.MODERATE - result := ParseFailSeverity(s) - assert.Equal(t, expected, result) - }) +func TestFlattenVulnerabilities(t *testing.T) { + // Create some sample data for testing + pkg1 := models.Package{ + Purl: "pkg1", + Vulnerabilities: []models.Vulnerability{ + {DisplayName: "Vuln1", Severity: "LOW"}, + {DisplayName: "Vuln2", Severity: "HIGH"}, + }, + } + + pkg2 := models.Package{ + Purl: "pkg2", + Vulnerabilities: []models.Vulnerability{ + {DisplayName: "Vuln3", Severity: "MODERATE"}, + }, + } + + // Slice of Packages to test + packages := []models.Package{pkg1, pkg2} + + // Call the FlattenVulnerabilities function + flattenedVulnerabilities := FlattenVulnerabilities(packages) + + // Define the expected result + expectedVulnerabilities := []models.Vulnerability{ + {DisplayName: "Vuln1", Severity: "LOW"}, + {DisplayName: "Vuln2", Severity: "HIGH"}, + {DisplayName: "Vuln3", Severity: "MODERATE"}, + } + + // Check if the actual result matches the expected result using assert.ElementsMatch + assert.ElementsMatch(t, expectedVulnerabilities, flattenedVulnerabilities) }