diff --git a/enricher/epss/epss.go b/enricher/epss/epss.go index 87fd37a60..ef95a1ec8 100644 --- a/enricher/epss/epss.go +++ b/enricher/epss/epss.go @@ -1,3 +1,4 @@ +// Package epss provides a epss enricher. package epss import ( @@ -16,6 +17,7 @@ import ( "strings" "time" + "github.com/google/uuid" "github.com/quay/zlog" "github.com/quay/claircore" @@ -42,7 +44,7 @@ type EPSSItem struct { const ( // Type is the type of data returned from the Enricher's Enrich method. - Type = `message/vnd.clair.map.vulnerability; enricher=clair.epss schema=https://csrc.nist.gov/schema/nvd/baseURL/1.1/cvss-v3.x.json` + Type = `message/vnd.clair.map.vulnerability; enricher=clair.epss schema=none` // DefaultBaseURL is the default place to look for EPSS feeds. // epss_scores-YYYY-MM-DD.csv.gz needs to be specified to get all data @@ -64,7 +66,7 @@ type Enricher struct { // Config is the configuration for Enricher. type Config struct { - BaseURL *string `json:"url" yaml:"url"` + URL *string `json:"url" yaml:"url"` } func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c *http.Client) error { @@ -73,24 +75,23 @@ func (e *Enricher) Configure(ctx context.Context, f driver.ConfigUnmarshaler, c e.c = c e.feedPath = currentFeedURL() if f == nil { - zlog.Debug(ctx).Msg("No configuration provided; proceeding with default settings") - return nil + return fmt.Errorf("configuration is nil") } if err := f(&cfg); err != nil { return err } - if cfg.BaseURL != nil { + if cfg.URL != nil { // validate the URL format - if _, err := url.Parse(*cfg.BaseURL); err != nil { - return fmt.Errorf("invalid URL format for BaseURL: %w", err) + if _, err := url.Parse(*cfg.URL); err != nil { + return fmt.Errorf("invalid URL format for URL: %w", err) } // only .gz file is supported - if strings.HasSuffix(*cfg.BaseURL, ".gz") { + if strings.HasSuffix(*cfg.URL, ".gz") { //overwrite feedPath is cfg provides another baseURL path - e.feedPath = *cfg.BaseURL + e.feedPath = *cfg.URL } else { - return fmt.Errorf("invalid baseURL root: expected a '.gz' file, but got '%q'", *cfg.BaseURL) + return fmt.Errorf("invalid baseURL root: expected a '.gz' file, but got '%q'", *cfg.URL) } } @@ -128,14 +129,19 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F if err = httputil.CheckResponse(resp, http.StatusOK); err != nil { return nil, "", fmt.Errorf("unable to fetch file: %w", err) } - - etag := resp.Header.Get("etag") - if etag == "" { - return nil, "", fmt.Errorf("ETag not found in response headers") + var str string + var newFingerprint driver.Fingerprint + str = resp.Header.Get("etag") + if str == "" { + newUUID, err := uuid.NewRandom() + if err != nil { + return nil, "", fmt.Errorf("failed to generate UUID: %w", err) + } + // Generate a UUID for customized URL + str = newUUID.String() + zlog.Warn(ctx).Msg("ETag not found; generated UUID for fingerprint") } - - newFingerprint := driver.Fingerprint(etag) - + newFingerprint = driver.Fingerprint(str) if prevFingerprint == newFingerprint { zlog.Info(ctx).Str("fingerprint", string(newFingerprint)).Msg("file unchanged; skipping processing") return nil, prevFingerprint, nil @@ -158,7 +164,6 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F var modelVersion, date string for _, field := range record { - field = strings.TrimSpace(field) field = strings.TrimPrefix(strings.TrimSpace(field), "#") key, value, found := strings.Cut(field, ":") if !found { @@ -176,11 +181,8 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F return nil, "", fmt.Errorf("missing metadata fields in record: %v", record) } csvReader.Comment = '#' - csvReader.FieldsPerRecord = 3 // Expect exactly 3 fields per record - if modelVersion == "" || date == "" { - return nil, "", fmt.Errorf("missing metadata fields in record: %v", record) - } + csvReader.FieldsPerRecord = 3 // Expect exactly 3 fields per record // Read and validate header line record, err = csvReader.Read() @@ -230,9 +232,7 @@ func (e *Enricher) FetchEnrichment(ctx context.Context, prevFingerprint driver.F func (e *Enricher) ParseEnrichment(ctx context.Context, rc io.ReadCloser) ([]driver.EnrichmentRecord, error) { ctx = zlog.ContextWithValues(ctx, "component", "enricher/epss/Enricher/ParseEnrichment") - defer func() { - _ = rc.Close() - }() + defer rc.Close() dec := json.NewDecoder(rc) ret := make([]driver.EnrichmentRecord, 0, 250_000) @@ -262,8 +262,8 @@ func (*Enricher) Name() string { } func currentFeedURL() string { - currentDate := time.Now() - formattedDate := currentDate.Format("2006-01-02") + yesterday := time.Now().AddDate(0, 0, -1) // Get yesterday's date + formattedDate := yesterday.Format("2006-01-02") filePath := fmt.Sprintf("epss_scores-%s.csv.gz", formattedDate) feedURL, err := url.Parse(DefaultBaseURL) @@ -354,7 +354,7 @@ func (e *Enricher) Enrich(ctx context.Context, g driver.EnrichmentGetter, r *cla } func newItemFeed(record []string, modelVersion string, scoreDate string) (driver.EnrichmentRecord, error) { - // Assuming record has already been validated to have 3 fields + // Validate the record has the expected length if len(record) != 3 { return driver.EnrichmentRecord{}, fmt.Errorf("unexpected record length: %d", len(record)) } diff --git a/enricher/epss/epss_test.go b/enricher/epss/epss_test.go index 042a6711c..32eeefcbf 100644 --- a/enricher/epss/epss_test.go +++ b/enricher/epss/epss_test.go @@ -38,7 +38,7 @@ func TestConfigure(t *testing.T) { Config: func(i interface{}) error { cfg := i.(*Config) s := "http://example.com/" - cfg.BaseURL = &s + cfg.URL = &s return nil }, Check: func(t *testing.T, err error) { @@ -58,11 +58,11 @@ func TestConfigure(t *testing.T) { }, }, { - Name: "BadURL", // Malformed URL in BaseURL + Name: "BadURL", // Malformed URL in URL Config: func(i interface{}) error { cfg := i.(*Config) s := "http://[notaurl:/" - cfg.BaseURL = &s + cfg.URL = &s return nil }, Check: func(t *testing.T, err error) { @@ -72,11 +72,11 @@ func TestConfigure(t *testing.T) { }, }, { - Name: "ValidGZURL", // Proper .gz URL in BaseURL + Name: "ValidGZURL", // Proper .gz URL in URL Config: func(i interface{}) error { cfg := i.(*Config) s := "http://example.com/epss_scores-2024-10-25.csv.gz" - cfg.BaseURL = &s + cfg.URL = &s return nil }, Check: func(t *testing.T, err error) { @@ -207,7 +207,7 @@ func (tc fetchTestcase) Run(ctx context.Context, srv *httptest.Server) func(*tes t.Fatal("expected Config type for i, but got a different type") } u := srv.URL + "/data.csv.gz" - cfg.BaseURL = &u + cfg.URL = &u return nil } @@ -259,7 +259,7 @@ func (tc parseTestcase) Run(ctx context.Context, srv *httptest.Server) func(*tes t.Fatal("assertion failed") } u := srv.URL + "/data.csv.gz" - cfg.BaseURL = &u + cfg.URL = &u return nil } if err := e.Configure(ctx, f, srv.Client()); err != nil { @@ -313,7 +313,7 @@ func TestEnrich(t *testing.T) { t.Fatal("assertion failed") } u := srv.URL + "/data.csv.gz" - cfg.BaseURL = &u + cfg.URL = &u return nil } if err := e.Configure(ctx, f, srv.Client()); err != nil {