Files
trivy/pkg/detector/library/driver.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), ", ")
}