| // Copyright 2023 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package worker |
| |
| import ( |
| "context" |
| "encoding/json" |
| "errors" |
| "os" |
| "path/filepath" |
| "strings" |
| "time" |
| |
| "fmt" |
| "net/http" |
| |
| "cloud.google.com/go/storage" |
| "google.golang.org/api/iterator" |
| |
| "golang.org/x/pkgsite-metrics/internal" |
| "golang.org/x/pkgsite-metrics/internal/bigquery" |
| "golang.org/x/pkgsite-metrics/internal/derrors" |
| "golang.org/x/pkgsite-metrics/internal/log" |
| "golang.org/x/pkgsite-metrics/internal/osv" |
| "golang.org/x/pkgsite-metrics/internal/vulndb" |
| "golang.org/x/pkgsite-metrics/internal/vulndbreqs" |
| ) |
| |
| func (s *Server) handleComputeRequests(w http.ResponseWriter, r *http.Request) (err error) { |
| defer derrors.Wrap(&err, "handleComputeRequests") |
| |
| ctx := r.Context() |
| // Don't use the Server's BigQuery client: it's for the wrong |
| // dataset. |
| vClient, err := bigquery.NewClientCreate(ctx, s.cfg.ProjectID, vulndbreqs.DatasetName) |
| if err != nil { |
| return err |
| } |
| keyName := "projects/" + s.cfg.ProjectID + "/secrets/vulndb-hmac-key" |
| hmacKey, err := internal.GetSecret(ctx, keyName) |
| if err != nil { |
| return err |
| } |
| err = vulndbreqs.ComputeAndStore(ctx, s.cfg.VulnDBBucketProjectID, vClient, []byte(hmacKey)) |
| if err != nil { |
| return err |
| } |
| fmt.Fprintf(w, "Successfully computed and stored request counts.\n") |
| return nil |
| } |
| |
| func (s *Server) handleVulnDB(w http.ResponseWriter, r *http.Request) (err error) { |
| defer derrors.Wrap(&err, "handleVulnDB") |
| |
| ctx := r.Context() |
| dbClient, err := bigquery.NewClientCreate(ctx, s.cfg.ProjectID, vulndb.DatasetName) |
| if err != nil { |
| return err |
| } |
| |
| c, err := storage.NewClient(ctx) |
| if err != nil { |
| return err |
| } |
| bucket := c.Bucket("go-vulndb") |
| if bucket == nil { |
| return errors.New("failed to create go-vulndb bucket") |
| } |
| |
| lmts, err := lastModified(ctx, dbClient) |
| if err != nil { |
| return err |
| } |
| entries, err := vulndbEntries(ctx, bucket) |
| if err != nil { |
| return err |
| } |
| |
| for _, e := range entries { |
| lmt, ok := lmts[e.ID] |
| if ok && e.ModifiedTime.Equal(lmt) { |
| // Skip adding the entry if nothing has changed in the meantime. |
| log.Infof(ctx, "skipping entry %s, it has not been modified", e.ID) |
| continue |
| } |
| if err = writeResult(ctx, false, w, dbClient, vulndb.TableName, e); err != nil { |
| return err |
| } |
| } |
| |
| return nil |
| } |
| |
| func vulndbEntries(ctx context.Context, bucket *storage.BucketHandle) ([]*vulndb.Entry, error) { |
| osvEntries, err := allVulnerabilities(ctx, bucket) |
| if err != nil { |
| return nil, err |
| } |
| var entries []*vulndb.Entry |
| for _, oe := range osvEntries { |
| entries = append(entries, vulndb.Convert(oe)) |
| } |
| return entries, nil |
| } |
| |
| // gcsOSVPrefix is the directory under which .json |
| // files with OSV entries are located. |
| const gcsOSVPrefix = "ID" |
| |
| // allVulnerabilities fetches all osv.Entries from GCS bucket located at ID/*.json paths. |
| func allVulnerabilities(ctx context.Context, bucket *storage.BucketHandle) ([]*osv.Entry, error) { |
| var entries []*osv.Entry |
| query := &storage.Query{Prefix: gcsOSVPrefix} |
| it := bucket.Objects(ctx, query) |
| for { |
| attrs, err := it.Next() |
| if err == iterator.Done { |
| break |
| } |
| if err != nil { |
| return nil, err |
| } |
| // Skip zip files and index.json. |
| if !strings.HasSuffix(attrs.Name, ".json") || strings.HasSuffix(attrs.Name, "index.json") { |
| continue |
| } |
| |
| e, err := readEntry(ctx, bucket, attrs.Name) |
| if err != nil { |
| return nil, err |
| } |
| entries = append(entries, e) |
| } |
| return entries, nil |
| } |
| |
| func readEntry(ctx context.Context, bucket *storage.BucketHandle, gcsPath string) (*osv.Entry, error) { |
| localPath := filepath.Join(os.TempDir(), "binary") |
| if err := copyToLocalFile(localPath, false, gcsPath, gcsOpenFileFunc(ctx, bucket)); err != nil { |
| return nil, err |
| } |
| js, err := os.ReadFile(localPath) |
| if err != nil { |
| return nil, err |
| } |
| var entry osv.Entry |
| if err := json.Unmarshal(js, &entry); err != nil { |
| return nil, err |
| } |
| return &entry, nil |
| } |
| |
| func lastModified(ctx context.Context, c *bigquery.Client) (map[string]time.Time, error) { |
| es, err := vulndb.ReadMostRecentDB(ctx, c) |
| if err != nil { |
| return nil, err |
| } |
| m := make(map[string]time.Time) |
| for _, e := range es { |
| m[e.ID] = e.ModifiedTime |
| } |
| return m, nil |
| } |