From 938087098dbb405d1a8b753abf10e9dac2a6946b Mon Sep 17 00:00:00 2001 From: crozzy Date: Thu, 9 May 2024 10:09:27 -0700 Subject: [PATCH] rhel: move IgnoreUnpatched config key from updater to matcher Previously the IgnoreUnpatched config key was a part of the RHEL updater and would dictate whether or not the updater would ingest unpatched vulnerabilities. This change moves that key to the RHEL matcher and dictates whether the matcher should check for a fixed_in_version when querying potential vulnerabilities. This makes the config option more usable at the expense of DB size. Signed-off-by: crozzy --- datastore/postgres/querybuilder.go | 3 ++ datastore/postgres/querybuilder_test.go | 14 ++++++++ libvuln/driver/matcher.go | 2 ++ matchers/defaults/defaults.go | 2 +- rhel/matcher.go | 12 ++++--- rhel/matcherfactory.go | 45 +++++++++++++++++++++++++ rhel/vex/parser.go | 10 +++--- rhel/vex/updater.go | 17 ++++------ 8 files changed, 83 insertions(+), 22 deletions(-) create mode 100644 rhel/matcherfactory.go diff --git a/datastore/postgres/querybuilder.go b/datastore/postgres/querybuilder.go index 859eb1631..f5dc093b1 100644 --- a/datastore/postgres/querybuilder.go +++ b/datastore/postgres/querybuilder.go @@ -7,6 +7,7 @@ import ( "github.com/doug-martin/goqu/v8" _ "github.com/doug-martin/goqu/v8/dialect/postgres" + "github.com/doug-martin/goqu/v8/exp" "github.com/quay/claircore" "github.com/quay/claircore/datastore" @@ -70,6 +71,8 @@ func buildGetQuery(record *claircore.IndexRecord, opts *datastore.GetOpts) (stri ex = goqu.Ex{"dist_arch": record.Distribution.Arch} case driver.RepositoryName: ex = goqu.Ex{"repo_name": record.Repository.Name} + case driver.HasFixedInVersion: + ex = goqu.Ex{"fixed_in_version": goqu.Op{exp.NeqOp.String(): ""}} default: return "", fmt.Errorf("was provided unknown matcher: %v", m) } diff --git a/datastore/postgres/querybuilder_test.go b/datastore/postgres/querybuilder_test.go index 707644aa4..fefee94f2 100644 --- a/datastore/postgres/querybuilder_test.go +++ b/datastore/postgres/querybuilder_test.go @@ -202,6 +202,20 @@ func TestGetQueryBuilderDeterministicArgs(t *testing.T) { } }, }, + { + name: "FixedInVersion", + expectedQuery: preamble + both + + `("fixed_in_version" != '')` + epilogue, + matchExps: []driver.MatchConstraint{driver.HasFixedInVersion}, + indexRecord: func() *claircore.IndexRecord { + pkgs := test.GenUniquePackages(1) + dists := test.GenUniqueDistributions(1) + return &claircore.IndexRecord{ + Package: pkgs[0], + Distribution: dists[0], + } + }, + }, } // This is safe to do because SQL doesn't care about what whitespace is diff --git a/libvuln/driver/matcher.go b/libvuln/driver/matcher.go index 97a1e0460..d2217b1e5 100644 --- a/libvuln/driver/matcher.go +++ b/libvuln/driver/matcher.go @@ -41,6 +41,8 @@ const ( DistributionPrettyName // should match claircore.Package.Repository.Name => claircore.Vulnerability.Package.Repository.Name RepositoryName + // should match claircore.Vulnerability.FixedInVersion != "" + HasFixedInVersion ) // Matcher is an interface which a Controller uses to query the vulnstore for vulnerabilities. diff --git a/matchers/defaults/defaults.go b/matchers/defaults/defaults.go index 13a8f1ea6..774e068f6 100644 --- a/matchers/defaults/defaults.go +++ b/matchers/defaults/defaults.go @@ -53,13 +53,13 @@ var defaultMatchers = []driver.Matcher{ &photon.Matcher{}, &python.Matcher{}, rhcc.Matcher, - &rhel.Matcher{}, &ruby.Matcher{}, &suse.Matcher{}, &ubuntu.Matcher{}, } func inner(ctx context.Context) error { + registry.Register("rhel", &rhel.MatcherFactory{}) for _, m := range defaultMatchers { mf := driver.MatcherStatic(m) registry.Register(m.Name(), mf) diff --git a/rhel/matcher.go b/rhel/matcher.go index 500ad348d..daa0334d1 100644 --- a/rhel/matcher.go +++ b/rhel/matcher.go @@ -13,7 +13,9 @@ import ( ) // Matcher implements driver.Matcher. -type Matcher struct{} +type Matcher struct { + ignoreUnpatched bool +} var _ driver.Matcher = (*Matcher)(nil) @@ -28,10 +30,12 @@ func (*Matcher) Filter(record *claircore.IndexRecord) bool { } // Query implements driver.Matcher. -func (*Matcher) Query() []driver.MatchConstraint { - return []driver.MatchConstraint{ - driver.PackageModule, +func (m *Matcher) Query() []driver.MatchConstraint { + mcs := []driver.MatchConstraint{driver.PackageModule} + if m.ignoreUnpatched { + mcs = append(mcs, driver.HasFixedInVersion) } + return mcs } // IsCPESubstringMatch is a Red Hat specific hack that handles the "CPE patterns" in the VEX diff --git a/rhel/matcherfactory.go b/rhel/matcherfactory.go new file mode 100644 index 000000000..135eded2c --- /dev/null +++ b/rhel/matcherfactory.go @@ -0,0 +1,45 @@ +package rhel + +import ( + "context" + "net/http" + + "github.com/quay/zlog" + + "github.com/quay/claircore/libvuln/driver" +) + +var ( + _ driver.MatcherFactory = (*MatcherFactory)(nil) + _ driver.MatcherConfigurable = (*MatcherFactory)(nil) +) + +type MatcherFactory struct { + ignoreUnpatched bool +} + +// MatcherFactory implements [driver.MatcherFactory] +func (f *MatcherFactory) Matcher(ctx context.Context) ([]driver.Matcher, error) { + m := &Matcher{ + ignoreUnpatched: f.ignoreUnpatched, + } + return []driver.Matcher{m}, nil +} + +type MatcherFactoryConfig struct { + IgnoreUnpatched bool `json:"ignore_unpatched" yaml:"ignore_unpatched"` +} + +// MatcherFactory implements driver.MatcherConfigurable. +func (f *MatcherFactory) Configure(ctx context.Context, cfg driver.MatcherConfigUnmarshaler, _ *http.Client) error { + var fc MatcherFactoryConfig + if err := cfg(&fc); err != nil { + return err + } + f.ignoreUnpatched = fc.IgnoreUnpatched + zlog.Info(ctx). + Str("component", "rhel/MatcherFactory.Configure"). + Bool("ignore_unpatched", f.ignoreUnpatched). + Msg("configured") + return nil +} diff --git a/rhel/vex/parser.go b/rhel/vex/parser.go index 7ef72dc47..84c615e61 100644 --- a/rhel/vex/parser.go +++ b/rhel/vex/parser.go @@ -92,13 +92,11 @@ func (u *Updater) DeltaParse(ctx context.Context, contents io.ReadCloser) ([]*cl return nil, nil, err } out[name] = fixedVulns - if !u.ignoreUnpatched { - knownAffectedVulns, err := creator.knownAffectedVulnerabilities(ctx, v, protoVuln) - if err != nil { - return nil, nil, err - } - out[name] = append(out[name], knownAffectedVulns...) + knownAffectedVulns, err := creator.knownAffectedVulnerabilities(ctx, v, protoVuln) + if err != nil { + return nil, nil, err } + out[name] = append(out[name], knownAffectedVulns...) } } vulns := []*claircore.Vulnerability{} diff --git a/rhel/vex/updater.go b/rhel/vex/updater.go index 8b0cc9d1b..01cf3e17b 100644 --- a/rhel/vex/updater.go +++ b/rhel/vex/updater.go @@ -40,18 +40,16 @@ const ( // // [Configure] must be called before [UpdaterSet]. type Factory struct { - c *http.Client - base *url.URL - ignoreUnpatched bool + c *http.Client + base *url.URL } // UpdaterSet constructs one Updater func (f *Factory) UpdaterSet(_ context.Context) (driver.UpdaterSet, error) { us := driver.NewUpdaterSet() u := &Updater{ - url: f.base, - client: f.c, - ignoreUnpatched: f.ignoreUnpatched, + url: f.base, + client: f.c, } err := us.Add(u) if err != nil { @@ -69,8 +67,6 @@ type FactoryConfig struct { // // Must include the trailing slash. URL string `json:"url" yaml:"url"` - // IgnoreUnpatched dictates whether to ingest known affected advisories from the VEX security data. - IgnoreUnpatched bool `json:"ignore_unpatched" yaml:"ignore_unpatched"` } // Configure implements driver.Configurable @@ -98,9 +94,8 @@ func (f *Factory) Configure(ctx context.Context, cf driver.ConfigUnmarshaler, c // Updater is responsible from reading VEX data served at the URL // and creating vulnerabilities. type Updater struct { - url *url.URL - client *http.Client - ignoreUnpatched bool + url *url.URL + client *http.Client } // fingerprint is used to track the state of the changes.csv and deletions.csv endpoints.