diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 8c681b57c6b2..e8f269b5ef54 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -66,6 +66,7 @@ trivy filesystem [flags] PATH --report string specify a compliance report format for the output (all,summary) (default "all") --reset remove all caches and database --reset-policy-bundle remove policy bundle + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,config,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index 1effd26cacf4..749e324f8d72 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -87,6 +87,7 @@ trivy image [flags] IMAGE_NAME --report string specify a format for the compliance report. (all,summary) (default "summary") --reset remove all caches and database --reset-policy-bundle remove policy bundle + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,config,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index e140d054f270..f6a7e955bf8f 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -78,6 +78,7 @@ trivy kubernetes [flags] { cluster | all | specific resources like kubectl. eg: --report string specify a report format for the output (all,summary) (default "all") --reset remove all caches and database --reset-policy-bundle remove policy bundle + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners string comma-separated list of what security issues to detect (vuln,config,secret,license) (default "vuln,config,secret,rbac") --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 6cf418c5780e..fd2f33e897eb 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -65,6 +65,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database --reset-policy-bundle remove policy bundle + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,config,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 69bcdb282d5c..302df0159438 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -67,6 +67,7 @@ trivy rootfs [flags] ROOTDIR --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database --reset-policy-bundle remove policy bundle + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,config,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index d031f61e4cff..e10659c5887b 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -20,47 +20,48 @@ trivy sbom [flags] SBOM_PATH ### Options ``` - --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") - --cache-ttl duration cache TTL when using redis as cache backend - --clear-cache clear image caches without scanning - --compliance string compliance report to generate - --custom-headers strings custom headers in client mode - --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db") - --download-db-only download/update vulnerability database but don't run a scan - --download-java-db-only download/update Java index database but don't run a scan - --exit-code int specify exit code when any security issues are found - --exit-on-eol int exit with the specified code when the OS reaches end of service/life - --file-patterns strings specify config file patterns - -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") - -h, --help help for sbom - --ignore-policy string specify the Rego file path to evaluate each vulnerability - --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) - --ignore-unfixed display only fixed vulnerabilities - --ignorefile string specify .trivyignore file (default ".trivyignore") - --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db") - --list-all-pkgs enabling the option will output all packages regardless of vulnerability - --no-progress suppress progress bar - --offline-scan do not issue API requests to identify dependencies - -o, --output string output file name - --redis-ca string redis ca file location, if using redis as cache backend - --redis-cert string redis certificate file location, if using redis as cache backend - --redis-key string redis key file location, if using redis as cache backend - --redis-tls enable redis TLS with public certificates, if using redis as cache backend - --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") - --reset remove all caches and database - --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) - --server string server address in client mode - -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) - --skip-db-update skip updating vulnerability database - --skip-dirs strings specify the directories or glob patterns to skip - --skip-files strings specify the files or glob patterns to skip - --skip-java-db-update skip updating Java index database - --slow scan over time with lower CPU and memory utilization - -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 strings comma-separated list of vulnerability types (os,library) (default [os,library]) + --cache-backend string cache backend (e.g. redis://localhost:6379) (default "fs") + --cache-ttl duration cache TTL when using redis as cache backend + --clear-cache clear image caches without scanning + --compliance string compliance report to generate + --custom-headers strings custom headers in client mode + --db-repository string OCI repository to retrieve trivy-db from (default "ghcr.io/aquasecurity/trivy-db") + --download-db-only download/update vulnerability database but don't run a scan + --download-java-db-only download/update Java index database but don't run a scan + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + --file-patterns strings specify config file patterns + -f, --format string format (table,json,template,sarif,cyclonedx,spdx,spdx-json,github,cosign-vuln) (default "table") + -h, --help help for sbom + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) + --ignore-unfixed display only fixed vulnerabilities + --ignorefile string specify .trivyignore file (default ".trivyignore") + --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db") + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + --no-progress suppress progress bar + --offline-scan do not issue API requests to identify dependencies + -o, --output string output file name + --redis-ca string redis ca file location, if using redis as cache backend + --redis-cert string redis certificate file location, if using redis as cache backend + --redis-key string redis key file location, if using redis as cache backend + --redis-tls enable redis TLS with public certificates, if using redis as cache backend + --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") + --reset remove all caches and database + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true + --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --server string server address in client mode + -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) + --skip-db-update skip updating vulnerability database + --skip-dirs strings specify the directories or glob patterns to skip + --skip-files strings specify the files or glob patterns to skip + --skip-java-db-update skip updating Java index database + --slow scan over time with lower CPU and memory utilization + -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 strings comma-separated list of vulnerability types (os,library) (default [os,library]) ``` ### Options inherited from parent commands diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index e294f010cfca..88e215aa7461 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -59,6 +59,7 @@ trivy vm [flags] VM_IMAGE --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database --reset-policy-bundle remove policy bundle + --retain-pkg-installed-files retains the files installed by each package in the analysis output when set to true --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --scanners strings comma-separated list of what security issues to detect (vuln,config,secret,license) (default [vuln,secret]) --secret-config string specify a path to config file for secret scanning (default "trivy-secret.yaml") diff --git a/integration/testdata/conda-spdx.json.golden b/integration/testdata/conda-spdx.json.golden index 59f288a94527..9be36f45a259 100644 --- a/integration/testdata/conda-spdx.json.golden +++ b/integration/testdata/conda-spdx.json.golden @@ -3,14 +3,14 @@ "dataLicense": "CC0-1.0", "SPDXID": "SPDXRef-DOCUMENT", "name": "testdata/fixtures/repo/conda", - "documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/repo/conda-08df146c-0996-4718-8648-b2a45769ab79", + "documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/repo/conda-3ff14136-e09f-4df9-80ea-000000000001", "creationInfo": { "licenseListVersion": "", "creators": [ "Organization: aquasecurity", "Tool: trivy-dev" ], - "created": "2023-06-27T05:37:40Z" + "created": "2020-09-10T14:20:30Z" }, "packages": [ { @@ -23,7 +23,7 @@ }, { "name": "openssl", - "SPDXID": "SPDXRef-Package-950f99cb9edd281", + "SPDXID": "SPDXRef-Package-c75d9dc75200186f", "versionInfo": "1.1.1q", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -41,7 +41,7 @@ }, { "name": "pip", - "SPDXID": "SPDXRef-Package-39020c06af94ca53", + "SPDXID": "SPDXRef-Package-195557cddf18e4a9", "versionInfo": "22.2.2", "supplier": "NOASSERTION", "downloadLocation": "NONE", @@ -105,21 +105,21 @@ }, { "spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125", - "relatedSpdxElement": "SPDXRef-Package-950f99cb9edd281", + "relatedSpdxElement": "SPDXRef-Package-c75d9dc75200186f", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-950f99cb9edd281", + "spdxElementId": "SPDXRef-Package-c75d9dc75200186f", "relatedSpdxElement": "SPDXRef-File-600e5e0110a84891", "relationshipType": "CONTAINS" }, { "spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125", - "relatedSpdxElement": "SPDXRef-Package-39020c06af94ca53", + "relatedSpdxElement": "SPDXRef-Package-195557cddf18e4a9", "relationshipType": "CONTAINS" }, { - "spdxElementId": "SPDXRef-Package-39020c06af94ca53", + "spdxElementId": "SPDXRef-Package-195557cddf18e4a9", "relatedSpdxElement": "SPDXRef-File-7eb62e2a3edddc0a", "relationshipType": "CONTAINS" } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 7c0200b7af44..ed7405fd8008 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -634,6 +634,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { SkipDirs: &flag.SkipDirsFlag, SkipFiles: &flag.SkipFilesFlag, FilePatterns: &flag.FilePatternsFlag, + OnlyDirs: &flag.OnlyDirsFlag, } configFlags := &flag.Flags{ diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 5baa61787a57..31475e888333 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -627,6 +627,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi DisabledAnalyzers: disabledAnalyzers(opts), SkipFiles: opts.SkipFiles, SkipDirs: opts.SkipDirs, + OnlyDirs: opts.OnlyDirs, FilePatterns: opts.FilePatterns, Offline: opts.OfflineScan, NoProgress: opts.NoProgress || opts.Quiet, @@ -664,6 +665,10 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi Full: opts.LicenseFull, ClassifierConfidenceLevel: opts.LicenseConfidenceLevel, }, + + // Retains the package's installed in the package information of + // the analysis result + RetainPkgInstalledFiles: opts.RetainPkgInstalledFiles, }, }, scanOptions, nil } diff --git a/pkg/fanal/analyzer/analyzer.go b/pkg/fanal/analyzer/analyzer.go index 8b9aed005b09..def98445524c 100644 --- a/pkg/fanal/analyzer/analyzer.go +++ b/pkg/fanal/analyzer/analyzer.go @@ -146,18 +146,21 @@ type PostAnalysisInput struct { type AnalysisOptions struct { Offline bool FileChecksum bool + + // retain package installed files in the analysis result + RetainPkgInstalledFiles bool } type AnalysisResult struct { - m sync.Mutex - OS types.OS - Repository *types.Repository - PackageInfos []types.PackageInfo - Applications []types.Application - Misconfigurations []types.Misconfiguration - Secrets []types.Secret - Licenses []types.LicenseFile - SystemInstalledFiles []string // A list of files installed by OS package manager + m sync.Mutex + OS types.OS + Repository *types.Repository + PackageInfos []types.PackageInfo + Applications []types.Application + Misconfigurations []types.Misconfiguration + Secrets []types.Secret + Licenses []types.LicenseFile + PkgInstalledFiles []string // A list of files installed by OS package manager // Digests contains SHA-256 digests of unpackaged files // used to search for SBOM attestation. @@ -178,7 +181,7 @@ func NewAnalysisResult() *AnalysisResult { func (r *AnalysisResult) isEmpty() bool { return lo.IsEmpty(r.OS) && r.Repository == nil && len(r.PackageInfos) == 0 && len(r.Applications) == 0 && - len(r.Misconfigurations) == 0 && len(r.Secrets) == 0 && len(r.Licenses) == 0 && len(r.SystemInstalledFiles) == 0 && + len(r.Misconfigurations) == 0 && len(r.Secrets) == 0 && len(r.Licenses) == 0 && len(r.PkgInstalledFiles) == 0 && r.BuildInfo == nil && len(r.Digests) == 0 && len(r.CustomResources) == 0 } @@ -272,7 +275,7 @@ func (r *AnalysisResult) Merge(new *AnalysisResult) { r.Misconfigurations = append(r.Misconfigurations, new.Misconfigurations...) r.Secrets = append(r.Secrets, new.Secrets...) r.Licenses = append(r.Licenses, new.Licenses...) - r.SystemInstalledFiles = append(r.SystemInstalledFiles, new.SystemInstalledFiles...) + r.PkgInstalledFiles = append(r.PkgInstalledFiles, new.PkgInstalledFiles...) if new.BuildInfo != nil { if r.BuildInfo == nil { @@ -470,7 +473,7 @@ func (ag AnalyzerGroup) PostAnalyze(ctx context.Context, compositeFS *CompositeF continue } - skippedFiles := result.SystemInstalledFiles + skippedFiles := result.PkgInstalledFiles for _, app := range result.Applications { skippedFiles = append(skippedFiles, app.FilePath) for _, lib := range app.Libraries { diff --git a/pkg/fanal/analyzer/analyzer_test.go b/pkg/fanal/analyzer/analyzer_test.go index d35dfd0c6227..50fad76e68df 100644 --- a/pkg/fanal/analyzer/analyzer_test.go +++ b/pkg/fanal/analyzer/analyzer_test.go @@ -347,7 +347,7 @@ func TestAnalyzerGroup_AnalyzeFile(t *testing.T) { }, }, }, - SystemInstalledFiles: []string{ + PkgInstalledFiles: []string{ "lib/libc.musl-x86_64.so.1", "lib/ld-musl-x86_64.so.1", }, diff --git a/pkg/fanal/analyzer/pkg/apk/apk.go b/pkg/fanal/analyzer/pkg/apk/apk.go index defccaeee1c9..6a4564e8b5c5 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk.go +++ b/pkg/fanal/analyzer/pkg/apk/apk.go @@ -34,7 +34,7 @@ type alpinePkgAnalyzer struct{} func (a alpinePkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) { scanner := bufio.NewScanner(input.Content) - parsedPkgs, installedFiles := a.parseApkInfo(scanner) + parsedPkgs, installedFiles := a.parseApkInfo(scanner, &input.Options) return &analyzer.AnalysisResult{ PackageInfos: []types.PackageInfo{ @@ -43,11 +43,17 @@ func (a alpinePkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp Packages: parsedPkgs, }, }, - SystemInstalledFiles: installedFiles, + PkgInstalledFiles: installedFiles, }, nil } -func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package, []string) { +func patchPkgInstalledFiles(pkg *types.Package, files []string, opts *analyzer.AnalysisOptions) { + if opts.RetainPkgInstalledFiles { + pkg.InstalledFiles = append(pkg.InstalledFiles, files...) + } +} + +func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner, opts *analyzer.AnalysisOptions) ([]types.Package, []string) { var ( pkgs []types.Package pkg types.Package @@ -89,7 +95,9 @@ func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package case "F:": dir = line[2:] case "R:": - installedFiles = append(installedFiles, path.Join(dir, line[2:])) + absPath := path.Join(dir, line[2:]) + patchPkgInstalledFiles(&pkg, []string{absPath}, opts) + installedFiles = append(installedFiles, absPath) case "p:": // provides (corresponds to provides in PKGINFO, concatenated by spaces into a single line) a.parseProvides(line, pkg.ID, provides) case "D:": // dependencies (corresponds to depend in PKGINFO, concatenated by spaces into a single line) diff --git a/pkg/fanal/analyzer/pkg/apk/apk_test.go b/pkg/fanal/analyzer/pkg/apk/apk_test.go index 1c1230404574..7bae1da0cd83 100644 --- a/pkg/fanal/analyzer/pkg/apk/apk_test.go +++ b/pkg/fanal/analyzer/pkg/apk/apk_test.go @@ -6,302 +6,428 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" ) -func TestParseApkInfo(t *testing.T) { - var tests = map[string]struct { - path string - wantPkgs []types.Package - wantFiles []string - }{ - "Valid": { - path: "./testdata/apk", - wantPkgs: []types.Package{ - { - ID: "musl@1.1.14-r10", - Name: "musl", - Version: "1.1.14-r10", - SrcName: "musl", - SrcVersion: "1.1.14-r10", - Licenses: []string{"MIT"}, - Arch: "x86_64", - Digest: "sha1:d68b402f35f57750f49156b0cb4e886a2ad35d2d", - }, - { - ID: "busybox@1.24.2-r9", - Name: "busybox", - Version: "1.24.2-r9", - SrcName: "busybox", - SrcVersion: "1.24.2-r9", - Licenses: []string{"GPL-2.0"}, - DependsOn: []string{"musl@1.1.14-r10"}, - Arch: "x86_64", - Digest: "sha1:ca124719267cd0bedc2f4cb850a286ac13f0ad44", - }, - { - ID: "alpine-baselayout@3.0.3-r0", - Name: "alpine-baselayout", - Version: "3.0.3-r0", - SrcName: "alpine-baselayout", - SrcVersion: "3.0.3-r0", - Licenses: []string{"GPL-2.0"}, - DependsOn: []string{"busybox@1.24.2-r9", "musl@1.1.14-r10"}, - Arch: "x86_64", - Digest: "sha1:a214896150411d72dd1fafdb32d1c6c4855cccfa", - }, - { - ID: "alpine-keys@1.1-r0", - Name: "alpine-keys", - Version: "1.1-r0", - SrcName: "alpine-keys", - SrcVersion: "1.1-r0", - Licenses: []string{"GPL-3.0"}, - Arch: "x86_64", - Digest: "sha1:4def7ffaee6aeba700c1d62570326f75cbb8fa25", - }, - { - ID: "zlib@1.2.8-r2", - Name: "zlib", - Version: "1.2.8-r2", - SrcName: "zlib", - SrcVersion: "1.2.8-r2", - Licenses: []string{"Zlib"}, - DependsOn: []string{"musl@1.1.14-r10"}, - Arch: "x86_64", - Digest: "sha1:efd04d34d40aa8eb331480127364c27a8ba760ef", - }, - { - ID: "libcrypto1.0@1.0.2h-r1", - Name: "libcrypto1.0", - Version: "1.0.2h-r1", - SrcName: "openssl", - SrcVersion: "1.0.2h-r1", - Licenses: []string{"openssl"}, - DependsOn: []string{"musl@1.1.14-r10", "zlib@1.2.8-r2"}, - Arch: "x86_64", - Digest: "sha1:65c860ff8f103b664f40ba849a3f5a51c69c8beb", - }, - { - ID: "libssl1.0@1.0.2h-r1", - Name: "libssl1.0", - Version: "1.0.2h-r1", - SrcName: "openssl", - SrcVersion: "1.0.2h-r1", - Licenses: []string{"openssl"}, - Digest: "sha1:7120f337e93b2b4c44e0f5f31a15b60dc678ca14", - DependsOn: []string{ - "libcrypto1.0@1.0.2h-r1", - "musl@1.1.14-r10", - }, - Arch: "x86_64", - }, - { - ID: "apk-tools@2.6.7-r0", - Name: "apk-tools", - Version: "2.6.7-r0", - SrcName: "apk-tools", - SrcVersion: "2.6.7-r0", - Licenses: []string{"GPL-2.0"}, - Digest: "sha1:0990c0acd62b4175818c3a4cc60ed11f14e23bd8", - DependsOn: []string{ - "libcrypto1.0@1.0.2h-r1", - "libssl1.0@1.0.2h-r1", - "musl@1.1.14-r10", - "zlib@1.2.8-r2", - }, - Arch: "x86_64", - }, - { - ID: "scanelf@1.1.6-r0", - Name: "scanelf", - Version: "1.1.6-r0", - SrcName: "pax-utils", - SrcVersion: "1.1.6-r0", - Licenses: []string{"GPL-2.0"}, - Digest: "sha1:f9bab817c5ad93e92a6218bc0f7596b657c02d90", - DependsOn: []string{"musl@1.1.14-r10"}, - Arch: "x86_64", - }, - { - ID: "musl-utils@1.1.14-r10", - Name: "musl-utils", - Version: "1.1.14-r10", - SrcName: "musl", - SrcVersion: "1.1.14-r10", - Licenses: []string{"MIT", "BSD-3-Clause", "GPL-2.0"}, - Digest: "sha1:608aa1dd39eff7bc6615d3e5e33383750f8f5ecc", - DependsOn: []string{ - "musl@1.1.14-r10", - "scanelf@1.1.6-r0", - }, - Arch: "x86_64", - }, - { - ID: "libc-utils@0.7-r0", - Name: "libc-utils", - Version: "0.7-r0", - SrcName: "libc-dev", - SrcVersion: "0.7-r0", - Licenses: []string{"GPL-3.0"}, - Digest: "sha1:9055bc7afd76cf2672198042f72fc4a5ed4fa961", - DependsOn: []string{"musl-utils@1.1.14-r10"}, - Arch: "x86_64", - }, - { - ID: "pkgconf@1.6.0-r0", - Name: "pkgconf", - Version: "1.6.0-r0", - SrcName: "pkgconf", - SrcVersion: "1.6.0-r0", - Licenses: []string{"ISC"}, - Digest: "sha1:e6242ac29589c8a84a4b179b491ea7c29fce66a9", - DependsOn: []string{"musl@1.1.14-r10"}, - Arch: "x86_64", - }, +var pkgs = []types.Package{ + { + ID: "musl@1.1.14-r10", + Name: "musl", + Version: "1.1.14-r10", + SrcName: "musl", + SrcVersion: "1.1.14-r10", + Licenses: []string{"MIT"}, + Arch: "x86_64", + Digest: "sha1:d68b402f35f57750f49156b0cb4e886a2ad35d2d", + InstalledFiles: []string{ + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", + }, + }, + { + ID: "busybox@1.24.2-r9", + Name: "busybox", + Version: "1.24.2-r9", + SrcName: "busybox", + SrcVersion: "1.24.2-r9", + Licenses: []string{"GPL-2.0"}, + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + Digest: "sha1:ca124719267cd0bedc2f4cb850a286ac13f0ad44", + InstalledFiles: []string{ + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", + }, + }, + { + ID: "alpine-baselayout@3.0.3-r0", + Name: "alpine-baselayout", + Version: "3.0.3-r0", + SrcName: "alpine-baselayout", + SrcVersion: "3.0.3-r0", + Licenses: []string{"GPL-2.0"}, + DependsOn: []string{"busybox@1.24.2-r9", "musl@1.1.14-r10"}, + Arch: "x86_64", + Digest: "sha1:a214896150411d72dd1fafdb32d1c6c4855cccfa", + InstalledFiles: []string{ + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/TZ", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/spool/cron/crontabs", + }, + }, + { + ID: "alpine-keys@1.1-r0", + Name: "alpine-keys", + Version: "1.1-r0", + SrcName: "alpine-keys", + SrcVersion: "1.1-r0", + Licenses: []string{"GPL-3.0"}, + Arch: "x86_64", + Digest: "sha1:4def7ffaee6aeba700c1d62570326f75cbb8fa25", + InstalledFiles: []string{ + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + }, + }, + { + ID: "zlib@1.2.8-r2", + Name: "zlib", + Version: "1.2.8-r2", + SrcName: "zlib", + SrcVersion: "1.2.8-r2", + Licenses: []string{"Zlib"}, + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + Digest: "sha1:efd04d34d40aa8eb331480127364c27a8ba760ef", + InstalledFiles: []string{ + "lib/libz.so.1.2.8", + "lib/libz.so.1", + }, + }, + { + ID: "libcrypto1.0@1.0.2h-r1", + Name: "libcrypto1.0", + Version: "1.0.2h-r1", + SrcName: "openssl", + SrcVersion: "1.0.2h-r1", + Licenses: []string{"openssl"}, + DependsOn: []string{"musl@1.1.14-r10", "zlib@1.2.8-r2"}, + Arch: "x86_64", + Digest: "sha1:65c860ff8f103b664f40ba849a3f5a51c69c8beb", + InstalledFiles: []string{ + "lib/libcrypto.so.1.0.0", + "usr/bin/c_rehash", + "usr/lib/libcrypto.so.1.0.0", + "usr/lib/engines/libubsec.so", + "usr/lib/engines/libatalla.so", + "usr/lib/engines/libcapi.so", + "usr/lib/engines/libgost.so", + "usr/lib/engines/libcswift.so", + "usr/lib/engines/libchil.so", + "usr/lib/engines/libgmp.so", + "usr/lib/engines/libnuron.so", + "usr/lib/engines/lib4758cca.so", + "usr/lib/engines/libsureware.so", + "usr/lib/engines/libpadlock.so", + "usr/lib/engines/libaep.so", + }, + }, + { + ID: "libssl1.0@1.0.2h-r1", + Name: "libssl1.0", + Version: "1.0.2h-r1", + SrcName: "openssl", + SrcVersion: "1.0.2h-r1", + Licenses: []string{"openssl"}, + Digest: "sha1:7120f337e93b2b4c44e0f5f31a15b60dc678ca14", + DependsOn: []string{ + "libcrypto1.0@1.0.2h-r1", + "musl@1.1.14-r10", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "lib/libssl.so.1.0.0", + "usr/lib/libssl.so.1.0.0", + }, + }, + { + ID: "apk-tools@2.6.7-r0", + Name: "apk-tools", + Version: "2.6.7-r0", + SrcName: "apk-tools", + SrcVersion: "2.6.7-r0", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:0990c0acd62b4175818c3a4cc60ed11f14e23bd8", + DependsOn: []string{ + "libcrypto1.0@1.0.2h-r1", + "libssl1.0@1.0.2h-r1", + "musl@1.1.14-r10", + "zlib@1.2.8-r2", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "sbin/apk", + }, + }, + { + ID: "scanelf@1.1.6-r0", + Name: "scanelf", + Version: "1.1.6-r0", + SrcName: "pax-utils", + SrcVersion: "1.1.6-r0", + Licenses: []string{"GPL-2.0"}, + Digest: "sha1:f9bab817c5ad93e92a6218bc0f7596b657c02d90", + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/bin/scanelf", + }, + }, + { + ID: "musl-utils@1.1.14-r10", + Name: "musl-utils", + Version: "1.1.14-r10", + SrcName: "musl", + SrcVersion: "1.1.14-r10", + Licenses: []string{"MIT", "BSD-3-Clause", "GPL-2.0"}, + Digest: "sha1:608aa1dd39eff7bc6615d3e5e33383750f8f5ecc", + DependsOn: []string{ + "musl@1.1.14-r10", + "scanelf@1.1.6-r0", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent", + }, + }, + { + ID: "libc-utils@0.7-r0", + Name: "libc-utils", + Version: "0.7-r0", + SrcName: "libc-dev", + SrcVersion: "0.7-r0", + Licenses: []string{"GPL-3.0"}, + Digest: "sha1:9055bc7afd76cf2672198042f72fc4a5ed4fa961", + DependsOn: []string{"musl-utils@1.1.14-r10"}, + Arch: "x86_64", + //InstalledFiles: []string{}, + }, + { + ID: "pkgconf@1.6.0-r0", + Name: "pkgconf", + Version: "1.6.0-r0", + SrcName: "pkgconf", + SrcVersion: "1.6.0-r0", + Licenses: []string{"ISC"}, + Digest: "sha1:e6242ac29589c8a84a4b179b491ea7c29fce66a9", + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/bin/pkgconf", + "usr/bin/pkg-config", + "usr/lib/libpkgconf.so.3.0.0", + "usr/lib/libpkgconf.so.3", + "usr/share/aclocal/pkg.m4", + }, + }, + + { + ID: "sqlite-libs@3.26.0-r3", + Name: "sqlite-libs", + Version: "3.26.0-r3", + SrcName: "sqlite", + SrcVersion: "3.26.0-r3", + Licenses: []string{"Public-Domain"}, + Digest: "sha1:1464946c3a5f0dd5a67ca1af930fc17af7a74474", + DependsOn: []string{"musl@1.1.14-r10"}, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/lib/libsqlite3.so.0", + "usr/lib/libsqlite3.so.0.8.6", + }, + }, + + { + ID: "test@2.9.11_pre20061021-r2", + Name: "test", + Version: "2.9.11_pre20061021-r2", + SrcName: "test-parent", + SrcVersion: "2.9.11_pre20061021-r2", + Licenses: []string{"Public-Domain"}, + Digest: "sha1:f0bf315ec54828188910e4a665c00bc48bdbdd7d", + DependsOn: []string{ + "pkgconf@1.6.0-r0", + "sqlite-libs@3.26.0-r3", + }, + Arch: "x86_64", + InstalledFiles: []string{ + "usr/lib/libsqlite3.so", + "usr/lib/pkgconfig/sqlite3.pc", + "usr/include/sqlite3ext.h", + "usr/include/sqlite3.h", + }, + }, +} - { - ID: "sqlite-libs@3.26.0-r3", - Name: "sqlite-libs", - Version: "3.26.0-r3", - SrcName: "sqlite", - SrcVersion: "3.26.0-r3", - Licenses: []string{"Public-Domain"}, - Digest: "sha1:1464946c3a5f0dd5a67ca1af930fc17af7a74474", - DependsOn: []string{"musl@1.1.14-r10"}, - Arch: "x86_64", - }, +var files = []string{ + // musl-1.1.14-r10 + "lib/libc.musl-x86_64.so.1", + "lib/ld-musl-x86_64.so.1", - { - ID: "test@2.9.11_pre20061021-r2", - Name: "test", - Version: "2.9.11_pre20061021-r2", - SrcName: "test-parent", - SrcVersion: "2.9.11_pre20061021-r2", - Licenses: []string{"Public-Domain"}, - Digest: "sha1:f0bf315ec54828188910e4a665c00bc48bdbdd7d", - DependsOn: []string{ - "pkgconf@1.6.0-r0", - "sqlite-libs@3.26.0-r3", - }, - Arch: "x86_64", - }, - }, - wantFiles: []string{ - // musl-1.1.14-r10 - "lib/libc.musl-x86_64.so.1", - "lib/ld-musl-x86_64.so.1", + // busybox-1.24.2-r9 + "bin/busybox", + "bin/sh", + "etc/securetty", + "etc/udhcpd.conf", + "etc/logrotate.d/acpid", - // busybox-1.24.2-r9 - "bin/busybox", - "bin/sh", - "etc/securetty", - "etc/udhcpd.conf", - "etc/logrotate.d/acpid", + // alpine-baselayout-3.0.3-r0 + "etc/hosts", + "etc/sysctl.conf", + "etc/group", + "etc/protocols", + "etc/fstab", + "etc/mtab", + "etc/profile", + "etc/TZ", + "etc/shells", + "etc/motd", + "etc/inittab", + "etc/hostname", + "etc/modules", + "etc/services", + "etc/shadow", + "etc/passwd", + "etc/profile.d/color_prompt", + "etc/sysctl.d/00-alpine.conf", + "etc/modprobe.d/i386.conf", + "etc/modprobe.d/blacklist.conf", + "etc/modprobe.d/aliases.conf", + "etc/modprobe.d/kms.conf", + "etc/crontabs/root", + "sbin/mkmntdirs", + "var/spool/cron/crontabs", - // alpine-baselayout-3.0.3-r0 - "etc/hosts", - "etc/sysctl.conf", - "etc/group", - "etc/protocols", - "etc/fstab", - "etc/mtab", - "etc/profile", - "etc/TZ", - "etc/shells", - "etc/motd", - "etc/inittab", - "etc/hostname", - "etc/modules", - "etc/services", - "etc/shadow", - "etc/passwd", - "etc/profile.d/color_prompt", - "etc/sysctl.d/00-alpine.conf", - "etc/modprobe.d/i386.conf", - "etc/modprobe.d/blacklist.conf", - "etc/modprobe.d/aliases.conf", - "etc/modprobe.d/kms.conf", - "etc/crontabs/root", - "sbin/mkmntdirs", - "var/spool/cron/crontabs", + // alpine-keys-1.1-r0 + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", + "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", - // alpine-keys-1.1-r0 - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4d07755e.rsa.pub", - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-524d27bb.rsa.pub", - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5243ef4b.rsa.pub", - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-5261cecb.rsa.pub", - "etc/apk/keys/alpine-devel@lists.alpinelinux.org-4a6a0840.rsa.pub", + // zlib-1.2.8-r2 + "lib/libz.so.1.2.8", + "lib/libz.so.1", - // zlib-1.2.8-r2 - "lib/libz.so.1.2.8", - "lib/libz.so.1", + // libcrypto1.0-1.0.2h-r1 + "lib/libcrypto.so.1.0.0", + "usr/bin/c_rehash", + "usr/lib/libcrypto.so.1.0.0", + "usr/lib/engines/libubsec.so", + "usr/lib/engines/libatalla.so", + "usr/lib/engines/libcapi.so", + "usr/lib/engines/libgost.so", + "usr/lib/engines/libcswift.so", + "usr/lib/engines/libchil.so", + "usr/lib/engines/libgmp.so", + "usr/lib/engines/libnuron.so", + "usr/lib/engines/lib4758cca.so", + "usr/lib/engines/libsureware.so", + "usr/lib/engines/libpadlock.so", + "usr/lib/engines/libaep.so", - // libcrypto1.0-1.0.2h-r1 - "lib/libcrypto.so.1.0.0", - "usr/bin/c_rehash", - "usr/lib/libcrypto.so.1.0.0", - "usr/lib/engines/libubsec.so", - "usr/lib/engines/libatalla.so", - "usr/lib/engines/libcapi.so", - "usr/lib/engines/libgost.so", - "usr/lib/engines/libcswift.so", - "usr/lib/engines/libchil.so", - "usr/lib/engines/libgmp.so", - "usr/lib/engines/libnuron.so", - "usr/lib/engines/lib4758cca.so", - "usr/lib/engines/libsureware.so", - "usr/lib/engines/libpadlock.so", - "usr/lib/engines/libaep.so", + // libssl1.0-1.0.2h-r1 + "lib/libssl.so.1.0.0", + "usr/lib/libssl.so.1.0.0", - // libssl1.0-1.0.2h-r1 - "lib/libssl.so.1.0.0", - "usr/lib/libssl.so.1.0.0", + // apk-tools-2.6.7-r0 + "sbin/apk", - // apk-tools-2.6.7-r0 - "sbin/apk", + // scanelf-1.1.6-r0 + "usr/bin/scanelf", - // scanelf-1.1.6-r0 - "usr/bin/scanelf", + // musl-utils-1.1.14-r10 + "sbin/ldconfig", + "usr/bin/iconv", + "usr/bin/ldd", + "usr/bin/getconf", + "usr/bin/getent", - // musl-utils-1.1.14-r10 - "sbin/ldconfig", - "usr/bin/iconv", - "usr/bin/ldd", - "usr/bin/getconf", - "usr/bin/getent", + // libc-utils-0.7-r0 - // libc-utils-0.7-r0 + // pkgconf-1.6.0-r0 + "usr/bin/pkgconf", + "usr/bin/pkg-config", + "usr/lib/libpkgconf.so.3.0.0", + "usr/lib/libpkgconf.so.3", + "usr/share/aclocal/pkg.m4", - // pkgconf-1.6.0-r0 - "usr/bin/pkgconf", - "usr/bin/pkg-config", - "usr/lib/libpkgconf.so.3.0.0", - "usr/lib/libpkgconf.so.3", - "usr/share/aclocal/pkg.m4", + // sqlite-libs-3.26.0-r3 + "usr/lib/libsqlite3.so.0", + "usr/lib/libsqlite3.so.0.8.6", - // sqlite-libs-3.26.0-r3 - "usr/lib/libsqlite3.so.0", - "usr/lib/libsqlite3.so.0.8.6", + // test-2.9.11_pre20061021-r2 + "usr/lib/libsqlite3.so", + "usr/lib/pkgconfig/sqlite3.pc", + "usr/include/sqlite3ext.h", + "usr/include/sqlite3.h", +} - // test-2.9.11_pre20061021-r2 - "usr/lib/libsqlite3.so", - "usr/lib/pkgconfig/sqlite3.pc", - "usr/include/sqlite3ext.h", - "usr/include/sqlite3.h", - }, +func TestParseApkInfo(t *testing.T) { + var tests = map[string]struct { + path string + wantPkgs []types.Package + wantFiles []string + RetainInstalledFiles bool + }{ + "Valid": { + path: "./testdata/apk", + RetainInstalledFiles: true, + wantPkgs: pkgs, + wantFiles: files, + }, + "do not retain pkg installed files": { + path: "./testdata/apk", + RetainInstalledFiles: false, + wantPkgs: pkgs, + wantFiles: files, }, } - a := alpinePkgAnalyzer{} - for testname, v := range tests { - read, err := os.Open(v.path) - if err != nil { - t.Errorf("%s : can't open file %s", testname, v.path) - } - scanner := bufio.NewScanner(read) - gotPkgs, gotFiles := a.parseApkInfo(scanner) - assert.Equal(t, v.wantPkgs, gotPkgs) - assert.Equal(t, v.wantFiles, gotFiles) + for testname, tt := range tests { + t.Run(testname, func(t *testing.T) { + a := alpinePkgAnalyzer{} + f, err := os.Open(tt.path) + defer f.Close() + require.NoError(t, err) + scanner := bufio.NewScanner(f) + gotPkgs, gotFiles := a.parseApkInfo(scanner, &analyzer.AnalysisOptions{RetainPkgInstalledFiles: tt.RetainInstalledFiles}) + + // Remove package installed files if necessary + wantPkgs := make([]types.Package, 0, len(tt.wantPkgs)) + for _, pkg := range tt.wantPkgs { + if !tt.RetainInstalledFiles { + pkg.InstalledFiles = nil + } + wantPkgs = append(wantPkgs, pkg) + } + + assert.Equal(t, wantPkgs, gotPkgs) + assert.Equal(t, tt.wantFiles, gotFiles) + }) } } diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go index c90fb0867f03..1a0693600d8c 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg.go @@ -52,7 +52,7 @@ var ( ) func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysisInput) (*analyzer.AnalysisResult, error) { - var systemInstalledFiles []string + var PkgInstalledFiles []string var packageInfos []types.PackageInfo // parse `available` file to get digest for packages @@ -74,7 +74,7 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis if err != nil { return err } - systemInstalledFiles = append(systemInstalledFiles, systemFiles...) + PkgInstalledFiles = append(PkgInstalledFiles, systemFiles...) return nil } // parse status files @@ -90,8 +90,8 @@ func (a dpkgAnalyzer) PostAnalyze(_ context.Context, input analyzer.PostAnalysis } return &analyzer.AnalysisResult{ - PackageInfos: packageInfos, - SystemInstalledFiles: systemInstalledFiles, + PackageInfos: packageInfos, + PkgInstalledFiles: PkgInstalledFiles, }, nil } diff --git a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go index 3d73a550866c..ce33cfc728a2 100644 --- a/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go +++ b/pkg/fanal/analyzer/pkg/dpkg/dpkg_test.go @@ -7,11 +7,11 @@ import ( "sort" "testing" + "github.com/aquasecurity/trivy/pkg/mapfs" "github.com/stretchr/testify/assert" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/mapfs" ) func Test_dpkgAnalyzer_Analyze(t *testing.T) { @@ -1420,7 +1420,7 @@ func Test_dpkgAnalyzer_Analyze(t *testing.T) { name: "info list", testFiles: map[string]string{"./testdata/tar.list": "var/lib/dpkg/info/tar.list"}, want: &analyzer.AnalysisResult{ - SystemInstalledFiles: []string{ + PkgInstalledFiles: []string{ "/bin/tar", "/etc", "/usr/lib/mime/packages/tar", diff --git a/pkg/fanal/analyzer/pkg/rpm/rpm.go b/pkg/fanal/analyzer/pkg/rpm/rpm.go index 8d1bacfdb041..349c1da4fb2a 100644 --- a/pkg/fanal/analyzer/pkg/rpm/rpm.go +++ b/pkg/fanal/analyzer/pkg/rpm/rpm.go @@ -76,7 +76,7 @@ func (a rpmPkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) Packages: parsedPkgs, }, }, - SystemInstalledFiles: installedFiles, + PkgInstalledFiles: installedFiles, }, nil } diff --git a/pkg/fanal/artifact/artifact.go b/pkg/fanal/artifact/artifact.go index 2028b601c744..ab1c2699c930 100644 --- a/pkg/fanal/artifact/artifact.go +++ b/pkg/fanal/artifact/artifact.go @@ -16,6 +16,7 @@ type Option struct { DisabledHandlers []types.HandlerType SkipFiles []string SkipDirs []string + OnlyDirs []string FilePatterns []string NoProgress bool Insecure bool @@ -42,6 +43,9 @@ type Option struct { // File walk WalkOption WalkOption + + // Retain package installed files in the output of the scanner + RetainPkgInstalledFiles bool } // WalkOption is a struct that allows users to define a custom walking behavior. @@ -56,6 +60,7 @@ func (o *Option) Sort() { }) sort.Strings(o.SkipFiles) sort.Strings(o.SkipDirs) + sort.Strings(o.OnlyDirs) sort.Strings(o.FilePatterns) } diff --git a/pkg/fanal/artifact/image/image.go b/pkg/fanal/artifact/image/image.go index 8f041a4722d4..8d90f12cfda4 100644 --- a/pkg/fanal/artifact/image/image.go +++ b/pkg/fanal/artifact/image/image.go @@ -75,7 +75,7 @@ func NewArtifact(img types.Image, c cache.ArtifactCache, opt artifact.Option) (a return Artifact{ image: img, cache: c, - walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs, opt.Slow), + walker: walker.NewLayerTar(opt.SkipFiles, opt.SkipDirs, opt.OnlyDirs, opt.Slow), analyzer: a, configAnalyzer: ca, handlerManager: handlerManager, @@ -264,8 +264,9 @@ func (a Artifact) inspectLayer(ctx context.Context, layerInfo LayerInfo, disable // Prepare variables var wg sync.WaitGroup opts := analyzer.AnalysisOptions{ - Offline: a.artifactOption.Offline, - FileChecksum: a.artifactOption.FileChecksum, + Offline: a.artifactOption.Offline, + FileChecksum: a.artifactOption.FileChecksum, + RetainPkgInstalledFiles: a.artifactOption.RetainPkgInstalledFiles, } result := analyzer.NewAnalysisResult() limit := semaphore.New(a.artifactOption.Slow) diff --git a/pkg/fanal/artifact/local/fs.go b/pkg/fanal/artifact/local/fs.go index d7c4a0573ff0..0140a0bf227b 100644 --- a/pkg/fanal/artifact/local/fs.go +++ b/pkg/fanal/artifact/local/fs.go @@ -56,7 +56,7 @@ func NewArtifact(rootPath string, c cache.ArtifactCache, opt artifact.Option) (a rootPath: filepath.ToSlash(filepath.Clean(rootPath)), cache: c, walker: walker.NewFS(buildPathsToSkip(rootPath, opt.SkipFiles), buildPathsToSkip(rootPath, opt.SkipDirs), - opt.Slow, opt.WalkOption.ErrorCallback), + buildPathsToSkip(rootPath, opt.OnlyDirs), opt.Slow, opt.WalkOption.ErrorCallback), analyzer: a, handlerManager: handlerManager, @@ -124,8 +124,9 @@ func (a Artifact) Inspect(ctx context.Context) (types.ArtifactReference, error) result := analyzer.NewAnalysisResult() limit := semaphore.New(a.artifactOption.Slow) opts := analyzer.AnalysisOptions{ - Offline: a.artifactOption.Offline, - FileChecksum: a.artifactOption.FileChecksum, + Offline: a.artifactOption.Offline, + FileChecksum: a.artifactOption.FileChecksum, + RetainPkgInstalledFiles: a.artifactOption.RetainPkgInstalledFiles, } // Prepare filesystem for post analysis diff --git a/pkg/fanal/artifact/local/fs_test.go b/pkg/fanal/artifact/local/fs_test.go index d18048f8e97b..0487cd3c0e12 100644 --- a/pkg/fanal/artifact/local/fs_test.go +++ b/pkg/fanal/artifact/local/fs_test.go @@ -47,7 +47,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:9101fcb54fd63b7dfde027bd669e159ed65aff15842057780f4b0c846bab6369", + BlobID: "sha256:786f2dde279fab207e37c84888bfc2027e27d782c9ec31cc6e7e3d71f5a6e42d", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -78,9 +78,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "host", Type: types.ArtifactFilesystem, - ID: "sha256:9101fcb54fd63b7dfde027bd669e159ed65aff15842057780f4b0c846bab6369", + ID: "sha256:786f2dde279fab207e37c84888bfc2027e27d782c9ec31cc6e7e3d71f5a6e42d", BlobIDs: []string{ - "sha256:9101fcb54fd63b7dfde027bd669e159ed65aff15842057780f4b0c846bab6369", + "sha256:786f2dde279fab207e37c84888bfc2027e27d782c9ec31cc6e7e3d71f5a6e42d", }, }, }, @@ -98,7 +98,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:d5fa75cdac006582a8f6bc4e3fcc8bfb70bd9d0403c24d8c2e3230d3f38a7ff5", + BlobID: "sha256:5673bd2465527ab735ac48863a40c34f31bed28cddb1cf9ee232d2bb8aded51f", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, }, @@ -108,9 +108,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "host", Type: types.ArtifactFilesystem, - ID: "sha256:d5fa75cdac006582a8f6bc4e3fcc8bfb70bd9d0403c24d8c2e3230d3f38a7ff5", + ID: "sha256:5673bd2465527ab735ac48863a40c34f31bed28cddb1cf9ee232d2bb8aded51f", BlobIDs: []string{ - "sha256:d5fa75cdac006582a8f6bc4e3fcc8bfb70bd9d0403c24d8c2e3230d3f38a7ff5", + "sha256:5673bd2465527ab735ac48863a40c34f31bed28cddb1cf9ee232d2bb8aded51f", }, }, }, @@ -121,7 +121,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:9101fcb54fd63b7dfde027bd669e159ed65aff15842057780f4b0c846bab6369", + BlobID: "sha256:786f2dde279fab207e37c84888bfc2027e27d782c9ec31cc6e7e3d71f5a6e42d", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, OS: types.OS{ @@ -167,7 +167,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0e0d362332d8928f71ac2c11e0813e2ec251dca9bdf1a66bd69cad8f2ef66ca1", + BlobID: "sha256:f597e7c957c2d808fa3b08ef9d6daee24a2a32dc3c1a52c71f6b7d646cb73426", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -189,9 +189,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/requirements.txt", Type: types.ArtifactFilesystem, - ID: "sha256:0e0d362332d8928f71ac2c11e0813e2ec251dca9bdf1a66bd69cad8f2ef66ca1", + ID: "sha256:f597e7c957c2d808fa3b08ef9d6daee24a2a32dc3c1a52c71f6b7d646cb73426", BlobIDs: []string{ - "sha256:0e0d362332d8928f71ac2c11e0813e2ec251dca9bdf1a66bd69cad8f2ef66ca1", + "sha256:f597e7c957c2d808fa3b08ef9d6daee24a2a32dc3c1a52c71f6b7d646cb73426", }, }, }, @@ -202,7 +202,7 @@ func TestArtifact_Inspect(t *testing.T) { }, putBlobExpectation: cache.ArtifactCachePutBlobExpectation{ Args: cache.ArtifactCachePutBlobArgs{ - BlobID: "sha256:0e0d362332d8928f71ac2c11e0813e2ec251dca9bdf1a66bd69cad8f2ef66ca1", + BlobID: "sha256:f597e7c957c2d808fa3b08ef9d6daee24a2a32dc3c1a52c71f6b7d646cb73426", BlobInfo: types.BlobInfo{ SchemaVersion: types.BlobJSONSchemaVersion, Applications: []types.Application{ @@ -224,9 +224,9 @@ func TestArtifact_Inspect(t *testing.T) { want: types.ArtifactReference{ Name: "testdata/requirements.txt", Type: types.ArtifactFilesystem, - ID: "sha256:0e0d362332d8928f71ac2c11e0813e2ec251dca9bdf1a66bd69cad8f2ef66ca1", + ID: "sha256:f597e7c957c2d808fa3b08ef9d6daee24a2a32dc3c1a52c71f6b7d646cb73426", BlobIDs: []string{ - "sha256:0e0d362332d8928f71ac2c11e0813e2ec251dca9bdf1a66bd69cad8f2ef66ca1", + "sha256:f597e7c957c2d808fa3b08ef9d6daee24a2a32dc3c1a52c71f6b7d646cb73426", }, }, }, diff --git a/pkg/fanal/artifact/vm/vm.go b/pkg/fanal/artifact/vm/vm.go index a9173ed00e7c..7871feae2496 100644 --- a/pkg/fanal/artifact/vm/vm.go +++ b/pkg/fanal/artifact/vm/vm.go @@ -45,8 +45,9 @@ func (a *Storage) Analyze(ctx context.Context, r *io.SectionReader) (types.BlobI result := analyzer.NewAnalysisResult() opts := analyzer.AnalysisOptions{ - Offline: a.artifactOption.Offline, - FileChecksum: a.artifactOption.FileChecksum, + Offline: a.artifactOption.Offline, + FileChecksum: a.artifactOption.FileChecksum, + RetainPkgInstalledFiles: a.artifactOption.RetainPkgInstalledFiles, } // Prepare filesystem for post analysis @@ -135,7 +136,7 @@ func NewArtifact(target string, c cache.ArtifactCache, opt artifact.Option) (art cache: c, analyzer: a, handlerManager: handlerManager, - walker: walker.NewVM(opt.SkipFiles, opt.SkipDirs, opt.Slow), + walker: walker.NewVM(opt.SkipFiles, opt.SkipDirs, opt.OnlyDirs, opt.Slow), artifactOption: opt, } diff --git a/pkg/fanal/cache/key.go b/pkg/fanal/cache/key.go index f9258caa866b..d167c65c4f08 100644 --- a/pkg/fanal/cache/key.go +++ b/pkg/fanal/cache/key.go @@ -29,9 +29,9 @@ func CalcKey(id string, analyzerVersions analyzer.Versions, hookVersions map[str HookVersions map[string]int SkipFiles []string SkipDirs []string + OnlyDirs []string FilePatterns []string `json:",omitempty"` - }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs, artifactOpt.FilePatterns} - + }{id, analyzerVersions, hookVersions, artifactOpt.SkipFiles, artifactOpt.SkipDirs, artifactOpt.OnlyDirs, artifactOpt.FilePatterns} if err := json.NewEncoder(h).Encode(keyBase); err != nil { return "", xerrors.Errorf("json encode error: %w", err) } diff --git a/pkg/fanal/handler/sysfile/filter.go b/pkg/fanal/handler/sysfile/filter.go index 09525aebc1e7..fc2135deecba 100644 --- a/pkg/fanal/handler/sysfile/filter.go +++ b/pkg/fanal/handler/sysfile/filter.go @@ -54,7 +54,7 @@ func newSystemFileFilteringPostHandler(artifact.Option) (handler.PostHandler, er // Handle removes files installed by OS package manager such as yum. func (h systemFileFilteringPostHandler) Handle(_ context.Context, result *analyzer.AnalysisResult, blob *types.BlobInfo) error { var systemFiles []string - for _, file := range append(result.SystemInstalledFiles, defaultSystemFiles...) { + for _, file := range append(result.PkgInstalledFiles, defaultSystemFiles...) { // Trim leading slashes to be the same format as the path in container images. systemFile := strings.TrimPrefix(file, "/") // We should check the root filepath ("/") and ignore it. diff --git a/pkg/fanal/handler/sysfile/filter_test.go b/pkg/fanal/handler/sysfile/filter_test.go index 6dc8d5af7b03..4c1ba1a32be1 100644 --- a/pkg/fanal/handler/sysfile/filter_test.go +++ b/pkg/fanal/handler/sysfile/filter_test.go @@ -21,7 +21,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { name: "happy path", result: &analyzer.AnalysisResult{ - SystemInstalledFiles: []string{ + PkgInstalledFiles: []string{ "/", "/usr/bin/pydoc", "/usr/bin/python", @@ -200,7 +200,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { name: "go binaries", result: &analyzer.AnalysisResult{ - SystemInstalledFiles: []string{ + PkgInstalledFiles: []string{ "usr/local/bin/goreleaser", }, }, @@ -223,7 +223,7 @@ func Test_systemFileFilterHook_Hook(t *testing.T) { { name: "Rust will not be skipped", result: &analyzer.AnalysisResult{ - SystemInstalledFiles: []string{ + PkgInstalledFiles: []string{ "app/Cargo.lock", }, }, diff --git a/pkg/fanal/handler/unpackaged/unpackaged.go b/pkg/fanal/handler/unpackaged/unpackaged.go index 5f450c7923cb..46c5ffc493f2 100644 --- a/pkg/fanal/handler/unpackaged/unpackaged.go +++ b/pkg/fanal/handler/unpackaged/unpackaged.go @@ -41,7 +41,7 @@ func NewUnpackagedHandler(opt artifact.Option) (handler.PostHandler, error) { func (h unpackagedHook) Handle(ctx context.Context, res *analyzer.AnalysisResult, blob *types.BlobInfo) error { for filePath, digest := range res.Digests { // Skip files installed by OS package managers. - if slices.Contains(res.SystemInstalledFiles, filePath) { + if slices.Contains(res.PkgInstalledFiles, filePath) { continue } diff --git a/pkg/fanal/types/artifact.go b/pkg/fanal/types/artifact.go index 6b46266ae1c5..07e9f7413a87 100644 --- a/pkg/fanal/types/artifact.go +++ b/pkg/fanal/types/artifact.go @@ -96,6 +96,9 @@ type Package struct { // lines from the lock file where the dependency is written Locations []Location `json:",omitempty"` + + // Files installed by the package + InstalledFiles []string `json:",omitempty"` } type Location struct { diff --git a/pkg/fanal/walker/fs.go b/pkg/fanal/walker/fs.go index 74be944e73ac..917c32413bfa 100644 --- a/pkg/fanal/walker/fs.go +++ b/pkg/fanal/walker/fs.go @@ -19,7 +19,7 @@ type FS struct { errCallback ErrorCallback } -func NewFS(skipFiles, skipDirs []string, slow bool, errCallback ErrorCallback) FS { +func NewFS(skipFiles, skipDirs []string, onlyDirs []string, slow bool, errCallback ErrorCallback) FS { if errCallback == nil { errCallback = func(pathname string, err error) error { // ignore permission errors @@ -32,7 +32,7 @@ func NewFS(skipFiles, skipDirs []string, slow bool, errCallback ErrorCallback) F } return FS{ - walker: newWalker(skipFiles, skipDirs, slow), + walker: newWalker(skipFiles, skipDirs, onlyDirs, slow), errCallback: errCallback, } } diff --git a/pkg/fanal/walker/fs_test.go b/pkg/fanal/walker/fs_test.go index 038e15ccc7d9..01c04f670eb5 100644 --- a/pkg/fanal/walker/fs_test.go +++ b/pkg/fanal/walker/fs_test.go @@ -19,6 +19,7 @@ func TestDir_Walk(t *testing.T) { skipFiles []string skipDirs []string errCallback walker.ErrorCallback + onlyDirs []string } tests := []struct { name string @@ -93,7 +94,7 @@ func TestDir_Walk(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, true, tt.fields.errCallback) + w := walker.NewFS(tt.fields.skipFiles, tt.fields.skipDirs, tt.fields.onlyDirs, true, tt.fields.errCallback) err := w.Walk(tt.rootDir, tt.analyzeFn) if tt.wantErr != "" { diff --git a/pkg/fanal/walker/tar.go b/pkg/fanal/walker/tar.go index ac4faaa070ac..a7096af6c4b4 100644 --- a/pkg/fanal/walker/tar.go +++ b/pkg/fanal/walker/tar.go @@ -25,14 +25,14 @@ type LayerTar struct { threshold int64 } -func NewLayerTar(skipFiles, skipDirs []string, slow bool) LayerTar { +func NewLayerTar(skipFiles, skipDirs, onlyDirs []string, slow bool) LayerTar { threshold := defaultSizeThreshold if slow { threshold = slowSizeThreshold } return LayerTar{ - walker: newWalker(skipFiles, skipDirs, slow), + walker: newWalker(skipFiles, skipDirs, onlyDirs, slow), threshold: threshold, } } diff --git a/pkg/fanal/walker/tar_test.go b/pkg/fanal/walker/tar_test.go index 8f1cb98a8010..86c54c77e63d 100644 --- a/pkg/fanal/walker/tar_test.go +++ b/pkg/fanal/walker/tar_test.go @@ -18,6 +18,7 @@ func TestLayerTar_Walk(t *testing.T) { type fields struct { skipFiles []string skipDirs []string + onlyDirs []string } tests := []struct { name string @@ -81,7 +82,7 @@ func TestLayerTar_Walk(t *testing.T) { f, err := os.Open("testdata/test.tar") require.NoError(t, err) - w := walker.NewLayerTar(tt.fields.skipFiles, tt.fields.skipDirs, true) + w := walker.NewLayerTar(tt.fields.skipFiles, tt.fields.skipDirs, tt.fields.onlyDirs, true) gotOpqDirs, gotWhFiles, err := w.Walk(f, tt.analyzeFn) if tt.wantErr != "" { diff --git a/pkg/fanal/walker/vm.go b/pkg/fanal/walker/vm.go index 172681d26ef4..a4a9bbeaa3b9 100644 --- a/pkg/fanal/walker/vm.go +++ b/pkg/fanal/walker/vm.go @@ -39,14 +39,14 @@ type VM struct { analyzeFn WalkFunc } -func NewVM(skipFiles, skipDirs []string, slow bool) VM { +func NewVM(skipFiles, skipDirs, onlyDirs []string, slow bool) VM { threshold := defaultSizeThreshold if slow { threshold = slowSizeThreshold } return VM{ - walker: newWalker(skipFiles, skipDirs, slow), + walker: newWalker(skipFiles, skipDirs, onlyDirs, slow), threshold: threshold, } } diff --git a/pkg/fanal/walker/walk.go b/pkg/fanal/walker/walk.go index 7205972b98e4..a4afd4e8ab88 100644 --- a/pkg/fanal/walker/walk.go +++ b/pkg/fanal/walker/walk.go @@ -32,10 +32,11 @@ type WalkFunc func(filePath string, info os.FileInfo, opener analyzer.Opener) er type walker struct { skipFiles []string skipDirs []string + onlyDirs []string slow bool } -func newWalker(skipFiles, skipDirs []string, slow bool) walker { +func newWalker(skipFiles, skipDirs, onlyDirs []string, slow bool) walker { var cleanSkipFiles, cleanSkipDirs []string for _, skipFile := range skipFiles { skipFile = filepath.ToSlash(filepath.Clean(skipFile)) @@ -43,6 +44,13 @@ func newWalker(skipFiles, skipDirs []string, slow bool) walker { cleanSkipFiles = append(cleanSkipFiles, skipFile) } + var cleanOnlyDirs []string + for _, onlyDir := range onlyDirs { + onlyDir = filepath.ToSlash(filepath.Clean(onlyDir)) + onlyDir = strings.TrimLeft(onlyDir, "/") + cleanOnlyDirs = append(cleanOnlyDirs, onlyDir) + } + for _, skipDir := range append(skipDirs, SystemDirs...) { skipDir = filepath.ToSlash(filepath.Clean(skipDir)) skipDir = strings.TrimLeft(skipDir, "/") @@ -52,6 +60,7 @@ func newWalker(skipFiles, skipDirs []string, slow bool) walker { return walker{ skipFiles: cleanSkipFiles, skipDirs: cleanSkipDirs, + onlyDirs: cleanOnlyDirs, slow: slow, } } @@ -91,5 +100,15 @@ func (w *walker) shouldSkipDir(dir string) bool { } } + // If onlyDirs is set then scan only these ones + if dir != "." && len(w.onlyDirs) > 0 { + for _, onlyDir := range w.onlyDirs { + if strings.HasPrefix(dir, onlyDir) || strings.HasPrefix(onlyDir, dir) { + return false + } + } + return true + } + return false } diff --git a/pkg/flag/scan_flags.go b/pkg/flag/scan_flags.go index 66280ecb255e..4932e10acfc9 100644 --- a/pkg/flag/scan_flags.go +++ b/pkg/flag/scan_flags.go @@ -18,6 +18,12 @@ var ( Default: []string{}, Usage: "specify the files or glob patterns to skip", } + OnlyDirsFlag = Flag{ + Name: "only-dirs", + ConfigName: "scan.only-dirs", + Default: []string{}, + Usage: "specify the directories where the traversal is allowed", + } OfflineScanFlag = Flag{ Name: "offline-scan", ConfigName: "scan.offline", @@ -77,44 +83,56 @@ var ( Default: false, Usage: "include development dependencies in the report (supported: npm, yarn)", } + RetainPkgInstalledFilesFlag = Flag{ + Name: "retain-pkg-installed-files", + ConfigName: "scan.retain-pkg-installed-files", + Default: false, + Usage: "retains the files installed by each package in the analysis output when set to true", + } ) type ScanFlagGroup struct { - SkipDirs *Flag - SkipFiles *Flag - OfflineScan *Flag - Scanners *Flag - FilePatterns *Flag - Slow *Flag - SBOMSources *Flag - RekorURL *Flag - IncludeDevDeps *Flag + SkipDirs *Flag + SkipFiles *Flag + OnlyDirs *Flag + OfflineScan *Flag + Scanners *Flag + FilePatterns *Flag + Slow *Flag + SBOMSources *Flag + RekorURL *Flag + IncludeDevDeps *Flag + RetainPkgInstalledFiles *Flag } type ScanOptions struct { - Target string - SkipDirs []string - SkipFiles []string - OfflineScan bool - Scanners types.Scanners - FilePatterns []string - Slow bool - SBOMSources []string - RekorURL string - IncludeDevDeps bool + Target string + SkipDirs []string + SkipFiles []string + OnlyDirs []string + OfflineScan bool + Scanners types.Scanners + FilePatterns []string + Slow bool + SBOMSources []string + RekorURL string + IncludeDevDeps bool + RetainPkgInstalledFiles bool } func NewScanFlagGroup() *ScanFlagGroup { return &ScanFlagGroup{ - SkipDirs: &SkipDirsFlag, - SkipFiles: &SkipFilesFlag, - OfflineScan: &OfflineScanFlag, - Scanners: &ScannersFlag, - FilePatterns: &FilePatternsFlag, - Slow: &SlowFlag, - SBOMSources: &SBOMSourcesFlag, - RekorURL: &RekorURLFlag, - IncludeDevDeps: &IncludeDevDepsFlag, + SkipDirs: &SkipDirsFlag, + SkipFiles: &SkipFilesFlag, + OnlyDirs: &OnlyDirsFlag, + OfflineScan: &OfflineScanFlag, + Scanners: &ScannersFlag, + FilePatterns: &FilePatternsFlag, + Slow: &SlowFlag, + SBOMSources: &SBOMSourcesFlag, + RekorURL: &RekorURLFlag, + IncludeDevDeps: &IncludeDevDepsFlag, + RetainPkgInstalledFiles: &RetainPkgInstalledFilesFlag, } } @@ -126,6 +144,7 @@ func (f *ScanFlagGroup) Flags() []*Flag { return []*Flag{ f.SkipDirs, f.SkipFiles, + f.OnlyDirs, f.OfflineScan, f.Scanners, f.FilePatterns, @@ -133,6 +152,7 @@ func (f *ScanFlagGroup) Flags() []*Flag { f.SBOMSources, f.RekorURL, f.IncludeDevDeps, + f.RetainPkgInstalledFiles, } } @@ -143,15 +163,17 @@ func (f *ScanFlagGroup) ToOptions(args []string) (ScanOptions, error) { } return ScanOptions{ - Target: target, - SkipDirs: getStringSlice(f.SkipDirs), - SkipFiles: getStringSlice(f.SkipFiles), - OfflineScan: getBool(f.OfflineScan), - Scanners: getUnderlyingStringSlice[types.Scanner](f.Scanners), - FilePatterns: getStringSlice(f.FilePatterns), - Slow: getBool(f.Slow), - SBOMSources: getStringSlice(f.SBOMSources), - RekorURL: getString(f.RekorURL), - IncludeDevDeps: getBool(f.IncludeDevDeps), + Target: target, + SkipDirs: getStringSlice(f.SkipDirs), + SkipFiles: getStringSlice(f.SkipFiles), + OnlyDirs: getStringSlice(f.OnlyDirs), + OfflineScan: getBool(f.OfflineScan), + Scanners: getUnderlyingStringSlice[types.Scanner](f.Scanners), + FilePatterns: getStringSlice(f.FilePatterns), + Slow: getBool(f.Slow), + SBOMSources: getStringSlice(f.SBOMSources), + RekorURL: getString(f.RekorURL), + IncludeDevDeps: getBool(f.IncludeDevDeps), + RetainPkgInstalledFiles: getBool(f.RetainPkgInstalledFiles), }, nil } diff --git a/pkg/flag/scan_flags_test.go b/pkg/flag/scan_flags_test.go index 1490ea3bf42f..719dcc4dc8e1 100644 --- a/pkg/flag/scan_flags_test.go +++ b/pkg/flag/scan_flags_test.go @@ -15,6 +15,7 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { type fields struct { skipDirs []string skipFiles []string + onlyDirs []string offlineScan bool scanners string } @@ -105,12 +106,23 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { }, assertion: require.NoError, }, + { + name: "only dirs", + fields: fields{ + onlyDirs: []string{"/Users"}, + }, + want: flag.ScanOptions{ + OnlyDirs: []string{"/Users"}, + }, + assertion: require.NoError, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { viper.Set(flag.SkipDirsFlag.ConfigName, tt.fields.skipDirs) viper.Set(flag.SkipFilesFlag.ConfigName, tt.fields.skipFiles) + viper.Set(flag.OnlyDirsFlag.ConfigName, tt.fields.onlyDirs) viper.Set(flag.OfflineScanFlag.ConfigName, tt.fields.offlineScan) viper.Set(flag.ScannersFlag.ConfigName, tt.fields.scanners) @@ -118,6 +130,7 @@ func TestScanFlagGroup_ToOptions(t *testing.T) { f := &flag.ScanFlagGroup{ SkipDirs: &flag.SkipDirsFlag, SkipFiles: &flag.SkipFilesFlag, + OnlyDirs: &flag.OnlyDirsFlag, OfflineScan: &flag.OfflineScanFlag, Scanners: &flag.ScannersFlag, } diff --git a/pkg/module/serialize/types.go b/pkg/module/serialize/types.go index 11bc34bed844..e2467d2b5f4d 100644 --- a/pkg/module/serialize/types.go +++ b/pkg/module/serialize/types.go @@ -20,7 +20,7 @@ type AnalysisResult struct { //PackageInfos []types.PackageInfo //Applications []types.Application //Secrets []types.Secret - //SystemInstalledFiles []string // A list of files installed by OS package manager + //PkgInstalledFiles []string // A list of files installed by OS package manager // Currently it supports custom resources only CustomResources []CustomResource