diff --git a/docs/docs/configuration/reporting.md b/docs/docs/configuration/reporting.md index 8580f36039..30dd2bccdf 100644 --- a/docs/docs/configuration/reporting.md +++ b/docs/docs/configuration/reporting.md @@ -1,5 +1,6 @@ # Reporting +## Supported Formats Trivy supports the following formats: - Table @@ -8,7 +9,7 @@ Trivy supports the following formats: - Template - SBOM -## Table (Default) +### Table (Default) | Scanner | Supported | |:----------------:|:---------:| @@ -21,7 +22,7 @@ Trivy supports the following formats: $ trivy image -f table golang:1.12-alpine ``` -### Show origins of vulnerable dependencies +#### Show origins of vulnerable dependencies | Scanner | Supported | |:----------------:|:---------:| @@ -105,7 +106,7 @@ Also, **glob-parent@3.1.0** with some vulnerabilities is included through chain Then, you can try to update **axios@0.21.4** and **cra-append-sw@2.7.0** to resolve vulnerabilities in **follow-redirects@1.14.6** and **glob-parent@3.1.0**. -## JSON +### JSON | Scanner | Supported | |:----------------:|:---------:| @@ -239,7 +240,7 @@ $ trivy image -f json -o results.json golang:1.12-alpine `VulnerabilityID`, `PkgName`, `InstalledVersion`, and `Severity` in `Vulnerabilities` are always filled with values, but other fields might be empty. -## SARIF +### SARIF | Scanner | Supported | |:----------------:|:---------:| | Vulnerability | ✓ | @@ -255,7 +256,7 @@ $ trivy image --format sarif -o report.sarif golang:1.12-alpine This SARIF file can be uploaded to GitHub code scanning results, and there is a [Trivy GitHub Action][action] for automating this process. -## Template +### Template | Scanner | Supported | |:----------------:|:---------:| @@ -264,7 +265,7 @@ This SARIF file can be uploaded to GitHub code scanning results, and there is a | Secret | ✓ | | License | ✓ | -### Custom Template +#### Custom Template {% raw %} ``` @@ -301,18 +302,18 @@ Critical: 0, High: 2 For other features of sprig, see the official [sprig][sprig] documentation. -### Load templates from a file +#### Load templates from a file You can load templates from a file prefixing the template path with an @. ``` $ trivy image --format template --template "@/path/to/template" golang:1.12-alpine ``` -### Default Templates +#### Default Templates If Trivy is installed using rpm then default templates can be found at `/usr/local/share/trivy/templates`. -#### JUnit +##### JUnit | Scanner | Supported | |:----------------:|:---------:| | Vulnerability | ✓ | @@ -325,7 +326,7 @@ In the following example using the template `junit.tpl` XML can be generated. $ trivy image --format template --template "@contrib/junit.tpl" -o junit-report.xml golang:1.12-alpine ``` -#### ASFF +##### ASFF | Scanner | Supported | |:----------------:|:---------:| | Vulnerability | ✓ | @@ -335,7 +336,7 @@ $ trivy image --format template --template "@contrib/junit.tpl" -o junit-report. Trivy also supports an [ASFF template for reporting findings to AWS Security Hub][asff] -#### HTML +##### HTML | Scanner | Supported | |:----------------:|:---------:| | Vulnerability | ✓ | @@ -353,9 +354,34 @@ The following example shows use of default HTML template when Trivy is installed $ trivy image --format template --template "@/usr/local/share/trivy/templates/html.tpl" -o report.html golang:1.12-alpine ``` -## SBOM +### SBOM See [here](../supply-chain/sbom.md) for details. +## Converting +To generate multiple reports, you can generate the JSON report first and convert it to other formats with the `convert` subcommand. + +```shell +$ trivy image --format json -o result.json --list-all-pkgs debian:11 +$ 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, + 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`. + +```shell +# Output all severities in JSON +$ trivy image --format json -o result.json --list-all-pkgs debian:11 + +# Output only critical issues in table format +$ trivy convert --format table --severity CRITICAL result.json +``` + +!!! note + JSON reports from "trivy aws" and "trivy k8s" are not yet supported. + [cargo-auditable]: https://github.com/rust-secure-code/cargo-auditable/ [action]: https://github.com/aquasecurity/trivy-action [asff]: ../../tutorials/integrations/aws-security-hub.md diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md index eae2fb6db8..57a12c4d8e 100644 --- a/docs/docs/references/configuration/cli/trivy.md +++ b/docs/docs/references/configuration/cli/trivy.md @@ -45,6 +45,7 @@ trivy [global flags] command [flags] target * [trivy aws](trivy_aws.md) - [EXPERIMENTAL] Scan AWS account * [trivy config](trivy_config.md) - Scan config files for misconfigurations +* [trivy convert](trivy_convert.md) - Convert Trivy JSON report into a different format * [trivy filesystem](trivy_filesystem.md) - Scan local filesystem * [trivy image](trivy_image.md) - Scan a container image * [trivy kubernetes](trivy_kubernetes.md) - [EXPERIMENTAL] Scan kubernetes cluster diff --git a/docs/docs/references/configuration/cli/trivy_convert.md b/docs/docs/references/configuration/cli/trivy_convert.md new file mode 100644 index 0000000000..9266fcdb33 --- /dev/null +++ b/docs/docs/references/configuration/cli/trivy_convert.md @@ -0,0 +1,52 @@ +## trivy convert + +Convert Trivy JSON report into a different format + +``` +trivy convert [flags] RESULT_JSON +``` + +### Examples + +``` + # report conversion + $ trivy image --format json --output result.json --list-all-pkgs debian:11 + $ trivy convert --format cyclonedx --output result.cdx result.json + +``` + +### Options + +``` + --compliance string compliance report to generate + --dependency-tree [EXPERIMENTAL] show dependency origin tree of vulnerable packages + --exit-code int specify exit code when any security issues are found + --exit-on-eol int exit with the specified code when the OS reaches end of service/life + -f, --format string format (table, json, template, sarif, cyclonedx, spdx, spdx-json, github, cosign-vuln) (default "table") + -h, --help help for convert + --ignore-policy string specify the Rego file path to evaluate each vulnerability + --ignorefile string specify .trivyignore file (default ".trivyignore") + --list-all-pkgs enabling the option will output all packages regardless of vulnerability + -o, --output string output file name + --report string specify a report format for the output. (all,summary) (default "all") + -s, --severity string severities of security issues to be displayed (comma separated) (default "UNKNOWN,LOW,MEDIUM,HIGH,CRITICAL") + -t, --template string output template +``` + +### Options inherited from parent commands + +``` + --cache-dir string cache directory (default "/path/to/cache") + -c, --config string config path (default "trivy.yaml") + -d, --debug debug mode + --generate-default-config write the default config to trivy-default.yaml + --insecure allow insecure server connections + -q, --quiet suppress progress bar and log output + --timeout duration timeout (default 5m0s) + -v, --version show version +``` + +### SEE ALSO + +* [trivy](trivy.md) - Unified security scanner + diff --git a/docs/getting-started/faq.md b/docs/getting-started/faq.md index b4a6340fda..220d25c15c 100644 --- a/docs/getting-started/faq.md +++ b/docs/getting-started/faq.md @@ -3,3 +3,9 @@ ### How to pronounce the name "Trivy"? `tri` is pronounced like **tri**gger, `vy` is pronounced like en**vy**. + +### How to generate multiple reports? +See [here](../docs/configuration/reporting.md#converting). + +### How to run Trivy under air-gapped environment? +See [here](../docs/advanced/air-gap.md). \ No newline at end of file diff --git a/pkg/cloud/aws/commands/run.go b/pkg/cloud/aws/commands/run.go index 6f2fe218c5..12b3096608 100644 --- a/pkg/cloud/aws/commands/run.go +++ b/pkg/cloud/aws/commands/run.go @@ -15,7 +15,7 @@ import ( "github.com/aquasecurity/trivy/pkg/cloud" "github.com/aquasecurity/trivy/pkg/cloud/aws/scanner" "github.com/aquasecurity/trivy/pkg/cloud/report" - cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/commands/operation" cr "github.com/aquasecurity/trivy/pkg/compliance/report" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" @@ -147,6 +147,6 @@ func Run(ctx context.Context, opt flag.Options) error { return fmt.Errorf("unable to write results: %w", err) } - cmd.Exit(opt, r.Failed()) + operation.Exit(opt, r.Failed()) return nil } diff --git a/pkg/cloud/report/report.go b/pkg/cloud/report/report.go index 1319bb903f..66d37c7521 100644 --- a/pkg/cloud/report/report.go +++ b/pkg/cloud/report/report.go @@ -19,7 +19,7 @@ const ( tableFormat = "table" ) -// Report represents a kubernetes scan report +// Report represents an AWS scan report type Report struct { Provider string AccountID string diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 00c6556bf1..222bbe5604 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -19,6 +19,7 @@ import ( javadb "github.com/aquasecurity/trivy-java-db/pkg/db" awscommands "github.com/aquasecurity/trivy/pkg/cloud/aws/commands" "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/commands/convert" "github.com/aquasecurity/trivy/pkg/commands/server" "github.com/aquasecurity/trivy/pkg/fanal/analyzer" "github.com/aquasecurity/trivy/pkg/flag" @@ -106,6 +107,7 @@ func NewApp(version string) *cobra.Command { NewClientCommand(globalFlags), NewServerCommand(globalFlags), NewConfigCommand(globalFlags), + NewConvertCommand(globalFlags), NewPluginCommand(), NewModuleCommand(globalFlags), NewKubernetesCommand(globalFlags), @@ -494,6 +496,47 @@ func NewRepositoryCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return cmd } +func NewConvertCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { + convertFlags := &flag.Flags{ + ScanFlagGroup: &flag.ScanFlagGroup{}, + ReportFlagGroup: flag.NewReportFlagGroup(), + } + cmd := &cobra.Command{ + Use: "convert [flags] RESULT_JSON", + Aliases: []string{"conv"}, + GroupID: groupUtility, + Short: "Convert Trivy JSON report into a different format", + Example: ` # report conversion + $ trivy image --format json --output result.json --list-all-pkgs debian:11 + $ trivy convert --format cyclonedx --output result.cdx result.json +`, + PreRunE: func(cmd *cobra.Command, args []string) error { + if err := convertFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + return validateArgs(cmd, args) + }, + RunE: func(cmd *cobra.Command, args []string) error { + if err := convertFlags.Bind(cmd); err != nil { + return xerrors.Errorf("flag bind error: %w", err) + } + opts, err := convertFlags.ToOptions(cmd.Version, args, globalFlags, outputWriter) + if err != nil { + return xerrors.Errorf("flag error: %w", err) + } + + return convert.Run(cmd.Context(), opts) + }, + SilenceErrors: true, + SilenceUsage: true, + } + cmd.SetFlagErrorFunc(flagErrorFunc) + convertFlags.AddFlags(cmd) + cmd.SetUsageTemplate(fmt.Sprintf(usageTemplate, convertFlags.Usages(cmd))) + + return cmd +} + // NewClientCommand returns the 'client' subcommand that is deprecated func NewClientCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { remoteFlags := flag.NewClientFlags() @@ -799,7 +842,7 @@ func NewModuleCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { if err != nil { return xerrors.Errorf("flag error: %w", err) } - return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.Registry()) + return module.Install(cmd.Context(), opts.ModuleDir, repo, opts.Quiet, opts.RegistryOpts()) }, }, &cobra.Command{ diff --git a/pkg/commands/artifact/run.go b/pkg/commands/artifact/run.go index 71662a53ed..7f2ce3dd81 100644 --- a/pkg/commands/artifact/run.go +++ b/pkg/commands/artifact/run.go @@ -4,9 +4,6 @@ import ( "context" "errors" "fmt" - "os" - - "github.com/aquasecurity/trivy/pkg/policy" "github.com/hashicorp/go-multierror" "github.com/spf13/viper" @@ -26,6 +23,7 @@ import ( "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/misconf" "github.com/aquasecurity/trivy/pkg/module" + "github.com/aquasecurity/trivy/pkg/policy" pkgReport "github.com/aquasecurity/trivy/pkg/report" "github.com/aquasecurity/trivy/pkg/result" "github.com/aquasecurity/trivy/pkg/rpc/client" @@ -273,15 +271,7 @@ func (r *runner) scanArtifact(ctx context.Context, opts flag.Options, initialize func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Report) (types.Report, error) { // Filter results - err := result.Filter(ctx, report, result.FilterOption{ - Severities: opts.Severities, - IgnoreUnfixed: opts.IgnoreUnfixed, - IncludeNonFailures: opts.IncludeNonFailures, - IgnoreFile: opts.IgnoreFile, - PolicyFile: opts.IgnorePolicy, - IgnoreLicenses: opts.IgnoredLicenses, - VEXPath: opts.VEXPath, - }) + err := result.Filter(ctx, report, opts.FilterOpts()) if err != nil { return types.Report{}, xerrors.Errorf("filtering error: %w", err) } @@ -290,18 +280,7 @@ func (r *runner) Filter(ctx context.Context, opts flag.Options, report types.Rep } func (r *runner) Report(opts flag.Options, report types.Report) error { - if err := pkgReport.Write(report, pkgReport.Option{ - AppVersion: opts.AppVersion, - Format: opts.Format, - Output: opts.Output, - Tree: opts.DependencyTree, - Severities: opts.Severities, - OutputTemplate: opts.Template, - IncludeNonFailures: opts.IncludeNonFailures, - Trace: opts.Trace, - Report: opts.ReportFormat, - Compliance: opts.Compliance, - }); err != nil { + if err := pkgReport.Write(report, opts.ReportOpts()); err != nil { return xerrors.Errorf("unable to write results: %w", err) } @@ -320,7 +299,7 @@ func (r *runner) initDB(ctx context.Context, opts flag.Options) error { // download the database file noProgress := opts.Quiet || opts.NoProgress - if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.Registry()); err != nil { + if err := operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, noProgress, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { return err } @@ -475,8 +454,8 @@ func Run(ctx context.Context, opts flag.Options, targetKind TargetKind) (err err return xerrors.Errorf("report error: %w", err) } - exitOnEOL(opts, report.Metadata) - Exit(opts, report.Results.Failed()) + operation.ExitOnEOL(opts, report.Metadata) + operation.Exit(opts, report.Results.Failed()) return nil } @@ -661,7 +640,7 @@ func initScannerConfig(opts flag.Options, cacheClient cache.Cache) (ScannerConfi // For image scanning ImageOption: ftypes.ImageOptions{ - RegistryOptions: opts.Registry(), + RegistryOptions: opts.RegistryOpts(), DockerOptions: ftypes.DockerOptions{ Host: opts.DockerHost, }, @@ -704,19 +683,6 @@ func scan(ctx context.Context, opts flag.Options, initializeScanner InitializeSc return report, nil } -func Exit(opts flag.Options, failedResults bool) { - if opts.ExitCode != 0 && failedResults { - os.Exit(opts.ExitCode) - } -} - -func exitOnEOL(opts flag.Options, m types.Metadata) { - if opts.ExitOnEOL != 0 && m.OS != nil && m.OS.Eosl { - log.Logger.Errorf("Detected EOL OS: %s %s", m.OS.Family, m.OS.Name) - os.Exit(opts.ExitOnEOL) - } -} - func canonicalVersion(ver string) string { if ver == devVersion { return ver diff --git a/pkg/commands/convert/run.go b/pkg/commands/convert/run.go new file mode 100644 index 0000000000..a9879e0d17 --- /dev/null +++ b/pkg/commands/convert/run.go @@ -0,0 +1,48 @@ +package convert + +import ( + "context" + "encoding/json" + "os" + + "golang.org/x/xerrors" + + "github.com/aquasecurity/trivy/pkg/commands/operation" + "github.com/aquasecurity/trivy/pkg/flag" + "github.com/aquasecurity/trivy/pkg/log" + "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/result" + "github.com/aquasecurity/trivy/pkg/types" +) + +func Run(ctx context.Context, opts flag.Options) (err error) { + f, err := os.Open(opts.Target) + if err != nil { + return xerrors.Errorf("file open error: %w", err) + } + defer f.Close() + + var r types.Report + if err = json.NewDecoder(f).Decode(&r); err != nil { + return xerrors.Errorf("json decode error: %w", err) + } + + // "convert" supports JSON results produced by Trivy scanning other than AWS and Kubernetes + if r.ArtifactName == "" && r.ArtifactType == "" { + return xerrors.New("AWS and Kubernetes scanning reports are not yet supported") + } + + if err = result.Filter(ctx, r, opts.FilterOpts()); err != nil { + return xerrors.Errorf("unable to filter results: %w", err) + } + + log.Logger.Debug("Writing report to output...") + if err = report.Write(r, opts.ReportOpts()); err != nil { + return xerrors.Errorf("unable to write results: %w", err) + } + + operation.ExitOnEOL(opts, r.Metadata) + operation.Exit(opts, r.Results.Failed()) + + return nil +} diff --git a/pkg/commands/operation/operation.go b/pkg/commands/operation/operation.go index 597f458438..ed47d469d5 100644 --- a/pkg/commands/operation/operation.go +++ b/pkg/commands/operation/operation.go @@ -16,10 +16,11 @@ import ( "github.com/aquasecurity/trivy-db/pkg/metadata" "github.com/aquasecurity/trivy/pkg/db" "github.com/aquasecurity/trivy/pkg/fanal/cache" - "github.com/aquasecurity/trivy/pkg/fanal/types" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/policy" + "github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/utils/fsutils" ) @@ -109,7 +110,7 @@ func (c Cache) ClearArtifacts() error { } // DownloadDB downloads the DB -func DownloadDB(ctx context.Context, appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool, opt types.RegistryOptions) error { +func DownloadDB(ctx context.Context, appVersion, cacheDir, dbRepository string, quiet, skipUpdate bool, opt ftypes.RegistryOptions) error { mu.Lock() defer mu.Unlock() @@ -201,3 +202,16 @@ func GetTLSConfig(caCertPath, certPath, keyPath string) (*x509.CertPool, tls.Cer return caCertPool, cert, nil } + +func Exit(opts flag.Options, failedResults bool) { + if opts.ExitCode != 0 && failedResults { + os.Exit(opts.ExitCode) + } +} + +func ExitOnEOL(opts flag.Options, m types.Metadata) { + if opts.ExitOnEOL != 0 && m.OS != nil && m.OS.Eosl { + log.Logger.Errorf("Detected EOL OS: %s %s", m.OS.Family, m.OS.Name) + os.Exit(opts.ExitOnEOL) + } +} diff --git a/pkg/commands/server/run.go b/pkg/commands/server/run.go index 92418fa663..7ddb6f7b3a 100644 --- a/pkg/commands/server/run.go +++ b/pkg/commands/server/run.go @@ -35,7 +35,7 @@ func Run(ctx context.Context, opts flag.Options) (err error) { // download the database file if err = operation.DownloadDB(ctx, opts.AppVersion, opts.CacheDir, opts.DBRepository, - true, opts.SkipDBUpdate, opts.Registry()); err != nil { + true, opts.SkipDBUpdate, opts.RegistryOpts()); err != nil { return err } @@ -58,6 +58,6 @@ func Run(ctx context.Context, opts flag.Options) (err error) { m.Register() server := rpcServer.NewServer(opts.AppVersion, opts.Listen, opts.CacheDir, opts.Token, opts.TokenHeader, - opts.DBRepository, opts.Registry()) + opts.DBRepository, opts.RegistryOpts()) return server.ListenAndServe(cache, opts.SkipDBUpdate) } diff --git a/pkg/flag/options.go b/pkg/flag/options.go index b6a417c00b..d51c99480e 100644 --- a/pkg/flag/options.go +++ b/pkg/flag/options.go @@ -18,6 +18,7 @@ import ( ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/log" "github.com/aquasecurity/trivy/pkg/report" + "github.com/aquasecurity/trivy/pkg/result" ) type Flag struct { @@ -122,8 +123,8 @@ func (o *Options) Align() { } } -// Registry returns options for OCI registries -func (o *Options) Registry() ftypes.RegistryOptions { +// RegistryOpts returns options for OCI registries +func (o *Options) RegistryOpts() ftypes.RegistryOptions { return ftypes.RegistryOptions{ Credentials: o.Credentials, RegistryToken: o.RegistryToken, @@ -133,6 +134,34 @@ func (o *Options) Registry() ftypes.RegistryOptions { } } +// FilterOpts returns options for filtering +func (o *Options) FilterOpts() result.FilterOption { + return result.FilterOption{ + Severities: o.Severities, + IgnoreUnfixed: o.IgnoreUnfixed, + IncludeNonFailures: o.IncludeNonFailures, + IgnoreFile: o.IgnoreFile, + PolicyFile: o.IgnorePolicy, + IgnoreLicenses: o.IgnoredLicenses, + VEXPath: o.VEXPath, + } +} + +func (o *Options) ReportOpts() report.Option { + return report.Option{ + AppVersion: o.AppVersion, + Format: o.Format, + Output: o.Output, + Tree: o.DependencyTree, + Severities: o.Severities, + OutputTemplate: o.Template, + IncludeNonFailures: o.IncludeNonFailures, + Trace: o.Trace, + Report: o.ReportFormat, + Compliance: o.Compliance, + } +} + func addFlag(cmd *cobra.Command, flag *Flag) { if flag == nil || flag.Name == "" { return diff --git a/pkg/k8s/commands/run.go b/pkg/k8s/commands/run.go index 32ba0af291..a10a1b95c2 100644 --- a/pkg/k8s/commands/run.go +++ b/pkg/k8s/commands/run.go @@ -10,6 +10,7 @@ import ( "github.com/aquasecurity/trivy-kubernetes/pkg/artifacts" "github.com/aquasecurity/trivy-kubernetes/pkg/k8s" cmd "github.com/aquasecurity/trivy/pkg/commands/artifact" + "github.com/aquasecurity/trivy/pkg/commands/operation" cr "github.com/aquasecurity/trivy/pkg/compliance/report" "github.com/aquasecurity/trivy/pkg/flag" "github.com/aquasecurity/trivy/pkg/k8s/report" @@ -120,7 +121,7 @@ func (r *runner) run(ctx context.Context, artifacts []*artifacts.Artifact) error return xerrors.Errorf("unable to write results: %w", err) } - cmd.Exit(r.flagOpts, rpt.Failed()) + operation.Exit(r.flagOpts, rpt.Failed()) return nil } diff --git a/pkg/sbom/spdx/marshal.go b/pkg/sbom/spdx/marshal.go index 2764ff57ac..8c11ffc4b4 100644 --- a/pkg/sbom/spdx/marshal.go +++ b/pkg/sbom/spdx/marshal.go @@ -139,6 +139,9 @@ func (m *Marshaler) Marshal(r types.Report) (*spdx.Document, error) { ) for _, result := range r.Results { + if len(result.Packages) == 0 { + continue + } parentPackage, err := m.resultToSpdxPackage(result, r.Metadata.OS, pkgDownloadLocation) if err != nil { return nil, xerrors.Errorf("failed to parse result: %w", err) diff --git a/pkg/sbom/spdx/marshal_test.go b/pkg/sbom/spdx/marshal_test.go index 6c56d0bc65..7af6ba75e4 100644 --- a/pkg/sbom/spdx/marshal_test.go +++ b/pkg/sbom/spdx/marshal_test.go @@ -761,6 +761,69 @@ func TestMarshaler_Marshal(t *testing.T) { }, }, }, + { + name: "happy path secret", + inputReport: types.Report{ + SchemaVersion: report.SchemaVersion, + ArtifactName: "secret", + ArtifactType: ftypes.ArtifactFilesystem, + Results: types.Results{ + { + Target: "key.pem", + Class: types.ClassSecret, + Secrets: []ftypes.SecretFinding{ + { + RuleID: "private-key", + Category: "AsymmetricPrivateKey", + Severity: "HIGH", + Title: "Asymmetric Private Key", + StartLine: 1, + EndLine: 1, + }, + }, + }, + }, + }, + wantSBOM: &spdx.Document{ + SPDXVersion: spdx.Version, + DataLicense: spdx.DataLicense, + SPDXIdentifier: "DOCUMENT", + DocumentName: "secret", + DocumentNamespace: "http://aquasecurity.github.io/trivy/filesystem/secret-3ff14136-e09f-4df9-80ea-000000000001", + + CreationInfo: &spdx.CreationInfo{ + Creators: []common.Creator{ + { + Creator: "aquasecurity", + CreatorType: "Organization", + }, + { + Creator: fmt.Sprintf("trivy-0.38.1"), + CreatorType: "Tool", + }, + }, + Created: "2021-08-25T12:20:30Z", + }, + Packages: []*spdx.Package{ + { + PackageName: "secret", + PackageSPDXIdentifier: "Filesystem-5c08d34162a2c5d3", + PackageDownloadLocation: "NONE", + PackageAttributionTexts: []string{ + "SchemaVersion: 2", + }, + PrimaryPackagePurpose: tspdx.PackagePurposeSource, + }, + }, + Relationships: []*spdx.Relationship{ + { + RefA: spdx.DocElementID{ElementRefID: "DOCUMENT"}, + RefB: spdx.DocElementID{ElementRefID: "Filesystem-5c08d34162a2c5d3"}, + Relationship: "DESCRIBES", + }, + }, + }, + }, } clock := fake.NewFakeClock(time.Date(2021, 8, 25, 12, 20, 30, 5, time.UTC))