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

Authenticate releases using the embedded verification key. #192

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
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
28 changes: 28 additions & 0 deletions WORKSPACE
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,34 @@ go_repository(
version = "v1.1.0",
)

go_repository(
name = "org_golang_x_crypto",
importpath = "golang.org/x/crypto",
sum = "h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=",
version = "v0.0.0-20211215153901-e495a2d5b3d3",
)

go_repository(
name = "org_golang_x_net",
importpath = "golang.org/x/net",
sum = "h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=",
version = "v0.0.0-20211112202133-69e39bad7dc2",
)

go_repository(
name = "org_golang_x_sys",
importpath = "golang.org/x/sys",
sum = "h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4=",
version = "v0.0.0-20210615035016-665e8c7367d1",
)

go_repository(
name = "org_golang_x_term",
importpath = "golang.org/x/term",
sum = "h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E=",
version = "v0.0.0-20201126162022-7de9c90e9dd1",
)

go_rules_dependencies()

go_register_toolchains(version = "1.16.4")
Expand Down
2 changes: 1 addition & 1 deletion core/repositories.go
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,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)
return httputil.DownloadBinary(url, "", "", destDir, destFile)
}

// 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 @@ -6,4 +6,5 @@ require (
github.com/bazelbuild/rules_go v0.29.0
github.com/hashicorp/go-version v1.3.0
github.com/mitchellh/go-homedir v1.1.0
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3
)
7 changes: 7 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,10 @@ github.com/hashicorp/go-version v1.3.0 h1:McDWVJIU/y+u1BRV06dPaLfLCaT7fUTJLp5r04
github.com/hashicorp/go-version v1.3.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y=
github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3 h1:0es+/5331RGQPcXlMfP+WrnIIS6dNnNRe0WB02W0F4M=
golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
1 change: 1 addition & 0 deletions httputil/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ go_library(
],
importpath = "github.com/bazelbuild/bazelisk/httputil",
visibility = ["//visibility:public"],
deps = ["@org_golang_x_crypto//openpgp:go_default_library"],
)

go_test(
Expand Down
36 changes: 35 additions & 1 deletion httputil/httputil.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package httputil

import (
"fmt"
"golang.org/x/crypto/openpgp"
"io"
"io/ioutil"
"log"
Expand All @@ -12,6 +13,7 @@ import (
"path/filepath"
"regexp"
"strconv"
"strings"
"time"
)

Expand Down Expand Up @@ -134,7 +136,7 @@ func parseRetryHeader(value string) (time.Duration, 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) (string, error) {
func DownloadBinary(originURL, signatureURL, verificationKey, destDir, destFile string) (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 @@ -174,6 +176,38 @@ func DownloadBinary(originURL, destDir, destFile string) (string, error) {
return "", fmt.Errorf("could not chmod file %s: %v", tmpfile.Name(), err)
}

if signatureURL != "" && verificationKey != "" {
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(strings.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

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! 🎉

}

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
60 changes: 57 additions & 3 deletions repositories/gcs.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,60 @@ const (
candidateBaseURL = "https://releases.bazel.build"
nonCandidateBaseURL = "https://storage.googleapis.com/bazel-builds/artifacts"
lastGreenBaseURL = "https://storage.googleapis.com/bazel-untrusted-builds/last_green_commit/"
verificationKey = `
-----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
emVsLWRldkBnb29nbGVncm91cHMuY29tPokCVQQTAQgAPwIbAwYLCQgHAwIGFQgC
CQoLBBYCAwECHgECF4AWIQRxodDvz+tigf0EN8k9WRm0SEV+4AUCXsoWGgUJC0fh
4QAKCRA9WRm0SEV+4NDCD/9c5rhZREBlikdi5QYRq1YOkwzJLXFoVe0FonEwMuWK
fQzT/rIwyh14tssptU5+eXwTEXL0ZDskgzvrFSpzjQZzcSG/gzNCATNfrZpC2nfE
SxMKOeIwQedn26YIHCI8s9tEQ7BSvfBfJgqfIo3IURhmfzNMj+qszca+3IDYAlAy
8lxUVbJcIQ0apnAdnIadtydzca56mMN7ma+btddaWLpAdyfUvQ/Zsx3TYYLF7inQ
km0JpzISN0fGngzGNDGNmtHNhCdSpyfkr+7fvpbKAYkSH7uZ1AIPDyHdLIwDQnX2
kbLRkxKncKGSDhUSdlJTl0x36cU+xmgO15FFdOyk3BUfrlfDrgXIBjeX8KNh9TV6
HgFFR/mNONoJ93ZvZQNO2s1gbPZJe3VJ1Q5PMLW1sdl8q8JthBwT/5TJ1k8E5VYj
jAc8dl+RAALxqj+eo5xI45o1FdV5s1aGDjbwFoCIhGCy2zaog1q5wnhmEptAAD0S
TVbJSpwNiLlPIcGVaCjXp8Ow3SzOGTRKIjFTO/I6FiSJOpgfri07clXmnb4ETjou
mUdglg8/8nQ120zHEOqoSzzIbTNUDjNZY8SuY6Ig3/ObQ/JAFS0i6h74KLfXUZzn
uETY7KURLdyPAhL37Hb9FDhvkJCUO/l6eqDh9jk1JjB7Cvb7hEvnbvDrr2hWNAL7
RrkCDQRXRJs5ARAA55/1VBlDpV/ElUyLmRyPCz/V+msHdinyw4Mv5DJQupuZwlMy
vxPPzc7GmsIfk1zuOzDWirNs22r43ak6dsAvpcU+iVBi46MqUcbNtC+kfxlKiToD
PCs82rdfCgHT7XYDzrCWlqNQ9++BqM2OYRIxyEucizeofWPlrJUgKvu8fWLVZ6bY
n4L/PqAhobhuSjRcoB5Tp81hGa4cscKIGIqhymfnguaY8viJ83tHPUqQJoApNPy8
q1pWHSDV6zBv71beqV2b6cBzp7VqNYOIuqE6ZNBFWuCG3zRc9ia2/bHxx2TGAQJt
PpPzitm0xkB3GGN06YnnSCE+f2j+7F0IO6uFlSy7ho0PoSFbDgR91kJK3S0ZBZx4
H21cIpWWBzf9Nd1M4H3O7KhnGSZDq6+tXZ9/F/ZUvCZHpQlJewDPY9315Ymacf5C
Zk8xeE5UUIxFMdOxF8B7Itb6rbFWv+tzWdX/0/M8/b0ZJhVvngWzuh/agdS4E5an
f7ahGWM96jPRIQEb9DRN2YGp9hOiX2sZqkhxE5zWqD2gdXp2ZAxMCTHf4ijzOVsO
nde7b5BqC0JL73gNwf1iOHyCAzqGiFfah8/odBTDhMsdVMsjSIxzcwlwRnzy+hBs
dYpP19ieJCMoERJTbUgSspPdhY/Y4ChzlFHjiAKYT6vXiYcKS04stCtHqwEAEQEA
AYkCPAQYAQgAJgIbDBYhBHGh0O/P62KB/QQ3yT1ZGbRIRX7gBQJeyhYlBQkLR+Hs
AAoJED1ZGbRIRX7g3Y8P/iuOAHmyCMeSELvUs9ZvLYJKGzmz67R8fJSmgst/Bs3p
dWCAjGE56M6UgZzHXK+fBRWFPDOXT64XNq0UIG7tThthwe4Gdvg/5rWG61Pe/vCZ
2FkMAlEMkuufZYMcw9jItHMKLcYyW/jtN9EzCX+vM6SZlu4o8la5rCIBEaiKfzft
a/dRMjW+RqQnU31NQCDAy3zoGUCQumJtv3GVbMYHIrRZua2yyNo9Iborh2SVdBbK
v9WJKH4JcCHd0/XDGdys6EXeATIIRxchumkmxpIg87OhsC0n5yuH1FnFIFQEjbYX
bb46F7ZFT+8Tov+lgMEw4CZmps4uvvZlKbIH4Zi/ULiobwvm2ad3nejWICmGmHYz
ro6t08hdcY6GnOzCpDwx9yHechMCkU3KEE98nb/CxcmA4VzDHudTJe7o0OyaSarh
6D5WcXf7D9FfcKmUD9xaCsfXh66OCksMVGE1JctrO1wQTF2jTdTUq7mmi30tlM+o
JjVk65OSOd4JYol8auzE4oXOfsNzXbyvj7WzM1v5m7C45jOL+Ly7I3IUzZNfF41J
AMmSd73EOoR9YH4qTrL3jx69Ekf7ww70Qea5enLE8xUgQfGTOaEHxkFcEovmzv54
6IVe083iK8alXD/9OUTaDY9NwMnOn1K1aU2XOfliGGLgwwaHg+wVFh5rZIHsDl7v
=Embu
-----END PGP PUBLIC KEY BLOCK-----
`
)

var (
Expand Down Expand Up @@ -127,7 +181,7 @@ func (gcs *GCSRepo) DownloadRelease(version, destDir, destFile string) (string,
}

url := fmt.Sprintf("%s/%s/release/%s", candidateBaseURL, version, srcFile)
return httputil.DownloadBinary(url, destDir, destFile)
return httputil.DownloadBinary(url, url+".sig", verificationKey, destDir, destFile)
}

func (gcs *GCSRepo) removeCandidates(history []string, lastN int) ([]string, error) {
Expand Down Expand Up @@ -216,7 +270,7 @@ func (gcs *GCSRepo) DownloadCandidate(version, destDir, destFile string) (string
baseVersion := versionComponents[0]
rcVersion := "rc" + versionComponents[1]
url := fmt.Sprintf("%s/%s/%s/%s", candidateBaseURL, baseVersion, rcVersion, srcFile)
return httputil.DownloadBinary(url, destDir, destFile)
return httputil.DownloadBinary(url, url+".sig", verificationKey, destDir, destFile)
}

// CommitRepo
Expand All @@ -237,5 +291,5 @@ func (gcs *GCSRepo) GetLastGreenCommit(bazeliskHome string, downstreamGreen bool
func (gcs *GCSRepo) DownloadAtCommit(commit, destDir, destFile string) (string, error) {
log.Printf("Using unreleased version at commit %s", commit)
url := fmt.Sprintf("%s/%s/%s/bazel", nonCandidateBaseURL, platforms.GetPlatform(), commit)
return httputil.DownloadBinary(url, destDir, destFile)
return httputil.DownloadBinary(url, "", "", destDir, destFile)
}
2 changes: 1 addition & 1 deletion repositories/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,7 @@ func (gh *GitHubRepo) DownloadVersion(fork, version, destDir, destFile string) (
return "", err
}
url := fmt.Sprintf(urlPattern, fork, version, filename)
return httputil.DownloadBinary(url, destDir, destFile)
return httputil.DownloadBinary(url, "", "", destDir, destFile)
}

// GetRollingVersions returns a list of all available rolling release versions.
Expand Down