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

     1  package alpine
     2  
     3  import (
     4  	"strings"
     5  	"time"
     6  
     7  	version "github.com/knqyf263/go-apk-version"
     8  	"golang.org/x/xerrors"
     9  	"k8s.io/utils/clock"
    10  
    11  	dbTypes "github.com/aquasecurity/trivy-db/pkg/types"
    12  	"github.com/aquasecurity/trivy-db/pkg/vulnsrc/alpine"
    13  	osver "github.com/devseccon/trivy/pkg/detector/ospkg/version"
    14  	ftypes "github.com/devseccon/trivy/pkg/fanal/types"
    15  	"github.com/devseccon/trivy/pkg/log"
    16  	"github.com/devseccon/trivy/pkg/scanner/utils"
    17  	"github.com/devseccon/trivy/pkg/types"
    18  )
    19  
    20  var (
    21  	eolDates = map[string]time.Time{
    22  		"2.0":  time.Date(2012, 4, 1, 23, 59, 59, 0, time.UTC),
    23  		"2.1":  time.Date(2012, 11, 1, 23, 59, 59, 0, time.UTC),
    24  		"2.2":  time.Date(2013, 5, 1, 23, 59, 59, 0, time.UTC),
    25  		"2.3":  time.Date(2013, 11, 1, 23, 59, 59, 0, time.UTC),
    26  		"2.4":  time.Date(2014, 5, 1, 23, 59, 59, 0, time.UTC),
    27  		"2.5":  time.Date(2014, 11, 1, 23, 59, 59, 0, time.UTC),
    28  		"2.6":  time.Date(2015, 5, 1, 23, 59, 59, 0, time.UTC),
    29  		"2.7":  time.Date(2015, 11, 1, 23, 59, 59, 0, time.UTC),
    30  		"3.0":  time.Date(2016, 5, 1, 23, 59, 59, 0, time.UTC),
    31  		"3.1":  time.Date(2016, 11, 1, 23, 59, 59, 0, time.UTC),
    32  		"3.2":  time.Date(2017, 5, 1, 23, 59, 59, 0, time.UTC),
    33  		"3.3":  time.Date(2017, 11, 1, 23, 59, 59, 0, time.UTC),
    34  		"3.4":  time.Date(2018, 5, 1, 23, 59, 59, 0, time.UTC),
    35  		"3.5":  time.Date(2018, 11, 1, 23, 59, 59, 0, time.UTC),
    36  		"3.6":  time.Date(2019, 5, 1, 23, 59, 59, 0, time.UTC),
    37  		"3.7":  time.Date(2019, 11, 1, 23, 59, 59, 0, time.UTC),
    38  		"3.8":  time.Date(2020, 5, 1, 23, 59, 59, 0, time.UTC),
    39  		"3.9":  time.Date(2020, 11, 1, 23, 59, 59, 0, time.UTC),
    40  		"3.10": time.Date(2021, 5, 1, 23, 59, 59, 0, time.UTC),
    41  		"3.11": time.Date(2021, 11, 1, 23, 59, 59, 0, time.UTC),
    42  		"3.12": time.Date(2022, 5, 1, 23, 59, 59, 0, time.UTC),
    43  		"3.13": time.Date(2022, 11, 1, 23, 59, 59, 0, time.UTC),
    44  		"3.14": time.Date(2023, 5, 1, 23, 59, 59, 0, time.UTC),
    45  		"3.15": time.Date(2023, 11, 1, 23, 59, 59, 0, time.UTC),
    46  		"3.16": time.Date(2024, 5, 23, 23, 59, 59, 0, time.UTC),
    47  		"3.17": time.Date(2024, 11, 22, 23, 59, 59, 0, time.UTC),
    48  		"3.18": time.Date(2025, 5, 9, 23, 59, 59, 0, time.UTC),
    49  		"edge": time.Date(9999, 1, 1, 0, 0, 0, 0, time.UTC),
    50  	}
    51  )
    52  
    53  type options struct {
    54  	clock clock.Clock
    55  }
    56  
    57  type option func(*options)
    58  
    59  func WithClock(c clock.Clock) option {
    60  	return func(opts *options) {
    61  		opts.clock = c
    62  	}
    63  }
    64  
    65  // Scanner implements the Alpine scanner
    66  type Scanner struct {
    67  	vs alpine.VulnSrc
    68  	*options
    69  }
    70  
    71  // NewScanner is the factory method for Scanner
    72  func NewScanner(opts ...option) *Scanner {
    73  	o := &options{
    74  		clock: clock.RealClock{},
    75  	}
    76  
    77  	for _, opt := range opts {
    78  		opt(o)
    79  	}
    80  	return &Scanner{
    81  		vs:      alpine.NewVulnSrc(),
    82  		options: o,
    83  	}
    84  }
    85  
    86  // Detect vulnerabilities in package using Alpine scanner
    87  func (s *Scanner) Detect(osVer string, repo *ftypes.Repository, pkgs []ftypes.Package) ([]types.DetectedVulnerability, error) {
    88  	log.Logger.Info("Detecting Alpine vulnerabilities...")
    89  	osVer = osver.Minor(osVer)
    90  	repoRelease := s.repoRelease(repo)
    91  
    92  	log.Logger.Debugf("alpine: os version: %s", osVer)
    93  	log.Logger.Debugf("alpine: package repository: %s", repoRelease)
    94  	log.Logger.Debugf("alpine: the number of packages: %d", len(pkgs))
    95  
    96  	stream := osVer
    97  	if repoRelease != "" && osVer != repoRelease {
    98  		// Prefer the repository release. Use OS version only when the repository is not detected.
    99  		stream = repoRelease
   100  		if repoRelease != "edge" { // TODO: we should detect the current edge version.
   101  			log.Logger.Warnf("Mixing Alpine versions is unsupported, OS: '%s', repository: '%s'", osVer, repoRelease)
   102  		}
   103  	}
   104  
   105  	var vulns []types.DetectedVulnerability
   106  	for _, pkg := range pkgs {
   107  		srcName := pkg.SrcName
   108  		if srcName == "" {
   109  			srcName = pkg.Name
   110  		}
   111  		advisories, err := s.vs.Get(stream, srcName)
   112  		if err != nil {
   113  			return nil, xerrors.Errorf("failed to get alpine advisories: %w", err)
   114  		}
   115  
   116  		sourceVersion, err := version.NewVersion(utils.FormatSrcVersion(pkg))
   117  		if err != nil {
   118  			log.Logger.Debugf("failed to parse Alpine Linux installed package version: %s", err)
   119  			continue
   120  		}
   121  
   122  		for _, adv := range advisories {
   123  			if !s.isVulnerable(sourceVersion, adv) {
   124  				continue
   125  			}
   126  			vulns = append(vulns, types.DetectedVulnerability{
   127  				VulnerabilityID:  adv.VulnerabilityID,
   128  				PkgID:            pkg.ID,
   129  				PkgName:          pkg.Name,
   130  				InstalledVersion: utils.FormatVersion(pkg),
   131  				FixedVersion:     adv.FixedVersion,
   132  				Layer:            pkg.Layer,
   133  				PkgRef:           pkg.Ref,
   134  				Custom:           adv.Custom,
   135  				DataSource:       adv.DataSource,
   136  			})
   137  		}
   138  	}
   139  	return vulns, nil
   140  }
   141  
   142  func (s *Scanner) isVulnerable(installedVersion version.Version, adv dbTypes.Advisory) bool {
   143  	// This logic is for unfixed vulnerabilities, but Trivy DB doesn't have advisories for unfixed vulnerabilities for now
   144  	// because Alpine just provides potentially vulnerable packages. It will cause a lot of false positives.
   145  	// This is for DevSecCon commercial products.
   146  	if adv.AffectedVersion != "" {
   147  		// AffectedVersion means which version introduced this vulnerability.
   148  		affectedVersion, err := version.NewVersion(adv.AffectedVersion)
   149  		if err != nil {
   150  			log.Logger.Debugf("failed to parse Alpine Linux affected package version: %s", err)
   151  			return false
   152  		}
   153  		if affectedVersion.GreaterThan(installedVersion) {
   154  			return false
   155  		}
   156  	}
   157  
   158  	// This logic is also for unfixed vulnerabilities.
   159  	if adv.FixedVersion == "" {
   160  		// It means the unfixed vulnerability
   161  		return true
   162  	}
   163  
   164  	// Compare versions for fixed vulnerabilities
   165  	fixedVersion, err := version.NewVersion(adv.FixedVersion)
   166  	if err != nil {
   167  		log.Logger.Debugf("failed to parse Alpine Linux fixed version: %s", err)
   168  		return false
   169  	}
   170  
   171  	// It means the fixed vulnerability
   172  	return installedVersion.LessThan(fixedVersion)
   173  }
   174  
   175  // IsSupportedVersion checks if the version is supported.
   176  func (s *Scanner) IsSupportedVersion(osFamily ftypes.OSType, osVer string) bool {
   177  	return osver.Supported(s.clock, eolDates, osFamily, osver.Minor(osVer))
   178  }
   179  
   180  func (s *Scanner) repoRelease(repo *ftypes.Repository) string {
   181  	if repo == nil {
   182  		return ""
   183  	}
   184  	release := repo.Release
   185  	if strings.Count(release, ".") > 1 {
   186  		release = release[:strings.LastIndex(release, ".")]
   187  	}
   188  	return release
   189  }