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