mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-12 07:40:48 -08:00
167 lines
5.5 KiB
Go
167 lines
5.5 KiB
Go
package library
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/samber/lo"
|
|
"golang.org/x/xerrors"
|
|
|
|
"github.com/aquasecurity/trivy-db/pkg/db"
|
|
"github.com/aquasecurity/trivy-db/pkg/ecosystem"
|
|
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
|
|
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
|
|
"github.com/aquasecurity/trivy/pkg/detector/library/compare"
|
|
"github.com/aquasecurity/trivy/pkg/detector/library/compare/bitnami"
|
|
"github.com/aquasecurity/trivy/pkg/detector/library/compare/maven"
|
|
"github.com/aquasecurity/trivy/pkg/detector/library/compare/npm"
|
|
"github.com/aquasecurity/trivy/pkg/detector/library/compare/pep440"
|
|
"github.com/aquasecurity/trivy/pkg/detector/library/compare/rubygems"
|
|
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
|
|
"github.com/aquasecurity/trivy/pkg/log"
|
|
"github.com/aquasecurity/trivy/pkg/types"
|
|
)
|
|
|
|
// NewDriver returns a driver according to the library type
|
|
func NewDriver(libType ftypes.LangType) (Driver, bool) {
|
|
var eco ecosystem.Type
|
|
var comparer compare.Comparer
|
|
|
|
switch libType {
|
|
case ftypes.Bundler, ftypes.GemSpec:
|
|
eco = ecosystem.RubyGems
|
|
comparer = rubygems.Comparer{}
|
|
case ftypes.RustBinary, ftypes.Cargo:
|
|
eco = ecosystem.Cargo
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Composer, ftypes.ComposerVendor:
|
|
eco = ecosystem.Composer
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.GoBinary, ftypes.GoModule:
|
|
eco = ecosystem.Go
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Jar, ftypes.Pom, ftypes.Gradle, ftypes.Sbt:
|
|
eco = ecosystem.Maven
|
|
comparer = maven.Comparer{}
|
|
case ftypes.Npm, ftypes.Yarn, ftypes.Pnpm, ftypes.Bun, ftypes.NodePkg, ftypes.JavaScript:
|
|
eco = ecosystem.Npm
|
|
comparer = npm.Comparer{}
|
|
case ftypes.NuGet, ftypes.DotNetCore, ftypes.PackagesProps:
|
|
eco = ecosystem.NuGet
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Pipenv, ftypes.Poetry, ftypes.Pip, ftypes.PythonPkg, ftypes.Uv:
|
|
eco = ecosystem.Pip
|
|
comparer = pep440.Comparer{}
|
|
case ftypes.Pub:
|
|
eco = ecosystem.Pub
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Hex:
|
|
eco = ecosystem.Erlang
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Conan:
|
|
eco = ecosystem.Conan
|
|
// Only semver can be used for version ranges
|
|
// https://docs.conan.io/en/latest/versioning/version_ranges.html
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Swift:
|
|
// Swift uses semver
|
|
// https://www.swift.org/package-manager/#importing-dependencies
|
|
eco = ecosystem.Swift
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Cocoapods:
|
|
// CocoaPods uses RubyGems version specifiers
|
|
// https://guides.cocoapods.org/making/making-a-cocoapod.html#cocoapods-versioning-specifics
|
|
eco = ecosystem.Cocoapods
|
|
comparer = rubygems.Comparer{}
|
|
case ftypes.CondaPkg, ftypes.CondaEnv:
|
|
log.Warn("Conda package is supported for SBOM, not for vulnerability scanning")
|
|
return Driver{}, false
|
|
case ftypes.Bitnami:
|
|
eco = ecosystem.Bitnami
|
|
comparer = bitnami.Comparer{}
|
|
case ftypes.K8sUpstream:
|
|
eco = ecosystem.Kubernetes
|
|
comparer = compare.GenericComparer{}
|
|
case ftypes.Julia:
|
|
eco = ecosystem.Julia
|
|
comparer = compare.GenericComparer{}
|
|
default:
|
|
log.Warn("The library type is not supported for vulnerability scanning",
|
|
log.String("type", string(libType)))
|
|
return Driver{}, false
|
|
}
|
|
return Driver{
|
|
ecosystem: eco,
|
|
comparer: comparer,
|
|
dbc: db.Config{},
|
|
}, true
|
|
}
|
|
|
|
// Driver represents security advisories for each programming language
|
|
type Driver struct {
|
|
ecosystem ecosystem.Type
|
|
comparer compare.Comparer
|
|
dbc db.Config
|
|
}
|
|
|
|
// Type returns the driver ecosystem
|
|
func (d *Driver) Type() string {
|
|
return string(d.ecosystem)
|
|
}
|
|
|
|
// DetectVulnerabilities scans buckets with the prefix according to the ecosystem.
|
|
// If "ecosystem" is pip, it looks for buckets with "pip::" and gets security advisories from those buckets.
|
|
// It allows us to add a new data source with the ecosystem prefix (e.g. pip::new-data-source)
|
|
// and detect vulnerabilities without specifying a specific bucket name.
|
|
func (d *Driver) DetectVulnerabilities(pkgID, pkgName, pkgVer string) ([]types.DetectedVulnerability, error) {
|
|
// e.g. "pip::", "npm::"
|
|
prefix := fmt.Sprintf("%s::", d.ecosystem)
|
|
advisories, err := d.dbc.GetAdvisories(prefix, vulnerability.NormalizePkgName(d.ecosystem, pkgName))
|
|
if err != nil {
|
|
return nil, xerrors.Errorf("failed to get %s advisories: %w", d.ecosystem, err)
|
|
}
|
|
|
|
var vulns []types.DetectedVulnerability
|
|
for _, adv := range advisories {
|
|
if !d.comparer.IsVulnerable(pkgVer, adv) {
|
|
continue
|
|
}
|
|
|
|
vuln := types.DetectedVulnerability{
|
|
VulnerabilityID: adv.VulnerabilityID,
|
|
VendorIDs: adv.VendorIDs, // Any vendors have specific IDs, e.g. GHSA, JLSEC
|
|
PkgID: pkgID,
|
|
PkgName: pkgName,
|
|
InstalledVersion: pkgVer,
|
|
FixedVersion: createFixedVersions(adv),
|
|
DataSource: adv.DataSource,
|
|
Custom: adv.Custom,
|
|
}
|
|
vulns = append(vulns, vuln)
|
|
}
|
|
|
|
return vulns, nil
|
|
}
|
|
|
|
func createFixedVersions(advisory dbTypes.Advisory) string {
|
|
if len(advisory.PatchedVersions) != 0 {
|
|
return joinFixedVersions(advisory.PatchedVersions)
|
|
}
|
|
|
|
var fixedVersions []string
|
|
for _, version := range advisory.VulnerableVersions {
|
|
for s := range strings.SplitSeq(version, ",") {
|
|
s = strings.TrimSpace(s)
|
|
if !strings.HasPrefix(s, "<=") && strings.HasPrefix(s, "<") {
|
|
s = strings.TrimPrefix(s, "<")
|
|
fixedVersions = append(fixedVersions, strings.TrimSpace(s))
|
|
}
|
|
}
|
|
}
|
|
return joinFixedVersions(fixedVersions)
|
|
}
|
|
|
|
func joinFixedVersions(fixedVersions []string) string {
|
|
return strings.Join(lo.Uniq(fixedVersions), ", ")
|
|
}
|