github.com/quay/claircore@v1.5.28/libvuln/libvuln.go (about) 1 package libvuln 2 3 import ( 4 "context" 5 "fmt" 6 "math/rand" 7 "reflect" 8 "time" 9 10 "github.com/google/uuid" 11 "github.com/quay/zlog" 12 "github.com/rs/zerolog" 13 14 "github.com/quay/claircore" 15 "github.com/quay/claircore/datastore" 16 "github.com/quay/claircore/internal/matcher" 17 "github.com/quay/claircore/libvuln/driver" 18 "github.com/quay/claircore/libvuln/updates" 19 "github.com/quay/claircore/matchers" 20 ) 21 22 // Libvuln exports methods for scanning an IndexReport and created 23 // a VulnerabilityReport. 24 // 25 // Libvuln also runs background updaters which keep the vulnerability 26 // database consistent. 27 type Libvuln struct { 28 store datastore.MatcherStore 29 locker LockSource 30 matchers []driver.Matcher 31 enrichers []driver.Enricher 32 updateRetention int 33 updaters *updates.Manager 34 } 35 36 // TODO (crozzy): Find a home for this and stop redefining it. 37 // LockSource abstracts over how locks are implemented. 38 // 39 // An online system needs distributed locks, offline use cases can use 40 // process-local locks. 41 type LockSource interface { 42 TryLock(context.Context, string) (context.Context, context.CancelFunc) 43 Lock(context.Context, string) (context.Context, context.CancelFunc) 44 Close(context.Context) error 45 } 46 47 // New creates a new instance of the Libvuln library 48 func New(ctx context.Context, opts *Options) (*Libvuln, error) { 49 ctx = zlog.ContextWithValues(ctx, "component", "libvuln/New") 50 51 // required 52 if opts.Store == nil { 53 return nil, fmt.Errorf("libvuln: must provide a Store") 54 } 55 if opts.UpdateRetention == 1 || opts.UpdateRetention < 0 { 56 return nil, fmt.Errorf("libvuln: must provide a valid UpdateRetention") 57 } 58 if opts.Client == nil { 59 return nil, fmt.Errorf("libvuln: must provide a *http.Client") 60 } 61 62 // optional 63 if opts.UpdateInterval == 0 || opts.UpdateInterval < time.Minute { 64 opts.UpdateInterval = updates.DefaultInterval 65 } 66 // This gives us a ±60 second range, rounded to the nearest tenth of a 67 // second. 68 const jitter = 120000 69 ms := time.Duration(rand.Intn(jitter)-(jitter/2)) * time.Microsecond 70 ms = ms.Round(100 * time.Millisecond) 71 opts.UpdateInterval += ms 72 73 if opts.UpdateWorkers <= 0 { 74 opts.UpdateWorkers = DefaultUpdateWorkers 75 } 76 77 if opts.UpdaterConfigs == nil { 78 opts.UpdaterConfigs = make(map[string]driver.ConfigUnmarshaler) 79 } 80 81 l := &Libvuln{ 82 store: opts.Store, 83 locker: opts.Locker, 84 updateRetention: opts.UpdateRetention, 85 enrichers: opts.Enrichers, 86 } 87 88 // create matchers based on the provided config. 89 var err error 90 l.matchers, err = matchers.NewMatchers(ctx, 91 opts.Client, 92 matchers.WithEnabled(opts.MatcherNames), 93 matchers.WithConfigs(opts.MatcherConfigs), 94 matchers.WithOutOfTree(opts.Matchers), 95 ) 96 if err != nil { 97 return nil, err 98 } 99 100 zlog.Info(ctx).Array("matchers", matcherLog(l.matchers)).Msg("matchers created") 101 102 l.updaters, err = updates.NewManager(ctx, 103 l.store, 104 l.locker, 105 opts.Client, 106 updates.WithBatchSize(opts.UpdateWorkers), 107 updates.WithInterval(opts.UpdateInterval), 108 updates.WithEnabled(opts.UpdaterSets), 109 updates.WithConfigs(opts.UpdaterConfigs), 110 updates.WithOutOfTree(opts.Updaters), 111 updates.WithGC(opts.UpdateRetention), 112 ) 113 if err != nil { 114 return nil, err 115 } 116 117 // launch background updater 118 if !opts.DisableBackgroundUpdates { 119 go l.updaters.Start(ctx) 120 } 121 122 zlog.Info(ctx).Msg("libvuln initialized") 123 return l, nil 124 } 125 126 func (l *Libvuln) Close(ctx context.Context) error { 127 l.locker.Close(ctx) 128 return nil 129 } 130 131 // FetchUpdates runs configured updaters. 132 func (l *Libvuln) FetchUpdates(ctx context.Context) error { 133 return l.updaters.Run(ctx) 134 } 135 136 // Scan creates a VulnerabilityReport given a manifest's IndexReport. 137 func (l *Libvuln) Scan(ctx context.Context, ir *claircore.IndexReport) (*claircore.VulnerabilityReport, error) { 138 if s, ok := l.store.(matcher.Store); ok { 139 return matcher.EnrichedMatch(ctx, ir, l.matchers, l.enrichers, s) 140 } 141 return matcher.Match(ctx, ir, l.matchers, l.store) 142 } 143 144 // UpdateOperations returns UpdateOperations in date descending order keyed by the 145 // Updater name 146 func (l *Libvuln) UpdateOperations(ctx context.Context, kind driver.UpdateKind, updaters ...string) (map[string][]driver.UpdateOperation, error) { 147 return l.store.GetUpdateOperations(ctx, kind, updaters...) 148 } 149 150 // DeleteUpdateOperations removes UpdateOperations. 151 // A call to GC or GCFull must be run after this to garbage collect vulnerabilities associated 152 // with the UpdateOperation. 153 // 154 // The number of UpdateOperations deleted is returned. 155 func (l *Libvuln) DeleteUpdateOperations(ctx context.Context, ref ...uuid.UUID) (int64, error) { 156 return l.store.DeleteUpdateOperations(ctx, ref...) 157 } 158 159 // UpdateDiff returns an UpdateDiff describing the changes between prev 160 // and cur. 161 func (l *Libvuln) UpdateDiff(ctx context.Context, prev, cur uuid.UUID) (*driver.UpdateDiff, error) { 162 return l.store.GetUpdateDiff(ctx, prev, cur) 163 } 164 165 // LatestUpdateOperations returns references for the latest update for every 166 // known updater. 167 // 168 // These references are okay to expose externally. 169 func (l *Libvuln) LatestUpdateOperations(ctx context.Context, kind driver.UpdateKind) (map[string][]driver.UpdateOperation, error) { 170 return l.store.GetLatestUpdateRefs(ctx, kind) 171 } 172 173 // LatestUpdateOperation returns a reference to the latest known update. 174 // 175 // This can be used by clients to determine if a call to Scan is likely to 176 // return new results. 177 func (l *Libvuln) LatestUpdateOperation(ctx context.Context, kind driver.UpdateKind) (uuid.UUID, error) { 178 return l.store.GetLatestUpdateRef(ctx, kind) 179 } 180 181 // GC will cleanup any update operations older then the configured UpdatesRetention value. 182 // GC is throttled and ensure its a good citizen to the database. 183 // 184 // The returned int is the number of outstanding UpdateOperations not deleted due to throttling. 185 // To run GC to completion use the GCFull method. 186 func (l *Libvuln) GC(ctx context.Context) (int64, error) { 187 if l.updateRetention == 0 { 188 return 0, fmt.Errorf("gc is disabled") 189 } 190 return l.store.GC(ctx, l.updateRetention) 191 } 192 193 // GCFull will run garbage collection until all expired update operations 194 // and stale vulnerabilites are removed in accordance with the UpdateRetention 195 // value. 196 // 197 // GCFull may return an error accompanied by its other return value, 198 // the number of oustanding update operations not deleted. 199 func (l *Libvuln) GCFull(ctx context.Context) (int64, error) { 200 if l.updateRetention == 0 { 201 return 0, fmt.Errorf("gc is disabled") 202 } 203 i, err := l.store.GC(ctx, l.updateRetention) 204 if err != nil { 205 return i, err 206 } 207 208 for i > 0 { 209 i, err = l.store.GC(ctx, l.updateRetention) 210 if err != nil { 211 return i, err 212 } 213 } 214 215 return i, err 216 } 217 218 // Initialized reports whether the backing vulnerability store is initialized. 219 func (l *Libvuln) Initialized(ctx context.Context) (bool, error) { 220 return l.store.Initialized(ctx) 221 } 222 223 // Matcherlog is a logging helper. It prints the name of every matcher and a 224 // generated documentation URL. 225 type matcherLog []driver.Matcher 226 227 func (l matcherLog) MarshalZerologArray(a *zerolog.Array) { 228 for _, m := range l { 229 t := reflect.ValueOf(m).Elem().Type() 230 a.Dict(zerolog.Dict(). 231 Str("name", m.Name()). 232 Str("docs", `https://pkg.go.dev/`+t.PkgPath())) 233 } 234 }