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 }