Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for BAZELISK_VERIFY_SHA256 #441

Merged
merged 1 commit into from
Apr 5, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ Additionally, a few special version names are supported for our official release

## Where does Bazelisk get Bazel from?

By default Bazelisk retrieves Bazel releases, release candidates and binaries built at green commits from Google Cloud Storage.
By default Bazelisk retrieves Bazel releases, release candidates and binaries built at green commits from Google Cloud Storage. The downloaded artifacts are validated against the SHA256 value recorded in `BAZELISK_VERIFY_SHA256` if this variable is set in the configuration file.

As mentioned in the previous section, the `<FORK>/<VERSION>` version format allows you to use your own Bazel fork hosted on GitHub:

Expand Down Expand Up @@ -149,6 +149,7 @@ The following variables can be set:
- `BAZELISK_SHUTDOWN`
- `BAZELISK_SKIP_WRAPPER`
- `BAZELISK_USER_AGENT`
- `BAZELISK_VERIFY_SHA256`
- `USE_BAZEL_VERSION`

Configuration variables are evaluated with precedence order. The preferred values are derived in order from highest to lowest precedence as follows:
Expand Down
48 changes: 48 additions & 0 deletions bazelisk_test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,50 @@ function test_bazel_download_path_go() {
(echo "FAIL: Expected to download bazel binary into specific path."; exit 1)
}

function test_bazel_verify_sha256() {
setup

echo "6.1.1" > .bazelversion

# First try to download and expect an invalid hash (it doesn't matter what it is).
if BAZELISK_HOME="$BAZELISK_HOME" BAZELISK_VERIFY_SHA256="invalid-hash" \
bazelisk version 2>&1 | tee log; then
echo "FAIL: Command should have errored out"; exit 1
fi

grep "need sha256=invalid-hash" log || \
(echo "FAIL: Expected to find hash mismatch"; exit 1)

# IMPORTANT: The mixture of lowercase and uppercase letters in the hashes below is
# intentional to ensure the variable contents are normalized before comparison.
# If updating these values, re-introduce randomness.
local os="$(uname -s | tr A-Z a-z)"
case "${os}" in
darwin)
expected_sha256="038e95BAE998340812562ab8d6ada1a187729630bc4940a4cd7920cc78acf156"
;;
linux)
expected_sha256="651a20d85531325df406b38f38A1c2578c49D5e61128fba034f5b6abdb3d303f"
;;
msys*|mingw*|cygwin*)
expected_sha256="1d997D344936a1d98784ae58db1152d083569556f85cd845e6e340EE855357f9"
;;
*)
echo "FAIL: Unknown OS ${os} in test"
exit 1
;;
esac

# Now try the same download as before but with the correct hash expectation. Note that the
# hash has a random uppercase / lowercase mixture to ensure this does not impact equality
# checks.
BAZELISK_HOME="$BAZELISK_HOME" BAZELISK_VERIFY_SHA256="${expected_sha256}" \
bazelisk version 2>&1 | tee log

grep "Build label:" log || \
(echo "FAIL: Expected to find 'Build label' in the output of 'bazelisk version'"; exit 1)
}

function test_bazel_download_path_py() {
setup

Expand Down Expand Up @@ -411,6 +455,10 @@ if [[ $BAZELISK_VERSION == "GO" ]]; then
test_bazel_download_path_go
echo

echo '# test_bazel_verify_sha256'
test_bazel_verify_sha256
echo

echo "# test_bazel_prepend_binary_directory_to_path_go"
test_bazel_prepend_binary_directory_to_path_go
echo
Expand Down
41 changes: 39 additions & 2 deletions core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -411,18 +411,55 @@ func downloadBazelIfNecessary(version string, baseDirectory string, repos *Repos
}

destDir := filepath.Join(baseDirectory, pathSegment, "bin")
expectedSha256 := strings.ToLower(GetEnvOrConfig("BAZELISK_VERIFY_SHA256"))

tmpDestFile := "bazel-tmp" + platforms.DetermineExecutableFilenameSuffix()
destFile := "bazel" + platforms.DetermineExecutableFilenameSuffix()

destPath := filepath.Join(destDir, destFile)
if _, err := os.Stat(destPath); err == nil {
return destPath, nil
}

var tmpDestPath string
if url := GetEnvOrConfig(BaseURLEnv); url != "" {
return repos.DownloadFromBaseURL(url, version, destDir, destFile)
tmpDestPath, err = repos.DownloadFromBaseURL(url, version, destDir, tmpDestFile)
} else {
tmpDestPath, err = downloader(destDir, tmpDestFile)
}
if err != nil {
return "", err
}

return downloader(destDir, destFile)
if len(expectedSha256) > 0 {
f, err := os.Open(tmpDestPath)
if err != nil {
os.Remove(tmpDestPath)
fweikert marked this conversation as resolved.
Show resolved Hide resolved
return "", fmt.Errorf("cannot open %s after download: %v", tmpDestPath, err)
}
defer os.Remove(tmpDestPath)
// We cannot defer f.Close() because keeping the handle open when we try to do the
// rename later on fails on Windows.

h := sha256.New()
if _, err := io.Copy(h, f); err != nil {
f.Close()
return "", fmt.Errorf("cannot compute sha256 of %s after download: %v", tmpDestPath, err)
}
f.Close()

actualSha256 := strings.ToLower(fmt.Sprintf("%x", h.Sum(nil)))
if expectedSha256 != actualSha256 {
return "", fmt.Errorf("%s has sha256=%s but need sha256=%s", tmpDestPath, actualSha256, expectedSha256)
}
}

// Only place the downloaded binary in its final location once we know it is fully downloaded
// and valid, to prevent invalid files from ever being executed.
if err = os.Rename(tmpDestPath, destPath); err != nil {
return "", fmt.Errorf("cannot rename %s to %s: %v", tmpDestPath, destPath, err)
}
return destPath, nil
}

func copyFile(src, dst string, perm os.FileMode) error {
Expand Down