Skip to content

Commit b261699

Browse files
committed
all: add GRPC gomote server
This change: - Adds a simple GRPC gomote server. - Updates the documentation for the audiance required for IAP authentication. - Adds a field for the backend service id in the build enviornment package. - Creates middleware for the GRPC server use in the existing HTTP servers. Updates golang/go#47521 Updates golang/go#48742 Change-Id: I2a56e39b96bf1b429f807f79c58aee3f72a45a33 Reviewed-on: https://go-review.googlesource.com/c/build/+/361098 Trust: Carlos Amedee <[email protected]> Run-TryBot: Carlos Amedee <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Alexander Rakoczy <[email protected]>
1 parent 0e19172 commit b261699

File tree

7 files changed

+112
-16
lines changed

7 files changed

+112
-16
lines changed

buildenv/envs.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,11 @@ type Environment struct {
138138

139139
// AWSRegion is the region where AWS resources are deployed.
140140
AWSRegion string
141+
142+
// iapServiceIDs is a map of service-backends to service IDs for the backend
143+
// services used by IAP enabled HTTP paths.
144+
// map[backend-service-name]service_id
145+
iapServiceIDs map[string]string
141146
}
142147

143148
// ComputePrefix returns the URI prefix for Compute Engine resources in a project.
@@ -202,6 +207,15 @@ func (e Environment) Credentials(ctx context.Context) (*google.Credentials, erro
202207
return creds, nil
203208
}
204209

210+
// IAPServiceID returns the service id for the backend service. If a path does not exist for a
211+
// backend, the service id will be an empty string.
212+
func (e Environment) IAPServiceID(backendServiceName string) string {
213+
if v, ok := e.iapServiceIDs[backendServiceName]; ok {
214+
return v
215+
}
216+
return ""
217+
}
218+
205219
// ByProjectID returns an Environment for the specified
206220
// project ID. It is currently limited to the symbolic-datum-552
207221
// and go-dashboard-dev projects.
@@ -254,6 +268,7 @@ var Staging = &Environment{
254268
COSServiceAccount: "[email protected]",
255269
AWSSecurityGroup: "staging-go-builders",
256270
AWSRegion: "us-east-1",
271+
iapServiceIDs: map[string]string{},
257272
}
258273

259274
// Production defines the environment that the coordinator and build
@@ -285,6 +300,10 @@ var Production = &Environment{
285300
COSServiceAccount: "[email protected]",
286301
AWSSecurityGroup: "go-builders",
287302
AWSRegion: "us-east-2",
303+
iapServiceIDs: map[string]string{
304+
"coordinator-internal-iap": "5961904996536591018",
305+
"relui-internal": "5124132661507612124",
306+
},
288307
}
289308

290309
var Development = &Environment{

cmd/coordinator/coordinator.go

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ import (
4040
builddash "golang.org/x/build/cmd/coordinator/internal/dashboard"
4141
"golang.org/x/build/cmd/coordinator/internal/legacydash"
4242
"golang.org/x/build/cmd/coordinator/protos"
43+
"golang.org/x/build/internal/access"
44+
"golang.org/x/build/internal/gomote"
45+
gomoteprotos "golang.org/x/build/internal/gomote/protos"
4346
"google.golang.org/grpc"
4447
grpc4 "grpc.go4.org"
4548

@@ -55,6 +58,7 @@ import (
5558
"golang.org/x/build/internal/buildstats"
5659
"golang.org/x/build/internal/cloud"
5760
"golang.org/x/build/internal/coordinator/pool"
61+
"golang.org/x/build/internal/coordinator/remote"
5862
"golang.org/x/build/internal/https"
5963
"golang.org/x/build/internal/secret"
6064
"golang.org/x/build/maintner/maintnerd/apipb"
@@ -236,9 +240,6 @@ func (r *linkRewriter) Flush() {
236240
r.buf = nil
237241
}
238242

239-
// grpcServer is a shared gRPC server. It is global, as it needs to be used in places that aren't factored otherwise.
240-
var grpcServer = grpc.NewServer()
241-
242243
func main() {
243244
https.RegisterFlags(flag.CommandLine)
244245
flag.Parse()
@@ -330,12 +331,26 @@ func main() {
330331
log.Printf("Failed to load static resources: %v", err)
331332
}
332333

333-
dashV1 := legacydash.Handler(gce.GoDSClient(), maintnerClient, string(masterKey()))
334+
var opts []grpc.ServerOption
335+
if env := buildenv.FromFlags(); env == buildenv.Production {
336+
var coordinatorBackend, serviceID = "coordinator-internal-iap", ""
337+
if serviceID = env.IAPServiceID(coordinatorBackend); serviceID == "" {
338+
log.Fatalf("unable to retrieve Service ID for backend service=%q", coordinatorBackend)
339+
}
340+
opts = append(opts, grpc.UnaryInterceptor(access.RequireIAPAuthUnaryInterceptor(access.IAPAudienceGCE(env.ProjectNumber, serviceID))))
341+
opts = append(opts, grpc.StreamInterceptor(access.RequireIAPAuthStreamInterceptor(access.IAPAudienceGCE(env.ProjectNumber, serviceID))))
342+
}
343+
// grpcServer is a shared gRPC server. It is global, as it needs to be used in places that aren't factored otherwise.
344+
grpcServer := grpc.NewServer(opts...)
345+
346+
dashV1 := legacydash.Handler(gce.GoDSClient(), maintnerClient, string(masterKey()), grpcServer)
334347
dashV2 := &builddash.Handler{Datastore: gce.GoDSClient(), Maintner: maintnerClient}
335348
gs := &gRPCServer{dashboardURL: "https://build.golang.org"}
349+
gomoteServer := gomote.New(remote.NewSessionPool(context.Background()))
336350
protos.RegisterCoordinatorServer(grpcServer, gs)
337-
http.HandleFunc("/", handleStatus) // Serve a status page at farmer.golang.org.
338-
http.Handle("build.golang.org/", dashV1) // Serve a build dashboard at build.golang.org.
351+
gomoteprotos.RegisterGomoteServiceServer(grpcServer, gomoteServer)
352+
http.HandleFunc("/", grpcHandlerFunc(grpcServer, handleStatus)) // Serve a status page at farmer.golang.org.
353+
http.Handle("build.golang.org/", dashV1) // Serve a build dashboard at build.golang.org.
339354
http.Handle("build-staging.golang.org/", dashV1)
340355
http.HandleFunc("/builders", handleBuilders)
341356
http.HandleFunc("/temporarylogs", handleLogs)

cmd/coordinator/internal/legacydash/dash.go

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import (
1818
"embed"
1919
"net/http"
2020
"sort"
21+
"strings"
2122

2223
"cloud.google.com/go/datastore"
2324
"github.com/NYTimes/gziphandler"
2425
"golang.org/x/build/maintner/maintnerd/apipb"
2526
"golang.org/x/build/repos"
27+
"google.golang.org/grpc"
2628
)
2729

2830
var (
@@ -44,12 +46,13 @@ var (
4446
// fakeResults controls whether to make up fake random results. If true, datastore is not used.
4547
const fakeResults = false
4648

47-
// Handler sets a datastore client, maintner client, and builder master key
48-
// at the package scope, and returns an HTTP mux for the legacy dashboard.
49-
func Handler(dc *datastore.Client, mc apipb.MaintnerServiceClient, key string) http.Handler {
49+
// Handler sets a datastore client, maintner client, builder master key and
50+
// GRPC server at the package scope, and returns an HTTP mux for the legacy dashboard.
51+
func Handler(dc *datastore.Client, mc apipb.MaintnerServiceClient, key string, grpcServer *grpc.Server) http.Handler {
5052
datastoreClient = dc
5153
maintnerClient = mc
5254
masterKey = key
55+
grpcServer = grpcServer
5356

5457
mux := http.NewServeMux()
5558

@@ -58,7 +61,7 @@ func Handler(dc *datastore.Client, mc apipb.MaintnerServiceClient, key string) h
5861
mux.Handle("/result", hstsGzip(AuthHandler(resultHandler))) // called by coordinator after build
5962

6063
// public handlers
61-
mux.Handle("/", hstsGzip(http.HandlerFunc(uiHandler)))
64+
mux.Handle("/", GRPCHandler(grpcServer, hstsGzip(http.HandlerFunc(uiHandler)))) // enables GRPC server for build.golang.org
6265
mux.Handle("/log/", hstsGzip(http.HandlerFunc(logHandler)))
6366

6467
// static handler
@@ -71,6 +74,18 @@ func Handler(dc *datastore.Client, mc apipb.MaintnerServiceClient, key string) h
7174
//go:embed static
7275
var static embed.FS
7376

77+
// GRPCHandler creates handler which intercepts requests intended for a GRPC server and directs the calls to the server.
78+
// All other requests are directed toward the passed in handler.
79+
func GRPCHandler(gs *grpc.Server, h http.Handler) http.Handler {
80+
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
81+
if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
82+
gs.ServeHTTP(w, r)
83+
return
84+
}
85+
h.ServeHTTP(w, r)
86+
})
87+
}
88+
7489
// hstsGzip is short for hstsHandler(GzipHandler(h)).
7590
func hstsGzip(h http.Handler) http.Handler {
7691
return hstsHandler(gziphandler.GzipHandler(h))

cmd/coordinator/status.go

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ import (
3939
"golang.org/x/build/internal/secret"
4040
"golang.org/x/build/kubernetes/api"
4141
"golang.org/x/oauth2"
42+
"google.golang.org/grpc"
4243
)
4344

4445
// status
@@ -628,13 +629,19 @@ func healthCheckerHandler(hc *healthChecker) http.Handler {
628629

629630
func uptime() time.Duration { return time.Since(processStartTime).Round(time.Second) }
630631

631-
func handleStatus(w http.ResponseWriter, r *http.Request) {
632-
// Support gRPC handlers. handleStatus is our toplevel ("/") handler, so reroute to the gRPC server for
633-
// matching requests.
634-
if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
635-
grpcServer.ServeHTTP(w, r)
636-
return
632+
// grpcHandlerFunc creates handler which intercepts requests intended for a GRPC server and directs the calls to the server.
633+
// All other requests are directed toward the passed in handler.
634+
func grpcHandlerFunc(gs *grpc.Server, h http.HandlerFunc) http.HandlerFunc {
635+
return func(w http.ResponseWriter, r *http.Request) {
636+
if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") {
637+
gs.ServeHTTP(w, r)
638+
return
639+
}
640+
h(w, r)
637641
}
642+
}
643+
644+
func handleStatus(w http.ResponseWriter, r *http.Request) {
638645
if r.URL.Path != "/" {
639646
http.NotFound(w, r)
640647
return

internal/access/access.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func IAPFromContext(ctx context.Context) (*IAPFields, error) {
5555
// It ensures that the caller has successfully authenticated via IAP. If the caller
5656
// has authenticated, the headers created by IAP will be added to the request scope
5757
// context passed down to the server implementation.
58+
// https://cloud.google.com/iap/docs/signed-headers-howto
5859
func iapAuthFunc(audience string, validatorFn validator) grpcauth.AuthFunc {
5960
return func(ctx context.Context) (context.Context, error) {
6061
md, ok := metadata.FromIncomingContext(ctx)
@@ -117,11 +118,17 @@ func RequireIAPAuthStreamInterceptor(audience string) grpc.StreamServerIntercept
117118
type validator func(ctx context.Context, token, audiance string) (*idtoken.Payload, error)
118119

119120
// IAPAudienceGCE returns the jwt audience for GCE and GKE services.
121+
// The project number is the numerical GCP project number the service is deployed in.
122+
// The service ID is the identifier for the backend service used to route IAP requests.
123+
// https://cloud.google.com/iap/docs/signed-headers-howto
120124
func IAPAudienceGCE(projectNumber int64, serviceID string) string {
121125
return fmt.Sprintf("/projects/%d/global/backendServices/%s", projectNumber, serviceID)
122126
}
123127

124128
// IAPAudienceAppEngine returns the JWT audience for App Engine services.
129+
// The project number is the numerical GCP project number the service is deployed in.
130+
// The project ID is the textual identifier for the GCP project that the App Engine instance is deployed in.
131+
// https://cloud.google.com/iap/docs/signed-headers-howto
125132
func IAPAudienceAppEngine(projectNumber int64, projectID string) string {
126133
return fmt.Sprintf("/projects/%d/apps/%s", projectNumber, projectID)
127134
}

internal/gomote/doc.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
// package gomote contains all of the necessary components to implement
6+
// and use the gomote funcitonality. Gomotes are instances which are dedicated
7+
// to an individual user or service.
8+
package gomote

internal/gomote/gomote.go

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright 2021 The Go Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package gomote
6+
7+
import (
8+
"golang.org/x/build/internal/coordinator/remote"
9+
"golang.org/x/build/internal/gomote/protos"
10+
)
11+
12+
// Server is a gomote server implementation.
13+
type Server struct {
14+
// embed the unimplemented server.
15+
protos.UnimplementedGomoteServiceServer
16+
17+
buildlets *remote.SessionPool
18+
}
19+
20+
// New creates a gomote server.
21+
func New(rsp *remote.SessionPool) *Server {
22+
return &Server{
23+
buildlets: rsp,
24+
}
25+
}

0 commit comments

Comments
 (0)