{cmd,internal}/worker: scan modules
Complete support for scanning modules.
- Add scan-modules command to cmd/worker.
- Remember modules we scanned in the Store.
- Track the last-modified vuln DB time to avoid unnecessary re-scanning.
Change-Id: Id2b6d3b2d91c6617d31f4fe6997babba2db220bd
Reviewed-on: https://go-review.googlesource.com/c/vulndb/+/393695
Trust: Jonathan Amsterdam <jba@google.com>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: kokoro <noreply+kokoro@google.com>
Reviewed-by: Damien Neil <dneil@google.com>
diff --git a/cmd/worker/main.go b/cmd/worker/main.go
index d31d9cb..198ff38 100644
--- a/cmd/worker/main.go
+++ b/cmd/worker/main.go
@@ -69,6 +69,7 @@
fmt.Fprintln(out, " list-cves TRIAGE_STATE: display info about CVE records")
fmt.Fprintln(out, " create-issues: create issues for CVEs that need them")
fmt.Fprintln(out, " show ID1 ID2 ...: display CVE records")
+ fmt.Fprintln(out, " scan-modules: scan modules for vulnerabilities")
fmt.Fprintln(out, "flags:")
flag.PrintDefaults()
}
@@ -138,6 +139,8 @@
return createIssuesCommand(ctx)
case "show":
return showCommand(ctx, flag.Args()[1:])
+ case "scan-modules":
+ return scanModulesCommand(ctx)
default:
return fmt.Errorf("unknown command: %q", flag.Arg(1))
}
@@ -277,6 +280,10 @@
return nil
}
+func scanModulesCommand(ctx context.Context) error {
+ return worker.ScanModules(ctx, cfg.Store)
+}
+
func die(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, format, args...)
fmt.Fprintln(os.Stderr)
diff --git a/go.mod b/go.mod
index 8e3d688..40c1311 100644
--- a/go.mod
+++ b/go.mod
@@ -10,6 +10,7 @@
require (
cloud.google.com/go/errorreporting v0.1.0
cloud.google.com/go/firestore v1.6.1
+ cloud.google.com/go/storage v1.10.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go v1.0.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.26.0
github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/trace v1.0.0
@@ -32,7 +33,7 @@
golang.org/x/sync v0.0.0-20210220032951-036812b2e83c
golang.org/x/time v0.0.0-20191024005414-555d28b269f0
golang.org/x/tools v0.1.8
- google.golang.org/api v0.68.0
+ google.golang.org/api v0.70.0
google.golang.org/grpc v1.44.0
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c
honnef.co/go/tools v0.2.2
@@ -41,7 +42,8 @@
require (
cloud.google.com/go v0.100.2 // indirect
- cloud.google.com/go/compute v1.2.0 // indirect
+ cloud.google.com/go/compute v1.3.0 // indirect
+ cloud.google.com/go/iam v0.3.0 // indirect
cloud.google.com/go/monitoring v1.2.0 // indirect
cloud.google.com/go/trace v1.0.0 // indirect
github.com/BurntSushi/toml v0.3.1 // indirect
@@ -69,10 +71,10 @@
go.opentelemetry.io/otel/sdk/metric v0.26.0 // indirect
go.opentelemetry.io/otel/trace v1.4.0 // indirect
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
- golang.org/x/sys v0.0.0-20220207234003-57398862261d // indirect
+ golang.org/x/sys v0.0.0-20220209214540-3681064d5158 // indirect
golang.org/x/text v0.3.7 // indirect
google.golang.org/appengine v1.6.7 // indirect
- google.golang.org/genproto v0.0.0-20220207185906-7721543eae58 // indirect
+ google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf // indirect
google.golang.org/protobuf v1.27.1 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
)
diff --git a/go.sum b/go.sum
index 386b967..5ef9680 100644
--- a/go.sum
+++ b/go.sum
@@ -37,14 +37,17 @@
cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc=
cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ=
cloud.google.com/go/compute v0.1.0/go.mod h1:GAesmwr110a34z04OlxYkATPBEfVhkymfTBXtfbBFow=
-cloud.google.com/go/compute v1.2.0 h1:EKki8sSdvDU0OO9mAXGwPXOTOgPz2l08R0/IutDH11I=
cloud.google.com/go/compute v1.2.0/go.mod h1:xlogom/6gr8RJGBe7nT2eGsQYAFUbbv8dbC29qE3Xmw=
+cloud.google.com/go/compute v1.3.0 h1:mPL/MzDDYHsh5tHRS9mhmhWlcgClCrCa6ApQCU6wnHI=
+cloud.google.com/go/compute v1.3.0/go.mod h1:cCZiE1NHEtai4wiufUhW8I8S1JKkAnhnQJWM7YD99wM=
cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE=
cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk=
cloud.google.com/go/errorreporting v0.1.0 h1:z40EhrjRspplwbpO+9DSnC4kgDokBi94T/gYwtdKL5Q=
cloud.google.com/go/errorreporting v0.1.0/go.mod h1:cZSiBMvrnl0X13pD9DwKf9sQ8Eqy3EzHqkyKBZxiIrM=
cloud.google.com/go/firestore v1.6.1 h1:8rBq3zRjnHx8UtBvaOWqBB1xq9jH6/wltfQLlTMh2Fw=
cloud.google.com/go/firestore v1.6.1/go.mod h1:asNXNOzBdyVQmEU+ggO8UPodTkEVFW5Qx+rwHnAz+EY=
+cloud.google.com/go/iam v0.3.0 h1:exkAomrVUuzx9kWFI1wm3KI0uoDeUFPB4kKGzx6x+Gc=
+cloud.google.com/go/iam v0.3.0/go.mod h1:XzJPvDayI+9zsASAFO68Hk07u3z+f+JrT2xXNdp4bnY=
cloud.google.com/go/monitoring v1.2.0 h1:fEvQITrhVcPM6vuDQcgPMbU5kZFeQFwZmE7v6+S8BPo=
cloud.google.com/go/monitoring v1.2.0/go.mod h1:tE8I08OzjWmXLhCopnPaUDpfGOEJOonfWXGR9E9SsFo=
cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I=
@@ -55,6 +58,7 @@
cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos=
cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk=
cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs=
+cloud.google.com/go/storage v1.10.0 h1:STgFzyU5/8miMl0//zKh2aQeTyeaUH3WN9bSUiJ09bA=
cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0=
cloud.google.com/go/trace v1.0.0 h1:laKx2y7IWMjguCe5zZx6n7qLtREk4kyE69SXVC0VSN8=
cloud.google.com/go/trace v1.0.0/go.mod h1:4iErSByzxkyHWzzlAj63/Gmjz0NH1ASqhJguHpGcr6A=
@@ -193,9 +197,11 @@
github.com/google/go-github/v41 v41.0.0/go.mod h1:XgmCA5H323A9rtgExdTcnDkcqp6S30AVACCBDOonIxg=
github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8=
github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU=
+github.com/google/martian v2.1.0+incompatible h1:/CP5g8u/VJHijgedC/Legn3BAbAaWPgecwXBIDzw5no=
github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs=
github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0=
+github.com/google/martian/v3 v3.2.1 h1:d8MncMlErDFTwQGBK1xhv026j9kqhvw1Qv9IbWT1VLQ=
github.com/google/martian/v3 v3.2.1/go.mod h1:oBOf6HBosgwRXnUGWUB05QECsc6uvmMiJ3+6W4l/CUk=
github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc=
@@ -510,9 +516,11 @@
golang.org/x/sys v0.0.0-20211213223007-03aa0b5f6827/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211216021012-1d35b9e2eb4e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220128215802-99c3d69c2c27/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220204135822-1c1b9b1eba6a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20220207234003-57398862261d h1:Bm7BNOQt2Qv7ZqysjeLjgCBanX+88Z/OtdvsrEv1Djc=
golang.org/x/sys v0.0.0-20220207234003-57398862261d/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158 h1:rm+CHSpPEEW2IsXUib1ThaHIjuBVZjxNgSKmBLFfD4c=
+golang.org/x/sys v0.0.0-20220209214540-3681064d5158/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211 h1:JGgROgKl9N8DuW20oFS5gxc+lE67/N3FcwmBPMe7ArY=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
@@ -624,8 +632,10 @@
google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
google.golang.org/api v0.63.0/go.mod h1:gs4ij2ffTRXwuzzgJl/56BdwJaA194ijkfn++9tDuPo=
google.golang.org/api v0.66.0/go.mod h1:I1dmXYpX7HGwz/ejRxwQp2qj5bFAz93HiCU1C1oYd9M=
-google.golang.org/api v0.68.0 h1:9eJiHhwJKIYX6sX2fUZxQLi7pDRA/MYu8c12q6WbJik=
+google.golang.org/api v0.67.0/go.mod h1:ShHKP8E60yPsKNw/w8w+VYaj9H6buA5UqDp8dhbQZ6g=
google.golang.org/api v0.68.0/go.mod h1:sOM8pTpwgflXRhz+oC8H2Dr+UcbMqkPPWNJo88Q7TH8=
+google.golang.org/api v0.70.0 h1:67zQnAE0T2rB0A3CwLSas0K+SbVzSxP+zTLkQLexeiw=
+google.golang.org/api v0.70.0/go.mod h1:Bs4ZM2HGifEvXwd50TtW70ovgJffJYw2oRCOFU/SkfA=
google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -701,10 +711,14 @@
google.golang.org/genproto v0.0.0-20211208223120-3a66f561d7aa/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20211221195035-429b39de9b1c/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220114231437-d2e6a121cae0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220126215142-9970aeb2e350/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220201184016-50beb8ab5c44/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220204002441-d6cc3cc0770e/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20220207185906-7721543eae58 h1:i67FGOy2/zGfhE3YgHdrOrcFbOBhqdcRoBrsDqSQrOI=
+google.golang.org/genproto v0.0.0-20220207164111-0872dc986b00/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
google.golang.org/genproto v0.0.0-20220207185906-7721543eae58/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
+google.golang.org/genproto v0.0.0-20220218161850-94dd64e39d7c/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf h1:SVYXkUz2yZS9FWb2Gm8ivSlbNQzL2Z/NpPKE3RG2jWk=
+google.golang.org/genproto v0.0.0-20220222213610-43724f9ea8cf/go.mod h1:kGP+zUP2Ddo0ayMi4YuN7C3WZyJvGLZRh8Z5wnAqvEI=
google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
diff --git a/internal/worker/scan_modules.go b/internal/worker/scan_modules.go
index b5384d4..e3ed344 100644
--- a/internal/worker/scan_modules.go
+++ b/internal/worker/scan_modules.go
@@ -11,14 +11,17 @@
"io"
"os"
"path/filepath"
+ "sort"
"strings"
"time"
+ "cloud.google.com/go/storage"
"golang.org/x/exp/vulncheck"
"golang.org/x/tools/go/packages"
vulnc "golang.org/x/vuln/client"
"golang.org/x/vulndb/internal/derrors"
"golang.org/x/vulndb/internal/worker/log"
+ "golang.org/x/vulndb/internal/worker/store"
)
// Selected repos under golang.org/x.
@@ -33,9 +36,9 @@
"golang.org/x/vuln", "golang.org/x/vulndb", "golang.org/x/website",
}
-// scanModules scans a list of Go modules for vulnerabilities.
+// ScanModules scans a list of Go modules for vulnerabilities.
// It assumes the root of each repo is a module, and there are no nested modules.
-func scanModules(ctx context.Context) error {
+func ScanModules(ctx context.Context, st store.Store) error {
dbClient, err := vulnc.NewClient([]string{vulnDBURL}, vulnc.Options{})
if err != nil {
return err
@@ -46,7 +49,7 @@
if err != nil {
return err
}
- if err := processModule(ctx, modulePath, latest, dbClient); err != nil {
+ if err := processModule(ctx, modulePath, latest, dbClient, st); err != nil {
return err
}
latestTagged, err := latestTaggedVersion(ctx, modulePath)
@@ -54,7 +57,7 @@
return err
}
if latestTagged != "" && latestTagged != latest {
- if err := processModule(ctx, modulePath, latestTagged, dbClient); err != nil {
+ if err := processModule(ctx, modulePath, latestTagged, dbClient, st); err != nil {
return err
}
}
@@ -62,8 +65,27 @@
return nil
}
-func processModule(ctx context.Context, modulePath, version string, dbClient vulnc.Client) error {
+func processModule(ctx context.Context, modulePath, version string, dbClient vulnc.Client, st store.Store) (err error) {
+ defer derrors.Wrap(&err, "processModule(%q, %q)", modulePath, version)
+
+ dbTime, err := vulnDBTime(ctx)
+ if err != nil {
+ return err
+ }
+ r, err := st.GetModuleScanRecord(ctx, modulePath, version, dbTime)
+ if err != nil {
+ return err
+ }
+ if r != nil {
+ // Already done.
+ log.Debugf(ctx, "already scanned %s@%s at DB time %s", modulePath, version, dbTime)
+ return nil
+ }
+
res, err := scanModule(ctx, modulePath, version, dbClient)
+ if err2 := createModuleScanRecord(ctx, st, modulePath, version, dbTime, res, err); err2 != nil {
+ return err2
+ }
if err != nil {
return err
}
@@ -75,6 +97,32 @@
return nil
}
+func createModuleScanRecord(ctx context.Context, st store.Store, path, version string, dbTime time.Time, res *vulncheck.Result, err error) error {
+ var errstr string
+ var vulnIDs []string
+ if err != nil {
+ errstr = err.Error()
+ } else {
+ m := map[string]bool{}
+ for _, v := range res.Vulns {
+ m[v.OSV.ID] = true
+ }
+ for id := range m {
+ vulnIDs = append(vulnIDs, id)
+ }
+ sort.Strings(vulnIDs)
+ }
+
+ return st.CreateModuleScanRecord(ctx, &store.ModuleScanRecord{
+ Path: path,
+ Version: version,
+ DBTime: dbTime,
+ Error: errstr,
+ VulnIDs: vulnIDs,
+ FinishedAt: time.Now(),
+ })
+}
+
// scanRepo clones the given repo and analyzes it for vulnerabilities. If commit
// is "HEAD", the head commit is scanned. Otherwise, commit must be a hex string
// corresponding to a commit, and that commit is checked out and scanned.
@@ -165,3 +213,19 @@
}
return nil
}
+
+// vulnDBTime returns the time that the vuln DB was last updated.
+func vulnDBTime(ctx context.Context) (_ time.Time, err error) {
+ // Until the vuln DB client supports this, use the update time
+ // of the index file.
+ defer derrors.Wrap(&err, "vulnDBTime")
+ c, err := storage.NewClient(ctx)
+ if err != nil {
+ return time.Time{}, err
+ }
+ attrs, err := c.Bucket(vulnDBBucket).Object("index.json").Attrs(ctx)
+ if err != nil {
+ return time.Time{}, err
+ }
+ return attrs.Updated, nil
+}
diff --git a/internal/worker/scan_modules_test.go b/internal/worker/scan_modules_test.go
index 9cca86c..fdeb4ae 100644
--- a/internal/worker/scan_modules_test.go
+++ b/internal/worker/scan_modules_test.go
@@ -13,6 +13,7 @@
"golang.org/x/exp/event"
vulnc "golang.org/x/vuln/client"
"golang.org/x/vulndb/internal/worker/log"
+ "golang.org/x/vulndb/internal/worker/store"
)
// TestScanModules is slow, so put it behind a flag.
@@ -22,10 +23,10 @@
if !*runScanModulesTest {
t.Skip("-scan flag missing")
}
- // Verify only that scanRepos works (doesn't return an error).
+ // Verify only that scanModules works (doesn't return an error).
ctx := event.WithExporter(context.Background(),
event.NewExporter(log.NewLineHandler(os.Stderr), nil))
- if err := scanModules(ctx); err != nil {
+ if err := ScanModules(ctx, store.NewMemStore()); err != nil {
t.Fatal(err)
}
}
diff --git a/internal/worker/server.go b/internal/worker/server.go
index 628ac7e..9039847 100644
--- a/internal/worker/server.go
+++ b/internal/worker/server.go
@@ -375,7 +375,7 @@
}
func (s *Server) handleScanModules(w http.ResponseWriter, r *http.Request) error {
- return scanModules(r.Context())
+ return ScanModules(r.Context(), s.cfg.Store)
}
func initOpenTelemetry(projectID string) (tp *sdktrace.TracerProvider, mp metric.MeterProvider, err error) {
diff --git a/internal/worker/worker.go b/internal/worker/worker.go
index 02fc986..f29175f 100644
--- a/internal/worker/worker.go
+++ b/internal/worker/worker.go
@@ -126,7 +126,10 @@
return c.msg
}
-const vulnDBURL = "https://storage.googleapis.com/go-vulndb"
+const (
+ vulnDBBucket = "go-vulndb"
+ vulnDBURL = "https://storage.googleapis.com/" + vulnDBBucket
+)
// readVulnDB returns a list of all CVE IDs in the Go vuln DB.
func readVulnDB(ctx context.Context) ([]string, error) {