feat(flag): add exit-on-eosl option (#3423)

Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
Jack Lin
2023-02-15 16:51:15 +08:00
committed by GitHub
parent aa8e185e03
commit 32acd293fd
8 changed files with 92 additions and 1 deletions

View File

@@ -16,6 +16,7 @@ Scan Flags
Report Flags Report Flags
--dependency-tree show dependency origin tree (EXPERIMENTAL) --dependency-tree show dependency origin tree (EXPERIMENTAL)
--exit-code int specify exit code when any security issues are found --exit-code int specify exit code when any security issues are found
--exit-on-eosl exit with the specified code when the os of image ends of service/life
-f, --format string format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github, cosign-vuln) (default "table") -f, --format string format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github, cosign-vuln) (default "table")
--ignore-policy string specify the Rego file path to evaluate each vulnerability --ignore-policy string specify the Rego file path to evaluate each vulnerability
--ignorefile string specify .trivyignore file (default ".trivyignore") --ignorefile string specify .trivyignore file (default ".trivyignore")

View File

@@ -39,6 +39,7 @@ Scan Flags
Report Flags Report Flags
--exit-code int specify exit code when any security issues are found --exit-code int specify exit code when any security issues are found
--exit-on-eosl exit with the specified code when the os of image ends of service/life
-f, --format string format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github, cosign-vuln) (default "table") -f, --format string format (table, json, sarif, template, cyclonedx, spdx, spdx-json, github, cosign-vuln) (default "table")
--ignore-policy string specify the Rego file path to evaluate each vulnerability --ignore-policy string specify the Rego file path to evaluate each vulnerability
--ignorefile string specify .trivyignore file (default ".trivyignore") --ignorefile string specify .trivyignore file (default ".trivyignore")

View File

@@ -63,6 +63,10 @@ ignore-policy:
# Default is 0 # Default is 0
exit-code: 0 exit-code: 0
# Same as '--exit-on-eosl'
# Default is false
exit-on-eosl: false
# Same as '--output' # Same as '--output'
# Default is empty (stdout) # Default is empty (stdout)
output: output:

View File

@@ -68,6 +68,45 @@ $ trivy image --exit-code 0 --severity MEDIUM,HIGH ruby:2.4.0
$ trivy image --exit-code 1 --severity CRITICAL ruby:2.4.0 $ trivy image --exit-code 1 --severity CRITICAL ruby:2.4.0
``` ```
## Exit on EOSL
Sometimes you may surprisingly get 0 vulnerabilities in an old image:
- Enabling `--ignore-unfixed` option while all packages have no fixed versions.
- Scanning a rather outdated OS (e.g. Ubuntu 10.04).
An OS at the end of service/life (EOSL) usually gets into this situation, which is definitely full of vulnerabilities.
Use the `exit-on-eosl` option accompanied by `exit-code` option to exit with a non-zero code.
This flag is available with the following targets.
- Container images (`trivy image`)
- Virtual machine images (`trivy vm`)
- SBOM (`trivy sbom`)
- Root filesystem (`trivy rootfs`)
```
$ trivy image --exit-code 1 --exit-on-eosl testbeds/ubuntu:10.04
```
<details>
<summary>Result</summary>
```
2023-01-19T22:05:54.358+0800 WARN This OS version is no longer supported by the distribution: ubuntu 10.04
2023-01-19T22:05:54.358+0800 WARN The vulnerability detection may be insufficient because security updates are not provided
testbeds/ubuntu:10.04 (ubuntu 10.04)
====================================
Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)
```
</details>
This option is useful for CI/CD. The following example will fail when a critical vulnerability is found or the OS is EOSL:
```
$ ./trivy image --exit-code 1 --exit-on-eosl --severity CRITICAL alpine:3.16.3
```
## Reset ## Reset
The `--reset` option removes all caches and database. The `--reset` option removes all caches and database.
After this, it takes a long time as the vulnerability database needs to be rebuilt locally. After this, it takes a long time as the vulnerability database needs to be rebuilt locally.

View File

@@ -293,6 +293,7 @@ func NewFilesystemCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup := flag.NewReportFlagGroup() reportFlagGroup := flag.NewReportFlagGroup()
reportFlagGroup.ReportFormat = nil // TODO: support --report summary reportFlagGroup.ReportFormat = nil // TODO: support --report summary
reportFlagGroup.Compliance = nil // disable '--compliance' reportFlagGroup.Compliance = nil // disable '--compliance'
reportFlagGroup.ExitOnEOSL = nil // disable '--exit-on-eosl'
fsFlags := &flag.Flags{ fsFlags := &flag.Flags{
CacheFlagGroup: flag.NewCacheFlagGroup(), CacheFlagGroup: flag.NewCacheFlagGroup(),
@@ -402,6 +403,7 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup := flag.NewReportFlagGroup() reportFlagGroup := flag.NewReportFlagGroup()
reportFlagGroup.ReportFormat = nil // TODO: support --report summary reportFlagGroup.ReportFormat = nil // TODO: support --report summary
reportFlagGroup.Compliance = nil // disable '--compliance' reportFlagGroup.Compliance = nil // disable '--compliance'
reportFlagGroup.ExitOnEOSL = nil // disable '--exit-on-eosl'
repoFlags := &flag.Flags{ repoFlags := &flag.Flags{
CacheFlagGroup: flag.NewCacheFlagGroup(), CacheFlagGroup: flag.NewCacheFlagGroup(),
@@ -549,6 +551,7 @@ func NewConfigCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs' reportFlagGroup.ListAllPkgs = nil // disable '--list-all-pkgs'
reportFlagGroup.ReportFormat = nil // TODO: support --report summary reportFlagGroup.ReportFormat = nil // TODO: support --report summary
reportFlagGroup.Compliance = nil // disable '--compliance' reportFlagGroup.Compliance = nil // disable '--compliance'
reportFlagGroup.ExitOnEOSL = nil // disable '--exit-on-eosl'
scanFlags := &flag.ScanFlagGroup{ scanFlags := &flag.ScanFlagGroup{
// Enable only '--skip-dirs' and '--skip-files' and disable other flags // Enable only '--skip-dirs' and '--skip-files' and disable other flags
@@ -764,6 +767,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
compliance := flag.ComplianceFlag compliance := flag.ComplianceFlag
compliance.Usage += fmt.Sprintf(" (%s,%s)", types.ComplianceK8sNsa, types.ComplianceK8sCIS) compliance.Usage += fmt.Sprintf(" (%s,%s)", types.ComplianceK8sNsa, types.ComplianceK8sCIS)
reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand.
reportFlagGroup.ExitOnEOSL = nil // disable '--exit-on-eosl'
k8sFlags := &flag.Flags{ k8sFlags := &flag.Flags{
CacheFlagGroup: flag.NewCacheFlagGroup(), CacheFlagGroup: flag.NewCacheFlagGroup(),
@@ -826,6 +830,7 @@ func NewAWSCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
compliance := flag.ComplianceFlag compliance := flag.ComplianceFlag
compliance.Usage += fmt.Sprintf(" (%s, %s)", types.ComplianceAWSCIS12, types.ComplianceAWSCIS14) compliance.Usage += fmt.Sprintf(" (%s, %s)", types.ComplianceAWSCIS12, types.ComplianceAWSCIS14)
reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.Compliance = &compliance // override usage as the accepted values differ for each subcommand.
reportFlagGroup.ExitOnEOSL = nil // disable '--exit-on-eosl'
awsFlags := &flag.Flags{ awsFlags := &flag.Flags{
AWSFlagGroup: flag.NewAWSFlagGroup(), AWSFlagGroup: flag.NewAWSFlagGroup(),

View File

@@ -454,6 +454,7 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err
return xerrors.Errorf("report error: %w", err) return xerrors.Errorf("report error: %w", err)
} }
exitOnEosl(opts, report.Metadata)
Exit(opts, report.Results.Failed()) Exit(opts, report.Results.Failed())
return nil return nil
@@ -663,6 +664,12 @@ func Exit(opts flag.Options, failedResults bool) {
} }
} }
func exitOnEosl(opts flag.Options, m types.Metadata) {
if opts.ReportOptions.ExitOnEOSL && m.OS != nil && m.OS.Eosl {
Exit(opts, true)
}
}
func canonicalVersion(ver string) string { func canonicalVersion(ver string) string {
if ver == devVersion { if ver == devVersion {
return ver return ver

View File

@@ -72,6 +72,12 @@ var (
Value: 0, Value: 0,
Usage: "specify exit code when any security issues are found", Usage: "specify exit code when any security issues are found",
} }
ExitOnEOSLFlag = Flag{
Name: "exit-on-eosl",
ConfigName: "exit-on-eosl",
Value: false,
Usage: "exit with the specified code when the os of image ends of service/life",
}
OutputFlag = Flag{ OutputFlag = Flag{
Name: "output", Name: "output",
ConfigName: "output", ConfigName: "output",
@@ -105,6 +111,7 @@ type ReportFlagGroup struct {
IgnoreFile *Flag IgnoreFile *Flag
IgnorePolicy *Flag IgnorePolicy *Flag
ExitCode *Flag ExitCode *Flag
ExitOnEOSL *Flag
Output *Flag Output *Flag
Severity *Flag Severity *Flag
Compliance *Flag Compliance *Flag
@@ -118,6 +125,7 @@ type ReportOptions struct {
ListAllPkgs bool ListAllPkgs bool
IgnoreFile string IgnoreFile string
ExitCode int ExitCode int
ExitOnEOSL bool
IgnorePolicy string IgnorePolicy string
Output io.Writer Output io.Writer
Severities []dbTypes.Severity Severities []dbTypes.Severity
@@ -134,6 +142,7 @@ func NewReportFlagGroup() *ReportFlagGroup {
IgnoreFile: &IgnoreFileFlag, IgnoreFile: &IgnoreFileFlag,
IgnorePolicy: &IgnorePolicyFlag, IgnorePolicy: &IgnorePolicyFlag,
ExitCode: &ExitCodeFlag, ExitCode: &ExitCodeFlag,
ExitOnEOSL: &ExitOnEOSLFlag,
Output: &OutputFlag, Output: &OutputFlag,
Severity: &SeverityFlag, Severity: &SeverityFlag,
Compliance: &ComplianceFlag, Compliance: &ComplianceFlag,
@@ -154,6 +163,7 @@ func (f *ReportFlagGroup) Flags() []*Flag {
f.IgnoreFile, f.IgnoreFile,
f.IgnorePolicy, f.IgnorePolicy,
f.ExitCode, f.ExitCode,
f.ExitOnEOSL,
f.Output, f.Output,
f.Severity, f.Severity,
f.Compliance, f.Compliance,
@@ -161,12 +171,18 @@ func (f *ReportFlagGroup) Flags() []*Flag {
} }
func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) { func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
exitCode := getInt(f.ExitCode)
exitOnEOSL := getBool(f.ExitOnEOSL)
format := getString(f.Format) format := getString(f.Format)
template := getString(f.Template) template := getString(f.Template)
dependencyTree := getBool(f.DependencyTree) dependencyTree := getBool(f.DependencyTree)
listAllPkgs := getBool(f.ListAllPkgs) listAllPkgs := getBool(f.ListAllPkgs)
output := getString(f.Output) output := getString(f.Output)
if exitOnEOSL && exitCode == 0 {
log.Logger.Warn("'--exit-on-eosl' is ignored because '--exit-code' is 0 or not specified. Use '--exit-on-eosl' option with non-zero '--exit-code' option.")
}
if format != "" && !slices.Contains(report.SupportedFormats, format) { if format != "" && !slices.Contains(report.SupportedFormats, format) {
return ReportOptions{}, xerrors.Errorf("unknown format: %v", format) return ReportOptions{}, xerrors.Errorf("unknown format: %v", format)
} }
@@ -223,7 +239,8 @@ func (f *ReportFlagGroup) ToOptions(out io.Writer) (ReportOptions, error) {
DependencyTree: dependencyTree, DependencyTree: dependencyTree,
ListAllPkgs: listAllPkgs, ListAllPkgs: listAllPkgs,
IgnoreFile: getString(f.IgnoreFile), IgnoreFile: getString(f.IgnoreFile),
ExitCode: getInt(f.ExitCode), ExitCode: exitCode,
ExitOnEOSL: exitOnEOSL,
IgnorePolicy: getString(f.IgnorePolicy), IgnorePolicy: getString(f.IgnorePolicy),
Output: out, Output: out,
Severities: splitSeverity(getStringSlice(f.Severity)), Severities: splitSeverity(getStringSlice(f.Severity)),

View File

@@ -25,6 +25,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
ignoreUnfixed bool ignoreUnfixed bool
ignoreFile string ignoreFile string
exitCode int exitCode int
exitOnEOSL bool
ignorePolicy string ignorePolicy string
output string output string
severities string severities string
@@ -202,6 +203,20 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
Severities: []dbTypes.Severity{dbTypes.SeverityLow}, Severities: []dbTypes.Severity{dbTypes.SeverityLow},
}, },
}, },
{
name: "invalid option combination: --exit-code 0 with --exit-on-eosl",
fields: fields{
exitCode: 0,
exitOnEOSL: true,
},
wantLogs: []string{
"'--exit-on-eosl' is ignored because '--exit-code' is 0 or not specified. Use '--exit-on-eosl' option with non-zero '--exit-code' option.",
},
want: flag.ReportOptions{
Output: os.Stdout,
ExitOnEOSL: true,
},
},
} }
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
@@ -220,6 +235,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
viper.Set(flag.IgnoreUnfixedFlag.ConfigName, tt.fields.ignoreUnfixed) viper.Set(flag.IgnoreUnfixedFlag.ConfigName, tt.fields.ignoreUnfixed)
viper.Set(flag.IgnorePolicyFlag.ConfigName, tt.fields.ignorePolicy) viper.Set(flag.IgnorePolicyFlag.ConfigName, tt.fields.ignorePolicy)
viper.Set(flag.ExitCodeFlag.ConfigName, tt.fields.exitCode) viper.Set(flag.ExitCodeFlag.ConfigName, tt.fields.exitCode)
viper.Set(flag.ExitOnEOSLFlag.ConfigName, tt.fields.exitOnEOSL)
viper.Set(flag.OutputFlag.ConfigName, tt.fields.output) viper.Set(flag.OutputFlag.ConfigName, tt.fields.output)
viper.Set(flag.SeverityFlag.ConfigName, tt.fields.severities) viper.Set(flag.SeverityFlag.ConfigName, tt.fields.severities)
viper.Set(flag.ComplianceFlag.ConfigName, tt.fields.compliane) viper.Set(flag.ComplianceFlag.ConfigName, tt.fields.compliane)
@@ -233,6 +249,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
IgnoreFile: &flag.IgnoreFileFlag, IgnoreFile: &flag.IgnoreFileFlag,
IgnorePolicy: &flag.IgnorePolicyFlag, IgnorePolicy: &flag.IgnorePolicyFlag,
ExitCode: &flag.ExitCodeFlag, ExitCode: &flag.ExitCodeFlag,
ExitOnEOSL: &flag.ExitOnEOSLFlag,
Output: &flag.OutputFlag, Output: &flag.OutputFlag,
Severity: &flag.SeverityFlag, Severity: &flag.SeverityFlag,
Compliance: &flag.ComplianceFlag, Compliance: &flag.ComplianceFlag,