Skip to content
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
1 change: 1 addition & 0 deletions MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ use_repo(
"com_github_gofrs_flock",
"com_github_hashicorp_go_version",
"com_github_mitchellh_go_homedir",
"org_golang_x_crypto",
"org_golang_x_term",
)
2 changes: 1 addition & 1 deletion core/core.go
Original file line number Diff line number Diff line change
Expand Up @@ -1395,7 +1395,7 @@ func downloadInstallerToCAS(installerURL, bazeliskHome string, config config.Con
tmpInstallerFile := fmt.Sprintf("%x-installer", tmpInstallerBytes)

// Download the installer
installerPath, err := httputil.DownloadBinary(installerURL, temporaryDownloadDir, tmpInstallerFile, config)
installerPath, err := httputil.DownloadBinary(installerURL, temporaryDownloadDir, tmpInstallerFile, config, true)
if err != nil {
return "", fmt.Errorf("failed to download installer: %w", err)
}
Expand Down
4 changes: 2 additions & 2 deletions core/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,7 @@ func (r *Repositories) DownloadFromBaseURL(baseURL, version, destDir, destFile s
}

url := fmt.Sprintf("%s/%s/%s", baseURL, version, srcFile)
return httputil.DownloadBinary(url, destDir, destFile, config)
return httputil.DownloadBinary(url, destDir, destFile, config, false)
}

// BuildURLFromFormat returns a Bazel download URL based on formatURL.
Expand Down Expand Up @@ -282,7 +282,7 @@ func (r *Repositories) DownloadFromFormatURL(config config.Config, formatURL, ve
return "", err
}

return httputil.DownloadBinary(url, destDir, destFile, config)
return httputil.DownloadBinary(url, destDir, destFile, config, false)
}

// CreateRepositories creates a new Repositories instance with the given repositories. Any nil repository will be replaced by a dummy repository that raises an error whenever a download is attempted.
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/gofrs/flock v0.13.0
github.com/hashicorp/go-version v1.7.0
github.com/mitchellh/go-homedir v1.1.0
golang.org/x/crypto v0.47.0
golang.org/x/term v0.39.0
)

Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
Expand Down
2 changes: 2 additions & 0 deletions httputil/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,15 @@ go_library(
"fake.go",
"httputil.go",
],
embedsrcs = ["bazel-release.pub.gpg"],
importpath = "github.com/bazelbuild/bazelisk/httputil",
visibility = ["//visibility:public"],
deps = [
"//config",
"//httputil/progress",
"@com_github_bgentry_go_netrc//netrc",
"@com_github_mitchellh_go_homedir//:go-homedir",
"@org_golang_x_crypto//openpgp:go_default_library",
],
)

Expand Down
76 changes: 76 additions & 0 deletions httputil/bazel-release.pub.gpg
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
-----BEGIN PGP PUBLIC KEY BLOCK-----

mQINBFdEmzkBEACzj8tMYUau9oFZWNDytcQWazEO6LrTTtdQ98d3JcnVyrpT16yg
I/QfGXA8LuDdKYpUDNjehLtBL3IZp4xe375Jh8v2IA2iQ5RXGN+lgKJ6rNwm15Kr
qYeCZlU9uQVpZuhKLXsWK6PleyQHjslNUN/HtykIlmMz4Nnl3orT7lMI5rsGCmk0
1Kth0DFh8SD9Vn2G4huddwxM8/tYj1QmWPCTgybATNuZ0L60INH8v6+J2jJzViVc
NRnR7mpouGmRy/rcr6eY9QieOwDou116TrVRFfcBRhocCI5b6uCRuhaqZ6Qs28Bx
4t5JVksXJ7fJoTy2B2s/rPx/8j4MDVEdU8b686ZDHbKYjaYBYEfBqePXScp8ndul
XWwS2lcedPihOUl6oQQYy59inWIpxi0agm0MXJAF1Bc3ToSQdHw/p0Y21kYxE2pg
EaUeElVccec5poAaHSPprUeej9bD9oIC4sMCsLs7eCQx2iP+cR7CItz6GQtuZrvS
PnKju1SKl5iwzfDQGpi6u6UAMFmc53EaH05naYDAigCueZ+/2rIaY358bECK6/VR
kyrBqpeq6VkWUeOkt03VqoPzrw4gEzRvfRtLj+D2j/pZCH3vyMYHzbaaXBv6AT0e
RmgtGo9I9BYqKSWlGEF0D+CQ3uZfOyovvrbYqNaHynFBtrx/ZkM82gMA5QARAQAB
tEdCYXplbCBEZXZlbG9wZXIgKEJhemVsIEFQVCByZXBvc2l0b3J5IGtleSkgPGJh
emVsLWRldkBnb29nbGVncm91cHMuY29tPokCPgQTAQIAKAIbAwYLCQgHAwIGFQgC
CQoLBBYCAwECHgECF4AFAlsGueoFCQeEhaQACgkQPVkZtEhFfuCojRAAqtUaEbK8
zVAPssZDRPun0k1XB3hXxEoe5kt00cl51F+KLXN2OM5gOn2PcUw4A+Ci+48cgt9b
hTWwWuC9OPn9OCvYVyuTJXT189Pmg+F9l3zD/vrD5gdFKDLJCUPo/tRBTDQqrRGA
JssWIzvGR65O2AosoIcj7VAfNj34CBHm25abNpGnWmkiREZzElLFqjTR+FwAMxyA
VJnPbn+K1zyi9xUZKcL1QzKcHBTPFAdZR6zTII/+03n4wAL/w8+x/A1ocmE7jxCI
cgq7vaHSpGmigU2+TXckUslIgIC64iqYBpPvFAPNlqXmo9rDfL2Imyyuz1ep7j/b
JrsOxVKwHO8HfgE2WcvcEmkjQ3kpW+qVflwPKsfKRN6oe1rX5l9MxS/nGPok4BII
V9Y82K3o8Yu0KUgbHhEsITNizBgeJSIEhbF9YAmMeBie6zRnsOKmOqnx2Y9OAfU7
QhpUoO9DBVk/c3KkiOSf6RYxjrLmou/tLKdsQaenKTDOH8fQTexnMYxRlp5yU1+9
eZOdJeRDm078tGB+IRWB3QElIgYiRbCd8VzgDsMJJQbQ2VdQlVaZL84d6Zntk2pL
a4HDB4nE+UpfoLcT7iM9hqn9b7NHzmHiPVJecNNGjLTvxZ1sW7+0S7oo7lOMrEPp
k84DXEqg20Cb3D7YKirwR7qi/StTdil3bYKJAk8EEwEIADkCGwMGCwkIBwMCBhUI
AgkKCwQWAgMBAh4BAheAFiEEcaHQ78/rYoH9BDfJPVkZtEhFfuAFAmKM1bQACgkQ
PVkZtEhFfuAD5A/7BdC4RiWxifnmfBX46bjMq0YVI5dcc4vPxDXpM4+AhVjjhVcg
mDWbhS/+OeYLcmw/TPd4h0/BLbwP5p+GyicgTc24XAmVEYFSOKfqwkn198hU3E6n
27HKQ8fjRnkvEHFd61kUJwU/pBWBNFe+0dKWUp4rJptLBnjb7+VPxFKFK05skhHV
sBSwKGfUehCuxw3rsMOiwlu4KQSOmpMStC7msPFT3/FiR46znBF4C5GxzAbXdLjw
BTXM89uwHVpE5HH1MB1jLjUj8Me6MfMvBL+H3Ogw/FqOPjrSVX4fPdt7nsezE3Gg
Elecsv+4oDfS6mAMxYuUAQyu/0kAcSl1bqmxvx4kJ6YnUD9RiMz3T32XgWKMmJDN
Q6vfOfyy7OviFjBhbaRWcIfWfTHrDMvrOXs+M+qPfyltb9HVPYt+d8HDcXzVsLsR
g9hUNUbddpignlo4waIJxAWiM9hl/GDFPOOL/UafSiOM+gI737zG4MWa22BPid5J
b1Ph3eWQkTWW+oYqaMjKfkFPy4jTwz9IKRXSrFZOzkbdon+iIWvbrXz0aXbzhj8I
TPrh1WZH0oUbNUAK81D3gGODglBGd5fypzSMJe4+aLaRLjb1M/rubY1JjQrGGhu8
6XyLmOcoZFNWBfTWlJ9CrOW3E22DnMuvuyl1wBk6kXv8HInoK4gUbJ8KWwO5Ag0E
V0SbOQEQAOef9VQZQ6VfxJVMi5kcjws/1fprB3Yp8sODL+QyULqbmcJTMr8Tz83O
xprCH5Nc7jsw1oqzbNtq+N2pOnbAL6XFPolQYuOjKlHGzbQvpH8ZSok6AzwrPNq3
XwoB0+12A86wlpajUPfvgajNjmESMchLnIs3qH1j5ayVICr7vH1i1Wem2J+C/z6g
IaG4bko0XKAeU6fNYRmuHLHCiBiKocpn54LmmPL4ifN7Rz1KkCaAKTT8vKtaVh0g
1eswb+9W3qldm+nAc6e1ajWDiLqhOmTQRVrght80XPYmtv2x8cdkxgECbT6T84rZ
tMZAdxhjdOmJ50ghPn9o/uxdCDurhZUsu4aND6EhWw4EfdZCSt0tGQWceB9tXCKV
lgc3/TXdTOB9zuyoZxkmQ6uvrV2ffxf2VLwmR6UJSXsAz2Pd9eWJmnH+QmZPMXhO
VFCMRTHTsRfAeyLW+q2xVr/rc1nV/9PzPP29GSYVb54Fs7of2oHUuBOWp3+2oRlj
Peoz0SEBG/Q0TdmBqfYTol9rGapIcROc1qg9oHV6dmQMTAkx3+Io8zlbDp3Xu2+Q
agtCS+94DcH9Yjh8ggM6hohX2ofP6HQUw4TLHVTLI0iMc3MJcEZ88voQbHWKT9fY
niQjKBESU21IErKT3YWP2OAoc5RR44gCmE+r14mHCktOLLQrR6sBABEBAAGJAiUE
GAECAA8CGwwFAlsGuf0FCQeEhcEACgkQPVkZtEhFfuCMcA/9GRtPSda2fW84ZXoc
9QrXQYl6JqZr+6wCmS029F3PD7OHE3F2aeFe+eZIWOFpQG6IKHLbZ2XbYnzAfSBA
TpnTjULbDlAk7dFBIWEZMu5aP8DGvdtsGLE+DZjiLoyaCsQisWp4vIOxiXBnymAy
iFcY570CJPm7/Woo5ACdNYHW67Jdq7KTIpMy9mrTvkJccdLrifksddlKDkrcUSyQ
6hHHDmtAdNGyD6Wnm/6Yx7lRM1shQyKxYO1RwFmaB1lsG65+5gKc7wXgyOtxyAbW
KFxsbbaBStvPo0amBuIxnprQe7CEKcc90SIG5Ji4v6yEyfBuG5bR92UDw8rIhLr9
nBprtUr87nsAU1mxFJoGEFmXekIZp5x3AvZw99OtNx8HGf02i0DKAME0c/PCUIck
t2epluZs2DDDuIG0eG2FX+MJDGErt6Tktwcoz2d6Qxh0TAZ9Dh9ci7/0FFcyYCyG
iiQ39Mr8xM1U91df9vwjq6/neisTsTMhkqwzkTD26NzoJz98oauDnB9hNeBKCX7b
A92/IAZ5tYzeSBstb12d+LfGpTo6Xl6/Pj0xGqMbE8ANfOix53Ugtm4ZODyynS7q
geZBSCfdoQTrUNxdO2xJuJ5BQVnBMcbYXxVYuaZb+VKioVKOsad7KMCTx5UseA/A
PEuflVm352z0x6cARlJwO5HhSx2JAjYEGAEIACACGwwWIQRxodDvz+tigf0EN8k9
WRm0SEV+4AUCYozV3wAKCRA9WRm0SEV+4HOTD/sElzm4kfrMbzxNjnA2WCwn0CdY
f2cmmAaFPmbuzy02dLDr9DIvyGfW7O8Wami+Oc63c9F09a+3ZjiTZP++Jrc8WrRs
L87q8H87zugIIglyobIQOzA9YUyV32Hip+nXR4rg7z0uDAIet3ggxnuPv9OXnT8p
8FdGPIvE2HCKwFwN1FSjv4/Coq1ryvDktkBeiWgqHB3zwDl7soczUqdXoRnqGKSY
F2Ezj6QhvAMz3d8lW5T281tN50HtHD8rhr2JcdoxYTYb2kaRTbh3rtdrDUIvKvP/
YYWlMdjGFaqhfL3wA9QD+WVUQTl7ifLAlfj1vS6ll9qdQRwb2tPYN+1BPmXWLNmK
qRP6ECWXkRinA81saWRLaA4otF5SaB1bLbp2ZrBMqYTDDBB0QjF5UcMFU5Pqxmya
FP+crpzZq+XgSgFfgCWcJ9PLTjkhzHFMTqnE7BVZdSYcRk2IBXtK7DJwuatH4A8m
MOV+qxN+ECjlRNNSyRasjuYVNdFVO6UUb9MMgOLsoJMpbCPJUQd9Wx6Q6irjTiUk
bImrkQjn0HGqTVGi3ASYpne7NE+yWOAw3ZH009UBTk5sPIdD6ZwlbHRNM+3OKWSC
3uoaOgq4H1d+hVSy7l198Frx5gfKoiTJUjLXgOmwCJUQfJjEspvw2XuFuVNfBzuk
MZaF+SBEZXd1ZSqB5Q==
=laPs
-----END PGP PUBLIC KEY BLOCK-----
42 changes: 41 additions & 1 deletion httputil/httputil.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@
package httputil

import (
"bytes"
_ "embed"
b64 "encoding/base64"
"errors"
"fmt"
"golang.org/x/crypto/openpgp"
"io"
"log"
"math/rand"
Expand Down Expand Up @@ -38,6 +41,9 @@ var (
MaxRequestDuration = time.Second * 30
retryHeaders = []string{"Retry-After", "X-RateLimit-Reset", "Rate-Limit-Reset"}
NotFound = errors.New("not found")

//go:embed bazel-release.pub.gpg
VerificationKey []byte
)

// Clock keeps track of time. It can return the current time, as well as move forward by sleeping for a certain period.
Expand Down Expand Up @@ -187,7 +193,7 @@ func tryFindNetrcFileCreds(host string) (string, error) {
}

// DownloadBinary downloads a file from the given URL into the specified location, marks it executable and returns its full path.
func DownloadBinary(originURL, destDir, destFile string, config config.Config) (string, error) {
func DownloadBinary(originURL, destDir, destFile string, config config.Config, verifySignature bool) (string, error) {
err := os.MkdirAll(destDir, 0755)
if err != nil {
return "", fmt.Errorf("could not create directory %s: %v", destDir, err)
Expand Down Expand Up @@ -247,6 +253,40 @@ func DownloadBinary(originURL, destDir, destFile string, config config.Config) (
return "", fmt.Errorf("could not chmod file %s: %v", tmpfile.Name(), err)
}

if verifySignature {
signatureURL := originURL + ".sig"

signature, err := get(signatureURL, "")
if err != nil {
return "", fmt.Errorf("HTTP GET %s failed: %v", signatureURL, err)
}
defer signature.Body.Close()

if signature.StatusCode != 200 {
return "", fmt.Errorf("HTTP GET %s failed with error %v", signatureURL, signature.StatusCode)
}

keys, err := openpgp.ReadArmoredKeyRing(bytes.NewReader(VerificationKey))
if err != nil {
return "", fmt.Errorf("failed to load the embedded Verification Key")
}

if len(keys) != 1 {
return "", fmt.Errorf("failed to load the embedded Verification Key")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting this together, @PiotrSikora! We'd love to leverage this too.

Three ideas for handling an expired embedded key:

  1. Add an error message on failure (bonus points for specifically looking if the key is expir,ed) pointing users to look for a new release since the embedded verification key may be expired.
  2. Add a --no-verify flag to skip verification entirely 😅
  3. Add a --verification-signature-file flag as a "break glass" method to use a different key, which folks can use to pull from a fork.

Before adding those workarounds, it'd likely be better to avoid the complexity if possible! If there's anyone who won't be able to upgrade bazelisk to get a new key, it'd be great to hear about that use case! The only actual caveat I can think of is remembering to upgrade this tool every N years with the new key, but that's well worth the tradeoff to me! 🎉

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

+1 to the flags, although I think we can do without for now - users (companies) with custom requirements are likely not using this code path (but rather forks or their own repo implementations).

}

tmpfile.Seek(0, io.SeekStart)

entity, err := openpgp.CheckDetachedSignature(keys, tmpfile, signature.Body)
if err != nil {
return "", fmt.Errorf("failed to verify the downloaded file using signature from %s", signatureURL)
}

for _, identity := range entity.Identities {
log.Printf("Signed by %s", identity.Name)
}
}

tmpfile.Close()
err = os.Rename(tmpfile.Name(), destinationPath)
if err != nil {
Expand Down
6 changes: 3 additions & 3 deletions repositories/gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ func (gcs *GCSRepo) DownloadLTS(version, destDir, destFile string, config config
}

url := fmt.Sprintf("%s/%s/%s/%s", ltsBaseURL, baseVersion, folder, srcFile)
return httputil.DownloadBinary(url, destDir, destFile, config)
return httputil.DownloadBinary(url, destDir, destFile, config, true)
}

// CommitRepo
Expand Down Expand Up @@ -225,7 +225,7 @@ func (gcs *GCSRepo) DownloadAtCommit(commit, destDir, destFile string, config co
return "", err
}
url := fmt.Sprintf("%s/%s/%s/bazel", commitBaseURL, platform, commit)
return httputil.DownloadBinary(url, destDir, destFile, config)
return httputil.DownloadBinary(url, destDir, destFile, config, false)
}

// RollingRepo
Expand Down Expand Up @@ -274,5 +274,5 @@ func (gcs *GCSRepo) DownloadRolling(version, destDir, destFile string, config co

releaseVersion := strings.Split(version, "-")[0]
url := fmt.Sprintf("%s/%s/rolling/%s/%s", ltsBaseURL, releaseVersion, version, srcFile)
return httputil.DownloadBinary(url, destDir, destFile, config)
return httputil.DownloadBinary(url, destDir, destFile, config, true)
}
2 changes: 1 addition & 1 deletion repositories/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,5 @@ func (gh *GitHubRepo) DownloadVersion(fork, version, destDir, destFile string, c
return "", err
}
url := fmt.Sprintf(urlPattern, fork, version, filename)
return httputil.DownloadBinary(url, destDir, destFile, config)
return httputil.DownloadBinary(url, destDir, destFile, config, false)
}