Skip to content

Commit

Permalink
epss: use etag as hint
Browse files Browse the repository at this point in the history
Signed-off-by: daynewlee <[email protected]>
  • Loading branch information
daynewlee committed Dec 1, 2024
1 parent 1f2f56a commit 97658f1
Show file tree
Hide file tree
Showing 2 changed files with 36 additions and 20 deletions.
40 changes: 23 additions & 17 deletions enricher/epss/epss.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@ import (
"strings"
"time"

"github.com/google/uuid"

"github.com/quay/claircore"
"github.com/quay/claircore/libvuln/driver"
"github.com/quay/claircore/pkg/tmp"
Expand All @@ -28,8 +26,6 @@ import (
var (
_ driver.Enricher = (*Enricher)(nil)
_ driver.EnrichmentUpdater = (*Enricher)(nil)

defaultFeed *url.URL
)

type EPSSItem struct {
Expand Down Expand Up @@ -86,15 +82,14 @@ func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c
ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/Configure")
var cfg Config
e.c = c
e.feedPath = currentFeedURL()
if f == nil {
zlog.Warn(ctx).Msg("No configuration provided; proceeding with default settings")
e.defaultURL()
zlog.Debug(ctx).Msg("No configuration provided; proceeding with default settings")
return nil
}
if err := f(&cfg); err != nil {
return err
}
e.defaultURL()
if cfg.FeedRoot != nil {
// validate the URL format
if _, err := url.Parse(*cfg.FeedRoot); err != nil {
Expand All @@ -103,28 +98,26 @@ func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c

// only .gz file is supported
if strings.HasSuffix(*cfg.FeedRoot, ".gz") {
//overwrite feedPath is cfg provides another feed path
e.feedPath = *cfg.FeedRoot
} else {
return fmt.Errorf("invalid feed root: expected a '.gz' file, but got '%s'", *cfg.FeedRoot)
return fmt.Errorf("invalid feed root: expected a '.gz' file, but got '%q'", *cfg.FeedRoot)
}
}

return nil
}

func (e *Enricher) FetchEnrichment(ctx context.Context, _ driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.Fingerprint) (io.ReadCloser, driver.Fingerprint, error) {
ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/FetchEnrichment")
newUUID := uuid.New()
hint := driver.Fingerprint(newUUID.String())
zlog.Info(ctx).Str("hint", string(hint)).Msg("starting fetch")

if e.feedPath == "" || !strings.HasSuffix(e.feedPath, ".gz") {
return nil, "", fmt.Errorf("invalid feed path: %q must be non-empty and end with '.gz'", e.feedPath)
}

out, err := tmp.NewFile("", "epss.")
if err != nil {
return nil, hint, err
return nil, "", err
}
var success bool
defer func() {
Expand All @@ -150,6 +143,19 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, _ driver.Fingerprint) (i
return nil, "", fmt.Errorf("unable to fetch file: received status %d", resp.StatusCode)
}

etag := resp.Header.Get("ETag")
if etag == "" {
return nil, "", fmt.Errorf("ETag not found in response headers")
}

newFingerprint := driver.Fingerprint(etag)

// If the ETag matches the previous fingerprint, no update is needed
if prevFingerprint == newFingerprint {
zlog.Info(ctx).Str("fingerprint", string(newFingerprint)).Msg("file unchanged; skipping processing")
return nil, prevFingerprint, nil
}

gzipReader, err := gzip.NewReader(resp.Body)
if err != nil {
return nil, "", fmt.Errorf("unable to decompress file: %w", err)
Expand Down Expand Up @@ -204,11 +210,11 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, _ driver.Fingerprint) (i

zlog.Info(ctx).Int("totalCVEs", totalCVEs).Msg("processed CVEs")
if _, err := out.Seek(0, io.SeekStart); err != nil {
return nil, hint, fmt.Errorf("unable to reset file pointer: %w", err)
return nil, newFingerprint, fmt.Errorf("unable to reset file pointer: %w", err)
}
success = true

return out, hint, nil
return out, newFingerprint, nil
}

// ParseEnrichment implements driver.EnrichmentUpdater.
Expand Down Expand Up @@ -246,7 +252,7 @@ func (*Enricher) Name() string {
return epssName
}

func (e *Enricher) defaultURL() {
func currentFeedURL() string {
currentDate := time.Now()
formattedDate := currentDate.Format("2006-01-02")
filePath := fmt.Sprintf("epss_scores-%s.csv.gz", formattedDate)
Expand All @@ -257,7 +263,7 @@ func (e *Enricher) defaultURL() {
}

feedURL.Path = path.Join(feedURL.Path, filePath)
e.feedPath = feedURL.String()
return feedURL.String()
}

func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *claircore.VulnerabilityReport) (string, []json.RawMessage, error) {
Expand Down
16 changes: 13 additions & 3 deletions enricher/epss/epss_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,28 +163,36 @@ func noopConfig(_ interface{}) error { return nil }

func mockServer(t *testing.T) *httptest.Server {
const root = `testdata/`

// Define a static ETag for testing purposes
const etagValue = `"test-etag-12345"`

srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch path.Ext(r.URL.Path) {
case ".gz": // only gz feed is supported
w.Header().Set("ETag", etagValue)

f, err := os.Open(filepath.Join(root, "data.csv"))
if err != nil {
t.Errorf("open failed: %v", err)
w.WriteHeader(http.StatusInternalServerError)
break
return
}
defer f.Close()

gz := gzip.NewWriter(w)
defer gz.Close()
if _, err := io.Copy(gz, f); err != nil {
t.Errorf("write error: %v", err)
w.WriteHeader(http.StatusInternalServerError)
break
return
}
default:
t.Errorf("unknown request path: %q", r.URL.Path)
w.WriteHeader(http.StatusBadRequest)
}
}))

t.Cleanup(srv.Close)
return srv
}
Expand Down Expand Up @@ -257,7 +265,9 @@ func (tc parseTestcase) Run(ctx context.Context, srv *httptest.Server) func(*tes
if err := e.Configure(ctx, f, srv.Client()); err != nil {
t.Errorf("unexpected error: %v", err)
}
rc, _, err := e.FetchEnrichment(ctx, "")

hint := driver.Fingerprint("test-e-tag-54321")
rc, _, err := e.FetchEnrichment(ctx, hint)
if err != nil {
t.Errorf("unexpected error: %v", err)
}
Expand Down

0 comments on commit 97658f1

Please sign in to comment.