Skip to content

Commit

Permalink
feat(sbom): add VEX support (aquasecurity#4053)
Browse files Browse the repository at this point in the history
  • Loading branch information
knqyf263 committed Apr 27, 2023
1 parent 5eab464 commit 11a5b91
Show file tree
Hide file tree
Showing 39 changed files with 1,103 additions and 124 deletions.
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_sbom.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ trivy sbom [flags] SBOM_PATH
-t, --template string output template
--token string for authentication in client/server mode
--token-header string specify a header name for token in client/server mode (default "Trivy-Token")
--vex string [EXPERIMENTAL] file path to VEX
--vuln-type string comma-separated list of vulnerability types (os,library) (default "os,library")
```

Expand Down
181 changes: 181 additions & 0 deletions docs/docs/supply-chain/vex.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,181 @@
# Vulnerability Exploitability Exchange (VEX)

!!! warning "EXPERIMENTAL"
This feature might change without preserving backwards compatibility.

Trivy supports filtering detected vulnerabilities using [the Vulnerability Exploitability Exchange (VEX)](https://www.ntia.gov/files/ntia/publications/vex_one-page_summary.pdf), a standardized format for sharing and exchanging information about vulnerabilities.
By providing VEX alongside the Software Bill of Materials (SBOM) during scanning, it is possible to filter vulnerabilities based on their status.
Currently, Trivy supports the following two formats:

- [CycloneDX](https://cyclonedx.org/capabilities/vex/)
- [OpenVEX](https://github.com/openvex/spec)

This is still an experimental implementation, with only minimal functionality added.

## CycloneDX
There are [two VEX formats](https://cyclonedx.org/capabilities/vex/) for CycloneDX:

- Independent BOM and VEX BOM
- BOM With Embedded VEX

Trivy only supports the Independent BOM and VEX BOM format, so you need to provide a separate VEX file alongside the SBOM.
The input SBOM format must be in CycloneDX format.

The following steps are required:

1. Generate a CycloneDX SBOM
2. Create a VEX based on the SBOM generated in step 1
3. Provide the VEX when scanning the CycloneDX SBOM

### Generating the SBOM
You can generate a CycloneDX SBOM with Trivy as follows:

```shell
$ trivy image --format cyclonedx --output debian11.sbom.cdx debian:11
```

### Create the VEX
Next, create a VEX based on the generated SBOM.
Multiple vulnerability statuses can be defined under `vulnerabilities`.
Take a look at the example below.

```
$ cat <<EOF > trivy.vex.cdx
{
"bomFormat": "CycloneDX",
"specVersion": "1.4",
"version": 1,
"vulnerabilities": [
{
"id": "CVE-2020-8911",
"analysis": {
"state": "not_affected",
"justification": "code_not_reachable",
"response": ["will_not_fix", "update"],
"detail": "The vulnerable function is not called"
},
"affects": [
{
"ref": "urn:cdx:3e671687-395b-41f5-a30f-a58921a69b79/1#pkg:golang/github.com/aws/aws-sdk-go@1.44.234"
}
]
}
]
}
EOF
```

This is a VEX document in the CycloneDX format.
The vulnerability ID, such as a CVE-ID or GHSA-ID, should be placed in `vulnerabilities.id`.
When the `analysis.state` is set to `not_affected`, Trivy will not detect the vulnerability.

BOM-Links must be placed in `affects.ref`.
The BOM-Link has the following syntax and consists of three elements:

```
urn:cdx:serialNumber/version#bom-ref
```

- serialNumber
- version
- bom-ref

These values must be obtained from the CycloneDX SBOM.
Please note that while the serialNumber starts with `urn:uuid:`, the BOM-Link starts with `urn:cdx:`.

The `bom-ref` must contain the BOM-Ref of the package affected by the vulnerability.
In the example above, since the Go package `github.com/aws/aws-sdk-go` is affected by CVE-2020-8911, it was necessary to specify the SBOM's BOM-Ref, `pkg:golang/github.com/aws/aws-sdk-go@1.44.234`.

For more details on CycloneDX VEX and BOM-Link, please refer to the following links:

- [CycloneDX VEX](https://cyclonedx.org/capabilities/vex/)
- [BOM-Link](https://cyclonedx.org/capabilities/bomlink/)
- [Examples](https://github.com/CycloneDX/bom-examples/tree/master)

### Scan SBOM with VEX
Provide the VEX when scanning the CycloneDX SBOM.

```
$ trivy sbom trivy.sbom.cdx --vex trivy.vex.cdx
...
2023-04-13T12:55:44.838+0300 INFO Filtered out the detected vulnerability {"VEX format": "CycloneDX", "vulnerability-id": "CVE-2020-8911", "status": "not_affected", "justification": "code_not_reachable"}
go.mod (gomod)
==============
Total: 1 (UNKNOWN: 0, LOW: 1, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
┌───────────────────────────┬───────────────┬──────────┬───────────────────┬───────────────┬────────────────────────────────────────────────────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │ Title │
├───────────────────────────┼───────────────┼──────────┼───────────────────┼───────────────┼────────────────────────────────────────────────────────────┤
│ github.com/aws/aws-sdk-go │ CVE-2020-8912 │ LOW │ 1.44.234 │ │ aws-sdk-go: In-band key negotiation issue in AWS S3 Crypto │
│ │ │ │ │ │ SDK for golang... │
│ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-8912 │
└───────────────────────────┴───────────────┴──────────┴───────────────────┴───────────────┴────────────────────────────────────────────────────────────┘
```

CVE-2020-8911 is no longer shown as it is filtered out according to the given CycloneDX VEX document.

## OpenVEX
Trivy also supports [OpenVEX](https://github.com/openvex/spec) that is designed to be minimal, compliant, interoperable, and embeddable.
Since OpenVEX aims to be SBOM format agnostic, both CycloneDX and SPDX formats are available for use as input SBOMs in Trivy.

The following steps are required:

1. Generate a SBOM (CycloneDX or SPDX)
2. Create a VEX based on the SBOM generated in step 1
3. Provide the VEX when scanning the SBOM

### Generating the SBOM
You can generate a CycloneDX or SPDX SBOM with Trivy as follows:

```shell
$ trivy image --format spdx-json --output debian11.spdx.json debian:11
```

### Create the VEX
Please see also [the example](https://github.com/openvex/examples).
The product identifiers differ depending on the SBOM format the VEX references.

- SPDX: [Package URL (PURL)](https://github.com/package-url/purl-spec)
- CycloneDX: [BOM-Link](https://cyclonedx.org/capabilities/bomlink/)

```
$ cat <<EOF > trivy.openvex
{
"@context": "https://openvex.dev/ns",
"@id": "https://openvex.dev/docs/public/vex-2e67563e128250cbcb3e98930df948dd053e43271d70dc50cfa22d57e03fe96f",
"author": "Aqua Security",
"timestamp": "2023-01-16T19:07:16.853479631-06:00",
"version": "1",
"statements": [
{
"vulnerability": "CVE-2019-8457",
"products": [
"pkg:deb/debian/libdb5.3@5.3.28+dfsg1-0.8?arch=arm64\u0026distro=debian-11.6"
],
"status": "not_affected",
"justification": "vulnerable_code_not_in_execute_path"
}
]
}
EOF
```

In the above example, PURLs, located in `packages.externalRefs.referenceLocator` are used since the input SBOM format is SPDX.

As for CycloneDX BOM-Link, please reference [the CycloneDX section](#cyclonedx).

### Scan SBOM with VEX
Provide the VEX when scanning the SBOM.

```
$ trivy sbom debian11.spdx.json --vex trivy.openvex
...
2023-04-26T17:56:05.358+0300 INFO Filtered out the detected vulnerability {"VEX format": "OpenVEX", "vulnerability-id": "CVE-2019-8457", "status": "not_affected", "justification": "vulnerable_code_not_in_execute_path"}
debian11.spdx.json (debian 11.6)
================================
Total: 80 (UNKNOWN: 0, LOW: 58, MEDIUM: 6, HIGH: 16, CRITICAL: 0)
```

CVE-2019-8457 is no longer shown as it is filtered out according to the given OpenVEX document.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -72,12 +72,14 @@ require (
github.com/open-policy-agent/opa v0.45.0
github.com/opencontainers/go-digest v1.0.0
github.com/opencontainers/image-spec v1.1.0-rc2.0.20221020182949-4df8887994e8
github.com/openvex/go-vex v0.2.0
github.com/owenrumney/go-sarif/v2 v2.1.2
github.com/package-url/packageurl-go v0.1.1-0.20220428063043-89078438f170
github.com/samber/lo v1.37.0
github.com/saracen/walker v0.1.3
github.com/secure-systems-lab/go-securesystemslib v0.5.0
github.com/sigstore/rekor v1.1.0
github.com/sirupsen/logrus v1.9.0
github.com/sosedoff/gitkit v0.3.0
github.com/spdx/tools-golang v0.5.0
github.com/spf13/cast v1.5.0
Expand Down Expand Up @@ -327,7 +329,6 @@ require (
github.com/sergi/go-diff v1.2.0 // indirect
github.com/shibumi/go-pathspec v1.3.0 // indirect
github.com/shopspring/decimal v1.2.0 // indirect
github.com/sirupsen/logrus v1.9.0 // indirect
github.com/skeema/knownhosts v1.1.0 // indirect
github.com/spf13/afero v1.9.3 // indirect
github.com/spf13/jwalterweatherman v1.1.0 // indirect
Expand Down
4 changes: 3 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -1427,6 +1427,8 @@ github.com/opencontainers/selinux v1.11.0 h1:+5Zbo97w3Lbmb3PeqQtpmTkMwsW5nRI3YaL
github.com/opencontainers/selinux v1.11.0/go.mod h1:E5dMC3VPuVvVHDYmi78qvhJp8+M586T4DlDRYpFkyec=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc=
github.com/openvex/go-vex v0.2.0 h1:7Q6VzdpZSZzYUyXB1dio/9LCGHp1iL3JldC+hMsbFg0=
github.com/openvex/go-vex v0.2.0/go.mod h1:jYmYbhQAO/0hquryXND/jMVDBcob8/KkVgzUEUAHsFI=
github.com/owenrumney/go-sarif v1.1.1/go.mod h1:dNDiPlF04ESR/6fHlPyq7gHKmrM0sHUvAGjsoh8ZH0U=
github.com/owenrumney/go-sarif/v2 v2.1.2 h1:PMDK7tXShJ9zsB7bfvlpADH5NEw1dfA9xwU8Xtdj73U=
github.com/owenrumney/go-sarif/v2 v2.1.2/go.mod h1:MSqMMx9WqlBSY7pXoOZWgEsVB4FDNfhcaXDA1j6Sr+w=
Expand Down Expand Up @@ -1510,7 +1512,7 @@ github.com/rogpeppe/go-internal v1.1.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFR
github.com/rogpeppe/go-internal v1.2.2/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE=
github.com/rogpeppe/go-internal v1.8.1 h1:geMPLpDpQOgVyCg5z5GoRwLHepNdb71NXb67XFkP+Eg=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rubenv/sql-migrate v1.2.0 h1:fOXMPLMd41sK7Tg75SXDec15k3zg5WNV6SjuDRiNfcU=
github.com/rubenv/sql-migrate v1.2.0/go.mod h1:Z5uVnq7vrIrPmHbVFfR4YLHRZquxeHpckCnRq0P/K9Y=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
Expand Down
22 changes: 14 additions & 8 deletions integration/sbom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,9 @@ func TestSBOM(t *testing.T) {
{
Target: "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)",
Vulnerabilities: []types.DetectedVulnerability{
{Ref: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"},
{Ref: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{Ref: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
},
},
},
Expand All @@ -91,9 +91,9 @@ func TestSBOM(t *testing.T) {
{
Target: "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)",
Vulnerabilities: []types.DetectedVulnerability{
{Ref: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"},
{Ref: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{Ref: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
{PkgRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"},
},
},
},
Expand All @@ -107,7 +107,13 @@ func TestSBOM(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs := []string{
"--cache-dir", cacheDir, "sbom", "-q", "--skip-db-update", "--format", tt.args.format,
"--cache-dir",
cacheDir,
"sbom",
"-q",
"--skip-db-update",
"--format",
tt.args.format,
}

// Set up the output file
Expand Down Expand Up @@ -154,7 +160,7 @@ func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant typ
for i, result := range overrideWant.Results {
want.Results[i].Target = result.Target
for j, vuln := range result.Vulnerabilities {
want.Results[i].Vulnerabilities[j].Ref = vuln.Ref
want.Results[i].Vulnerabilities[j].PkgRef = vuln.PkgRef
}
}

Expand Down
1 change: 1 addition & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,7 @@ nav:
- SBOM: docs/supply-chain/attestation/sbom.md
- Cosign Vulnerability Scan Record: docs/supply-chain/attestation/vuln.md
- SBOM Attestation in Rekor: docs/supply-chain/attestation/rekor.md
- VEX: docs/supply-chain/vex.md
- Compliance:
- Reports: docs/compliance/compliance.md
- Advanced:
Expand Down
22 changes: 4 additions & 18 deletions pkg/cloud/report/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,13 @@ import (
"sort"
"time"

ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"

"github.com/aquasecurity/defsec/pkg/scan"
"github.com/aquasecurity/tml"

ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/flag"

"github.com/aquasecurity/trivy/pkg/report"

"github.com/aquasecurity/trivy/pkg/result"

"github.com/aquasecurity/defsec/pkg/scan"
pkgReport "github.com/aquasecurity/trivy/pkg/report"
"github.com/aquasecurity/trivy/pkg/result"
"github.com/aquasecurity/trivy/pkg/types"
)

Expand Down Expand Up @@ -70,16 +65,7 @@ func Write(rep *Report, opt flag.Options, fromCache bool) error {
for _, resultsAtTime := range rep.Results {
for _, res := range resultsAtTime.Results {
resCopy := res
if err := result.Filter(
ctx,
&resCopy,
opt.Severities,
false,
false,
"",
"",
nil,
); err != nil {
if err := result.FilterResult(ctx, &resCopy, result.FilterOption{Severities: opt.Severities}); err != nil {
return err
}
sort.Slice(resCopy.Misconfigurations, func(i, j int) bool {
Expand Down
20 changes: 12 additions & 8 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -271,16 +271,20 @@ func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initialize
}

func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error) {
results := report.Results

// Filter results
for i := range results {
err := result.Filter(ctx, &results[i], opts.Severities, opts.IgnoreUnfixed, opts.IncludeNonFailures,
opts.IgnoreFile, opts.IgnorePolicy, opts.IgnoredLicenses)
if err != nil {
return types.Report{}, xerrors.Errorf("unable to filter vulnerabilities: %w", err)
}
err := result.Filter(ctx, report, result.FilterOption{
Severities: opts.Severities,
IgnoreUnfixed: opts.IgnoreUnfixed,
IncludeNonFailures: opts.IncludeNonFailures,
IgnoreFile: opts.IgnoreFile,
PolicyFile: opts.IgnorePolicy,
IgnoreLicenses: opts.IgnoredLicenses,
VEXPath: opts.VEXPath,
})
if err != nil {
return types.Report{}, xerrors.Errorf("filtering error: %w", err)
}

return report, nil
}

Expand Down
2 changes: 1 addition & 1 deletion pkg/detector/library/detect.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ func detect(driver Driver, libs []ftypes.Package) ([]types.DetectedVulnerability
for i := range vulns {
vulns[i].Layer = lib.Layer
vulns[i].PkgPath = lib.FilePath
vulns[i].Ref = lib.Ref
vulns[i].PkgRef = lib.Ref
}
vulnerabilities = append(vulnerabilities, vulns...)
}
Expand Down
2 changes: 1 addition & 1 deletion pkg/detector/ospkg/alma/alma.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Packa
PkgName: pkg.Name,
InstalledVersion: installed,
FixedVersion: fixedVersion.String(),
Ref: pkg.Ref,
PkgRef: pkg.Ref,
Layer: pkg.Layer,
DataSource: adv.DataSource,
Custom: adv.Custom,
Expand Down
2 changes: 1 addition & 1 deletion pkg/detector/ospkg/alpine/alpine.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ func (s *Scanner) Detect(osVer string, repo *ftypes.Repository, pkgs []ftypes.Pa
InstalledVersion: utils.FormatVersion(pkg),
FixedVersion: adv.FixedVersion,
Layer: pkg.Layer,
Ref: pkg.Ref,
PkgRef: pkg.Ref,
Custom: adv.Custom,
DataSource: adv.DataSource,
})
Expand Down
2 changes: 1 addition & 1 deletion pkg/detector/ospkg/amazon/amazon.go
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Packa
PkgName: pkg.Name,
InstalledVersion: installed,
FixedVersion: adv.FixedVersion,
Ref: pkg.Ref,
PkgRef: pkg.Ref,
Layer: pkg.Layer,
Custom: adv.Custom,
DataSource: adv.DataSource,
Expand Down
Loading

0 comments on commit 11a5b91

Please sign in to comment.