mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-22 15:16:33 -08:00
feat(python): Include Conda packages in SBOMs (#3379)
Co-authored-by: knqyf263 <knqyf263@gmail.com>
This commit is contained in:
@@ -5,10 +5,12 @@
|
||||
## Other language-specific packages
|
||||
|
||||
| Language | File | Dependency location[^1] |
|
||||
|----------|--------------|:-----------------------:|
|
||||
|----------|-------------------|:-----------------------:|
|
||||
| Python | conda package[^2] | - |
|
||||
| Swift | Podfile.lock | - |
|
||||
|
||||
[^1]: Use `startline == 1 and endline == 1` for unsupported file types
|
||||
[^2]: `envs/*/conda-meta/*.json`
|
||||
|
||||
[os_packages]: ../vulnerability/detection/os.md
|
||||
[language_packages]: ../vulnerability/detection/language.md
|
||||
|
||||
@@ -28,6 +28,8 @@ func TestFilesystem(t *testing.T) {
|
||||
helmValuesFile []string
|
||||
skipFiles []string
|
||||
skipDirs []string
|
||||
command string
|
||||
format string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
@@ -263,6 +265,24 @@ func TestFilesystem(t *testing.T) {
|
||||
},
|
||||
golden: "testdata/secrets.json.golden",
|
||||
},
|
||||
{
|
||||
name: "conda generating CycloneDX SBOM",
|
||||
args: args{
|
||||
command: "rootfs",
|
||||
format: "cyclonedx",
|
||||
input: "testdata/fixtures/fs/conda",
|
||||
},
|
||||
golden: "testdata/conda-cyclonedx.json.golden",
|
||||
},
|
||||
{
|
||||
name: "conda generating SPDX SBOM",
|
||||
args: args{
|
||||
command: "rootfs",
|
||||
format: "spdx-json",
|
||||
input: "testdata/fixtures/fs/conda",
|
||||
},
|
||||
golden: "testdata/conda-spdx.json.golden",
|
||||
},
|
||||
}
|
||||
|
||||
// Set up testing DB
|
||||
@@ -273,9 +293,24 @@ func TestFilesystem(t *testing.T) {
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
||||
command := "fs"
|
||||
if tt.args.command != "" {
|
||||
command = tt.args.command
|
||||
}
|
||||
|
||||
format := "json"
|
||||
if tt.args.format != "" {
|
||||
format = tt.args.format
|
||||
}
|
||||
|
||||
osArgs := []string{
|
||||
"-q", "--cache-dir", cacheDir, "fs", "--skip-db-update", "--skip-policy-update",
|
||||
"--format", "json", "--offline-scan", "--security-checks", tt.args.securityChecks,
|
||||
"-q", "--cache-dir", cacheDir, command, "--skip-db-update", "--skip-policy-update",
|
||||
"--format", format, "--offline-scan",
|
||||
}
|
||||
|
||||
if tt.args.securityChecks != "" {
|
||||
osArgs = append(osArgs, "--security-checks", tt.args.securityChecks)
|
||||
}
|
||||
|
||||
if len(tt.args.policyPaths) != 0 {
|
||||
@@ -353,7 +388,16 @@ func TestFilesystem(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
|
||||
// Compare want and got
|
||||
switch format {
|
||||
case "cyclonedx":
|
||||
compareCycloneDX(t, tt.golden, outputFile)
|
||||
case "spdx-json":
|
||||
compareSpdxJson(t, tt.golden, outputFile)
|
||||
case "json":
|
||||
compareReports(t, tt.golden, outputFile)
|
||||
default:
|
||||
require.Fail(t, "invalid format", "format: %s", format)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,9 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
"github.com/spdx/tools-golang/jsonloader"
|
||||
"github.com/spdx/tools-golang/spdx"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
@@ -121,6 +124,50 @@ func readReport(t *testing.T, filePath string) types.Report {
|
||||
return report
|
||||
}
|
||||
|
||||
func readCycloneDX(t *testing.T, filePath string) *cdx.BOM {
|
||||
f, err := os.Open(filePath)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
bom := cdx.NewBOM()
|
||||
decoder := cdx.NewBOMDecoder(f, cdx.BOMFileFormatJSON)
|
||||
err = decoder.Decode(bom)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We don't compare values which change each time an SBOM is generated
|
||||
bom.Metadata.Timestamp = ""
|
||||
bom.Metadata.Component.BOMRef = ""
|
||||
bom.SerialNumber = ""
|
||||
if bom.Components != nil {
|
||||
for i := range *bom.Components {
|
||||
(*bom.Components)[i].BOMRef = ""
|
||||
}
|
||||
}
|
||||
if bom.Dependencies != nil {
|
||||
for j := range *bom.Dependencies {
|
||||
(*bom.Dependencies)[j].Ref = ""
|
||||
(*bom.Dependencies)[j].Dependencies = nil
|
||||
}
|
||||
}
|
||||
|
||||
return bom
|
||||
}
|
||||
|
||||
func readSpdxJson(t *testing.T, filePath string) *spdx.Document2_2 {
|
||||
f, err := os.Open(filePath)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
bom, err := jsonloader.Load2_2(f)
|
||||
require.NoError(t, err)
|
||||
|
||||
// We don't compare values which change each time an SBOM is generated
|
||||
bom.CreationInfo.Created = ""
|
||||
bom.CreationInfo.DocumentNamespace = ""
|
||||
|
||||
return bom
|
||||
}
|
||||
|
||||
func execute(osArgs []string) error {
|
||||
// Setup CLI App
|
||||
app := commands.NewApp("dev")
|
||||
@@ -136,3 +183,15 @@ func compareReports(t *testing.T, wantFile, gotFile string) {
|
||||
got := readReport(t, gotFile)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func compareCycloneDX(t *testing.T, wantFile, gotFile string) {
|
||||
want := readCycloneDX(t, wantFile)
|
||||
got := readCycloneDX(t, gotFile)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func compareSpdxJson(t *testing.T, wantFile, gotFile string) {
|
||||
want := readSpdxJson(t, wantFile)
|
||||
got := readSpdxJson(t, gotFile)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
@@ -3,11 +3,9 @@
|
||||
package integration
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"testing"
|
||||
|
||||
cdx "github.com/CycloneDX/cyclonedx-go"
|
||||
v1 "github.com/google/go-containerregistry/pkg/v1"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
@@ -128,9 +126,7 @@ func TestSBOM(t *testing.T) {
|
||||
// Compare want and got
|
||||
switch tt.args.format {
|
||||
case "cyclonedx":
|
||||
want := decodeCycloneDX(t, tt.golden)
|
||||
got := decodeCycloneDX(t, outputFile)
|
||||
assert.Equal(t, want, got)
|
||||
compareCycloneDX(t, tt.golden, outputFile)
|
||||
case "json":
|
||||
compareSBOMReports(t, tt.golden, outputFile, tt.override)
|
||||
default:
|
||||
@@ -165,18 +161,3 @@ func compareSBOMReports(t *testing.T, wantFile, gotFile string, overrideWant typ
|
||||
got := readReport(t, gotFile)
|
||||
assert.Equal(t, want, got)
|
||||
}
|
||||
|
||||
func decodeCycloneDX(t *testing.T, filePath string) *cdx.BOM {
|
||||
f, err := os.Open(filePath)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
bom := cdx.NewBOM()
|
||||
decoder := cdx.NewBOMDecoder(f, cdx.BOMFileFormatJSON)
|
||||
err = decoder.Decode(bom)
|
||||
require.NoError(t, err)
|
||||
|
||||
bom.Metadata.Timestamp = ""
|
||||
|
||||
return bom
|
||||
}
|
||||
|
||||
83
integration/testdata/conda-cyclonedx.json.golden
vendored
Normal file
83
integration/testdata/conda-cyclonedx.json.golden
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"bomFormat": "CycloneDX",
|
||||
"specVersion": "1.4",
|
||||
"serialNumber": "urn:uuid:4dd4cf4a-d4de-4ea0-b75f-ad617f31b5a9",
|
||||
"version": 1,
|
||||
"metadata": {
|
||||
"timestamp": "2023-01-08T23:57:37+00:00",
|
||||
"tools": [
|
||||
{
|
||||
"vendor": "aquasecurity",
|
||||
"name": "trivy",
|
||||
"version": "dev"
|
||||
}
|
||||
],
|
||||
"component": {
|
||||
"bom-ref": "582a7c6f-b30e-4b65-a911-f3f5034aa003",
|
||||
"type": "application",
|
||||
"name": "testdata/fixtures/fs/conda",
|
||||
"properties": [
|
||||
{
|
||||
"name": "aquasecurity:trivy:SchemaVersion",
|
||||
"value": "2"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"components": [
|
||||
{
|
||||
"bom-ref": "pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json",
|
||||
"type": "library",
|
||||
"name": "openssl",
|
||||
"version": "1.1.1q",
|
||||
"licenses": [
|
||||
{
|
||||
"expression": "OpenSSL"
|
||||
}
|
||||
],
|
||||
"purl": "pkg:conda/openssl@1.1.1q",
|
||||
"properties": [
|
||||
{
|
||||
"name": "aquasecurity:trivy:PkgType",
|
||||
"value": "conda-pkg"
|
||||
},
|
||||
{
|
||||
"name": "aquasecurity:trivy:FilePath",
|
||||
"value": "miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"bom-ref": "pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json",
|
||||
"type": "library",
|
||||
"name": "pip",
|
||||
"version": "22.2.2",
|
||||
"licenses": [
|
||||
{
|
||||
"expression": "MIT"
|
||||
}
|
||||
],
|
||||
"purl": "pkg:conda/pip@22.2.2",
|
||||
"properties": [
|
||||
{
|
||||
"name": "aquasecurity:trivy:PkgType",
|
||||
"value": "conda-pkg"
|
||||
},
|
||||
{
|
||||
"name": "aquasecurity:trivy:FilePath",
|
||||
"value": "miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"dependencies": [
|
||||
{
|
||||
"ref": "582a7c6f-b30e-4b65-a911-f3f5034aa003",
|
||||
"dependsOn": [
|
||||
"pkg:conda/openssl@1.1.1q?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fopenssl-1.1.1q-h7f8727e_0.json",
|
||||
"pkg:conda/pip@22.2.2?file_path=miniconda3%2Fenvs%2Ftestenv%2Fconda-meta%2Fpip-22.2.2-py38h06a4308_0.json"
|
||||
]
|
||||
}
|
||||
],
|
||||
"vulnerabilities": []
|
||||
}
|
||||
101
integration/testdata/conda-spdx.json.golden
vendored
Normal file
101
integration/testdata/conda-spdx.json.golden
vendored
Normal file
@@ -0,0 +1,101 @@
|
||||
{
|
||||
"SPDXID": "SPDXRef-DOCUMENT",
|
||||
"creationInfo": {
|
||||
"created": "2023-01-08T23:58:16.700785648Z",
|
||||
"creators": [
|
||||
"Tool: trivy",
|
||||
"Organization: aquasecurity"
|
||||
]
|
||||
},
|
||||
"dataLicense": "CC0-1.0",
|
||||
"documentDescribes": [
|
||||
"SPDXRef-Filesystem-6e0ac6a0fab50ab4"
|
||||
],
|
||||
"documentNamespace": "http://aquasecurity.github.io/trivy/filesystem/testdata/fixtures/fs/conda-3be0d21e-5711-451e-8b1b-2ac8775a3abb",
|
||||
"files": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-File-600e5e0110a84891",
|
||||
"fileName": "miniconda3/envs/testenv/conda-meta/openssl-1.1.1q-h7f8727e_0.json"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-File-7eb62e2a3edddc0a",
|
||||
"fileName": "miniconda3/envs/testenv/conda-meta/pip-22.2.2-py38h06a4308_0.json"
|
||||
}
|
||||
],
|
||||
"name": "testdata/fixtures/fs/conda",
|
||||
"packages": [
|
||||
{
|
||||
"SPDXID": "SPDXRef-Application-ee5ef1aa4ac89125",
|
||||
"filesAnalyzed": false,
|
||||
"name": "conda-pkg",
|
||||
"sourceInfo": "Conda"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
|
||||
"attributionTexts": [
|
||||
"SchemaVersion: 2"
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"name": "testdata/fixtures/fs/conda"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-2984084f02572600",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceLocator": "pkg:conda/openssl@1.1.1q",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"hasFiles": [
|
||||
"SPDXRef-File-600e5e0110a84891"
|
||||
],
|
||||
"licenseConcluded": "OpenSSL",
|
||||
"licenseDeclared": "OpenSSL",
|
||||
"name": "openssl",
|
||||
"versionInfo": "1.1.1q"
|
||||
},
|
||||
{
|
||||
"SPDXID": "SPDXRef-Package-ac33eb699b3aa81d",
|
||||
"externalRefs": [
|
||||
{
|
||||
"referenceCategory": "PACKAGE-MANAGER",
|
||||
"referenceLocator": "pkg:conda/pip@22.2.2",
|
||||
"referenceType": "purl"
|
||||
}
|
||||
],
|
||||
"filesAnalyzed": false,
|
||||
"hasFiles": [
|
||||
"SPDXRef-File-7eb62e2a3edddc0a"
|
||||
],
|
||||
"licenseConcluded": "MIT",
|
||||
"licenseDeclared": "MIT",
|
||||
"name": "pip",
|
||||
"versionInfo": "22.2.2"
|
||||
}
|
||||
],
|
||||
"relationships": [
|
||||
{
|
||||
"relatedSpdxElement": "SPDXRef-Filesystem-6e0ac6a0fab50ab4",
|
||||
"relationshipType": "DESCRIBES",
|
||||
"spdxElementId": "SPDXRef-DOCUMENT"
|
||||
},
|
||||
{
|
||||
"relatedSpdxElement": "SPDXRef-Application-ee5ef1aa4ac89125",
|
||||
"relationshipType": "CONTAINS",
|
||||
"spdxElementId": "SPDXRef-Filesystem-6e0ac6a0fab50ab4"
|
||||
},
|
||||
{
|
||||
"relatedSpdxElement": "SPDXRef-Package-2984084f02572600",
|
||||
"relationshipType": "CONTAINS",
|
||||
"spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125"
|
||||
},
|
||||
{
|
||||
"relatedSpdxElement": "SPDXRef-Package-ac33eb699b3aa81d",
|
||||
"relationshipType": "CONTAINS",
|
||||
"spdxElementId": "SPDXRef-Application-ee5ef1aa4ac89125"
|
||||
}
|
||||
],
|
||||
"spdxVersion": "SPDX-2.2"
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"build": "h7f8727e_0",
|
||||
"build_number": 0,
|
||||
"channel": "https://repo.anaconda.com/pkgs/main/linux-64",
|
||||
"constrains": [],
|
||||
"depends": [
|
||||
"ca-certificates",
|
||||
"libgcc-ng >=7.5.0"
|
||||
],
|
||||
"extracted_package_dir": "/home/mmaitre/miniconda3/pkgs/openssl-1.1.1q-h7f8727e_0",
|
||||
"features": "",
|
||||
"files": [
|
||||
"bin/c_rehash",
|
||||
"<SNIP>",
|
||||
"ssl/openssl.cnf.dist"
|
||||
],
|
||||
"fn": "openssl-1.1.1q-h7f8727e_0.conda",
|
||||
"legacy_bz2_md5": "ad51928702694e3f6d25b7d4229c84e6",
|
||||
"license": "OpenSSL",
|
||||
"license_family": "Apache",
|
||||
"link": {
|
||||
"source": "/home/user/miniconda3/pkgs/openssl-1.1.1q-h7f8727e_0",
|
||||
"type": 1
|
||||
},
|
||||
"md5": "2ac47797afee2ece8d339c18b095b8d8",
|
||||
"name": "openssl",
|
||||
"package_tarball_full_path": "/home/user/miniconda3/pkgs/openssl-1.1.1q-h7f8727e_0.conda",
|
||||
"paths_data": {
|
||||
"paths": [
|
||||
{
|
||||
"_path": "bin/c_rehash",
|
||||
"file_mode": "text",
|
||||
"path_type": "hardlink",
|
||||
"prefix_placeholder": "/opt/conda/conda-bld/openssl_1657551138854/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_place",
|
||||
"sha256": "04c9f4a5c91e20a24d14a36668b5bebe826d6087fb2337b68e33a4d485f5586f",
|
||||
"sha256_in_prefix": "fc2a6b708cccb8ba90d20e54a9b07257fce009bf315a36f73d3e542dd8674921",
|
||||
"size_in_bytes": 6991
|
||||
},
|
||||
{
|
||||
"_path": "<SNIP>"
|
||||
},
|
||||
{
|
||||
"_path": "ssl/openssl.cnf.dist",
|
||||
"path_type": "hardlink",
|
||||
"sha256": "f10ba64917b4458fafc1e078c2eb9e6a7602e68fc98c2e9e6df5e1636ae27d6b",
|
||||
"sha256_in_prefix": "f10ba64917b4458fafc1e078c2eb9e6a7602e68fc98c2e9e6df5e1636ae27d6b",
|
||||
"size_in_bytes": 10909
|
||||
}
|
||||
],
|
||||
"paths_version": 1
|
||||
},
|
||||
"requested_spec": "None",
|
||||
"sha256": "49804293b87141523b2606836ece8e2aaa5202983698fd91e7c36bdb8c8a8de5",
|
||||
"size": 2649280,
|
||||
"subdir": "linux-64",
|
||||
"timestamp": 1657551292835,
|
||||
"track_features": "",
|
||||
"url": "https://repo.anaconda.com/pkgs/main/linux-64/openssl-1.1.1q-h7f8727e_0.conda",
|
||||
"version": "1.1.1q"
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"build": "py38h06a4308_0",
|
||||
"build_number": 0,
|
||||
"channel": "https://repo.anaconda.com/pkgs/main/linux-64",
|
||||
"constrains": [],
|
||||
"depends": [
|
||||
"python >=3.8,<3.9.0a0",
|
||||
"setuptools",
|
||||
"wheel"
|
||||
],
|
||||
"extracted_package_dir": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0",
|
||||
"features": "",
|
||||
"files": [
|
||||
"bin/pip",
|
||||
"<SNIP>",
|
||||
"lib/python3.8/site-packages/pip/py.typed"
|
||||
],
|
||||
"fn": "pip-22.2.2-py38h06a4308_0.conda",
|
||||
"legacy_bz2_md5": "2ac9f1cfec65a1e4ef00cc0132ecd753",
|
||||
"legacy_bz2_size": 2849993,
|
||||
"license": "MIT",
|
||||
"license_family": "MIT",
|
||||
"link": {
|
||||
"source": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0",
|
||||
"type": 1
|
||||
},
|
||||
"md5": "ed3e0331e7c614b3148c9911e1fc15e3",
|
||||
"name": "pip",
|
||||
"package_tarball_full_path": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0.conda",
|
||||
"paths_data": {
|
||||
"paths": [
|
||||
{
|
||||
"_path": "bin/pip",
|
||||
"file_mode": "text",
|
||||
"path_type": "hardlink",
|
||||
"prefix_placeholder": "/opt/conda/conda-bld/pip_1664552683240/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold",
|
||||
"sha256": "1110c03ca2fb86e43e8b52a61cd9d7c722b2817fc893a613e1a947f5d7049008",
|
||||
"sha256_in_prefix": "14ed5ba79d096035b83a469e863acbbaadd3ea6ca5f23f79eefa91fa857d3f19",
|
||||
"size_in_bytes": 475
|
||||
},
|
||||
{
|
||||
"_path": "<SNIP>"
|
||||
},
|
||||
{
|
||||
"_path": "lib/python3.8/site-packages/pip/py.typed",
|
||||
"path_type": "hardlink",
|
||||
"sha256": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762",
|
||||
"sha256_in_prefix": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762",
|
||||
"size_in_bytes": 286
|
||||
}
|
||||
],
|
||||
"paths_version": 1
|
||||
},
|
||||
"requested_spec": "None",
|
||||
"sha256": "3fb76b94cfa5ea9732bfb241b3d234ec0a5a48d16755c3c1ef3c94630f91eb26",
|
||||
"size": 2417732,
|
||||
"subdir": "linux-64",
|
||||
"timestamp": 1664552878795,
|
||||
"track_features": "",
|
||||
"url": "https://repo.anaconda.com/pkgs/main/linux-64/pip-22.2.2-py38h06a4308_0.conda",
|
||||
"version": "22.2.2"
|
||||
}
|
||||
@@ -65,6 +65,9 @@ func NewDriver(libType string) (Driver, error) {
|
||||
case ftypes.Cocoapods:
|
||||
log.Logger.Warn("CocoaPods is supported for SBOM, not for vulnerability scanning")
|
||||
return Driver{}, ErrSBOMSupportOnly
|
||||
case ftypes.CondaPkg:
|
||||
log.Logger.Warn("Conda package is supported for SBOM, not for vulnerability scanning")
|
||||
return Driver{}, ErrSBOMSupportOnly
|
||||
default:
|
||||
return Driver{}, xerrors.Errorf("unsupported type %s", libType)
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import (
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/config/all"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/executable"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/c/conan"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/conda/meta"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dart/pub"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/deps"
|
||||
_ "github.com/aquasecurity/trivy/pkg/fanal/analyzer/language/dotnet/nuget"
|
||||
|
||||
@@ -61,6 +61,9 @@ const (
|
||||
TypeNuget Type = "nuget"
|
||||
TypeDotNetCore Type = "dotnet-core"
|
||||
|
||||
// Conda
|
||||
TypeCondaPkg Type = "conda-pkg"
|
||||
|
||||
// Python
|
||||
TypePythonPkg Type = "python-pkg"
|
||||
TypePip Type = "pip"
|
||||
@@ -132,7 +135,7 @@ var (
|
||||
// TypeLanguages has all language analyzers
|
||||
TypeLanguages = []Type{
|
||||
TypeBundler, TypeGemSpec, TypeCargo, TypeComposer, TypeJar, TypePom, TypeGradleLock,
|
||||
TypeNpmPkgLock, TypeNodePkg, TypeYarn, TypePnpm, TypeNuget, TypeDotNetCore,
|
||||
TypeNpmPkgLock, TypeNodePkg, TypeYarn, TypePnpm, TypeNuget, TypeDotNetCore, TypeCondaPkg,
|
||||
TypePythonPkg, TypePip, TypePipenv, TypePoetry, TypeGoBinary, TypeGoMod, TypeRustBinary, TypeConanLock,
|
||||
TypeCocoaPods, TypePubSpecLock, TypeMixLock,
|
||||
}
|
||||
@@ -145,7 +148,7 @@ var (
|
||||
}
|
||||
|
||||
// TypeIndividualPkgs has all analyzers for individual packages
|
||||
TypeIndividualPkgs = []Type{TypeGemSpec, TypeNodePkg, TypePythonPkg, TypeGoBinary, TypeJar, TypeRustBinary}
|
||||
TypeIndividualPkgs = []Type{TypeGemSpec, TypeNodePkg, TypeCondaPkg, TypePythonPkg, TypeGoBinary, TypeJar, TypeRustBinary}
|
||||
|
||||
// TypeConfigFiles has all config file analyzers
|
||||
TypeConfigFiles = []Type{TypeYaml, TypeJSON, TypeDockerfile, TypeTerraform, TypeCloudFormation, TypeHelm}
|
||||
|
||||
46
pkg/fanal/analyzer/language/conda/meta/meta.go
Normal file
46
pkg/fanal/analyzer/language/conda/meta/meta.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
|
||||
"golang.org/x/xerrors"
|
||||
|
||||
"github.com/aquasecurity/go-dep-parser/pkg/conda/meta"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer/language"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
)
|
||||
|
||||
func init() {
|
||||
analyzer.RegisterAnalyzer(&metaAnalyzer{})
|
||||
}
|
||||
|
||||
const version = 1
|
||||
|
||||
var fileRegex = regexp.MustCompile(`.*/envs/.+/conda-meta/.+-.+-.+\.json`)
|
||||
|
||||
type metaAnalyzer struct{}
|
||||
|
||||
func (a metaAnalyzer) Analyze(_ context.Context, input analyzer.AnalysisInput) (*analyzer.AnalysisResult, error) {
|
||||
p := meta.NewParser()
|
||||
libs, deps, err := p.Parse(input.Content)
|
||||
if err != nil {
|
||||
return nil, xerrors.Errorf("%s parse error: %w", input.FilePath, err)
|
||||
}
|
||||
|
||||
return language.ToAnalysisResult(types.CondaPkg, input.FilePath, input.FilePath, libs, deps), nil
|
||||
}
|
||||
func (a metaAnalyzer) Required(filePath string, _ os.FileInfo) bool {
|
||||
return fileRegex.MatchString(filepath.ToSlash(filePath))
|
||||
}
|
||||
|
||||
func (a metaAnalyzer) Type() analyzer.Type {
|
||||
return analyzer.TypeCondaPkg
|
||||
}
|
||||
|
||||
func (a metaAnalyzer) Version() int {
|
||||
return version
|
||||
}
|
||||
101
pkg/fanal/analyzer/language/conda/meta/meta_test.go
Normal file
101
pkg/fanal/analyzer/language/conda/meta/meta_test.go
Normal file
@@ -0,0 +1,101 @@
|
||||
package meta
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/analyzer"
|
||||
"github.com/aquasecurity/trivy/pkg/fanal/types"
|
||||
)
|
||||
|
||||
func Test_packagingAnalyzer_Analyze(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
inputFile string
|
||||
want *analyzer.AnalysisResult
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
name: "pip",
|
||||
inputFile: "testdata/pip-22.2.2-py38h06a4308_0.json",
|
||||
want: &analyzer.AnalysisResult{
|
||||
Applications: []types.Application{
|
||||
{
|
||||
Type: types.CondaPkg,
|
||||
FilePath: "testdata/pip-22.2.2-py38h06a4308_0.json",
|
||||
Libraries: []types.Package{
|
||||
{
|
||||
Name: "pip",
|
||||
Version: "22.2.2",
|
||||
Licenses: []string{"MIT"},
|
||||
FilePath: "testdata/pip-22.2.2-py38h06a4308_0.json",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
inputFile: "testdata/invalid.json",
|
||||
wantErr: "unable to parse conda package",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
f, err := os.Open(tt.inputFile)
|
||||
require.NoError(t, err)
|
||||
defer f.Close()
|
||||
|
||||
stat, err := f.Stat()
|
||||
require.NoError(t, err)
|
||||
|
||||
a := metaAnalyzer{}
|
||||
ctx := context.Background()
|
||||
got, err := a.Analyze(ctx, analyzer.AnalysisInput{
|
||||
FilePath: tt.inputFile,
|
||||
Info: stat,
|
||||
Content: f,
|
||||
})
|
||||
|
||||
if tt.wantErr != "" {
|
||||
assert.ErrorContains(t, err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
assert.NoError(t, err)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_packagingAnalyzer_Required(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
filePath string
|
||||
want bool
|
||||
}{
|
||||
{
|
||||
name: "pip",
|
||||
filePath: "/home/<user>/miniconda3/envs/<env>/conda-meta/pip-22.2.2-py38h06a4308_0.json",
|
||||
want: true,
|
||||
},
|
||||
{
|
||||
name: "invalid",
|
||||
filePath: "/home/<user>/miniconda3/envs/<env>/conda-meta/invalid.json",
|
||||
want: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
a := metaAnalyzer{}
|
||||
got := a.Required(tt.filePath, nil)
|
||||
assert.Equal(t, tt.want, got)
|
||||
})
|
||||
}
|
||||
}
|
||||
1
pkg/fanal/analyzer/language/conda/meta/testdata/invalid.json
vendored
Normal file
1
pkg/fanal/analyzer/language/conda/meta/testdata/invalid.json
vendored
Normal file
@@ -0,0 +1 @@
|
||||
{}
|
||||
62
pkg/fanal/analyzer/language/conda/meta/testdata/pip-22.2.2-py38h06a4308_0.json
vendored
Normal file
62
pkg/fanal/analyzer/language/conda/meta/testdata/pip-22.2.2-py38h06a4308_0.json
vendored
Normal file
@@ -0,0 +1,62 @@
|
||||
{
|
||||
"build": "py38h06a4308_0",
|
||||
"build_number": 0,
|
||||
"channel": "https://repo.anaconda.com/pkgs/main/linux-64",
|
||||
"constrains": [],
|
||||
"depends": [
|
||||
"python >=3.8,<3.9.0a0",
|
||||
"setuptools",
|
||||
"wheel"
|
||||
],
|
||||
"extracted_package_dir": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0",
|
||||
"features": "",
|
||||
"files": [
|
||||
"bin/pip",
|
||||
"<SNIP>",
|
||||
"lib/python3.8/site-packages/pip/py.typed"
|
||||
],
|
||||
"fn": "pip-22.2.2-py38h06a4308_0.conda",
|
||||
"legacy_bz2_md5": "2ac9f1cfec65a1e4ef00cc0132ecd753",
|
||||
"legacy_bz2_size": 2849993,
|
||||
"license": "MIT",
|
||||
"license_family": "MIT",
|
||||
"link": {
|
||||
"source": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0",
|
||||
"type": 1
|
||||
},
|
||||
"md5": "ed3e0331e7c614b3148c9911e1fc15e3",
|
||||
"name": "pip",
|
||||
"package_tarball_full_path": "/home/user/miniconda3/pkgs/pip-22.2.2-py38h06a4308_0.conda",
|
||||
"paths_data": {
|
||||
"paths": [
|
||||
{
|
||||
"_path": "bin/pip",
|
||||
"file_mode": "text",
|
||||
"path_type": "hardlink",
|
||||
"prefix_placeholder": "/opt/conda/conda-bld/pip_1664552683240/_h_env_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold_placehold",
|
||||
"sha256": "1110c03ca2fb86e43e8b52a61cd9d7c722b2817fc893a613e1a947f5d7049008",
|
||||
"sha256_in_prefix": "14ed5ba79d096035b83a469e863acbbaadd3ea6ca5f23f79eefa91fa857d3f19",
|
||||
"size_in_bytes": 475
|
||||
},
|
||||
{
|
||||
"_path": "<SNIP>"
|
||||
},
|
||||
{
|
||||
"_path": "lib/python3.8/site-packages/pip/py.typed",
|
||||
"path_type": "hardlink",
|
||||
"sha256": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762",
|
||||
"sha256_in_prefix": "10156fbcf4539ff788a73e5ee50ced48276b317ed0c1ded53fddd14a82256762",
|
||||
"size_in_bytes": 286
|
||||
}
|
||||
],
|
||||
"paths_version": 1
|
||||
},
|
||||
"requested_spec": "None",
|
||||
"sha256": "3fb76b94cfa5ea9732bfb241b3d234ec0a5a48d16755c3c1ef3c94630f91eb26",
|
||||
"size": 2417732,
|
||||
"subdir": "linux-64",
|
||||
"timestamp": 1664552878795,
|
||||
"track_features": "",
|
||||
"url": "https://repo.anaconda.com/pkgs/main/linux-64/pip-22.2.2-py38h06a4308_0.conda",
|
||||
"version": "22.2.2"
|
||||
}
|
||||
@@ -232,12 +232,13 @@ func ApplyLayers(layers []types.BlobInfo) types.ArtifactDetail {
|
||||
return mergedLayer
|
||||
}
|
||||
|
||||
// aggregate merges all packages installed by pip/gem/npm/jar into each application
|
||||
// aggregate merges all packages installed by pip/gem/npm/jar/conda into each application
|
||||
func aggregate(detail *types.ArtifactDetail) {
|
||||
var apps []types.Application
|
||||
|
||||
aggregatedApps := map[string]*types.Application{
|
||||
types.PythonPkg: {Type: types.PythonPkg},
|
||||
types.CondaPkg: {Type: types.CondaPkg},
|
||||
types.GemSpec: {Type: types.GemSpec},
|
||||
types.NodePkg: {Type: types.NodePkg},
|
||||
types.Jar: {Type: types.Jar},
|
||||
|
||||
@@ -36,6 +36,9 @@ var (
|
||||
// python
|
||||
types.PythonPkg,
|
||||
|
||||
// conda
|
||||
types.CondaPkg,
|
||||
|
||||
// node.js
|
||||
types.NodePkg,
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ const (
|
||||
Pip = "pip"
|
||||
Pipenv = "pipenv"
|
||||
Poetry = "poetry"
|
||||
CondaPkg = "conda-pkg"
|
||||
PythonPkg = "python-pkg"
|
||||
NodePkg = "node-pkg"
|
||||
Yarn = "yarn"
|
||||
|
||||
@@ -85,6 +85,8 @@ func (p *PackageURL) PackageType() string {
|
||||
return ftypes.Jar
|
||||
case packageurl.TypeGem:
|
||||
return ftypes.GemSpec
|
||||
case packageurl.TypeConda:
|
||||
return ftypes.CondaPkg
|
||||
case packageurl.TypePyPi:
|
||||
return ftypes.PythonPkg
|
||||
case packageurl.TypeGolang:
|
||||
@@ -305,6 +307,8 @@ func purlType(t string) string {
|
||||
return packageurl.TypeGem
|
||||
case ftypes.NuGet, ftypes.DotNetCore:
|
||||
return packageurl.TypeNuget
|
||||
case ftypes.CondaPkg:
|
||||
return packageurl.TypeConda
|
||||
case ftypes.PythonPkg, ftypes.Pip, ftypes.Pipenv, ftypes.Poetry:
|
||||
return packageurl.TypePyPi
|
||||
case ftypes.GoBinary, ftypes.GoModule:
|
||||
|
||||
@@ -134,6 +134,21 @@ func TestNewPackageURL(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "conda package",
|
||||
typ: ftypes.CondaPkg,
|
||||
pkg: ftypes.Package{
|
||||
Name: "absl-py",
|
||||
Version: "0.4.1",
|
||||
},
|
||||
want: purl.PackageURL{
|
||||
PackageURL: packageurl.PackageURL{
|
||||
Type: packageurl.TypeConda,
|
||||
Name: "absl-py",
|
||||
Version: "0.4.1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "composer package",
|
||||
typ: ftypes.Composer,
|
||||
@@ -451,6 +466,18 @@ func TestFromString(t *testing.T) {
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "happy path for conda",
|
||||
purl: "pkg:conda/absl-py@0.4.1",
|
||||
want: purl.PackageURL{
|
||||
PackageURL: packageurl.PackageURL{
|
||||
Type: packageurl.TypeConda,
|
||||
Name: "absl-py",
|
||||
Version: "0.4.1",
|
||||
Qualifiers: packageurl.Qualifiers{},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "bad rpm",
|
||||
purl: "pkg:rpm/redhat/a--@1.0.0",
|
||||
|
||||
@@ -249,7 +249,7 @@ func (e *Marshaler) marshalComponents(r types.Report, bomRef string) (*[]cdx.Com
|
||||
}
|
||||
|
||||
if result.Type == ftypes.NodePkg || result.Type == ftypes.PythonPkg ||
|
||||
result.Type == ftypes.GemSpec || result.Type == ftypes.Jar {
|
||||
result.Type == ftypes.GemSpec || result.Type == ftypes.Jar || result.Type == ftypes.CondaPkg {
|
||||
// If a package is language-specific package that isn't associated with a lock file,
|
||||
// it will be a dependency of a component under "metadata".
|
||||
// e.g.
|
||||
|
||||
@@ -143,7 +143,7 @@ func initApplication(pkg spdx.Package2_2) *ftypes.Application {
|
||||
FilePath: pkg.PackageSourceInfo,
|
||||
}
|
||||
if pkg.PackageName == ftypes.NodePkg || pkg.PackageName == ftypes.PythonPkg ||
|
||||
pkg.PackageName == ftypes.GemSpec || pkg.PackageName == ftypes.Jar {
|
||||
pkg.PackageName == ftypes.GemSpec || pkg.PackageName == ftypes.Jar || pkg.PackageName == ftypes.CondaPkg {
|
||||
app.FilePath = ""
|
||||
}
|
||||
return app
|
||||
|
||||
@@ -31,6 +31,7 @@ import (
|
||||
var (
|
||||
pkgTargets = map[string]string{
|
||||
ftypes.PythonPkg: "Python",
|
||||
ftypes.CondaPkg: "Conda",
|
||||
ftypes.GemSpec: "Ruby",
|
||||
ftypes.NodePkg: "Node.js",
|
||||
ftypes.Jar: "Java",
|
||||
|
||||
Reference in New Issue
Block a user