Skip to content

Commit

Permalink
feat: introduce package UIDs for improved vulnerability mapping (#6583)
Browse files Browse the repository at this point in the history
Signed-off-by: knqyf263 <knqyf263@gmail.com>
  • Loading branch information
knqyf263 committed May 3, 2024
1 parent 770b141 commit 998f750
Show file tree
Hide file tree
Showing 71 changed files with 1,531 additions and 874 deletions.
4 changes: 2 additions & 2 deletions Dockerfile.protoc
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/protoc-gen-twirp@v8.1.0
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
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
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
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
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
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/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?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/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?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/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810",
},
},
{
PkgIdentifier: ftypes.PkgIdentifier{
BOMRef: "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?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/bash@4.2.46-31.el7?arch=x86_64&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[1].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?arch=x86_64&epoch=1&distro=centos-7.6.1810"
want.Results[0].Vulnerabilities[2].PkgIdentifier.BOMRef = "pkg:rpm/centos/openssl-libs@1.0.2k-16.el7?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
Expand Up @@ -57,7 +57,8 @@
"PkgID": "openssl-libs@1.1.1k-4.el8.x86_64",
"PkgName": "openssl-libs",
"PkgIdentifier": {
"PURL": "pkg:rpm/alma/openssl-libs@1.1.1k-4.el8?arch=x86_64\u0026distro=alma-8.5\u0026epoch=1"
"PURL": "pkg:rpm/alma/openssl-libs@1.1.1k-4.el8?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
Expand Up @@ -59,7 +59,8 @@
"PkgID": "libcrypto1.1@1.1.1c-r0",
"PkgName": "libcrypto1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?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": "libcrypto1.1@1.1.1c-r0",
"PkgName": "libcrypto1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/libcrypto1.1@1.1.1c-r0?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": "libssl1.1@1.1.1c-r0",
"PkgName": "libssl1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?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": "libssl1.1@1.1.1c-r0",
"PkgName": "libssl1.1",
"PkgIdentifier": {
"PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2"
"PURL": "pkg:apk/alpine/libssl1.1@1.1.1c-r0?arch=x86_64\u0026distro=3.10.2",
"UID": "e132dcfcc51772ef"
},
"InstalledVersion": "1.1.1c-r0",
"FixedVersion": "1.1.1d-r2",
Expand Down

0 comments on commit 998f750

Please sign in to comment.