1package redhat
2
3import (
4	"strings"
5	"time"
6
7	"github.com/aquasecurity/trivy-db/pkg/vulnsrc/redhat"
8
9	version "github.com/knqyf263/go-rpm-version"
10	"golang.org/x/xerrors"
11
12	"github.com/aquasecurity/fanal/analyzer/os"
13	ftypes "github.com/aquasecurity/fanal/types"
14	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
15	"github.com/aquasecurity/trivy/pkg/log"
16	"github.com/aquasecurity/trivy/pkg/scanner/utils"
17	"github.com/aquasecurity/trivy/pkg/types"
18)
19
20var (
21	redhatEOLDates = map[string]time.Time{
22		"4": time.Date(2017, 5, 31, 23, 59, 59, 0, time.UTC),
23		"5": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
24		"6": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
25		// N/A
26		"7": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC),
27		"8": time.Date(3000, 1, 1, 23, 59, 59, 0, time.UTC),
28	}
29	centosEOLDates = map[string]time.Time{
30		"3": time.Date(2010, 10, 31, 23, 59, 59, 0, time.UTC),
31		"4": time.Date(2012, 2, 29, 23, 59, 59, 0, time.UTC),
32		"5": time.Date(2017, 3, 31, 23, 59, 59, 0, time.UTC),
33		"6": time.Date(2020, 11, 30, 23, 59, 59, 0, time.UTC),
34		"7": time.Date(2024, 6, 30, 23, 59, 59, 0, time.UTC),
35		// N/A
36		"8": time.Date(3000, 6, 30, 23, 59, 59, 0, time.UTC),
37	}
38)
39
40type Scanner struct {
41	vs dbTypes.VulnSrc
42}
43
44func NewScanner() *Scanner {
45	return &Scanner{
46		vs: redhat.NewVulnSrc(),
47	}
48}
49
50func (s *Scanner) Detect(osVer string, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
51	log.Logger.Info("Detecting RHEL/CentOS vulnerabilities...")
52	if strings.Count(osVer, ".") > 0 {
53		osVer = osVer[:strings.Index(osVer, ".")]
54	}
55	log.Logger.Debugf("redhat: os version: %s", osVer)
56	log.Logger.Debugf("redhat: the number of packages: %d", len(pkgs))
57
58	var vulns []types.DetectedVulnerability
59	for _, pkg := range pkgs {
60		// For Red Hat Security Data API containing only source package names
61		advisories, err := s.vs.Get(osVer, pkg.SrcName)
62		if err != nil {
63			return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err)
64		}
65
66		installed := utils.FormatVersion(pkg)
67		installedVersion := version.NewVersion(installed)
68
69		for _, adv := range advisories {
70			if adv.FixedVersion != "" {
71				continue
72			}
73			vuln := types.DetectedVulnerability{
74				VulnerabilityID:  adv.VulnerabilityID,
75				PkgName:          pkg.Name,
76				InstalledVersion: installed,
77				Layer:            pkg.Layer,
78			}
79			vulns = append(vulns, vuln)
80		}
81
82		// For Red Hat OVAL containing only binary package names
83		advisories, err = s.vs.Get(osVer, pkg.Name)
84		if err != nil {
85			return nil, xerrors.Errorf("failed to get Red Hat advisories: %w", err)
86		}
87
88		for _, adv := range advisories {
89			fixedVersion := version.NewVersion(adv.FixedVersion)
90			if installedVersion.LessThan(fixedVersion) {
91				vuln := types.DetectedVulnerability{
92					VulnerabilityID:  adv.VulnerabilityID,
93					PkgName:          pkg.Name,
94					InstalledVersion: installed,
95					FixedVersion:     fixedVersion.String(),
96					Layer:            pkg.Layer,
97				}
98				vulns = append(vulns, vuln)
99			}
100		}
101	}
102	return vulns, nil
103}
104
105func (s *Scanner) IsSupportedVersion(osFamily, osVer string) bool {
106	now := time.Now()
107	return s.isSupportedVersion(now, osFamily, osVer)
108}
109
110func (s *Scanner) isSupportedVersion(now time.Time, osFamily, osVer string) bool {
111	if strings.Count(osVer, ".") > 0 {
112		osVer = osVer[:strings.Index(osVer, ".")]
113	}
114
115	var eolDate time.Time
116	var ok bool
117	if osFamily == os.RedHat {
118		eolDate, ok = redhatEOLDates[osVer]
119	} else if osFamily == os.CentOS {
120		eolDate, ok = centosEOLDates[osVer]
121	}
122	if !ok {
123		log.Logger.Warnf("This OS version is not on the EOL list: %s %s", osFamily, osVer)
124		return false
125	}
126	return now.Before(eolDate)
127}
128