diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 5a4a9df540b7..2f67f3401211 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -19,10 +19,153 @@ Trivy supports the following formats: | Secret | ✓ | | License | ✓ | +```bash +$ trivy image -f table golang:1.22.11-alpine3.20 ``` -$ trivy image -f table golang:1.12-alpine + +
+Result + +``` +... + +Report Summary + +┌─────────────────────────────────────────────┬──────────┬─────────────────┬─────────┐ +│ Target │ Type │ Vulnerabilities │ Secrets │ +├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ +│ golang:1.22.11-alpine3.20 (alpine 3.20.5) │ alpine │ 6 │ - │ +├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ +│ usr/local/go/bin/go │ gobinary │ 1 │ - │ +├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ +... +├─────────────────────────────────────────────┼──────────┼─────────────────┼─────────┤ +│ 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 │ +└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘ + +... +``` + +
+ +#### Table mode +!!! warning "EXPERIMENTAL" + This feature might change without preserving backwards compatibility. + +Trivy supports the following modes for `table` format: + +| Mode | Enabled by default | +|:----------------------------:|:-----------------:| +| [summary](#summary-table) | ✓[^1] | +| [detailed](#detailed-tables) | ✓ | + +You can 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 + +``` +┌───────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ +│ 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 │ +└───────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ ``` +
+ +##### Detailed tables +Detailed tables contain information about found security issues for each target with more detailed information (CVE-ID, severity, version, etc.). + +
+Detailed tables + +``` + +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 │ +└─────────┴────────────────┴──────────┴────────┴───────────────────┴──────────────────────────────┴──────────────────────────────────────────────────────────────┘ + +``` +
+ #### Show origins of vulnerable dependencies | Scanner | Supported | @@ -474,7 +617,7 @@ $ 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. [Filtering options](./filtering.md) such as `--severity` are also available with `convert`. @@ -518,3 +661,5 @@ $ trivy convert --format table --severity CRITICAL result.json [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 9915d15283a7..2d977a4f1d18 100644 --- a/docs/docs/references/configuration/cli/trivy_config.md +++ b/docs/docs/references/configuration/cli/trivy_config.md @@ -51,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 d76d303e3b03..f396e92a8fdd 100644 --- a/docs/docs/references/configuration/cli/trivy_convert.md +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -30,8 +30,10 @@ 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) -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 154b7ddea2ea..9e99c441f268 100644 --- a/docs/docs/references/configuration/cli/trivy_filesystem.md +++ b/docs/docs/references/configuration/cli/trivy_filesystem.md @@ -92,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 6cf60b526208..4e90a2e19471 100644 --- a/docs/docs/references/configuration/cli/trivy_image.md +++ b/docs/docs/references/configuration/cli/trivy_image.md @@ -114,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 98e54bb98c72..4265b790cd74 100644 --- a/docs/docs/references/configuration/cli/trivy_repository.md +++ b/docs/docs/references/configuration/cli/trivy_repository.md @@ -90,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 2ab6043da4d1..4607a25f56a7 100644 --- a/docs/docs/references/configuration/cli/trivy_rootfs.md +++ b/docs/docs/references/configuration/cli/trivy_rootfs.md @@ -93,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 454f8f5d8cfe..42df969bfefd 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -68,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 2aef1a8864ac..49010ac26e85 100644 --- a/docs/docs/references/configuration/cli/trivy_vm.md +++ b/docs/docs/references/configuration/cli/trivy_vm.md @@ -81,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 b246a687da11..6a2cd1e47dea 100644 --- a/docs/docs/references/configuration/config-file.md +++ b/docs/docs/references/configuration/config-file.md @@ -553,6 +553,11 @@ severity: - HIGH - CRITICAL +# Same as '--table-mode' +table-mode: + - summary + - detailed + # Same as '--template' template: "" diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden index 44cf87a6c095..ad5fc250401e 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": "Java", "Class": "license", @@ -30,6 +26,7 @@ "PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus", "FilePath": "", "Name": "EPL-1.0", + "Text": "", "Confidence": 1, "Link": "" }, @@ -39,6 +36,7 @@ "PkgName": "org.ow2.asm:asm", "FilePath": "", "Name": "BSD-3-Clause", + "Text": "", "Confidence": 1, "Link": "" }, @@ -48,18 +46,11 @@ "PkgName": "org.slf4j:slf4j-api", "FilePath": "", "Name": "MIT License", + "Text": "", "Confidence": 1, "Link": "" } ] - }, - { - "Target": "pom.xml", - "Class": "license" - }, - { - "Target": "Loose File License(s)", - "Class": "license-file" } ] } diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 7bedce99ff65..86e3428e5925 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.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 584bc6f6bddc..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" @@ -18,6 +20,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 +45,14 @@ 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 && 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...") if err = report.Write(ctx, r, opts); err != nil { return xerrors.Errorf("unable to write results: %w", err) } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index 0b7772a091f3..cd8ffc8f7777 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/flag/report_flags.go b/pkg/flag/report_flags.go index d69443e89547..90ac077abcee 100644 --- a/pkg/flag/report_flags.go +++ b/pkg/flag/report_flags.go @@ -109,6 +109,13 @@ var ( ConfigName: "scan.show-suppressed", Usage: "[EXPERIMENTAL] show suppressed vulnerabilities", } + 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", + } ) // ReportFlagGroup composes common printer flag structs @@ -128,6 +135,7 @@ type ReportFlagGroup struct { Severity *Flag[[]string] Compliance *Flag[string] ShowSuppressed *Flag[bool] + TableMode *Flag[[]string] } type ReportOptions struct { @@ -145,6 +153,7 @@ type ReportOptions struct { Severities []dbTypes.Severity Compliance spec.ComplianceSpec ShowSuppressed bool + TableModes []types.TableMode } func NewReportFlagGroup() *ReportFlagGroup { @@ -163,6 +172,7 @@ func NewReportFlagGroup() *ReportFlagGroup { Severity: SeverityFlag.Clone(), Compliance: ComplianceFlag.Clone(), ShowSuppressed: ShowSuppressedFlag.Clone(), + TableMode: TableModeFlag.Clone(), } } @@ -186,6 +196,7 @@ func (f *ReportFlagGroup) Flags() []Flagger { f.Severity, f.Compliance, f.ShowSuppressed, + f.TableMode, } } @@ -198,6 +209,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { template := f.Template.Value() dependencyTree := f.DependencyTree.Value() listAllPkgs := f.ListAllPkgs.Value() + tableModes := f.TableMode.Value() if template != "" { if format == "" { @@ -227,6 +239,11 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { } } + // "--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()) if err != nil { return ReportOptions{}, xerrors.Errorf("unable to load compliance spec: %w", err) @@ -259,6 +276,7 @@ func (f *ReportFlagGroup) ToOptions() (ReportOptions, error) { Severities: toSeverity(f.Severity.Value()), Compliance: cs, ShowSuppressed: f.ShowSuppressed.Value(), + TableModes: xstrings.ToTSlice[types.TableMode](tableModes), }, nil } diff --git a/pkg/flag/report_flags_test.go b/pkg/flag/report_flags_test.go index ab4baa53fbff..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,11 +33,13 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { compliance string debug bool pkgTypes string + tableModes []string } tests := []struct { name string fields fields want flag.ReportOptions + wantErr string wantLogs []string }{ { @@ -160,6 +163,14 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { Severities: []dbTypes.Severity{dbTypes.SeverityLow}, }, }, + { + name: "invalid option combination: --table-modes with --format json", + fields: fields{ + format: "json", + tableModes: xstrings.ToStringSlice(types.SupportedTableModes), + }, + wantErr: `"--table-mode" can be used only with "--format table".`, + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { @@ -184,6 +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) + setSliceValue(flag.TableModeFlag.ConfigName, tt.fields.tableModes) // Assert options f := &flag.ReportFlagGroup{ @@ -199,10 +211,15 @@ func TestReportFlagGroup_ToOptions(t *testing.T) { OutputPluginArg: flag.OutputPluginArgFlag.Clone(), Severity: flag.SeverityFlag.Clone(), Compliance: flag.ComplianceFlag.Clone(), + TableMode: flag.TableModeFlag.Clone(), } 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 diff --git a/pkg/report/table/summary.go b/pkg/report/table/summary.go new file mode 100644 index 000000000000..c726605ae78e --- /dev/null +++ b/pkg/report/table/summary.go @@ -0,0 +1,305 @@ +package table + +import ( + "bytes" + "fmt" + "slices" + "sort" + + "github.com/fatih/color" + "github.com/samber/lo" + + "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" +) + +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 + + String() string // Required to show correct logs +} + +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 +} + +func (s VulnerabilityScanner) String() string { + return string(types.VulnerabilityScanner) +} + +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 +} + +func (s MisconfigScanner) String() string { + return string(types.MisconfigScanner) +} + +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 +} + +func (s SecretScanner) String() string { + return string(types.SecretScanner) +} + +type LicenseScanner struct{ scannerAlignment } + +func (s LicenseScanner) Header() string { + return "Licenses" +} + +func (s LicenseScanner) Count(result types.Result) int { + if result.Class == types.ClassLicense || result.Class == types.ClassLicenseFile { + return len(result.Licenses) + } + return -1 +} + +func (s LicenseScanner) String() string { + return string(types.LicenseScanner) +} + +type summaryRenderer struct { + w *bytes.Buffer + isTerminal bool + scanners []Scanner + logger *log.Logger +} + +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) + if lo.IsNil(s) { + continue + } + ss = append(ss, s) + } + + return &summaryRenderer{ + w: buf, + isTerminal: isTerminal, + scanners: ss, + logger: log.WithPrefix("report"), + } +} + +func (r *summaryRenderer) Render(report types.Report) { + if len(r.scanners) == 0 { + r.logger.Warn("No enabled scanners found. Summary table will not be displayed.") + return + } + + 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(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(report.Results) == 0 { + r.showEmptyResultsWarning() + alignments[0] = table.AlignCenter + t.SetAlignment(alignments...) + 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 +} + +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. +func (r *summaryRenderer) showEmptyResultsWarning() { + resultByFiles := []Scanner{ + NewScanner(types.VulnerabilityScanner), + NewScanner(types.MisconfigScanner), + } + resultByFindings := []Scanner{ + NewScanner(types.SecretScanner), + NewScanner(types.LicenseScanner), + } + + if scanners := lo.Intersect(resultByFiles, r.scanners); len(scanners) > 0 { + 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.Info("No issues detected with scanner(s).", log.Any("scanners", scanners)) + } +} + +// 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/summary_test.go b/pkg/report/table/summary_test.go new file mode 100644 index 000000000000..341cde115240 --- /dev/null +++ b/pkg/report/table/summary_test.go @@ -0,0 +1,342 @@ +package table_test + +import ( + "bytes" + "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" +) + +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", + }, + }, + } + jarVulns = types.Result{ + 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", + 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/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, + 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", + 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", + }, + }, + } + + 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: "requirements.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, + } + + 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, + Licenses: []types.DetectedLicense{ + { + FilePath: "LICENSE", + }, + }, + } +) + +func Test_renderSummary(t *testing.T) { + tests := []struct { + name string + scanners types.Scanners + 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, + jarVulns, + npmVulns, + dockerfileMisconfig, + secret, + osLicense, + jarLicense, + npmLicenses, + fileLicense, + }, + }, + want: ` +Report Summary + +┌───────────────────────────────────┬────────────┬─────────────────┬───────────────────┬─────────┬──────────┐ +│ Target │ Type │ Vulnerabilities │ Misconfigurations │ Secrets │ Licenses │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ 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 │ - │ - │ - │ +├───────────────────────────────────┼────────────┼─────────────────┼───────────────────┼─────────┼──────────┤ +│ 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 │ +└───────────────────────────────────┴────────────┴─────────────────┴───────────────────┴─────────┴──────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + +`, + }, + { + name: "happy path vuln scanner only", + scanners: []types.Scanner{ + types.VulnerabilityScanner, + }, + report: types.Report{ + Results: []types.Result{ + osVuln, + jarVulns, + }, + }, + want: ` +Report Summary + +┌───────────────────────────────────┬────────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├───────────────────────────────────┼────────┼─────────────────┤ +│ 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 │ +└───────────────────────────────────┴────────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + +`, + }, + { + 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 │ Secrets │ +├──────────────────┼──────┼─────────────────┼─────────┤ +│ requirements.txt │ pip │ 0 │ - │ +├──────────────────┼──────┼─────────────────┼─────────┤ +│ requirements.txt │ text │ - │ 1 │ +└──────────────────┴──────┴─────────────────┴─────────┘ +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) + +`, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + 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 751f86f6cccc..8f0d0902ff46 100644 --- a/pkg/report/table/table.go +++ b/pkg/report/table/table.go @@ -34,6 +34,7 @@ type Writer struct { // Use one buffer for all renderers buf *bytes.Buffer + summaryRenderer *summaryRenderer vulnerabilityRenderer Renderer misconfigRenderer Renderer secretRenderer Renderer @@ -44,6 +45,7 @@ type Writer struct { } type Options struct { + Scanners types.Scanners Severities []dbTypes.Severity Output io.Writer @@ -53,6 +55,9 @@ type Options struct { // Show suppressed findings ShowSuppressed bool + // Show/hide summary/detailed tables + TableModes []types.TableMode + // For misconfigurations IncludeNonFailures bool Trace bool @@ -65,13 +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), + buf: buf, + + 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, } } @@ -82,12 +90,18 @@ type Renderer interface { // Write writes the result on standard output func (tw *Writer) Write(_ context.Context, report types.Report) error { - 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.Summary) { + tw.summaryRenderer.Render(report) + } + + 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 98456fb95930..42934ccdcb58 100644 --- a/pkg/report/table/table_test.go +++ b/pkg/report/table/table_test.go @@ -5,7 +5,6 @@ import ( "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" @@ -16,16 +15,30 @@ import ( func TestWriter_Write(t *testing.T) { testCases := []struct { name string + scanners types.Scanners + tableModes []types.TableMode results types.Results - expectedOutput string + wantOutput string includeNonFailures bool }{ { name: "vulnerability and custom resource", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + tableModes: types.SupportedTableModes, 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", @@ -33,6 +46,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", @@ -48,28 +62,172 @@ func TestWriter_Write(t *testing.T) { }, }, }, - expectedOutput: ` -test () -======= + wantOutput: ` +Report Summary + +┌──────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────┼──────┼─────────────────┤ +│ test.jar │ jar │ 1 │ +└──────────┴──────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + + +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 │ +└────────────────┴───────────────┴──────────┴──────────────┴───────────────────┴───────────────┴───────────────────────────────────────────┘ `, }, { name: "no vulns", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + tableModes: types.SupportedTableModes, + results: types.Results{ + { + Target: "test", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, + }, + }, + wantOutput: ` +Report Summary + +┌──────────┬──────┬─────────────────┐ +│ Target │ Type │ Vulnerabilities │ +├──────────┼──────┼─────────────────┤ +│ test.jar │ jar │ 0 │ +└──────────┴──────┴─────────────────┘ +Legend: +- '-': Not scanned +- '0': Clean (no security findings detected) + +`, + }, + { + name: "no summary", + scanners: types.Scanners{ + types.VulnerabilityScanner, + }, + 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", + Class: types.ClassLangPkg, + Type: ftypes.Jar, + Packages: []ftypes.Package{ + { + Name: "foo", + Version: "1.2.3", + FilePath: "test.jar", + }, + }, + }, + }, + wantOutput: ``, + }, + { + name: "no scanners", results: types.Results{ { Target: "test", Class: types.ClassLangPkg, + Type: ftypes.Jar, }, }, - expectedOutput: ``, + wantOutput: ``, }, } @@ -85,10 +243,11 @@ Total: 1 (MEDIUM: 0, HIGH: 1) dbTypes.SeverityHigh, dbTypes.SeverityMedium, }, + Scanners: tc.scanners, + TableModes: tc.tableModes, }) - err := writer.Write(nil, types.Report{Results: tc.results}) - require.NoError(t, err) - assert.Equal(t, tc.expectedOutput, tableWritten.String(), tc.name) + _ = writer.Write(nil, types.Report{Results: tc.results}) + assert.Equal(t, tc.wantOutput, tableWritten.String(), tc.name) }) } } diff --git a/pkg/report/table/vulnerability.go b/pkg/report/table/vulnerability.go index c19aa0d103a2..548f4871e0b5 100644 --- a/pkg/report/table/vulnerability.go +++ b/pkg/report/table/vulnerability.go @@ -71,9 +71,8 @@ func NewVulnerabilityRenderer(buf *bytes.Buffer, isTerminal, tree, suppressed bo func (r *vulnerabilityRenderer) Render(result types.Result) { // There are 3 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(result.Vulnerabilities) > 0 || result.Class == types.ClassOSPkg || (r.showSuppressed && len(result.ModifiedFindings) > 0) { + if len(result.Vulnerabilities) > 0 || (r.showSuppressed && len(result.ModifiedFindings) > 0) { r.renderDetectedVulnerabilities(result) if r.tree { @@ -136,8 +135,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) @@ -370,6 +367,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, "/") diff --git a/pkg/report/table/vulnerability_test.go b/pkg/report/table/vulnerability_test.go index 5ac1310282d9..57f50b01b3f6 100644 --- a/pkg/report/table/vulnerability_test.go +++ b/pkg/report/table/vulnerability_test.go @@ -426,12 +426,7 @@ Suppressed Vulnerabilities (Total: 1) }, }, showSuppressed: false, - want: ` -test -==== -Total: 0 (MEDIUM: 0, HIGH: 0) - -`, + want: "", }, { name: "suppressed all language package vulnerabilities without `showSuppressed` flag", diff --git a/pkg/report/writer.go b/pkg/report/writer.go index 12caeb74ff85..6a7e83d4a093 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.NewWriter(table.Options{ + Scanners: option.Scanners, Output: output, Severities: option.Severities, Tree: option.DependencyTree, @@ -54,6 +55,7 @@ func Write(ctx context.Context, report types.Report, option flag.Options) (err e RenderCause: option.RenderCause, LicenseRiskThreshold: option.LicenseRiskThreshold, IgnoredLicenses: option.IgnoredLicenses, + TableModes: option.TableModes, }) case types.FormatJSON: writer = &JSONWriter{ diff --git a/pkg/rpc/client/client.go b/pkg/rpc/client/client.go index 328d880e0779..3c964c51a88e 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, VulnSeveritySources: xstrings.ToStringSlice(opts.VulnSeveritySources), diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 39d5a7615474..4dcfcf02114b 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -96,6 +96,7 @@ func (s *ScanServer) ToOptions(in *rpcScanner.ScanOptions) types.ScanOptions { Scanners: scanners, IncludeDevDeps: in.IncludeDevDeps, LicenseCategories: licenseCategories, + LicenseFull: in.LicenseFull, Distro: distro, VulnSeveritySources: vulnSeveritySources, } diff --git a/pkg/scanner/local/scan.go b/pkg/scanner/local/scan.go index 124587487d0d..35fe90f4a7df 100644 --- a/pkg/scanner/local/scan.go +++ b/pkg/scanner/local/scan.go @@ -261,21 +261,44 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions var results types.Results scanner := licensing.NewScanner(options.LicenseCategories) - // License - OS packages - var osPkgLicenses []types.DetectedLicense - for _, pkg := range target.Packages { + // 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 { - osPkgLicenses = append(osPkgLicenses, toDetectedLicense(scanner, license, pkg.Name, "")) + licenses = append(licenses, toDetectedLicense(scanner, license, pkg.Name, "")) } } - results = append(results, types.Result{ + return &types.Result{ Target: "OS Packages", Class: types.ClassLicense, - Licenses: osPkgLicenses, - }) + Licenses: licenses, + } +} - // License - language-specific packages - for _, app := range target.Applications { +func (s Scanner) scanApplicationLicenses(apps []ftypes.Application, scanner licensing.Scanner) []types.Result { + var results []types.Result + + for _, app := range apps { var langLicenses []types.DetectedLicense for _, lib := range app.Packages { for _, license := range lib.Licenses { @@ -292,19 +315,29 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions // 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, + }) + } + } - // License - file header or license file - var fileLicenses []types.DetectedLicense - for _, license := range target.Licenses { + 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) - fileLicenses = append(fileLicenses, types.DetectedLicense{ + detectedLicenses = append(detectedLicenses, types.DetectedLicense{ Severity: severity, Category: category, FilePath: license.FilePath, @@ -312,16 +345,14 @@ func (s Scanner) scanLicenses(target types.ScanTarget, options types.ScanOptions Confidence: finding.Confidence, Link: finding.Link, }) - } } - results = append(results, types.Result{ + + return &types.Result{ Target: "Loose File License(s)", Class: types.ClassLicenseFile, - Licenses: fileLicenses, - }) - - return results + Licenses: detectedLicenses, + } } func toDetectedMisconfiguration(res ftypes.MisconfResult, defaultSeverity dbTypes.Severity, diff --git a/pkg/scanner/local/scan_test.go b/pkg/scanner/local/scan_test.go index 34cdbf00c754..ea01fe1eecb6 100644 --- a/pkg/scanner/local/scan_test.go +++ b/pkg/scanner/local/scan_test.go @@ -344,6 +344,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/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"` diff --git a/pkg/types/scan.go b/pkg/types/scan.go index 1d1586082b4e..6dec84d74e4e 100644 --- a/pkg/types/scan.go +++ b/pkg/types/scan.go @@ -112,7 +112,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 @@ -120,6 +120,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 3927282ad880..89464829ec7b 100644 --- a/rpc/scanner/service.pb.go +++ b/rpc/scanner/service.pb.go @@ -153,6 +153,7 @@ type ScanOptions struct { 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"` VulnSeveritySources []string `protobuf:"bytes,8,rep,name=vuln_severity_sources,json=vulnSeveritySources,proto3" json:"vuln_severity_sources,omitempty"` + LicenseFull bool `protobuf:"varint,9,opt,name=license_full,json=licenseFull,proto3" json:"license_full,omitempty"` } func (x *ScanOptions) Reset() { @@ -236,6 +237,13 @@ func (x *ScanOptions) GetVulnSeveritySources() []string { return nil } +func (x *ScanOptions) GetLicenseFull() bool { + if x != nil { + return x.LicenseFull + } + return false +} + type ScanResponse struct { state protoimpl.MessageState sizeCache protoimpl.SizeCache @@ -422,7 +430,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, 0xc8, 0x03, 0x0a, 0x0b, 0x53, 0x63, 0x61, 0x6e, 0x4f, + 0x05, 0x6e, 0x61, 0x6d, 0x65, 0x73, 0x22, 0xeb, 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, @@ -444,58 +452,61 @@ var file_rpc_scanner_service_proto_rawDesc = []byte{ 0x69, 0x73, 0x74, 0x72, 0x6f, 0x12, 0x32, 0x0a, 0x15, 0x76, 0x75, 0x6c, 0x6e, 0x5f, 0x73, 0x65, 0x76, 0x65, 0x72, 0x69, 0x74, 0x79, 0x5f, 0x73, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x18, 0x08, 0x20, 0x03, 0x28, 0x09, 0x52, 0x13, 0x76, 0x75, 0x6c, 0x6e, 0x53, 0x65, 0x76, 0x65, 0x72, 0x69, - 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 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, + 0x74, 0x79, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x73, 0x12, 0x21, 0x0a, 0x0c, 0x6c, 0x69, 0x63, + 0x65, 0x6e, 0x73, 0x65, 0x5f, 0x66, 0x75, 0x6c, 0x6c, 0x18, 0x09, 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 3602875473c7..49dfe497cd46 100644 --- a/rpc/scanner/service.proto +++ b/rpc/scanner/service.proto @@ -30,6 +30,7 @@ message ScanOptions { repeated string pkg_relationships = 6; common.OS distro = 7; repeated string vuln_severity_sources = 8; + bool license_full = 9; reserved 3; // deleted 'list_all_packages' } diff --git a/rpc/scanner/service.twirp.go b/rpc/scanner/service.twirp.go index d763d6a0804d..d49372d0dd88 100644 --- a/rpc/scanner/service.twirp.go +++ b/rpc/scanner/service.twirp.go @@ -1094,50 +1094,51 @@ func callClientError(ctx context.Context, h *twirp.ClientHooks, err twirp.Error) } var twirpFileDescriptor0 = []byte{ - // 710 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0xcd, 0x6e, 0xe3, 0x36, - 0x10, 0x86, 0x2d, 0xc7, 0x96, 0xc7, 0x45, 0xe3, 0xb0, 0x4d, 0xa0, 0x38, 0x4d, 0x6b, 0xf8, 0x50, - 0x18, 0x28, 0x60, 0x37, 0x4e, 0x8b, 0xfe, 0xdd, 0x9a, 0xa4, 0x45, 0x8a, 0x16, 0x09, 0xe8, 0xa0, - 0x87, 0x5e, 0x54, 0x9a, 0x9a, 0x28, 0x84, 0x65, 0x49, 0x21, 0x29, 0x01, 0x7e, 0x95, 0x7d, 0xa2, - 0x7d, 0x81, 0x7d, 0x9f, 0x05, 0x49, 0xc9, 0x88, 0xed, 0x64, 0x4f, 0xd2, 0xcc, 0xf7, 0xcd, 0xcc, - 0x47, 0xf2, 0x23, 0xe1, 0x54, 0xe6, 0x7c, 0xaa, 0x38, 0x4b, 0x53, 0x94, 0x53, 0x85, 0xb2, 0x14, - 0x1c, 0x27, 0xb9, 0xcc, 0x74, 0x46, 0xfa, 0x5a, 0x8a, 0x72, 0x3d, 0xa9, 0xc0, 0x49, 0x79, 0x31, - 0x08, 0x0c, 0x99, 0x67, 0xab, 0x55, 0x96, 0x6e, 0x73, 0x47, 0xef, 0x1a, 0xd0, 0x9b, 0x73, 0x96, - 0x52, 0x7c, 0x2e, 0x50, 0x69, 0x72, 0x02, 0x6d, 0xcd, 0x64, 0x8c, 0x3a, 0x68, 0x0c, 0x1b, 0xe3, - 0x2e, 0xad, 0x22, 0xf2, 0x0d, 0xf4, 0x98, 0xd4, 0xe2, 0x91, 0x71, 0x1d, 0x8a, 0x28, 0x68, 0x5a, - 0x10, 0xea, 0xd4, 0x6d, 0x44, 0x4e, 0xc1, 0x5f, 0x24, 0xd9, 0x22, 0x14, 0x91, 0x0a, 0xbc, 0xa1, - 0x37, 0xee, 0xd2, 0x8e, 0x89, 0x6f, 0x23, 0x45, 0x7e, 0x82, 0x4e, 0x96, 0x6b, 0x91, 0xa5, 0x2a, - 0x68, 0x0d, 0x1b, 0xe3, 0xde, 0xec, 0x7c, 0xb2, 0xab, 0x70, 0x62, 0x34, 0xdc, 0x39, 0x12, 0xad, - 0xd9, 0xa3, 0x21, 0xf8, 0x7f, 0x0b, 0x8e, 0xa9, 0x42, 0x45, 0xbe, 0x84, 0x83, 0x94, 0xad, 0x50, - 0x05, 0x0d, 0xdb, 0xdc, 0x05, 0xa3, 0xf7, 0x9e, 0x93, 0x5f, 0x95, 0x92, 0x33, 0xe8, 0xe6, 0xcb, - 0x38, 0xd4, 0xeb, 0x7c, 0xc3, 0xf4, 0xf3, 0x65, 0xfc, 0x60, 0x62, 0x32, 0x00, 0xbf, 0x9a, 0xa8, - 0x82, 0xa6, 0xc3, 0xea, 0x98, 0x70, 0x20, 0x89, 0x1b, 0x15, 0x72, 0xa6, 0x31, 0xce, 0xa4, 0x40, - 0x23, 0xd7, 0x1b, 0xf7, 0x66, 0x3f, 0x7c, 0x52, 0xee, 0xa4, 0x92, 0x78, 0xb5, 0x29, 0xbb, 0x49, - 0xb5, 0x5c, 0xd3, 0xa3, 0x64, 0x37, 0x4f, 0xc6, 0xd0, 0x17, 0x29, 0x4f, 0x8a, 0x08, 0xc3, 0x08, - 0xcb, 0x30, 0xc2, 0x5c, 0x05, 0x07, 0xc3, 0xc6, 0xd8, 0xa7, 0x9f, 0x57, 0xf9, 0x6b, 0x2c, 0xaf, - 0x31, 0x57, 0xe4, 0x3b, 0x38, 0x32, 0xeb, 0x90, 0x98, 0x30, 0x3b, 0xe4, 0x49, 0xe4, 0x2a, 0x68, - 0x5b, 0xcd, 0xfd, 0x7c, 0x19, 0xd3, 0x97, 0x79, 0x32, 0x86, 0x76, 0x24, 0x94, 0x96, 0x59, 0xd0, - 0xb1, 0xdb, 0xdb, 0xaf, 0xf4, 0xba, 0x03, 0x9f, 0xdc, 0xcd, 0x69, 0x85, 0x93, 0x19, 0x1c, 0x97, - 0x45, 0x92, 0x86, 0x0a, 0x4b, 0x94, 0x42, 0xaf, 0x43, 0x95, 0x15, 0x92, 0xa3, 0x0a, 0x7c, 0xdb, - 0xfa, 0x0b, 0x03, 0xce, 0x2b, 0x6c, 0xee, 0xa0, 0xc1, 0xff, 0x70, 0xf2, 0xfa, 0x0a, 0x49, 0x1f, - 0xbc, 0x25, 0xae, 0x2b, 0xa3, 0x98, 0x5f, 0xf2, 0x3d, 0x1c, 0x94, 0x2c, 0x29, 0xd0, 0xfa, 0xa3, - 0x37, 0x1b, 0xec, 0x6f, 0x5c, 0x7d, 0x9e, 0xd4, 0x11, 0x7f, 0x6d, 0xfe, 0xdc, 0xf8, 0xab, 0xe5, - 0x7b, 0xfd, 0xd6, 0x28, 0x82, 0xcf, 0x9c, 0x11, 0x55, 0x9e, 0xa5, 0x0a, 0xc9, 0x10, 0x9a, 0x99, - 0xb2, 0xcd, 0x5f, 0x5b, 0x51, 0x33, 0x53, 0x64, 0x06, 0x1d, 0x89, 0xaa, 0x48, 0xb4, 0x73, 0x5c, - 0x6f, 0x16, 0xec, 0xcf, 0xa3, 0x96, 0x40, 0x6b, 0xe2, 0xe8, 0x83, 0x07, 0x6d, 0x97, 0x7b, 0xd3, - 0xea, 0x37, 0x70, 0x68, 0xf6, 0x01, 0x25, 0x5b, 0x88, 0x44, 0x68, 0xe3, 0x83, 0xa6, 0x6d, 0x7f, - 0xb6, 0xad, 0xe2, 0xdf, 0x17, 0xa4, 0x35, 0xdd, 0xad, 0x21, 0x0f, 0x70, 0xb4, 0x12, 0x8a, 0x67, - 0xe9, 0xa3, 0x88, 0x0b, 0xc9, 0x6a, 0xff, 0x9b, 0x46, 0xdf, 0x6e, 0x37, 0xba, 0x46, 0x8d, 0x5c, - 0x63, 0xf4, 0xcf, 0x0e, 0x9d, 0xee, 0x37, 0x30, 0xd7, 0x80, 0x27, 0x4c, 0x19, 0x33, 0x18, 0xcd, - 0x2e, 0x20, 0x04, 0x5a, 0xc6, 0xf2, 0x81, 0x67, 0x93, 0xf6, 0x9f, 0x5c, 0x80, 0x9f, 0x33, 0xbe, - 0x64, 0x31, 0x1a, 0x93, 0x99, 0xb1, 0xc7, 0xdb, 0x63, 0xef, 0x1d, 0x4a, 0x37, 0x34, 0xf2, 0x27, - 0xf4, 0x79, 0xa1, 0x74, 0xb6, 0x0a, 0x25, 0xd6, 0xce, 0xe8, 0xd8, 0xd2, 0xaf, 0xb6, 0x4b, 0xaf, - 0x2c, 0x8b, 0x56, 0x24, 0x7a, 0xc8, 0xb7, 0x62, 0x45, 0x7e, 0x84, 0x8e, 0x42, 0x2e, 0x51, 0x3b, - 0x67, 0xed, 0x6d, 0xdd, 0xdc, 0x82, 0x7f, 0x88, 0x34, 0x12, 0x69, 0x4c, 0x6b, 0x2e, 0xf9, 0x05, - 0xfc, 0xea, 0xd2, 0xa8, 0xa0, 0x6b, 0xeb, 0xce, 0x5f, 0xdf, 0xa9, 0xca, 0x45, 0x74, 0x43, 0x9f, - 0xdd, 0x43, 0x67, 0xee, 0x4e, 0x9d, 0xdc, 0x40, 0xcb, 0xfc, 0x92, 0x37, 0x5e, 0x99, 0xea, 0xa5, - 0x1b, 0x7c, 0xfd, 0x16, 0xec, 0xfc, 0xf7, 0xfb, 0xe5, 0x7f, 0x17, 0xb1, 0xd0, 0x4f, 0xc5, 0xc2, - 0x0c, 0x9f, 0xb2, 0xe7, 0x82, 0x29, 0xe4, 0x85, 0xb9, 0x19, 0x53, 0x5b, 0x38, 0x7d, 0xf1, 0x00, - 0xff, 0x56, 0x7d, 0x17, 0x6d, 0xfb, 0xaa, 0x5e, 0x7e, 0x0c, 0x00, 0x00, 0xff, 0xff, 0xce, 0x3a, - 0x39, 0x26, 0x9e, 0x05, 0x00, 0x00, + // 729 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x7c, 0x54, 0x5f, 0x6f, 0xe3, 0x44, + 0x10, 0x57, 0xe2, 0x34, 0x71, 0x26, 0x27, 0x2e, 0x5d, 0xb8, 0x93, 0x2f, 0xc7, 0x41, 0xc8, 0x03, + 0x8a, 0x84, 0x94, 0xd0, 0x1c, 0x88, 0x7f, 0x6f, 0x5c, 0x5b, 0x54, 0x04, 0x6a, 0xb5, 0xa9, 0x78, + 0xe0, 0xc5, 0x6c, 0xd6, 0x53, 0x77, 0x15, 0xc7, 0x76, 0x77, 0xd7, 0x96, 0xf2, 0x55, 0xf8, 0x5e, + 0x7c, 0x0a, 0xbe, 0x04, 0xda, 0x3f, 0x8e, 0x9a, 0xa4, 0xe5, 0xc9, 0x3b, 0x33, 0xbf, 0x99, 0xf9, + 0x79, 0xf6, 0x37, 0x0b, 0x6f, 0x64, 0xc9, 0xe7, 0x8a, 0xb3, 0x3c, 0x47, 0x39, 0x57, 0x28, 0x6b, + 0xc1, 0x71, 0x56, 0xca, 0x42, 0x17, 0x64, 0xa8, 0xa5, 0xa8, 0xb7, 0x33, 0x1f, 0x9c, 0xd5, 0x67, + 0xa3, 0xc8, 0x80, 0x79, 0xb1, 0xd9, 0x14, 0xf9, 0x3e, 0x76, 0xf2, 0x77, 0x0b, 0x06, 0x4b, 0xce, + 0x72, 0x8a, 0x0f, 0x15, 0x2a, 0x4d, 0x5e, 0x43, 0x57, 0x33, 0x99, 0xa2, 0x8e, 0x5a, 0xe3, 0xd6, + 0xb4, 0x4f, 0xbd, 0x45, 0x3e, 0x87, 0x01, 0x93, 0x5a, 0xdc, 0x31, 0xae, 0x63, 0x91, 0x44, 0x6d, + 0x1b, 0x84, 0xc6, 0x75, 0x95, 0x90, 0x37, 0x10, 0xae, 0xb2, 0x62, 0x15, 0x8b, 0x44, 0x45, 0xc1, + 0x38, 0x98, 0xf6, 0x69, 0xcf, 0xd8, 0x57, 0x89, 0x22, 0xdf, 0x41, 0xaf, 0x28, 0xb5, 0x28, 0x72, + 0x15, 0x75, 0xc6, 0xad, 0xe9, 0x60, 0xf1, 0x6e, 0x76, 0xc8, 0x70, 0x66, 0x38, 0x5c, 0x3b, 0x10, + 0x6d, 0xd0, 0x93, 0x31, 0x84, 0xbf, 0x09, 0x8e, 0xb9, 0x42, 0x45, 0x3e, 0x81, 0x93, 0x9c, 0x6d, + 0x50, 0x45, 0x2d, 0x5b, 0xdc, 0x19, 0x93, 0x7f, 0x03, 0x47, 0xdf, 0xa7, 0x92, 0xb7, 0xd0, 0x2f, + 0xd7, 0x69, 0xac, 0xb7, 0xe5, 0x0e, 0x19, 0x96, 0xeb, 0xf4, 0xd6, 0xd8, 0x64, 0x04, 0xa1, 0xef, + 0xa8, 0xa2, 0xb6, 0x8b, 0x35, 0x36, 0xe1, 0x40, 0x32, 0xd7, 0x2a, 0xe6, 0x4c, 0x63, 0x5a, 0x48, + 0x81, 0x86, 0x6e, 0x30, 0x1d, 0x2c, 0xbe, 0xf9, 0x5f, 0xba, 0x33, 0x4f, 0xf1, 0xc3, 0x2e, 0xed, + 0x22, 0xd7, 0x72, 0x4b, 0x4f, 0xb3, 0x43, 0x3f, 0x99, 0xc2, 0x50, 0xe4, 0x3c, 0xab, 0x12, 0x8c, + 0x13, 0xac, 0xe3, 0x04, 0x4b, 0x15, 0x9d, 0x8c, 0x5b, 0xd3, 0x90, 0x7e, 0xe4, 0xfd, 0xe7, 0x58, + 0x9f, 0x63, 0xa9, 0xc8, 0x57, 0x70, 0x6a, 0xfe, 0x43, 0x62, 0xc6, 0x6c, 0x93, 0x7b, 0x51, 0xaa, + 0xa8, 0x6b, 0x39, 0x0f, 0xcb, 0x75, 0x4a, 0x1f, 0xfb, 0xc9, 0x14, 0xba, 0x89, 0x50, 0x5a, 0x16, + 0x51, 0xcf, 0x8e, 0x77, 0xe8, 0xf9, 0xba, 0x0b, 0x9f, 0x5d, 0x2f, 0xa9, 0x8f, 0x93, 0x05, 0xbc, + 0xaa, 0xab, 0x2c, 0x8f, 0x15, 0xd6, 0x28, 0x85, 0xde, 0xc6, 0xaa, 0xa8, 0x24, 0x47, 0x15, 0x85, + 0xb6, 0xf4, 0xc7, 0x26, 0xb8, 0xf4, 0xb1, 0xa5, 0x0b, 0x91, 0x2f, 0xe0, 0x45, 0x33, 0x99, 0xbb, + 0x2a, 0xcb, 0xa2, 0xbe, 0x25, 0x3c, 0xf0, 0xbe, 0xcb, 0x2a, 0xcb, 0x46, 0x7f, 0xc1, 0xeb, 0xa7, + 0x87, 0x40, 0x86, 0x10, 0xac, 0x71, 0xeb, 0xb5, 0x64, 0x8e, 0xe4, 0x6b, 0x38, 0xa9, 0x59, 0x56, + 0xa1, 0x95, 0xd0, 0x60, 0x31, 0x3a, 0x9e, 0x6d, 0x73, 0xe5, 0xd4, 0x01, 0x7f, 0x6c, 0x7f, 0xdf, + 0xfa, 0xb5, 0x13, 0x06, 0xc3, 0xce, 0x24, 0x81, 0x17, 0x4e, 0xab, 0xaa, 0x2c, 0x72, 0x85, 0x64, + 0x0c, 0xed, 0x42, 0xd9, 0xe2, 0x4f, 0xfd, 0x74, 0xbb, 0x50, 0x64, 0x01, 0x3d, 0x89, 0xaa, 0xca, + 0xb4, 0x13, 0xe5, 0x60, 0x11, 0x1d, 0xf7, 0xa3, 0x16, 0x40, 0x1b, 0xe0, 0xe4, 0x9f, 0x00, 0xba, + 0xce, 0xf7, 0xec, 0x36, 0x5c, 0xc0, 0x4b, 0x33, 0x2a, 0x94, 0x6c, 0x25, 0x32, 0xa1, 0x8d, 0x54, + 0xda, 0xb6, 0xfc, 0xdb, 0x7d, 0x16, 0x7f, 0x3c, 0x02, 0x6d, 0xe9, 0x61, 0x0e, 0xb9, 0x85, 0xd3, + 0x8d, 0x50, 0xbc, 0xc8, 0xef, 0x44, 0x5a, 0x49, 0xd6, 0xac, 0x88, 0x29, 0xf4, 0xe5, 0x7e, 0xa1, + 0x73, 0xd4, 0xc8, 0x35, 0x26, 0xbf, 0x1f, 0xc0, 0xe9, 0x71, 0x01, 0xb3, 0x29, 0x3c, 0x63, 0xca, + 0xe8, 0xc5, 0x70, 0x76, 0x06, 0x21, 0xd0, 0x31, 0x5b, 0x11, 0x05, 0xd6, 0x69, 0xcf, 0xe4, 0x0c, + 0xc2, 0x92, 0xf1, 0x35, 0x4b, 0xd1, 0xe8, 0xd0, 0xb4, 0x7d, 0xb5, 0xdf, 0xf6, 0xc6, 0x45, 0xe9, + 0x0e, 0x46, 0x7e, 0x81, 0x21, 0xaf, 0x94, 0x2e, 0x36, 0xb1, 0xc4, 0x46, 0x3c, 0x3d, 0x9b, 0xfa, + 0xe9, 0x7e, 0xea, 0x07, 0x8b, 0xa2, 0x1e, 0x44, 0x5f, 0xf2, 0x3d, 0x5b, 0x91, 0x6f, 0xa1, 0xa7, + 0x90, 0x4b, 0xd4, 0x4e, 0x7c, 0x47, 0xa3, 0x5b, 0xda, 0xe0, 0xa5, 0xc8, 0x13, 0x91, 0xa7, 0xb4, + 0xc1, 0x92, 0x1f, 0x20, 0xf4, 0xca, 0x53, 0x51, 0xdf, 0xe6, 0xbd, 0x7b, 0x7a, 0x52, 0x5e, 0x45, + 0x74, 0x07, 0x5f, 0xdc, 0x40, 0x6f, 0xe9, 0x6e, 0x9d, 0x5c, 0x40, 0xc7, 0x1c, 0xc9, 0x33, 0x0f, + 0x91, 0x7f, 0x0c, 0x47, 0x9f, 0x3d, 0x17, 0x76, 0xfa, 0xfb, 0xf9, 0xfd, 0x9f, 0x67, 0xa9, 0xd0, + 0xf7, 0xd5, 0xca, 0x34, 0x9f, 0xb3, 0x87, 0x8a, 0x29, 0xe4, 0x95, 0x59, 0x9e, 0xb9, 0x4d, 0x9c, + 0x3f, 0x7a, 0xa3, 0x7f, 0xf2, 0xdf, 0x55, 0xd7, 0x3e, 0xbc, 0xef, 0xff, 0x0b, 0x00, 0x00, 0xff, + 0xff, 0xe0, 0xa9, 0x3c, 0x91, 0xc1, 0x05, 0x00, 0x00, }