go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth/authctx/context.go (about) 1 // Copyright 2019 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package authctx allows to run subprocesses in an environment with ambient 16 // auth. 17 // 18 // Supports setting up an auth context for LUCI tools, gsutil and gcloud, Git, 19 // Docker and Firebase. 20 // 21 // Git auth depends on presence of Git wrapper and git-credential-luci in PATH. 22 // Docker auth depends on presence of docker-credential-luci in PATH. 23 package authctx 24 25 import ( 26 "context" 27 "encoding/json" 28 "fmt" 29 "io/ioutil" 30 "net" 31 "os" 32 "path/filepath" 33 "runtime" 34 "strings" 35 "sync" 36 37 "go.chromium.org/luci/common/errors" 38 "go.chromium.org/luci/common/logging" 39 "go.chromium.org/luci/common/system/environ" 40 41 "go.chromium.org/luci/lucictx" 42 43 "go.chromium.org/luci/auth" 44 "go.chromium.org/luci/auth/integration/devshell" 45 "go.chromium.org/luci/auth/integration/firebase" 46 "go.chromium.org/luci/auth/integration/gcemeta" 47 "go.chromium.org/luci/auth/integration/gsutil" 48 "go.chromium.org/luci/auth/integration/localauth" 49 ) 50 51 // Context knows how to prepare an environment with ambient authentication for 52 // various tools: LUCI, gsutil, Docker, Git, Firebase. 53 // 54 // 'Launch' launches a bunch of local HTTP servers and writes a bunch of 55 // configuration files that point to these servers. 'Export' then exposes 56 // location of these configuration files to subprocesses, so they can discover 57 // local HTTP servers and use them to mint tokens. 58 type Context struct { 59 // ID is used in logs, filenames and in LUCI_CONTEXT (if we launch a new one). 60 // 61 // Usually a logical account name associated with this context, e.g. "task" or 62 // "system". 63 ID string 64 65 // Options define how to build the root authenticator. 66 // 67 // This authenticator (perhaps indirectly through LUCI_CONTEXT created in 68 // 'Launch') will be used by all other auth helpers to grab access tokens. 69 // 70 // If Options.Method is LUCIContextMethod, indicating there's some existing 71 // LUCI_CONTEXT with "local_auth" section we should use, and service account 72 // impersonation is not requested (Options.ActAsServiceAccount == "") the 73 // existing LUCI_CONTEXT is reused. Otherwise launches a new local_auth server 74 // (that uses given auth options to mint tokens) and puts its location into 75 // the new LUCI_CONTEXT. Either way, subprocesses launched with an environment 76 // modified by 'Export' will see a functional LUCI_CONTEXT. 77 // 78 // When reusing an existing LUCI_CONTEXT, subprocesses inherit all OAuth 79 // scopes permissible there. 80 Options auth.Options 81 82 // ExposeSystemAccount indicates if this authentication context should also 83 // expose non-default "system" logical LUCI account (using the same 84 // credentials as the default account). 85 // 86 // This is an advanced feature used to emulate Swarming environment. 87 ExposeSystemAccount bool 88 89 // EnableGitAuth enables authentication for Git subprocesses. 90 // 91 // Assumes 'git' binary is actually gitwrapper and that 'git-credential-luci' 92 // binary is in PATH. 93 // 94 // Requires "https://www.googleapis.com/auth/gerritcodereview" OAuth scope. 95 EnableGitAuth bool 96 97 // EnableDockerAuth enables authentication for Docker. 98 // 99 // Assumes 'docker-credential-luci' is in PATH. 100 // 101 // Requires Google Storage OAuth scopes. See GCR docs for more info. 102 EnableDockerAuth bool 103 104 // EnableDevShell enables DevShell server and gsutil auth shim. 105 // 106 // They are used to make gsutil and gcloud use LUCI authentication. 107 // 108 // On Windows only gsutil auth shim is enabled, since enabling DevShell there 109 // triggers bugs in gsutil. See https://crbug.com/788058#c14. 110 // 111 // Requires Google Storage OAuth scopes. See GS docs for more info. 112 // 113 // TODO(vadimsh): Delete this method if EnableGCEEmulation works everywhere. 114 EnableDevShell bool 115 116 // EnableGCEEmulation enables emulation of GCE instance environment. 117 // 118 // Overrides EnableDevShell if used. Will likely completely replace 119 // EnableDevShell in the near future. 120 // 121 // It does multiple things by setting environment variables and writing config 122 // files: 123 // * Creates new empty CLOUDSDK_CONFIG directory, to make sure we don't 124 // reuse existing gcloud cache. 125 // * Creates new BOTO_CONFIG, telling gsutil to use new empty state dir. 126 // * Launches a local server that imitates GCE metadata server. 127 // * Tells gcloud, gsutil and various Go and Python libraries to use this 128 // server by setting env vars like GCE_METADATA_HOST (and a bunch more). 129 // 130 // This tricks gcloud, gsutil and various Go and Python libraries that use 131 // Application Default Credentials into believing they run on GCE so that 132 // they request OAuth2 tokens via GCE metadata server (which is implemented by 133 // us). 134 // 135 // This is not a foolproof way: nothing prevents clients from ignoring env 136 // vars and hitting metadata.google.internal directly. But most clients 137 // respect env vars we set. 138 EnableGCEEmulation bool 139 140 // EnableFirebaseAuth enables Firebase auth shim. 141 // 142 // It is used to make Firebase use LUCI authentication. 143 // 144 // Requires "https://www.googleapis.com/auth/firebase" OAuth scope. 145 EnableFirebaseAuth bool 146 147 // KnownGerritHosts is list of Gerrit hosts to force git authentication for. 148 // 149 // By default public hosts are accessed anonymously, and the anonymous access 150 // has very low quota. Context needs to know all such hostnames in advance to 151 // be able to force authenticated access to them. 152 KnownGerritHosts []string 153 154 localAuth *lucictx.LocalAuth // non-nil when running localauth.Server 155 tmpDir string // non empty if we created a new temp dir 156 authenticator *auth.Authenticator // used by in-process helpers 157 anonymous bool // true if not associated with any account 158 email string // an account email or "" for anonymous 159 luciSrv *localauth.Server // non-nil if we launched a LUCI_CONTEXT subcontext 160 161 gitHome string // custom HOME for git or "" if not using git auth 162 163 dockerConfig string // location for Docker configuration files 164 dockerTmpDir string // location for Docker temporary files 165 166 gsutilSrv *gsutil.Server // gsutil auth shim server 167 168 // Note: these fields are used in both EnableGCEEmulation and EnableDevShell 169 // modes. 170 gsutilState string // path to a context-managed state directory 171 gsutilBoto string // path to a generated .boto file 172 173 devShellSrv *devshell.Server // DevShell server instance 174 devShellAddr *net.TCPAddr // address local DevShell instance is listening on 175 176 gcemetaSrv *gcemeta.Server // fake GCE metadata server 177 gcemetaAddr string // "host:port" address of the fake metadata server 178 gcloudConfDir string // directory with gcloud configs 179 180 firebaseSrv *firebase.Server // firebase auth shim server 181 firebaseTokenURL string // URL to get firebase auth token from 182 } 183 184 // Launch launches this auth context. It must be called before any other method. 185 // 186 // It launches various local server and prepares various configs, by putting 187 // them into tempDir which may be "" to use some new ioutil.TempDir. 188 // 189 // The given context.Context is used for logging and to pick up the initial 190 // ambient authentication (per auth.NewAuthenticator contract, see its docs). 191 // 192 // To run a subprocess within this new auth context use 'Export' to modify 193 // an environ for a new process. 194 func (ac *Context) Launch(ctx context.Context, tempDir string) (err error) { 195 // EnableGCEEmulation provides a superset of EnableDevShell features. No need 196 // to have both enabled at the same time (they also conflict with each other). 197 if ac.EnableGCEEmulation { 198 ac.EnableDevShell = false 199 } 200 201 defer func() { 202 if err != nil { 203 ac.Close(ctx) 204 } 205 }() 206 207 if tempDir == "" { 208 ac.tmpDir, err = ioutil.TempDir("", "luci") 209 if err != nil { 210 return errors.Annotate(err, "failed to create a temp directory").Err() 211 } 212 tempDir = ac.tmpDir 213 } 214 215 // Make a generator that will be used to generate tokens for subprocesses 216 // that request them via LUCI_CONTEXT protocol or via GCE metadata emulation. 217 tokens := auth.NewTokenGenerator(ctx, ac.Options) 218 opts := tokens.Options() // normalized, with opts.Method populated 219 220 // Construct the OAuth2 authenticator to be used directly by the helpers 221 // hosted in the current process (devshell, gsutil, firebase) using scopes 222 // passed via ac.Options. Out-of-process helpers (git, docker) will use 223 // `tokens` via the LUCI_CONTEXT protocol or GCE metadata server emulation. 224 ac.authenticator, err = tokens.Authenticator(opts.Scopes, "") 225 if err != nil { 226 return errors.Annotate(err, "failed to construct authenticator for %q account", ac.ID).Err() 227 } 228 229 // Figure out what email is associated with this account (if any). 230 ac.email, err = ac.authenticator.GetEmail() 231 switch { 232 case err == auth.ErrLoginRequired: 233 // This context is not associated with any account. This happens when 234 // running Swarming tasks without service account specified or running 235 // locally without doing 'luci-auth login' first. 236 ac.anonymous = true 237 case err != nil: 238 return errors.Annotate(err, "failed to get email of %q account", ac.ID).Err() 239 } 240 241 // Check whether we are allowed to inherit the existing LUCI_CONTEXT. We do it 242 // if 'opts' indicate to use LUCI_CONTEXT and do NOT use impersonation. When 243 // impersonating, we must launch a new auth server to actually perform it 244 // there. 245 // 246 // If we can't reuse the existing LUCI_CONTEXT, launch a new one (deriving 247 // a new context.Context with it). 248 // 249 // If there's no auth credentials at all, do not launch any LUCI_CONTEXT (it 250 // is impossible without credentials). Subprocesses will discover lack of 251 // ambient credentials on their own and fail (or proceed) appropriately. 252 canInherit := opts.Method == auth.LUCIContextMethod && opts.ActAsServiceAccount == "" && !ac.ExposeSystemAccount 253 if !canInherit && !ac.anonymous { 254 if ac.luciSrv, ac.localAuth, err = launchSrv(ctx, tokens, ac.ID, ac.ExposeSystemAccount); err != nil { 255 return errors.Annotate(err, "failed to launch local auth server for %q account", ac.ID).Err() 256 } 257 } 258 259 // Now setup various credential helpers (they all mutate 'ac' and return 260 // annotated errors). 261 if ac.EnableGitAuth { 262 if err := ac.setupGitAuth(tempDir); err != nil { 263 return err 264 } 265 } 266 if ac.EnableDockerAuth { 267 if err := ac.setupDockerAuth(tempDir); err != nil { 268 return err 269 } 270 } 271 if ac.EnableDevShell && !ac.anonymous { 272 if err := ac.setupDevShellAuth(ctx, tempDir); err != nil { 273 return err 274 } 275 } 276 if ac.EnableGCEEmulation { 277 if err := ac.setupGCEEmulationAuth(ctx, tokens, tempDir); err != nil { 278 return err 279 } 280 } 281 if ac.EnableFirebaseAuth && !ac.anonymous { 282 if err := ac.setupFirebaseAuth(ctx); err != nil { 283 return err 284 } 285 } 286 287 return nil 288 } 289 290 // Close stops this context, cleaning up after it. 291 // 292 // The given context.Context is used for deadlines and for logging. 293 // 294 // The auth context is not usable after this call. Logs errors inside (there's 295 // nothing caller can do about them anyway). 296 func (ac *Context) Close(ctx context.Context) { 297 // Stop all the servers in parallel. 298 wg := sync.WaitGroup{} 299 stop := func(what string, srv interface{ Stop(context.Context) error }) { 300 wg.Add(1) 301 go func() { 302 defer wg.Done() 303 if err := srv.Stop(ctx); err != nil { 304 logging.Errorf(ctx, "Failed to stop %s server for %q account: %s", what, ac.ID, err) 305 } 306 }() 307 } 308 // Note: can't move != nil check into stop(...) because 'srv' becomes 309 // a "typed nil interface", which is not nil itself. 310 if ac.luciSrv != nil { 311 stop("local auth", ac.luciSrv) 312 } 313 if ac.gsutilSrv != nil { 314 stop("gsutil shim", ac.gsutilSrv) 315 } 316 if ac.devShellSrv != nil { 317 stop("devshell", ac.devShellSrv) 318 } 319 if ac.gcemetaSrv != nil { 320 stop("fake GCE metadata server", ac.gcemetaSrv) 321 } 322 if ac.firebaseSrv != nil { 323 stop("firebase shim", ac.firebaseSrv) 324 } 325 wg.Wait() 326 327 // Cleanup the rest of the garbage. 328 cleanup := func(what, where string) { 329 if where != "" { 330 if err := os.RemoveAll(where); err != nil { 331 logging.Errorf(ctx, "Failed to clean up %s for %q account at [%s]: %s", what, ac.ID, where, err) 332 } 333 } 334 } 335 cleanup("git HOME", ac.gitHome) 336 cleanup("gsutil state", ac.gsutilState) 337 cleanup("gcloud config dir", ac.gcloudConfDir) 338 cleanup("docker configs", ac.dockerConfig) 339 cleanup("docker temp dir", ac.dockerTmpDir) 340 cleanup("created temp dir", ac.tmpDir) 341 342 // And finally reset the state as if nothing happened. 343 ac.localAuth = nil 344 ac.tmpDir = "" 345 ac.authenticator = nil 346 ac.anonymous = false 347 ac.email = "" 348 ac.luciSrv = nil 349 ac.gitHome = "" 350 ac.dockerConfig = "" 351 ac.dockerTmpDir = "" 352 ac.gsutilSrv = nil 353 ac.gsutilState = "" 354 ac.gsutilBoto = "" 355 ac.devShellSrv = nil 356 ac.devShellAddr = nil 357 ac.gcemetaSrv = nil 358 ac.gcemetaAddr = "" 359 ac.gcloudConfDir = "" 360 ac.firebaseSrv = nil 361 ac.firebaseTokenURL = "" 362 } 363 364 // Authenticator returns an authenticator used by this context. 365 // 366 // It is the one constructed from Options. It is safe to use it directly. 367 func (ac *Context) Authenticator() *auth.Authenticator { 368 return ac.authenticator 369 } 370 371 // Export exports details of this context into the environment, so it can 372 // be inherited by subprocesses that support it. 373 // 374 // It does two inter-dependent things: 375 // 1. Updates LUCI_CONTEXT in 'ctx' so that LUCI tools can use the local 376 // token server. 377 // 2. Mutates 'env' so that various third party tools can also use local 378 // tokens. 379 // 380 // To successfully launch a subprocess, LUCI_CONTEXT in returned context.Context 381 // *must* be exported into 'env' (e.g. via lucictx.Export(...) followed by 382 // SetInEnviron). 383 func (ac *Context) Export(ctx context.Context, env environ.Env) context.Context { 384 // Mutate LUCI_CONTEXT to use localauth.Server{...} launched by us (if any). 385 ctx = ac.SetLocalAuth(ctx) 386 387 if ac.EnableGitAuth { 388 env.Set("GIT_TERMINAL_PROMPT", "0") // no interactive prompts 389 env.Set("INFRA_GIT_WRAPPER_HOME", ac.gitHome) // tell gitwrapper about the new HOME 390 } 391 392 if ac.EnableDockerAuth { 393 env.Set("DOCKER_CONFIG", ac.dockerConfig) 394 env.Set("DOCKER_TMPDIR", ac.dockerTmpDir) 395 } 396 397 if ac.EnableDevShell && !ac.anonymous { 398 if ac.devShellAddr != nil { 399 env.Set(devshell.EnvKey, fmt.Sprintf("%d", ac.devShellAddr.Port)) 400 } else { 401 // See https://crbug.com/788058#c14. 402 logging.Warningf(ctx, "Disabling devshell auth for account %q", ac.ID) 403 } 404 } 405 406 if ac.EnableGCEEmulation { 407 env.Set("CLOUDSDK_CONFIG", ac.gcloudConfDir) 408 if !ac.anonymous { 409 // Used by google.auth.compute_engine Python library to grab tokens. 410 env.Set("GCE_METADATA_ROOT", ac.gcemetaAddr) 411 // Used by google.auth.compute_engine Python library to "ping" metadata srv. 412 env.Set("GCE_METADATA_IP", ac.gcemetaAddr) 413 // Used by cloud.google.com/go/compute/metadata Go library. 414 env.Set("GCE_METADATA_HOST", ac.gcemetaAddr) 415 } 416 } 417 418 // Prepare .boto configs if faking Cloud in some way. Do it even if running 419 // anonymously, since in this case we want to switch gsutil to run in 420 // anonymous mode as well (by forbidding it to use default ~/.boto that may 421 // have some credential in it). 422 if ac.EnableDevShell || ac.EnableGCEEmulation { 423 // Note: gsutilBoto may be empty here if running anonymously in DevShell 424 // mode. This is fine, it tells gsutil not to use default ~/.boto. 425 env.Set("BOTO_CONFIG", ac.gsutilBoto) 426 env.Remove("BOTO_PATH") 427 } 428 429 if ac.EnableFirebaseAuth && !ac.anonymous { 430 // This env var is supposed to contain a refresh token. Its presence 431 // switches Firebase into "CI mode" where it doesn't try to grab credentials 432 // from disk or via gcloud. The actual value doesn't matter, since we 433 // replace the endpoint that consumes this token below. 434 env.Set("FIREBASE_TOKEN", "ignored-non-empty-value") 435 // Instruct Firebase to use the local server for "refreshing" the token. 436 // Usually this is "https://www.googleapis.com" and it takes a refresh token 437 // and returns an access token. We replace it with a local version that 438 // just returns task account access tokens. 439 env.Set("FIREBASE_TOKEN_URL", ac.firebaseTokenURL) 440 } 441 442 return ctx 443 } 444 445 // SetLocalAuth updates `local_auth` section of LUCI_CONTEXT. 446 // 447 // Note that this would allow LUCI libraries to use this auth context, but 448 // other software (gsutil, gcloud, firebase etc) will not see it. They need 449 // various environment variables to be exported first. Use Export for that. 450 func (ac *Context) SetLocalAuth(ctx context.Context) context.Context { 451 if ac.localAuth != nil { 452 return lucictx.SetLocalAuth(ctx, ac.localAuth) 453 } 454 return ctx 455 } 456 457 // Report logs the service account email used by this auth context. 458 func (ac *Context) Report(ctx context.Context) { 459 account := ac.email 460 if ac.anonymous { 461 account = "anonymous" 462 } 463 logging.Infof(ctx, 464 "%q account is %s (git_auth: %v, devshell: %v, emulate_gce:%v, docker:%v, firebase: %v)", 465 ac.ID, account, ac.EnableGitAuth, ac.EnableDevShell, ac.EnableGCEEmulation, 466 ac.EnableDockerAuth, ac.EnableFirebaseAuth) 467 } 468 469 //////////////////////////////////////////////////////////////////////////////// 470 471 // launchSrv launches new localauth.Server that serves LUCI_CONTEXT protocol. 472 // 473 // Returns the server itself (so it can be stopped) and also LocalAuth section 474 // that can be put into LUCI_CONTEXT to make subprocesses use the server. 475 func launchSrv(ctx context.Context, tokens *auth.TokenGenerator, accID string, exposeSystemAccount bool) (*localauth.Server, *lucictx.LocalAuth, error) { 476 generators := make(map[string]localauth.TokenGenerator, 2) 477 generators[accID] = tokens 478 if exposeSystemAccount { 479 generators["system"] = tokens 480 } 481 srv := &localauth.Server{ 482 TokenGenerators: generators, 483 DefaultAccountID: accID, 484 } 485 la, err := srv.Start(ctx) 486 return srv, la, err 487 } 488 489 func (ac *Context) setupGitAuth(tempDir string) error { 490 ac.gitHome = filepath.Join(tempDir, "git-home-"+ac.ID) 491 if err := os.Mkdir(ac.gitHome, 0700); err != nil { 492 return errors.Annotate(err, "failed to create git HOME for %q account at %s", ac.ID, ac.gitHome).Err() 493 } 494 if err := ac.writeGitConfig(); err != nil { 495 return errors.Annotate(err, "failed to setup .gitconfig for %q account", ac.ID).Err() 496 } 497 return nil 498 } 499 500 func (ac *Context) writeGitConfig() error { 501 var cfg gitConfig 502 if !ac.anonymous { 503 cfg = gitConfig{ 504 IsWindows: runtime.GOOS == "windows", 505 UserEmail: ac.email, 506 UserName: strings.Split(ac.email, "@")[0], 507 UseCredentialHelper: true, 508 KnownGerritHosts: ac.KnownGerritHosts, 509 } 510 } else { 511 cfg = gitConfig{ 512 IsWindows: runtime.GOOS == "windows", 513 UserEmail: "anonymous@example.com", // otherwise git doesn't work 514 UserName: "anonymous", 515 UseCredentialHelper: false, // fetch will be anonymous, push will fail 516 KnownGerritHosts: nil, // don't force non-anonymous fetch for public hosts 517 } 518 } 519 return cfg.Write(filepath.Join(ac.gitHome, ".gitconfig")) 520 } 521 522 func (ac *Context) setupDockerAuth(tempDir string) error { 523 ac.dockerConfig = filepath.Join(tempDir, "docker-cfg-"+ac.ID) 524 if err := os.Mkdir(ac.dockerConfig, 0700); err != nil { 525 return errors.Annotate(err, "failed to create Docker configuration directory for %q account at %s", ac.ID, ac.dockerConfig).Err() 526 } 527 if err := ac.writeDockerConfig(); err != nil { 528 return errors.Annotate(err, "failed to create config.json for %q account", ac.ID).Err() 529 } 530 ac.dockerTmpDir = filepath.Join(tempDir, "docker-tmp-"+ac.ID) 531 if err := os.Mkdir(ac.dockerTmpDir, 0700); err != nil { 532 return errors.Annotate(err, "failed to create Docker temporary directory for %q account at %s", ac.ID, ac.dockerTmpDir).Err() 533 } 534 return nil 535 } 536 537 func (ac *Context) writeDockerConfig() error { 538 f, err := os.Create(filepath.Join(ac.dockerConfig, "config.json")) 539 if err != nil { 540 return err 541 } 542 defer f.Close() 543 config := map[string]map[string]string{ 544 "credHelpers": { 545 "us.gcr.io": "luci", 546 "staging-k8s.gcr.io": "luci", 547 "asia.gcr.io": "luci", 548 "gcr.io": "luci", 549 "marketplace.gcr.io": "luci", 550 "eu.gcr.io": "luci", 551 "us-central1-docker.pkg.dev": "luci", 552 }, 553 } 554 if err := json.NewEncoder(f).Encode(&config); err != nil { 555 return errors.Annotate(err, "cannot encode configuration").Err() 556 } 557 return f.Close() 558 } 559 560 func (ac *Context) setupDevShellAuth(ctx context.Context, tempDir string) error { 561 source, err := ac.authenticator.TokenSource() 562 if err != nil { 563 return errors.Annotate(err, "failed to get token source for %q account", ac.ID).Err() 564 } 565 566 // The directory for .boto and gsutil credentials cache (including access 567 // tokens). 568 ac.gsutilState = filepath.Join(tempDir, "gsutil-"+ac.ID) 569 if err := os.Mkdir(ac.gsutilState, 0700); err != nil { 570 return errors.Annotate(err, "failed to create gsutil state dir for %q account at %s", ac.ID, ac.gsutilState).Err() 571 } 572 573 // Launch gsutil auth shim server. It will put a specially constructed .boto 574 // into gsutilState dir (and return path to it). 575 ac.gsutilSrv = &gsutil.Server{ 576 Source: source, 577 StateDir: ac.gsutilState, 578 } 579 if ac.gsutilBoto, err = ac.gsutilSrv.Start(ctx); err != nil { 580 return errors.Annotate(err, "failed to start gsutil auth shim server for %q account", ac.ID).Err() 581 } 582 583 // Presence of DevShell env var breaks gsutil on Windows. Luckily, we rarely 584 // need to use gcloud in Windows, and gsutil (which we do use on Windows 585 // extensively) is covered by gsutil auth shim server setup above. 586 if runtime.GOOS != "windows" { 587 ac.devShellSrv = &devshell.Server{ 588 Source: source, 589 Email: ac.email, 590 } 591 if ac.devShellAddr, err = ac.devShellSrv.Start(ctx); err != nil { 592 return errors.Annotate(err, "failed to start the DevShell server").Err() 593 } 594 } 595 596 return nil 597 } 598 599 func (ac *Context) setupGCEEmulationAuth(ctx context.Context, tokens *auth.TokenGenerator, tempDir string) error { 600 // Launch the fake GCE metadata server. 601 botoGCEAccount := "" 602 if !ac.anonymous { 603 ac.gcemetaSrv = &gcemeta.Server{ 604 Generator: tokens, 605 Email: ac.email, 606 Scopes: tokens.Options().Scopes, 607 MinTokenLifetime: tokens.Options().MinTokenLifetime, 608 } 609 var err error 610 if ac.gcemetaAddr, err = ac.gcemetaSrv.Start(ctx); err != nil { 611 return errors.Annotate(err, "failed to start fake GCE metadata server for %q account", ac.ID).Err() 612 } 613 botoGCEAccount = "default" // switch .boto to use GCE auth 614 } 615 616 // Prepare clean gcloud config, otherwise gcloud will reuse cached "is on GCE" 617 // value from ~/.config/gcloud/gce and will not bother contacting the fake GCE 618 // metadata server on non-GCE machines. Additionally in anonymous mode we 619 // want to avoid using any cached credentials (also stored in the default 620 // ~/.config/gcloud/...). 621 ac.gcloudConfDir = filepath.Join(tempDir, "gcloud-"+ac.ID) 622 if err := os.Mkdir(ac.gcloudConfDir, 0700); err != nil { 623 return errors.Annotate(err, "failed to create gcloud config dir for %q account at %s", ac.ID, ac.gcloudConfDir).Err() 624 } 625 626 // The directory for .boto and gsutil credentials cache. We need to replace it 627 // to tell gsutil NOT to use whatever tokens it had cached in the default 628 // ~/.gsutil/... state dir. 629 var err error 630 ac.gsutilState = filepath.Join(tempDir, "gsutil-"+ac.ID) 631 ac.gsutilBoto, err = gsutil.PrepareStateDir(&gsutil.Boto{ 632 StateDir: ac.gsutilState, 633 GCEServiceAccount: botoGCEAccount, // may be "" in anonymous mode 634 }) 635 return errors.Annotate(err, "failed to setup .boto for %q account", ac.ID).Err() 636 } 637 638 func (ac *Context) setupFirebaseAuth(ctx context.Context) error { 639 source, err := ac.authenticator.TokenSource() 640 if err != nil { 641 return errors.Annotate(err, "failed to get token source for %q account", ac.ID).Err() 642 } 643 // Launch firebase auth shim server. It will provide an URL from which we'll 644 // fetch an auth token. 645 ac.firebaseSrv = &firebase.Server{ 646 Source: source, 647 } 648 if ac.firebaseTokenURL, err = ac.firebaseSrv.Start(ctx); err != nil { 649 return errors.Annotate(err, "failed to start firebase auth shim server for %q account", ac.ID).Err() 650 } 651 return nil 652 }