Skip to content

Commit da17dc7

Browse files
feat: add --distro flag to manually specify OS distribution for vulnerability scanning (#8070)
Signed-off-by: knqyf263 <knqyf263@gmail.com> Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
1 parent 90f1d8d commit da17dc7

27 files changed

+441
-138
lines changed

docs/docs/references/configuration/cli/trivy_filesystem.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ trivy filesystem [flags] PATH
3535
- "precise": Prioritizes precise by minimizing false positives.
3636
- "comprehensive": Aims to detect more security findings at the cost of potential false positives.
3737
(precise,comprehensive) (default "precise")
38+
--distro string [EXPERIMENTAL] specify a distribution, <family>/<version>
3839
--download-db-only download/update vulnerability database but don't run a scan
3940
--download-java-db-only download/update Java index database but don't run a scan
4041
--enable-modules strings [EXPERIMENTAL] module names to enable

docs/docs/references/configuration/cli/trivy_image.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ trivy image [flags] IMAGE_NAME
4949
- "precise": Prioritizes precise by minimizing false positives.
5050
- "comprehensive": Aims to detect more security findings at the cost of potential false positives.
5151
(precise,comprehensive) (default "precise")
52+
--distro string [EXPERIMENTAL] specify a distribution, <family>/<version>
5253
--docker-host string unix domain socket path to use for docker scanning
5354
--download-db-only download/update vulnerability database but don't run a scan
5455
--download-java-db-only download/update Java index database but don't run a scan

docs/docs/references/configuration/cli/trivy_kubernetes.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ trivy kubernetes [flags] [CONTEXT]
4545
- "comprehensive": Aims to detect more security findings at the cost of potential false positives.
4646
(precise,comprehensive) (default "precise")
4747
--disable-node-collector When the flag is activated, the node-collector job will not be executed, thus skipping misconfiguration findings on the node.
48+
--distro string [EXPERIMENTAL] specify a distribution, <family>/<version>
4849
--download-db-only download/update vulnerability database but don't run a scan
4950
--download-java-db-only download/update Java index database but don't run a scan
5051
--exclude-kinds strings indicate the kinds exclude from scanning (example: node)

docs/docs/references/configuration/cli/trivy_rootfs.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ trivy rootfs [flags] ROOTDIR
3737
- "precise": Prioritizes precise by minimizing false positives.
3838
- "comprehensive": Aims to detect more security findings at the cost of potential false positives.
3939
(precise,comprehensive) (default "precise")
40+
--distro string [EXPERIMENTAL] specify a distribution, <family>/<version>
4041
--download-db-only download/update vulnerability database but don't run a scan
4142
--download-java-db-only download/update Java index database but don't run a scan
4243
--enable-modules strings [EXPERIMENTAL] module names to enable

docs/docs/references/configuration/cli/trivy_sbom.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ trivy sbom [flags] SBOM_PATH
2929
- "precise": Prioritizes precise by minimizing false positives.
3030
- "comprehensive": Aims to detect more security findings at the cost of potential false positives.
3131
(precise,comprehensive) (default "precise")
32+
--distro string [EXPERIMENTAL] specify a distribution, <family>/<version>
3233
--download-db-only download/update vulnerability database but don't run a scan
3334
--download-java-db-only download/update Java index database but don't run a scan
3435
--exit-code int specify exit code when any security issues are found

docs/docs/references/configuration/cli/trivy_vm.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ trivy vm [flags] VM_IMAGE
3333
- "precise": Prioritizes precise by minimizing false positives.
3434
- "comprehensive": Aims to detect more security findings at the cost of potential false positives.
3535
(precise,comprehensive) (default "precise")
36+
--distro string [EXPERIMENTAL] specify a distribution, <family>/<version>
3637
--download-db-only download/update vulnerability database but don't run a scan
3738
--download-java-db-only download/update Java index database but don't run a scan
3839
--enable-modules strings [EXPERIMENTAL] module names to enable

docs/docs/references/configuration/config-file.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -570,6 +570,9 @@ scan:
570570
# Same as '--detection-priority'
571571
detection-priority: "precise"
572572

573+
# Same as '--distro'
574+
distro: ""
575+
573576
# Same as '--file-patterns'
574577
file-patterns: []
575578

docs/docs/scanner/vulnerability.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -339,6 +339,12 @@ Regardless of the chosen mode, user review of detected vulnerabilities is crucia
339339
- `precise`: Review thoroughly, considering potential missed vulnerabilities.
340340
- `comprehensive`: Carefully investigate each reported vulnerability due to increased false positive possibility.
341341
342+
### Overriding OS version
343+
By default, Trivy automatically detects the OS during container image scanning and performs vulnerability detection based on that OS.
344+
However, in some cases, you may want to scan an image with a different OS version than the one detected.
345+
Also, you may want to specify the OS version when OS is not detected.
346+
For these cases, Trivy supports a `--distro` flag using the `<family>/<version>` format (e.g. `alpine/3.20`) to set the desired OS version.
347+
342348
[^1]: https://github.com/GoogleContainerTools/distroless
343349
344350
[nvd-CVE-2023-0464]: https://nvd.nist.gov/vuln/detail/CVE-2023-0464

integration/client_server_test.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ type csArgs struct {
3636
ListAllPackages bool
3737
Target string
3838
secretConfig string
39+
Distro string
3940
}
4041

4142
func TestClientServer(t *testing.T) {
@@ -52,6 +53,18 @@ func TestClientServer(t *testing.T) {
5253
},
5354
golden: "testdata/alpine-39.json.golden",
5455
},
56+
{
57+
name: "alpine 3.9 as alpine 3.10",
58+
args: csArgs{
59+
Input: "testdata/fixtures/images/alpine-39.tar.gz",
60+
Distro: "alpine/3.10",
61+
},
62+
override: func(t *testing.T, want, got *types.Report) {
63+
want.Metadata.OS.Name = "3.10"
64+
want.Results[0].Target = "testdata/fixtures/images/alpine-39.tar.gz (alpine 3.10)"
65+
},
66+
golden: "testdata/alpine-39.json.golden",
67+
},
5568
{
5669
name: "alpine 3.9 with high and critical severity",
5770
args: csArgs{
@@ -684,6 +697,10 @@ func setupClient(t *testing.T, c csArgs, addr string, cacheDir string) []string
684697
osArgs = append(osArgs, c.Target)
685698
}
686699

700+
if c.Distro != "" {
701+
osArgs = append(osArgs, "--distro", c.Distro)
702+
}
703+
687704
return osArgs
688705
}
689706

integration/standalone_tar_test.go

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ func TestTar(t *testing.T) {
2424
SkipDirs []string
2525
SkipFiles []string
2626
DetectionPriority ftypes.DetectionPriority
27+
Distro string
2728
}
2829
tests := []struct {
29-
name string
30-
args args
31-
golden string
30+
name string
31+
args args
32+
golden string
33+
override func(t *testing.T, want, got *types.Report)
3234
}{
3335
{
3436
name: "alpine 3.9",
@@ -159,6 +161,19 @@ func TestTar(t *testing.T) {
159161
},
160162
golden: "testdata/alpine-39-ignore-cveids.json.golden",
161163
},
164+
{
165+
name: "alpine 3.9 as alpine 3.10",
166+
args: args{
167+
Format: types.FormatJSON,
168+
Input: "testdata/fixtures/images/alpine-39.tar.gz",
169+
Distro: "alpine/3.10",
170+
},
171+
override: func(t *testing.T, want, got *types.Report) {
172+
want.Metadata.OS.Name = "3.10"
173+
want.Results[0].Target = "testdata/fixtures/images/alpine-39.tar.gz (alpine 3.10)"
174+
},
175+
golden: "testdata/alpine-39.json.golden",
176+
},
162177
{
163178
name: "alpine 3.10",
164179
args: args{
@@ -345,7 +360,7 @@ func TestTar(t *testing.T) {
345360
name: "sle micro rancher 5.4",
346361
args: args{
347362
Format: types.FormatJSON,
348-
Input: "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz",
363+
Input: "testdata/fixtures/images/sle-micro-rancher-5.4_ndb.tar.gz",
349364
},
350365
golden: "testdata/sl-micro-rancher5.4.json.golden",
351366
},
@@ -434,8 +449,14 @@ func TestTar(t *testing.T) {
434449
osArgs = append(osArgs, "--detection-priority", string(tt.args.DetectionPriority))
435450
}
436451

452+
if tt.args.Distro != "" {
453+
osArgs = append(osArgs, "--distro", tt.args.Distro)
454+
}
455+
437456
// Run Trivy
438-
runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{})
457+
runTest(t, osArgs, tt.golden, "", tt.args.Format, runOptions{
458+
override: overrideFuncs(overrideUID, tt.override),
459+
})
439460
})
440461
}
441462
}

pkg/commands/app.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -476,6 +476,8 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
476476
repoFlags.ReportFlagGroup.Compliance = nil // disable '--compliance'
477477
repoFlags.ReportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol'
478478

479+
repoFlags.ScanFlagGroup.DistroFlag = nil // `repo` subcommand doesn't support scanning OS packages, so we can disable `--distro`
480+
479481
repoFlags.CacheFlagGroup.CacheBackend.Default = string(cache.TypeMemory) // Use memory cache by default
480482

481483
cmd := &cobra.Command{

pkg/commands/artifact/run.go

Lines changed: 1 addition & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -498,16 +498,7 @@ func (r *runner) initScannerConfig(ctx context.Context, opts flag.Options) (Scan
498498
target = opts.Input
499499
}
500500

501-
scanOptions := types.ScanOptions{
502-
PkgTypes: opts.PkgTypes,
503-
PkgRelationships: opts.PkgRelationships,
504-
Scanners: opts.Scanners,
505-
ImageConfigScanners: opts.ImageConfigScanners, // this is valid only for 'image' subcommand
506-
ScanRemovedPackages: opts.ScanRemovedPkgs, // this is valid only for 'image' subcommand
507-
LicenseCategories: opts.LicenseCategories,
508-
FilePatterns: opts.FilePatterns,
509-
IncludeDevDeps: opts.IncludeDevDeps,
510-
}
501+
scanOptions := opts.ScanOpts()
511502

512503
if len(opts.ImageConfigScanners) != 0 {
513504
log.WithPrefix(log.PrefixContainerImage).Info("Container image config scanners", log.Any("scanners", opts.ImageConfigScanners))

pkg/fanal/types/artifact.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,14 @@ type OS struct {
1515
Extended bool `json:"extended,omitempty"`
1616
}
1717

18+
func (o *OS) String() string {
19+
s := string(o.Family)
20+
if o.Name != "" {
21+
s += "/" + o.Name
22+
}
23+
return s
24+
}
25+
1826
func (o *OS) Detected() bool {
1927
return o.Family != ""
2028
}

pkg/fanal/types/artifact_test.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package types
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestOS_String(t *testing.T) {
10+
type fields struct {
11+
Family OSType
12+
Name string
13+
}
14+
tests := []struct {
15+
name string
16+
fields fields
17+
want string
18+
}{
19+
{
20+
name: "family and name",
21+
fields: fields{
22+
Family: OSType("ubuntu"),
23+
Name: "22.04",
24+
},
25+
want: "ubuntu/22.04",
26+
},
27+
{
28+
name: "empty name",
29+
fields: fields{
30+
Family: OSType("ubuntu"),
31+
Name: "",
32+
},
33+
want: "ubuntu",
34+
},
35+
{
36+
name: "empty",
37+
fields: fields{
38+
Family: "",
39+
Name: "",
40+
},
41+
want: "",
42+
},
43+
}
44+
for _, tt := range tests {
45+
t.Run(tt.name, func(t *testing.T) {
46+
o := &OS{
47+
Family: tt.fields.Family,
48+
Name: tt.fields.Name,
49+
}
50+
assert.Equal(t, tt.want, o.String())
51+
})
52+
}
53+
}

pkg/fanal/types/const.go

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -87,13 +87,37 @@ const (
8787
OCP LangType = "ocp" // Red Hat OpenShift Container Platform
8888
)
8989

90-
var AggregatingTypes = []LangType{
91-
PythonPkg,
92-
CondaPkg,
93-
GemSpec,
94-
NodePkg,
95-
Jar,
96-
}
90+
var (
91+
OSTypes = []OSType{
92+
Alma,
93+
Alpine,
94+
Amazon,
95+
Azure,
96+
CBLMariner,
97+
CentOS,
98+
Chainguard,
99+
Debian,
100+
Fedora,
101+
OpenSUSE,
102+
OpenSUSELeap,
103+
OpenSUSETumbleweed,
104+
Oracle,
105+
Photon,
106+
RedHat,
107+
Rocky,
108+
SLEMicro,
109+
SLES,
110+
Ubuntu,
111+
Wolfi,
112+
}
113+
AggregatingTypes = []LangType{
114+
PythonPkg,
115+
CondaPkg,
116+
GemSpec,
117+
NodePkg,
118+
Jar,
119+
}
120+
)
97121

98122
// Config files
99123
const (

pkg/flag/options.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -449,6 +449,21 @@ func (o *Options) enableSBOM() {
449449
}
450450
}
451451

452+
// ScanOpts returns options for scanning
453+
func (o *Options) ScanOpts() types.ScanOptions {
454+
return types.ScanOptions{
455+
PkgTypes: o.PkgTypes,
456+
PkgRelationships: o.PkgRelationships,
457+
Scanners: o.Scanners,
458+
ImageConfigScanners: o.ImageConfigScanners, // this is valid only for 'image' subcommand
459+
ScanRemovedPackages: o.ScanRemovedPkgs, // this is valid only for 'image' subcommand
460+
LicenseCategories: o.LicenseCategories,
461+
FilePatterns: o.FilePatterns,
462+
IncludeDevDeps: o.IncludeDevDeps,
463+
Distro: o.Distro,
464+
}
465+
}
466+
452467
// RegistryOpts returns options for OCI registries
453468
func (o *Options) RegistryOpts() ftypes.RegistryOptions {
454469
return ftypes.RegistryOptions{

0 commit comments

Comments
 (0)