feat(sbom): Support license detection for SBOM scan (#6072)

Co-authored-by: DmitriyLewen <dmitriy.lewen@smartforce.io>
This commit is contained in:
Ivo Šmíd
2024-03-18 10:34:26 +01:00
committed by GitHub
parent ab74caa87f
commit eb3ceb323d
9 changed files with 328 additions and 21 deletions

View File

@@ -53,7 +53,7 @@ trivy [global flags] command [flags] target
* [trivy plugin](trivy_plugin.md) - Manage plugins * [trivy plugin](trivy_plugin.md) - Manage plugins
* [trivy repository](trivy_repository.md) - Scan a repository * [trivy repository](trivy_repository.md) - Scan a repository
* [trivy rootfs](trivy_rootfs.md) - Scan rootfs * [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 server](trivy_server.md) - Server mode
* [trivy version](trivy_version.md) - Print the version * [trivy version](trivy_version.md) - Print the version
* [trivy vm](trivy_vm.md) - [EXPERIMENTAL] Scan a virtual machine image * [trivy vm](trivy_vm.md) - [EXPERIMENTAL] Scan a virtual machine image

View File

@@ -1,6 +1,6 @@
## trivy sbom ## trivy sbom
Scan SBOM for vulnerabilities Scan SBOM for vulnerabilities and licenses
``` ```
trivy sbom [flags] SBOM_PATH 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-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-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 --ignore-unfixed display only fixed vulnerabilities
--ignored-licenses strings specify a list of license to ignore
--ignorefile string specify .trivyignore file (default ".trivyignore") --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") --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 --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") --rekor-url string [EXPERIMENTAL] address of rekor STL server (default "https://rekor.sigstore.dev")
--reset remove all caches and database --reset remove all caches and database
--sbom-sources strings [EXPERIMENTAL] try to retrieve SBOM from the specified sources (oci,rekor) --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 --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]) -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 --show-suppressed [EXPERIMENTAL] show suppressed vulnerabilities

View File

@@ -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. Currently, the standard license scanning doesn't support filesystem and repository scanning.
| License scanning | Image | Rootfs | Filesystem | Repository | | License scanning | Image | Rootfs | Filesystem | Repository | SBOM |
| :-------------------: | :---: | :----: | :--------: | :--------: | |:---------------------:|:-----:|:------:|:----------:|:----------:|:----:|
| Standard | ✅ | ✅ | - | - | | Standard | ✅ | ✅ | - | - | ✅ |
| Full (--license-full) | ✅ | ✅ | ✅ | ✅ | | Full (--license-full) | ✅ | ✅ | ✅ | ✅ | - |
License checking classifies the identified licenses and map the classification to severity. License checking classifies the identified licenses and map the classification to severity.

View File

@@ -1,6 +1,6 @@
# SBOM scanning # 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 - CycloneDX
- SPDX - 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 !!! note
Passing SBOMs generated by tool other than Trivy may result in inaccurate detection Passing SBOMs generated by tool other than Trivy may result in inaccurate detection
because Trivy relies on custom properties in SBOM for accurate scanning. because Trivy relies on custom properties in SBOM for accurate scanning.

View File

@@ -6,11 +6,11 @@ import (
"path/filepath" "path/filepath"
"testing" "testing"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
v1 "github.com/google/go-containerregistry/pkg/v1" v1 "github.com/google/go-containerregistry/pkg/v1"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/types" "github.com/aquasecurity/trivy/pkg/types"
) )
@@ -19,6 +19,7 @@ func TestSBOM(t *testing.T) {
input string input string
format string format string
artifactType string artifactType string
scanners string
} }
tests := []struct { tests := []struct {
name string 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 // Set up testing DB
@@ -157,6 +168,11 @@ func TestSBOM(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
scanners := "vuln"
if tt.args.scanners != "" {
scanners = tt.args.scanners
}
osArgs := []string{ osArgs := []string{
"--cache-dir", "--cache-dir",
cacheDir, cacheDir,
@@ -165,6 +181,8 @@ func TestSBOM(t *testing.T) {
"--skip-db-update", "--skip-db-update",
"--format", "--format",
tt.args.format, tt.args.format,
"--scanners",
scanners,
} }
// Set up the output file // Set up the output file
@@ -223,5 +241,10 @@ func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant typ
} }
got := readReport(t, gotFile) 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) assert.Equal(t, want, got)
} }

View File

@@ -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": []
}

View File

@@ -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"
}
]
}

View File

@@ -1125,11 +1125,24 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
reportFlagGroup.DependencyTree = nil // disable '--dependency-tree' reportFlagGroup.DependencyTree = nil // disable '--dependency-tree'
reportFlagGroup.ReportFormat = nil // TODO: support --report summary 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 := 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.IncludeDevDeps = nil // disable '--include-dev-deps'
scanFlagGroup.Parallel = nil // disable '--parallel' 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{ sbomFlags := &flag.Flags{
GlobalFlagGroup: globalFlags, GlobalFlagGroup: globalFlags,
CacheFlagGroup: flag.NewCacheFlagGroup(), CacheFlagGroup: flag.NewCacheFlagGroup(),
@@ -1139,11 +1152,12 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
ScanFlagGroup: scanFlagGroup, ScanFlagGroup: scanFlagGroup,
SBOMFlagGroup: flag.NewSBOMFlagGroup(), SBOMFlagGroup: flag.NewSBOMFlagGroup(),
VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(), VulnerabilityFlagGroup: flag.NewVulnerabilityFlagGroup(),
LicenseFlagGroup: licenseFlagGroup,
} }
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "sbom [flags] SBOM_PATH", Use: "sbom [flags] SBOM_PATH",
Short: "Scan SBOM for vulnerabilities", Short: "Scan SBOM for vulnerabilities and licenses",
GroupID: groupScanning, GroupID: groupScanning,
Example: ` # Scan CycloneDX and show the result in tables Example: ` # Scan CycloneDX and show the result in tables
$ trivy sbom /path/to/report.cdx $ trivy sbom /path/to/report.cdx
@@ -1166,9 +1180,6 @@ func NewSBOMCommand(globalFlags *flag.GlobalFlagGroup) *cobra.Command {
return xerrors.Errorf("flag error: %w", err) return xerrors.Errorf("flag error: %w", err)
} }
// Scan vulnerabilities
options.Scanners = types.Scanners{types.VulnerabilityScanner}
return artifact.Run(cmd.Context(), options, artifact.TargetSBOM) return artifact.Run(cmd.Context(), options, artifact.TargetSBOM)
}, },
SilenceErrors: true, SilenceErrors: true,

View File

@@ -67,15 +67,93 @@ var mapping = map[string]string{
"MPL 2": MPL20, "MPL 2": MPL20,
// BSD // BSD
"BSD": BSD3Clause, // 2? 3? "BSD": BSD3Clause, // 2? 3?
"BSD-2-CLAUSE": BSD2Clause, "BSD-2-CLAUSE": BSD2Clause,
"BSD-3-CLAUSE": BSD3Clause, "BSD-3-CLAUSE": BSD3Clause,
"BSD-4-CLAUSE": BSD4Clause, "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": Apache20, // 1? 2? // APACHE
"APACHE 2.0": Apache20, "APACHE LICENSE": Apache10,
"RUBY": Ruby, "APACHE SOFTWARE LICENSES": Apache10,
"ZLIB": Zlib, "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
"PUBLIC DOMAIN": Unlicense, "PUBLIC DOMAIN": Unlicense,