github.com/quay/claircore@v1.5.28/python/matcher.go (about)

     1  package python
     2  
     3  import (
     4  	"context"
     5  	"net/url"
     6  
     7  	"github.com/quay/zlog"
     8  
     9  	"github.com/quay/claircore"
    10  	"github.com/quay/claircore/libvuln/driver"
    11  	"github.com/quay/claircore/pkg/pep440"
    12  )
    13  
    14  var _ driver.Matcher = (*Matcher)(nil)
    15  
    16  // Matcher attempts to correlate discovered python packages with reported
    17  // vulnerabilities.
    18  type Matcher struct{}
    19  
    20  // Name implements driver.Matcher.
    21  func (*Matcher) Name() string { return "python" }
    22  
    23  // Filter implements driver.Matcher.
    24  func (*Matcher) Filter(record *claircore.IndexRecord) bool {
    25  	return record.Package.NormalizedVersion.Kind == "pep440"
    26  }
    27  
    28  // Query implements driver.Matcher.
    29  func (*Matcher) Query() []driver.MatchConstraint {
    30  	return []driver.MatchConstraint{driver.RepositoryName}
    31  }
    32  
    33  // Vulnerable implements driver.Matcher.
    34  func (*Matcher) Vulnerable(ctx context.Context, record *claircore.IndexRecord, vuln *claircore.Vulnerability) (bool, error) {
    35  	// TODO(ross): This is a common pattern for OSV vulnerabilities. This should be moved into
    36  	// a common place for all OSV vulnerability matchers.
    37  
    38  	if vuln.FixedInVersion == "" {
    39  		return true, nil
    40  	}
    41  
    42  	// Parse the package first. If it cannot be parsed, it cannot properly be analyzed for vulnerabilities.
    43  	rv, err := pep440.Parse(record.Package.Version)
    44  	if err != nil {
    45  		zlog.Warn(ctx).
    46  			Str("package", record.Package.Name).
    47  			Stringer("version", &rv).
    48  			Msg("unable to parse python package version")
    49  		return false, err
    50  	}
    51  
    52  	decodedVersions, err := url.ParseQuery(vuln.FixedInVersion)
    53  	if err != nil {
    54  		return false, err
    55  	}
    56  
    57  	introduced := decodedVersions.Get("introduced")
    58  	// If there is an introduced version, check if the package's version is lower.
    59  	if introduced != "" {
    60  		iv, err := pep440.Parse(introduced)
    61  		if err != nil {
    62  			zlog.Warn(ctx).
    63  				Str("package", vuln.Package.Name).
    64  				Str("version", introduced).
    65  				Msg("unable to parse python introduced version")
    66  			return false, err
    67  		}
    68  		// If the package's version is less than the introduced version, it's not vulnerable.
    69  		if rv.Compare(&iv) < 0 {
    70  			return false, nil
    71  		}
    72  	}
    73  
    74  	fixedVersion := decodedVersions.Get("fixed")
    75  	lastAffected := decodedVersions.Get("lastAffected")
    76  	switch {
    77  	case fixedVersion != "":
    78  		uv, err := pep440.Parse(fixedVersion)
    79  		if err != nil {
    80  			zlog.Warn(ctx).
    81  				Str("package", vuln.Package.Name).
    82  				Str("version", fixedVersion).
    83  				Msg("unable to parse python fixed version")
    84  			return false, err
    85  		}
    86  		// The package is affected if its version is less than the fixed version.
    87  		return rv.Compare(&uv) < 0, nil
    88  	case lastAffected != "":
    89  		la, err := pep440.Parse(lastAffected)
    90  		if err != nil {
    91  			zlog.Warn(ctx).
    92  				Str("package", vuln.Package.Name).
    93  				Str("version", lastAffected).
    94  				Msg("unable to parse python last_affected version")
    95  			return false, err
    96  		}
    97  		// The package is affected if its version is less than or equal to the last affected version.
    98  		return rv.Compare(&la) <= 0, nil
    99  	}
   100  
   101  	// Just say the package is vulnerable, by default.
   102  	return true, nil
   103  }