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  }