internal/worker: make go-vulndb path consistent and explicit

- Sandbox and docker use the same location of go-vulndb
- We pass this location to the sandbox instead of hardcoding it into the
  sandbox implementation
- scanner now uses this path, which enables testing

The vulndb location is dictated via GO_ECOSYSTEM_VULNDB_DIR environment
variable, similar to how directory with binaries is handled.

Change-Id: I738c4fd454ae28def478c0440147a78564bb5772
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/477358
Reviewed-by: Jonathan Amsterdam <jba@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/cmd/govulncheck_sandbox/govulncheck_sandbox.go b/cmd/govulncheck_sandbox/govulncheck_sandbox.go
index 94c252d..a8ef94c 100644
--- a/cmd/govulncheck_sandbox/govulncheck_sandbox.go
+++ b/cmd/govulncheck_sandbox/govulncheck_sandbox.go
@@ -25,24 +25,26 @@
 	"golang.org/x/pkgsite-metrics/internal/worker"
 )
 
-// vulnDBDir should contain a local copy of the vuln DB, with a LAST_MODIFIED
-// file containing a timestamp.
-var vulnDBDir = flag.String("vulndb", "/go-vulndb", "directory of local vuln DB")
-
+// main function for govulncheck sandbox that accepts four inputs
+// in the following order:
+//   - path to govulncheck
+//   - govulncheck mode
+//   - input module or binary to analyze
+//   - full path to the vulnerability database
 func main() {
 	flag.Parse()
-	run(os.Stdout, flag.Args(), *vulnDBDir)
+	run(os.Stdout, flag.Args())
 }
 
-func run(w io.Writer, args []string, vulnDBDir string) {
+func run(w io.Writer, args []string) {
 
 	fail := func(err error) {
 		fmt.Fprintf(w, `{"Error": %q}`, err)
 		fmt.Fprintln(w)
 	}
 
-	if len(args) != 3 {
-		fail(errors.New("need three args: govulncheck path, mode, and module dir or binary"))
+	if len(args) != 4 {
+		fail(errors.New("need four args: govulncheck path, mode, input module dir or binary, full path to vuln db"))
 		return
 	}
 	mode := args[1]
@@ -51,7 +53,7 @@
 		return
 	}
 
-	resp, err := runGovulncheck(args[0], mode, args[2], vulnDBDir)
+	resp, err := runGovulncheck(args[0], mode, args[2], args[3])
 	if err != nil {
 		fail(err)
 		return
diff --git a/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go b/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go
index 486b79c..59fb837 100644
--- a/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go
+++ b/cmd/govulncheck_sandbox/govulncheck_sandbox_test.go
@@ -59,7 +59,7 @@
 	}
 
 	t.Run("source", func(t *testing.T) {
-		resp, err := runTest([]string{govulncheckPath, worker.ModeGovulncheck, "testdata/module"}, vulndb)
+		resp, err := runTest([]string{govulncheckPath, worker.ModeGovulncheck, "testdata/module", vulndb})
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -82,7 +82,7 @@
 			t.Fatal(derrors.IncludeStderr(err))
 		}
 		defer os.Remove(binary)
-		resp, err := runTest([]string{govulncheckPath, worker.ModeBinary, binary}, vulndb)
+		resp, err := runTest([]string{govulncheckPath, worker.ModeBinary, binary, vulndb})
 		if err != nil {
 			t.Fatal(err)
 		}
@@ -91,38 +91,33 @@
 
 	// Errors
 	for _, test := range []struct {
-		name   string
-		args   []string
-		vulndb string
-		want   string
+		name string
+		args []string
+		want string
 	}{
 		{
-			name:   "too few args",
-			args:   []string{"testdata/module"},
-			vulndb: vulndb,
-			want:   "need three args",
+			name: "too few args",
+			args: []string{"testdata/module", vulndb},
+			want: "need four args",
 		},
 		{
-			name:   "no vulndb",
-			args:   []string{govulncheckPath, worker.ModeGovulncheck, "testdata/module"},
-			vulndb: "does not exist",
-			want:   "does not exist",
+			name: "no vulndb",
+			args: []string{govulncheckPath, worker.ModeGovulncheck, "testdata/module", "does not exist"},
+			want: "does not exist",
 		},
 		{
-			name:   "no mode",
-			args:   []string{govulncheckPath, "MODE", "testdata/module"},
-			vulndb: vulndb,
-			want:   "not a valid mode",
+			name: "no mode",
+			args: []string{govulncheckPath, "MODE", "testdata/module", vulndb},
+			want: "not a valid mode",
 		},
 		{
-			name:   "no module",
-			args:   []string{govulncheckPath, worker.ModeGovulncheck, "testdata/nosuchmodule"},
-			vulndb: vulndb,
-			want:   "no such file",
+			name: "no module",
+			args: []string{govulncheckPath, worker.ModeGovulncheck, "testdata/nosuchmodule", vulndb},
+			want: "no such file",
 		},
 	} {
 		t.Run(test.name, func(t *testing.T) {
-			_, err := runTest(test.args, test.vulndb)
+			_, err := runTest(test.args)
 			if err == nil {
 				t.Fatal("got nil, want error")
 			}
@@ -133,8 +128,8 @@
 	}
 }
 
-func runTest(args []string, vulndbDir string) (*govulncheck.SandboxResponse, error) {
+func runTest(args []string) (*govulncheck.SandboxResponse, error) {
 	var buf bytes.Buffer
-	run(&buf, args, vulndbDir)
+	run(&buf, args)
 	return govulncheck.UnmarshalSandboxResponse(buf.Bytes())
 }
diff --git a/cmd/worker/Dockerfile b/cmd/worker/Dockerfile
index 775d554..7ecf4cf 100644
--- a/cmd/worker/Dockerfile
+++ b/cmd/worker/Dockerfile
@@ -57,8 +57,10 @@
 COPY go-image.tar.gz .
 RUN tar --same-owner -pxzf go-image.tar.gz -C rootfs
 
-# Copy the downloaded copy of the vuln DB into the bundle root.
-COPY go-vulndb rootfs/go-vulndb
+# Copy the downloaded copy of the vuln DB
+# into the /app dir similar to binaries.
+ARG VULNDB_DIR=/app/go-vulndb
+COPY go-vulndb $VULNDB_DIR
 
 COPY config.json .
 
@@ -105,4 +107,6 @@
 
 ENV GO_ECOSYSTEM_BINARY_DIR=$BINARY_DIR
 
+ENV GO_ECOSYSTEM_VULNDB_DIR=$VULNDB_DIR
+
 CMD ["./worker"]
diff --git a/config.json.commented b/config.json.commented
index 1d87669..30a0932 100644
--- a/config.json.commented
+++ b/config.json.commented
@@ -107,6 +107,14 @@
             "options": ["bind"]
         },
         {
+            # Mount /app/go-vulndb inside the sandbox to
+            # the same directory outside.
+            "destination": "/app/go-vulndb",
+            "type": "none",
+            "source": "/app/go-vulndb",
+            "options": ["bind"]
+        },
+        {
             # Mount /tmp/modules inside the sandbox to
             # the same directory outside.
             "destination": "/tmp/modules",
diff --git a/internal/config/config.go b/internal/config/config.go
index 0e5bbde..6a9212e 100644
--- a/internal/config/config.go
+++ b/internal/config/config.go
@@ -86,6 +86,9 @@
 	// BinaryDir is the local directory for binaries.
 	BinaryDir string
 
+	// VulnDBDir is the local directory of the vulnerability database.
+	VulnDBDir string
+
 	// The host, port and user of the pkgsite database used to find
 	// modules to scan.
 	PkgsiteDBHost string
@@ -128,6 +131,7 @@
 		VulnDBBucketProjectID: os.Getenv("GO_ECOSYSTEM_VULNDB_BUCKET_PROJECT"),
 		BinaryBucket:          os.Getenv("GO_ECOSYSTEM_BINARY_BUCKET"),
 		BinaryDir:             GetEnv("GO_ECOSYSTEM_BINARY_DIR", "/tmp/binaries"),
+		VulnDBDir:             GetEnv("GO_ECOSYSTEM_VULNDB_DIR", "/tmp/go-vulndb"),
 		PkgsiteDBHost:         GetEnv("GO_ECOSYSTEM_PKGSITE_DB_HOST", "localhost"),
 		PkgsiteDBPort:         GetEnv("GO_ECOSYSTEM_PKGSITE_DB_PORT", "5432"),
 		PkgsiteDBName:         GetEnv("GO_ECOSYSTEM_PKGSITE_DB_NAME", "discovery-db"),
diff --git a/internal/worker/govulncheck_scan.go b/internal/worker/govulncheck_scan.go
index dadc28c..5086f36 100644
--- a/internal/worker/govulncheck_scan.go
+++ b/internal/worker/govulncheck_scan.go
@@ -135,6 +135,7 @@
 	binaryDir   string
 
 	govulncheckPath string
+	vulnDBDir       string
 }
 
 func newScanner(ctx context.Context, h *GovulncheckServer) (*scanner, error) {
@@ -162,6 +163,7 @@
 		sbox:            sbox,
 		binaryDir:       h.cfg.BinaryDir,
 		govulncheckPath: filepath.Join(h.cfg.BinaryDir, "govulncheck"),
+		vulnDBDir:       h.cfg.VulnDBDir,
 	}, nil
 }
 
@@ -382,7 +384,7 @@
 
 func (s *scanner) runGovulncheckSandbox(ctx context.Context, mode, arg string) (*govulncheck.SandboxResponse, error) {
 	log.Infof(ctx, "running govulncheck in sandbox: mode %s, arg %q", mode, arg)
-	cmd := s.sbox.Command(filepath.Join(s.binaryDir, "govulncheck_sandbox"), s.govulncheckPath, mode, arg)
+	cmd := s.sbox.Command(filepath.Join(s.binaryDir, "govulncheck_sandbox"), s.govulncheckPath, mode, arg, s.vulnDBDir)
 	stdout, err := cmd.Output()
 	log.Infof(ctx, "govulncheck in sandbox finished with err=%v", err)
 	if err != nil {
@@ -427,6 +429,7 @@
 func (s *scanner) runGovulncheckCmd(pattern, tempDir string, stats *scanStats) ([]*govulncheckapi.Vuln, error) {
 	start := time.Now()
 	govulncheckCmd := exec.Command(s.govulncheckPath, "-json", pattern)
+	govulncheckCmd.Env = append(govulncheckCmd.Environ(), "GOVULNDB=file://"+s.vulnDBDir)
 	govulncheckCmd.Dir = tempDir
 	output, err := govulncheckCmd.Output()
 	if err != nil {