Skip to content

Commit

Permalink
cmd/relui,gerrit: use application default creds for Gerrit auth
Browse files Browse the repository at this point in the history
relui needs to use its service account to access the private security
repository, and while I'm at it it might as well use it for the public
repo too. Add support to the gerrit package and use it in relui.

I adapted this code from the Gerrit team's auth daemon:
https://gerrit.googlesource.com/gcompute-tools/+/refs/heads/master/git-cookie-authdaemon
There may be better ways to do it but this works on my machine.

For golang/go#53799.

Change-Id: Iec302f4e4e336c21258019b0c20898280e249380
Reviewed-on: https://go-review.googlesource.com/c/build/+/417215
TryBot-Result: Gopher Robot <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Reviewed-by: Dmitri Shuralyov <[email protected]>
Run-TryBot: Heschi Kreinick <[email protected]>
Auto-Submit: Heschi Kreinick <[email protected]>
  • Loading branch information
heschi authored and gopherbot committed Jul 13, 2022
1 parent f2f1cba commit 644dfce
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 36 deletions.
5 changes: 3 additions & 2 deletions cmd/release/release.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import (
"log"
"math"
"math/rand"
"net/http"
"os"
"path/filepath"
"regexp"
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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()
Expand Down
1 change: 0 additions & 1 deletion cmd/relui/deployment-prod.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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"
- "[email protected]"
- "[email protected]"
Expand Down
9 changes: 7 additions & 2 deletions cmd/relui/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand All @@ -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.")
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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,
Expand Down
68 changes: 50 additions & 18 deletions gerrit/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ import (
"encoding/hex"
"fmt"
"io/ioutil"
"log"
"net/http"
"net/http/cookiejar"
"net/url"
Expand All @@ -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.
Expand All @@ -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
Expand Down Expand Up @@ -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")
Expand All @@ -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
Expand All @@ -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 {
Expand All @@ -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 {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
4 changes: 3 additions & 1 deletion gerrit/gerrit.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 8 additions & 7 deletions internal/relui/buildrelease_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
},
Expand Down
7 changes: 4 additions & 3 deletions internal/relui/workflows.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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)
Expand Down
4 changes: 2 additions & 2 deletions internal/task/buildrelease.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand Down

0 comments on commit 644dfce

Please sign in to comment.