diff --git a/cmd/release/release.go b/cmd/release/release.go index 4bb7df587a..b4cdab2bae 100644 --- a/cmd/release/release.go +++ b/cmd/release/release.go @@ -15,6 +15,7 @@ import ( "log" "math" "math/rand" + "net/http" "os" "path/filepath" "regexp" @@ -125,7 +126,7 @@ const gerritURL = "https://go.googlesource.com/go" func doRelease(ctx *workflow.TaskContext, revision, version string, target *releasetargets.Target, stagingDir string, watch bool) error { srcBuf := &bytes.Buffer{} - if err := task.WriteSourceArchive(ctx, gerritURL, revision, version, srcBuf); err != nil { + if err := task.WriteSourceArchive(ctx, http.DefaultClient, gerritURL, revision, version, srcBuf); err != nil { return fmt.Errorf("Building source archive: %v", err) } @@ -261,7 +262,7 @@ func writeSourceFile(ctx *workflow.TaskContext, revision, version, outPath strin if err != nil { return err } - if err := task.WriteSourceArchive(ctx, gerritURL, revision, version, w); err != nil { + if err := task.WriteSourceArchive(ctx, http.DefaultClient, gerritURL, revision, version, w); err != nil { return err } return w.Close() diff --git a/cmd/relui/deployment-prod.yaml b/cmd/relui/deployment-prod.yaml index 05a066b4f8..e49eb8ed0b 100644 --- a/cmd/relui/deployment-prod.yaml +++ b/cmd/relui/deployment-prod.yaml @@ -32,7 +32,6 @@ spec: # Define the site header and external service configuration. - "--site-title=Go Releases" - "--site-header-css=Site-header--production" - - "--gerrit-api-secret=secret:symbolic-datum-552/gobot-password" - "--sendgrid-api-key=secret:symbolic-datum-552/sendgrid-sendonly-api-key" - "--announce-mail-from=announce@golang.org" - "--announce-mail-to=golang-nuts@googlegroups.com" diff --git a/cmd/relui/main.go b/cmd/relui/main.go index d6e086e79d..dfeddcceee 100644 --- a/cmd/relui/main.go +++ b/cmd/relui/main.go @@ -35,6 +35,7 @@ import ( "golang.org/x/build/internal/secret" "golang.org/x/build/internal/task" "golang.org/x/oauth2" + "golang.org/x/oauth2/google" ) var ( @@ -57,7 +58,6 @@ func main() { if err := secret.InitFlagSupport(context.Background()); err != nil { log.Fatalln(err) } - gerritAPIFlag := secret.Flag("gerrit-api-secret", "Gerrit API secret to use for workflows that interact with Gerrit.") sendgridAPIKey := secret.Flag("sendgrid-api-key", "SendGrid API key for workflows involving sending email.") var annMail task.MailHeader addressVarFlag(&annMail.From, "announce-mail-from", "The From address to use for the announcement mail.") @@ -92,8 +92,12 @@ func main() { Title: *siteTitle, CSSClass: *siteHeaderCSS, } + creds, err := google.FindDefaultCredentials(ctx, gerrit.OAuth2Scopes...) + if err != nil { + log.Fatalf("reading GCP credentials: %v", err) + } gerritClient := &task.RealGerritClient{ - Client: gerrit.NewClient("https://go-review.googlesource.com", gerrit.BasicAuth("git-gobot.golang.org", *gerritAPIFlag)), + Client: gerrit.NewClient("https://go-review.googlesource.com", gerrit.OAuth2Auth(creds.TokenSource)), } versionTasks := &task.VersionTasks{ Gerrit: gerritClient, @@ -135,6 +139,7 @@ func main() { defer db.Close() buildTasks := &relui.BuildReleaseTasks{ + GerritHTTPClient: oauth2.NewClient(ctx, creds.TokenSource), GerritURL: "https://go.googlesource.com/go", PrivateGerritURL: "https://team.googlesource.com/go-private", CreateBuildlet: coordinator.CreateBuildlet, diff --git a/gerrit/auth.go b/gerrit/auth.go index 428f9adc5e..7afad05c4e 100644 --- a/gerrit/auth.go +++ b/gerrit/auth.go @@ -10,7 +10,6 @@ import ( "encoding/hex" "fmt" "io/ioutil" - "log" "net/http" "net/http/cookiejar" "net/url" @@ -22,12 +21,14 @@ import ( "strings" "sync" "time" + + "golang.org/x/oauth2" ) // Auth is a Gerrit authentication mode. // The most common ones are NoAuth or BasicAuth. type Auth interface { - setAuth(*Client, *http.Request) + setAuth(*Client, *http.Request) error } // BasicAuth sends a username and password. @@ -39,8 +40,9 @@ type basicAuth struct { username, password string } -func (ba basicAuth) setAuth(c *Client, r *http.Request) { +func (ba basicAuth) setAuth(c *Client, r *http.Request) error { r.SetBasicAuth(ba.username, ba.password) + return nil } // GitCookiesAuth derives the Gerrit authentication token from @@ -69,7 +71,7 @@ func netrcPath() string { type gitCookiesAuth struct{} -func (gitCookiesAuth) setAuth(c *Client, r *http.Request) { +func (gitCookiesAuth) setAuth(c *Client, r *http.Request) error { // First look in Git's http.cookiefile, which is where Gerrit // now tells users to store this information. git := exec.Command("git", "config", "http.cookiefile") @@ -82,16 +84,17 @@ func (gitCookiesAuth) setAuth(c *Client, r *http.Request) { cookieFile := strings.TrimSpace(string(gitOut)) if len(cookieFile) != 0 { auth := &gitCookieFileAuth{file: cookieFile} - auth.setAuth(c, r) + if err := auth.setAuth(c, r); err != nil { + return err + } if len(r.Header["Cookie"]) > 0 { - return + return nil } } url, err := url.Parse(c.url) if err != nil { - // Something else will complain about this. - return + return err } // If not there, then look in $HOME/.netrc, which is where Gerrit @@ -107,10 +110,10 @@ func (gitCookiesAuth) setAuth(c *Client, r *http.Request) { f := strings.Fields(line) if len(f) >= 6 && f[0] == "machine" && f[1] == host && f[2] == "login" && f[4] == "password" { r.SetBasicAuth(f[3], f[5]) - return + return nil } } - log.Printf("no authentication configured for Gerrit; tried both git config http.cookiefile and %s", netrc) + return fmt.Errorf("no authentication configured for Gerrit; tried both git config http.cookiefile and %s", netrc) } type gitCookieFileAuth struct { @@ -130,22 +133,21 @@ func (a *gitCookieFileAuth) loadCookieFileOnce() { a.jar = parseGitCookies(string(data)) } -func (a *gitCookieFileAuth) setAuth(c *Client, r *http.Request) { +func (a *gitCookieFileAuth) setAuth(c *Client, r *http.Request) error { a.once.Do(a.loadCookieFileOnce) if a.err != nil { - log.Print(a.err) - return + return a.err } url, err := url.Parse(c.url) if err != nil { - // Something else will complain about this. - return + return err } for _, cookie := range a.jar.Cookies(url) { r.AddCookie(cookie) } + return nil } func parseGitCookies(data string) *cookiejar.Jar { @@ -178,12 +180,41 @@ func parseGitCookies(data string) *cookiejar.Jar { return jar } +// Scopes to use when creating a TokenSource. +var OAuth2Scopes = []string{ + "https://www.googleapis.com/auth/cloud-platform", + "https://www.googleapis.com/auth/gerritcodereview", + "https://www.googleapis.com/auth/source.full_control", + "https://www.googleapis.com/auth/source.read_write", + "https://www.googleapis.com/auth/source.read_only", +} + +// OAuth2Auth uses the given TokenSource to authenticate requests. +func OAuth2Auth(src oauth2.TokenSource) Auth { + return oauth2Auth{src} +} + +type oauth2Auth struct { + src oauth2.TokenSource +} + +func (a oauth2Auth) setAuth(c *Client, r *http.Request) error { + token, err := a.src.Token() + if err != nil { + return err + } + token.SetAuthHeader(r) + return nil +} + // NoAuth makes requests unauthenticated. var NoAuth = noAuth{} type noAuth struct{} -func (noAuth) setAuth(c *Client, r *http.Request) {} +func (noAuth) setAuth(c *Client, r *http.Request) error { + return nil +} type digestAuth struct { Username, Password, Realm, NONCE, QOP, Opaque, Algorithm string @@ -254,12 +285,13 @@ func getDigestAuthString(auth *digestAuth, url *url.URL, method string, nc int) return buf.String() } -func (a digestAuth) setAuth(c *Client, r *http.Request) { +func (a digestAuth) setAuth(c *Client, r *http.Request) error { resp, err := http.Get(r.URL.String()) if err != nil { - return + return err } setDigestAuth(r, a.Username, a.Password, resp, 1) + return nil } // DigestAuth returns an Auth implementation which sends diff --git a/gerrit/gerrit.go b/gerrit/gerrit.go index d2914c4acc..810f0d7f53 100644 --- a/gerrit/gerrit.go +++ b/gerrit/gerrit.go @@ -160,7 +160,9 @@ func (c *Client) do(ctx context.Context, dst interface{}, method, path string, o if contentType != "" { req.Header.Set("Content-Type", contentType) } - c.auth.setAuth(c, req) + if err := c.auth.setAuth(c, req); err != nil { + return fmt.Errorf("setting Gerrit auth: %v", err) + } res, err := c.httpClient().Do(req) if err != nil { return err diff --git a/internal/relui/buildrelease_test.go b/internal/relui/buildrelease_test.go index 94a38960b5..53981cff4a 100644 --- a/internal/relui/buildrelease_test.go +++ b/internal/relui/buildrelease_test.go @@ -135,13 +135,14 @@ func newReleaseTestDeps(t *testing.T, wantVersion string) *releaseTestDeps { snapshotServer := httptest.NewServer(http.HandlerFunc(serveSnapshot)) t.Cleanup(snapshotServer.Close) buildTasks := &BuildReleaseTasks{ - GerritURL: snapshotServer.URL, - GCSClient: nil, - ScratchURL: "file://" + filepath.ToSlash(scratchDir), - ServingURL: "file://" + filepath.ToSlash(servingDir), - CreateBuildlet: fakeBuildlets.createBuildlet, - DownloadURL: dlServer.URL, - PublishFile: publishFile, + GerritHTTPClient: http.DefaultClient, + GerritURL: snapshotServer.URL, + GCSClient: nil, + ScratchURL: "file://" + filepath.ToSlash(scratchDir), + ServingURL: "file://" + filepath.ToSlash(servingDir), + CreateBuildlet: fakeBuildlets.createBuildlet, + DownloadURL: dlServer.URL, + PublishFile: publishFile, ApproveAction: func(*workflow.TaskContext, interface{}) error { return nil }, diff --git a/internal/relui/workflows.go b/internal/relui/workflows.go index 0995fe342c..f984fa54d6 100644 --- a/internal/relui/workflows.go +++ b/internal/relui/workflows.go @@ -795,6 +795,7 @@ func (tasks *BuildReleaseTasks) addBuildTasks(wd *workflow.Definition, majorVers // BuildReleaseTasks serves as an adapter to the various build tasks in the task package. type BuildReleaseTasks struct { + GerritHTTPClient *http.Client GerritURL string PrivateGerritURL string GCSClient *storage.Client @@ -808,16 +809,16 @@ type BuildReleaseTasks struct { func (b *BuildReleaseTasks) buildSource(ctx *workflow.TaskContext, revision, securityRevision, version string) (artifact, error) { return b.runBuildStep(ctx, nil, "", artifact{}, "src.tar.gz", func(_ *task.BuildletStep, _ io.Reader, w io.Writer) error { if securityRevision != "" { - return task.WriteSourceArchive(ctx, b.PrivateGerritURL, securityRevision, version, w) + return task.WriteSourceArchive(ctx, b.GerritHTTPClient, b.PrivateGerritURL, securityRevision, version, w) } - return task.WriteSourceArchive(ctx, b.GerritURL, revision, version, w) + return task.WriteSourceArchive(ctx, b.GerritHTTPClient, b.GerritURL, revision, version, w) }) } func (b *BuildReleaseTasks) checkSourceMatch(ctx *workflow.TaskContext, head, version string, source artifact) error { _, err := b.runBuildStep(ctx, nil, "", source, "", func(_ *task.BuildletStep, r io.Reader, _ io.Writer) error { branchArchive := &bytes.Buffer{} - if err := task.WriteSourceArchive(ctx, b.GerritURL, head, version, branchArchive); err != nil { + if err := task.WriteSourceArchive(ctx, b.GerritHTTPClient, b.GerritURL, head, version, branchArchive); err != nil { return err } branchHashes, err := tarballHashes(branchArchive) diff --git a/internal/task/buildrelease.go b/internal/task/buildrelease.go index fe4b7041ab..4a55e044ed 100644 --- a/internal/task/buildrelease.go +++ b/internal/task/buildrelease.go @@ -28,10 +28,10 @@ import ( ) // WriteSourceArchive writes a source archive to out, based on revision with version written in as VERSION. -func WriteSourceArchive(ctx *workflow.TaskContext, gerritURL, revision, version string, out io.Writer) error { +func WriteSourceArchive(ctx *workflow.TaskContext, client *http.Client, gerritURL, revision, version string, out io.Writer) error { ctx.Printf("Create source archive.") tarURL := gerritURL + "/+archive/" + revision + ".tar.gz" - resp, err := http.Get(tarURL) + resp, err := client.Get(tarURL) if err != nil { return err }