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  }