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 report summary table #8177

Open
wants to merge 27 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 11 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
ff62336
feat(cli): add report summary
knqyf263 Dec 25, 2024
ca75fc9
test: fix table tests
DmitriyLewen Jan 16, 2025
ef26940
feat: add `--no-summary` flag
DmitriyLewen Jan 16, 2025
3ea064f
test: add test for `renderSummary`
DmitriyLewen Jan 16, 2025
8b2acf7
mage docs:generate
DmitriyLewen Jan 16, 2025
751823a
refactor: rename `no-summary` to `no-summary-table`
DmitriyLewen Jan 17, 2025
37a072d
fix: linter errors
DmitriyLewen Jan 17, 2025
f4f46a5
test: refactor secret result
DmitriyLewen Jan 17, 2025
4292e65
Merge branch 'main' of github.com:aquasecurity/trivy into feat/summar…
DmitriyLewen Jan 17, 2025
19fc0fe
docs: add info about summary table
DmitriyLewen Jan 17, 2025
a27f879
feat: add logs about `-` and `0` in summary table
DmitriyLewen Jan 17, 2025
239251b
fix: linter error
DmitriyLewen Jan 17, 2025
8b9d908
Merge branch 'main' of github.com:aquasecurity/trivy into feat/summar…
DmitriyLewen Jan 29, 2025
e11221a
refactor: use footer instead of log for legend
DmitriyLewen Jan 29, 2025
5f61893
refactor: use log when results array is empty
DmitriyLewen Jan 29, 2025
85edf16
chore: add comment for showEmptyResultsWarning
DmitriyLewen Jan 29, 2025
d5ca966
refactor
DmitriyLewen Jan 29, 2025
eb4d2fa
refactor: hide empty results for OS packages license and file licenses
DmitriyLewen Jan 29, 2025
a57dcc6
fix: add LicenseFiles into summary table
DmitriyLewen Jan 29, 2025
4f349c5
test: add LicenseFiles in test
DmitriyLewen Jan 29, 2025
84169b4
feat: split aggregated pkgs
DmitriyLewen Jan 29, 2025
892d6de
fix: show packages without vulns
DmitriyLewen Jan 29, 2025
db0cbe1
fix: tests
DmitriyLewen Jan 29, 2025
28ebb24
refactor: don't show empty vuln table title for OS pkgs
DmitriyLewen Jan 29, 2025
0166a45
refactor: use info log when results didn't find
DmitriyLewen Jan 29, 2025
5f13964
fix: typo
DmitriyLewen Jan 29, 2025
7f863b9
refactor: show legend after table
DmitriyLewen Jan 29, 2025
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
73 changes: 72 additions & 1 deletion docs/docs/configuration/reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,81 @@ Trivy supports the following formats:
| Secret | ✓ |
| License | ✓ |

```bash
$ trivy image -f table golang:1.22.11-alpine3.21
```
$ trivy image -f table golang:1.12-alpine

<details>
<summary>Result</summary>

```
...


Report Summary

┌─────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐
│ Target │ Type │ Vulnerabilities │ Secrets │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ golang:1.22.11-alpine3.21 (alpine 3.21.2) │ alpine │ 0 │ - │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ Node.js │ node-pkg │ 0 │ - │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/bin/go │ gobinary │ 0 │ - │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/bin/gofmt │ gobinary │ 0 │ - │
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
...
├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤
│ usr/local/go/pkg/tool/linux_amd64/vet │ gobinary │ 0 │ - │
└─────────────────────────────────────────────┴──────────┴─────────────────┴─────────┘

golang:1.22.11-alpine3.21 (alpine 3.21.2)

Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0)

```

</details>

#### Summary table
Before result tables Trivy shows summary table.

<details>
<summary>Report Summary</summary>

```
┌───────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐
│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ Java │ jar │ 2 │ - │ - │ - │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ requirements.txt │ text │ 0 │ - │ - │ - │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ requirements.txt │ text │ - │ - │ 1 │ - │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ OS Packages │ - │ - │ - │ - │ 1 │
├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤
│ Java │ - │ - │ - │ - │ 0 │
└───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘
```

</details>

This table:

- include columns for enabled [scanners](../references/terminology.md#scanner) only.
- Contains separate lines for the same targets but different scanners.
- `-` means that Trivy didn't scan this target.
- `0` means that Trivy scanned this target, but found no vulns/misconfigs.

!!! note
Use `--no-summary-table` flag to hide summary table.

#### Show origins of vulnerable dependencies

| Scanner | Supported |
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_config.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ trivy config [flags] DIR
--k8s-version string specify k8s version to validate outdated api by it (example: 1.21.0)
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-summary-table hide summary table
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
--password strings password. Comma-separated passwords allowed. TRIVY_PASSWORD should be used for security reasons.
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_convert.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ trivy convert [flags] RESULT_JSON
--ignore-policy string specify the Rego file path to evaluate each vulnerability
--ignorefile string specify .trivyignore file (default ".trivyignore")
--list-all-pkgs output all packages in the JSON report regardless of vulnerability
--no-summary-table hide summary table
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
--report string specify a report format for the output (all,summary) (default "all")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ trivy filesystem [flags] PATH
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-progress suppress progress bar
--no-summary-table hide summary table
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
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 @@ -82,6 +82,7 @@ trivy image [flags] IMAGE_NAME
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-progress suppress progress bar
--no-summary-table hide summary table
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ trivy kubernetes [flags] [CONTEXT]
--list-all-pkgs output all packages in the JSON report regardless of vulnerability
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--no-progress suppress progress bar
--no-summary-table hide summary table
--node-collector-imageref string indicate the image reference for the node-collector scan job (default "ghcr.io/aquasecurity/node-collector:0.3.1")
--node-collector-namespace string specify the namespace in which the node-collector job should be deployed (default "trivy-temp")
--offline-scan do not issue API requests to identify dependencies
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL)
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-progress suppress progress bar
--no-summary-table hide summary table
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
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 @@ -66,6 +66,7 @@ trivy rootfs [flags] ROOTDIR
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-progress suppress progress bar
--no-summary-table hide summary table
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
Expand Down
1 change: 1 addition & 0 deletions docs/docs/references/configuration/cli/trivy_sbom.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ trivy sbom [flags] SBOM_PATH
--java-db-repository strings OCI repository(ies) to retrieve trivy-java-db in order of priority (default [mirror.gcr.io/aquasec/trivy-java-db:1,ghcr.io/aquasecurity/trivy-java-db:1])
--list-all-pkgs output all packages in the JSON report regardless of vulnerability
--no-progress suppress progress bar
--no-summary-table hide summary table
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
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 @@ -58,6 +58,7 @@ trivy vm [flags] VM_IMAGE
--misconfig-scanners strings comma-separated list of misconfig scanners to use for misconfiguration scanning (default [azure-arm,cloudformation,dockerfile,helm,kubernetes,terraform,terraformplan-json,terraformplan-snapshot])
--module-dir string specify directory to the wasm modules that will be loaded (default "$HOME/.trivy/modules")
--no-progress suppress progress bar
--no-summary-table hide summary table
--offline-scan do not issue API requests to identify dependencies
-o, --output string output file name
--output-plugin-arg string [EXPERIMENTAL] output plugin arguments
Expand Down
3 changes: 3 additions & 0 deletions docs/docs/references/configuration/config-file.md
Original file line number Diff line number Diff line change
Expand Up @@ -521,6 +521,9 @@ ignorefile: ".trivyignore"
# Same as '--list-all-pkgs'
list-all-pkgs: false

# Same as '--no-summary-table'
no-summary-table: false

# Same as '--output'
output: ""

Expand Down
17 changes: 17 additions & 0 deletions pkg/flag/report_flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,11 @@ var (
ConfigName: "scan.show-suppressed",
Usage: "[EXPERIMENTAL] show suppressed vulnerabilities",
}
NoSummaryTableFlag = Flag[bool]{
Name: "no-summary-table",
ConfigName: "no-summary-table",
Usage: "hide summary table",
}
)

// ReportFlagGroup composes common printer flag structs
Expand All @@ -128,6 +133,7 @@ type ReportFlagGroup struct {
Severity *Flag[[]string]
Compliance *Flag[string]
ShowSuppressed *Flag[bool]
NoSummaryTable *Flag[bool]
}

type ReportOptions struct {
Expand All @@ -145,6 +151,7 @@ type ReportOptions struct {
Severities []dbTypes.Severity
Compliance spec.ComplianceSpec
ShowSuppressed bool
NoSummaryTable bool
}

func NewReportFlagGroup() *ReportFlagGroup {
Expand All @@ -163,6 +170,7 @@ func NewReportFlagGroup() *ReportFlagGroup {
Severity: SeverityFlag.Clone(),
Compliance: ComplianceFlag.Clone(),
ShowSuppressed: ShowSuppressedFlag.Clone(),
NoSummaryTable: NoSummaryTableFlag.Clone(),
}
}

Expand All @@ -186,6 +194,7 @@ func (f *ReportFlagGroup) Flags() []Flagger {
f.Severity,
f.Compliance,
f.ShowSuppressed,
f.NoSummaryTable,
}
}

Expand All @@ -198,6 +207,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
template := f.Template.Value()
dependencyTree := f.DependencyTree.Value()
listAllPkgs := f.ListAllPkgs.Value()
noSummaryTable := f.NoSummaryTable.Value()

if template != "" {
if format == "" {
Expand Down Expand Up @@ -227,6 +237,12 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
}
}

// "--so-summary" option is available only with "--format table".
if noSummaryTable && format != types.FormatTable {
noSummaryTable = false
log.Warn(`"--no-summary-table" can be used only with "--format table".`)
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For example, suppose a flag has already been added for a long time, and we don't want to terminate a program for backward compatibility as it will change behavior. In that case, we have to make it a warning, but an error is basically better because the user may not see the warning message.

Suggested change
log.Warn(`"--no-summary-table" can be used only with "--format table".`)
return xerrors.New(`"--no-summary-table" can be used only with "--format table".`)

}

cs, err := loadComplianceTypes(f.Compliance.Value())
if err != nil {
return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err)
Expand Down Expand Up @@ -259,6 +275,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) {
Severities: toSeverity(f.Severity.Value()),
Compliance: cs,
ShowSuppressed: f.ShowSuppressed.Value(),
NoSummaryTable: noSummaryTable,
}, nil
}

Expand Down
17 changes: 17 additions & 0 deletions pkg/flag/report_flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
compliance string
debug bool
pkgTypes string
noSummaryTable bool
}
tests := []struct {
name string
Expand Down Expand Up @@ -115,6 +116,20 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
ListAllPkgs: true,
},
},
{
name: "invalid option combination: --no-summary-table with --format json",
fields: fields{
format: "json",
noSummaryTable: true,
},
wantLogs: []string{
`"--no-summary-table" can be used only with "--format table".`,
},
want: flag.ReportOptions{
Format: "json",
NoSummaryTable: false,
},
},
{
name: "happy path with output plugin args",
fields: fields{
Expand Down Expand Up @@ -184,6 +199,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
setValue(flag.OutputPluginArgFlag.ConfigName, tt.fields.outputPluginArgs)
setValue(flag.SeverityFlag.ConfigName, tt.fields.severities)
setValue(flag.ComplianceFlag.ConfigName, tt.fields.compliance)
setValue(flag.NoSummaryTableFlag.ConfigName, tt.fields.noSummaryTable)

// Assert options
f := &flag.ReportFlagGroup{
Expand All @@ -199,6 +215,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) {
OutputPluginArg: flag.OutputPluginArgFlag.Clone(),
Severity: flag.SeverityFlag.Clone(),
Compliance: flag.ComplianceFlag.Clone(),
NoSummaryTable: flag.NoSummaryTableFlag.Clone(),
}

got, err := f.ToOptions()
Expand Down
86 changes: 86 additions & 0 deletions pkg/report/table/summary.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package table

import (
"github.com/aquasecurity/table"
"github.com/aquasecurity/trivy/pkg/types"
)

type Scanner interface {
Header() string
Alignment() table.Alignment

// Count returns the number of findings, but -1 if the scanner is not applicable
Count(result types.Result) int
}

func NewScanner(scanner types.Scanner) Scanner {
switch scanner {
case types.VulnerabilityScanner:
return VulnerabilityScanner{}
case types.MisconfigScanner:
return MisconfigScanner{}
case types.SecretScanner:
return SecretScanner{}
case types.LicenseScanner:
return LicenseScanner{}
}
return nil
}

type scannerAlignment struct{}

func (s scannerAlignment) Alignment() table.Alignment {
return table.AlignCenter
}

type VulnerabilityScanner struct{ scannerAlignment }

func (s VulnerabilityScanner) Header() string {
return "Vulnerabilities"
}

func (s VulnerabilityScanner) Count(result types.Result) int {
if result.Class == types.ClassOSPkg || result.Class == types.ClassLangPkg {
return len(result.Vulnerabilities)
}
return -1
}

type MisconfigScanner struct{ scannerAlignment }

func (s MisconfigScanner) Header() string {
return "Misconfigurations"
}

func (s MisconfigScanner) Count(result types.Result) int {
if result.Class == types.ClassConfig {
return len(result.Misconfigurations)
}
return -1
}

type SecretScanner struct{ scannerAlignment }

func (s SecretScanner) Header() string {
return "Secrets"
}

func (s SecretScanner) Count(result types.Result) int {
if result.Class == types.ClassSecret {
return len(result.Secrets)
}
return -1
}

type LicenseScanner struct{ scannerAlignment }

func (s LicenseScanner) Header() string {
return "Licenses"
}

func (s LicenseScanner) Count(result types.Result) int {
if result.Class == types.ClassLicense {
return len(result.Licenses)
}
return -1
}
Loading
Loading