github.com/quay/claircore@v1.5.28/internal/matcher/controller.go (about) 1 package matcher 2 3 import ( 4 "context" 5 "time" 6 7 "github.com/quay/zlog" 8 9 "github.com/quay/claircore" 10 "github.com/quay/claircore/datastore" 11 "github.com/quay/claircore/libvuln/driver" 12 ) 13 14 // Controller is a control structure used to find vulnerabilities affecting 15 // a set of packages. 16 type Controller struct { 17 // an implemented Matcher 18 m driver.Matcher 19 // a vulnstore.Vulnerability instance for querying vulnerabilities 20 store datastore.Vulnerability 21 } 22 23 // NewController is a constructor for a Controller 24 func NewController(m driver.Matcher, store datastore.Vulnerability) *Controller { 25 return &Controller{ 26 m: m, 27 store: store, 28 } 29 } 30 31 // Match is the entrypoint for [Controller]. 32 func (mc *Controller) Match(ctx context.Context, records []*claircore.IndexRecord) (map[string][]*claircore.Vulnerability, error) { 33 ctx = zlog.ContextWithValues(ctx, 34 "component", "internal/matcher/Controller.Match", 35 "matcher", mc.m.Name()) 36 // find the packages the matcher is interested in. 37 interested := mc.findInterested(records) 38 zlog.Debug(ctx). 39 Int("interested", len(interested)). 40 Int("records", len(records)). 41 Msg("interest") 42 43 // early return; do not call db at all 44 if len(interested) == 0 { 45 return map[string][]*claircore.Vulnerability{}, nil 46 } 47 48 remoteMatcher, matchedVulns, err := mc.queryRemoteMatcher(ctx, interested) 49 if remoteMatcher { 50 if err != nil { 51 zlog.Error(ctx).Err(err).Msg("remote matcher error, returning empty results") 52 return map[string][]*claircore.Vulnerability{}, nil 53 } 54 return matchedVulns, nil 55 } 56 57 dbSide, authoritative := mc.dbFilter() 58 zlog.Debug(ctx). 59 Bool("opt-in", dbSide). 60 Bool("authoritative", authoritative). 61 Msg("version filter compatible?") 62 63 // query the vulnstore 64 vulns, err := mc.query(ctx, interested, dbSide) 65 if err != nil { 66 return nil, err 67 } 68 zlog.Debug(ctx). 69 Int("vulnerabilities", len(vulns)). 70 Msg("query") 71 72 if authoritative { 73 return vulns, nil 74 } 75 // filter the vulns 76 filteredVulns, err := mc.filter(ctx, interested, vulns) 77 if err != nil { 78 return nil, err 79 } 80 zlog.Debug(ctx). 81 Int("filtered", len(filteredVulns)). 82 Msg("filtered") 83 return filteredVulns, nil 84 } 85 86 // If RemoteMatcher exists, it will call the matcher service which runs on a remote 87 // machine and fetches the vulnerabilities associated with the IndexRecords. 88 func (mc *Controller) queryRemoteMatcher(ctx context.Context, interested []*claircore.IndexRecord) (bool, map[string][]*claircore.Vulnerability, error) { 89 f, ok := mc.m.(driver.RemoteMatcher) 90 if !ok { 91 return false, nil, nil 92 } 93 tctx, cancel := context.WithTimeout(ctx, 60*time.Second) 94 defer cancel() 95 vulns, err := f.QueryRemoteMatcher(tctx, interested) 96 return true, vulns, err 97 } 98 99 // DbFilter reports whether the db-side version filtering can be used, and 100 // whether it's authoritative. 101 func (mc *Controller) dbFilter() (bool, bool) { 102 f, ok := mc.m.(driver.VersionFilter) 103 if !ok { 104 return false, false 105 } 106 return true, f.VersionAuthoritative() 107 } 108 109 func (mc *Controller) findInterested(records []*claircore.IndexRecord) []*claircore.IndexRecord { 110 out := []*claircore.IndexRecord{} 111 for _, record := range records { 112 if mc.m.Filter(record) { 113 out = append(out, record) 114 } 115 } 116 return out 117 } 118 119 // Query asks the Matcher how we should query the vulnstore then performs the query and returns all 120 // matched vulnerabilities. 121 func (mc *Controller) query(ctx context.Context, interested []*claircore.IndexRecord, dbSide bool) (map[string][]*claircore.Vulnerability, error) { 122 // ask the matcher how we should query the vulnstore 123 matchers := mc.m.Query() 124 getOpts := datastore.GetOpts{ 125 Matchers: matchers, 126 Debug: true, 127 VersionFiltering: dbSide, 128 } 129 matches, err := mc.store.Get(ctx, interested, getOpts) 130 if err != nil { 131 return nil, err 132 } 133 return matches, nil 134 } 135 136 // Filter method asks the matcher if the given package is affected by the returned vulnerability. if so; its added to a result map where the key is the package ID 137 // and the value is a Vulnerability. if not it is not added to the result. 138 func (mc *Controller) filter(ctx context.Context, interested []*claircore.IndexRecord, vulns map[string][]*claircore.Vulnerability) (map[string][]*claircore.Vulnerability, error) { 139 filtered := map[string][]*claircore.Vulnerability{} 140 for _, record := range interested { 141 match, err := filterVulns(ctx, mc.m, record, vulns[record.Package.ID]) 142 if err != nil { 143 return nil, err 144 } 145 filtered[record.Package.ID] = match 146 } 147 return filtered, nil 148 } 149 150 // filter returns only the vulnerabilities affected by the provided package. 151 func filterVulns(ctx context.Context, m driver.Matcher, record *claircore.IndexRecord, vulns []*claircore.Vulnerability) ([]*claircore.Vulnerability, error) { 152 filtered := []*claircore.Vulnerability{} 153 for _, vuln := range vulns { 154 match, err := m.Vulnerable(ctx, record, vuln) 155 if err != nil { 156 return nil, err 157 } 158 if match { 159 filtered = append(filtered, vuln) 160 } 161 } 162 return filtered, nil 163 }