github.com/devseccon/trivy@v0.47.1-0.20231123133102-bd902a0bd996/pkg/detector/ospkg/ubuntu/ubuntu.go (about)

     1  package ubuntu
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	version "github.com/knqyf263/go-deb-version"
     8  	"golang.org/x/xerrors"
     9  	"k8s.io/utils/clock"
    10  
    11  	"github.com/aquasecurity/trivy-db/pkg/vulnsrc/ubuntu"
    12  	osver "github.com/devseccon/trivy/pkg/detector/ospkg/version"
    13  	ftypes "github.com/devseccon/trivy/pkg/fanal/types"
    14  	"github.com/devseccon/trivy/pkg/log"
    15  	"github.com/devseccon/trivy/pkg/scanner/utils"
    16  	"github.com/devseccon/trivy/pkg/types"
    17  )
    18  
    19  var (
    20  	eolDates = map[string]time.Time{
    21  		"4.10":      time.Date(2006, 4, 30, 23, 59, 59, 0, time.UTC),
    22  		"5.04":      time.Date(2006, 10, 31, 23, 59, 59, 0, time.UTC),
    23  		"5.10":      time.Date(2007, 4, 13, 23, 59, 59, 0, time.UTC),
    24  		"6.06":      time.Date(2011, 6, 1, 23, 59, 59, 0, time.UTC),
    25  		"6.10":      time.Date(2008, 4, 25, 23, 59, 59, 0, time.UTC),
    26  		"7.04":      time.Date(2008, 10, 19, 23, 59, 59, 0, time.UTC),
    27  		"7.10":      time.Date(2009, 4, 18, 23, 59, 59, 0, time.UTC),
    28  		"8.04":      time.Date(2013, 5, 9, 23, 59, 59, 0, time.UTC),
    29  		"8.10":      time.Date(2010, 4, 30, 23, 59, 59, 0, time.UTC),
    30  		"9.04":      time.Date(2010, 10, 23, 23, 59, 59, 0, time.UTC),
    31  		"9.10":      time.Date(2011, 4, 29, 23, 59, 59, 0, time.UTC),
    32  		"10.04":     time.Date(2015, 4, 29, 23, 59, 59, 0, time.UTC),
    33  		"10.10":     time.Date(2012, 4, 10, 23, 59, 59, 0, time.UTC),
    34  		"11.04":     time.Date(2012, 10, 28, 23, 59, 59, 0, time.UTC),
    35  		"11.10":     time.Date(2013, 5, 9, 23, 59, 59, 0, time.UTC),
    36  		"12.04":     time.Date(2019, 4, 26, 23, 59, 59, 0, time.UTC),
    37  		"12.04-ESM": time.Date(2019, 4, 28, 23, 59, 59, 0, time.UTC),
    38  		"12.10":     time.Date(2014, 5, 16, 23, 59, 59, 0, time.UTC),
    39  		"13.04":     time.Date(2014, 1, 27, 23, 59, 59, 0, time.UTC),
    40  		"13.10":     time.Date(2014, 7, 17, 23, 59, 59, 0, time.UTC),
    41  		"14.04":     time.Date(2022, 4, 25, 23, 59, 59, 0, time.UTC),
    42  		"14.04-ESM": time.Date(2024, 4, 25, 23, 59, 59, 0, time.UTC),
    43  		"14.10":     time.Date(2015, 7, 23, 23, 59, 59, 0, time.UTC),
    44  		"15.04":     time.Date(2016, 1, 23, 23, 59, 59, 0, time.UTC),
    45  		"15.10":     time.Date(2016, 7, 22, 23, 59, 59, 0, time.UTC),
    46  		"16.04":     time.Date(2021, 4, 21, 23, 59, 59, 0, time.UTC),
    47  		"16.04-ESM": time.Date(2026, 4, 29, 23, 59, 59, 0, time.UTC),
    48  		"16.10":     time.Date(2017, 7, 20, 23, 59, 59, 0, time.UTC),
    49  		"17.04":     time.Date(2018, 1, 13, 23, 59, 59, 0, time.UTC),
    50  		"17.10":     time.Date(2018, 7, 19, 23, 59, 59, 0, time.UTC),
    51  		"18.04":     time.Date(2023, 5, 31, 23, 59, 59, 0, time.UTC),
    52  		"18.04-ESM": time.Date(2028, 3, 31, 23, 59, 59, 0, time.UTC),
    53  		"18.10":     time.Date(2019, 7, 18, 23, 59, 59, 0, time.UTC),
    54  		"19.04":     time.Date(2020, 1, 18, 23, 59, 59, 0, time.UTC),
    55  		"19.10":     time.Date(2020, 7, 17, 23, 59, 59, 0, time.UTC),
    56  		"20.04":     time.Date(2025, 4, 23, 23, 59, 59, 0, time.UTC),
    57  		"20.10":     time.Date(2021, 7, 22, 23, 59, 59, 0, time.UTC),
    58  		"21.04":     time.Date(2022, 1, 20, 23, 59, 59, 0, time.UTC),
    59  		"21.10":     time.Date(2022, 7, 14, 23, 59, 59, 0, time.UTC),
    60  		"22.04":     time.Date(2027, 4, 23, 23, 59, 59, 0, time.UTC),
    61  		"22.10":     time.Date(2023, 7, 20, 23, 59, 59, 0, time.UTC),
    62  		"23.04":     time.Date(2024, 1, 20, 23, 59, 59, 0, time.UTC),
    63  	}
    64  )
    65  
    66  type options struct {
    67  	clock clock.Clock
    68  }
    69  
    70  type option func(*options)
    71  
    72  func WithClock(c clock.Clock) option {
    73  	return func(opts *options) {
    74  		opts.clock = c
    75  	}
    76  }
    77  
    78  // Scanner implements the Ubuntu scanner
    79  type Scanner struct {
    80  	vs ubuntu.VulnSrc
    81  	*options
    82  }
    83  
    84  // NewScanner is the factory method for Scanner
    85  func NewScanner(opts ...option) *Scanner {
    86  	o := &options{
    87  		clock: clock.RealClock{},
    88  	}
    89  
    90  	for _, opt := range opts {
    91  		opt(o)
    92  	}
    93  	return &Scanner{
    94  		vs:      ubuntu.NewVulnSrc(),
    95  		options: o,
    96  	}
    97  }
    98  
    99  // Detect scans and returns the vulnerabilities
   100  func (s *Scanner) Detect(osVer string, _ *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
   101  	log.Logger.Info("Detecting Ubuntu vulnerabilities...")
   102  	log.Logger.Debugf("ubuntu: os version: %s", osVer)
   103  	log.Logger.Debugf("ubuntu: the number of packages: %d", len(pkgs))
   104  
   105  	var vulns []types.DetectedVulnerability
   106  	for _, pkg := range pkgs {
   107  		osVer = s.versionFromEolDates(osVer)
   108  		advisories, err := s.vs.Get(osVer, pkg.SrcName)
   109  		if err != nil {
   110  			return nil, xerrors.Errorf("failed to get Ubuntu advisories: %w", err)
   111  		}
   112  
   113  		sourceVersion, err := version.NewVersion(utils.FormatSrcVersion(pkg))
   114  		if err != nil {
   115  			log.Logger.Debugf("failed to parse Ubuntu installed package version: %w", err)
   116  			continue
   117  		}
   118  
   119  		for _, adv := range advisories {
   120  			vuln := types.DetectedVulnerability{
   121  				VulnerabilityID:  adv.VulnerabilityID,
   122  				PkgID:            pkg.ID,
   123  				PkgName:          pkg.Name,
   124  				InstalledVersion: utils.FormatVersion(pkg),
   125  				FixedVersion:     adv.FixedVersion,
   126  				PkgRef:           pkg.Ref,
   127  				Layer:            pkg.Layer,
   128  				Custom:           adv.Custom,
   129  				DataSource:       adv.DataSource,
   130  			}
   131  
   132  			if adv.FixedVersion == "" {
   133  				vulns = append(vulns, vuln)
   134  				continue
   135  			}
   136  
   137  			fixedVersion, err := version.NewVersion(adv.FixedVersion)
   138  			if err != nil {
   139  				log.Logger.Debugf("failed to parse Ubuntu package version: %w", err)
   140  				continue
   141  			}
   142  
   143  			if sourceVersion.LessThan(fixedVersion) {
   144  				vulns = append(vulns, vuln)
   145  			}
   146  		}
   147  	}
   148  	return vulns, nil
   149  }
   150  
   151  // IsSupportedVersion checks is OSFamily can be scanned using Ubuntu scanner
   152  func (s *Scanner) IsSupportedVersion(osFamily ftypes.OSType, osVer string) bool {
   153  	return osver.Supported(s.clock, eolDates, osFamily, osVer)
   154  }
   155  
   156  // versionFromEolDates checks if actual (not ESM) version is not outdated
   157  func (s *Scanner) versionFromEolDates(osVer string) string {
   158  	if _, ok := eolDates[osVer]; ok {
   159  		return osVer
   160  	}
   161  
   162  	// if base version (not ESM) is still actual
   163  	// we need to use this version
   164  	// e.g. Ubuntu doesn't have vulnerabilities for `18.04-ESM`, because `18.04` is not outdated
   165  	// then we need to get vulnerabilities for `18.04`
   166  	// if `18.04` is outdated - we need to use `18.04-ESM` (we will return error until we add `18.04-ESM` to eolDates)
   167  	ver := strings.TrimRight(osVer, "-ESM")
   168  	if eol, ok := eolDates[ver]; ok && s.clock.Now().Before(eol) {
   169  		return ver
   170  	}
   171  	return osVer
   172  }