Skip to content

Commit

Permalink
feat: EPSS support (#89)
Browse files Browse the repository at this point in the history
* Updated documentation for EPSS
* STDOUT now renders EPSS percentage
* Centralization of enrichment
* Initial HTML rendering of EPSS score
* Proper HTML rendering
* Updates testify
* Updates test coverage
* Version bump to 0.4.0
* Updates CodeQL to v2
* Updates workflow versions
  • Loading branch information
djschleen committed Dec 6, 2022
1 parent ed7b8f0 commit 4747311
Show file tree
Hide file tree
Showing 25 changed files with 312 additions and 2,302 deletions.
8 changes: 4 additions & 4 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,11 +35,11 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3

# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
Expand All @@ -50,7 +50,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2

# ℹ️ Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
Expand All @@ -64,4 +64,4 @@ jobs:
# make release

- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2
2 changes: 1 addition & 1 deletion .github/workflows/go-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
-
name: Setup Go
uses: actions/setup-go@v2
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
steps:
-
name: Checkout
uses: actions/checkout@v2
uses: actions/checkout@v3
with:
fetch-depth: 0
-
Expand All @@ -24,7 +24,7 @@ jobs:
go-version: 1.19
-
name: Run GoReleaser
uses: goreleaser/goreleaser-action@v2
uses: goreleaser/goreleaser-action@v3
with:
distribution: goreleaser
version: ${{ env.GITHUB_REF_NAME }}
Expand All @@ -35,11 +35,11 @@ jobs:
name: Generate SBOM
uses: anchore/sbom-action@v0
with:
artifact-name: bomber.spdx.json
artifact-name: bomber.cyclonedx.json
path: .
-
name: Release SBOM
uses: anchore/sbom-action/publish-sbom@v0
with:
sbom-artifact-match: ".*\\.spdx.json$"
sbom-artifact-match: ".*\\.cyclonedx.json$"

4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
"Distro",
"DKFM",
"dpkg",
"Encricher",
"Epss",
"errcheck",
"gofmt",
"gomod",
Expand All @@ -21,6 +23,7 @@
"ignoretests",
"incredibles",
"Infof",
"jedib",
"JSONAPI",
"kirinlabs",
"kisielk",
Expand All @@ -32,6 +35,7 @@
"sbom",
"sboms",
"Smashicons",
"snyk",
"Sonatype",
"SPDXID",
"structs",
Expand Down
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ There are quite a few SBOM formats available today. ```bomber``` supports the fo

At this time, please note that [OSV](doc/providers/osv.md) is free and does not require any credentials to use, [Sonatype OSS Index](doc/providers/ossindex.md) is free but requires you to register and obtain a token, and [Snyk](doc/providers/snyk.md) support requires a Snyk license.

In addition to data ```bomber``` collects from Providers, it also [enriches](#data-enrichment) vulnerability data with extra information such as exploitation probabilities.

### Provider Support

Please note that *each provider supports different ecosystems*, so if you're not seeing any vulnerabilities in one, try another. An ecosystem is simply the package manager, or type of package. Examples include rpm, npm, gems, etc. It is important to understand that each provider may report different vulnerabilities. If in doubt, look at a few of them.
Expand Down Expand Up @@ -147,6 +149,16 @@ Example command:
bomber scan bad-bom.json --output=json > filename.json
```

## 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/)

### Exploit Prediction Scoring System (EPSS)

[EPSS](https://www.first.org/epss/) stands for Exploit Prediction Scoring System and is framework that predicts the probability of a vulnerability being exploited. [EPSS](https://www.first.org/epss/) is often used to help in identifying high risk vulnerabilities to prioritize for remediation.

[EPSS](https://www.first.org/epss/) uses a percentage for probability. So if you see 94, the score is that is trying to say that vulnerability has a 94% probability of exploitation. And it stands to reason that a vulnerability with a score like 94, is something that deserves immediate attention, where a vulnerability with a score of like say 20 deserves to take a lower priority.

## Advanced stuff

If you wish, you can set two environment variables to store your credentials, and not have to type them on the command line. Check out the [Environment Variables](####Environment-Variables) information later in this README.
Expand Down Expand Up @@ -198,3 +210,4 @@ Thank you to [Sonatype](https://sonatype.com) for providing a wicked tool like t

Many thanks to our friends and fellow ```bomber``` contributors at [Snyk](https://snyk.io) for creating a provider, and coding up processing an SBOM from STDIN. You guys rock.

EPSS description comes from the team at [Nucleus](https://nucleussec.com/blog/what-is-epss/). Thank you!
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import (
)

var (
version = "0.3.5"
version = "0.4.0"
output string
//Afs stores a global OS Filesystem that is used throughout bomber
Afs = &afero.Afero{Fs: afero.NewOsFs()}
Expand Down
6 changes: 6 additions & 0 deletions cmd/scan.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"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/models"
"github.com/devops-kung-fu/bomber/providers"
"github.com/devops-kung-fu/bomber/renderers"
Expand Down Expand Up @@ -77,6 +78,11 @@ var (

response, err = provider.Scan(purls, &credentials)

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

util.DoIf(output != "json", func() {
s.Stop()
})
Expand Down
7 changes: 7 additions & 0 deletions formats/cyclonedx/cyclonedx.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ func TestBytes() []byte {
"version": "v0.6.0",
"cpe": "cpe:2.3:a:CycloneDX:cyclonedx-go:v0.6.0:*:*:*:*:*:*:*",
"purl": "pkg:golang/github.com/CycloneDX/cyclonedx-go@v0.6.0",
"licenses": [
{
"license": {
"id": "MIT"
}
}
],
"properties": [{
"name": "syft:package:metadataType",
"value": "GolangBinMetadata"
Expand Down
11 changes: 11 additions & 0 deletions formats/cyclonedx/cyclonedx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,14 @@ func TestPurls(t *testing.T) {
assert.Len(t, purls, 1)
assert.Equal(t, "pkg:golang/github.com/CycloneDX/cyclonedx-go@v0.6.0", purls[0])
}

func TestLicenses(t *testing.T) {
var sbom cyclone.BOM
err := json.Unmarshal(TestBytes(), &sbom)
assert.NoError(t, err)
assert.NotNil(t, sbom)

licenses := Licenses(&sbom)

assert.Len(t, licenses, 1)
}
1 change: 0 additions & 1 deletion formats/spdx/spdx.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,6 @@ func (bom *BOM) Purls() (purls []string) {

// Licenses returns a slice of strings that contain all of the licenses found in the SBOM
func (bom *BOM) Licenses() (licenses []string) {

return
}

Expand Down
7 changes: 7 additions & 0 deletions formats/spdx/spdx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ func TestPurls(t *testing.T) {
assert.Len(t, purls, 1)
assert.Equal(t, "pkg:golang/github.com/CycloneDX/cyclonedx-go@v0.6.0", purls[0])
}

func TestLicenses(t *testing.T) {
var sbom BOM
licenses := sbom.Licenses()

assert.Len(t, licenses, 0)
}
1 change: 0 additions & 1 deletion formats/syft/syft.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ func (bom *BOM) Purls() (purls []string) {

// Licenses returns a slice of strings that contain all of the licenses found in the SBOM
func (bom *BOM) Licenses() (licenses []string) {

return
}

Expand Down
7 changes: 7 additions & 0 deletions formats/syft/syft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,10 @@ func TestPurls(t *testing.T) {
assert.Len(t, purls, 1)
assert.Equal(t, "pkg:golang/github.com/CycloneDX/cyclonedx-go@v0.6.0", purls[0])
}

func TestLicenses(t *testing.T) {
var sbom BOM
licenses := sbom.Licenses()

assert.Len(t, licenses, 0)
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ require (
github.com/remeh/sizedwaitgroup v1.0.0
github.com/spf13/afero v1.9.3
github.com/spf13/cobra v1.6.1
github.com/stretchr/testify v1.8.0
github.com/stretchr/testify v1.8.1
k8s.io/utils v0.0.0-20221108210102-8e77b1f39fe2
)

Expand Down
4 changes: 3 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -193,13 +193,15 @@ 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=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.4/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.0 h1:pSgiaMZlXftHpm5L7V1+rVB+AZJydKsMxsQBIJw4PKk=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778/go.mod h1:2MuV+tbUrU1zIOPMxZ5EncGwgmMJsa+9ucAQZXxsObs=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no=
github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM=
Expand Down
49 changes: 49 additions & 0 deletions lib/enrichment/epss.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package enrichment

import (
"encoding/json"
"fmt"
"log"
"strings"

"github.com/kirinlabs/HttpRequest"

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

const epssBaseURL = "https://api.first.org/data/v1/epss?cve="

// Enrich adds epss score data to vulnerabilities
func Enrich(vulnerabilities []models.Vulnerability) (enriched []models.Vulnerability, err error) {
identifiers := []string{}
for _, v := range vulnerabilities {
identifiers = append(identifiers, v.ID)
}
req := HttpRequest.NewRequest()
resp, _ := req.JSON().Get(fmt.Sprintf("%s%s", epssBaseURL, strings.Join(identifiers, ",")))
defer func() {
_ = resp.Close()
}()

log.Println("EPSS Response Status:", resp.StatusCode())

body, _ := resp.Body()
if resp.StatusCode() == 200 {
var epss models.Epss
err = json.Unmarshal(body, &epss)
if err != nil {
return
}
log.Println("EPSS response total:", epss.Total)

for i, v := range vulnerabilities {
for _, sv := range epss.Scores {
if sv.Cve == v.ID {
vulnerabilities[i].Epss = sv
}
}
}
return vulnerabilities, nil
}
return
}
34 changes: 34 additions & 0 deletions lib/enrichment/epss_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package enrichment

import (
"testing"

"github.com/stretchr/testify/assert"

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

func TestEnrich(t *testing.T) {
vulnerabilities := []models.Vulnerability{
{
ID: "CVE-2021-43138",
},
{
ID: "CVE-2020-15084",
},
{
ID: "CVE-2020-28282",
},
{
ID: "sonatype-2020-1214",
},
}
enriched, err := Enrich(vulnerabilities)

assert.NoError(t, err)
assert.Len(t, enriched, 4)

assert.Empty(t, enriched[3].Epss.Cve)
assert.Equal(t, enriched[0].Epss.Cve, "CVE-2021-43138")

}
5 changes: 5 additions & 0 deletions models/interfaces.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,8 @@ type Provider interface {
type Renderer interface {
Render(results Results) error
}

// Encricher defines methods that can enrich a collection of vulnerabilities
type Enricher interface {
Enrich(vulnerabilities []Vulnerability) (enriched []Vulnerability, err error)
}
25 changes: 24 additions & 1 deletion models/structs.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package models

import "time"
import (
"time"
)

// Package encapsulates information about a package/component and it's vulnerabilities
type Package struct {
Expand All @@ -22,6 +24,7 @@ type Vulnerability struct {
Reference string `json:"reference,omitempty"`
ExternalReferences []interface{} `json:"externalReferences,omitempty"`
Severity string `json:"severity,omitempty"`
Epss EpssScore `json:"epss,omitempty"`
}

// Summary is a struct used to keep track of severity counts
Expand Down Expand Up @@ -79,3 +82,23 @@ func NewResults(packages []Package, summary Summary, scanned []ScannedFile, lice
Licenses: licenses,
}
}

// Epss encapsulates the response of a query to the Epss scoring API
type Epss struct {
Status string `json:"status,omitempty"`
StatusCode int64 `json:"status-code,omitempty"`
Version string `json:"version,omitempty"`
Access string `json:"access,omitempty"`
Total int64 `json:"total,omitempty"`
Offset int64 `json:"offset,omitempty"`
Limit int64 `json:"limit,omitempty"`
Scores []EpssScore `json:"data,omitempty"`
}

// EpssScore contains epss score data for a specific CVE
type EpssScore struct {
Cve string `json:"cve,omitempty"`
Epss string `json:"epss,omitempty"`
Percentile string `json:"percentile,omitempty"`
Date string `json:"date,omitempty"`
}

0 comments on commit 4747311

Please sign in to comment.