mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-06 04:41:18 -08:00
535 lines
20 KiB
Go
535 lines
20 KiB
Go
//go:build integration || vm_integration || module_integration || k8s_integration
|
|
|
|
package integration
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"flag"
|
|
"fmt"
|
|
"io"
|
|
"net"
|
|
"os"
|
|
"path/filepath"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
cdx "github.com/CycloneDX/cyclonedx-go"
|
|
"github.com/samber/lo"
|
|
spdxjson "github.com/spdx/tools-golang/json"
|
|
"github.com/spdx/tools-golang/spdx"
|
|
"github.com/spdx/tools-golang/spdxlib"
|
|
"github.com/spf13/viper"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"github.com/xeipuuv/gojsonschema"
|
|
|
|
"github.com/aquasecurity/trivy-db/pkg/metadata"
|
|
"github.com/aquasecurity/trivy/internal/dbtest"
|
|
"github.com/aquasecurity/trivy/internal/testutil"
|
|
"github.com/aquasecurity/trivy/pkg/clock"
|
|
"github.com/aquasecurity/trivy/pkg/commands"
|
|
"github.com/aquasecurity/trivy/pkg/db"
|
|
"github.com/aquasecurity/trivy/pkg/types"
|
|
"github.com/aquasecurity/trivy/pkg/uuid"
|
|
"github.com/aquasecurity/trivy/pkg/vex/repo"
|
|
|
|
_ "modernc.org/sqlite"
|
|
)
|
|
|
|
var update = flag.Bool("update", false, "update golden files")
|
|
|
|
const SPDXSchema = "https://raw.githubusercontent.com/spdx/spdx-spec/support/v%s/schemas/spdx-schema.json"
|
|
|
|
// Golden file paths
|
|
const (
|
|
// Container image tests (docker_engine_test.go, client_server_test.go, standalone_tar_test.go, registry_test.go)
|
|
goldenAlmaLinux8 = "testdata/almalinux-8.json.golden"
|
|
goldenAlpine39 = "testdata/alpine-39.json.golden"
|
|
goldenAlpine39HighCritical = "testdata/alpine-39-high-critical.json.golden"
|
|
goldenAlpine39IgnoreCVEIDs = "testdata/alpine-39-ignore-cveids.json.golden"
|
|
goldenAlpine39Skip = "testdata/alpine-39-skip.json.golden"
|
|
goldenAlpine310JSON = "testdata/alpine-310.json.golden"
|
|
goldenAlpine310ASFF = "testdata/alpine-310.asff.golden"
|
|
goldenAlpine310GitLab = "testdata/alpine-310.gitlab.golden"
|
|
goldenAlpine310GitLabCodeQuality = "testdata/alpine-310.gitlab-codequality.golden"
|
|
goldenAlpine310GSBOM = "testdata/alpine-310.gsbom.golden"
|
|
goldenAlpine310HTML = "testdata/alpine-310.html.golden"
|
|
goldenAlpine310JUnit = "testdata/alpine-310.junit.golden"
|
|
goldenAlpine310SARIF = "testdata/alpine-310.sarif.golden"
|
|
goldenAlpineDistroless = "testdata/alpine-distroless.json.golden"
|
|
goldenAmazon1 = "testdata/amazon-1.json.golden"
|
|
goldenAmazon2 = "testdata/amazon-2.json.golden"
|
|
goldenBusyboxWithLockfile = "testdata/busybox-with-lockfile.json.golden"
|
|
goldenCentOS6 = "testdata/centos-6.json.golden"
|
|
goldenCentOS7 = "testdata/centos-7.json.golden"
|
|
goldenCentOS7IgnoreUnfixed = "testdata/centos-7-ignore-unfixed.json.golden"
|
|
goldenCentOS7Medium = "testdata/centos-7-medium.json.golden"
|
|
goldenDebianBuster = "testdata/debian-buster.json.golden"
|
|
goldenDebianBusterIgnoreUnfixed = "testdata/debian-buster-ignore-unfixed.json.golden"
|
|
goldenDebianStretch = "testdata/debian-stretch.json.golden"
|
|
goldenDistrolessBase = "testdata/distroless-base.json.golden"
|
|
goldenDistrolessPython27 = "testdata/distroless-python27.json.golden"
|
|
goldenFluentdGems = "testdata/fluentd-gems.json.golden"
|
|
goldenFluentdMultipleLockfilesCDX = "testdata/fluentd-multiple-lockfiles.cdx.json.golden"
|
|
goldenMariner10 = "testdata/mariner-1.0.json.golden"
|
|
goldenNPM = "testdata/npm.json.golden"
|
|
goldenNPMGitLab = "testdata/npm.gitlab.golden"
|
|
goldenNPMUbuntuSeverity = "testdata/npm-ubuntu-severity.json.golden"
|
|
goldenOpenSUSELeap151 = "testdata/opensuse-leap-151.json.golden"
|
|
goldenOpenSUSETumbleweed = "testdata/opensuse-tumbleweed.json.golden"
|
|
goldenOracleLinux8 = "testdata/oraclelinux-8.json.golden"
|
|
goldenPhoton30 = "testdata/photon-30.json.golden"
|
|
goldenPom = "testdata/pom.json.golden"
|
|
goldenRockyLinux8 = "testdata/rockylinux-8.json.golden"
|
|
goldenSecrets = "testdata/secrets.json.golden"
|
|
goldenSecretsASFF = "testdata/secrets.asff.golden"
|
|
goldenSLMicroRancher54 = "testdata/sl-micro-rancher5.4.json.golden"
|
|
goldenTestRepo = "testdata/test-repo.json.golden"
|
|
goldenUBI7 = "testdata/ubi-7.json.golden"
|
|
goldenUBI7Comprehensive = "testdata/ubi-7-comprehensive.json.golden"
|
|
goldenUbuntu1804 = "testdata/ubuntu-1804.json.golden"
|
|
goldenUbuntu1804IgnoreUnfixed = "testdata/ubuntu-1804-ignore-unfixed.json.golden"
|
|
|
|
// Repository/Filesystem tests (repo_test.go, config_test.go)
|
|
goldenBun = "testdata/bun.json.golden"
|
|
goldenCargoLock = "testdata/cargo.lock.json.golden"
|
|
goldenCocoaPods = "testdata/cocoapods.json.golden"
|
|
goldenComposerLock = "testdata/composer.lock.json.golden"
|
|
goldenComposerVendor = "testdata/composer.vendor.json.golden"
|
|
goldenConan = "testdata/conan.json.golden"
|
|
goldenCondaCycloneDX = "testdata/conda-cyclonedx.json.golden"
|
|
goldenCondaEnvironmentCycloneDX = "testdata/conda-environment-cyclonedx.json.golden"
|
|
goldenCondaSPDX = "testdata/conda-spdx.json.golden"
|
|
goldenDockerfile = "testdata/dockerfile.json.golden"
|
|
goldenDockerfileCustomPolicies = "testdata/dockerfile-custom-policies.json.golden"
|
|
goldenDockerfileFilePattern = "testdata/dockerfile_file_pattern.json.golden"
|
|
goldenDotNet = "testdata/dotnet.json.golden"
|
|
goldenGoMod = "testdata/gomod.json.golden"
|
|
goldenGoModSkip = "testdata/gomod-skip.json.golden"
|
|
goldenGoModVEX = "testdata/gomod-vex.json.golden"
|
|
goldenGradle = "testdata/gradle.json.golden"
|
|
goldenHelm = "testdata/helm.json.golden"
|
|
goldenHelmBadName = "testdata/helm_badname.json.golden"
|
|
goldenHelmTestChart = "testdata/helm_testchart.json.golden"
|
|
goldenHelmTestChartOverridden = "testdata/helm_testchart.overridden.json.golden"
|
|
goldenJuliaSPDX = "testdata/julia-spdx.json.golden"
|
|
goldenMixLock = "testdata/mix.lock.json.golden"
|
|
goldenNPMWithDev = "testdata/npm-with-dev.json.golden"
|
|
goldenNuGet = "testdata/nuget.json.golden"
|
|
goldenPackagesProps = "testdata/packagesprops.json.golden"
|
|
goldenPip = "testdata/pip.json.golden"
|
|
goldenPipenv = "testdata/pipenv.json.golden"
|
|
goldenPnpm = "testdata/pnpm.json.golden"
|
|
goldenPoetry = "testdata/poetry.json.golden"
|
|
goldenPomCycloneDX = "testdata/pom-cyclonedx.json.golden"
|
|
goldenPubspecLock = "testdata/pubspec.lock.json.golden"
|
|
goldenSBT = "testdata/sbt.json.golden"
|
|
goldenSwift = "testdata/swift.json.golden"
|
|
goldenTerraformExcludeMisconfigsRemoteModule = "testdata/terraform-exclude-misconfs-remote-module.json.golden"
|
|
goldenTerraformOpenTofuRegistry = "testdata/terraform-opentofu-registry.json.golden"
|
|
goldenTerraformRemoteModule = "testdata/terraform-remote-module.json.golden"
|
|
goldenTerraformRemoteModuleInChild = "testdata/terraform-remote-module-in-child.json.golden"
|
|
goldenTerraformRemoteSubmodule = "testdata/terraform-remote-submodule.json.golden"
|
|
goldenTerraformTerraformRegistry = "testdata/terraform-terraform-registry.json.golden"
|
|
goldenUV = "testdata/uv.json.golden"
|
|
goldenYarn = "testdata/yarn.json.golden"
|
|
|
|
// SBOM tests (sbom_test.go)
|
|
goldenFluentdMultipleLockfiles = "testdata/fluentd-multiple-lockfiles.json.golden"
|
|
goldenFluentdMultipleLockfilesShortCDX = "testdata/fluentd-multiple-lockfiles-short.cdx.json.golden"
|
|
goldenLicenseCycloneDX = "testdata/license-cyclonedx.json.golden"
|
|
goldenMinikubeKBOM = "testdata/minikube-kbom.json.golden"
|
|
|
|
// Convert tests (convert_test.go)
|
|
goldenNPMCycloneDX = "testdata/npm-cyclonedx.json.golden"
|
|
goldenConvertNPMWithSuppressed = "testdata/fixtures/convert/npm-with-suppressed.json.golden"
|
|
|
|
// VM tests (vm_test.go)
|
|
goldenAmazonLinux2GP2X86VM = "testdata/amazonlinux2-gp2-x86-vm.json.golden"
|
|
goldenUbuntuGP2X86VM = "testdata/ubuntu-gp2-x86-vm.json.golden"
|
|
|
|
// Module tests (module_test.go)
|
|
goldenSpring4ShellJRE8 = "testdata/spring4shell-jre8.json.golden"
|
|
goldenSpring4ShellJRE11 = "testdata/spring4shell-jre11.json.golden"
|
|
|
|
// Plugin tests (plugin_test.go)
|
|
goldenCountPlugin020 = "testdata/count-0.2.0-plugin.txt.golden"
|
|
goldenCountPlugin010WithBeforeFlag = "testdata/count-0.1.0-plugin-with-before-flag.txt.golden"
|
|
)
|
|
|
|
func initDB(t *testing.T) string {
|
|
fixtureDir := filepath.Join("testdata", "fixtures", "db")
|
|
entries, err := os.ReadDir(fixtureDir)
|
|
require.NoError(t, err)
|
|
|
|
var fixtures []string
|
|
for _, entry := range entries {
|
|
if entry.IsDir() {
|
|
continue
|
|
}
|
|
fixtures = append(fixtures, filepath.Join(fixtureDir, entry.Name()))
|
|
}
|
|
|
|
cacheDir := dbtest.InitDB(t, fixtures)
|
|
defer dbtest.Close()
|
|
|
|
err = metadata.NewClient(db.Dir(cacheDir)).Update(metadata.Metadata{
|
|
Version: db.SchemaVersion,
|
|
NextUpdate: time.Now().Add(24 * time.Hour),
|
|
UpdatedAt: time.Now(),
|
|
DownloadedAt: time.Now(),
|
|
})
|
|
require.NoError(t, err)
|
|
|
|
dbtest.InitJavaDB(t, cacheDir)
|
|
return cacheDir
|
|
}
|
|
|
|
func initVEXRepository(t *testing.T, homeDir, cacheDir string) {
|
|
t.Helper()
|
|
|
|
// Copy config directory
|
|
configSrc := "testdata/fixtures/vex/config/repository.yaml"
|
|
configDst := filepath.Join(homeDir, ".trivy", "vex", "repository.yaml")
|
|
testutil.CopyFile(t, configSrc, configDst)
|
|
|
|
// Copy repository directory
|
|
repoSrc := "testdata/fixtures/vex/repositories"
|
|
repoDst := filepath.Join(cacheDir, "vex", "repositories")
|
|
testutil.CopyDir(t, repoSrc, repoDst)
|
|
|
|
// Copy VEX file
|
|
vexSrc := "testdata/fixtures/vex/file/openvex.json"
|
|
repoDir := filepath.Join(repoDst, "default")
|
|
vexDst := filepath.Join(repoDir, "0.1", "openvex.json")
|
|
testutil.CopyFile(t, vexSrc, vexDst)
|
|
|
|
// Write a dummy cache metadata
|
|
testutil.MustWriteJSON(t, filepath.Join(repoDir, "cache.json"), repo.CacheMetadata{
|
|
UpdatedAt: time.Now(),
|
|
})
|
|
|
|
// Verify that necessary files exist
|
|
requiredFiles := []string{
|
|
configDst,
|
|
filepath.Join(repoDir, "vex-repository.json"),
|
|
filepath.Join(repoDir, "0.1", "index.json"),
|
|
filepath.Join(repoDir, "0.1", "openvex.json"),
|
|
}
|
|
|
|
for _, file := range requiredFiles {
|
|
require.FileExists(t, file)
|
|
}
|
|
}
|
|
|
|
func getFreePort() (int, error) {
|
|
addr, err := net.ResolveTCPAddr("tcp", "localhost:0")
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
|
|
l, err := net.ListenTCP("tcp", addr)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
defer l.Close()
|
|
return l.Addr().(*net.TCPAddr).Port, nil
|
|
}
|
|
|
|
func waitPort(ctx context.Context, addr string) error {
|
|
for {
|
|
conn, err := net.Dial("tcp", addr)
|
|
if err == nil && conn != nil {
|
|
return nil
|
|
}
|
|
select {
|
|
case <-ctx.Done():
|
|
return err
|
|
default:
|
|
time.Sleep(1 * time.Second)
|
|
}
|
|
}
|
|
}
|
|
|
|
func readReport(t *testing.T, filePath string) types.Report {
|
|
t.Helper()
|
|
|
|
f, err := os.Open(filePath)
|
|
require.NoError(t, err, filePath)
|
|
defer f.Close()
|
|
|
|
var report types.Report
|
|
err = json.NewDecoder(f).Decode(&report)
|
|
require.NoError(t, err, filePath)
|
|
|
|
// We don't compare history because the nano-seconds in "created" don't match
|
|
report.Metadata.ImageConfig.History = nil
|
|
|
|
// We don't compare repo digests because archives don't contain them
|
|
report.Metadata.RepoDigests = nil
|
|
for i := range report.Metadata.Layers {
|
|
report.Metadata.Layers[i].Digest = ""
|
|
}
|
|
|
|
for i, result := range report.Results {
|
|
for j := range result.Vulnerabilities {
|
|
report.Results[i].Vulnerabilities[j].Layer.Digest = ""
|
|
}
|
|
|
|
sort.Slice(result.CustomResources, func(i, j int) bool {
|
|
if result.CustomResources[i].Type != result.CustomResources[j].Type {
|
|
return result.CustomResources[i].Type < result.CustomResources[j].Type
|
|
}
|
|
return result.CustomResources[i].FilePath < result.CustomResources[j].FilePath
|
|
})
|
|
}
|
|
|
|
return report
|
|
}
|
|
|
|
func readCycloneDX(t *testing.T, filePath string) *cdx.BOM {
|
|
f, err := os.Open(filePath)
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
bom := cdx.NewBOM()
|
|
decoder := cdx.NewBOMDecoder(f, cdx.BOMFileFormatJSON)
|
|
err = decoder.Decode(bom)
|
|
require.NoError(t, err)
|
|
|
|
// Sort components
|
|
if bom.Components != nil {
|
|
sort.Slice(*bom.Components, func(i, j int) bool {
|
|
return (*bom.Components)[i].Name < (*bom.Components)[j].Name
|
|
})
|
|
for i := range *bom.Components {
|
|
sort.Slice(*(*bom.Components)[i].Properties, func(ii, jj int) bool {
|
|
return (*(*bom.Components)[i].Properties)[ii].Name < (*(*bom.Components)[i].Properties)[jj].Name
|
|
})
|
|
}
|
|
sort.Slice(*bom.Vulnerabilities, func(i, j int) bool {
|
|
return (*bom.Vulnerabilities)[i].ID < (*bom.Vulnerabilities)[j].ID
|
|
})
|
|
}
|
|
|
|
return bom
|
|
}
|
|
|
|
func readSpdxJson(t *testing.T, filePath string) *spdx.Document {
|
|
f, err := os.Open(filePath)
|
|
require.NoError(t, err)
|
|
defer f.Close()
|
|
|
|
bom, err := spdxjson.Read(f)
|
|
require.NoError(t, err)
|
|
|
|
sort.Slice(bom.Relationships, func(i, j int) bool {
|
|
if bom.Relationships[i].RefA.ElementRefID != bom.Relationships[j].RefA.ElementRefID {
|
|
return bom.Relationships[i].RefA.ElementRefID < bom.Relationships[j].RefA.ElementRefID
|
|
}
|
|
return bom.Relationships[i].RefB.ElementRefID < bom.Relationships[j].RefB.ElementRefID
|
|
})
|
|
|
|
sort.Slice(bom.Files, func(i, j int) bool {
|
|
return bom.Files[i].FileSPDXIdentifier < bom.Files[j].FileSPDXIdentifier
|
|
})
|
|
|
|
// We don't compare values which change each time an SBOM is generated
|
|
bom.CreationInfo.Created = ""
|
|
bom.DocumentNamespace = ""
|
|
|
|
return bom
|
|
}
|
|
|
|
type OverrideFunc func(t *testing.T, want, got *types.Report)
|
|
type runOptions struct {
|
|
wantErr string
|
|
override OverrideFunc
|
|
fakeUUID string
|
|
}
|
|
|
|
// runTest runs Trivy with the given args and compares the output with the golden file.
|
|
// The output file is created in a temporary directory, unless -update flag is set, in which case
|
|
// the golden file is updated directly.
|
|
func runTest(t *testing.T, osArgs []string, wantFile string, format types.Format, opts runOptions) {
|
|
// Ensure that tests updating golden files don't use override functions
|
|
// as overrides would modify the golden file output
|
|
if *update && opts.override != nil {
|
|
require.Fail(t, "invalid test configuration", "cannot use override functions when -update is set")
|
|
}
|
|
|
|
if opts.fakeUUID != "" {
|
|
uuid.SetFakeUUID(t, opts.fakeUUID)
|
|
}
|
|
// Set fake UUID v7 for ReportID generation. Format is not configurable.
|
|
uuid.SetFakeUUIDV7(t, "017b7d41-e09f-7000-80ea-%012d")
|
|
|
|
// Set up the output file
|
|
outputFile := filepath.Join(t.TempDir(), "output.json")
|
|
if *update {
|
|
outputFile = wantFile
|
|
}
|
|
osArgs = append(osArgs, "--output", outputFile)
|
|
|
|
// Run Trivy
|
|
err := execute(osArgs)
|
|
if opts.wantErr != "" {
|
|
require.ErrorContains(t, err, opts.wantErr)
|
|
return
|
|
}
|
|
require.NoError(t, err)
|
|
|
|
// Compare want and got
|
|
switch format {
|
|
case types.FormatCycloneDX:
|
|
compareCycloneDX(t, wantFile, outputFile)
|
|
case types.FormatSPDXJSON:
|
|
compareSPDXJson(t, wantFile, outputFile)
|
|
case types.FormatJSON:
|
|
compareReports(t, wantFile, outputFile, opts.override)
|
|
case types.FormatTemplate, types.FormatSarif, types.FormatGitHub:
|
|
compareRawFiles(t, wantFile, outputFile)
|
|
default:
|
|
require.Fail(t, "invalid format", "format: %s", format)
|
|
}
|
|
}
|
|
|
|
func execute(osArgs []string) error {
|
|
// viper.XXX() (e.g. viper.ReadInConfig()) affects the global state, so we need to reset it after each test.
|
|
defer viper.Reset()
|
|
|
|
// Set a fake time
|
|
ctx := clock.With(context.Background(), time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC))
|
|
|
|
// Setup CLI App
|
|
app := commands.NewApp()
|
|
app.SetOut(io.Discard)
|
|
app.SetArgs(osArgs)
|
|
|
|
// Run Trivy
|
|
return app.ExecuteContext(ctx)
|
|
}
|
|
|
|
func compareRawFiles(t *testing.T, wantFile, gotFile string) {
|
|
want, err := os.ReadFile(wantFile)
|
|
require.NoError(t, err)
|
|
got, err := os.ReadFile(gotFile)
|
|
require.NoError(t, err)
|
|
assert.Equal(t, string(want), string(got))
|
|
}
|
|
|
|
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(t, &want, &got)
|
|
}
|
|
|
|
assert.Equal(t, want, got)
|
|
}
|
|
|
|
func compareCycloneDX(t *testing.T, wantFile, gotFile string) {
|
|
want := readCycloneDX(t, wantFile)
|
|
got := readCycloneDX(t, gotFile)
|
|
assert.Equal(t, want, got)
|
|
|
|
// Validate CycloneDX output against the JSON schema
|
|
validateReport(t, got.JSONSchema, got)
|
|
}
|
|
|
|
func compareSPDXJson(t *testing.T, wantFile, gotFile string) {
|
|
want := readSpdxJson(t, wantFile)
|
|
got := readSpdxJson(t, gotFile)
|
|
assert.Equal(t, want, got)
|
|
|
|
SPDXVersion, ok := strings.CutPrefix(want.SPDXVersion, "SPDX-")
|
|
assert.True(t, ok)
|
|
|
|
require.NoError(t, spdxlib.ValidateDocument(got))
|
|
|
|
// Validate SPDX output against the JSON schema
|
|
validateReport(t, fmt.Sprintf(SPDXSchema, SPDXVersion), got)
|
|
}
|
|
|
|
func validateReport(t *testing.T, schema string, report any) {
|
|
schemaLoader := gojsonschema.NewReferenceLoader(schema)
|
|
documentLoader := gojsonschema.NewGoLoader(report)
|
|
result, err := gojsonschema.Validate(schemaLoader, documentLoader)
|
|
require.NoError(t, err)
|
|
|
|
if valid := result.Valid(); !valid {
|
|
errs := lo.Map(result.Errors(), func(err gojsonschema.ResultError, _ int) string {
|
|
return err.String()
|
|
})
|
|
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 = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// overrideFingerprint only checks for the presence of the fingerprint and clears it;
|
|
// the fingerprint is calculated from artifactID, target, pkgID, and vulnerabilityID,
|
|
// but may not match as the artifactID can vary depending on the scanning context.
|
|
func overrideFingerprint(t *testing.T, want, got *types.Report) {
|
|
for i, result := range got.Results {
|
|
for j, vuln := range result.Vulnerabilities {
|
|
assert.NotEmptyf(t, vuln.Fingerprint, "Fingerprint is empty: %s", vuln.VulnerabilityID)
|
|
assert.Lenf(t, vuln.Fingerprint, 71, "Fingerprint should be 71 characters (sha256: + 64 hex chars): %s", vuln.VulnerabilityID)
|
|
// Do not compare Fingerprint as the artifactID varies between tests
|
|
got.Results[i].Vulnerabilities[j].Fingerprint = ""
|
|
}
|
|
}
|
|
for i, result := range want.Results {
|
|
for j := range result.Vulnerabilities {
|
|
want.Results[i].Vulnerabilities[j].Fingerprint = ""
|
|
}
|
|
}
|
|
}
|
|
|
|
// overrideDockerRemovedFields clears image config fields that were removed from Docker API
|
|
// cf. https://github.com/moby/moby/blob/d0ad1357a141c795e1e0490e3fed00ddabcb91b9/docs/api/version-history.md
|
|
func overrideDockerRemovedFields(_ *testing.T, want, got *types.Report) {
|
|
// Clear Container field (removed in Docker API v1.45)
|
|
got.Metadata.ImageConfig.Container = ""
|
|
want.Metadata.ImageConfig.Container = ""
|
|
|
|
// Clear Image field (removed in Docker API v1.50)
|
|
got.Metadata.ImageConfig.Config.Image = ""
|
|
want.Metadata.ImageConfig.Config.Image = ""
|
|
|
|
// Clear Hostname field (removed in Docker API v1.50)
|
|
got.Metadata.ImageConfig.Config.Hostname = ""
|
|
want.Metadata.ImageConfig.Config.Hostname = ""
|
|
}
|