Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add third party flag for os packages #4109

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions docs/docs/configuration/filtering.md
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,48 @@ Total: 7 (UNKNOWN: 0, LOW: 1, MEDIUM: 1, HIGH: 3, CRITICAL: 2)

</details>

## By Package Name

| Scanner | Supported |
|:----------------:|:---------:|
| Vulnerability | ✓ |
| Misconfiguration | |
| Secret | |
| License | |

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

By default, Trivy ignores language package files installed from package managers. You can read about it [here](../scanner/vulnerability/os.md#data-source-selection).

To scan these files as language packages, use `--third-party-os-pkgs` options with package names.

!!! warning
This feature removes the OS package from the report, so the dependency tree may not be complete.


```bash
$ trivy -d image --third-party-os-pkgs keycloak test_image
```

<details>
<summary>Result</summary>

```bash
2023-04-27T10:42:50.884+0600 DEBUG wolfi: the number of packages: 17
2023-04-27T10:42:50.885+0600 INFO Number of language-specific files: 1
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

2023-04-27T10:42:50.906+0600 INFO Table result includes only package filenames. Use '--format json' option to get the full path to the package file.

Java (jar)

Total: 4 (UNKNOWN: 0, LOW: 0, MEDIUM: 2, HIGH: 1, CRITICAL: 1)

```

</details>

## By Open Policy Agent

| Scanner | Supported |
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_image.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ trivy image [flags] IMAGE_NAME
-t, --template string output template
--tf-exclude-downloaded-modules remove results for downloaded modules in .terraform folder
--tf-vars strings specify paths to override the Terraform tfvars files
--third-party-os-pkgs strings [EXPERIMENTAL] parse files of these os packages as language packages (use GitHub and GitLab database for these files)
--token string for authentication in client/server mode
--token-header string specify a header name for token in client/server mode (default "Trivy-Token")
--trace enable more verbose trace output for custom queries
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_rootfs.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,7 @@ trivy rootfs [flags] ROOTDIR
-t, --template string output template
--tf-exclude-downloaded-modules remove results for downloaded modules in .terraform folder
--tf-vars strings specify paths to override the Terraform tfvars files
--third-party-os-pkgs strings [EXPERIMENTAL] parse files of these os packages as language packages (use GitHub and GitLab database for these files)
--token string for authentication in client/server mode
--token-header string specify a header name for token in client/server mode (default "Trivy-Token")
--trace enable more verbose trace output for custom queries
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ trivy vm [flags] VM_IMAGE
-t, --template string output template
--tf-exclude-downloaded-modules remove results for downloaded modules in .terraform folder
--tf-vars strings specify paths to override the Terraform tfvars files
--third-party-os-pkgs strings [EXPERIMENTAL] parse files of these os packages as language packages (use GitHub and GitLab database for these files)
--token string for authentication in client/server mode
--token-header string specify a header name for token in client/server mode (default "Trivy-Token")
--vuln-type strings comma-separated list of vulnerability types (os,library) (default [os,library])
Expand Down
20 changes: 13 additions & 7 deletions pkg/commands/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup.ReportFormat = &reportFormat
reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'

scanFlagGroup := flag.NewScanFlagGroup()
scanFlagGroup.ThirdPartyOSPkgs = nil // disable `--third-party-os-pkgs`

fsFlags := &flag.Flags{
CacheFlagGroup: flag.NewCacheFlagGroup(),
DBFlagGroup: flag.NewDBFlagGroup(),
Expand All @@ -332,7 +335,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
RegistryFlagGroup: flag.NewRegistryFlagGroup(),
RegoFlagGroup: flag.NewRegoFlagGroup(),
ReportFlagGroup: reportFlagGroup,
ScanFlagGroup: flag.NewScanFlagGroup(),
ScanFlagGroup: scanFlagGroup,
SecretFlagGroup: flag.NewSecretFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
}
Expand Down Expand Up @@ -448,9 +451,10 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
RepoFlagGroup: flag.NewRepoFlagGroup(),
}
repoFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary
repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance'
repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'
repoFlags.ReportFlagGroup.ReportFormat = nil // TODO: support --report summary
repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance'
repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'
repoFlags.ScanFlagGroup.ThirdPartyOSPkgs = nil // disable `--third-party-os-pkgs`

cmd := &cobra.Command{
Use: "repository [flags] REPO_URL",
Expand Down Expand Up @@ -875,7 +879,8 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
types.RBACScanner,
)
scanFlags.Scanners = &scanners
scanFlags.IncludeDevDeps = nil // disable '--include-dev-deps'
scanFlags.IncludeDevDeps = nil // disable '--include-dev-deps'
scanFlags.ThirdPartyOSPkgs = nil // disable `--third-party-os-pkgs`

// required only SourceFlag
imageFlags := &flag.ImageFlagGroup{ImageSources: &flag.SourceFlag}
Expand Down Expand Up @@ -1097,8 +1102,9 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup.ReportFormat = nil // TODO: support --report summary

scanFlagGroup := flag.NewScanFlagGroup()
scanFlagGroup.Scanners = nil // disable '--scanners' as it always scans for vulnerabilities
scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps'
scanFlagGroup.Scanners = nil // disable '--scanners' as it always scans for vulnerabilities
scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps'
scanFlagGroup.ThirdPartyOSPkgs = nil // disable `--third-party-os-pkgs`

sbomFlags := &flag.Flags{
CacheFlagGroup: flag.NewCacheFlagGroup(),
Expand Down
1 change: 1 addition & 0 deletions pkg/commands/artifact/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi
DisabledAnalyzers: disabledAnalyzers(opts),
SkipFiles: opts.SkipFiles,
SkipDirs: opts.SkipDirs,
ThirdPartyOSPkgs: opts.ThirdPartyOSPkgs,
FilePatterns: opts.FilePatterns,
Offline: opts.OfflineScan,
NoProgress: opts.NoProgress || opts.Quiet,
Expand Down
1 change: 1 addition & 0 deletions pkg/fanal/analyzer/analyzer.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ var (
type AnalyzerOptions struct {
Group Group
Slow bool
ThirdPartyOSPkgs []string // To exclude these package files from system files
FilePatterns []string
DisabledAnalyzers []Type
MisconfScannerOption misconf.ScannerOption
Expand Down
54 changes: 34 additions & 20 deletions pkg/fanal/analyzer/pkg/apk/apk.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ const analyzerVersion = 2

var requiredFiles = []string{"lib/apk/db/installed"}

type alpinePkgAnalyzer struct{}
type alpinePkgAnalyzer struct {
ThirdPartyPkgs []string
}

func (a alpinePkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
func (a *alpinePkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
scanner := bufio.NewScanner(input.Content)
parsedPkgs, installedFiles := a.parseApkInfo(scanner)

Expand All @@ -47,14 +49,15 @@ func (a alpinePkgAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInp
}, nil
}

func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package, []string) {
func (a *alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package, []string) {
var (
pkgs []types.Package
pkg types.Package
version string
dir string
installedFiles []string
provides = map[string]string{} // for dependency graph
pkgs []types.Package
pkg types.Package
pkgInstalledFiles []string
version string
dir string
installedFiles []string
provides = map[string]string{} // for dependency graph
)

for scanner.Scan() {
Expand All @@ -64,15 +67,20 @@ func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package
if len(line) < 2 {
if !pkg.Empty() {
pkgs = append(pkgs, pkg)
installedFiles = append(installedFiles, pkgInstalledFiles...)
}
pkg = types.Package{}
pkgInstalledFiles = []string{}
continue
}

// ref. https://wiki.alpinelinux.org/wiki/Apk_spec
switch line[:2] {
case "P:":
pkg.Name = line[2:]
// If user has marked package as third party package - we need to skip this package and parse files of this package as language packages
if !slices.Contains(a.ThirdPartyPkgs, line[2:]) {
pkg.Name = line[2:]
}
case "V:":
version = line[2:]
if !apkVersion.Valid(version) {
Expand All @@ -89,7 +97,7 @@ func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package
case "F:":
dir = line[2:]
case "R:":
installedFiles = append(installedFiles, path.Join(dir, line[2:]))
pkgInstalledFiles = append(pkgInstalledFiles, path.Join(dir, line[2:]))
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)
Expand All @@ -114,6 +122,7 @@ func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package
// in case of last paragraph
if !pkg.Empty() {
pkgs = append(pkgs, pkg)
installedFiles = append(installedFiles, pkgInstalledFiles...)
}

pkgs = a.uniquePkgs(pkgs)
Expand All @@ -124,7 +133,7 @@ func (a alpinePkgAnalyzer) parseApkInfo(scanner *bufio.Scanner) ([]types.Package
return pkgs, installedFiles
}

func (a alpinePkgAnalyzer) trimRequirement(s string) string {
func (a *alpinePkgAnalyzer) trimRequirement(s string) string {
// Trim version requirements
// e.g.
// so:libssl.so.1.1=1.1 => so:libssl.so.1.1
Expand All @@ -135,7 +144,7 @@ func (a alpinePkgAnalyzer) trimRequirement(s string) string {
return s
}

func (a alpinePkgAnalyzer) parseLicense(line string) []string {
func (a *alpinePkgAnalyzer) parseLicense(line string) []string {
line = line[2:] // Remove "L:"
if line == "" {
return nil
Expand All @@ -155,7 +164,7 @@ func (a alpinePkgAnalyzer) parseLicense(line string) []string {
return licenses
}

func (a alpinePkgAnalyzer) parseProvides(line, pkgID string, provides map[string]string) {
func (a *alpinePkgAnalyzer) parseProvides(line, pkgID string, provides map[string]string) {
for _, p := range strings.Fields(line[2:]) {
p = a.trimRequirement(p)

Expand All @@ -164,7 +173,7 @@ func (a alpinePkgAnalyzer) parseProvides(line, pkgID string, provides map[string
}
}

func (a alpinePkgAnalyzer) parseDependencies(line string) []string {
func (a *alpinePkgAnalyzer) parseDependencies(line string) []string {
line = line[2:] // Remove "D:"
return lo.FilterMap(strings.Fields(line), func(d string, _ int) (string, bool) {
// e.g. D:!uclibc-utils scanelf musl=1.1.14-r10 so:libc.musl-x86_64.so.1
Expand All @@ -175,7 +184,7 @@ func (a alpinePkgAnalyzer) parseDependencies(line string) []string {
})
}

func (a alpinePkgAnalyzer) consolidateDependencies(pkgs []types.Package, provides map[string]string) {
func (a *alpinePkgAnalyzer) consolidateDependencies(pkgs []types.Package, provides map[string]string) {
for i := range pkgs {
// e.g. libc6 => libc6@2.31-13+deb11u4
pkgs[i].DependsOn = lo.FilterMap(pkgs[i].DependsOn, func(d string, _ int) (string, bool) {
Expand All @@ -193,7 +202,7 @@ func (a alpinePkgAnalyzer) consolidateDependencies(pkgs []types.Package, provide
}
}

func (a alpinePkgAnalyzer) uniquePkgs(pkgs []types.Package) (uniqPkgs []types.Package) {
func (a *alpinePkgAnalyzer) uniquePkgs(pkgs []types.Package) (uniqPkgs []types.Package) {
uniq := map[string]struct{}{}
for _, pkg := range pkgs {
if _, ok := uniq[pkg.Name]; ok {
Expand All @@ -205,18 +214,23 @@ func (a alpinePkgAnalyzer) uniquePkgs(pkgs []types.Package) (uniqPkgs []types.Pa
return uniqPkgs
}

func (a alpinePkgAnalyzer) Required(filePath string, _ os.FileInfo) bool {
func (a *alpinePkgAnalyzer) Required(filePath string, _ os.FileInfo) bool {
return slices.Contains(requiredFiles, filePath)
}

func (a alpinePkgAnalyzer) Type() analyzer.Type {
func (a *alpinePkgAnalyzer) Type() analyzer.Type {
return analyzer.TypeApk
}

func (a alpinePkgAnalyzer) Version() int {
func (a *alpinePkgAnalyzer) Version() int {
return analyzerVersion
}

func (a *alpinePkgAnalyzer) Init(opt analyzer.AnalyzerOptions) error {
a.ThirdPartyPkgs = opt.ThirdPartyOSPkgs
return nil
}

// decodeChecksumLine decodes checksum line
func decodeChecksumLine(line string) digest.Digest {
if len(line) < 2 {
Expand Down
32 changes: 8 additions & 24 deletions pkg/fanal/analyzer/pkg/apk/apk_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ import (

func TestParseApkInfo(t *testing.T) {
var tests = map[string]struct {
path string
wantPkgs []types.Package
wantFiles []string
path string
thirdPartyPkgs []string
wantPkgs []types.Package
wantFiles []string
}{
"Valid": {
path: "./testdata/apk",
path: "./testdata/apk",
thirdPartyPkgs: []string{"busybox"},
wantPkgs: []types.Package{
{
ID: "musl@1.1.14-r10",
Expand All @@ -29,27 +31,16 @@ func TestParseApkInfo(t *testing.T) {
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",
DependsOn: []string{"musl@1.1.14-r10"},
},
{
ID: "alpine-keys@1.1-r0",
Expand Down Expand Up @@ -193,13 +184,6 @@ func TestParseApkInfo(t *testing.T) {
"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",

// alpine-baselayout-3.0.3-r0
"etc/hosts",
"etc/sysctl.conf",
Expand Down Expand Up @@ -293,8 +277,8 @@ func TestParseApkInfo(t *testing.T) {
},
},
}
a := alpinePkgAnalyzer{}
for testname, v := range tests {
a := alpinePkgAnalyzer{ThirdPartyPkgs: v.thirdPartyPkgs}
read, err := os.Open(v.path)
if err != nil {
t.Errorf("%s : can't open file %s", testname, v.path)
Expand Down
Loading