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

feat: introduce package UIDs for improved vulnerability mapping #6583

Merged
merged 11 commits into from
May 3, 2024
4 changes: 2 additions & 2 deletions Dockerfile.protoc
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ RUN curl --retry 5 -OL https://github.com/protocolbuffers/protobuf/releases/down

# Install Go tools
RUN go install github.com/twitchtv/twirp/[email protected]
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.27.1
RUN go install github.com/magefile/mage@v1.14.0
RUN go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.34.0
RUN go install github.com/magefile/mage@v1.15.0

ENV TRIVY_PROTOC_CONTAINER=true
17 changes: 13 additions & 4 deletions integration/client_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -283,7 +283,9 @@ func TestClientServer(t *testing.T) {
osArgs = append(osArgs, "--secret-config", tt.args.secretConfig)
}

runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{})
runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{
override: overrideUID,
})
})
}
}
Expand Down Expand Up @@ -397,7 +399,9 @@ func TestClientServerWithFormat(t *testing.T) {
t.Setenv("AWS_ACCOUNT_ID", "123456789012")
osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden)

runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{})
runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{
override: overrideUID,
})
})
}
}
Expand Down Expand Up @@ -475,7 +479,10 @@ func TestClientServerWithToken(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
osArgs := setupClient(t, tt.args, addr, cacheDir, tt.golden)
runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{wantErr: tt.wantErr})
runTest(t, osArgs, tt.golden, "", types.FormatJSON, runOptions{
override: overrideUID,
wantErr: tt.wantErr,
})
})
}
}
Expand All @@ -501,7 +508,9 @@ func TestClientServerWithRedis(t *testing.T) {
osArgs := setupClient(t, testArgs, addr, cacheDir, golden)

// Run Trivy client
runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{})
runTest(t, osArgs, golden, "", types.FormatJSON, runOptions{
override: overrideUID,
})
})

// Terminate the Redis container
Expand Down
37 changes: 34 additions & 3 deletions integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,10 @@ func readSpdxJson(t *testing.T, filePath string) *spdx.Document {
return bom
}

type OverrideFunc func(t *testing.T, want, got *types.Report)
type runOptions struct {
wantErr string
override func(want, got *types.Report)
override OverrideFunc
fakeUUID string
}

Expand Down Expand Up @@ -262,11 +263,11 @@ func compareRawFiles(t *testing.T, wantFile, gotFile string) {
assert.EqualValues(t, string(want), string(got))
}

func compareReports(t *testing.T, wantFile, gotFile string, override func(want, got *types.Report)) {
func compareReports(t *testing.T, wantFile, gotFile string, override func(t *testing.T, want, got *types.Report)) {
want := readReport(t, wantFile)
got := readReport(t, gotFile)
if override != nil {
override(&want, &got)
override(t, &want, &got)
}
assert.Equal(t, want, got)
}
Expand Down Expand Up @@ -307,3 +308,33 @@ func validateReport(t *testing.T, schema string, report any) {
assert.True(t, valid, strings.Join(errs, "\n"))
}
}

func overrideFuncs(funcs ...OverrideFunc) OverrideFunc {
return func(t *testing.T, want, got *types.Report) {
for _, f := range funcs {
if f == nil {
continue
}
f(t, want, got)
}
}
}

// overrideUID only checks for the presence of the package UID and clears the UID;
// the UID is calculated from the package metadata, but the UID does not match
// as it varies slightly depending on the mode of scanning, e.g. the digest of the layer.
func overrideUID(t *testing.T, want, got *types.Report) {
for i, result := range got.Results {
for j, vuln := range result.Vulnerabilities {
assert.NotEmptyf(t, vuln.PkgIdentifier.UID, "UID is empty: %s", vuln.VulnerabilityID)
// Do not compare UID as the package metadata is slightly different between the tests,
// causing different UIDs.
got.Results[i].Vulnerabilities[j].PkgIdentifier.UID = ""
}
}
for i, result := range want.Results {
for j := range result.Vulnerabilities {
want.Results[i].Vulnerabilities[j].PkgIdentifier.UID = ""
}
}
}
4 changes: 2 additions & 2 deletions integration/registry_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -202,12 +202,12 @@ func TestRegistry(t *testing.T) {
// Run Trivy
runTest(t, osArgs, tc.golden, "", types.FormatJSON, runOptions{
wantErr: tc.wantErr,
override: func(_, got *types.Report) {
override: overrideFuncs(overrideUID, func(t *testing.T, _, got *types.Report) {
got.ArtifactName = tc.imageName
for i := range got.Results {
got.Results[i].Target = fmt.Sprintf("%s (alpine 3.10.2)", tc.imageName)
}
},
}),
})
})
}
Expand Down
6 changes: 3 additions & 3 deletions integration/repo_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ func TestRepository(t *testing.T) {
name string
args args
golden string
override func(want, got *types.Report)
override func(t *testing.T, want, got *types.Report)
}{
{
name: "gomod",
Expand Down Expand Up @@ -378,7 +378,7 @@ func TestRepository(t *testing.T) {
skipFiles: []string{"testdata/fixtures/repo/gomod/submod2/go.mod"},
},
golden: "testdata/gomod-skip.json.golden",
override: func(want, _ *types.Report) {
override: func(_ *testing.T, want, _ *types.Report) {
want.ArtifactType = ftypes.ArtifactFilesystem
},
},
Expand All @@ -392,7 +392,7 @@ func TestRepository(t *testing.T) {
input: "testdata/fixtures/repo/custom-policy",
},
golden: "testdata/dockerfile-custom-policies.json.golden",
override: func(want, got *types.Report) {
override: func(_ *testing.T, want, got *types.Report) {
want.ArtifactType = ftypes.ArtifactFilesystem
},
},
Expand Down
134 changes: 56 additions & 78 deletions integration/sbom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@ import (
"path/filepath"
"testing"

ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

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

Expand All @@ -25,7 +25,7 @@ func TestSBOM(t *testing.T) {
name string
args args
golden string
override types.Report
override OverrideFunc
}{
{
name: "centos7 cyclonedx",
Expand All @@ -35,31 +35,17 @@ func TestSBOM(t *testing.T) {
artifactType: "cyclonedx",
},
golden: "testdata/centos-7.json.golden",
override: types.Report{
ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.json",
ArtifactType: ftypes.ArtifactType("cyclonedx"),
Results: types.Results{
{
Target: "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)",
Vulnerabilities: []types.DetectedVulnerability{
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/[email protected]?arch=x86_64&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810",
},
},
},
},
},
override: func(t *testing.T, want, got *types.Report) {
want.ArtifactName = "testdata/fixtures/sbom/centos-7-cyclonedx.json"
want.ArtifactType = ftypes.ArtifactCycloneDX

require.Len(t, got.Results, 1)
want.Results[0].Target = "testdata/fixtures/sbom/centos-7-cyclonedx.json (centos 7.6.1810)"

require.Len(t, got.Results[0].Vulnerabilities, 3)
want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "pkg:rpm/centos/[email protected]?arch=x86_64&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810"
},
},
{
Expand Down Expand Up @@ -88,31 +74,17 @@ func TestSBOM(t *testing.T) {
artifactType: "cyclonedx",
},
golden: "testdata/centos-7.json.golden",
override: types.Report{
ArtifactName: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl",
ArtifactType: ftypes.ArtifactType("cyclonedx"),
Results: types.Results{
{
Target: "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)",
Vulnerabilities: []types.DetectedVulnerability{
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/[email protected]?arch=x86_64&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810",
},
},
},
},
},
override: func(t *testing.T, want, got *types.Report) {
want.ArtifactName = "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl"
want.ArtifactType = ftypes.ArtifactCycloneDX

require.Len(t, got.Results, 1)
want.Results[0].Target = "testdata/fixtures/sbom/centos-7-cyclonedx.intoto.jsonl (centos 7.6.1810)"

require.Len(t, got.Results[0].Vulnerabilities, 3)
want.Results[0].Vulnerabilities[0].PkgIdentifier.BOMRef = "pkg:rpm/centos/[email protected]?arch=x86_64&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/[email protected]?arch=x86_64&epoch=1&distro=centos-7.6.1810"
},
},
{
Expand All @@ -123,14 +95,12 @@ func TestSBOM(t *testing.T) {
artifactType: "spdx",
},
golden: "testdata/centos-7.json.golden",
override: types.Report{
ArtifactName: "testdata/fixtures/sbom/centos-7-spdx.txt",
ArtifactType: ftypes.ArtifactType("spdx"),
Results: types.Results{
{
Target: "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)",
},
},
override: func(t *testing.T, want, got *types.Report) {
want.ArtifactName = "testdata/fixtures/sbom/centos-7-spdx.txt"
want.ArtifactType = ftypes.ArtifactSPDX

require.Len(t, got.Results, 1)
want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.txt (centos 7.6.1810)"
},
},
{
Expand All @@ -141,14 +111,12 @@ func TestSBOM(t *testing.T) {
artifactType: "spdx",
},
golden: "testdata/centos-7.json.golden",
override: types.Report{
ArtifactName: "testdata/fixtures/sbom/centos-7-spdx.json",
ArtifactType: ftypes.ArtifactType("spdx"),
Results: types.Results{
{
Target: "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)",
},
},
override: func(t *testing.T, want, got *types.Report) {
want.ArtifactName = "testdata/fixtures/sbom/centos-7-spdx.json"
want.ArtifactType = ftypes.ArtifactSPDX

require.Len(t, got.Results, 1)
want.Results[0].Target = "testdata/fixtures/sbom/centos-7-spdx.json (centos 7.6.1810)"
},
},
{
Expand Down Expand Up @@ -195,20 +163,30 @@ func TestSBOM(t *testing.T) {
osArgs = append(osArgs, tt.args.input)

// Run "trivy sbom"
err := execute(osArgs)
assert.NoError(t, err)

// Compare want and got
switch tt.args.format {
case "json":
compareSBOMReports(t, tt.golden, outputFile, tt.override)
default:
require.Fail(t, "invalid format", "format: %s", tt.args.format)
}
runTest(t, osArgs, tt.golden, outputFile, types.Format(tt.args.format), runOptions{
override: overrideFuncs(overrideSBOMReport, overrideUID, tt.override),
})
})
}
}

func overrideSBOMReport(t *testing.T, want, got *types.Report) {
want.Metadata.ImageID = ""
want.Metadata.ImageConfig = v1.ConfigFile{}
want.Metadata.DiffIDs = nil
for i, result := range want.Results {
for j := range result.Vulnerabilities {
want.Results[i].Vulnerabilities[j].Layer.DiffID = ""
}
}

// when running on Windows FS
got.ArtifactName = filepath.ToSlash(filepath.Clean(got.ArtifactName))
for i, result := range got.Results {
got.Results[i].Target = filepath.ToSlash(filepath.Clean(result.Target))
}
}

// TODO(teppei): merge into compareReports
func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant types.Report) {
want := readReport(t, wantFile)
Expand Down
3 changes: 2 additions & 1 deletion integration/testdata/almalinux-8.json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,8 @@
"PkgID": "[email protected]_64",
"PkgName": "openssl-libs",
"PkgIdentifier": {
"PURL": "pkg:rpm/alma/[email protected]?arch=x86_64\u0026distro=alma-8.5\u0026epoch=1"
"PURL": "pkg:rpm/alma/[email protected]?arch=x86_64\u0026distro=alma-8.5\u0026epoch=1",
"UID": "3f965238234faa63"
},
"InstalledVersion": "1:1.1.1k-4.el8",
"FixedVersion": "1:1.1.1k-5.el8_5",
Expand Down
12 changes: 8 additions & 4 deletions integration/testdata/alpine-310.json.golden
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@
"PkgID": "[email protected]",
"PkgName": "libcrypto1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2",
"UID": "c6c116a4441ec6de"
},
"InstalledVersion": "1.1.1c-r0",
"FixedVersion": "1.1.1d-r0",
Expand Down Expand Up @@ -131,7 +132,8 @@
"PkgID": "[email protected]",
"PkgName": "libcrypto1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2",
"UID": "c6c116a4441ec6de"
},
"InstalledVersion": "1.1.1c-r0",
"FixedVersion": "1.1.1d-r2",
Expand Down Expand Up @@ -213,7 +215,8 @@
"PkgID": "[email protected]",
"PkgName": "libssl1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2",
"UID": "e132dcfcc51772ef"
},
"InstalledVersion": "1.1.1c-r0",
"FixedVersion": "1.1.1d-r0",
Expand Down Expand Up @@ -285,7 +288,8 @@
"PkgID": "[email protected]",
"PkgName": "libssl1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/[email protected]?arch=x86_64\u0026distro=3.10.2",
"UID": "e132dcfcc51772ef"
},
"InstalledVersion": "1.1.1c-r0",
"FixedVersion": "1.1.1d-r2",
Expand Down
Loading
Loading