diff --git a/docs/docs/references/configuration/cli/trivy.md b/docs/docs/references/configuration/cli/trivy.md index f11635a25992..f3c543a210f9 100644 --- a/docs/docs/references/configuration/cli/trivy.md +++ b/docs/docs/references/configuration/cli/trivy.md @@ -53,7 +53,7 @@ trivy [global flags] command [flags] target * [trivy plugin](trivy_plugin.md) - Manage plugins * [trivy repository](trivy_repository.md) - Scan a repository * [trivy rootfs](trivy_rootfs.md) - Scan rootfs -* [trivy sbom](trivy_sbom.md) - Scan SBOM for vulnerabilities +* [trivy sbom](trivy_sbom.md) - Scan SBOM for vulnerabilities and licenses * [trivy server](trivy_server.md) - Server mode * [trivy version](trivy_version.md) - Print the version * [trivy vm](trivy_vm.md) - [EXPERIMENTAL] Scan a virtual machine image diff --git a/docs/docs/references/configuration/cli/trivy_sbom.md b/docs/docs/references/configuration/cli/trivy_sbom.md index f30144c34e9d..5d941e9744ba 100644 --- a/docs/docs/references/configuration/cli/trivy_sbom.md +++ b/docs/docs/references/configuration/cli/trivy_sbom.md @@ -1,6 +1,6 @@ ## trivy sbom -Scan SBOM for vulnerabilities +Scan SBOM for vulnerabilities and licenses ``` trivy sbom [flags] SBOM_PATH @@ -36,6 +36,7 @@ trivy sbom [flags] SBOM_PATH --ignore-policy string specify the Rego file path to evaluate each vulnerability --ignore-status strings comma-separated list of vulnerability status to ignore (unknown,not_affected,affected,fixed,under_investigation,will_not_fix,fix_deferred,end_of_life) --ignore-unfixed display only fixed vulnerabilities + --ignored-licenses strings specify a list of license to ignore --ignorefile string specify .trivyignore file (default ".trivyignore") --java-db-repository string OCI repository to retrieve trivy-java-db from (default "ghcr.io/aquasecurity/trivy-java-db:1") --list-all-pkgs enabling the option will output all packages regardless of vulnerability @@ -50,6 +51,7 @@ trivy sbom [flags] SBOM_PATH --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev") --reset remove all caches and database --sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) + --scanners strings comma-separated list of what security issues to detect (vuln,license) (default [vuln]) --server string server address in client mode -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 diff --git a/docs/docs/scanner/license.md b/docs/docs/scanner/license.md index dad487965481..6033542e4bea 100644 --- a/docs/docs/scanner/license.md +++ b/docs/docs/scanner/license.md @@ -30,10 +30,10 @@ To configure the confidence level, you can use `--license-confidence-level`. Thi Currently, the standard license scanning doesn't support filesystem and repository scanning. -| License scanning | Image | Rootfs | Filesystem | Repository | -| :-------------------: | :---: | :----: | :--------: | :--------: | -| Standard | ✅ | ✅ | - | - | -| Full (--license-full) | ✅ | ✅ | ✅ | ✅ | +| License scanning | Image | Rootfs | Filesystem | Repository | SBOM | +|:---------------------:|:-----:|:------:|:----------:|:----------:|:----:| +| Standard | ✅ | ✅ | - | - | ✅ | +| Full (--license-full) | ✅ | ✅ | ✅ | ✅ | - | License checking classifies the identified licenses and map the classification to severity. diff --git a/docs/docs/target/sbom.md b/docs/docs/target/sbom.md index a287455fc68a..4ea50035df1c 100644 --- a/docs/docs/target/sbom.md +++ b/docs/docs/target/sbom.md @@ -1,6 +1,6 @@ # SBOM scanning -Trivy can take the following SBOM formats as an input and scan for vulnerabilities. +Trivy can take the following SBOM formats as an input and scan for vulnerabilities and licenses. - CycloneDX - SPDX @@ -17,6 +17,9 @@ $ trivy sbom /path/to/sbom_file ``` +By default, vulnerability scan in SBOM is executed. You can use `--scanners vuln,license` +command property to select also license scan, or `--scanners license` alone. + !!! note Passing SBOMs generated by tool other than Trivy may result in inaccurate detection because Trivy relies on custom properties in SBOM for accurate scanning. diff --git a/integration/sbom_test.go b/integration/sbom_test.go index dc18cb43bceb..65c99f9e9600 100644 --- a/integration/sbom_test.go +++ b/integration/sbom_test.go @@ -6,11 +6,11 @@ import ( "path/filepath" "testing" + ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" v1 "github.com/google/go-containerregistry/pkg/v1" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" - ftypes "github.com/aquasecurity/trivy/pkg/fanal/types" "github.com/aquasecurity/trivy/pkg/types" ) @@ -19,6 +19,7 @@ func TestSBOM(t *testing.T) { input string format string artifactType string + scanners string } tests := []struct { name string @@ -150,6 +151,16 @@ func TestSBOM(t *testing.T) { }, }, }, + { + name: "license check cyclonedx json", + args: args{ + input: "testdata/fixtures/sbom/license-cyclonedx.json", + format: "json", + artifactType: "cyclonedx", + scanners: "license", + }, + golden: "testdata/license-cyclonedx.json.golden", + }, } // Set up testing DB @@ -157,6 +168,11 @@ func TestSBOM(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { + scanners := "vuln" + if tt.args.scanners != "" { + scanners = tt.args.scanners + } + osArgs := []string{ "--cache-dir", cacheDir, @@ -165,6 +181,8 @@ func TestSBOM(t *testing.T) { "--skip-db-update", "--format", tt.args.format, + "--scanners", + scanners, } // Set up the output file @@ -223,5 +241,10 @@ func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant typ } got := readReport(t, gotFile) + // when running on Windows FS + got.ArtifactName = filepath.ToSlash(filepath.Clean(got.ArtifactName)) + for i, result := range got.Results { + got.Results[i].Target = filepath.ToSlash(filepath.Clean(result.Target)) + } assert.Equal(t, want, got) } diff --git a/integration/testdata/fixtures/sbom/license-cyclonedx.json b/integration/testdata/fixtures/sbom/license-cyclonedx.json new file mode 100644 index 000000000000..e8353ca609cc --- /dev/null +++ b/integration/testdata/fixtures/sbom/license-cyclonedx.json @@ -0,0 +1,125 @@ +{ + "$schema": "http://cyclonedx.org/schema/bom-1.5.schema.json", + "bomFormat": "CycloneDX", + "specVersion": "1.5", + "serialNumber": "urn:uuid:c09512e3-47e7-4eff-8f76-5d7ae72b26a5", + "version": 1, + "metadata": { + "timestamp": "2024-03-10T14:57:31+00:00", + "tools": { + "components": [ + { + "type": "application", + "group": "aquasecurity", + "name": "trivy", + "version": "dev" + } + ] + }, + "component": { + "bom-ref": "acc9d4aa-4158-4969-a497-637e114fde0c", + "type": "application", + "name": "C:/Users/bedla.czech/IdeaProjects/sbom-demo", + "properties": [ + { + "name": "aquasecurity:trivy:SchemaVersion", + "value": "2" + } + ] + } + }, + "components": [ + { + "bom-ref": "eb56cd49-da98-4b08-bfc8-9880fb063cf1", + "type": "application", + "name": "pom.xml", + "properties": [ + { + "name": "aquasecurity:trivy:Class", + "value": "lang-pkgs" + }, + { + "name": "aquasecurity:trivy:Type", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/org.eclipse.sisu/org.eclipse.sisu.plexus@0.3.0.M1", + "type": "library", + "group": "org.eclipse.sisu", + "name": "org.eclipse.sisu.plexus", + "version": "0.3.0.M1", + "licenses": [ + { + "license": { + "name": "EPL-1.0" + } + } + ], + "purl": "pkg:maven/org.eclipse.sisu/org.eclipse.sisu.plexus@0.3.0.M1", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.0.M1" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/org.ow2.asm/asm@9.5", + "type": "library", + "group": "org.ow2.asm", + "name": "asm", + "version": "9.5", + "licenses": [ + { + "license": { + "name": "BSD-3-Clause" + } + } + ], + "purl": "pkg:maven/org.ow2.asm/asm@9.5", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "org.ow2.asm:asm:9.5" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + }, + { + "bom-ref": "pkg:maven/org.slf4j/slf4j-api@2.0.11", + "type": "library", + "group": "org.slf4j", + "name": "slf4j-api", + "version": "2.0.11", + "licenses": [ + { + "license": { + "name": "MIT License" + } + } + ], + "purl": "pkg:maven/org.slf4j/slf4j-api@2.0.11", + "properties": [ + { + "name": "aquasecurity:trivy:PkgID", + "value": "org.slf4j:slf4j-api:2.0.11" + }, + { + "name": "aquasecurity:trivy:PkgType", + "value": "pom" + } + ] + } + ], + "dependencies": [], + "vulnerabilities": [] +} diff --git a/integration/testdata/license-cyclonedx.json.golden b/integration/testdata/license-cyclonedx.json.golden new file mode 100644 index 000000000000..cf69da9756ed --- /dev/null +++ b/integration/testdata/license-cyclonedx.json.golden @@ -0,0 +1,65 @@ +{ + "SchemaVersion": 2, + "CreatedAt": "2021-08-25T12:20:30.000000005Z", + "ArtifactName": "testdata/fixtures/sbom/license-cyclonedx.json", + "ArtifactType": "cyclonedx", + "Metadata": { + "ImageConfig": { + "architecture": "", + "created": "0001-01-01T00:00:00Z", + "os": "", + "rootfs": { + "type": "", + "diff_ids": null + }, + "config": {} + } + }, + "Results": [ + { + "Target": "OS Packages", + "Class": "license" + }, + { + "Target": "pom.xml", + "Class": "license" + }, + { + "Target": "Java", + "Class": "license", + "Licenses": [ + { + "Severity": "MEDIUM", + "Category": "reciprocal", + "PkgName": "org.eclipse.sisu:org.eclipse.sisu.plexus", + "FilePath": "", + "Name": "EPL-1.0", + "Confidence": 1, + "Link": "" + }, + { + "Severity": "LOW", + "Category": "notice", + "PkgName": "org.ow2.asm:asm", + "FilePath": "", + "Name": "BSD-3-Clause", + "Confidence": 1, + "Link": "" + }, + { + "Severity": "UNKNOWN", + "Category": "unknown", + "PkgName": "org.slf4j:slf4j-api", + "FilePath": "", + "Name": "MIT License", + "Confidence": 1, + "Link": "" + } + ] + }, + { + "Target": "Loose File License(s)", + "Class": "license-file" + } + ] +} diff --git a/pkg/commands/app.go b/pkg/commands/app.go index 069c9f8b71cd..41d1d2ff645d 100644 --- a/pkg/commands/app.go +++ b/pkg/commands/app.go @@ -1125,11 +1125,24 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' reportFlagGroup.ReportFormat = nil // TODO: support --report summary + scanners := flag.ScannersFlag.Clone() + scanners.Values = xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + types.LicenseScanner, + }) + scanners.Default = xstrings.ToStringSlice(types.Scanners{ + types.VulnerabilityScanner, + }) scanFlagGroup := flag.NewScanFlagGroup() - scanFlagGroup.Scanners = nil // disable '--scanners' as it always scans for vulnerabilities + scanFlagGroup.Scanners = scanners // allow only 'vuln' and 'license' options for '--scanners' scanFlagGroup.IncludeDevDeps = nil // disable '--include-dev-deps' scanFlagGroup.Parallel = nil // disable '--parallel' + licenseFlagGroup := flag.NewLicenseFlagGroup() + // License full-scan and confidence-level are for file content only + licenseFlagGroup.LicenseFull = nil + licenseFlagGroup.LicenseConfidenceLevel = nil + sbomFlags := &flag.Flags{ GlobalFlagGroup: globalFlags, CacheFlagGroup: flag.NewCacheFlagGroup(), @@ -1139,11 +1152,12 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { ScanFlagGroup: scanFlagGroup, SBOMFlagGroup: flag.NewSBOMFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), + LicenseFlagGroup: licenseFlagGroup, } cmd := &cobra.Command{ Use: "sbom [flags] SBOM_PATH", - Short: "Scan SBOM for vulnerabilities", + Short: "Scan SBOM for vulnerabilities and licenses", GroupID: groupScanning, Example: ` # Scan CycloneDX and show the result in tables $ trivy sbom /path/to/report.cdx @@ -1166,9 +1180,6 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command { return xerrors.Errorf("flag error: %w", err) } - // Scan vulnerabilities - options.Scanners = types.Scanners{types.VulnerabilityScanner} - return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) }, SilenceErrors: true, diff --git a/pkg/licensing/normalize.go b/pkg/licensing/normalize.go index 942d388a3f52..0493747a9430 100644 --- a/pkg/licensing/normalize.go +++ b/pkg/licensing/normalize.go @@ -67,15 +67,93 @@ var mapping = map[string]string{ "MPL 2": MPL20, // BSD - "BSD": BSD3Clause, // 2? 3? - "BSD-2-CLAUSE": BSD2Clause, - "BSD-3-CLAUSE": BSD3Clause, - "BSD-4-CLAUSE": BSD4Clause, - - "APACHE": Apache20, // 1? 2? - "APACHE 2.0": Apache20, - "RUBY": Ruby, - "ZLIB": Zlib, + "BSD": BSD3Clause, // 2? 3? + "BSD-2-CLAUSE": BSD2Clause, + "BSD-3-CLAUSE": BSD3Clause, + "BSD-4-CLAUSE": BSD4Clause, + "BSD 2 CLAUSE": BSD2Clause, + "BSD 2-CLAUSE": BSD2Clause, + "BSD 2-CLAUSE LICENSE": BSD2Clause, + "THE BSD 2-CLAUSE LICENSE": BSD2Clause, + "THE 2-CLAUSE BSD LICENSE": BSD2Clause, + "TWO-CLAUSE BSD-STYLE LICENSE": BSD2Clause, + "BSD 3 CLAUSE": BSD3Clause, + "BSD 3-CLAUSE": BSD3Clause, + "BSD 3-CLAUSE LICENSE": BSD3Clause, + "THE BSD 3-CLAUSE LICENSE": BSD3Clause, + "BSD 3-CLAUSE \"NEW\" OR \"REVISED\" LICENSE (BSD-3-CLAUSE)": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE (NEW BSD LICENSE)": BSD3Clause, + "NEW BSD LICENSE": BSD3Clause, + "MODIFIED BSD LICENSE": BSD3Clause, + "REVISED BSD": BSD3Clause, + "REVISED BSD LICENSE": BSD3Clause, + "THE NEW BSD LICENSE": BSD3Clause, + "3-CLAUSE BSD LICENSE": BSD3Clause, + "BSD 3-CLAUSE NEW LICENSE": BSD3Clause, + "BSD LICENSE": BSD3Clause, + "EDL 1.0": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE - V 1.0": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE V. 1.0": BSD3Clause, + "ECLIPSE DISTRIBUTION LICENSE V1.0": BSD3Clause, + "THE BSD LICENSE": BSD4Clause, + + // APACHE + "APACHE LICENSE": Apache10, + "APACHE SOFTWARE LICENSES": Apache10, + "APACHE": Apache20, // 1? 2? + "APACHE 2.0": Apache20, + "APACHE 2": Apache20, + "APACHE V2": Apache20, + "APACHE 2.0 LICENSE": Apache20, + "APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, + "THE APACHE SOFTWARE LICENSE, VERSION 2.0": Apache20, + "APACHE LICENSE (V2.0)": Apache20, + "APACHE LICENSE 2.0": Apache20, + "APACHE LICENSE V2.0": Apache20, + "APACHE LICENSE VERSION 2.0": Apache20, + "APACHE LICENSE, VERSION 2.0": Apache20, + "APACHE PUBLIC LICENSE 2.0": Apache20, + "APACHE SOFTWARE LICENSE - VERSION 2.0": Apache20, + "THE APACHE LICENSE, VERSION 2.0": Apache20, + "APACHE-2.0 LICENSE": Apache20, + "APACHE 2 STYLE LICENSE": Apache20, + "ASF 2.0": Apache20, + + // CC0-1.0 + "CC0 1.0 UNIVERSAL": CC010, + "PUBLIC DOMAIN, PER CREATIVE COMMONS CC0": CC010, + + // CDDL 1.0 + "CDDL 1.0": CDDL10, + "CDDL LICENSE": CDDL10, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.0": CDDL10, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.0": CDDL10, + + // CDDL 1.1 + "CDDL 1.1": CDDL11, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) VERSION 1.1": CDDL11, + "COMMON DEVELOPMENT AND DISTRIBUTION LICENSE (CDDL) V1.1": CDDL11, + + // EPL 1.0 + "ECLIPSE PUBLIC LICENSE - VERSION 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE (EPL) 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE V1.0": EPL10, + "ECLIPSE PUBLIC LICENSE, VERSION 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE - V 1.0": EPL10, + "ECLIPSE PUBLIC LICENSE - V1.0": EPL10, + "ECLIPSE PUBLIC LICENSE (EPL), VERSION 1.0": EPL10, + + // EPL 2.0 + "ECLIPSE PUBLIC LICENSE - VERSION 2.0": EPL20, + "EPL 2.0": EPL20, + "ECLIPSE PUBLIC LICENSE - V 2.0": EPL20, + "ECLIPSE PUBLIC LICENSE V2.0": EPL20, + "ECLIPSE PUBLIC LICENSE, VERSION 2.0": EPL20, + "THE ECLIPSE PUBLIC LICENSE VERSION 2.0": EPL20, + "ECLIPSE PUBLIC LICENSE V. 2.0": EPL20, + + "RUBY": Ruby, + "ZLIB": Zlib, // Public Domain "PUBLIC DOMAIN": Unlicense,