From ff62336fe08a6ae0196216fcf7b4a3be042c1cf7 Mon Sep 17 00:00:00 2001 From: knqyf263 Date: Wed, 25 Dec 2024 14:02:39 +0400 Subject: [PATCH 01/45] feat(cli): add report summary Signed-off-by: knqyf263 --- pkg/report/table/summary.go | 86 +++++++++++++++++++++++++++++++++++++ pkg/report/table/table.go | 75 ++++++++++++++++++++++++++++++++ pkg/report/writer.go | 1 + 3 files changed, 162 insertions(+) create mode 100644 pkg/report/table/summary.go diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go new file mode 100644 index 000000000000..e9bffc242050 --- /dev/null +++ b/pkg/report/table/summary.go @@ -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 +} diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 8bfa75922013..08f7651b5173 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -10,6 +10,8 @@ import ( "strings" "github.com/fatih/color" + "github.com/samber/lo" + "golang.org/x/xerrors" "github.com/aquasecurity/table" "github.com/aquasecurity/tml" @@ -29,6 +31,7 @@ var ( // Writer implements Writer and output in tabular form type Writer struct { + Scanners types.Scanners Severities []dbTypes.Severity Output io.Writer @@ -53,6 +56,13 @@ type Renderer interface { // Write writes the result on standard output func (tw Writer) Write(_ context.Context, report types.Report) error { + if !tw.isOutputToTerminal() { + tml.DisableFormatting() + } + + if err := tw.renderSummary(report); err != nil { + return xerrors.Errorf("failed to render summary: %w", err) + } for _, result := range report.Results { // Not display a table of custom resources @@ -64,6 +74,60 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { return nil } +func (tw Writer) renderSummary(report types.Report) error { + // Fprintln has a bug + if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"); err != nil { + return err + } + + t := newTableWriter(tw.Output, tw.isOutputToTerminal()) + t.SetAutoMerge(false) + t.SetColumnMaxWidth(80) + + var scanners []Scanner + for _, scanner := range tw.Scanners { + s := NewScanner(scanner) + if lo.IsNil(s) { + continue + } + scanners = append(scanners, s) + } + + headers := []string{ + "Target", + "Type", + } + alignments := []table.Alignment{ + table.AlignLeft, + table.AlignCenter, + } + for _, scanner := range scanners { + headers = append(headers, scanner.Header()) + alignments = append(alignments, scanner.Alignment()) + } + t.SetHeaders(headers...) + t.SetAlignment(alignments...) + + for _, result := range report.Results { + resultType := string(result.Type) + if result.Class == types.ClassSecret { + resultType = "text" + } else if result.Class == types.ClassLicense { + resultType = "-" + } + rows := []string{ + result.Target, + resultType, + } + for _, scanner := range scanners { + rows = append(rows, tw.colorizeCount(scanner.Count(result))) + } + t.AddRows(rows) + } + t.Render() + return nil +} + func (tw Writer) write(result types.Result) { if result.IsEmpty() && result.Class != types.ClassOSPkg { return @@ -97,6 +161,17 @@ func (tw Writer) isOutputToTerminal() bool { return IsOutputToTerminal(tw.Output) } +func (tw Writer) colorizeCount(count int) string { + if count < 0 { + return "-" + } + sprintf := fmt.Sprintf + if count != 0 && tw.isOutputToTerminal() { + sprintf = color.New(color.FgHiRed).SprintfFunc() + } + return sprintf("%d", count) +} + func newTableWriter(output io.Writer, isTerminal bool) *table.Table { tableWriter := table.New(output) if isTerminal { // use ansi output if we're not piping elsewhere diff --git a/pkg/report/writer.go b/pkg/report/writer.go index f25d579d66ef..c8079fa0f969 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -45,6 +45,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e switch option.Format { case types.FormatTable: writer = &table.Writer{ + Scanners: option.Scanners, Output: output, Severities: option.Severities, Tree: option.DependencyTree, From ca75fc90900dd483269f2c662850128292bd146f Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Thu, 16 Jan 2025 11:26:29 +0600 Subject: [PATCH 02/45] test: fix table tests --- pkg/report/table/table.go | 6 ++++ pkg/report/table/table_test.go | 55 ++++++++++++++++++++++++++++++---- 2 files changed, 55 insertions(+), 6 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 08f7651b5173..6b8fd86a51e9 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -93,6 +93,12 @@ func (tw Writer) renderSummary(report types.Report) error { scanners = append(scanners, s) } + // It should be an impossible case. + // But it is possible when Trivy is used as a library. + if len(scanners) == 0 { + return xerrors.Errorf("unable to find scanners") + } + headers := []string{ "Target", "Type", diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index d52dda0dc232..0da0bd361b5b 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -16,15 +16,21 @@ import ( func TestWriter_Write(t *testing.T) { testCases := []struct { name string + scanners types.Scanners results types.Results - expectedOutput string + wantOutput string + wantError string includeNonFailures bool }{ { name: "vulnerability and custom resource", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, results: types.Results{ { Target: "test", + Type: ftypes.Jar, Class: types.ClassLangPkg, Vulnerabilities: []types.DetectedVulnerability{ { @@ -48,9 +54,17 @@ func TestWriter_Write(t *testing.T) { }, }, }, - expectedOutput: ` -test () -======= + wantOutput: ` +Report Summary + +┌────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├────────┼──────┼─────────────────┤ +│ test │ jar │ 1 │ +└────────┴──────┴─────────────────┘ + +test (jar) +========== Total: 1 (MEDIUM: 0, HIGH: 1) ┌─────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┐ @@ -63,13 +77,36 @@ Total: 1 (MEDIUM: 0, HIGH: 1) }, { name: "no vulns", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + results: types.Results{ + { + Target: "test", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + }, + }, + wantOutput: ` +Report Summary + +┌────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├────────┼──────┼─────────────────┤ +│ test │ jar │ 0 │ +└────────┴──────┴─────────────────┘ +`, + }, + { + name: "no scanners", results: types.Results{ { Target: "test", Class: types.ClassLangPkg, + Type: ftypes.Jar, }, }, - expectedOutput: ``, + wantError: "unable to find scanners", }, } @@ -85,10 +122,16 @@ Total: 1 (MEDIUM: 0, HIGH: 1) dbTypes.SeverityHigh, dbTypes.SeverityMedium, }, + Scanners: tc.scanners, } err := writer.Write(nil, types.Report{Results: tc.results}) + if tc.wantError != "" { + require.Error(t, err) + return + } + require.NoError(t, err) - assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name) + assert.Equal(t, tc.wantOutput, tableWritten.String(), tc.name) }) } } From ef269402e73eea8836c4388f981fb32ec926c678 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Thu, 16 Jan 2025 11:59:10 +0600 Subject: [PATCH 03/45] feat: add `--no-summary` flag --- pkg/flag/report_flags.go | 17 +++++++++++++++++ pkg/flag/report_flags_test.go | 17 +++++++++++++++++ pkg/report/table/table.go | 9 +++++++-- pkg/report/table/table_test.go | 19 ++++++++++++++++++- pkg/report/writer.go | 1 + 5 files changed, 60 insertions(+), 3 deletions(-) diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index d69443e89547..52db8e050574 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -109,6 +109,11 @@ var ( ConfigName: "scan.show-suppressed", Usage: "[EXPERIMENTAL] show suppressed vulnerabilities", } + NoSummaryFlag = Flag[bool]{ + Name: "no-summary", + ConfigName: "no-summary", + Usage: "hide summary table", + } ) // ReportFlagGroup composes common printer flag structs @@ -128,6 +133,7 @@ type ReportFlagGroup struct { Severity *Flag[[]string] Compliance *Flag[string] ShowSuppressed *Flag[bool] + NoSummary *Flag[bool] } type ReportOptions struct { @@ -145,6 +151,7 @@ type ReportOptions struct { Severities []dbTypes.Severity Compliance spec.ComplianceSpec ShowSuppressed bool + NoSummary bool } func NewReportFlagGroup() *ReportFlagGroup { @@ -163,6 +170,7 @@ func NewReportFlagGroup() *ReportFlagGroup { Severity: SeverityFlag.Clone(), Compliance: ComplianceFlag.Clone(), ShowSuppressed: ShowSuppressedFlag.Clone(), + NoSummary: NoSummaryFlag.Clone(), } } @@ -186,6 +194,7 @@ func (f *ReportFlagGroup) Flags() []Flagger { f.Severity, f.Compliance, f.ShowSuppressed, + f.NoSummary, } } @@ -198,6 +207,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { template := f.Template.Value() dependencyTree := f.DependencyTree.Value() listAllPkgs := f.ListAllPkgs.Value() + noSummary := f.NoSummary.Value() if template != "" { if format == "" { @@ -227,6 +237,12 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { } } + // "--so-summary" option is available only with "--format table". + if noSummary && format != types.FormatTable { + noSummary = false + log.Warn(`"--no-summary" 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) @@ -259,6 +275,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Severities: toSeverity(f.Severity.Value()), Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), + NoSummary: noSummary, }, nil } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index ab4baa53fbff..ded87412f590 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -32,6 +32,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { compliance string debug bool pkgTypes string + noSummary bool } tests := []struct { name string @@ -115,6 +116,20 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { ListAllPkgs: true, }, }, + { + name: "invalid option combination: --no-summary with --format json", + fields: fields{ + format: "json", + noSummary: true, + }, + wantLogs: []string{ + `"--no-summary" can be used only with "--format table".`, + }, + want: flag.ReportOptions{ + Format: "json", + NoSummary: false, + }, + }, { name: "happy path with output plugin args", fields: fields{ @@ -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.NoSummaryFlag.ConfigName, tt.fields.noSummary) // Assert options f := &flag.ReportFlagGroup{ @@ -199,6 +215,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { OutputPluginArg: flag.OutputPluginArgFlag.Clone(), Severity: flag.SeverityFlag.Clone(), Compliance: flag.ComplianceFlag.Clone(), + NoSummary: flag.NoSummaryFlag.Clone(), } got, err := f.ToOptions() diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 6b8fd86a51e9..ca9442d32616 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -41,6 +41,9 @@ type Writer struct { // Show suppressed findings ShowSuppressed bool + // Hide summary table + NoSummary bool + // For misconfigurations IncludeNonFailures bool Trace bool @@ -60,8 +63,10 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { tml.DisableFormatting() } - if err := tw.renderSummary(report); err != nil { - return xerrors.Errorf("failed to render summary: %w", err) + if !tw.NoSummary { + if err := tw.renderSummary(report); err != nil { + return xerrors.Errorf("failed to render summary: %w", err) + } } for _, result := range report.Results { diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 0da0bd361b5b..78f3ae63c126 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -17,6 +17,7 @@ func TestWriter_Write(t *testing.T) { testCases := []struct { name string scanners types.Scanners + noSummary bool results types.Results wantOutput string wantError string @@ -97,6 +98,21 @@ Report Summary └────────┴──────┴─────────────────┘ `, }, + { + name: "no summary", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + noSummary: true, + results: types.Results{ + { + Target: "test", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + }, + }, + wantOutput: ``, + }, { name: "no scanners", results: types.Results{ @@ -122,7 +138,8 @@ Report Summary dbTypes.SeverityHigh, dbTypes.SeverityMedium, }, - Scanners: tc.scanners, + Scanners: tc.scanners, + NoSummary: tc.noSummary, } err := writer.Write(nil, types.Report{Results: tc.results}) if tc.wantError != "" { diff --git a/pkg/report/writer.go b/pkg/report/writer.go index c8079fa0f969..38cb16fdb8a0 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -54,6 +54,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e Trace: option.Trace, LicenseRiskThreshold: option.LicenseRiskThreshold, IgnoredLicenses: option.IgnoredLicenses, + NoSummary: option.NoSummary, } case types.FormatJSON: writer = &JSONWriter{ From 3ea064f9fdd1b59bdcbf601efa4b8c7a50558c7f Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Thu, 16 Jan 2025 15:13:30 +0600 Subject: [PATCH 04/45] test: add test for `renderSummary` --- pkg/report/table/table.go | 2 +- pkg/report/table/table_private_test.go | 227 +++++++++++++++++++++++++ 2 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 pkg/report/table/table_private_test.go diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index ca9442d32616..0da4b4a2c1c2 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -123,7 +123,7 @@ func (tw Writer) renderSummary(report types.Report) error { resultType := string(result.Type) if result.Class == types.ClassSecret { resultType = "text" - } else if result.Class == types.ClassLicense { + } else if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile { resultType = "-" } rows := []string{ diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go new file mode 100644 index 000000000000..d732c298ac2d --- /dev/null +++ b/pkg/report/table/table_private_test.go @@ -0,0 +1,227 @@ +package table + +import ( + "bytes" + "testing" + + "github.com/aquasecurity/tml" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +var ( + osVuln = types.Result{ + Target: "test (alpine 3.20.3)", + Class: types.ClassOSPkg, + Type: ftypes.Alpine, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2024-9143", + PkgName: "libcrypto3", + }, + { + VulnerabilityID: "CVE-2024-9143", + PkgName: "libssl3", + }, + }, + } + jarVuln = types.Result{ + Target: "Java", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-42003", + PkgName: "com.fasterxml.jackson.core:jackson-databind", + PkgPath: "app/jackson-databind-2.13.4.1.jar", + }, + { + VulnerabilityID: "CVE-2021-44832", + PkgName: "org.apache.logging.log4j:log4j-core", + PkgPath: "app/log4j-core-2.17.0.jar", + }, + }, + } + + noVuln = types.Result{ + Target: "requirements.txt", + Class: types.ClassLangPkg, + Type: ftypes.Pip, + } + + dockerfileMisconfig = types.Result{ + Target: "app/Dockerfile", + Class: types.ClassConfig, + Type: ftypes.Dockerfile, + Misconfigurations: []types.DetectedMisconfiguration{ + { + ID: "DS002", + }, + { + ID: "DS026", + }, + }, + } + secret = types.Result{ + Target: "/app/aws-secrets.txt", + Class: types.ClassSecret, + Secrets: []types.DetectedSecret{ + { + RuleID: "aws-access-key-id", + }, + }, + } + osLicense = types.Result{ + Target: "OS Packages", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Name: "GPL-2.0-only", + }, + }, + } + + jarLicense = types.Result{ + Target: "Java", + Class: types.ClassLicense, + } + fileLicense = types.Result{ + Target: "Loose File License(s)", + Class: types.ClassLicenseFile, + } +) + +func Test_renderSummary(t *testing.T) { + tests := []struct { + name string + scanners types.Scanners + noSummary bool + report types.Report + want string + }{ + { + name: "happy path all scanners", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + types.MisconfigScanner, + types.SecretScanner, + types.LicenseScanner, + }, + report: types.Report{ + Results: []types.Result{ + osVuln, + jarVuln, + dockerfileMisconfig, + secret, + osLicense, + jarLicense, + fileLicense, + }, + }, + want: ` +Report Summary + +┌───────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ +│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ Java │ jar │ 2 │ - │ - │ - │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ /app/aws-secrets.txt │ text │ - │ - │ 1 │ - │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ OS Packages │ - │ - │ - │ - │ 1 │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ Java │ - │ - │ - │ - │ 0 │ +├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ Loose File License(s) │ - │ - │ - │ - │ - │ +└───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ +`, + }, + { + name: "happy path vuln scanner only", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + }, + report: types.Report{ + Results: []types.Result{ + osVuln, + jarVuln, + }, + }, + want: ` +Report Summary + +┌──────────────────────┬────────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────────────────┼────────┼─────────────────┤ +│ test (alpine 3.20.3) │ alpine │ 2 │ +├──────────────────────┼────────┼─────────────────┤ +│ Java │ jar │ 2 │ +└──────────────────────┴────────┴─────────────────┘ +`, + }, + { + name: "happy path vuln scanner only without vulnerabilities", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + }, + report: types.Report{ + Results: []types.Result{ + noVuln, + }, + }, + want: ` +Report Summary + +┌──────────────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────────────┼──────┼─────────────────┤ +│ requirements.txt │ pip │ 0 │ +└──────────────────┴──────┴─────────────────┘ +`, + }, + { + name: "happy path vuln scanner only", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + }, + report: types.Report{ + Results: []types.Result{ + osVuln, + jarVuln, + }, + }, + want: ` +Report Summary + +┌──────────────────────┬────────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────────────────┼────────┼─────────────────┤ +│ test (alpine 3.20.3) │ alpine │ 2 │ +├──────────────────────┼────────┼─────────────────┤ +│ Java │ jar │ 2 │ +└──────────────────────┴────────┴─────────────────┘ +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tml.DisableFormatting() + tableWritten := bytes.Buffer{} + writer := Writer{ + Output: &tableWritten, + Scanners: tt.scanners, + NoSummary: tt.noSummary, + } + err := writer.renderSummary(tt.report) + require.NoError(t, err) + assert.Equal(t, tt.want, tableWritten.String()) + }) + } +} From 8b2acf7169a847ec2dd26102c4ac7f040b9604dc Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Thu, 16 Jan 2025 15:18:59 +0600 Subject: [PATCH 05/45] mage docs:generate --- docs/docs/references/configuration/cli/trivy_config.md | 1 + docs/docs/references/configuration/cli/trivy_convert.md | 1 + docs/docs/references/configuration/cli/trivy_filesystem.md | 1 + docs/docs/references/configuration/cli/trivy_image.md | 1 + docs/docs/references/configuration/cli/trivy_kubernetes.md | 1 + docs/docs/references/configuration/cli/trivy_repository.md | 1 + docs/docs/references/configuration/cli/trivy_rootfs.md | 1 + docs/docs/references/configuration/cli/trivy_sbom.md | 1 + docs/docs/references/configuration/cli/trivy_vm.md | 1 + docs/docs/references/configuration/config-file.md | 3 +++ 10 files changed, 12 insertions(+) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 7cc65a04e949..8ae258d3806c 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -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 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. diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md index d76d303e3b03..3c53495d27f5 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -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 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") diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index dab87fc541fc..e3aa97ed4a3c 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -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 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 diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index d9c312602190..b426621d0b02 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -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 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 diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 959532e1c806..4b5ee8fdc092 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -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 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 diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 38ae6611b595..99f37c1ee5b5 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -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 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 diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 35cc54ff66a3..45c34d3a5a6a 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -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 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 diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 63f855b13335..5fc019e7fa06 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -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 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 diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 6fe9de30fcd8..5441bee3d9c4 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -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 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 diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index fe6332522ee0..41b3153e5462 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -521,6 +521,9 @@ ignorefile: ".trivyignore" # Same as '--list-all-pkgs' list-all-pkgs: false +# Same as '--no-summary' +no-summary: false + # Same as '--output' output: "" From 751823ab5b640bda9303c6f506a50aa474c0e50c Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 17 Jan 2025 13:29:05 +0600 Subject: [PATCH 06/45] refactor: rename `no-summary` to `no-summary-table` --- .../configuration/cli/trivy_config.md | 2 +- .../configuration/cli/trivy_convert.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_kubernetes.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../configuration/cli/trivy_sbom.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- .../references/configuration/config-file.md | 4 ++-- pkg/flag/report_flags.go | 24 +++++++++---------- pkg/flag/report_flags_test.go | 18 +++++++------- pkg/report/table/table.go | 4 ++-- pkg/report/table/table_private_test.go | 16 ++++++------- pkg/report/table/table_test.go | 8 +++---- pkg/report/writer.go | 2 +- 16 files changed, 47 insertions(+), 47 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 8ae258d3806c..fb9d40a8cf69 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -36,7 +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 hide summary table + --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. diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md index 3c53495d27f5..ec01f6014eaf 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -27,7 +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 hide summary table + --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") diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index e3aa97ed4a3c..409b30357fee 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -64,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index b426621d0b02..8cace7ae9eef 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -82,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 4b5ee8fdc092..27f372ae3b89 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -77,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index 99f37c1ee5b5..4df9b3798d56 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -63,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 45c34d3a5a6a..a02dc2b289e8 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -66,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 5fc019e7fa06..0ecd234ab457 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -45,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 5441bee3d9c4..3d2349a78ca6 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -58,7 +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 hide summary table + --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 diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 41b3153e5462..01cf1a9a27cd 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -521,8 +521,8 @@ ignorefile: ".trivyignore" # Same as '--list-all-pkgs' list-all-pkgs: false -# Same as '--no-summary' -no-summary: false +# Same as '--no-summary-table' +no-summary-table: false # Same as '--output' output: "" diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index 52db8e050574..ee7db0198c77 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -109,9 +109,9 @@ var ( ConfigName: "scan.show-suppressed", Usage: "[EXPERIMENTAL] show suppressed vulnerabilities", } - NoSummaryFlag = Flag[bool]{ - Name: "no-summary", - ConfigName: "no-summary", + NoSummaryTableFlag = Flag[bool]{ + Name: "no-summary-table", + ConfigName: "no-summary-table", Usage: "hide summary table", } ) @@ -133,7 +133,7 @@ type ReportFlagGroup struct { Severity *Flag[[]string] Compliance *Flag[string] ShowSuppressed *Flag[bool] - NoSummary *Flag[bool] + NoSummaryTable *Flag[bool] } type ReportOptions struct { @@ -151,7 +151,7 @@ type ReportOptions struct { Severities []dbTypes.Severity Compliance spec.ComplianceSpec ShowSuppressed bool - NoSummary bool + NoSummaryTable bool } func NewReportFlagGroup() *ReportFlagGroup { @@ -170,7 +170,7 @@ func NewReportFlagGroup() *ReportFlagGroup { Severity: SeverityFlag.Clone(), Compliance: ComplianceFlag.Clone(), ShowSuppressed: ShowSuppressedFlag.Clone(), - NoSummary: NoSummaryFlag.Clone(), + NoSummaryTable: NoSummaryTableFlag.Clone(), } } @@ -194,7 +194,7 @@ func (f *ReportFlagGroup) Flags() []Flagger { f.Severity, f.Compliance, f.ShowSuppressed, - f.NoSummary, + f.NoSummaryTable, } } @@ -207,7 +207,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { template := f.Template.Value() dependencyTree := f.DependencyTree.Value() listAllPkgs := f.ListAllPkgs.Value() - noSummary := f.NoSummary.Value() + noSummaryTable := f.NoSummaryTable.Value() if template != "" { if format == "" { @@ -238,9 +238,9 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { } // "--so-summary" option is available only with "--format table". - if noSummary && format != types.FormatTable { - noSummary = false - log.Warn(`"--no-summary" can be used only with "--format table".`) + if noSummaryTable && format != types.FormatTable { + noSummaryTable = false + log.Warn(`"--no-summary-table" can be used only with "--format table".`) } cs, err := loadComplianceTypes(f.Compliance.Value()) @@ -275,7 +275,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Severities: toSeverity(f.Severity.Value()), Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), - NoSummary: noSummary, + NoSummaryTable: noSummaryTable, }, nil } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index ded87412f590..4cd99fe8aef7 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -32,7 +32,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { compliance string debug bool pkgTypes string - noSummary bool + noSummaryTable bool } tests := []struct { name string @@ -117,17 +117,17 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { }, }, { - name: "invalid option combination: --no-summary with --format json", + name: "invalid option combination: --no-summary-table with --format json", fields: fields{ - format: "json", - noSummary: true, + format: "json", + noSummaryTable: true, }, wantLogs: []string{ - `"--no-summary" can be used only with "--format table".`, + `"--no-summary-table" can be used only with "--format table".`, }, want: flag.ReportOptions{ - Format: "json", - NoSummary: false, + Format: "json", + NoSummaryTable: false, }, }, { @@ -199,7 +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.NoSummaryFlag.ConfigName, tt.fields.noSummary) + setValue(flag.NoSummaryTableFlag.ConfigName, tt.fields.noSummaryTable) // Assert options f := &flag.ReportFlagGroup{ @@ -215,7 +215,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { OutputPluginArg: flag.OutputPluginArgFlag.Clone(), Severity: flag.SeverityFlag.Clone(), Compliance: flag.ComplianceFlag.Clone(), - NoSummary: flag.NoSummaryFlag.Clone(), + NoSummaryTable: flag.NoSummaryTableFlag.Clone(), } got, err := f.ToOptions() diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 0da4b4a2c1c2..a4aeac332266 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -42,7 +42,7 @@ type Writer struct { ShowSuppressed bool // Hide summary table - NoSummary bool + NoSummaryTable bool // For misconfigurations IncludeNonFailures bool @@ -63,7 +63,7 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { tml.DisableFormatting() } - if !tw.NoSummary { + if !tw.NoSummaryTable { if err := tw.renderSummary(report); err != nil { return xerrors.Errorf("failed to render summary: %w", err) } diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index d732c298ac2d..77a5ca064996 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -95,11 +95,11 @@ var ( func Test_renderSummary(t *testing.T) { tests := []struct { - name string - scanners types.Scanners - noSummary bool - report types.Report - want string + name string + scanners types.Scanners + noSummaryTable bool + report types.Report + want string }{ { name: "happy path all scanners", @@ -215,9 +215,9 @@ Report Summary tml.DisableFormatting() tableWritten := bytes.Buffer{} writer := Writer{ - Output: &tableWritten, - Scanners: tt.scanners, - NoSummary: tt.noSummary, + Output: &tableWritten, + Scanners: tt.scanners, + NoSummaryTable: tt.noSummaryTable, } err := writer.renderSummary(tt.report) require.NoError(t, err) diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 78f3ae63c126..d88e8ee355c0 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -17,7 +17,7 @@ func TestWriter_Write(t *testing.T) { testCases := []struct { name string scanners types.Scanners - noSummary bool + noSummaryTable bool results types.Results wantOutput string wantError string @@ -103,7 +103,7 @@ Report Summary scanners: types.Scanners{ types.VulnerabilityScanner, }, - noSummary: true, + noSummaryTable: true, results: types.Results{ { Target: "test", @@ -138,8 +138,8 @@ Report Summary dbTypes.SeverityHigh, dbTypes.SeverityMedium, }, - Scanners: tc.scanners, - NoSummary: tc.noSummary, + Scanners: tc.scanners, + NoSummaryTable: tc.noSummaryTable, } err := writer.Write(nil, types.Report{Results: tc.results}) if tc.wantError != "" { diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 38cb16fdb8a0..81d76dca69c7 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -54,7 +54,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e Trace: option.Trace, LicenseRiskThreshold: option.LicenseRiskThreshold, IgnoredLicenses: option.IgnoredLicenses, - NoSummary: option.NoSummary, + NoSummaryTable: option.NoSummaryTable, } case types.FormatJSON: writer = &JSONWriter{ From 37a072d2223fd01c575fe57eba13b709ae8fe89b Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 17 Jan 2025 13:34:07 +0600 Subject: [PATCH 07/45] fix: linter errors --- pkg/report/table/table_private_test.go | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 77a5ca064996..15fc4d5c8e6f 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -4,11 +4,12 @@ import ( "bytes" "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "github.com/aquasecurity/tml" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" ) var ( From f4f46a5045d52eb9dd9b7b746cbe9598c4cb33e8 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 17 Jan 2025 14:02:44 +0600 Subject: [PATCH 08/45] test: refactor secret result --- pkg/report/table/table_private_test.go | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 15fc4d5c8e6f..c54c3ea5fb4c 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -66,7 +66,7 @@ var ( }, } secret = types.Result{ - Target: "/app/aws-secrets.txt", + Target: "requirements.txt", Class: types.ClassSecret, Secrets: []types.DetectedSecret{ { @@ -133,7 +133,7 @@ Report Summary ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │ ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ /app/aws-secrets.txt │ text │ - │ - │ 1 │ - │ +│ requirements.txt │ text │ - │ - │ 1 │ - │ ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ OS Packages │ - │ - │ - │ - │ 1 │ ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ @@ -167,23 +167,27 @@ Report Summary `, }, { - name: "happy path vuln scanner only without vulnerabilities", + name: "happy path no vulns + secret", scanners: []types.Scanner{ types.VulnerabilityScanner, + types.SecretScanner, }, report: types.Report{ Results: []types.Result{ noVuln, + secret, }, }, want: ` Report Summary -┌──────────────────┬──────┬─────────────────┐ -│ Target │ Type │ Vulnerabilities │ -├──────────────────┼──────┼─────────────────┤ -│ requirements.txt │ pip │ 0 │ -└──────────────────┴──────┴─────────────────┘ +┌──────────────────┬──────┬─────────────────┬─────────┐ +│ Target │ Type │ Vulnerabilities │ Secrets │ +├──────────────────┼──────┼─────────────────┼─────────┤ +│ requirements.txt │ pip │ 0 │ - │ +├──────────────────┼──────┼─────────────────┼─────────┤ +│ requirements.txt │ text │ - │ 1 │ +└──────────────────┴──────┴─────────────────┴─────────┘ `, }, { From 19fc0fe95eeb0e0ffe3f06660930f5b654506401 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 17 Jan 2025 14:35:33 +0600 Subject: [PATCH 09/45] docs: add info about summary table --- docs/docs/configuration/reporting.md | 73 +++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 1 deletion(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index c6ddc1727a71..389731c6f107 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -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 + +
+Result + +``` +... + + +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) + ``` +
+ +#### Summary table +Before result tables Trivy shows summary table. + +
+Report 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 │ +└───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ +``` + +
+ +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 | From a27f879092d5e3f3dd08efd2b68666633416d0bd Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 17 Jan 2025 14:38:38 +0600 Subject: [PATCH 10/45] feat: add logs about `-` and `0` in summary table --- pkg/report/table/table.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index a4aeac332266..8b1b7804563b 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -9,6 +9,7 @@ import ( "slices" "strings" + "github.com/aquasecurity/trivy/pkg/log" "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" @@ -80,6 +81,9 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { } func (tw Writer) renderSummary(report types.Report) error { + log.WithPrefix("report").Info("Report Summary table contains special symbols", + log.String("'-'", "Target didn't scanned"), + log.String("'0'", "Target scanned, but didn't contain vulns/misconfigs/secrets/licenses")) // Fprintln has a bug if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"); err != nil { return err From 239251bde638e6697e0beda1549c5c64bb8730d9 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Fri, 17 Jan 2025 14:51:16 +0600 Subject: [PATCH 11/45] fix: linter error --- pkg/report/table/table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 8b1b7804563b..eeac9794da5f 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -9,7 +9,6 @@ import ( "slices" "strings" - "github.com/aquasecurity/trivy/pkg/log" "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" @@ -17,6 +16,7 @@ import ( "github.com/aquasecurity/table" "github.com/aquasecurity/tml" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" ) From e11221a1af736f987de1aa165f28d1658efdacdf Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 11:52:47 +0600 Subject: [PATCH 12/45] refactor: use footer instead of log for legend --- pkg/report/table/table.go | 9 ++++---- pkg/report/table/table_private_test.go | 32 ++++++++------------------ 2 files changed, 13 insertions(+), 28 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index eeac9794da5f..3c5c59a33bde 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -16,7 +16,6 @@ import ( "github.com/aquasecurity/table" "github.com/aquasecurity/tml" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" ) @@ -81,11 +80,11 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { } func (tw Writer) renderSummary(report types.Report) error { - log.WithPrefix("report").Info("Report Summary table contains special symbols", - log.String("'-'", "Target didn't scanned"), - log.String("'0'", "Target scanned, but didn't contain vulns/misconfigs/secrets/licenses")) // Fprintln has a bug - if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"); err != nil { + if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"+ + "Legend:\n"+ + "- '-': Not scanned\n"+ + "- '0': Clean (no security findings detected)\n"); err != nil { return err } diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index c54c3ea5fb4c..30a5842f8f2e 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -124,6 +124,9 @@ func Test_renderSummary(t *testing.T) { want: ` Report Summary +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) ┌───────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ │ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │ ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ @@ -157,6 +160,9 @@ Report Summary want: ` Report Summary +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) ┌──────────────────────┬────────┬─────────────────┐ │ Target │ Type │ Vulnerabilities │ ├──────────────────────┼────────┼─────────────────┤ @@ -181,6 +187,9 @@ Report Summary want: ` Report Summary +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) ┌──────────────────┬──────┬─────────────────┬─────────┐ │ Target │ Type │ Vulnerabilities │ Secrets │ ├──────────────────┼──────┼─────────────────┼─────────┤ @@ -188,29 +197,6 @@ Report Summary ├──────────────────┼──────┼─────────────────┼─────────┤ │ requirements.txt │ text │ - │ 1 │ └──────────────────┴──────┴─────────────────┴─────────┘ -`, - }, - { - name: "happy path vuln scanner only", - scanners: []types.Scanner{ - types.VulnerabilityScanner, - }, - report: types.Report{ - Results: []types.Result{ - osVuln, - jarVuln, - }, - }, - want: ` -Report Summary - -┌──────────────────────┬────────┬─────────────────┐ -│ Target │ Type │ Vulnerabilities │ -├──────────────────────┼────────┼─────────────────┤ -│ test (alpine 3.20.3) │ alpine │ 2 │ -├──────────────────────┼────────┼─────────────────┤ -│ Java │ jar │ 2 │ -└──────────────────────┴────────┴─────────────────┘ `, }, } From 5f61893c4a1597c8fbc4a305b97086e37ea6c0ad Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 12:37:21 +0600 Subject: [PATCH 13/45] refactor: use log when results array is empty --- pkg/report/table/table.go | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 3c5c59a33bde..542f1098781d 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -9,6 +9,8 @@ import ( "slices" "strings" + "github.com/aquasecurity/trivy/pkg/log" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" @@ -80,6 +82,11 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { } func (tw Writer) renderSummary(report types.Report) error { + if len(report.Results) == 0 { + tw.showEmptyResultsWarning() + return nil + } + // Fprintln has a bug if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"+ "Legend:\n"+ @@ -142,6 +149,34 @@ func (tw Writer) renderSummary(report types.Report) error { return nil } +// showEmptyResultsWarning +func (tw Writer) showEmptyResultsWarning() { + resultByFiles := []types.Scanner{ + types.VulnerabilityScanner, + types.MisconfigScanner, + } + resultByFindings := []types.Scanner{ + types.SecretScanner, + types.LicenseScanner, + } + + var warnStrings []string + if scanners := lo.Intersect(resultByFiles, tw.Scanners); len(scanners) > 0 { + warnStrings = append(warnStrings, fmt.Sprintf("Supported files for %s scanner(s) not found", + strings.Join(xstrings.ToStringSlice(scanners), "/"))) + } + if scanners := lo.Intersect(resultByFindings, tw.Scanners); len(scanners) > 0 { + warnStrings = append(warnStrings, fmt.Sprintf("No results found for %s scanner(s)", + strings.Join(xstrings.ToStringSlice(scanners), "/"))) + } + + if len(warnStrings) == 0 { + warnStrings = append(warnStrings, "Scanners are not enabled.") + } + + log.WithPrefix("report").Warn(strings.Join(warnStrings, ". ")) +} + func (tw Writer) write(result types.Result) { if result.IsEmpty() && result.Class != types.ClassOSPkg { return From 85edf167684f538e4ffc63e5fa7bfff94641ff0e Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 12:41:12 +0600 Subject: [PATCH 14/45] chore: add comment for showEmptyResultsWarning --- pkg/report/table/table.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 542f1098781d..a3ae206a5435 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -149,7 +149,9 @@ func (tw Writer) renderSummary(report types.Report) error { return nil } -// showEmptyResultsWarning +// showEmptyResultsWarning shows WARN why the results array is empty based on the enabled scanners. +// We need to separate the vuln/misconfig and secret/license scanners, +// because the results array contains results without findings for vulns/misconfig only. func (tw Writer) showEmptyResultsWarning() { resultByFiles := []types.Scanner{ types.VulnerabilityScanner, From d5ca9660c17933cba0414171f917d2ce847d74a7 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 12:46:47 +0600 Subject: [PATCH 15/45] refactor --- pkg/report/table/table.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index a3ae206a5435..d9caca9c86aa 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -164,11 +164,11 @@ func (tw Writer) showEmptyResultsWarning() { var warnStrings []string if scanners := lo.Intersect(resultByFiles, tw.Scanners); len(scanners) > 0 { - warnStrings = append(warnStrings, fmt.Sprintf("Supported files for %s scanner(s) not found", + warnStrings = append(warnStrings, fmt.Sprintf("Supported files for %s scanner(s) not found.", strings.Join(xstrings.ToStringSlice(scanners), "/"))) } if scanners := lo.Intersect(resultByFindings, tw.Scanners); len(scanners) > 0 { - warnStrings = append(warnStrings, fmt.Sprintf("No results found for %s scanner(s)", + warnStrings = append(warnStrings, fmt.Sprintf("No results found for %s scanner(s).", strings.Join(xstrings.ToStringSlice(scanners), "/"))) } @@ -176,7 +176,7 @@ func (tw Writer) showEmptyResultsWarning() { warnStrings = append(warnStrings, "Scanners are not enabled.") } - log.WithPrefix("report").Warn(strings.Join(warnStrings, ". ")) + log.WithPrefix("report").Warn(strings.Join(warnStrings, " ")) } func (tw Writer) write(result types.Result) { From eb4d2faa7cb052f85f7a5804d4efb3b3fe4a7c19 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 13:31:28 +0600 Subject: [PATCH 16/45] refactor: hide empty results for OS packages license and file licenses --- pkg/flag/options.go | 1 + pkg/rpc/client/client.go | 1 + pkg/rpc/server/server.go | 1 + pkg/scanner/local/scan.go | 63 ++++++++++-------- pkg/scanner/local/scan_test.go | 1 + pkg/types/scan.go | 3 +- rpc/scanner/service.pb.go | 116 ++++++++++++++++++--------------- rpc/scanner/service.proto | 1 + rpc/scanner/service.twirp.go | 89 ++++++++++++------------- 9 files changed, 152 insertions(+), 124 deletions(-) diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 775eec1366cd..4a21450fd5eb 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -460,6 +460,7 @@ func (o *Options) ScanOpts() types.ScanOptions { ImageConfigScanners: o.ImageConfigScanners, // this is valid only for 'image' subcommand ScanRemovedPackages: o.ScanRemovedPkgs, // this is valid only for 'image' subcommand LicenseCategories: o.LicenseCategories, + LicenseFull: o.LicenseFull, FilePatterns: o.FilePatterns, IncludeDevDeps: o.IncludeDevDeps, Distro: o.Distro, diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index dd13b145878f..9f64df447613 100644 --- a/pkg/rpc/client/client.go +++ b/pkg/rpc/client/client.go @@ -97,6 +97,7 @@ func (s Scanner) Scan(ctx context.Context, target, artifactKey string, blobKeys PkgRelationships: xstrings.ToStringSlice(opts.PkgRelationships), Scanners: xstrings.ToStringSlice(opts.Scanners), LicenseCategories: licenseCategories, + LicenseFull: opts.LicenseFull, IncludeDevDeps: opts.IncludeDevDeps, Distro: distro, }, diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 0bc7492783d8..9a9eb62fa9ec 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -87,6 +87,7 @@ func (s *ScanServer) ToOptions(in *rpcScanner.ScanOptions) types.ScanOptions { Scanners: scanners, IncludeDevDeps: in.IncludeDevDeps, LicenseCategories: licenseCategories, + LicenseFull: in.LicenseFull, Distro: distro, } } diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 1d117999ede1..55178e30d032 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -262,17 +262,22 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions scanner := licensing.NewScanner(options.LicenseCategories) // License - OS packages - var osPkgLicenses []types.DetectedLicense - for _, pkg := range target.Packages { - for _, license := range pkg.Licenses { - osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, "")) + if len(target.Packages) > 0 { + var osPkgLicenses []types.DetectedLicense + for _, pkg := range target.Packages { + for _, license := range pkg.Licenses { + osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, "")) + } } + // We only need to add result with OS package licenses if Packages were found. + // This is to avoid user confusion. + // e.g. when we didn't find packages but show that we didn't find licenses in the Packages. + results = append(results, types.Result{ + Target: "OS Packages", + Class: types.ClassLicense, + Licenses: osPkgLicenses, + }) } - results = append(results, types.Result{ - Target: "OS Packages", - Class: types.ClassLicense, - Licenses: osPkgLicenses, - }) // License - language-specific packages for _, app := range target.Applications { @@ -300,26 +305,32 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions } // License - file header or license file - var fileLicenses []types.DetectedLicense - for _, license := range target.Licenses { - for _, finding := range license.Findings { - category, severity := scanner.Scan(finding.Name) - fileLicenses = append(fileLicenses, types.DetectedLicense{ - Severity: severity, - Category: category, - FilePath: license.FilePath, - Name: finding.Name, - Confidence: finding.Confidence, - Link: finding.Link, - }) + if options.LicenseFull { + var fileLicenses []types.DetectedLicense + for _, license := range target.Licenses { + for _, finding := range license.Findings { + category, severity := scanner.Scan(finding.Name) + fileLicenses = append(fileLicenses, types.DetectedLicense{ + Severity: severity, + Category: category, + FilePath: license.FilePath, + Name: finding.Name, + Confidence: finding.Confidence, + Link: finding.Link, + }) + } } + + // We only need to add the result with license files if the `--license-full` flag is enabled. + // This is to avoid user confusion. + // e.g. the user might think that we were looking for licenses but didn't find them. + results = append(results, types.Result{ + Target: "Loose File License(s)", + Class: types.ClassLicenseFile, + Licenses: fileLicenses, + }) } - results = append(results, types.Result{ - Target: "Loose File License(s)", - Class: types.ClassLicenseFile, - Licenses: fileLicenses, - }) return results } diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 0e904806fbd4..1de72044f21e 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -298,6 +298,7 @@ func TestScanner_Scan(t *testing.T) { options: types.ScanOptions{ PkgRelationships: ftypes.Relationships, Scanners: types.Scanners{types.LicenseScanner}, + LicenseFull: true, }, }, fixtures: []string{"testdata/fixtures/happy.yaml"}, diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 4dedbce6fcf1..cdb161f407c5 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -111,7 +111,7 @@ type ScanTarget struct { CustomResources []types.CustomResource } -// ScanOptions holds the attributes for scanning vulnerabilities +// ScanOptions holds the attributes for scanning vulnerabilities/licenses type ScanOptions struct { PkgTypes []string PkgRelationships []types.Relationship @@ -119,6 +119,7 @@ type ScanOptions struct { ImageConfigScanners Scanners // Scanners for container image configuration ScanRemovedPackages bool LicenseCategories map[types.LicenseCategory][]string + LicenseFull bool FilePatterns []string IncludeDevDeps bool Distro types.OS // Forced OS diff --git a/rpc/scanner/service.pb.go b/rpc/scanner/service.pb.go index 00ff3485240c..4c27ba8c0600 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -152,6 +152,7 @@ type ScanOptions struct { IncludeDevDeps bool `protobuf:"varint,5,opt,name=include_dev_deps,json=includeDevDeps,proto3" json:"include_dev_deps,omitempty"` PkgRelationships []string `protobuf:"bytes,6,rep,name=pkg_relationships,json=pkgRelationships,proto3" json:"pkg_relationships,omitempty"` Distro *common.OS `protobuf:"bytes,7,opt,name=distro,proto3" json:"distro,omitempty"` + LicenseFull bool `protobuf:"varint,8,opt,name=license_full,json=licenseFull,proto3" json:"license_full,omitempty"` } func (x *ScanOptions) Reset() { @@ -228,6 +229,13 @@ func (x *ScanOptions) GetDistro() *common.OS { return nil } +func (x *ScanOptions) GetLicenseFull() bool { + if x != nil { + return x.LicenseFull + } + return false +} + type ScanResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -414,7 +422,7 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x52, 0x07, 0x6f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x22, 0x20, 0x0a, 0x08, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, - 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0x94, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, + 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xb7, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, 0x70, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x1b, 0x0a, 0x09, 0x70, 0x6b, 0x67, 0x5f, 0x74, 0x79, 0x70, 0x65, 0x73, 0x18, 0x01, 0x20, 0x03, 0x28, 0x09, 0x52, 0x08, 0x70, 0x6b, 0x67, 0x54, 0x79, 0x70, 0x65, 0x73, 0x12, 0x1a, 0x0a, 0x08, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x73, 0x18, @@ -433,58 +441,60 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x6c, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x68, 0x69, 0x70, 0x73, 0x12, 0x28, 0x0a, 0x06, 0x64, 0x69, 0x73, 0x74, 0x72, 0x6f, 0x18, 0x07, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x06, 0x64, - 0x69, 0x73, 0x74, 0x72, 0x6f, 0x1a, 0x60, 0x0a, 0x16, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, - 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, 0x72, 0x79, 0x12, - 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, - 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0b, - 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52, 0x05, 0x76, 0x61, - 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, 0x22, 0x64, 0x0a, - 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x20, 0x0a, - 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, 0x72, 0x69, 0x76, - 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, 0x6f, 0x73, 0x12, - 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, 0x03, 0x28, 0x0b, - 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, - 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, 0x65, 0x73, 0x75, - 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x12, 0x16, - 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, - 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, - 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x56, - 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, 0x0f, 0x76, 0x75, - 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x12, 0x54, 0x0a, - 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, - 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, - 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, - 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, - 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, - 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, 0x06, 0x20, 0x01, - 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, 0x74, 0x79, 0x70, - 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, 0x12, 0x31, 0x0a, - 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, 0x28, 0x0b, 0x32, - 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x50, - 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, - 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, 0x73, 0x6f, 0x75, - 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, - 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, 0x73, 0x65, 0x63, - 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, - 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, 0x72, 0x65, 0x74, - 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, - 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, 0x09, 0x20, 0x03, - 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, - 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, - 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x32, 0x50, 0x0a, 0x07, 0x53, - 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, 0x53, 0x63, 0x61, 0x6e, 0x12, 0x1d, - 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, - 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x1e, 0x2e, - 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, - 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x33, 0x5a, - 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, 0x71, 0x75, 0x61, - 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2f, 0x72, - 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x3b, 0x73, 0x63, 0x61, 0x6e, 0x6e, - 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, + 0x69, 0x73, 0x74, 0x72, 0x6f, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, + 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x18, 0x08, 0x20, 0x01, 0x28, 0x08, 0x52, 0x0b, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x46, 0x75, 0x6c, 0x6c, 0x1a, 0x60, 0x0a, 0x16, 0x4c, 0x69, 0x63, 0x65, + 0x6e, 0x73, 0x65, 0x43, 0x61, 0x74, 0x65, 0x67, 0x6f, 0x72, 0x69, 0x65, 0x73, 0x45, 0x6e, 0x74, + 0x72, 0x79, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, + 0x03, 0x6b, 0x65, 0x79, 0x12, 0x30, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x02, 0x20, + 0x01, 0x28, 0x0b, 0x32, 0x1a, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x4c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x52, + 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x3a, 0x02, 0x38, 0x01, 0x4a, 0x04, 0x08, 0x03, 0x10, 0x04, + 0x22, 0x64, 0x0a, 0x0c, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x12, 0x20, 0x0a, 0x02, 0x6f, 0x73, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0b, 0x32, 0x10, 0x2e, 0x74, + 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x4f, 0x53, 0x52, 0x02, + 0x6f, 0x73, 0x12, 0x32, 0x0a, 0x07, 0x72, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x18, 0x03, 0x20, + 0x03, 0x28, 0x0b, 0x32, 0x18, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, + 0x6e, 0x65, 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x52, 0x65, 0x73, 0x75, 0x6c, 0x74, 0x52, 0x07, 0x72, + 0x65, 0x73, 0x75, 0x6c, 0x74, 0x73, 0x22, 0xd5, 0x03, 0x0a, 0x06, 0x52, 0x65, 0x73, 0x75, 0x6c, + 0x74, 0x12, 0x16, 0x0a, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x06, 0x74, 0x61, 0x72, 0x67, 0x65, 0x74, 0x12, 0x45, 0x0a, 0x0f, 0x76, 0x75, 0x6c, + 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, 0x18, 0x02, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x1b, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x56, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x79, 0x52, + 0x0f, 0x76, 0x75, 0x6c, 0x6e, 0x65, 0x72, 0x61, 0x62, 0x69, 0x6c, 0x69, 0x74, 0x69, 0x65, 0x73, + 0x12, 0x54, 0x0a, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, + 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x18, 0x04, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x26, 0x2e, 0x74, 0x72, + 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, + 0x74, 0x65, 0x64, 0x4d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 0x74, + 0x69, 0x6f, 0x6e, 0x52, 0x11, 0x6d, 0x69, 0x73, 0x63, 0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, + 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x18, + 0x06, 0x20, 0x01, 0x28, 0x09, 0x52, 0x05, 0x63, 0x6c, 0x61, 0x73, 0x73, 0x12, 0x12, 0x0a, 0x04, + 0x74, 0x79, 0x70, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x04, 0x74, 0x79, 0x70, 0x65, + 0x12, 0x31, 0x0a, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x73, 0x18, 0x05, 0x20, 0x03, + 0x28, 0x0b, 0x32, 0x15, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, + 0x6e, 0x2e, 0x50, 0x61, 0x63, 0x6b, 0x61, 0x67, 0x65, 0x52, 0x08, 0x70, 0x61, 0x63, 0x6b, 0x61, + 0x67, 0x65, 0x73, 0x12, 0x47, 0x0a, 0x10, 0x63, 0x75, 0x73, 0x74, 0x6f, 0x6d, 0x5f, 0x72, 0x65, + 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x07, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1c, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x43, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x52, 0x0f, 0x63, 0x75, 0x73, + 0x74, 0x6f, 0x6d, 0x52, 0x65, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x35, 0x0a, 0x07, + 0x73, 0x65, 0x63, 0x72, 0x65, 0x74, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1b, 0x2e, + 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x53, 0x65, 0x63, + 0x72, 0x65, 0x74, 0x46, 0x69, 0x6e, 0x64, 0x69, 0x6e, 0x67, 0x52, 0x07, 0x73, 0x65, 0x63, 0x72, + 0x65, 0x74, 0x73, 0x12, 0x39, 0x0a, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x18, + 0x09, 0x20, 0x03, 0x28, 0x0b, 0x32, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x63, 0x6f, + 0x6d, 0x6d, 0x6f, 0x6e, 0x2e, 0x44, 0x65, 0x74, 0x65, 0x63, 0x74, 0x65, 0x64, 0x4c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x52, 0x08, 0x6c, 0x69, 0x63, 0x65, 0x6e, 0x73, 0x65, 0x73, 0x32, 0x50, + 0x0a, 0x07, 0x53, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x12, 0x45, 0x0a, 0x04, 0x53, 0x63, 0x61, + 0x6e, 0x12, 0x1d, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, + 0x72, 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x1a, 0x1e, 0x2e, 0x74, 0x72, 0x69, 0x76, 0x79, 0x2e, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, + 0x2e, 0x76, 0x31, 0x2e, 0x53, 0x63, 0x61, 0x6e, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, + 0x42, 0x33, 0x5a, 0x31, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f, 0x6d, 0x2f, 0x61, + 0x71, 0x75, 0x61, 0x73, 0x65, 0x63, 0x75, 0x72, 0x69, 0x74, 0x79, 0x2f, 0x74, 0x72, 0x69, 0x76, + 0x79, 0x2f, 0x72, 0x70, 0x63, 0x2f, 0x73, 0x63, 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x3b, 0x73, 0x63, + 0x61, 0x6e, 0x6e, 0x65, 0x72, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/rpc/scanner/service.proto b/rpc/scanner/service.proto index d809d36610ee..5e43ea997d8b 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -29,6 +29,7 @@ message ScanOptions { bool include_dev_deps = 5; repeated string pkg_relationships = 6; common.OS distro = 7; + bool license_full = 8; reserved 3; // deleted 'list_all_packages' } diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index d7525d23c45e..6c8ab44bd09b 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -1094,48 +1094,49 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 685 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0x51, 0x6f, 0x1a, 0x47, - 0x10, 0x16, 0x1c, 0x86, 0x63, 0xa8, 0x6a, 0xbc, 0x6a, 0xad, 0x35, 0xae, 0x5b, 0xc4, 0x43, 0x85, - 0x54, 0x09, 0x6a, 0xdc, 0xaa, 0x4d, 0xf2, 0x16, 0xdb, 0x89, 0x1c, 0x25, 0xb2, 0xb5, 0x58, 0x79, - 0xc8, 0x0b, 0x59, 0xf6, 0xc6, 0xe7, 0x15, 0xc7, 0xed, 0x79, 0x77, 0x0f, 0x89, 0xff, 0x91, 0xa7, - 0xfc, 0xaf, 0xfc, 0x9f, 0x68, 0xf7, 0x0e, 0x64, 0x30, 0xce, 0x13, 0x3b, 0x33, 0xdf, 0xcc, 0x7c, - 0xcc, 0x7c, 0x37, 0x70, 0xa4, 0x33, 0x31, 0x34, 0x82, 0xa7, 0x29, 0xea, 0xa1, 0x41, 0xbd, 0x90, - 0x02, 0x07, 0x99, 0x56, 0x56, 0x91, 0xb6, 0xd5, 0x72, 0xb1, 0x1c, 0x94, 0xc1, 0xc1, 0xe2, 0xb4, - 0x43, 0x1d, 0x58, 0xa8, 0xf9, 0x5c, 0xa5, 0x9b, 0xd8, 0xde, 0xd7, 0x0a, 0xb4, 0xc6, 0x82, 0xa7, - 0x0c, 0x1f, 0x72, 0x34, 0x96, 0x1c, 0x42, 0xdd, 0x72, 0x1d, 0xa3, 0xa5, 0x95, 0x6e, 0xa5, 0xdf, - 0x64, 0xa5, 0x45, 0xfe, 0x80, 0x16, 0xd7, 0x56, 0xde, 0x71, 0x61, 0x27, 0x32, 0xa2, 0x55, 0x1f, - 0x84, 0x95, 0xeb, 0x2a, 0x22, 0x47, 0x10, 0x4e, 0x13, 0x35, 0x9d, 0xc8, 0xc8, 0xd0, 0xa0, 0x1b, - 0xf4, 0x9b, 0xac, 0xe1, 0xec, 0xab, 0xc8, 0x90, 0xff, 0xa0, 0xa1, 0x32, 0x2b, 0x55, 0x6a, 0x68, - 0xad, 0x5b, 0xe9, 0xb7, 0x46, 0x27, 0x83, 0x6d, 0x86, 0x03, 0xc7, 0xe1, 0xba, 0x00, 0xb1, 0x15, - 0xba, 0xd7, 0x85, 0xf0, 0xbd, 0x14, 0x98, 0x1a, 0x34, 0xe4, 0x17, 0xd8, 0x4b, 0xf9, 0x1c, 0x0d, - 0xad, 0xf8, 0xe2, 0x85, 0xd1, 0xfb, 0x12, 0x14, 0xf4, 0xcb, 0x54, 0x72, 0x0c, 0xcd, 0x6c, 0x16, - 0x4f, 0xec, 0x32, 0x5b, 0x23, 0xc3, 0x6c, 0x16, 0xdf, 0x3a, 0x9b, 0x74, 0x20, 0x2c, 0x3b, 0x1a, - 0x5a, 0x2d, 0x62, 0x2b, 0x9b, 0x08, 0x20, 0x49, 0xd1, 0x6a, 0x22, 0xb8, 0xc5, 0x58, 0x69, 0x89, - 0x8e, 0x6e, 0xd0, 0x6f, 0x8d, 0xfe, 0xf9, 0x21, 0xdd, 0x41, 0x49, 0xf1, 0x7c, 0x9d, 0x76, 0x99, - 0x5a, 0xbd, 0x64, 0x07, 0xc9, 0xb6, 0x9f, 0xf4, 0xa1, 0x2d, 0x53, 0x91, 0xe4, 0x11, 0x4e, 0x22, - 0x5c, 0x4c, 0x22, 0xcc, 0x0c, 0xdd, 0xeb, 0x56, 0xfa, 0x21, 0xfb, 0xb9, 0xf4, 0x5f, 0xe0, 0xe2, - 0x02, 0x33, 0x43, 0xfe, 0x82, 0x03, 0xf7, 0x3f, 0x34, 0x26, 0xdc, 0x37, 0xb9, 0x97, 0x99, 0xa1, - 0x75, 0xcf, 0xb9, 0x9d, 0xcd, 0x62, 0xf6, 0xd8, 0x4f, 0xfa, 0x50, 0x8f, 0xa4, 0xb1, 0x5a, 0xd1, - 0x86, 0x1f, 0x6f, 0xbb, 0xe4, 0x5b, 0x2c, 0x7c, 0x70, 0x3d, 0x66, 0x65, 0xbc, 0xf3, 0x19, 0x0e, - 0x77, 0xb3, 0x25, 0x6d, 0x08, 0x66, 0xb8, 0x2c, 0x97, 0xee, 0x9e, 0xe4, 0x6f, 0xd8, 0x5b, 0xf0, - 0x24, 0x47, 0xbf, 0xeb, 0xd6, 0xa8, 0xf3, 0x74, 0x08, 0xab, 0xdd, 0xb0, 0x02, 0xf8, 0xb2, 0xfa, - 0x7f, 0xe5, 0x5d, 0x2d, 0x0c, 0xda, 0xb5, 0x5e, 0x04, 0x3f, 0x15, 0xa2, 0x32, 0x99, 0x4a, 0x0d, - 0x92, 0x2e, 0x54, 0x95, 0xf1, 0xc5, 0x77, 0xb1, 0xab, 0x2a, 0x43, 0x46, 0xd0, 0xd0, 0x68, 0xf2, - 0xc4, 0x16, 0xea, 0x69, 0x8d, 0xe8, 0xd3, 0x7e, 0xcc, 0x03, 0xd8, 0x0a, 0xd8, 0xfb, 0x16, 0x40, - 0xbd, 0xf0, 0x3d, 0x2b, 0xdb, 0x4b, 0xd8, 0x5f, 0xe4, 0x49, 0x8a, 0x9a, 0x4f, 0x65, 0x22, 0xad, - 0xdb, 0x69, 0xd5, 0x97, 0x3f, 0xde, 0x64, 0xf1, 0xf1, 0x11, 0x68, 0xc9, 0xb6, 0x73, 0xc8, 0x2d, - 0x1c, 0xcc, 0xa5, 0x11, 0x2a, 0xbd, 0x93, 0x71, 0xae, 0xf9, 0x4a, 0xcb, 0xae, 0xd0, 0x9f, 0x9b, - 0x85, 0x2e, 0xd0, 0xa2, 0xb0, 0x18, 0x7d, 0xd8, 0x82, 0xb3, 0xa7, 0x05, 0x9c, 0xa4, 0x45, 0xc2, - 0x8d, 0x5b, 0xac, 0xe3, 0x5c, 0x18, 0x84, 0x40, 0xcd, 0xc9, 0x97, 0x06, 0xde, 0xe9, 0xdf, 0xe4, - 0x14, 0xc2, 0x8c, 0x8b, 0x19, 0x8f, 0xd1, 0x09, 0xc6, 0xb5, 0xfd, 0x75, 0xb3, 0xed, 0x4d, 0x11, - 0x65, 0x6b, 0x18, 0x79, 0x0b, 0x6d, 0x91, 0x1b, 0xab, 0xe6, 0x13, 0x8d, 0x46, 0xe5, 0x5a, 0xa0, - 0xa1, 0x0d, 0x9f, 0xfa, 0xdb, 0x66, 0xea, 0xb9, 0x47, 0xb1, 0x12, 0xc4, 0xf6, 0xc5, 0x86, 0x6d, - 0xc8, 0xbf, 0xd0, 0x30, 0x28, 0x34, 0x5a, 0x43, 0xc3, 0x5d, 0xa3, 0x1b, 0xfb, 0xe0, 0x1b, 0x99, - 0x46, 0x32, 0x8d, 0xd9, 0x0a, 0x4b, 0x5e, 0x40, 0x58, 0x7e, 0x00, 0x86, 0x36, 0x7d, 0xde, 0xc9, - 0xee, 0x49, 0x95, 0x2a, 0x62, 0x6b, 0xf8, 0xe8, 0x06, 0x1a, 0xe3, 0x62, 0xeb, 0xe4, 0x12, 0x6a, - 0xee, 0x49, 0x9e, 0xb9, 0x18, 0xe5, 0xd5, 0xea, 0xfc, 0xfe, 0x5c, 0xb8, 0xd0, 0xdf, 0xeb, 0xb3, - 0x4f, 0xa7, 0xb1, 0xb4, 0xf7, 0xf9, 0xd4, 0x35, 0x1f, 0xf2, 0x87, 0x9c, 0x1b, 0x14, 0xb9, 0x96, - 0x76, 0x39, 0xf4, 0x89, 0xc3, 0x47, 0xc7, 0xf4, 0x55, 0xf9, 0x3b, 0xad, 0xfb, 0x0b, 0x79, 0xf6, - 0x3d, 0x00, 0x00, 0xff, 0xff, 0x40, 0xd0, 0xe7, 0xda, 0x6a, 0x05, 0x00, 0x00, + // 704 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xdb, 0x6e, 0xdb, 0x38, + 0x10, 0x85, 0x2d, 0xc7, 0x96, 0xc7, 0xc1, 0xc6, 0x21, 0x76, 0x03, 0xc6, 0xd9, 0xec, 0x7a, 0xfd, + 0xb0, 0x30, 0xb0, 0x80, 0xbd, 0x71, 0x76, 0xd1, 0xdb, 0x5b, 0x73, 0x29, 0x52, 0xb4, 0x48, 0x40, + 0x07, 0x7d, 0xe8, 0x8b, 0x4b, 0x53, 0x13, 0x85, 0xb0, 0x2c, 0x29, 0x24, 0x65, 0xc0, 0xbf, 0xd2, + 0x1f, 0xe9, 0x97, 0xf4, 0x7f, 0x0a, 0x52, 0x92, 0x11, 0x3b, 0x4e, 0x9f, 0xa4, 0x99, 0x39, 0x33, + 0xe7, 0x90, 0x73, 0x40, 0x38, 0x54, 0xa9, 0x18, 0x6a, 0xc1, 0xe3, 0x18, 0xd5, 0x50, 0xa3, 0x5a, + 0x48, 0x81, 0x83, 0x54, 0x25, 0x26, 0x21, 0x6d, 0xa3, 0xe4, 0x62, 0x39, 0x28, 0x8a, 0x83, 0xc5, + 0x49, 0x87, 0x5a, 0xb0, 0x48, 0xe6, 0xf3, 0x24, 0x5e, 0xc7, 0xf6, 0xbe, 0x56, 0xa0, 0x35, 0x16, + 0x3c, 0x66, 0xf8, 0x90, 0xa1, 0x36, 0xe4, 0x00, 0xea, 0x86, 0xab, 0x10, 0x0d, 0xad, 0x74, 0x2b, + 0xfd, 0x26, 0x2b, 0x22, 0xf2, 0x27, 0xb4, 0xb8, 0x32, 0xf2, 0x8e, 0x0b, 0x33, 0x91, 0x01, 0xad, + 0xba, 0x22, 0x94, 0xa9, 0xab, 0x80, 0x1c, 0x82, 0x3f, 0x8d, 0x92, 0xe9, 0x44, 0x06, 0x9a, 0x7a, + 0x5d, 0xaf, 0xdf, 0x64, 0x0d, 0x1b, 0x5f, 0x05, 0x9a, 0xbc, 0x80, 0x46, 0x92, 0x1a, 0x99, 0xc4, + 0x9a, 0xd6, 0xba, 0x95, 0x7e, 0x6b, 0x74, 0x3c, 0xd8, 0x54, 0x38, 0xb0, 0x1a, 0xae, 0x73, 0x10, + 0x2b, 0xd1, 0xbd, 0x2e, 0xf8, 0x1f, 0xa4, 0xc0, 0x58, 0xa3, 0x26, 0xbf, 0xc2, 0x4e, 0xcc, 0xe7, + 0xa8, 0x69, 0xc5, 0x0d, 0xcf, 0x83, 0xde, 0x37, 0x2f, 0x97, 0x5f, 0xb4, 0x92, 0x23, 0x68, 0xa6, + 0xb3, 0x70, 0x62, 0x96, 0xe9, 0x0a, 0xe9, 0xa7, 0xb3, 0xf0, 0xd6, 0xc6, 0xa4, 0x03, 0x7e, 0xc1, + 0xa8, 0x69, 0x35, 0xaf, 0x95, 0x31, 0x11, 0x40, 0xa2, 0x9c, 0x6a, 0x22, 0xb8, 0xc1, 0x30, 0x51, + 0x12, 0xad, 0x5c, 0xaf, 0xdf, 0x1a, 0xfd, 0xf7, 0x53, 0xb9, 0x83, 0x42, 0xe2, 0xd9, 0xaa, 0xed, + 0x22, 0x36, 0x6a, 0xc9, 0xf6, 0xa3, 0xcd, 0x3c, 0xe9, 0x43, 0x5b, 0xc6, 0x22, 0xca, 0x02, 0x9c, + 0x04, 0xb8, 0x98, 0x04, 0x98, 0x6a, 0xba, 0xd3, 0xad, 0xf4, 0x7d, 0xf6, 0x4b, 0x91, 0x3f, 0xc7, + 0xc5, 0x39, 0xa6, 0x9a, 0xfc, 0x03, 0xfb, 0xf6, 0x1c, 0x0a, 0x23, 0xee, 0x48, 0xee, 0x65, 0xaa, + 0x69, 0xdd, 0x69, 0x6e, 0xa7, 0xb3, 0x90, 0x3d, 0xce, 0x93, 0x3e, 0xd4, 0x03, 0xa9, 0x8d, 0x4a, + 0x68, 0xc3, 0x5d, 0x6f, 0xbb, 0xd0, 0x9b, 0x2f, 0x7c, 0x70, 0x3d, 0x66, 0x45, 0x9d, 0xfc, 0x05, + 0xbb, 0xe5, 0x29, 0xef, 0xb2, 0x28, 0xa2, 0xbe, 0x23, 0x6f, 0x15, 0xb9, 0xcb, 0x2c, 0x8a, 0x3a, + 0x5f, 0xe0, 0x60, 0xfb, 0x81, 0x48, 0x1b, 0xbc, 0x19, 0x2e, 0x0b, 0x5f, 0xd8, 0x5f, 0xf2, 0x2f, + 0xec, 0x2c, 0x78, 0x94, 0xa1, 0xb3, 0x43, 0x6b, 0xd4, 0x79, 0x7a, 0x4f, 0xe5, 0xfa, 0x58, 0x0e, + 0x7c, 0x5d, 0x7d, 0x59, 0x79, 0x5f, 0xf3, 0xbd, 0x76, 0xad, 0x17, 0xc0, 0x6e, 0xee, 0x3b, 0x9d, + 0x26, 0xb1, 0x46, 0xd2, 0x85, 0x6a, 0xa2, 0xdd, 0xf0, 0x6d, 0x07, 0xa8, 0x26, 0x9a, 0x8c, 0xa0, + 0xa1, 0x50, 0x67, 0x91, 0xc9, 0x0d, 0xd6, 0x1a, 0xd1, 0xa7, 0x7c, 0xcc, 0x01, 0x58, 0x09, 0xec, + 0x7d, 0xf7, 0xa0, 0x9e, 0xe7, 0x9e, 0x75, 0xf6, 0x05, 0xec, 0x2d, 0xb2, 0x28, 0x46, 0xc5, 0xa7, + 0x32, 0x92, 0xc6, 0xae, 0xbd, 0xea, 0xc6, 0x1f, 0xad, 0xab, 0xf8, 0xf4, 0x08, 0xb4, 0x64, 0x9b, + 0x3d, 0xe4, 0x16, 0xf6, 0xe7, 0x52, 0x8b, 0x24, 0xbe, 0x93, 0x61, 0xa6, 0x78, 0x69, 0x77, 0x3b, + 0xe8, 0xef, 0xf5, 0x41, 0xe7, 0x68, 0x50, 0x18, 0x0c, 0x3e, 0x6e, 0xc0, 0xd9, 0xd3, 0x01, 0xd6, + 0xf5, 0x22, 0xe2, 0xda, 0xee, 0xde, 0x6a, 0xce, 0x03, 0x42, 0xa0, 0x66, 0x1d, 0x4e, 0x3d, 0x97, + 0x74, 0xff, 0xe4, 0x04, 0xfc, 0x94, 0x8b, 0x19, 0x0f, 0xd1, 0x7a, 0xca, 0xd2, 0xfe, 0xb6, 0x4e, + 0x7b, 0x93, 0x57, 0xd9, 0x0a, 0x46, 0xde, 0x41, 0x5b, 0x64, 0xda, 0x24, 0xf3, 0x89, 0x42, 0x9d, + 0x64, 0x4a, 0xa0, 0xa6, 0x0d, 0xd7, 0xfa, 0xfb, 0x7a, 0xeb, 0x99, 0x43, 0xb1, 0x02, 0xc4, 0xf6, + 0xc4, 0x5a, 0xac, 0xc9, 0xff, 0xd0, 0xd0, 0x28, 0x14, 0x1a, 0x4d, 0xfd, 0x6d, 0x57, 0x37, 0x76, + 0xc5, 0x4b, 0x19, 0x07, 0x32, 0x0e, 0x59, 0x89, 0x25, 0xaf, 0xc0, 0x2f, 0x9c, 0xa7, 0x69, 0xd3, + 0xf5, 0x1d, 0x6f, 0xbf, 0xa9, 0xc2, 0x45, 0x6c, 0x05, 0x1f, 0xdd, 0x40, 0x63, 0x9c, 0x6f, 0x9d, + 0x5c, 0x40, 0xcd, 0xfe, 0x92, 0x67, 0x1e, 0x95, 0xe2, 0x61, 0xeb, 0xfc, 0xf1, 0x5c, 0x39, 0xf7, + 0xdf, 0xdb, 0xd3, 0xcf, 0x27, 0xa1, 0x34, 0xf7, 0xd9, 0xd4, 0x92, 0x0f, 0xf9, 0x43, 0xc6, 0x35, + 0x8a, 0x4c, 0x49, 0xb3, 0x1c, 0xba, 0xc6, 0xe1, 0xa3, 0xf7, 0xf6, 0x4d, 0xf1, 0x9d, 0xd6, 0xdd, + 0x23, 0x7a, 0xfa, 0x23, 0x00, 0x00, 0xff, 0xff, 0x41, 0x7e, 0x12, 0x20, 0x8d, 0x05, 0x00, 0x00, } From a57dcc69729674ca2c56558ff2182368b17f1661 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 13:35:26 +0600 Subject: [PATCH 17/45] fix: add LicenseFiles into summary table --- pkg/report/table/summary.go | 2 +- pkg/report/table/table.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index e9bffc242050..1cc617c6e563 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -79,7 +79,7 @@ func (s LicenseScanner) Header() string { } func (s LicenseScanner) Count(result types.Result) int { - if result.Class == types.ClassLicense { + if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile { return len(result.Licenses) } return -1 diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index d9caca9c86aa..b5d2bc74061b 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -9,8 +9,6 @@ import ( "slices" "strings" - "github.com/aquasecurity/trivy/pkg/log" - xstrings "github.com/aquasecurity/trivy/pkg/x/strings" "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" @@ -18,7 +16,9 @@ import ( "github.com/aquasecurity/table" "github.com/aquasecurity/tml" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) var ( From 4f349c5246f5be441af832444a9e9a6316ac7cd0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 13:49:17 +0600 Subject: [PATCH 18/45] test: add LicenseFiles in test --- pkg/report/table/table_private_test.go | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 30a5842f8f2e..8307e69dfac6 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -91,6 +91,11 @@ var ( fileLicense = types.Result{ Target: "Loose File License(s)", Class: types.ClassLicenseFile, + Licenses: []types.DetectedLicense{ + { + FilePath: "LICENSE", + }, + }, } ) @@ -142,7 +147,7 @@ Legend: ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ Java │ - │ - │ - │ - │ 0 │ ├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ Loose File License(s) │ - │ - │ - │ - │ - │ +│ Loose File License(s) │ - │ - │ - │ - │ 1 │ └───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ `, }, From 84169b4fc9922ebb285938dc0403b16ef5680764 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 16:11:10 +0600 Subject: [PATCH 19/45] feat: split aggregated pkgs --- pkg/report/table/table.go | 68 ++++++++++++++- pkg/report/table/table_private_test.go | 110 ++++++++++++++++++------- pkg/report/table/table_test.go | 6 ++ pkg/report/table/vulnerability.go | 4 +- 4 files changed, 157 insertions(+), 31 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index b5d2bc74061b..e276a26434b5 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -7,8 +7,10 @@ import ( "os" "runtime" "slices" + "sort" "strings" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" @@ -16,6 +18,7 @@ import ( "github.com/aquasecurity/table" "github.com/aquasecurity/tml" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" @@ -129,7 +132,7 @@ func (tw Writer) renderSummary(report types.Report) error { t.SetHeaders(headers...) t.SetAlignment(alignments...) - for _, result := range report.Results { + for _, result := range splitAggregatedPackages(report.Results) { resultType := string(result.Type) if result.Class == types.ClassSecret { resultType = "text" @@ -179,6 +182,69 @@ func (tw Writer) showEmptyResultsWarning() { log.WithPrefix("report").Warn(strings.Join(warnStrings, " ")) } +// splitAggregatedPackages splits aggregated packages into different results with path as target. +// Other results will be returned as is. +func splitAggregatedPackages(results types.Results) types.Results { + var newResults types.Results + + for _, result := range results { + if !slices.Contains(ftypes.AggregatingTypes, result.Type) && + // License results from applications don't have `Type`. + (result.Class != types.ClassLicense || !slices.Contains(lo.Values(langpkg.PkgTargets), result.Target)) { + newResults = append(newResults, result) + continue + } + + newResults = append(newResults, splitAggregatedVulns(result)...) + newResults = append(newResults, splitAggregatedLicenses(result)...) + + } + return newResults +} + +func splitAggregatedVulns(result types.Result) types.Results { + var newResults types.Results + + vulns := make(map[string][]types.DetectedVulnerability) + for _, vuln := range result.Vulnerabilities { + pkgPath := rootJarFromPath(vuln.PkgPath) + vulns[pkgPath] = append(vulns[pkgPath], vuln) + } + for pkgPath, v := range vulns { + newResult := result + newResult.Target = lo.Ternary(pkgPath != "", pkgPath, result.Target) + newResult.Vulnerabilities = v + + newResults = append(newResults, newResult) + } + + sort.Slice(newResults, func(i, j int) bool { + return newResults[i].Target < newResults[j].Target + }) + return newResults +} + +func splitAggregatedLicenses(result types.Result) types.Results { + var newResults types.Results + + licenses := make(map[string][]types.DetectedLicense) + for _, license := range result.Licenses { + licenses[license.FilePath] = append(licenses[license.FilePath], license) + } + for filePath, l := range licenses { + newResult := result + newResult.Target = lo.Ternary(filePath != "", filePath, result.Target) + newResult.Licenses = l + + newResults = append(newResults, newResult) + } + + sort.Slice(newResults, func(i, j int) bool { + return newResults[i].Target < newResults[j].Target + }) + return newResults +} + func (tw Writer) write(result types.Result) { if result.IsEmpty() && result.Class != types.ClassOSPkg { return diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 8307e69dfac6..f6c54818d2e2 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -28,7 +28,7 @@ var ( }, }, } - jarVuln = types.Result{ + jarVulns = types.Result{ Target: "Java", Class: types.ClassLangPkg, Type: ftypes.Jar, @@ -41,7 +41,35 @@ var ( { VulnerabilityID: "CVE-2021-44832", PkgName: "org.apache.logging.log4j:log4j-core", - PkgPath: "app/log4j-core-2.17.0.jar", + PkgPath: "app/jackson-databind-2.13.4.1.jar/nested/app2/log4j-core-2.17.0.jar", + }, + }, + } + + npmVulns = types.Result{ + Target: "Node.js", + Class: types.ClassLangPkg, + Type: ftypes.NodePkg, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2022-37601", + PkgName: "loader-utils", + PkgPath: "loader-utils/package.json", + }, + { + VulnerabilityID: "CVE-2022-37599", + PkgName: "loader-utils", + PkgPath: "loader-utils/package.json", + }, + { + VulnerabilityID: "CVE-2021-23566", + PkgName: "nanoid", + PkgPath: "nanoid/package.json", + }, + { + VulnerabilityID: "CVE-2024-55565", + PkgName: "nanoid", + PkgPath: "nanoid/package.json", }, }, } @@ -88,6 +116,24 @@ var ( Target: "Java", Class: types.ClassLicense, } + + npmLicenses = types.Result{ + Target: "Node.js", + Class: types.ClassLicense, + Licenses: []types.DetectedLicense{ + { + Name: "MIT", + FilePath: "loader-utils/package.json", + Category: "notice", + }, + { + Name: "MIT", + FilePath: "nanoid/package.json", + Category: "notice", + }, + }, + } + fileLicense = types.Result{ Target: "Loose File License(s)", Class: types.ClassLicenseFile, @@ -118,11 +164,13 @@ func Test_renderSummary(t *testing.T) { report: types.Report{ Results: []types.Result{ osVuln, - jarVuln, + jarVulns, + npmVulns, dockerfileMisconfig, secret, osLicense, jarLicense, + npmLicenses, fileLicense, }, }, @@ -132,23 +180,29 @@ Report Summary Legend: - '-': Not scanned - '0': Clean (no security findings detected) -┌───────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ -│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ Java │ jar │ 2 │ - │ - │ - │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ requirements.txt │ text │ - │ - │ 1 │ - │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ OS Packages │ - │ - │ - │ - │ 1 │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ Java │ - │ - │ - │ - │ 0 │ -├───────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ -│ Loose File License(s) │ - │ - │ - │ - │ 1 │ -└───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ +┌───────────────────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ +│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │ - │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ loader-utils/package.json │ node-pkg │ 2 │ - │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ nanoid/package.json │ node-pkg │ 2 │ - │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ app/Dockerfile │ dockerfile │ - │ 2 │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ requirements.txt │ text │ - │ - │ 1 │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ OS Packages │ - │ - │ - │ - │ 1 │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ loader-utils/package.json │ - │ - │ - │ - │ 1 │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ nanoid/package.json │ - │ - │ - │ - │ 1 │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ Loose File License(s) │ - │ - │ - │ - │ 1 │ +└───────────────────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ `, }, { @@ -159,7 +213,7 @@ Legend: report: types.Report{ Results: []types.Result{ osVuln, - jarVuln, + jarVulns, }, }, want: ` @@ -168,13 +222,13 @@ Report Summary Legend: - '-': Not scanned - '0': Clean (no security findings detected) -┌──────────────────────┬────────┬─────────────────┐ -│ Target │ Type │ Vulnerabilities │ -├──────────────────────┼────────┼─────────────────┤ -│ test (alpine 3.20.3) │ alpine │ 2 │ -├──────────────────────┼────────┼─────────────────┤ -│ Java │ jar │ 2 │ -└──────────────────────┴────────┴─────────────────┘ +┌───────────────────────────────────┬────────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├───────────────────────────────────┼────────┼─────────────────┤ +│ test (alpine 3.20.3) │ alpine │ 2 │ +├───────────────────────────────────┼────────┼─────────────────┤ +│ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │ +└───────────────────────────────────┴────────┴─────────────────┘ `, }, { diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index d88e8ee355c0..911b27f01f10 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -58,6 +58,9 @@ func TestWriter_Write(t *testing.T) { wantOutput: ` Report Summary +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) ┌────────┬──────┬─────────────────┐ │ Target │ Type │ Vulnerabilities │ ├────────┼──────┼─────────────────┤ @@ -91,6 +94,9 @@ Total: 1 (MEDIUM: 0, HIGH: 1) wantOutput: ` Report Summary +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) ┌────────┬──────┬─────────────────┐ │ Target │ Type │ Vulnerabilities │ ├────────┼──────┼─────────────────┤ diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 1c08236a033c..786b719ff09a 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -139,8 +139,6 @@ func (r *vulnerabilityRenderer) setVulnerabilityRows(tw *table.Table, vulns []ty for _, v := range vulns { lib := v.PkgName if v.PkgPath != "" { - // get path to root jar - // for other languages return unchanged path pkgPath := rootJarFromPath(v.PkgPath) fileName := filepath.Base(pkgPath) lib = fmt.Sprintf("%s (%s)", v.PkgName, fileName) @@ -373,6 +371,8 @@ var jarExtensions = []string{ ".ear", } +// rootJarFromPath returns path to root jar. +// For other languages return unchanged path func rootJarFromPath(path string) string { // File paths are always forward-slashed in Trivy paths := strings.Split(path, "/") From 892d6de100872ccb3e9f2019366a4555c86176c9 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 16:53:28 +0600 Subject: [PATCH 20/45] fix: show packages without vulns --- pkg/report/table/table.go | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index e276a26434b5..7c684f3a7b7c 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -10,7 +10,6 @@ import ( "sort" "strings" - "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/fatih/color" "github.com/samber/lo" "golang.org/x/xerrors" @@ -20,6 +19,7 @@ import ( dbTypes "github.com/aquasecurity/trivy-db/pkg/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/types" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) @@ -205,7 +205,11 @@ func splitAggregatedPackages(results types.Results) types.Results { func splitAggregatedVulns(result types.Result) types.Results { var newResults types.Results - vulns := make(map[string][]types.DetectedVulnerability) + // Save packages to display them in the table even if no vulnerabilities were found + vulns := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, []types.DetectedVulnerability) { + return rootJarFromPath(pkg.FilePath), []types.DetectedVulnerability{} + }) + for _, vuln := range result.Vulnerabilities { pkgPath := rootJarFromPath(vuln.PkgPath) vulns[pkgPath] = append(vulns[pkgPath], vuln) From db0cbe1e0a34f622db9ddfb35943f1f60043fcd1 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 16:53:33 +0600 Subject: [PATCH 21/45] fix: tests --- .../testdata/license-cyclonedx.json.golden | 11 ++--- pkg/report/table/table_private_test.go | 18 +++++++ pkg/report/table/table_test.go | 47 ++++++++++++------- 3 files changed, 52 insertions(+), 24 deletions(-) diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index 95f8acd896b1..6371e7961789 100644 --- a/integration/testdata/license-cyclonedx.json.golden +++ b/integration/testdata/license-cyclonedx.json.golden @@ -16,10 +16,6 @@ } }, "Results": [ - { - "Target": "OS Packages", - "Class": "license" - }, { "Target": "pom.xml", "Class": "license" @@ -34,6 +30,7 @@ "PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus", "FilePath": "", "Name": "EPL-1.0", + "Text": "", "Confidence": 1, "Link": "" }, @@ -43,6 +40,7 @@ "PkgName": "org.ow2.asm:asm", "FilePath": "", "Name": "BSD-3-Clause", + "Text": "", "Confidence": 1, "Link": "" }, @@ -52,14 +50,11 @@ "PkgName": "org.slf4j:slf4j-api", "FilePath": "", "Name": "MIT License", + "Text": "", "Confidence": 1, "Link": "" } ] - }, - { - "Target": "Loose File License(s)", - "Class": "license-file" } ] } diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index f6c54818d2e2..5e200ef14e3d 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -32,6 +32,20 @@ var ( Target: "Java", Class: types.ClassLangPkg, Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "com.fasterxml.jackson.core:jackson-databind", + FilePath: "app/jackson-databind-2.13.4.1.jar", + }, + { + Name: "com.google.code.gson:gson", + FilePath: "app/gson-2.11.0.jar", + }, + { + Name: "org.apache.logging.log4j:log4j-core", + FilePath: "app/jackson-databind-2.13.4.1.jar/nested/app2/log4j-core-2.17.0.jar", + }, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2022-42003", @@ -185,6 +199,8 @@ Legend: ├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ test (alpine 3.20.3) │ alpine │ 2 │ - │ - │ - │ ├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ app/gson-2.11.0.jar │ jar │ 0 │ - │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │ - │ - │ - │ ├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ loader-utils/package.json │ node-pkg │ 2 │ - │ - │ - │ @@ -227,6 +243,8 @@ Legend: ├───────────────────────────────────┼────────┼─────────────────┤ │ test (alpine 3.20.3) │ alpine │ 2 │ ├───────────────────────────────────┼────────┼─────────────────┤ +│ app/gson-2.11.0.jar │ jar │ 0 │ +├───────────────────────────────────┼────────┼─────────────────┤ │ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │ └───────────────────────────────────┴────────┴─────────────────┘ `, diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 911b27f01f10..bcf14a867269 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -33,6 +33,13 @@ func TestWriter_Write(t *testing.T) { Target: "test", Type: ftypes.Jar, Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2020-0001", @@ -40,6 +47,7 @@ func TestWriter_Write(t *testing.T) { InstalledVersion: "1.2.3", PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", Status: dbTypes.StatusWillNotFix, + PkgPath: "test.jar", Vulnerability: dbTypes.Vulnerability{ Title: "foobar", Description: "baz", @@ -61,22 +69,22 @@ Report Summary Legend: - '-': Not scanned - '0': Clean (no security findings detected) -┌────────┬──────┬─────────────────┐ -│ Target │ Type │ Vulnerabilities │ -├────────┼──────┼─────────────────┤ -│ test │ jar │ 1 │ -└────────┴──────┴─────────────────┘ +┌──────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────┼──────┼─────────────────┤ +│ test.jar │ jar │ 1 │ +└──────────┴──────┴─────────────────┘ test (jar) ========== Total: 1 (MEDIUM: 0, HIGH: 1) -┌─────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┐ -│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ -├─────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ -│ foo │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ foobar │ -│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │ -└─────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ +┌────────────────┬───────────────┬──────────┬──────────────┬───────────────────┬───────────────┬───────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├────────────────┼───────────────┼──────────┼──────────────┼───────────────────┼───────────────┼───────────────────────────────────────────┤ +│ foo (test.jar) │ CVE-2020-0001 │ HIGH │ will_not_fix │ 1.2.3 │ │ foobar │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2020-0001 │ +└────────────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ `, }, { @@ -89,6 +97,13 @@ Total: 1 (MEDIUM: 0, HIGH: 1) Target: "test", Class: types.ClassLangPkg, Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, }, }, wantOutput: ` @@ -97,11 +112,11 @@ Report Summary Legend: - '-': Not scanned - '0': Clean (no security findings detected) -┌────────┬──────┬─────────────────┐ -│ Target │ Type │ Vulnerabilities │ -├────────┼──────┼─────────────────┤ -│ test │ jar │ 0 │ -└────────┴──────┴─────────────────┘ +┌──────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────┼──────┼─────────────────┤ +│ test.jar │ jar │ 0 │ +└──────────┴──────┴─────────────────┘ `, }, { From 28ebb248359928ee4e1dd65409ec0fb17fecc4fc Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 16:57:28 +0600 Subject: [PATCH 22/45] refactor: don't show empty vuln table title for OS pkgs --- pkg/report/table/vulnerability.go | 5 ++--- pkg/report/table/vulnerability_test.go | 7 +------ 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index 786b719ff09a..cad983a2ad33 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -72,11 +72,10 @@ func NewVulnerabilityRenderer(result types.Result, isTerminal, tree, suppressed } func (r *vulnerabilityRenderer) Render() string { - // There are 3 cases when we show the vulnerability table (or only target and `Total: 0...`): + // There are 2 cases when we show the vulnerability table (or only target and `Total: 0...`): // When Result contains vulnerabilities; - // When Result target is OS packages even if no vulnerabilities are found; // When we show non-empty `Suppressed Vulnerabilities` table. - if len(r.result.Vulnerabilities) > 0 || r.result.Class == types.ClassOSPkg || (r.showSuppressed && len(r.result.ModifiedFindings) > 0) { + if len(r.result.Vulnerabilities) > 0 || (r.showSuppressed && len(r.result.ModifiedFindings) > 0) { r.renderDetectedVulnerabilities() if r.tree { diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go index 9a773c90eafe..b75d05d07c23 100644 --- a/pkg/report/table/vulnerability_test.go +++ b/pkg/report/table/vulnerability_test.go @@ -425,12 +425,7 @@ Suppressed Vulnerabilities (Total: 1) }, }, showSuppressed: false, - want: ` -test -==== -Total: 0 (MEDIUM: 0, HIGH: 0) - -`, + want: "s", }, { name: "suppressed all language package vulnerabilities without `showSuppressed` flag", From 0166a455e483fc26658f47e250c3e970a16bd9d7 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 16:59:24 +0600 Subject: [PATCH 23/45] refactor: use info log when results didn't find --- pkg/report/table/table.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 7c684f3a7b7c..afb65f9ab3df 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -179,7 +179,7 @@ func (tw Writer) showEmptyResultsWarning() { warnStrings = append(warnStrings, "Scanners are not enabled.") } - log.WithPrefix("report").Warn(strings.Join(warnStrings, " ")) + log.WithPrefix("report").Info(strings.Join(warnStrings, " ")) } // splitAggregatedPackages splits aggregated packages into different results with path as target. From 5f13964afc7e20cfc2f0a065bcb4e06fe9bd1380 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 17:20:05 +0600 Subject: [PATCH 24/45] fix: typo --- pkg/report/table/vulnerability_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go index b75d05d07c23..bc1c9ab5dda0 100644 --- a/pkg/report/table/vulnerability_test.go +++ b/pkg/report/table/vulnerability_test.go @@ -425,7 +425,7 @@ Suppressed Vulnerabilities (Total: 1) }, }, showSuppressed: false, - want: "s", + want: "", }, { name: "suppressed all language package vulnerabilities without `showSuppressed` flag", From 7f863b96ec4101f3bba1a6ebd4a0cf8aa07e1a4b Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 29 Jan 2025 17:50:33 +0600 Subject: [PATCH 25/45] refactor: show legend after table --- pkg/report/table/table.go | 12 ++++++++---- pkg/report/table/table_private_test.go | 21 ++++++++++++--------- pkg/report/table/table_test.go | 14 ++++++++------ 3 files changed, 28 insertions(+), 19 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index afb65f9ab3df..186aaaa9669e 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -91,10 +91,7 @@ func (tw Writer) renderSummary(report types.Report) error { } // Fprintln has a bug - if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"+ - "Legend:\n"+ - "- '-': Not scanned\n"+ - "- '0': Clean (no security findings detected)\n"); err != nil { + if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"); err != nil { return err } @@ -149,6 +146,13 @@ func (tw Writer) renderSummary(report types.Report) error { t.AddRows(rows) } t.Render() + + // Show legend + if err := tml.Fprintf(tw.Output, "Legend:\n"+ + "- '-': Not scanned\n"+ + "- '0': Clean (no security findings detected)\n\n"); err != nil { + return err + } return nil } diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 5e200ef14e3d..8a269d71fc62 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -191,9 +191,6 @@ func Test_renderSummary(t *testing.T) { want: ` Report Summary -Legend: -- '-': Not scanned -- '0': Clean (no security findings detected) ┌───────────────────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ │ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │ ├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ @@ -219,6 +216,10 @@ Legend: ├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ │ Loose File License(s) │ - │ - │ - │ - │ 1 │ └───────────────────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + `, }, { @@ -235,9 +236,6 @@ Legend: want: ` Report Summary -Legend: -- '-': Not scanned -- '0': Clean (no security findings detected) ┌───────────────────────────────────┬────────┬─────────────────┐ │ Target │ Type │ Vulnerabilities │ ├───────────────────────────────────┼────────┼─────────────────┤ @@ -247,6 +245,10 @@ Legend: ├───────────────────────────────────┼────────┼─────────────────┤ │ app/jackson-databind-2.13.4.1.jar │ jar │ 2 │ └───────────────────────────────────┴────────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + `, }, { @@ -264,9 +266,6 @@ Legend: want: ` Report Summary -Legend: -- '-': Not scanned -- '0': Clean (no security findings detected) ┌──────────────────┬──────┬─────────────────┬─────────┐ │ Target │ Type │ Vulnerabilities │ Secrets │ ├──────────────────┼──────┼─────────────────┼─────────┤ @@ -274,6 +273,10 @@ Legend: ├──────────────────┼──────┼─────────────────┼─────────┤ │ requirements.txt │ text │ - │ 1 │ └──────────────────┴──────┴─────────────────┴─────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + `, }, } diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index bcf14a867269..f8ca0a02cfb6 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -66,14 +66,15 @@ func TestWriter_Write(t *testing.T) { wantOutput: ` Report Summary -Legend: -- '-': Not scanned -- '0': Clean (no security findings detected) ┌──────────┬──────┬─────────────────┐ │ Target │ Type │ Vulnerabilities │ ├──────────┼──────┼─────────────────┤ │ test.jar │ jar │ 1 │ └──────────┴──────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + test (jar) ========== @@ -109,14 +110,15 @@ Total: 1 (MEDIUM: 0, HIGH: 1) wantOutput: ` Report Summary -Legend: -- '-': Not scanned -- '0': Clean (no security findings detected) ┌──────────┬──────┬─────────────────┐ │ Target │ Type │ Vulnerabilities │ ├──────────┼──────┼─────────────────┤ │ test.jar │ jar │ 0 │ └──────────┴──────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + `, }, { From 2628b70a9b8cf652bc27b0e653251719122a00ce Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:17:24 +0600 Subject: [PATCH 26/45] docs: fix typo Co-authored-by: Teppei Fukuda --- docs/docs/configuration/reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 389731c6f107..f1e2373d668f 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -86,7 +86,7 @@ Before result tables Trivy shows summary table. This table: -- include columns for enabled [scanners](../references/terminology.md#scanner) only. +- Includes 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. From c78bfe5d138f7235f7969bec2f8e79ae25282343 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 4 Feb 2025 16:17:58 +0600 Subject: [PATCH 27/45] fix: nested list Co-authored-by: Teppei Fukuda --- docs/docs/configuration/reporting.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index f1e2373d668f..d0573dc271c0 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -88,8 +88,8 @@ This table: - Includes 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. + - `-` means that the scanner didn't scan this target. + - `0` means that the scanner scanned this target, but found no security issues. !!! note Use `--no-summary-table` flag to hide summary table. From ba8f75697d971da648ace136657f323160d9fcce Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 4 Feb 2025 16:45:33 +0600 Subject: [PATCH 28/45] refactor: stop iter vulns --- pkg/report/table/table.go | 25 ++++++++++--------------- pkg/report/table/table_private_test.go | 10 ++++++++++ 2 files changed, 20 insertions(+), 15 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 186aaaa9669e..3cb7e3a08aee 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -207,29 +207,24 @@ func splitAggregatedPackages(results types.Results) types.Results { } func splitAggregatedVulns(result types.Result) types.Results { - var newResults types.Results - // Save packages to display them in the table even if no vulnerabilities were found - vulns := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, []types.DetectedVulnerability) { - return rootJarFromPath(pkg.FilePath), []types.DetectedVulnerability{} + resultMap := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, *types.Result) { + filePath := rootJarFromPath(pkg.FilePath) + return filePath, &types.Result{ + Target: lo.Ternary(filePath != "", filePath, result.Target), + Class: result.Class, + Type: result.Type, + } }) - for _, vuln := range result.Vulnerabilities { pkgPath := rootJarFromPath(vuln.PkgPath) - vulns[pkgPath] = append(vulns[pkgPath], vuln) - } - for pkgPath, v := range vulns { - newResult := result - newResult.Target = lo.Ternary(pkgPath != "", pkgPath, result.Target) - newResult.Vulnerabilities = v - - newResults = append(newResults, newResult) + resultMap[pkgPath].Vulnerabilities = append(resultMap[pkgPath].Vulnerabilities, vuln) } - + newResults := lo.Values(resultMap) sort.Slice(newResults, func(i, j int) bool { return newResults[i].Target < newResults[j].Target }) - return newResults + return lo.FromSlicePtr(newResults) } func splitAggregatedLicenses(result types.Result) types.Results { diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 8a269d71fc62..5fcc0f8899b9 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -64,6 +64,16 @@ var ( Target: "Node.js", Class: types.ClassLangPkg, Type: ftypes.NodePkg, + Packages: []ftypes.Package{ + { + Name: "loader-utils@2.0.2", + FilePath: "loader-utils/package.json", + }, + { + Name: "nanoid@3.1.25", + FilePath: "nanoid/package.json", + }, + }, Vulnerabilities: []types.DetectedVulnerability{ { VulnerabilityID: "CVE-2022-37601", From d37ad35f806eacc63d1a6f366207c68b770112f9 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 4 Feb 2025 17:13:17 +0600 Subject: [PATCH 29/45] refactor: split license functions --- pkg/scanner/local/scan.go | 100 ++++++++++++++++++++++---------------- 1 file changed, 58 insertions(+), 42 deletions(-) diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 55178e30d032..aba7a77e8226 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -261,26 +261,44 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions var results types.Results scanner := licensing.NewScanner(options.LicenseCategories) - // License - OS packages - if len(target.Packages) > 0 { - var osPkgLicenses []types.DetectedLicense - for _, pkg := range target.Packages { - for _, license := range pkg.Licenses { - osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, "")) - } + // Scan licenses for OS packages + if result := s.scanOSPackageLicenses(target.Packages, scanner); result != nil { + results = append(results, *result) + } + + // Scan licenses for language-specific packages + results = append(results, s.scanApplicationLicenses(target.Applications, scanner)...) + + // Scan licenses in file headers or license files + if result := s.scanFileLicenses(target.Licenses, scanner, options); result != nil { + results = append(results, *result) + } + + return results +} + +func (s Scanner) scanOSPackageLicenses(packages []ftypes.Package, scanner licensing.Scanner) *types.Result { + if len(packages) == 0 { + return nil + } + + var licenses []types.DetectedLicense + for _, pkg := range packages { + for _, license := range pkg.Licenses { + licenses = append(licenses, toDetectedLicense(scanner, license, pkg.Name, "")) } - // We only need to add result with OS package licenses if Packages were found. - // This is to avoid user confusion. - // e.g. when we didn't find packages but show that we didn't find licenses in the Packages. - results = append(results, types.Result{ - Target: "OS Packages", - Class: types.ClassLicense, - Licenses: osPkgLicenses, - }) } + return &types.Result{ + Target: "OS Packages", + Class: types.ClassLicense, + Licenses: licenses, + } +} + +func (s Scanner) scanApplicationLicenses(apps []ftypes.Application, scanner licensing.Scanner) []types.Result { + var results []types.Result - // License - language-specific packages - for _, app := range target.Applications { + for _, app := range apps { var langLicenses []types.DetectedLicense for _, lib := range app.Packages { for _, license := range lib.Licenses { @@ -304,35 +322,33 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions }) } - // License - file header or license file - if options.LicenseFull { - var fileLicenses []types.DetectedLicense - for _, license := range target.Licenses { - for _, finding := range license.Findings { - category, severity := scanner.Scan(finding.Name) - fileLicenses = append(fileLicenses, types.DetectedLicense{ - Severity: severity, - Category: category, - FilePath: license.FilePath, - Name: finding.Name, - Confidence: finding.Confidence, - Link: finding.Link, - }) + return results +} - } +func (s Scanner) scanFileLicenses(licenses []ftypes.LicenseFile, scanner licensing.Scanner, options types.ScanOptions) *types.Result { + if !options.LicenseFull { + return nil + } + var detectedLicenses []types.DetectedLicense + for _, license := range licenses { + for _, finding := range license.Findings { + category, severity := scanner.Scan(finding.Name) + detectedLicenses = append(detectedLicenses, types.DetectedLicense{ + Severity: severity, + Category: category, + FilePath: license.FilePath, + Name: finding.Name, + Confidence: finding.Confidence, + Link: finding.Link, + }) } - - // We only need to add the result with license files if the `--license-full` flag is enabled. - // This is to avoid user confusion. - // e.g. the user might think that we were looking for licenses but didn't find them. - results = append(results, types.Result{ - Target: "Loose File License(s)", - Class: types.ClassLicenseFile, - Licenses: fileLicenses, - }) } - return results + return &types.Result{ + Target: "Loose File License(s)", + Class: types.ClassLicenseFile, + Licenses: detectedLicenses, + } } func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbTypes.Severity, From 786e704264f691391a0130536ce4256adacd583e Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 4 Feb 2025 17:20:56 +0600 Subject: [PATCH 30/45] refactor: use error for no-summary + no-table format --- pkg/flag/report_flags.go | 3 +-- pkg/flag/report_flags_test.go | 29 ++++++++++++++--------------- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index ee7db0198c77..f534c82a6a3a 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -239,8 +239,7 @@ 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".`) + return ReportOptions{}, xerrors.New(`"--no-summary-table" can be used only with "--format table".`) } cs, err := loadComplianceTypes(f.Compliance.Value()) diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index 4cd99fe8aef7..c3a31dd27dae 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -38,6 +38,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { name string fields fields want flag.ReportOptions + wantErr string wantLogs []string }{ { @@ -116,20 +117,6 @@ 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{ @@ -175,6 +162,14 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { Severities: []dbTypes.Severity{dbTypes.SeverityLow}, }, }, + { + name: "invalid option combination: --no-summary-table with --format json", + fields: fields{ + format: "json", + noSummaryTable: true, + }, + wantErr: `"--no-summary-table" can be used only with "--format table".`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -219,7 +214,11 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { } got, err := f.ToOptions() - require.NoError(t, err) + if tt.wantErr != "" { + require.Contains(t, err.Error(), tt.wantErr) + return + } + assert.EqualExportedValuesf(t, tt.want, got, "ToOptions()") // Assert log messages From 9c3e8ce12ea4f663a4f47575a8bf34657543b367 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 4 Feb 2025 17:35:38 +0600 Subject: [PATCH 31/45] refactor: add empty table with `-` --- pkg/report/table/table.go | 11 ++++--- pkg/report/table/table_private_test.go | 42 ++++++++++++++++++++++++++ pkg/report/table/table_test.go | 7 +++++ 3 files changed, 55 insertions(+), 5 deletions(-) diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 3cb7e3a08aee..44b9b58a8055 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -85,11 +85,6 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { } func (tw Writer) renderSummary(report types.Report) error { - if len(report.Results) == 0 { - tw.showEmptyResultsWarning() - return nil - } - // Fprintln has a bug if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"); err != nil { return err @@ -145,6 +140,12 @@ func (tw Writer) renderSummary(report types.Report) error { } t.AddRows(rows) } + + if len(report.Results) == 0 { + t.AddRows(slices.Repeat([]string{"-"}, len(scanners)+2)) + tw.showEmptyResultsWarning() + } + t.Render() // Show legend diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/table_private_test.go index 5fcc0f8899b9..59f91181f924 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/table_private_test.go @@ -287,6 +287,48 @@ Legend: - '-': Not scanned - '0': Clean (no security findings detected) +`, + }, + { + name: "happy path without supported files for vulns + secret", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + types.SecretScanner, + }, + want: ` +Report Summary + +┌────────┬──────┬─────────────────┬─────────┐ +│ Target │ Type │ Vulnerabilities │ Secrets │ +├────────┼──────┼─────────────────┼─────────┤ +│ - │ - │ - │ - │ +└────────┴──────┴─────────────────┴─────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + +`, + }, + { + name: "happy path without supported files for all scanners", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + types.SecretScanner, + types.MisconfigScanner, + types.LicenseScanner, + }, + want: ` +Report Summary + +┌────────┬──────┬─────────────────┬─────────┬───────────────────┬──────────┐ +│ Target │ Type │ Vulnerabilities │ Secrets │ Misconfigurations │ Licenses │ +├────────┼──────┼─────────────────┼─────────┼───────────────────┼──────────┤ +│ - │ - │ - │ - │ - │ - │ +└────────┴──────┴─────────────────┴─────────┴───────────────────┴──────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + `, }, } diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index f8ca0a02cfb6..0a53c0b79104 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -132,6 +132,13 @@ Legend: Target: "test", Class: types.ClassLangPkg, Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, }, }, wantOutput: ``, From 878b9f4896973781af1358e6d6459cb756a20c8d Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 5 Feb 2025 12:50:39 +0600 Subject: [PATCH 32/45] refactor: use `Renderer` interface for summary table --- pkg/report/table/summary.go | 235 ++++++++++++++++++ ...{table_private_test.go => summary_test.go} | 29 +-- pkg/report/table/table.go | 188 +------------- 3 files changed, 248 insertions(+), 204 deletions(-) rename pkg/report/table/{table_private_test.go => summary_test.go} (96%) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index 1cc617c6e563..f5333855d5a8 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -1,8 +1,22 @@ package table import ( + "bytes" + "fmt" + "slices" + "sort" + "strings" + "github.com/aquasecurity/table" + "github.com/aquasecurity/tml" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" + "github.com/fatih/color" + "github.com/samber/lo" + "golang.org/x/xerrors" ) type Scanner interface { @@ -11,6 +25,8 @@ type Scanner interface { // Count returns the number of findings, but -1 if the scanner is not applicable Count(result types.Result) int + + String() string // Required to show correct logs } func NewScanner(scanner types.Scanner) Scanner { @@ -46,6 +62,10 @@ func (s VulnerabilityScanner) Count(result types.Result) int { return -1 } +func (s VulnerabilityScanner) String() string { + return string(types.VulnerabilityScanner) +} + type MisconfigScanner struct{ scannerAlignment } func (s MisconfigScanner) Header() string { @@ -59,6 +79,10 @@ func (s MisconfigScanner) Count(result types.Result) int { return -1 } +func (s MisconfigScanner) String() string { + return string(types.MisconfigScanner) +} + type SecretScanner struct{ scannerAlignment } func (s SecretScanner) Header() string { @@ -72,6 +96,10 @@ func (s SecretScanner) Count(result types.Result) int { return -1 } +func (s SecretScanner) String() string { + return string(types.SecretScanner) +} + type LicenseScanner struct{ scannerAlignment } func (s LicenseScanner) Header() string { @@ -84,3 +112,210 @@ func (s LicenseScanner) Count(result types.Result) int { } return -1 } + +func (s LicenseScanner) String() string { + return string(types.LicenseScanner) +} + +type summaryRenderer struct { + w *bytes.Buffer + report types.Report + isTerminal bool + scanners []Scanner +} + +func NewSummaryRenderer(report types.Report, isTerminal bool, scanners types.Scanners) (*summaryRenderer, error) { + var ss []Scanner + for _, scanner := range scanners { + s := NewScanner(scanner) + if lo.IsNil(s) { + continue + } + ss = append(ss, s) + } + + // It should be an impossible case. + // But it is possible when Trivy is used as a library. + if len(scanners) == 0 { + return nil, xerrors.Errorf("unable to find scanners") + } + + buf := bytes.NewBuffer([]byte{}) + if !isTerminal { + tml.DisableFormatting() + } + return &summaryRenderer{ + w: buf, + report: report, + isTerminal: isTerminal, + scanners: ss, + }, nil +} + +func (r *summaryRenderer) Render() string { + r.renderSummary() + + return r.w.String() +} + +func (r *summaryRenderer) printf(format string, args ...any) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + +func (r *summaryRenderer) renderSummary() { + r.printf("\nReport Summary\n\n") + + t := newTableWriter(r.w, r.isTerminal) + t.SetAutoMerge(false) + t.SetColumnMaxWidth(80) + + headers := []string{ + "Target", + "Type", + } + alignments := []table.Alignment{ + table.AlignLeft, + table.AlignCenter, + } + for _, scanner := range r.scanners { + headers = append(headers, scanner.Header()) + alignments = append(alignments, scanner.Alignment()) + } + t.SetHeaders(headers...) + t.SetAlignment(alignments...) + + for _, result := range splitAggregatedPackages(r.report.Results) { + resultType := string(result.Type) + if result.Class == types.ClassSecret { + resultType = "text" + } else if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile { + resultType = "-" + } + rows := []string{ + result.Target, + resultType, + } + for _, scanner := range r.scanners { + rows = append(rows, r.colorizeCount(scanner.Count(result))) + } + t.AddRows(rows) + } + + if len(r.report.Results) == 0 { + r.showEmptyResultsWarning() + t.AddRows(slices.Repeat([]string{"-"}, len(r.scanners)+2)) + } + + t.Render() + + // Show legend + r.printf("Legend:\n" + + "- '-': Not scanned\n" + + "- '0': Clean (no security findings detected)\n\n") + + return +} + +// showEmptyResultsWarning shows WARN why the results array is empty based on the enabled scanners. +// We need to separate the vuln/misconfig and secret/license scanners, +// because the results array contains results without findings for vulns/misconfig only. +func (r *summaryRenderer) showEmptyResultsWarning() { + resultByFiles := []Scanner{ + NewScanner(types.VulnerabilityScanner), + NewScanner(types.MisconfigScanner), + } + resultByFindings := []Scanner{ + NewScanner(types.SecretScanner), + NewScanner(types.LicenseScanner), + } + + var warnStrings []string + if scanners := lo.Intersect(resultByFiles, r.scanners); len(scanners) > 0 { + warnStrings = append(warnStrings, fmt.Sprintf("Supported files for %s scanner(s) not found.", + strings.Join(xstrings.ToStringSlice(scanners), "/"))) + } + if scanners := lo.Intersect(resultByFindings, r.scanners); len(scanners) > 0 { + warnStrings = append(warnStrings, fmt.Sprintf("No results found for %s scanner(s).", + strings.Join(xstrings.ToStringSlice(scanners), "/"))) + } + + if len(warnStrings) == 0 { + warnStrings = append(warnStrings, "Scanners are not enabled.") + } + + log.WithPrefix("report").Info(strings.Join(warnStrings, " ")) +} + +// splitAggregatedPackages splits aggregated packages into different results with path as target. +// Other results will be returned as is. +func splitAggregatedPackages(results types.Results) types.Results { + var newResults types.Results + + for _, result := range results { + if !slices.Contains(ftypes.AggregatingTypes, result.Type) && + // License results from applications don't have `Type`. + (result.Class != types.ClassLicense || !slices.Contains(lo.Values(langpkg.PkgTargets), result.Target)) { + newResults = append(newResults, result) + continue + } + + newResults = append(newResults, splitAggregatedVulns(result)...) + newResults = append(newResults, splitAggregatedLicenses(result)...) + + } + return newResults +} + +func splitAggregatedVulns(result types.Result) types.Results { + // Save packages to display them in the table even if no vulnerabilities were found + resultMap := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, *types.Result) { + filePath := rootJarFromPath(pkg.FilePath) + return filePath, &types.Result{ + Target: lo.Ternary(filePath != "", filePath, result.Target), + Class: result.Class, + Type: result.Type, + } + }) + for _, vuln := range result.Vulnerabilities { + pkgPath := rootJarFromPath(vuln.PkgPath) + resultMap[pkgPath].Vulnerabilities = append(resultMap[pkgPath].Vulnerabilities, vuln) + } + newResults := lo.Values(resultMap) + sort.Slice(newResults, func(i, j int) bool { + return newResults[i].Target < newResults[j].Target + }) + return lo.FromSlicePtr(newResults) +} + +func splitAggregatedLicenses(result types.Result) types.Results { + var newResults types.Results + + licenses := make(map[string][]types.DetectedLicense) + for _, license := range result.Licenses { + licenses[license.FilePath] = append(licenses[license.FilePath], license) + } + for filePath, l := range licenses { + newResult := result + newResult.Target = lo.Ternary(filePath != "", filePath, result.Target) + newResult.Licenses = l + + newResults = append(newResults, newResult) + } + + sort.Slice(newResults, func(i, j int) bool { + return newResults[i].Target < newResults[j].Target + }) + return newResults +} + +func (r *summaryRenderer) colorizeCount(count int) string { + if count < 0 { + return "-" + } + sprintf := fmt.Sprintf + if count != 0 && r.isTerminal { + sprintf = color.New(color.FgHiRed).SprintfFunc() + } + return sprintf("%d", count) +} diff --git a/pkg/report/table/table_private_test.go b/pkg/report/table/summary_test.go similarity index 96% rename from pkg/report/table/table_private_test.go rename to pkg/report/table/summary_test.go index 59f91181f924..29200d670a64 100644 --- a/pkg/report/table/table_private_test.go +++ b/pkg/report/table/summary_test.go @@ -1,15 +1,12 @@ -package table +package table_test import ( - "bytes" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - - "github.com/aquasecurity/tml" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" + "github.com/aquasecurity/trivy/pkg/report/table" "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/require" ) var ( @@ -171,11 +168,10 @@ var ( func Test_renderSummary(t *testing.T) { tests := []struct { - name string - scanners types.Scanners - noSummaryTable bool - report types.Report - want string + name string + scanners types.Scanners + report types.Report + want string }{ { name: "happy path all scanners", @@ -335,16 +331,9 @@ Legend: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - tml.DisableFormatting() - tableWritten := bytes.Buffer{} - writer := Writer{ - Output: &tableWritten, - Scanners: tt.scanners, - NoSummaryTable: tt.noSummaryTable, - } - err := writer.renderSummary(tt.report) + r, err := table.NewSummaryRenderer(tt.report, false, tt.scanners) require.NoError(t, err) - assert.Equal(t, tt.want, tableWritten.String()) + require.Equal(t, tt.want, r.Render(), tt.name) }) } } diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 44b9b58a8055..63e06805df2b 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -7,21 +7,15 @@ import ( "os" "runtime" "slices" - "sort" "strings" "github.com/fatih/color" - "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/table" "github.com/aquasecurity/tml" dbTypes "github.com/aquasecurity/trivy-db/pkg/types" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" - "github.com/aquasecurity/trivy/pkg/log" - "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/types" - xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) var ( @@ -69,9 +63,11 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { } if !tw.NoSummaryTable { - if err := tw.renderSummary(report); err != nil { - return xerrors.Errorf("failed to render summary: %w", err) + renderer, err := NewSummaryRenderer(report, tw.isOutputToTerminal(), tw.Scanners) + if err != nil { + return xerrors.Errorf("failed to create summary renderer: %w", err) } + _, _ = fmt.Fprint(tw.Output, renderer.Render()) } for _, result := range report.Results { @@ -84,171 +80,6 @@ func (tw Writer) Write(_ context.Context, report types.Report) error { return nil } -func (tw Writer) renderSummary(report types.Report) error { - // Fprintln has a bug - if err := tml.Fprintf(tw.Output, "\nReport Summary\n\n"); err != nil { - return err - } - - t := newTableWriter(tw.Output, tw.isOutputToTerminal()) - t.SetAutoMerge(false) - t.SetColumnMaxWidth(80) - - var scanners []Scanner - for _, scanner := range tw.Scanners { - s := NewScanner(scanner) - if lo.IsNil(s) { - continue - } - scanners = append(scanners, s) - } - - // It should be an impossible case. - // But it is possible when Trivy is used as a library. - if len(scanners) == 0 { - return xerrors.Errorf("unable to find scanners") - } - - headers := []string{ - "Target", - "Type", - } - alignments := []table.Alignment{ - table.AlignLeft, - table.AlignCenter, - } - for _, scanner := range scanners { - headers = append(headers, scanner.Header()) - alignments = append(alignments, scanner.Alignment()) - } - t.SetHeaders(headers...) - t.SetAlignment(alignments...) - - for _, result := range splitAggregatedPackages(report.Results) { - resultType := string(result.Type) - if result.Class == types.ClassSecret { - resultType = "text" - } else if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile { - resultType = "-" - } - rows := []string{ - result.Target, - resultType, - } - for _, scanner := range scanners { - rows = append(rows, tw.colorizeCount(scanner.Count(result))) - } - t.AddRows(rows) - } - - if len(report.Results) == 0 { - t.AddRows(slices.Repeat([]string{"-"}, len(scanners)+2)) - tw.showEmptyResultsWarning() - } - - t.Render() - - // Show legend - if err := tml.Fprintf(tw.Output, "Legend:\n"+ - "- '-': Not scanned\n"+ - "- '0': Clean (no security findings detected)\n\n"); err != nil { - return err - } - return nil -} - -// showEmptyResultsWarning shows WARN why the results array is empty based on the enabled scanners. -// We need to separate the vuln/misconfig and secret/license scanners, -// because the results array contains results without findings for vulns/misconfig only. -func (tw Writer) showEmptyResultsWarning() { - resultByFiles := []types.Scanner{ - types.VulnerabilityScanner, - types.MisconfigScanner, - } - resultByFindings := []types.Scanner{ - types.SecretScanner, - types.LicenseScanner, - } - - var warnStrings []string - if scanners := lo.Intersect(resultByFiles, tw.Scanners); len(scanners) > 0 { - warnStrings = append(warnStrings, fmt.Sprintf("Supported files for %s scanner(s) not found.", - strings.Join(xstrings.ToStringSlice(scanners), "/"))) - } - if scanners := lo.Intersect(resultByFindings, tw.Scanners); len(scanners) > 0 { - warnStrings = append(warnStrings, fmt.Sprintf("No results found for %s scanner(s).", - strings.Join(xstrings.ToStringSlice(scanners), "/"))) - } - - if len(warnStrings) == 0 { - warnStrings = append(warnStrings, "Scanners are not enabled.") - } - - log.WithPrefix("report").Info(strings.Join(warnStrings, " ")) -} - -// splitAggregatedPackages splits aggregated packages into different results with path as target. -// Other results will be returned as is. -func splitAggregatedPackages(results types.Results) types.Results { - var newResults types.Results - - for _, result := range results { - if !slices.Contains(ftypes.AggregatingTypes, result.Type) && - // License results from applications don't have `Type`. - (result.Class != types.ClassLicense || !slices.Contains(lo.Values(langpkg.PkgTargets), result.Target)) { - newResults = append(newResults, result) - continue - } - - newResults = append(newResults, splitAggregatedVulns(result)...) - newResults = append(newResults, splitAggregatedLicenses(result)...) - - } - return newResults -} - -func splitAggregatedVulns(result types.Result) types.Results { - // Save packages to display them in the table even if no vulnerabilities were found - resultMap := lo.SliceToMap(result.Packages, func(pkg ftypes.Package) (string, *types.Result) { - filePath := rootJarFromPath(pkg.FilePath) - return filePath, &types.Result{ - Target: lo.Ternary(filePath != "", filePath, result.Target), - Class: result.Class, - Type: result.Type, - } - }) - for _, vuln := range result.Vulnerabilities { - pkgPath := rootJarFromPath(vuln.PkgPath) - resultMap[pkgPath].Vulnerabilities = append(resultMap[pkgPath].Vulnerabilities, vuln) - } - newResults := lo.Values(resultMap) - sort.Slice(newResults, func(i, j int) bool { - return newResults[i].Target < newResults[j].Target - }) - return lo.FromSlicePtr(newResults) -} - -func splitAggregatedLicenses(result types.Result) types.Results { - var newResults types.Results - - licenses := make(map[string][]types.DetectedLicense) - for _, license := range result.Licenses { - licenses[license.FilePath] = append(licenses[license.FilePath], license) - } - for filePath, l := range licenses { - newResult := result - newResult.Target = lo.Ternary(filePath != "", filePath, result.Target) - newResult.Licenses = l - - newResults = append(newResults, newResult) - } - - sort.Slice(newResults, func(i, j int) bool { - return newResults[i].Target < newResults[j].Target - }) - return newResults -} - func (tw Writer) write(result types.Result) { if result.IsEmpty() && result.Class != types.ClassOSPkg { return @@ -282,17 +113,6 @@ func (tw Writer) isOutputToTerminal() bool { return IsOutputToTerminal(tw.Output) } -func (tw Writer) colorizeCount(count int) string { - if count < 0 { - return "-" - } - sprintf := fmt.Sprintf - if count != 0 && tw.isOutputToTerminal() { - sprintf = color.New(color.FgHiRed).SprintfFunc() - } - return sprintf("%d", count) -} - func newTableWriter(output io.Writer, isTerminal bool) *table.Table { tableWriter := table.New(output) if isTerminal { // use ansi output if we're not piping elsewhere From 4b6214b14db4a05bbd63672d38411b9192b54037 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Wed, 5 Feb 2025 13:09:56 +0600 Subject: [PATCH 33/45] fix: linter error --- pkg/report/table/summary.go | 7 ++++--- pkg/report/table/summary_test.go | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index f5333855d5a8..20e001c9e8d6 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -7,6 +7,10 @@ import ( "sort" "strings" + "github.com/fatih/color" + "github.com/samber/lo" + "golang.org/x/xerrors" + "github.com/aquasecurity/table" "github.com/aquasecurity/tml" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" @@ -14,9 +18,6 @@ import ( "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/types" xstrings "github.com/aquasecurity/trivy/pkg/x/strings" - "github.com/fatih/color" - "github.com/samber/lo" - "golang.org/x/xerrors" ) type Scanner interface { diff --git a/pkg/report/table/summary_test.go b/pkg/report/table/summary_test.go index 29200d670a64..0c800a38f2c2 100644 --- a/pkg/report/table/summary_test.go +++ b/pkg/report/table/summary_test.go @@ -3,10 +3,11 @@ package table_test import ( "testing" + "github.com/stretchr/testify/require" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report/table" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/require" ) var ( From 8de06d7ae9947448b244f2adfaa2f6925d0c7ab0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 3 Mar 2025 13:00:29 +0600 Subject: [PATCH 34/45] refactor: after rebase --- pkg/report/table/summary.go | 50 ++++++++++++-------------------- pkg/report/table/summary_test.go | 8 +++-- pkg/report/table/table.go | 25 ++++++---------- pkg/report/table/table_test.go | 15 ++-------- 4 files changed, 35 insertions(+), 63 deletions(-) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index 20e001c9e8d6..22fb5323d6c5 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -9,7 +9,6 @@ import ( "github.com/fatih/color" "github.com/samber/lo" - "golang.org/x/xerrors" "github.com/aquasecurity/table" "github.com/aquasecurity/tml" @@ -120,12 +119,15 @@ func (s LicenseScanner) String() string { type summaryRenderer struct { w *bytes.Buffer - report types.Report isTerminal bool scanners []Scanner } -func NewSummaryRenderer(report types.Report, isTerminal bool, scanners types.Scanners) (*summaryRenderer, error) { +func NewSummaryRenderer(buf *bytes.Buffer, isTerminal bool, scanners types.Scanners) *summaryRenderer { + if !isTerminal { + tml.DisableFormatting() + } + var ss []Scanner for _, scanner := range scanners { s := NewScanner(scanner) @@ -135,36 +137,19 @@ func NewSummaryRenderer(report types.Report, isTerminal bool, scanners types.Sca ss = append(ss, s) } - // It should be an impossible case. - // But it is possible when Trivy is used as a library. - if len(scanners) == 0 { - return nil, xerrors.Errorf("unable to find scanners") - } - - buf := bytes.NewBuffer([]byte{}) - if !isTerminal { - tml.DisableFormatting() - } return &summaryRenderer{ w: buf, - report: report, isTerminal: isTerminal, scanners: ss, - }, nil -} - -func (r *summaryRenderer) Render() string { - r.renderSummary() - - return r.w.String() + } } -func (r *summaryRenderer) printf(format string, args ...any) { - // nolint - _ = tml.Fprintf(r.w, format, args...) -} +func (r *summaryRenderer) Render(report types.Report) { + if len(r.scanners) == 0 { + log.WithPrefix("report").Info("No enabled scanners found. Summary table will not be displayed.") + return + } -func (r *summaryRenderer) renderSummary() { r.printf("\nReport Summary\n\n") t := newTableWriter(r.w, r.isTerminal) @@ -186,7 +171,7 @@ func (r *summaryRenderer) renderSummary() { t.SetHeaders(headers...) t.SetAlignment(alignments...) - for _, result := range splitAggregatedPackages(r.report.Results) { + for _, result := range splitAggregatedPackages(report.Results) { resultType := string(result.Type) if result.Class == types.ClassSecret { resultType = "text" @@ -203,7 +188,7 @@ func (r *summaryRenderer) renderSummary() { t.AddRows(rows) } - if len(r.report.Results) == 0 { + if len(report.Results) == 0 { r.showEmptyResultsWarning() t.AddRows(slices.Repeat([]string{"-"}, len(r.scanners)+2)) } @@ -218,6 +203,11 @@ func (r *summaryRenderer) renderSummary() { return } +func (r *summaryRenderer) printf(format string, args ...any) { + // nolint + _ = tml.Fprintf(r.w, format, args...) +} + // showEmptyResultsWarning shows WARN why the results array is empty based on the enabled scanners. // We need to separate the vuln/misconfig and secret/license scanners, // because the results array contains results without findings for vulns/misconfig only. @@ -241,10 +231,6 @@ func (r *summaryRenderer) showEmptyResultsWarning() { strings.Join(xstrings.ToStringSlice(scanners), "/"))) } - if len(warnStrings) == 0 { - warnStrings = append(warnStrings, "Scanners are not enabled.") - } - log.WithPrefix("report").Info(strings.Join(warnStrings, " ")) } diff --git a/pkg/report/table/summary_test.go b/pkg/report/table/summary_test.go index 0c800a38f2c2..601e3a9f76a4 100644 --- a/pkg/report/table/summary_test.go +++ b/pkg/report/table/summary_test.go @@ -1,6 +1,7 @@ package table_test import ( + "bytes" "testing" "github.com/stretchr/testify/require" @@ -332,9 +333,10 @@ Legend: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - r, err := table.NewSummaryRenderer(tt.report, false, tt.scanners) - require.NoError(t, err) - require.Equal(t, tt.want, r.Render(), tt.name) + buf := bytes.NewBuffer([]byte{}) + r := table.NewSummaryRenderer(buf, false, tt.scanners) + r.Render(tt.report) + require.Equal(t, tt.want, buf.String()) }) } } diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index 3e1e7fbae202..f09b5a4f0aea 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -11,7 +11,6 @@ import ( "strings" "github.com/fatih/color" - "golang.org/x/xerrors" "github.com/aquasecurity/table" "github.com/aquasecurity/tml" @@ -35,7 +34,7 @@ type Writer struct { // Use one buffer for all renderers buf *bytes.Buffer - summaryRenderer Renderer + summaryRenderer *summaryRenderer vulnerabilityRenderer Renderer misconfigRenderer Renderer secretRenderer Renderer @@ -71,14 +70,16 @@ type Options struct { func NewWriter(options Options) *Writer { buf := bytes.NewBuffer([]byte{}) + isTerminal := IsOutputToTerminal(options.Output) return &Writer{ buf: buf, - vulnerabilityRenderer: NewVulnerabilityRenderer(buf, IsOutputToTerminal(options.Output), options.Tree, options.ShowSuppressed, options.Severities), - misconfigRenderer: NewMisconfigRenderer(buf, options.Severities, options.Trace, options.IncludeNonFailures, IsOutputToTerminal(options.Output), options.RenderCause), - secretRenderer: NewSecretRenderer(buf, IsOutputToTerminal(options.Output), options.Severities), - pkgLicenseRenderer: NewPkgLicenseRenderer(buf, IsOutputToTerminal(options.Output), options.Severities), - fileLicenseRenderer: NewFileLicenseRenderer(buf, IsOutputToTerminal(options.Output), options.Severities), + summaryRenderer: NewSummaryRenderer(buf, isTerminal, options.Scanners), + vulnerabilityRenderer: NewVulnerabilityRenderer(buf, isTerminal, options.Tree, options.ShowSuppressed, options.Severities), + misconfigRenderer: NewMisconfigRenderer(buf, options.Severities, options.Trace, options.IncludeNonFailures, isTerminal, options.RenderCause), + secretRenderer: NewSecretRenderer(buf, isTerminal, options.Severities), + pkgLicenseRenderer: NewPkgLicenseRenderer(buf, isTerminal, options.Severities), + fileLicenseRenderer: NewFileLicenseRenderer(buf, isTerminal, options.Severities), options: options, } } @@ -89,16 +90,8 @@ type Renderer interface { // Write writes the result on standard output func (tw *Writer) Write(_ context.Context, report types.Report) error { - if !IsOutputToTerminal(tw.options.Output) { - tml.DisableFormatting() - } - if !tw.options.NoSummaryTable { - renderer, err := NewSummaryRenderer(report, IsOutputToTerminal(tw.options.Output), tw.options.Scanners) - if err != nil { - return xerrors.Errorf("failed to create summary renderer: %w", err) - } - _, _ = fmt.Fprint(tw.options.Output, renderer.Render()) + tw.summaryRenderer.Render(report) } for _, result := range report.Results { diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 2894d486bb4d..0d89e6f2a5ac 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -4,13 +4,11 @@ import ( "bytes" "testing" - "github.com/stretchr/testify/assert" - "github.com/stretchr/testify/require" - dbTypes "github.com/aquasecurity/trivy-db/pkg/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report/table" "github.com/aquasecurity/trivy/pkg/types" + "github.com/stretchr/testify/assert" ) func TestWriter_Write(t *testing.T) { @@ -20,7 +18,6 @@ func TestWriter_Write(t *testing.T) { noSummaryTable bool results types.Results wantOutput string - wantError string includeNonFailures bool }{ { @@ -152,7 +149,7 @@ Legend: Type: ftypes.Jar, }, }, - wantError: "unable to find scanners", + wantOutput: ``, }, } @@ -171,13 +168,7 @@ Legend: Scanners: tc.scanners, NoSummaryTable: tc.noSummaryTable, }) - err := writer.Write(nil, types.Report{Results: tc.results}) - if tc.wantError != "" { - require.Error(t, err) - return - } - - require.NoError(t, err) + _ = writer.Write(nil, types.Report{Results: tc.results}) assert.Equal(t, tc.wantOutput, tableWritten.String(), tc.name) }) } From 5ea3e4f9905eeabac83c17e33bbf4ddbe0f71e1f Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 3 Mar 2025 13:03:40 +0600 Subject: [PATCH 35/45] fix(report): skip applications without licenses --- integration/testdata/license-cyclonedx.json.golden | 4 ---- pkg/scanner/local/scan.go | 14 +++++++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index bc136a5cc661..ad5fc250401e 100644 --- a/integration/testdata/license-cyclonedx.json.golden +++ b/integration/testdata/license-cyclonedx.json.golden @@ -51,10 +51,6 @@ "Link": "" } ] - }, - { - "Target": "pom.xml", - "Class": "license" } ] } diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 0788b576474c..8c474a44e484 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -315,11 +315,15 @@ func (s Scanner) scanApplicationLicenses(apps []ftypes.Application, scanner lice // When the file path is empty, we will overwrite it with the pre-defined value. targetName = t } - results = append(results, types.Result{ - Target: targetName, - Class: types.ClassLicense, - Licenses: langLicenses, - }) + + if len(langLicenses) != 0 { + results = append(results, types.Result{ + Target: targetName, + Class: types.ClassLicense, + Licenses: langLicenses, + }) + } + } return results From 1159d632dcc4d2a2c18d93b33846e18f0bfe1172 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 3 Mar 2025 13:59:59 +0600 Subject: [PATCH 36/45] refactor: enable `--scanners` for convert mode --- docs/docs/configuration/reporting.md | 8 ++++++-- docs/docs/references/configuration/cli/trivy_convert.md | 1 + .../docs/references/configuration/cli/trivy_kubernetes.md | 1 - pkg/commands/app.go | 8 ++++++++ pkg/commands/convert/run.go | 8 +++++++- pkg/report/table/summary.go | 2 +- 6 files changed, 23 insertions(+), 5 deletions(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 195c7ba45bcc..d38aa3731d14 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -548,6 +548,10 @@ $ trivy convert --format cyclonedx --output result.cdx result.json Please note that if you want to convert to a format that requires a list of packages, such as SBOM, you need to add the `--list-all-pkgs` flag when outputting in JSON. +!!! warning + JSON reports from "trivy k8s" are not yet supported. + +### Filtering [Filtering options](./filtering.md) such as `--severity` are also available with `convert`. ```shell @@ -558,8 +562,8 @@ $ trivy image --format json -o result.json --list-all-pkgs debian:11 $ trivy convert --format table --severity CRITICAL result.json ``` -!!! note - JSON reports from "trivy k8s" are not yet supported. +### Table format +To display the [summary table](#summary-table), you need to [enable the scanners](./others.md#enabledisable-scanners) that were used to generate the [json report](#converting). [cargo-auditable]: https://github.com/rust-secure-code/cargo-auditable/ [action]: https://github.com/aquasecurity/trivy-action diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md index ec01f6014eaf..06db5cd9bf77 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -31,6 +31,7 @@ trivy convert [flags] RESULT_JSON -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") + --scanners strings List of scanners included when generating the json report. Used only for rendering the summary table. (vuln,misconfig,secret,license) (default [vuln,secret]) -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities -t, --template string output template diff --git a/docs/docs/references/configuration/cli/trivy_kubernetes.md b/docs/docs/references/configuration/cli/trivy_kubernetes.md index 227ddda8c043..3f71510682d7 100644 --- a/docs/docs/references/configuration/cli/trivy_kubernetes.md +++ b/docs/docs/references/configuration/cli/trivy_kubernetes.md @@ -77,7 +77,6 @@ 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 diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 7bedce99ff65..45e3fb8b15c6 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -520,6 +520,13 @@ func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { ReportFlagGroup: flag.NewReportFlagGroup(), } + // To display the summary table, we need to enable scanners (to build columns). + // We can't get scanner information from the report (we don't include empty licenses and secrets in the report). + // So we need to ask the user to configure scanners (if needed). + convertFlags.ScanFlagGroup.Scanners = flag.ScannersFlag.Clone() + convertFlags.ScanFlagGroup.Scanners.Default = nil // disable default scanners + convertFlags.ScanFlagGroup.Scanners.Usage = "List of scanners included when generating the json report. Used only for rendering the summary table." + cmd := &cobra.Command{ Use: "convert [flags] RESULT_JSON", Aliases: []string{"conv"}, @@ -977,6 +984,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' + reportFlagGroup.NoSummaryTable = nil //disable '--no-summary' formatFlag := flag.FormatFlag.Clone() formatFlag.Values = xstrings.ToStringSlice([]types.Format{ diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go index 584bc6f6bddc..e160bcc1a4be 100644 --- a/pkg/commands/convert/run.go +++ b/pkg/commands/convert/run.go @@ -18,6 +18,7 @@ import ( ) func Run(ctx context.Context, opts flag.Options) (err error) { + logger := log.WithPrefix("convert") ctx, cancel := context.WithTimeout(ctx, opts.Timeout) defer cancel() @@ -42,7 +43,12 @@ func Run(ctx context.Context, opts flag.Options) (err error) { return xerrors.Errorf("unable to filter results: %w", err) } - log.Debug("Writing report to output...") + if len(opts.Scanners) == 0 && opts.Format == types.FormatTable && !opts.NoSummaryTable { + logger.Info("To display the summary table, enable the scanners used to generate the json report") + opts.NoSummaryTable = true + } + + logger.Debug("Writing report to output...") if err = report.Write(ctx, r, opts); err != nil { return xerrors.Errorf("unable to write results: %w", err) } diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index 22fb5323d6c5..9d23de3bbf2b 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -146,7 +146,7 @@ func NewSummaryRenderer(buf *bytes.Buffer, isTerminal bool, scanners types.Scann func (r *summaryRenderer) Render(report types.Report) { if len(r.scanners) == 0 { - log.WithPrefix("report").Info("No enabled scanners found. Summary table will not be displayed.") + log.WithPrefix("report").Warn("No enabled scanners found. Summary table will not be displayed.") return } From 5b048c17003459b46fb3ff2856a6203ff06751fb Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 3 Mar 2025 14:00:41 +0600 Subject: [PATCH 37/45] fix: linter errors --- pkg/commands/app.go | 2 +- pkg/report/table/table_test.go | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 45e3fb8b15c6..58900c8b4334 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -984,7 +984,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - reportFlagGroup.NoSummaryTable = nil //disable '--no-summary' + reportFlagGroup.NoSummaryTable = nil // disable '--no-summary' formatFlag := flag.FormatFlag.Clone() formatFlag.Values = xstrings.ToStringSlice([]types.Format{ diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index 0d89e6f2a5ac..aab5c574e41c 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -4,11 +4,12 @@ import ( "bytes" "testing" + "github.com/stretchr/testify/assert" + dbTypes "github.com/aquasecurity/trivy-db/pkg/types" ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/report/table" "github.com/aquasecurity/trivy/pkg/types" - "github.com/stretchr/testify/assert" ) func TestWriter_Write(t *testing.T) { From fd00dc9737290d81a50956ada8a26689dc69f1d2 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 3 Mar 2025 14:37:16 +0600 Subject: [PATCH 38/45] mage docs:generate --- docs/docs/references/configuration/cli/trivy_convert.md | 2 +- pkg/commands/app.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md index 06db5cd9bf77..eeba378dbffc 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -31,7 +31,7 @@ trivy convert [flags] RESULT_JSON -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") - --scanners strings List of scanners included when generating the json report. Used only for rendering the summary table. (vuln,misconfig,secret,license) (default [vuln,secret]) + --scanners strings List of scanners included when generating the json report. Used only for rendering the summary table. (vuln,misconfig,secret,license) -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities -t, --template string output template diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 58900c8b4334..e8a450006310 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -984,7 +984,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - reportFlagGroup.NoSummaryTable = nil // disable '--no-summary' + reportFlagGroup.NoSummaryTable = nil // disable '--no-summary-table' formatFlag := flag.FormatFlag.Clone() formatFlag.Values = xstrings.ToStringSlice([]types.Format{ From 67a729cd2b36e3171316c26133da2e5a15c8b9a2 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Mon, 3 Mar 2025 16:02:56 +0600 Subject: [PATCH 39/45] refactor: add logger into summaryRenderer --- pkg/report/table/summary.go | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index 9d23de3bbf2b..c876467cc25a 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -5,7 +5,6 @@ import ( "fmt" "slices" "sort" - "strings" "github.com/fatih/color" "github.com/samber/lo" @@ -16,7 +15,6 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/scanner/langpkg" "github.com/aquasecurity/trivy/pkg/types" - xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) type Scanner interface { @@ -121,6 +119,7 @@ type summaryRenderer struct { w *bytes.Buffer isTerminal bool scanners []Scanner + logger *log.Logger } func NewSummaryRenderer(buf *bytes.Buffer, isTerminal bool, scanners types.Scanners) *summaryRenderer { @@ -141,12 +140,13 @@ func NewSummaryRenderer(buf *bytes.Buffer, isTerminal bool, scanners types.Scann w: buf, isTerminal: isTerminal, scanners: ss, + logger: log.WithPrefix("report"), } } func (r *summaryRenderer) Render(report types.Report) { if len(r.scanners) == 0 { - log.WithPrefix("report").Warn("No enabled scanners found. Summary table will not be displayed.") + r.logger.Warn("No enabled scanners found. Summary table will not be displayed.") return } @@ -221,17 +221,12 @@ func (r *summaryRenderer) showEmptyResultsWarning() { NewScanner(types.LicenseScanner), } - var warnStrings []string if scanners := lo.Intersect(resultByFiles, r.scanners); len(scanners) > 0 { - warnStrings = append(warnStrings, fmt.Sprintf("Supported files for %s scanner(s) not found.", - strings.Join(xstrings.ToStringSlice(scanners), "/"))) + r.logger.Warn("Supported files for scanner(s) not found.", log.Any("scanners", scanners)) } if scanners := lo.Intersect(resultByFindings, r.scanners); len(scanners) > 0 { - warnStrings = append(warnStrings, fmt.Sprintf("No results found for %s scanner(s).", - strings.Join(xstrings.ToStringSlice(scanners), "/"))) + r.logger.Warn("No issues detected with scanner(s).", log.Any("scanners", scanners)) } - - log.WithPrefix("report").Info(strings.Join(warnStrings, " ")) } // splitAggregatedPackages splits aggregated packages into different results with path as target. From ea453d8774f327ce5b8bc802142001a7bfd36ba0 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 4 Mar 2025 13:16:05 +0600 Subject: [PATCH 40/45] refactor: change --no-summary-table to -tables-mode --- docs/docs/configuration/reporting.md | 129 ++++++++++++++---- .../configuration/cli/trivy_config.md | 2 +- .../configuration/cli/trivy_convert.md | 2 +- .../configuration/cli/trivy_filesystem.md | 2 +- .../configuration/cli/trivy_image.md | 2 +- .../configuration/cli/trivy_repository.md | 2 +- .../configuration/cli/trivy_rootfs.md | 2 +- .../configuration/cli/trivy_sbom.md | 2 +- .../references/configuration/cli/trivy_vm.md | 2 +- .../references/configuration/config-file.md | 8 +- pkg/commands/app.go | 2 +- pkg/commands/convert/run.go | 10 +- pkg/flag/report_flags.go | 28 ++-- pkg/flag/report_flags_test.go | 15 +- pkg/report/table/table.go | 18 +-- pkg/report/table/table_test.go | 85 +++++++++++- pkg/report/writer.go | 2 +- pkg/types/report.go | 12 ++ 18 files changed, 248 insertions(+), 77 deletions(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index d38aa3731d14..7fd9f549e7b2 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -20,7 +20,7 @@ Trivy supports the following formats: | License | ✓ | ```bash -$ trivy image -f table golang:1.22.11-alpine3.21 +$ trivy image -f table golang:1.22.11-alpine3.20 ```
@@ -29,35 +29,95 @@ $ trivy image -f table golang:1.22.11-alpine3.21 ``` ... - 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 │ - │ +│ golang:1.22.11-alpine3.20 (alpine 3.20.5) │ alpine │ 6 │ - │ ├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ -│ usr/local/go/bin/gofmt │ gobinary │ 0 │ - │ +│ usr/local/go/bin/go │ gobinary │ 1 │ - │ ├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ ... ├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ -│ usr/local/go/pkg/tool/linux_amd64/vet │ gobinary │ 0 │ - │ +│ usr/local/go/pkg/tool/linux_amd64/vet │ gobinary │ 1 │ - │ └─────────────────────────────────────────────┴──────────┴─────────────────┴─────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + + +golang:1.22.11-alpine3.20 (alpine 3.20.5) + +Total: 6 (UNKNOWN: 2, LOW: 0, MEDIUM: 2, HIGH: 2, CRITICAL: 0) + +┌────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┬─────────────────────────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ libcrypto3 │ CVE-2024-12797 │ HIGH │ fixed │ 3.3.2-r1 │ 3.3.3-r0 │ openssl: RFC7250 handshakes with unauthenticated servers │ +│ │ │ │ │ │ │ don't abort as expected │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-12797 │ +│ ├────────────────┼──────────┤ │ ├───────────────┼─────────────────────────────────────────────────────────────┤ +│ │ CVE-2024-13176 │ MEDIUM │ │ │ 3.3.2-r2 │ openssl: Timing side-channel in ECDSA signature computation │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-13176 │ +├────────────┼────────────────┼──────────┤ │ ├───────────────┼─────────────────────────────────────────────────────────────┤ +│ libssl3 │ CVE-2024-12797 │ HIGH │ │ │ 3.3.3-r0 │ openssl: RFC7250 handshakes with unauthenticated servers │ +│ │ │ │ │ │ │ don't abort as expected │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-12797 │ +│ ├────────────────┼──────────┤ │ ├───────────────┼─────────────────────────────────────────────────────────────┤ +│ │ CVE-2024-13176 │ MEDIUM │ │ │ 3.3.2-r2 │ openssl: Timing side-channel in ECDSA signature computation │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2024-13176 │ +├────────────┼────────────────┼──────────┤ ├───────────────────┼───────────────┼─────────────────────────────────────────────────────────────┤ +│ musl │ CVE-2025-26519 │ UNKNOWN │ │ 1.2.5-r0 │ 1.2.5-r1 │ musl libc 0.9.13 through 1.2.5 before 1.2.6 has an │ +│ │ │ │ │ │ │ out-of-bounds write ...... │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-26519 │ +├────────────┤ │ │ │ │ │ │ +│ musl-utils │ │ │ │ │ │ │ +│ │ │ │ │ │ │ │ +│ │ │ │ │ │ │ │ +└────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┴─────────────────────────────────────────────────────────────┘ + +usr/local/go/bin/go (gobinary) + +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) + +┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ stdlib │ CVE-2025-22866 │ MEDIUM │ fixed │ v1.22.11 │ 1.22.12, 1.23.6, 1.24.0-rc.3 │ crypto/internal/nistec: golang: Timing sidechannel for P-256 │ +│ │ │ │ │ │ │ on ppc64le in crypto/internal/nistec │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-22866 │ +└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘ -golang:1.22.11-alpine3.21 (alpine 3.21.2) - -Total: 0 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 0) - +... ```
-#### Summary table -Before result tables Trivy shows summary table. +#### Table mode +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports the following modes for `table` format: + +| Mode | Enable by default | +|:----------------------------:|:-----------------:| +| [summary](#summary-table) | ✓[^1] | +| [detailed](#detailed-tables) | ✓ | + +!!! note + Use `--table-mode` flag to enable/disable table mode(s). + + +##### Summary table +Summary table contains general information about the scan performed. + +Nuances of table contents: + +- Table includes columns for enabled [scanners](../references/terminology.md#scanner) only. Use `--scanners` flag to enable/disable scanners. +- Table includes separate lines for the same targets but different scanners. + - `-` means that the scanner didn't scan this target. + - `0` means that the scanner scanned this target, but found no security issues.
Report Summary @@ -84,15 +144,28 @@ Before result tables Trivy shows summary table.
-This table: +##### Detailed tables +Detailed tables contains information about found security issues for each target with more detailed information (CVE-id, severity, version, etc.). -- Includes columns for enabled [scanners](../references/terminology.md#scanner) only. -- Contains separate lines for the same targets but different scanners. - - `-` means that the scanner didn't scan this target. - - `0` means that the scanner scanned this target, but found no security issues. +
+Detailed tables + +``` + +usr/local/go/bin/go (gobinary) + +Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) -!!! note - Use `--no-summary-table` flag to hide summary table. +┌─────────┬────────────────┬──────────┬────────┬───────────────────┬──────────────────────────────┬──────────────────────────────────────────────────────────────┐ +│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │ Title │ +├─────────┼────────────────┼──────────┼────────┼───────────────────┼──────────────────────────────┼──────────────────────────────────────────────────────────────┤ +│ stdlib │ CVE-2025-22866 │ MEDIUM │ fixed │ v1.22.11 │ 1.22.12, 1.23.6, 1.24.0-rc.3 │ crypto/internal/nistec: golang: Timing sidechannel for P-256 │ +│ │ │ │ │ │ │ on ppc64le in crypto/internal/nistec │ +│ │ │ │ │ │ │ https://avd.aquasec.com/nvd/cve-2025-22866 │ +└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘ + +``` +
#### Show origins of vulnerable dependencies @@ -545,13 +618,9 @@ $ trivy convert --format cyclonedx --output result.cdx result.json ``` !!! note - Please note that if you want to convert to a format that requires a list of packages, + Please note that if you want to convert to a format that requires a list of packages, such as SBOM, you need to add the `--list-all-pkgs` flag when outputting in JSON. -!!! warning - JSON reports from "trivy k8s" are not yet supported. - -### Filtering [Filtering options](./filtering.md) such as `--severity` are also available with `convert`. ```shell @@ -562,8 +631,8 @@ $ trivy image --format json -o result.json --list-all-pkgs debian:11 $ trivy convert --format table --severity CRITICAL result.json ``` -### Table format -To display the [summary table](#summary-table), you need to [enable the scanners](./others.md#enabledisable-scanners) that were used to generate the [json report](#converting). +!!! note + JSON reports from "trivy k8s" are not yet supported. [cargo-auditable]: https://github.com/rust-secure-code/cargo-auditable/ [action]: https://github.com/aquasecurity/trivy-action @@ -593,3 +662,5 @@ To display the [summary table](#summary-table), you need to [enable the scanners [sbt-lockfile]: ../coverage/language/java.md#sbt [pubspec-lock]: ../coverage/language/dart.md#dart [cargo-binaries]: ../coverage/language/rust.md#binaries + +[^1]: To show summary table in `convert` mode - you need to enable the scanners used during JSON report generation. \ No newline at end of file diff --git a/docs/docs/references/configuration/cli/trivy_config.md b/docs/docs/references/configuration/cli/trivy_config.md index 2031c2193671..2d977a4f1d18 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -36,7 +36,6 @@ 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. @@ -52,6 +51,7 @@ trivy config [flags] DIR --skip-check-update skip fetching rego check updates --skip-dirs strings specify the directories or glob patterns to skip --skip-files strings specify the files or glob patterns to skip + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md index eeba378dbffc..f396e92a8fdd 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -27,13 +27,13 @@ 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") --scanners strings List of scanners included when generating the json report. Used only for rendering the summary table. (vuln,misconfig,secret,license) -s, --severity strings severities of security issues to be displayed (UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL) (default [UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL]) --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -t, --template string output template ``` diff --git a/docs/docs/references/configuration/cli/trivy_filesystem.md b/docs/docs/references/configuration/cli/trivy_filesystem.md index 6c58adee6dbd..9e99c441f268 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -64,7 +64,6 @@ 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 @@ -93,6 +92,7 @@ trivy filesystem [flags] PATH --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files diff --git a/docs/docs/references/configuration/cli/trivy_image.md b/docs/docs/references/configuration/cli/trivy_image.md index d95d47ea49d3..4e90a2e19471 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -83,7 +83,6 @@ 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 @@ -115,6 +114,7 @@ trivy image [flags] IMAGE_NAME --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --token string for authentication in client/server mode diff --git a/docs/docs/references/configuration/cli/trivy_repository.md b/docs/docs/references/configuration/cli/trivy_repository.md index c223727ef9fb..4265b790cd74 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -63,7 +63,6 @@ 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 @@ -91,6 +90,7 @@ trivy repository [flags] (REPO_PATH | REPO_URL) --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) --tag string pass the tag name to be scanned -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules diff --git a/docs/docs/references/configuration/cli/trivy_rootfs.md b/docs/docs/references/configuration/cli/trivy_rootfs.md index 1f67f2955570..4607a25f56a7 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -66,7 +66,6 @@ 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 @@ -94,6 +93,7 @@ trivy rootfs [flags] ROOTDIR --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --tf-vars strings specify paths to override the Terraform tfvars files diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index 2f9c1fe79100..42df969bfefd 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -45,7 +45,6 @@ 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 @@ -69,6 +68,7 @@ trivy sbom [flags] SBOM_PATH --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -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") diff --git a/docs/docs/references/configuration/cli/trivy_vm.md b/docs/docs/references/configuration/cli/trivy_vm.md index 85fd0b68baea..49010ac26e85 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -58,7 +58,6 @@ 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 @@ -82,6 +81,7 @@ trivy vm [flags] VM_IMAGE --skip-files strings specify the files or glob patterns to skip --skip-java-db-update skip updating Java index database --skip-vex-repo-update [EXPERIMENTAL] Skip VEX Repository update + --table-mode strings [EXPERIMENTAL] tables that will be displayed in 'table' format (summary,detailed) (default [summary,detailed]) -t, --template string output template --tf-exclude-downloaded-modules exclude misconfigurations for downloaded terraform modules --token string for authentication in client/server mode diff --git a/docs/docs/references/configuration/config-file.md b/docs/docs/references/configuration/config-file.md index 6aba1b772585..6a2cd1e47dea 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -529,9 +529,6 @@ ignorefile: ".trivyignore" # Same as '--list-all-pkgs' list-all-pkgs: false -# Same as '--no-summary-table' -no-summary-table: false - # Same as '--output' output: "" @@ -556,6 +553,11 @@ severity: - HIGH - CRITICAL +# Same as '--table-mode' +table-mode: + - summary + - detailed + # Same as '--template' template: "" diff --git a/pkg/commands/app.go b/pkg/commands/app.go index e8a450006310..86e3428e5925 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -984,7 +984,7 @@ func NewKubernetesCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { } reportFlagGroup.Compliance = compliance // override usage as the accepted values differ for each subcommand. reportFlagGroup.ExitOnEOL = nil // disable '--exit-on-eol' - reportFlagGroup.NoSummaryTable = nil // disable '--no-summary-table' + reportFlagGroup.TableMode = nil // disable '--table-mode' formatFlag := flag.FormatFlag.Clone() formatFlag.Values = xstrings.ToStringSlice([]types.Format{ diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go index e160bcc1a4be..928b6a65f40d 100644 --- a/pkg/commands/convert/run.go +++ b/pkg/commands/convert/run.go @@ -4,7 +4,9 @@ import ( "context" "encoding/json" "os" + "slices" + "github.com/samber/lo" "golang.org/x/xerrors" "github.com/aquasecurity/trivy/pkg/commands/operation" @@ -43,9 +45,11 @@ func Run(ctx context.Context, opts flag.Options) (err error) { return xerrors.Errorf("unable to filter results: %w", err) } - if len(opts.Scanners) == 0 && opts.Format == types.FormatTable && !opts.NoSummaryTable { - logger.Info("To display the summary table, enable the scanners used to generate the json report") - opts.NoSummaryTable = true + if len(opts.Scanners) == 0 && opts.Format == types.FormatTable && slices.Contains(opts.TableModes, types.Summary) { + logger.Info("To display the summary table, enable the scanners used during JSON report generation.") + opts.TableModes = lo.Filter(opts.TableModes, func(mode types.TableMode, _ int) bool { + return mode != types.Summary + }) } logger.Debug("Writing report to output...") diff --git a/pkg/flag/report_flags.go b/pkg/flag/report_flags.go index f534c82a6a3a..90ac077abcee 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -109,10 +109,12 @@ 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", + TableModeFlag = Flag[[]string]{ + Name: "table-mode", + ConfigName: "table-mode", + Default: xstrings.ToStringSlice(types.SupportedTableModes), + Values: xstrings.ToStringSlice(types.SupportedTableModes), + Usage: "[EXPERIMENTAL] tables that will be displayed in 'table' format", } ) @@ -133,7 +135,7 @@ type ReportFlagGroup struct { Severity *Flag[[]string] Compliance *Flag[string] ShowSuppressed *Flag[bool] - NoSummaryTable *Flag[bool] + TableMode *Flag[[]string] } type ReportOptions struct { @@ -151,7 +153,7 @@ type ReportOptions struct { Severities []dbTypes.Severity Compliance spec.ComplianceSpec ShowSuppressed bool - NoSummaryTable bool + TableModes []types.TableMode } func NewReportFlagGroup() *ReportFlagGroup { @@ -170,7 +172,7 @@ func NewReportFlagGroup() *ReportFlagGroup { Severity: SeverityFlag.Clone(), Compliance: ComplianceFlag.Clone(), ShowSuppressed: ShowSuppressedFlag.Clone(), - NoSummaryTable: NoSummaryTableFlag.Clone(), + TableMode: TableModeFlag.Clone(), } } @@ -194,7 +196,7 @@ func (f *ReportFlagGroup) Flags() []Flagger { f.Severity, f.Compliance, f.ShowSuppressed, - f.NoSummaryTable, + f.TableMode, } } @@ -207,7 +209,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { template := f.Template.Value() dependencyTree := f.DependencyTree.Value() listAllPkgs := f.ListAllPkgs.Value() - noSummaryTable := f.NoSummaryTable.Value() + tableModes := f.TableMode.Value() if template != "" { if format == "" { @@ -237,9 +239,9 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { } } - // "--so-summary" option is available only with "--format table". - if noSummaryTable && format != types.FormatTable { - return ReportOptions{}, xerrors.New(`"--no-summary-table" can be used only with "--format table".`) + // "--table-mode" option is available only with "--format table". + if viper.IsSet(TableModeFlag.ConfigName) && format != types.FormatTable { + return ReportOptions{}, xerrors.New(`"--table-mode" can be used only with "--format table".`) } cs, err := loadComplianceTypes(f.Compliance.Value()) @@ -274,7 +276,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Severities: toSeverity(f.Severity.Value()), Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), - NoSummaryTable: noSummaryTable, + TableModes: xstrings.ToTSlice[types.TableMode](tableModes), }, nil } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index c3a31dd27dae..7999ab6c84bd 100644 --- a/pkg/flag/report_flags_test.go +++ b/pkg/flag/report_flags_test.go @@ -13,6 +13,7 @@ import ( iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/types" + xstrings "github.com/aquasecurity/trivy/pkg/x/strings" ) func TestReportFlagGroup_ToOptions(t *testing.T) { @@ -32,7 +33,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { compliance string debug bool pkgTypes string - noSummaryTable bool + tableModes []string } tests := []struct { name string @@ -163,12 +164,12 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { }, }, { - name: "invalid option combination: --no-summary-table with --format json", + name: "invalid option combination: --table-modes with --format json", fields: fields{ - format: "json", - noSummaryTable: true, + format: "json", + tableModes: xstrings.ToStringSlice(types.SupportedTableModes), }, - wantErr: `"--no-summary-table" can be used only with "--format table".`, + wantErr: `"--table-mode" can be used only with "--format table".`, }, } for _, tt := range tests { @@ -194,7 +195,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) + setSliceValue(flag.TableModeFlag.ConfigName, tt.fields.tableModes) // Assert options f := &flag.ReportFlagGroup{ @@ -210,7 +211,7 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { OutputPluginArg: flag.OutputPluginArgFlag.Clone(), Severity: flag.SeverityFlag.Clone(), Compliance: flag.ComplianceFlag.Clone(), - NoSummaryTable: flag.NoSummaryTableFlag.Clone(), + TableMode: flag.TableModeFlag.Clone(), } got, err := f.ToOptions() diff --git a/pkg/report/table/table.go b/pkg/report/table/table.go index f09b5a4f0aea..8f0d0902ff46 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -55,8 +55,8 @@ type Options struct { // Show suppressed findings ShowSuppressed bool - // Hide summary table - NoSummaryTable bool + // Show/hide summary/detailed tables + TableModes []types.TableMode // For misconfigurations IncludeNonFailures bool @@ -90,16 +90,18 @@ type Renderer interface { // Write writes the result on standard output func (tw *Writer) Write(_ context.Context, report types.Report) error { - if !tw.options.NoSummaryTable { + if slices.Contains(tw.options.TableModes, types.Summary) { tw.summaryRenderer.Render(report) } - for _, result := range report.Results { - // Not display a table of custom resources - if result.Class == types.ClassCustom { - continue + if slices.Contains(tw.options.TableModes, types.Detailed) { + for _, result := range report.Results { + // Not display a table of custom resources + if result.Class == types.ClassCustom { + continue + } + tw.render(result) } - tw.render(result) } tw.flush() diff --git a/pkg/report/table/table_test.go b/pkg/report/table/table_test.go index aab5c574e41c..42934ccdcb58 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -16,7 +16,7 @@ func TestWriter_Write(t *testing.T) { testCases := []struct { name string scanners types.Scanners - noSummaryTable bool + tableModes []types.TableMode results types.Results wantOutput string includeNonFailures bool @@ -26,6 +26,7 @@ func TestWriter_Write(t *testing.T) { scanners: types.Scanners{ types.VulnerabilityScanner, }, + tableModes: types.SupportedTableModes, results: types.Results{ { Target: "test", @@ -91,6 +92,7 @@ Total: 1 (MEDIUM: 0, HIGH: 1) scanners: types.Scanners{ types.VulnerabilityScanner, }, + tableModes: types.SupportedTableModes, results: types.Results{ { Target: "test", @@ -124,7 +126,82 @@ Legend: scanners: types.Scanners{ types.VulnerabilityScanner, }, - noSummaryTable: true, + tableModes: []types.TableMode{ + types.Detailed, + }, + results: types.Results{ + { + Target: "test", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, + }, + }, + wantOutput: ``, + }, + { + name: "no detailed", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + tableModes: []types.TableMode{ + types.Summary, + }, + results: types.Results{ + { + Target: "test", + Type: ftypes.Jar, + Class: types.ClassLangPkg, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, + Vulnerabilities: []types.DetectedVulnerability{ + { + VulnerabilityID: "CVE-2020-0001", + PkgName: "foo", + InstalledVersion: "1.2.3", + PrimaryURL: "https://avd.aquasec.com/nvd/cve-2020-0001", + Status: dbTypes.StatusWillNotFix, + PkgPath: "test.jar", + Vulnerability: dbTypes.Vulnerability{ + Title: "foobar", + Description: "baz", + Severity: "HIGH", + }, + }, + }, + }, + }, + wantOutput: ` +Report Summary + +┌──────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────┼──────┼─────────────────┤ +│ test.jar │ jar │ 1 │ +└──────────┴──────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + +`, + }, + { + name: "no tables", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + tableModes: []types.TableMode{}, results: types.Results{ { Target: "test", @@ -166,8 +243,8 @@ Legend: dbTypes.SeverityHigh, dbTypes.SeverityMedium, }, - Scanners: tc.scanners, - NoSummaryTable: tc.noSummaryTable, + Scanners: tc.scanners, + TableModes: tc.tableModes, }) _ = writer.Write(nil, types.Report{Results: tc.results}) assert.Equal(t, tc.wantOutput, tableWritten.String(), tc.name) diff --git a/pkg/report/writer.go b/pkg/report/writer.go index e99cf071e84b..6a7e83d4a093 100644 --- a/pkg/report/writer.go +++ b/pkg/report/writer.go @@ -55,7 +55,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e RenderCause: option.RenderCause, LicenseRiskThreshold: option.LicenseRiskThreshold, IgnoredLicenses: option.IgnoredLicenses, - NoSummaryTable: option.NoSummaryTable, + TableModes: option.TableModes, }) case types.FormatJSON: writer = &JSONWriter{ diff --git a/pkg/types/report.go b/pkg/types/report.go index 3ada68a942d8..3ba97171ca4c 100644 --- a/pkg/types/report.go +++ b/pkg/types/report.go @@ -105,6 +105,18 @@ var ( } ) +type TableMode string + +const ( + Summary TableMode = "summary" + Detailed TableMode = "detailed" +) + +var SupportedTableModes = []TableMode{ + Summary, + Detailed, +} + // Result holds a target and detected vulnerabilities type Result struct { Target string `json:"Target"` From 9d58ab2c69a8b0e9f79e2cf486adc02d94f51c6b Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:05:01 +0600 Subject: [PATCH 41/45] docs: remove not for `--table-mode` flag Co-authored-by: Teppei Fukuda --- docs/docs/configuration/reporting.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 7fd9f549e7b2..90068ed5b1ee 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -105,8 +105,7 @@ Trivy supports the following modes for `table` format: | [summary](#summary-table) | ✓[^1] | | [detailed](#detailed-tables) | ✓ | -!!! note - Use `--table-mode` flag to enable/disable table mode(s). +You can use `--table-mode` flag to enable/disable table mode(s). ##### Summary table From 5b6e9eaea9e1e418469222dda73487843f3dc2b9 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:05:26 +0600 Subject: [PATCH 42/45] docs: fix typo Co-authored-by: Teppei Fukuda --- docs/docs/configuration/reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 90068ed5b1ee..fc40c71a6065 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -100,7 +100,7 @@ Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 1, HIGH: 0, CRITICAL: 0) Trivy supports the following modes for `table` format: -| Mode | Enable by default | +| Mode | Enabled by default | |:----------------------------:|:-----------------:| | [summary](#summary-table) | ✓[^1] | | [detailed](#detailed-tables) | ✓ | From 71a53b25f4244b4ef6bdb2199551a367c2064b95 Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:05:46 +0600 Subject: [PATCH 43/45] docs: fix typo Co-authored-by: Teppei Fukuda --- docs/docs/configuration/reporting.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index fc40c71a6065..2f67f3401211 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -144,7 +144,7 @@ Nuances of table contents: ##### Detailed tables -Detailed tables contains information about found security issues for each target with more detailed information (CVE-id, severity, version, etc.). +Detailed tables contain information about found security issues for each target with more detailed information (CVE-ID, severity, version, etc.).
Detailed tables From df6c0de7b9b1df8fcb0f54c8ff77f7751f08251e Mon Sep 17 00:00:00 2001 From: DmitriyLewen <91113035+DmitriyLewen@users.noreply.github.com> Date: Tue, 4 Mar 2025 14:58:50 +0600 Subject: [PATCH 44/45] refactor: use Info log for secrets/licenses Co-authored-by: Teppei Fukuda --- pkg/report/table/summary.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index c876467cc25a..c1edfdf7cffa 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -225,7 +225,7 @@ func (r *summaryRenderer) showEmptyResultsWarning() { r.logger.Warn("Supported files for scanner(s) not found.", log.Any("scanners", scanners)) } if scanners := lo.Intersect(resultByFindings, r.scanners); len(scanners) > 0 { - r.logger.Warn("No issues detected with scanner(s).", log.Any("scanners", scanners)) + r.logger.Info("No issues detected with scanner(s).", log.Any("scanners", scanners)) } } From 014e4c46277fef3903da0d388d443d4a16a0d9d2 Mon Sep 17 00:00:00 2001 From: DmitriyLewen Date: Tue, 4 Mar 2025 15:08:43 +0600 Subject: [PATCH 45/45] refactor: use AlignCenter for empty summary table --- pkg/report/table/summary.go | 2 ++ pkg/report/table/summary_test.go | 4 ++-- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go index c1edfdf7cffa..c726605ae78e 100644 --- a/pkg/report/table/summary.go +++ b/pkg/report/table/summary.go @@ -190,6 +190,8 @@ func (r *summaryRenderer) Render(report types.Report) { if len(report.Results) == 0 { r.showEmptyResultsWarning() + alignments[0] = table.AlignCenter + t.SetAlignment(alignments...) t.AddRows(slices.Repeat([]string{"-"}, len(r.scanners)+2)) } diff --git a/pkg/report/table/summary_test.go b/pkg/report/table/summary_test.go index 601e3a9f76a4..341cde115240 100644 --- a/pkg/report/table/summary_test.go +++ b/pkg/report/table/summary_test.go @@ -299,7 +299,7 @@ Report Summary ┌────────┬──────┬─────────────────┬─────────┐ │ Target │ Type │ Vulnerabilities │ Secrets │ ├────────┼──────┼─────────────────┼─────────┤ -│ - │ - │ - │ - │ +│ - │ - │ - │ - │ └────────┴──────┴─────────────────┴─────────┘ Legend: - '-': Not scanned @@ -321,7 +321,7 @@ Report Summary ┌────────┬──────┬─────────────────┬─────────┬───────────────────┬──────────┐ │ Target │ Type │ Vulnerabilities │ Secrets │ Misconfigurations │ Licenses │ ├────────┼──────┼─────────────────┼─────────┼───────────────────┼──────────┤ -│ - │ - │ - │ - │ - │ - │ +│ - │ - │ - │ - │ - │ - │ └────────┴──────┴─────────────────┴─────────┴───────────────────┴──────────┘ Legend: - '-': Not scanned