golang.org/x/build@v0.0.0-20240506185731-218518f32b70/cmd/gomoteserver/gomoteserver.go (about) 1 // Copyright 2023 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 //go:build linux || darwin 6 7 package main 8 9 import ( 10 "context" 11 "flag" 12 "log" 13 "net/http" 14 "strings" 15 16 "cloud.google.com/go/compute/metadata" 17 "cloud.google.com/go/storage" 18 "go.chromium.org/luci/auth" 19 buildbucketpb "go.chromium.org/luci/buildbucket/proto" 20 "go.chromium.org/luci/grpc/prpc" 21 "go.chromium.org/luci/hardcoded/chromeinfra" 22 "go.chromium.org/luci/swarming/client/swarming" 23 "golang.org/x/build/buildenv" 24 "golang.org/x/build/internal/access" 25 "golang.org/x/build/internal/coordinator/pool" 26 "golang.org/x/build/internal/coordinator/remote" 27 "golang.org/x/build/internal/gomote" 28 gomotepb "golang.org/x/build/internal/gomote/protos" 29 "golang.org/x/build/internal/gomoteserver/ui" 30 "golang.org/x/build/internal/https" 31 "golang.org/x/build/internal/rendezvous" 32 "golang.org/x/build/internal/secret" 33 "golang.org/x/build/revdial/v2" 34 "google.golang.org/api/option" 35 "google.golang.org/grpc" 36 ) 37 38 var ( 39 sshAddr = flag.String("ssh_addr", ":2222", "Address the gomote SSH server should listen on") 40 buildEnvName = flag.String("env", "", "The build environment configuration to use. Not required if running in dev mode locally or prod mode on GCE.") 41 mode = flag.String("mode", "", "Valid modes are 'dev', 'prod', or '' for auto-detect. dev means localhost development, not be confused with staging on go-dashboard-dev, which is still the 'prod' mode.") 42 ) 43 44 var Version string // set by linker -X 45 46 const ( 47 gomoteHost = "gomote.golang.org" 48 gomoteSSHHost = "gomotessh.golang.org" 49 ) 50 51 func main() { 52 https.RegisterFlags(flag.CommandLine) 53 if err := secret.InitFlagSupport(context.Background()); err != nil { 54 log.Fatalln(err) 55 } 56 hostKey := secret.Flag("private-host-key", "Gomote SSH Server host private key") 57 pubKey := secret.Flag("public-host-key", "Gomote SSH Server host public key") 58 flag.Parse() 59 60 log.Println("starting gomote server") 61 ctx, cancel := context.WithCancel(context.Background()) 62 defer cancel() 63 64 sp := remote.NewSessionPool(context.Background()) 65 sshCA := mustRetrieveSSHCertificateAuthority() 66 67 var gomoteBucket string 68 var opts []grpc.ServerOption 69 if *buildEnvName == "" && *mode != "dev" && metadata.OnGCE() { 70 projectID, err := metadata.ProjectID() 71 if err != nil { 72 log.Fatalf("metadata.ProjectID() = %v", err) 73 } 74 luciEnv := buildenv.ByProjectID("golang-ci-luci") 75 env := buildenv.ByProjectID(projectID) 76 gomoteBucket = luciEnv.GomoteTransferBucket 77 var coordinatorBackend, serviceID = "coordinator-internal-iap", "" 78 if serviceID = env.IAPServiceID(coordinatorBackend); serviceID == "" { 79 log.Fatalf("unable to retrieve Service ID for backend service=%q", coordinatorBackend) 80 } 81 opts = append(opts, grpc.UnaryInterceptor(access.RequireIAPAuthUnaryInterceptor(access.IAPSkipAudienceValidation))) 82 opts = append(opts, grpc.StreamInterceptor(access.RequireIAPAuthStreamInterceptor(access.IAPSkipAudienceValidation))) 83 } 84 grpcServer := grpc.NewServer(opts...) 85 rdv := rendezvous.New(ctx) 86 gomoteServer, err := gomote.NewSwarming(sp, sshCA, gomoteBucket, mustStorageClient(), rdv, mustSwarmingClient(ctx), mustBuildersClient(ctx)) 87 if err != nil { 88 log.Fatalf("unable to create gomote server: %s", err) 89 } 90 gomotepb.RegisterGomoteServiceServer(grpcServer, gomoteServer) 91 92 mux := http.NewServeMux() 93 mux.HandleFunc("/reverse", rdv.HandleReverse) 94 mux.Handle("/revdial", revdial.ConnHandler()) 95 mux.HandleFunc("/style.css", ui.Redirect(ui.HandleStyleCSS, gomoteSSHHost, gomoteHost)) 96 mux.HandleFunc("/", ui.Redirect(grpcHandlerFunc(grpcServer, ui.HandleStatusFunc(sp, Version)), gomoteSSHHost, gomoteHost)) // Serve a status page. 97 98 sshServ, err := remote.NewSSHServer(*sshAddr, []byte(*hostKey), []byte(*pubKey), sshCA, sp, remote.EnableLUCIOption()) 99 if err != nil { 100 log.Printf("unable to configure SSH server: %s", err) 101 } else { 102 go func() { 103 log.Printf("running SSH server on %s", *sshAddr) 104 err := sshServ.ListenAndServe() 105 log.Printf("SSH server ended with error: %v", err) 106 }() 107 defer func() { 108 err := sshServ.Close() 109 if err != nil { 110 log.Printf("unable to close SSH server: %s", err) 111 } 112 }() 113 } 114 log.Fatalln(https.ListenAndServe(context.Background(), mux)) 115 } 116 117 func mustRetrieveSSHCertificateAuthority() (privateKey []byte) { 118 privateKey, _, err := remote.SSHKeyPair() 119 if err != nil { 120 log.Fatalf("unable to create SSH CA cert: %s", err) 121 } 122 return privateKey 123 } 124 125 func mustStorageClient() *storage.Client { 126 if metadata.OnGCE() { 127 sc, err := pool.StorageClient(context.Background()) 128 if err != nil { 129 log.Fatalf("unable to create authenticated storage client: %v", err) 130 } 131 return sc 132 } 133 sc, err := storage.NewClient(context.Background(), option.WithoutAuthentication()) 134 if err != nil { 135 log.Fatalf("unable to create unauthenticated storage client: %s", err) 136 } 137 return sc 138 } 139 140 // grpcHandlerFunc creates handler which intercepts requests intended for a GRPC server and directs the calls to the server. 141 // All other requests are directed toward the passed in handler. 142 func grpcHandlerFunc(gs *grpc.Server, h http.HandlerFunc) http.HandlerFunc { 143 return func(w http.ResponseWriter, r *http.Request) { 144 if r.ProtoMajor == 2 && strings.HasPrefix(r.Header.Get("Content-Type"), "application/grpc") { 145 gs.ServeHTTP(w, r) 146 return 147 } 148 h(w, r) 149 } 150 } 151 152 func mustSwarmingClient(ctx context.Context) swarming.Client { 153 c, err := swarming.NewClient(ctx, swarming.ClientOptions{ 154 ServiceURL: "https://chromium-swarm.appspot.com", 155 UserAgent: "go-gomoteserver", 156 Auth: auth.Options{Method: auth.GCEMetadataMethod}, 157 }) 158 if err != nil { 159 log.Fatalf("unable to create swarming client: %s", err) 160 } 161 return c 162 } 163 164 func mustBuildersClient(ctx context.Context) buildbucketpb.BuildersClient { 165 httpC, err := auth.NewAuthenticator(ctx, auth.SilentLogin, auth.Options{Method: auth.GCEMetadataMethod}).Client() 166 if err != nil { 167 log.Fatalf("unable to create buildbucket authenticator: %s", err) 168 } 169 prpcC := prpc.Client{C: httpC, Host: chromeinfra.BuildbucketHost} 170 return buildbucketpb.NewBuildersClient(&prpcC) 171 }