Files
trivy/pkg/detector/ospkg/redhat/redhat.go
Teppei Fukuda 983ac15f22 ci: add depguard (#6963)
Signed-off-by: knqyf263 <knqyf263@gmail.com>
2024-06-20 02:48:08 +00:00

219 lines
6.2 KiB
Go

package redhat
import (
"context"
"fmt"
"slices"
"sort"
"strings"
"time"
version "github.com/knqyf263/go-rpm-version"
"github.com/samber/lo"
"golang.org/x/xerrors"
dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
ustrings "github.com/aquasecurity/trivy-db/pkg/utils/strings"
redhat "github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat-oval"
"github.com/aquasecurity/trivy-db/pkg/vulnsrc/vulnerability"
osver "github.com/aquasecurity/trivy/pkg/detector/ospkg/version"
ftypes "github.com/aquasecurity/trivy/pkg/fanal/types"
"github.com/aquasecurity/trivy/pkg/log"
"github.com/aquasecurity/trivy/pkg/scanner/utils"
"github.com/aquasecurity/trivy/pkg/types"
)
var (
defaultContentSets = map[string][]string{
"6": {
"rhel-6-server-rpms",
"rhel-6-server-extras-rpms",
},
"7": {
"rhel-7-server-rpms",
"rhel-7-server-extras-rpms",
},
"8": {
"rhel-8-for-x86_64-baseos-rpms",
"rhel-8-for-x86_64-appstream-rpms",
},
"9": {
"rhel-9-for-x86_64-baseos-rpms",
"rhel-9-for-x86_64-appstream-rpms",
},
}
redhatEOLDates = map[string]time.Time{
"4": time.Date(2017, 5, 31, 23, 59, 59, 0, time.UTC),
"5": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
"6": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
// N/A
"7": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC),
"8": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC),
"9": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC),
}
centosEOLDates = map[string]time.Time{
"3": time.Date(2010, 10, 31, 23, 59, 59, 0, time.UTC),
"4": time.Date(2012, 2, 29, 23, 59, 59, 0, time.UTC),
"5": time.Date(2017, 3, 31, 23, 59, 59, 0, time.UTC),
"6": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
"7": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
"8": time.Date(2021, 12, 31, 23, 59, 59, 0, time.UTC),
}
excludedVendorsSuffix = []string{
".remi",
}
)
// Scanner implements the RedHat scanner
type Scanner struct {
vs redhat.VulnSrc
}
// NewScanner is the factory method for Scanner
func NewScanner() *Scanner {
return &Scanner{
vs: redhat.NewVulnSrc(),
}
}
// Detect scans and returns redhat vulnerabilities
func (s *Scanner) Detect(ctx context.Context, osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
osVer = osver.Major(osVer)
log.InfoContext(ctx, "Detecting RHEL/CentOS vulnerabilities...", log.String("os_version", osVer),
log.Int("pkg_num", len(pkgs)))
var vulns []types.DetectedVulnerability
for _, pkg := range pkgs {
if !isFromSupportedVendor(pkg) {
log.DebugContext(ctx, "Skipping the package with unsupported vendor", log.String("package", pkg.Name))
continue
}
detectedVulns, err := s.detect(osVer, pkg)
if err != nil {
return nil, xerrors.Errorf("redhat vulnerability detection error: %w", err)
}
vulns = append(vulns, detectedVulns...)
}
return vulns, nil
}
func (s *Scanner) detect(osVer string, pkg ftypes.Package) ([]types.DetectedVulnerability, error) {
// For Red Hat OVAL v2 containing only binary package names
pkgName := addModularNamespace(pkg.Name, pkg.Modularitylabel)
var contentSets []string
var nvr string
if pkg.BuildInfo == nil {
contentSets = defaultContentSets[osVer]
} else {
contentSets = pkg.BuildInfo.ContentSets
nvr = fmt.Sprintf("%s-%s", pkg.BuildInfo.Nvr, pkg.BuildInfo.Arch)
}
advisories, err := s.vs.Get(pkgName, contentSets, []string{nvr})
if err != nil {
return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err)
}
installed := utils.FormatVersion(pkg)
installedVersion := version.NewVersion(installed)
uniqVulns := make(map[string]types.DetectedVulnerability)
for _, adv := range advisories {
// if Arches for advisory is empty or pkg.Arch is "noarch", then any Arches are affected
if len(adv.Arches) != 0 && pkg.Arch != "noarch" {
if !slices.Contains(adv.Arches, pkg.Arch) {
continue
}
}
vulnID := adv.VulnerabilityID
vuln := types.DetectedVulnerability{
VulnerabilityID: vulnID,
PkgID: pkg.ID,
PkgName: pkg.Name,
InstalledVersion: utils.FormatVersion(pkg),
PkgIdentifier: pkg.Identifier,
Status: adv.Status,
Layer: pkg.Layer,
SeveritySource: vulnerability.RedHat,
Vulnerability: dbTypes.Vulnerability{
Severity: adv.Severity.String(),
},
Custom: adv.Custom,
}
// unpatched vulnerabilities
if adv.FixedVersion == "" {
// Red Hat may contain several advisories for the same vulnerability (RHSA advisories).
// To avoid overwriting the fixed version by mistake, we should skip unpatched vulnerabilities if they were added earlier
if _, ok := uniqVulns[vulnID]; !ok {
uniqVulns[vulnID] = vuln
}
continue
}
// patched vulnerabilities
fixedVersion := version.NewVersion(adv.FixedVersion)
if installedVersion.LessThan(fixedVersion) {
vuln.VendorIDs = adv.VendorIDs
vuln.FixedVersion = fixedVersion.String()
if v, ok := uniqVulns[vulnID]; ok {
// In case two advisories resolve the same CVE-ID.
// e.g. The first fix might be incomplete.
v.VendorIDs = ustrings.Unique(append(v.VendorIDs, vuln.VendorIDs...))
// The newer fixed version should be taken.
if version.NewVersion(v.FixedVersion).LessThan(fixedVersion) {
v.FixedVersion = vuln.FixedVersion
}
uniqVulns[vulnID] = v
} else {
uniqVulns[vulnID] = vuln
}
}
}
vulns := lo.Values(uniqVulns)
sort.Slice(vulns, func(i, j int) bool {
return vulns[i].VulnerabilityID < vulns[j].VulnerabilityID
})
return vulns, nil
}
// IsSupportedVersion checks is OSFamily can be scanned with Redhat scanner
func (s *Scanner) IsSupportedVersion(ctx context.Context, osFamily ftypes.OSType, osVer string) bool {
osVer = osver.Major(osVer)
if osFamily == ftypes.CentOS {
return osver.Supported(ctx, centosEOLDates, osFamily, osVer)
}
return osver.Supported(ctx, redhatEOLDates, osFamily, osVer)
}
func isFromSupportedVendor(pkg ftypes.Package) bool {
for _, suffix := range excludedVendorsSuffix {
if strings.HasSuffix(pkg.Release, suffix) {
return false
}
}
return true
}
func addModularNamespace(name, label string) string {
// e.g. npm, nodejs:12:8030020201124152102:229f0a1c => nodejs:12::npm
var count int
for i, r := range label {
if r == ':' {
count++
}
if count == 2 {
return label[:i] + "::" + name
}
}
return name
}