internal/worker, etc.: use same binary directory for sandbox

Simplify the code by using the same directory for binaries both
inside and outside the sandbox.

We choose /tmp/binaries as that directory. We put it in /tmp rather
than at the root because we want non-superusers to be able to run the
worker on their local machines, and they can't create directories at
the filesystem root.

We add a bind mount to config.json.commented in order to make the
sandbox's /tmp/binaries directory refer to the one outside the sanbox.

We also rearrange the Dockerfile slightly to improve caching.

Change-Id: I8e2c4bcd37029a816472fcbac976a3158acdb209
Reviewed-on: https://go-review.googlesource.com/c/pkgsite-metrics/+/476195
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Jonathan Amsterdam <jba@google.com>
Reviewed-by: Zvonimir Pavlinovic <zpavlinovic@google.com>
diff --git a/cmd/worker/Dockerfile b/cmd/worker/Dockerfile
index 41de2b1..c8b14e3 100644
--- a/cmd/worker/Dockerfile
+++ b/cmd/worker/Dockerfile
@@ -17,14 +17,19 @@
 WORKDIR /
 
 # Create some directories.
-# Binaries run inside the sandbox live here.
-RUN mkdir -p /bundle/rootfs/binaries
+
 # The worker binary and related files live here.
 RUN mkdir app
 # The module being analyzed is unzipped here.
 # The sandbox mounts this directory.
 RUN mkdir module
 
+# Where binaries live.
+# The sandbox config.json file maps these to the same paths
+# inside the sandbox.
+RUN mkdir /tmp/binaries
+
+
 #### Sandbox setup
 
 # Install runsc.
@@ -37,7 +42,9 @@
 
 # Create the runsc bundle.
 WORKDIR /bundle
-COPY config.json .
+
+# The root of the bundle filesystem.
+RUN mkdir rootfs
 
 # go-image.tar.gz is a complete Docker image of a Go installation in tar format.
 # Use it for the bundle's OS filesystem.
@@ -47,6 +54,8 @@
 # Copy the downloaded copy of the vuln DB into the bundle root.
 COPY go-vulndb rootfs/go-vulndb
 
+COPY config.json .
+
 #### Building binaries
 
 # Set the working directory outside $GOPATH to ensure module mode is enabled.
@@ -68,16 +77,17 @@
 # Build the worker binary and put it in /app.
 RUN go build -mod=readonly -o /app/worker ./cmd/worker
 
+# Install the latest version of govulncheck in the binaries directory.
+RUN GOBIN=/tmp/binaries go install golang.org/x/vuln/cmd/govulncheck@latest
+
 # Build the program that runs vulncheck inside the sandbox and install it in the sandbox's
 # binaries directory.
-RUN go build -mod=readonly -o /bundle/rootfs/binaries/vulncheck_sandbox ./cmd/vulncheck_sandbox
-
-# Install the latest version of govulncheck in the sandbox's binaries directory
-RUN GOBIN=/bundle/rootfs/binaries go install golang.org/x/vuln/cmd/govulncheck@latest
+RUN go build -mod=readonly -o /tmp/binaries/vulncheck_sandbox ./cmd/vulncheck_sandbox
 
 # Build the sandbox runner program and put it in the bundle root.
 RUN go build -mod=readonly -o /bundle/rootfs/runner ./internal/sandbox/runner.go
 
+
 #### Worker setup
 
 WORKDIR /app
diff --git a/config.json.commented b/config.json.commented
index ad9abc2..b13d7d0 100644
--- a/config.json.commented
+++ b/config.json.commented
@@ -90,6 +90,14 @@
                 "nodev",
                 "ro"
             ]
+        },
+        {
+            # Mount /tmp/binaries inside the sandbox to
+	    # the same directory outside.
+            "destination": "/tmp/binaries",
+            "type": "none",
+            "source": "/tmp/binaries",
+            "options": ["bind"]
         }
     ],
     "linux": {
diff --git a/internal/worker/analysis.go b/internal/worker/analysis.go
index ca88015..45645a1 100644
--- a/internal/worker/analysis.go
+++ b/internal/worker/analysis.go
@@ -101,7 +101,7 @@
 	if req.Insecure {
 		destPath = filepath.Join(tempDir, "binary")
 	} else {
-		destPath = path.Join(sandboxRoot, "binaries", path.Base(req.Binary))
+		destPath = path.Join(binaryDir, path.Base(req.Binary))
 	}
 	if s.cfg.BinaryBucket == "" {
 		return nil, nil, errors.New("missing binary bucket (define GO_ECOSYSTEM_BINARY_BUCKET)")
diff --git a/internal/worker/scan.go b/internal/worker/scan.go
index ef5d53c..10f9c5b 100644
--- a/internal/worker/scan.go
+++ b/internal/worker/scan.go
@@ -28,7 +28,15 @@
 	"golang.org/x/pkgsite-metrics/internal/proxy"
 )
 
-const sandboxRoot = "/bundle/rootfs"
+const (
+	// The root of the sandbox, relative to the docker container.
+	sandboxRoot = "/bundle/rootfs"
+
+	// The directory where binaries live. The sandbox mounts this
+	// directory to the same path internally, so this path works
+	// for both secure and insecure modes.
+	binaryDir = "/tmp/binaries"
+)
 
 var activeScans atomic.Int32
 
diff --git a/internal/worker/vulncheck_scan.go b/internal/worker/vulncheck_scan.go
index 5d93699..e3c666d 100644
--- a/internal/worker/vulncheck_scan.go
+++ b/internal/worker/vulncheck_scan.go
@@ -292,9 +292,8 @@
 	sandboxGoModCache = "root/go/pkg/mod"
 	// The Go cache resides in its default location, $HOME/.cache/go-build.
 	sandboxGoCache = "root/.cache/go-build"
-
-	// Within the sandbox, govulncheck will be located at /binaries/govulncheck path.
-	sandboxGovulncheck = "/binaries/govulncheck"
+	// Where the govulncheck binary lives.
+	govulncheckPath = binaryDir + "/govulncheck"
 )
 
 // runScanModule fetches the module version from the proxy, and analyzes it for
@@ -318,9 +317,9 @@
 	return bvulns, err
 }
 
-func (s *scanner) runGovulncheckScanSandbox(ctx context.Context, modulePath, version, binaryDir, mode string, stats *scanStats) (_ []*govulncheckapi.Vuln, err error) {
+func (s *scanner) runGovulncheckScanSandbox(ctx context.Context, modulePath, version, binDir, mode string, stats *scanStats) (_ []*govulncheckapi.Vuln, err error) {
 	if mode == ModeBinary {
-		return s.runBinaryScanSandbox(ctx, modulePath, version, binaryDir, stats)
+		return s.runBinaryScanSandbox(ctx, modulePath, version, binDir, stats)
 	}
 
 	const insecure = false
@@ -332,7 +331,7 @@
 
 	log.Infof(ctx, "running govulncheck in sandbox: %s@%s", modulePath, version)
 	smdir := strings.TrimPrefix(mdir, sandboxRoot)
-	stdout, err := s.sbox.Command("/binaries/vulncheck_sandbox", sandboxGovulncheck, ModeGovulncheck, smdir).Output()
+	stdout, err := s.sbox.Command(binaryDir+"/vulncheck_sandbox", govulncheckPath, ModeGovulncheck, smdir).Output()
 	log.Infof(ctx, "done with govulncheck in sandbox: %s@%s err=%v", modulePath, version, err)
 
 	if err != nil {
@@ -355,7 +354,7 @@
 	// Copy the binary from GCS to the local disk, because vulncheck.Binary
 	// requires a ReaderAt and GCS doesn't provide that.
 	gcsPathname := fmt.Sprintf("%s/%s@%s/%s", gcsBinaryDir, modulePath, version, binDir)
-	const destDir = "/bundle/rootfs/binaries"
+	const destDir = binaryDir
 	log.Debug(ctx, "copying",
 		"from", gcsPathname,
 		"to", destDir,
@@ -376,7 +375,7 @@
 	}
 
 	log.Infof(ctx, "running vulncheck in sandbox on %s: %s@%s/%s", modulePath, version, binDir, destf.Name())
-	stdout, err := s.sbox.Command("/binaries/vulncheck_sandbox", sandboxGovulncheck, ModeBinary, destf.Name()).Output()
+	stdout, err := s.sbox.Command(binaryDir+"/vulncheck_sandbox", govulncheckPath, ModeBinary, destf.Name()).Output()
 	log.Infof(ctx, "done with vulncheck in sandbox on %s: %s@%s/%s err=%v", modulePath, version, binDir, destf.Name(), err)
 
 	if err != nil {
@@ -432,7 +431,7 @@
 }
 
 func runGovulncheckCmd(ctx context.Context, pattern, tempDir string, stats *scanStats) ([]*govulncheckapi.Vuln, error) {
-	govulncheckName := "/bundle/rootfs/binaries/govulncheck"
+	govulncheckName := govulncheckPath
 	if !fileExists(govulncheckName) {
 		govulncheckName = "govulncheck"
 	}