go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/auth/auth.go (about) 1 // Copyright 2015 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 auth implements a wrapper around golang.org/x/oauth2. 16 // 17 // Its main improvement is the on-disk cache for authentication tokens, which is 18 // especially important for 3-legged interactive OAuth flows: its usage 19 // eliminates annoying login prompts each time a program is used (because the 20 // refresh token can now be reused). The cache also allows to reduce unnecessary 21 // token refresh calls when sharing a service account between processes. 22 // 23 // The package also implements some best practices regarding interactive login 24 // flows in CLI programs. It makes it easy to implement a login process as 25 // a separate interactive step that happens before the main program loop. 26 // 27 // The antipattern it tries to prevent is "launch an interactive login flow 28 // whenever program hits 'Not Authorized' response from the server". This 29 // usually results in a very confusing behavior, when login prompts pop up 30 // unexpectedly at random time, random places and from multiple goroutines at 31 // once, unexpectedly consuming unintended stdin input. 32 package auth 33 34 import ( 35 "context" 36 "net/http" 37 "path/filepath" 38 "slices" 39 "strings" 40 "sync" 41 "time" 42 43 "cloud.google.com/go/compute/metadata" 44 45 "golang.org/x/oauth2" 46 "google.golang.org/grpc/credentials" 47 48 "go.chromium.org/luci/auth/internal" 49 "go.chromium.org/luci/common/clock" 50 "go.chromium.org/luci/common/data/stringset" 51 "go.chromium.org/luci/common/errors" 52 "go.chromium.org/luci/common/logging" 53 "go.chromium.org/luci/common/retry" 54 "go.chromium.org/luci/common/retry/transient" 55 "go.chromium.org/luci/lucictx" 56 ) 57 58 var ( 59 // ErrLoginRequired is returned by Transport or GetAccessToken in case long 60 // term credentials are not cached and the user must go through an interactive 61 // login flow. 62 ErrLoginRequired = errors.New("interactive login is required") 63 64 // ErrInsufficientAccess is returned by Login or Transport if an access token 65 // can't be minted for given OAuth scopes. For example if a GCE instance 66 // wasn't granted access to requested scopes when it was created. 67 ErrInsufficientAccess = internal.ErrInsufficientAccess 68 69 // ErrNoEmail is returned by GetEmail() if the cached credentials are not 70 // associated with some particular email. This may happen, for example, when 71 // using a refresh token that doesn't have 'userinfo.email' scope. 72 ErrNoEmail = errors.New("the token is not associated with an email") 73 74 // ErrBadOptions is returned by Login() or Transport() if Options passed 75 // to authenticator indicate incompatible features. This likely indicates 76 // a programming error. 77 ErrBadOptions = errors.New("bad authenticator options") 78 79 // ErrAudienceRequired is returned when UseIDTokens is set without specifying 80 // the target audience for ID tokens. 81 ErrAudienceRequired = errors.New("using ID tokens requires specifying an audience string") 82 83 // ErrNoIDToken is returned by GetAccessToken when UseIDTokens option is true, 84 // but the authentication method doesn't actually support ID tokens either 85 // inherently by its nature (e.g. not implemented) or due to its configuration 86 // (e.g. no necessary scopes). 87 ErrNoIDToken = errors.New("ID tokens are not supported in this configuration") 88 89 // ErrNoAccessToken is returned by GetAccessToken when UseIDTokens option is 90 // false (i.e. the caller wants access tokens), but the authentication method 91 // doesn't actually support access tokens. 92 ErrNoAccessToken = errors.New("access tokens are not supported in this configuration") 93 ) 94 95 // Some known Google API OAuth scopes. 96 const ( 97 OAuthScopeEmail = "https://www.googleapis.com/auth/userinfo.email" 98 OAuthScopeIAM = "https://www.googleapis.com/auth/iam" 99 ) 100 101 const ( 102 // GCEServiceAccount is special value that can be passed instead of path to 103 // a service account credentials file to indicate that GCE VM credentials 104 // should be used instead of a real credentials file. 105 GCEServiceAccount = ":gce" 106 ) 107 108 // Method defines a method to use to obtain authentication token. 109 type Method string 110 111 // Supported authentication methods. 112 const ( 113 // AutoSelectMethod can be used to allow the library to pick a method most 114 // appropriate for given set of options and the current execution environment. 115 // 116 // For example, passing ServiceAccountJSONPath or ServiceAccountJSON makes 117 // Authenticator to pick ServiceAccountMethod. 118 // 119 // See SelectBestMethod function for details. 120 AutoSelectMethod Method = "" 121 122 // UserCredentialsMethod is used for interactive OAuth 3-legged login flow. 123 // 124 // Using this method requires specifying an OAuth client by passing ClientID 125 // and ClientSecret in Options when calling NewAuthenticator. 126 // 127 // Additionally, SilentLogin and OptionalLogin (i.e. non-interactive) login 128 // modes rely on a presence of a refresh token in the token cache, thus using 129 // these modes with UserCredentialsMethod also requires configured token 130 // cache (see SecretsDir field of Options). 131 UserCredentialsMethod Method = "UserCredentialsMethod" 132 133 // ServiceAccountMethod is used to authenticate as a service account using 134 // a private key. 135 // 136 // Callers of NewAuthenticator must pass either a path to a JSON file with 137 // service account key (as produced by Google Cloud Console) or a body of this 138 // JSON file. See ServiceAccountJSONPath and ServiceAccountJSON fields in 139 // Options. 140 // 141 // Using ServiceAccountJSONPath has an advantage: Authenticator always loads 142 // the private key from the file before refreshing the token, it allows to 143 // replace the key while the process is running. 144 ServiceAccountMethod Method = "ServiceAccountMethod" 145 146 // GCEMetadataMethod is used on Compute Engine to use tokens provided by 147 // Metadata server. See https://cloud.google.com/compute/docs/authentication 148 GCEMetadataMethod Method = "GCEMetadataMethod" 149 150 // LUCIContextMethod is used by LUCI-aware applications to fetch tokens though 151 // a local auth server (discoverable via "local_auth" key in LUCI_CONTEXT). 152 // 153 // This method is similar in spirit to GCEMetadataMethod: it uses some local 154 // HTTP server as a provider of OAuth access tokens, which gives an ambient 155 // authentication context to apps that use it. 156 // 157 // There are some big differences: 158 // 1. LUCIContextMethod supports minting tokens for multiple different set 159 // of scopes, unlike GCE metadata server that always gives a token with 160 // preconfigured scopes (set when the GCE instance was created). 161 // 2. LUCIContextMethod is not GCE-specific. It doesn't use magic link-local 162 // IP address. It can run on any machine. 163 // 3. The access to the local auth server is controlled by file system 164 // permissions of LUCI_CONTEXT file (there's a secret in this file). 165 // 4. There can be many local auth servers running at once (on different 166 // ports). Useful for bringing up sub-contexts, in particular in 167 // combination with ActAsServiceAccount ("sudo" mode) or for tests. 168 // 169 // See auth/integration/localauth package for the implementation of the server 170 // side of the protocol. 171 LUCIContextMethod Method = "LUCIContextMethod" 172 ) 173 174 // LoginMode is used as enum in NewAuthenticator function. 175 type LoginMode string 176 177 const ( 178 // InteractiveLogin indicates to Authenticator that it is okay to run an 179 // interactive login flow (via Login()) in Transport(), Client() or other 180 // factories if there's no cached token. 181 // 182 // This is typically used with UserCredentialsMethod to generate an OAuth 183 // refresh token and put it in the token cache at the start of the program, 184 // when grabbing a transport. 185 // 186 // Has no effect when used with service account credentials. 187 InteractiveLogin LoginMode = "InteractiveLogin" 188 189 // SilentLogin indicates to Authenticator that it must return a transport that 190 // implements authentication, but it is NOT OK to run interactive login flow 191 // to make it. 192 // 193 // Transport() and other factories will fail with ErrLoginRequired error if 194 // there's no cached token or one can't be generated on the fly in 195 // non-interactive mode. This may happen when using UserCredentialsMethod. 196 // 197 // It is always OK to use SilentLogin mode with service accounts credentials 198 // (ServiceAccountMethod mode), since no user interaction is necessary to 199 // generate an access token in this case. 200 SilentLogin LoginMode = "SilentLogin" 201 202 // OptionalLogin indicates to Authenticator that it should return a transport 203 // that implements authentication, but it is OK to return non-authenticating 204 // transport if there are no valid cached credentials. 205 // 206 // An interactive login flow will never be invoked. An unauthenticated client 207 // will be returned if no credentials are present. 208 // 209 // Can be used when making calls to backends that allow anonymous access. This 210 // is especially useful with UserCredentialsMethod: a user may start using 211 // the service right away (in anonymous mode), and later login (using Login() 212 // method or any other way of initializing credentials cache) to get more 213 // permissions. 214 // 215 // When used with ServiceAccountMethod it is identical to SilentLogin, since 216 // it makes no sense to ignore invalid service account credentials when the 217 // caller is explicitly asking the authenticator to use them. 218 // 219 // Has the original meaning when used with GCEMetadataMethod: it instructs to 220 // skip authentication if the token returned by GCE metadata service doesn't 221 // have all requested scopes. 222 OptionalLogin LoginMode = "OptionalLogin" 223 ) 224 225 // Options are used by NewAuthenticator call. 226 type Options struct { 227 // Transport is underlying round tripper to use for requests. 228 // 229 // Default: http.DefaultTransport. 230 Transport http.RoundTripper 231 232 // Method defines how to grab authentication tokens. 233 // 234 // Default: AutoSelectMethod. 235 Method Method 236 237 // UseIDTokens indicates to use ID tokens instead of access tokens. 238 // 239 // All methods that use or return OAuth access tokens would use ID tokens 240 // instead. This is useful, for example, when calling APIs that are hosted on 241 // Cloud Run or served via Cloud Endpoints. 242 // 243 // When setting to true, make sure to specify a correct Audience if the 244 // default one is not appropriate. 245 // 246 // When using UserCredentialsMethod implicitly appends OAuthScopeEmail to the 247 // list of OAuth scopes, since this scope is needed to get ID tokens in this 248 // mode. 249 // 250 // Default: false. 251 UseIDTokens bool 252 253 // Scopes is a list of OAuth scopes to request. 254 // 255 // Ignored when using ID tokens. 256 // 257 // Default: [OAuthScopeEmail]. 258 Scopes []string 259 260 // Audience is the audience to put into ID tokens. 261 // 262 // It will become `aud` claim in the token. Should usually be some "https://" 263 // URL. Services that validate ID tokens check this field. 264 // 265 // Ignored when not using ID tokens or when using UserCredentialsMethod (the 266 // audience always matches OAuth2 ClientID in this case). 267 // 268 // Defaults: the value of ClientID to mimic UserCredentialsMethod. 269 Audience string 270 271 // ActAsServiceAccount is used to act as a specified service account email. 272 // 273 // When this option is set, there are two identities involved: 274 // 1. A service account identity specified by `ActAsServiceAccount`. 275 // 2. An identity conveyed by the authenticator options (via cached refresh 276 // token, or via `ServiceAccountJSON`, or other similar ways), i.e. the 277 // identity asserted by the authenticator in case `ActAsServiceAccount` is 278 // not set. It is referred to below as the Actor identity. 279 // 280 // The resulting authenticator will produce access tokens for service account 281 // `ActAsServiceAccount`, using the Actor identity to generate them via some 282 // "acting" API. 283 // 284 // If `ActViaLUCIRealm` is not set, the "acting" API is Google Cloud IAM. 285 // The Actor credentials will internally be used to generate access tokens 286 // with IAM scope (see `OAuthScopeIAM`). These tokens will then be used to 287 // call `generateAccessToken` Cloud IAM RPC to obtain the final tokens that 288 // belong to the service account `ActAsServiceAccount`. This requires the 289 // Actor to have "iam.serviceAccounts.getAccessToken" Cloud IAM permission, 290 // which is usually granted via "Service Account Token Creator" IAM role. 291 // 292 // If `ActViaLUCIRealm` is set, the "acting" API is the LUCI Token Server. 293 // The Actor credentials will internally be used to generate access tokens 294 // with just email scope (see `OAuthScopeEmail`). These tokens will then be 295 // used to call `MintServiceAccountToken` RPC. This requires the following 296 // LUCI permissions in the realm specified by `ActViaLUCIRealm`: 297 // 1. The Actor needs "luci.serviceAccounts.mintToken" permission. 298 // 2. The target service account needs "luci.serviceAccounts.existInRealm" 299 // permission. 300 // 3. The LUCI project the realm belongs to must be authorized to use the 301 // target service account (currently via project_owned_accounts.cfg global 302 // config file). 303 // 304 // Regardless of what "acting" API is used, `Scopes` parameter specifies what 305 // OAuth scopes to request for the final access token belonging to 306 // `ActAsServiceAccount`. 307 // 308 // Default: none. 309 ActAsServiceAccount string 310 311 // ActViaLUCIRealm is a LUCI Realm to use to authorize access to the service 312 // account when "acting" as it through a LUCI Token Server. 313 // 314 // See `ActAsServiceAccount` for a detailed explanation. 315 // 316 // Should have form "<project>:<realm>" (e.g. "chromium:ci"). It instructs 317 // the Token Server to lookup acting permissions in a realm named "<realm>", 318 // defined in `realms.cfg` project config file in a LUCI project named 319 // "<project>". 320 // 321 // Using this option requires `TokenServerHost` to be set. 322 // 323 // Default: none. 324 ActViaLUCIRealm string 325 326 // TokenServerHost is a hostname of a LUCI Token Server to use when acting. 327 // 328 // Used only when `ActAsServiceAccount` and `ActViaLUCIRealm` are set. 329 // 330 // Default: none. 331 TokenServerHost string 332 333 // ClientID is OAuth client ID to use with UserCredentialsMethod. 334 // 335 // See https://developers.google.com/identity/protocols/OAuth2InstalledApp 336 // (in particular everything related to "Desktop apps"). 337 // 338 // Together with Scopes forms a cache key in the token cache, which in 339 // practical terms means there can be only one concurrently "logged in" user 340 // per [ClientID, Scopes] combination. So if multiple binaries use exact same 341 // ClientID and Scopes, they'll share credentials cache (a login in one app 342 // makes the user logged in in the other app too). 343 // 344 // If you don't want to share login information between tools, use separate 345 // ClientID or SecretsDir values. 346 // 347 // If not set, UserCredentialsMethod auth method will not work. 348 // 349 // Default: none. 350 ClientID string 351 352 // ClientSecret is OAuth client secret to use with UserCredentialsMethod. 353 // 354 // Default: none. 355 ClientSecret string 356 357 // LoginSessionsHost is a hostname of a service that implements LoginSessions 358 // pRPC service to use for performing interactive OAuth login flow instead 359 // of using OOB redirect URI. 360 // 361 // Matters only when using UserCredentialsMethod method. When used, the stdout 362 // must be attached to a real terminal (i.e. not redirected to a file or 363 // pipe). This is a precaution against using this login method on bots or from 364 // scripts which is never correct and can be dangerous. 365 // 366 // Default: none 367 LoginSessionsHost string 368 369 // ServiceAccountJSONPath is a path to a JSON blob with a private key to use. 370 // 371 // Can also be set to GCEServiceAccount (':gce') to indicate that the GCE VM 372 // service account should be used instead. Useful in CLI interfaces. This 373 // works only if Method is set to AutoSelectMethod (which is the default for 374 // most CLI apps). If GCEServiceAccount is used on a machine without GCE 375 // metadata server, authenticator methods return an error. 376 // 377 // Used only with ServiceAccountMethod. 378 ServiceAccountJSONPath string 379 380 // ServiceAccountJSON is a body of JSON key file to use. 381 // 382 // Overrides ServiceAccountJSONPath if given. 383 ServiceAccountJSON []byte 384 385 // GCEAccountName is an account name to query to fetch token for from metadata 386 // server when GCEMetadataMethod is used. 387 // 388 // If given account wasn't granted required set of scopes during instance 389 // creation time, Transport() call fails with ErrInsufficientAccess. 390 // 391 // Default: "default" account. 392 GCEAccountName string 393 394 // GCEAllowAsDefault indicates whether it is OK to pick GCE authentication 395 // method as default if no other methods apply. 396 // 397 // Effective only when running on GCE and Method is set to AutoSelectMethod. 398 // 399 // In theory using GCE metadata server for authentication when it is 400 // available looks attractive. In practice, especially if running in a 401 // heterogeneous fleet with a mix of GCE and non-GCE machines, automatically 402 // enabling GCE-based authentication is very surprising when it happens. 403 // 404 // Default: false (don't "sniff" GCE environment). 405 GCEAllowAsDefault bool 406 407 // SecretsDir can be used to set the path to a directory where tokens 408 // are cached. 409 // 410 // If not set, tokens will be cached only in the process memory. For refresh 411 // tokens it means the user would have to go through the login process each 412 // time process is started. For service account tokens it means there'll be 413 // HTTP round trip to OAuth backend to generate access token each time the 414 // process is started. 415 SecretsDir string 416 417 // DisableMonitoring can be used to disable the monitoring instrumentation. 418 // 419 // The transport produced by this authenticator sends tsmon metrics IFF: 420 // 1. DisableMonitoring is false (default). 421 // 2. The context passed to 'NewAuthenticator' has monitoring initialized. 422 DisableMonitoring bool 423 424 // MonitorAs is used for 'client' field of monitoring metrics. 425 // 426 // The default is 'luci-go'. 427 MonitorAs string 428 429 // MinTokenLifetime defines a minimally acceptable lifetime of access tokens 430 // generated internally by authenticating http.RoundTripper, TokenSource and 431 // PerRPCCredentials. 432 // 433 // Not used when GetAccessToken is called directly (it accepts this parameter 434 // as an argument). 435 // 436 // The default is 2 min. There's rarely a need to change it and using smaller 437 // values may be dangerous (e.g. if the request gets stuck somewhere or the 438 // token is cached incorrectly it may expire before it is checked). 439 MinTokenLifetime time.Duration 440 441 // testingCache is used in unit tests. 442 testingCache internal.TokenCache 443 // testingBaseTokenProvider is used in unit tests. 444 testingBaseTokenProvider internal.TokenProvider 445 // testingIAMTokenProvider is used in unit tests. 446 testingIAMTokenProvider internal.TokenProvider 447 } 448 449 // PopulateDefaults populates empty fields of `opts` with default values. 450 // 451 // It is called automatically by NewAuthenticator. Use it only if you need to 452 // normalize and examine auth.Options before passing them to NewAuthenticator. 453 func (opts *Options) PopulateDefaults() { 454 // Set the default scope, sort and dedup scopes. 455 if len(opts.Scopes) == 0 { 456 opts.Scopes = []string{OAuthScopeEmail} // also implies "openid" 457 } 458 opts.Scopes = normalizeScopes(opts.Scopes) 459 460 // Fill in blanks with default values. 461 if opts.Audience == "" { 462 opts.Audience = opts.ClientID 463 } 464 if opts.GCEAccountName == "" { 465 opts.GCEAccountName = "default" 466 } 467 if opts.Transport == nil { 468 opts.Transport = http.DefaultTransport 469 } 470 if opts.MinTokenLifetime == 0 { 471 opts.MinTokenLifetime = 2 * time.Minute 472 } 473 474 // TODO(vadimsh): Check SecretsDir permissions. It should be 0700. 475 if opts.SecretsDir != "" && !filepath.IsAbs(opts.SecretsDir) { 476 var err error 477 opts.SecretsDir, err = filepath.Abs(opts.SecretsDir) 478 if err != nil { 479 panic(errors.Annotate(err, "failed to get abs path to token cache dir").Err()) 480 } 481 } 482 } 483 484 // SelectBestMethod returns a most appropriate authentication method for the 485 // given set of options and the current execution environment. 486 // 487 // Invoked by Authenticator if AutoSelectMethod is passed as Method in Options. 488 // It picks the first applicable method in this order: 489 // - ServiceAccountMethod (if the service account private key is configured). 490 // - LUCIContextMethod (if running inside LUCI_CONTEXT with an auth server). 491 // - GCEMetadataMethod (if running on GCE and GCEAllowAsDefault is true). 492 // - UserCredentialsMethod (if no other method applies). 493 // 494 // Beware: it may do relatively heavy calls on first usage (to detect GCE 495 // environment). Fast after that. 496 func SelectBestMethod(ctx context.Context, opts Options) Method { 497 // Asked to use JSON private key. 498 if opts.ServiceAccountJSONPath != "" || len(opts.ServiceAccountJSON) != 0 { 499 if opts.ServiceAccountJSONPath == GCEServiceAccount { 500 return GCEMetadataMethod 501 } 502 return ServiceAccountMethod 503 } 504 505 // Have a local auth server and an account we are allowed to pick by default. 506 // If no default account is given, don't automatically pick up this method. 507 if la := lucictx.GetLocalAuth(ctx); la != nil && la.DefaultAccountId != "" { 508 return LUCIContextMethod 509 } 510 511 // Running on GCE and callers are fine with automagically picking up GCE 512 // metadata server. 513 if opts.GCEAllowAsDefault && metadata.OnGCE() { 514 return GCEMetadataMethod 515 } 516 517 return UserCredentialsMethod 518 } 519 520 // Authenticator is a factory for http.RoundTripper objects that know how to use 521 // cached credentials and how to send monitoring metrics (if tsmon package was 522 // imported). 523 // 524 // Authenticator also knows how to run interactive login flow, if required. 525 type Authenticator struct { 526 // Immutable members. 527 loginMode LoginMode 528 opts *Options 529 transport http.RoundTripper 530 ctx context.Context 531 532 // Mutable members. 533 lock sync.RWMutex 534 err error 535 536 // baseToken is a token (and its provider and cache) whose possession is 537 // sufficient to get the final access token used for authentication of user 538 // calls (see 'authToken' below). 539 // 540 // Methods like 'CheckLoginRequired' check that the base token exists in the 541 // cache or can be generated on the fly. 542 // 543 // In actor mode, the base token has scopes necessary for the corresponding 544 // acting API to work (e.g. IAM scope when using Cloud's generateAccessToken). 545 // The base token is also always using whatever auth method was specified by 546 // Options.Method. 547 // 548 // In non-actor mode, baseToken coincides with authToken: both point to the 549 // exact same struct. 550 baseToken *tokenWithProvider 551 552 // authToken is a token (and its provider and cache) that is actually used for 553 // authentication of user calls. 554 // 555 // It is a token returned by 'GetAccessToken'. It is always scoped to 'Scopes' 556 // list, as passed to NewAuthenticator via Options. 557 // 558 // In actor mode, it is derived from the base token by using some "acting" API 559 // (which one depends on Options, see ActAsServiceAccount comment). This 560 // process is non-interactive and thus can always be performed as long as we 561 // have the base token. 562 // 563 // In non-actor mode it is the main token generated by the authenticator. In 564 // this case it coincides with baseToken: both point to the exact same object. 565 authToken *tokenWithProvider 566 } 567 568 // NewAuthenticator returns a new instance of Authenticator given its options. 569 // 570 // The authenticator is essentially a factory for http.RoundTripper that knows 571 // how to use and update cached credentials tokens. It is bound to the given 572 // context: uses its logger, clock and deadline. 573 func NewAuthenticator(ctx context.Context, loginMode LoginMode, opts Options) *Authenticator { 574 opts.PopulateDefaults() 575 576 // See ensureInitialized for the rest of the initialization. 577 auth := &Authenticator{ 578 ctx: ctx, 579 loginMode: loginMode, 580 opts: &opts, 581 } 582 auth.transport = NewModifyingTransport(opts.Transport, auth.authTokenInjector) 583 584 // Include the token refresh time into the monitored request time. 585 if globalInstrumentTransport != nil && !opts.DisableMonitoring { 586 monitorAs := opts.MonitorAs 587 if monitorAs == "" { 588 monitorAs = "luci-go" 589 } 590 instrumented := globalInstrumentTransport(ctx, auth.transport, monitorAs) 591 if instrumented != auth.transport { 592 logging.Debugf(ctx, "Enabling monitoring instrumentation (client == %q)", monitorAs) 593 auth.transport = instrumented 594 } 595 } 596 597 return auth 598 } 599 600 // Transport optionally performs a login and returns http.RoundTripper. 601 // 602 // It is a high level wrapper around CheckLoginRequired() and Login() calls. See 603 // documentation for LoginMode for more details. 604 func (a *Authenticator) Transport() (http.RoundTripper, error) { 605 switch useAuth, err := a.doLoginIfRequired(false); { 606 case err != nil: 607 return nil, err 608 case useAuth: 609 return a.transport, nil // token-injecting transport 610 default: 611 return a.opts.Transport, nil // original non-authenticating transport 612 } 613 } 614 615 // Client optionally performs a login and returns http.Client. 616 // 617 // It uses transport returned by Transport(). See documentation for LoginMode 618 // for more details. 619 func (a *Authenticator) Client() (*http.Client, error) { 620 transport, err := a.Transport() 621 if err != nil { 622 return nil, err 623 } 624 return &http.Client{Transport: transport}, nil 625 } 626 627 // TokenSource optionally performs a login and returns oauth2.TokenSource. 628 // 629 // Can be used for interoperability with libraries that use golang.org/x/oauth2. 630 // 631 // It doesn't support 'OptionalLogin' mode, since oauth2.TokenSource must return 632 // some token. Otherwise its logic is similar to Transport(). In particular it 633 // may return ErrLoginRequired if interactive login is required, but the 634 // authenticator is in the silent mode. See LoginMode enum for more details. 635 func (a *Authenticator) TokenSource() (oauth2.TokenSource, error) { 636 if _, err := a.doLoginIfRequired(true); err != nil { 637 return nil, err 638 } 639 return tokenSource{a}, nil 640 } 641 642 // PerRPCCredentials optionally performs a login and returns PerRPCCredentials. 643 // 644 // It can be used to authenticate outbound gPRC RPC's. 645 // 646 // Has same logic as Transport(), in particular supports OptionalLogin mode. 647 // See Transport() for more details. 648 func (a *Authenticator) PerRPCCredentials() (credentials.PerRPCCredentials, error) { 649 switch useAuth, err := a.doLoginIfRequired(false); { 650 case err != nil: 651 return nil, err 652 case useAuth: 653 return perRPCCreds{a}, nil // token-injecting PerRPCCredentials 654 default: 655 return perRPCCreds{}, nil // noop PerRPCCredentials 656 } 657 } 658 659 // GetAccessToken returns a valid token with the specified minimum lifetime. 660 // 661 // Returns either an access token or an ID token based on UseIDTokens 662 // authenticator option. 663 // 664 // Does not interact with the user. May return ErrLoginRequired. 665 func (a *Authenticator) GetAccessToken(lifetime time.Duration) (*oauth2.Token, error) { 666 tok, err := a.currentToken() 667 if err != nil { 668 return nil, err 669 } 670 671 // If interested in using ID tokens, but there's no ID token cached yet, force 672 // a refresh. Note that auth methods that don't support ID tokens at all 673 // return internal.NoIDToken in place of the ID token (and it is != ""). This 674 // case is handled below. 675 forceRefresh := tok != nil && a.opts.UseIDTokens && tok.IDToken == "" 676 677 // Impose some arbitrary limit, since <= 0 lifetime won't work. 678 if lifetime < time.Second { 679 lifetime = time.Second 680 } 681 682 if tok == nil || forceRefresh || internal.TokenExpiresInRnd(a.ctx, tok, lifetime) { 683 // Give 5 sec extra to make sure callers definitely receive a token that 684 // has at least 'lifetime' seconds of life left. Without this margin, we 685 // can get into an unlucky situation where the token is valid here, but 686 // no longer valid (has fewer than 'lifetime' life left) up the stack, due 687 // to the passage of time. 688 var err error 689 tok, err = a.refreshToken(tok, lifetime+5*time.Second) 690 if err != nil { 691 if err == ErrLoginRequired { 692 return nil, err 693 } 694 return nil, errors.Annotate(err, "failed to refresh auth token").Err() 695 } 696 // Note: no randomization here. It is a sanity check that verifies 697 // refreshToken did its job. 698 if internal.TokenExpiresIn(a.ctx, tok, lifetime) { 699 return nil, errors.Reason("failed to refresh auth token: still stale even after refresh").Err() 700 } 701 } 702 703 if a.opts.UseIDTokens { 704 if tok.IDToken == "" || tok.IDToken == internal.NoIDToken { 705 return nil, ErrNoIDToken 706 } 707 return &oauth2.Token{ 708 AccessToken: tok.IDToken, 709 Expiry: tok.Expiry, 710 TokenType: "Bearer", 711 }, nil 712 } 713 714 // This should not be happening, but in case it does, better to return an 715 // error instead of a phony access token. 716 if tok.Token.AccessToken == internal.NoAccessToken { 717 return nil, ErrNoAccessToken 718 } 719 720 return &tok.Token, nil 721 } 722 723 // GetEmail returns an email associated with the credentials. 724 // 725 // In most cases this is a fast call that hits the cache. In some rare cases it 726 // may do an RPC to the Token Info endpoint to grab an email associated with the 727 // token. 728 // 729 // Returns ErrNoEmail if the email is not available. This may happen, for 730 // example, when using a refresh token that doesn't have 'userinfo.email' scope. 731 // Callers must expect this error to show up and should prepare a fallback. 732 // 733 // Returns an error if the email can't be fetched due to some other transient 734 // or fatal error. In particular, returns ErrLoginRequired if interactive login 735 // is required to get the token in the first place. 736 func (a *Authenticator) GetEmail() (string, error) { 737 // Grab last known token and its associated email. Note that this call also 738 // initializes the guts of the authenticator, including a.authToken. 739 tok, err := a.currentToken() 740 switch { 741 case err != nil: 742 return "", err 743 case tok != nil && tok.Email == internal.NoEmail: 744 return "", ErrNoEmail 745 case tok != nil && tok.Email != internal.UnknownEmail: 746 return tok.Email, nil 747 } 748 749 // There's no token cached yet (and thus email is not known). First try to ask 750 // the provider for email only. Most providers can return it without doing any 751 // RPCs or heavy calls. If this is not supported, resort to a heavier code 752 // paths that actually refreshes the token and grabs its email along the way. 753 a.lock.RLock() 754 email := a.authToken.provider.Email() 755 a.lock.RUnlock() 756 switch { 757 case email == internal.NoEmail: 758 return "", ErrNoEmail 759 case email != "": 760 return email, nil 761 } 762 763 // The provider doesn't know the email. We need a forceful token refresh to 764 // grab it (or discover it is NoEmail). This is relatively rare. It happens 765 // only when using UserAuth TokenProvider and there's no cached token at all 766 // or it is in old format that don't have email field. 767 // 768 // Pass -1 as lifetime to force trigger the refresh right now. 769 tok, err = a.refreshToken(tok, -1) 770 switch { 771 case err == ErrLoginRequired: 772 return "", err 773 case err != nil: 774 return "", errors.Annotate(err, "failed to refresh auth token").Err() 775 case tok.Email == internal.NoEmail: 776 return "", ErrNoEmail 777 case tok.Email == internal.UnknownEmail: // this must not happen, but let's be cautious 778 return "", errors.Reason("internal error when fetching the email, see logs").Err() 779 default: 780 return tok.Email, nil 781 } 782 } 783 784 // CheckLoginRequired decides whether an interactive login is required. 785 // 786 // It examines the token cache and the configured authentication method to 787 // figure out whether we can attempt to grab an access token without involving 788 // the user interaction. 789 // 790 // Note: it does not check that the cached refresh token is still valid (i.e. 791 // not revoked). A revoked token will result in ErrLoginRequired error on a 792 // first attempt to use it. 793 // 794 // Returns: 795 // - nil if we have a valid cached token or can mint one on the fly. 796 // - ErrLoginRequired if we have no cached token and need to bother the user. 797 // - ErrInsufficientAccess if the configured auth method can't mint the token 798 // we require (e.g. when using GCE method and the instance doesn't have all 799 // requested OAuth scopes). 800 // - Generic error on other unexpected errors. 801 func (a *Authenticator) CheckLoginRequired() error { 802 a.lock.Lock() 803 defer a.lock.Unlock() 804 805 if err := a.ensureInitialized(); err != nil { 806 return err 807 } 808 809 // No cached base token and the token provider requires interaction with the 810 // user: need to login. Only non-interactive token providers are allowed to 811 // mint tokens on the fly, see refreshToken. 812 if a.baseToken.token == nil && a.baseToken.provider.RequiresInteraction() { 813 return ErrLoginRequired 814 } 815 816 return nil 817 } 818 819 // Login perform an interaction with the user to get a long term refresh token 820 // and cache it. 821 // 822 // Blocks for user input, can use stdin. It overwrites currently cached 823 // credentials, if any. 824 // 825 // When used with non-interactive token providers (e.g. based on service 826 // accounts), just clears the cached access token, so next the next 827 // authenticated call gets a fresh one. 828 func (a *Authenticator) Login() error { 829 a.lock.Lock() 830 defer a.lock.Unlock() 831 832 err := a.ensureInitialized() 833 if err != nil { 834 return err 835 } 836 837 // Remove any cached tokens to trigger full relogin. 838 if err := a.purgeCredentialsCacheLocked(); err != nil { 839 return err 840 } 841 842 if !a.baseToken.provider.RequiresInteraction() { 843 return nil // can mint the token on the fly, no need for login 844 } 845 846 // Create an initial base token. This may require interaction with a user. Do 847 // not do retries here, since Login is called when the user is looking, let 848 // the user do the retries (since if MintToken() interacts with the user, 849 // retrying it automatically will be extra confusing). 850 a.baseToken.token, err = a.baseToken.provider.MintToken(a.ctx, nil) 851 if err != nil { 852 return err 853 } 854 855 // Store the initial token in the cache. Don't abort if it fails, the token 856 // is still usable from the memory. 857 if err := a.baseToken.putToCache(a.ctx); err != nil { 858 logging.Warningf(a.ctx, "Failed to write token to cache: %s", err) 859 } 860 861 return nil 862 } 863 864 // PurgeCredentialsCache removes cached tokens. 865 // 866 // Does not revoke them! 867 func (a *Authenticator) PurgeCredentialsCache() error { 868 a.lock.Lock() 869 defer a.lock.Unlock() 870 if err := a.ensureInitialized(); err != nil { 871 return err 872 } 873 return a.purgeCredentialsCacheLocked() 874 } 875 876 func (a *Authenticator) purgeCredentialsCacheLocked() error { 877 // No need to purge twice if baseToken == authToken, which is the case in 878 // non-actor mode. 879 var merr errors.MultiError 880 if a.baseToken != a.authToken { 881 merr = errors.NewMultiError( 882 a.baseToken.purgeToken(a.ctx), 883 a.authToken.purgeToken(a.ctx)) 884 } else { 885 merr = errors.NewMultiError(a.baseToken.purgeToken(a.ctx)) 886 } 887 888 switch total, first := merr.Summary(); { 889 case total == 0: 890 return nil 891 case total == 1: 892 return first 893 default: 894 return merr 895 } 896 } 897 898 //////////////////////////////////////////////////////////////////////////////// 899 // credentials.PerRPCCredentials implementation. 900 901 type perRPCCreds struct { 902 a *Authenticator 903 } 904 905 func (creds perRPCCreds) GetRequestMetadata(ctx context.Context, uri ...string) (map[string]string, error) { 906 if len(uri) == 0 { 907 panic("perRPCCreds: no URI given") 908 } 909 if creds.a == nil { 910 return nil, nil 911 } 912 tok, err := creds.a.GetAccessToken(creds.a.opts.MinTokenLifetime) 913 if err != nil { 914 return nil, err 915 } 916 return map[string]string{ 917 "Authorization": tok.Type() + " " + tok.AccessToken, 918 }, nil 919 } 920 921 func (creds perRPCCreds) RequireTransportSecurity() bool { return true } 922 923 //////////////////////////////////////////////////////////////////////////////// 924 // oauth2.TokenSource implementation. 925 926 type tokenSource struct { 927 a *Authenticator 928 } 929 930 // Token is part of oauth2.TokenSource interface. 931 func (s tokenSource) Token() (*oauth2.Token, error) { 932 return s.a.GetAccessToken(s.a.opts.MinTokenLifetime) 933 } 934 935 //////////////////////////////////////////////////////////////////////////////// 936 // Authenticator private methods. 937 938 // actingMode returns possible ways the authenticator can "act" as an account. 939 type actingMode int 940 941 const ( 942 actingModeNone actingMode = 0 943 actingModeIAM actingMode = 1 944 actingModeLUCI actingMode = 2 945 ) 946 947 // actingMode returns an acting mode based on Options. 948 func (a *Authenticator) actingMode() actingMode { 949 switch { 950 case a.opts.ActAsServiceAccount == "": 951 return actingModeNone 952 case a.opts.ActViaLUCIRealm != "": 953 return actingModeLUCI 954 default: 955 return actingModeIAM 956 } 957 } 958 959 // checkInitialized is (true, <err>) if initialization happened (successfully or 960 // not) of (false, nil) if not. 961 func (a *Authenticator) checkInitialized() (bool, error) { 962 if a.err != nil || a.baseToken != nil { 963 return true, a.err 964 } 965 return false, nil 966 } 967 968 // ensureInitialized instantiates TokenProvider and reads token from cache. 969 // 970 // It is supposed to be called under the lock. 971 func (a *Authenticator) ensureInitialized() error { 972 // Already initialized (successfully or not)? 973 if initialized, err := a.checkInitialized(); initialized { 974 return err 975 } 976 977 // ActViaLUCIRealm makes sense only with ActAsServiceAccount. 978 if a.opts.ActViaLUCIRealm != "" && a.opts.ActAsServiceAccount == "" { 979 a.err = ErrBadOptions 980 return a.err 981 } 982 983 // SelectBestMethod may do heavy calls like talking to GCE metadata server, 984 // call it lazily here rather than in NewAuthenticator. 985 if a.opts.Method == AutoSelectMethod { 986 a.opts.Method = SelectBestMethod(a.ctx, *a.opts) 987 } 988 989 // In Actor mode, switch the base token to have scopes required to call 990 // the API used to generate target auth tokens. In non-actor mode, the base 991 // token is also the target auth token, so scope it to whatever scopes were 992 // requested via Options. 993 var scopes []string 994 var useIDTokens bool 995 switch a.actingMode() { 996 case actingModeNone: 997 scopes = a.opts.Scopes 998 useIDTokens = a.opts.UseIDTokens 999 // To get ID tokens when using end-user credentials we need userinfo scope. 1000 if useIDTokens && a.opts.Method == UserCredentialsMethod { 1001 if !slices.Contains(scopes, OAuthScopeEmail) { 1002 scopes = normalizeScopes(append(scopes, OAuthScopeEmail)) 1003 } 1004 } 1005 case actingModeIAM: 1006 scopes = []string{OAuthScopeIAM} 1007 useIDTokens = false // IAM uses OAuth tokens 1008 case actingModeLUCI: 1009 scopes = []string{OAuthScopeEmail} 1010 useIDTokens = false // LUCI currently uses OAuth tokens 1011 default: 1012 panic("impossible") 1013 } 1014 a.baseToken = &tokenWithProvider{} 1015 a.baseToken.provider, a.err = makeBaseTokenProvider(a.ctx, a.opts, scopes, useIDTokens) 1016 if a.err != nil { 1017 return a.err // note: this can be ErrInsufficientAccess 1018 } 1019 1020 // In non-actor mode, the token we must check in 'CheckLoginRequired' is the 1021 // same as returned by 'GetAccessToken'. In actor mode, they are different. 1022 // See comments for 'baseToken' and 'authToken'. 1023 switch a.actingMode() { 1024 case actingModeNone: 1025 a.authToken = a.baseToken 1026 case actingModeIAM: 1027 a.authToken = &tokenWithProvider{} 1028 a.authToken.provider, a.err = makeIAMTokenProvider(a.ctx, a.opts) 1029 case actingModeLUCI: 1030 a.authToken = &tokenWithProvider{} 1031 a.authToken.provider, a.err = makeLUCITokenProvider(a.ctx, a.opts) 1032 default: 1033 panic("impossible") 1034 } 1035 if a.err != nil { 1036 return a.err 1037 } 1038 1039 // Initialize the token cache. Use the disk cache only if SecretsDir is given 1040 // and any of the providers is not "lightweight" (so it makes sense to 1041 // actually hit the disk, rather then call the provider each time new token is 1042 // needed). 1043 if a.opts.testingCache != nil { 1044 a.baseToken.cache = a.opts.testingCache 1045 a.authToken.cache = a.opts.testingCache 1046 } else { 1047 cache := internal.ProcTokenCache 1048 if !a.baseToken.provider.Lightweight() || !a.authToken.provider.Lightweight() { 1049 if a.opts.SecretsDir != "" { 1050 cache = &internal.DiskTokenCache{ 1051 Context: a.ctx, 1052 SecretsDir: a.opts.SecretsDir, 1053 } 1054 } else { 1055 logging.Warningf(a.ctx, "Disabling auth disk token cache. Not configured.") 1056 } 1057 } 1058 // Use the disk cache only for non-lightweight providers to avoid 1059 // unnecessarily leaks of tokens to the disk. 1060 if a.baseToken.provider.Lightweight() { 1061 a.baseToken.cache = internal.ProcTokenCache 1062 } else { 1063 a.baseToken.cache = cache 1064 } 1065 if a.authToken.provider.Lightweight() { 1066 a.authToken.cache = internal.ProcTokenCache 1067 } else { 1068 a.authToken.cache = cache 1069 } 1070 } 1071 1072 // Interactive providers need to know whether there's a cached token (to ask 1073 // to run interactive login if there's none). Non-interactive providers do not 1074 // care about state of the cache that much (they know how to update it 1075 // themselves). So examine the cache here only when using interactive 1076 // provider. Non interactive providers will do it lazily on a first 1077 // refreshToken(...) call. 1078 if a.baseToken.provider.RequiresInteraction() { 1079 // Broken token cache is not a fatal error. So just log it and forget, a new 1080 // token will be minted in Login. 1081 if err := a.baseToken.fetchFromCache(a.ctx); err != nil { 1082 logging.Warningf(a.ctx, "Failed to read auth token from cache: %s", err) 1083 } 1084 } 1085 1086 // Note: a.authToken.provider is either equal to a.baseToken.provider (if not 1087 // using actor mode), or (when using actor mode) it doesn't require 1088 // interaction (because all "acting" providers are non-interactive). So don't 1089 // bother fetching 'authToken' from the cache. It will be fetched lazily on 1090 // the first use. 1091 1092 return nil 1093 } 1094 1095 // doLoginIfRequired optionally performs an interactive login. 1096 // 1097 // This is the main place where LoginMode handling is performed. Used by various 1098 // factories (Transport, PerRPCCredentials, TokenSource, ...). 1099 // 1100 // If requiresAuth is false, we respect OptionalLogin mode. If true - we treat 1101 // OptionalLogin mode as SilentLogin: some authentication mechanisms (like 1102 // oauth2.TokenSource) require valid tokens no matter what. The corresponding 1103 // factories set requiresAuth to true. 1104 // 1105 // Returns: 1106 // 1107 // (true, nil) if successfully initialized the authenticator with some token. 1108 // (false, nil) to disable authentication (for OptionalLogin mode). 1109 // (false, err) on errors. 1110 func (a *Authenticator) doLoginIfRequired(requiresAuth bool) (useAuth bool, err error) { 1111 err = a.CheckLoginRequired() // also initializes guts for effectiveLoginMode() 1112 effectiveMode := a.effectiveLoginMode() 1113 if requiresAuth && effectiveMode == OptionalLogin { 1114 effectiveMode = SilentLogin 1115 } 1116 switch { 1117 case err == nil: 1118 return true, nil // have a valid cached base token 1119 case err == ErrInsufficientAccess && effectiveMode == OptionalLogin: 1120 return false, nil // have the base token, but it doesn't have enough scopes 1121 case err != ErrLoginRequired: 1122 return false, err // some error we can't handle (we handle only ErrLoginRequired) 1123 case effectiveMode == SilentLogin: 1124 return false, ErrLoginRequired // can't run Login in SilentLogin mode 1125 case effectiveMode == OptionalLogin: 1126 return false, nil // we can skip auth in OptionalLogin if we have no token 1127 case effectiveMode != InteractiveLogin: 1128 return false, errors.Reason("invalid mode argument: %s", effectiveMode).Err() 1129 } 1130 if err := a.Login(); err != nil { 1131 return false, err 1132 } 1133 return true, nil 1134 } 1135 1136 // effectiveLoginMode returns a login mode to use, considering what kind of a 1137 // token provider is being used. 1138 // 1139 // See comments for OptionalLogin for more info. The gist of it: we treat 1140 // OptionalLogin as SilentLogin when using a service account private key. 1141 func (a *Authenticator) effectiveLoginMode() (lm LoginMode) { 1142 // a.opts.Method is modified under a lock, need to grab the lock to avoid a 1143 // race. Note that a.loginMode is immutable and can be read outside the 1144 // lock. We skip the locking if we know for sure that the return value will be 1145 // same as a.loginMode (which is the case for a.loginMode != OptionalLogin). 1146 lm = a.loginMode 1147 if lm == OptionalLogin { 1148 a.lock.RLock() 1149 if a.opts.Method == ServiceAccountMethod { 1150 lm = SilentLogin 1151 } 1152 a.lock.RUnlock() 1153 } 1154 return 1155 } 1156 1157 // currentToken returns last known authentication token (or nil). 1158 // 1159 // If the token is not loaded yet, will attempt to load it from the on-disk 1160 // cache. Returns nil if it's not there. 1161 // 1162 // It lock a.lock inside. It MUST NOT be called when a.lock is held. It will 1163 // lazily call 'ensureInitialized' if necessary, returning its error. 1164 func (a *Authenticator) currentToken() (tok *internal.Token, err error) { 1165 a.lock.RLock() 1166 initialized, err := a.checkInitialized() 1167 if initialized && err == nil { 1168 tok = a.authToken.token 1169 } 1170 a.lock.RUnlock() 1171 if err != nil { 1172 return 1173 } 1174 1175 if !initialized || tok == nil { 1176 a.lock.Lock() 1177 defer a.lock.Unlock() 1178 1179 if !initialized { 1180 if err = a.ensureInitialized(); err != nil { 1181 return 1182 } 1183 tok = a.authToken.token 1184 } 1185 1186 if tok == nil { 1187 // Reading the token from cache is best effort. A broken cache is treated 1188 // like a cache miss. 1189 if cacheErr := a.authToken.fetchFromCache(a.ctx); cacheErr != nil { 1190 logging.Warningf(a.ctx, "Failed to read auth token from cache: %s", cacheErr) 1191 } 1192 tok = a.authToken.token 1193 } 1194 } 1195 1196 return 1197 } 1198 1199 // refreshToken compares current auth token to 'prev' and launches token refresh 1200 // procedure if they still match. 1201 // 1202 // Returns a refreshed token (if a refresh procedure happened) or the current 1203 // token, if it's already different from 'prev'. Acts as "Compare-And-Swap" 1204 // where "Swap" is a token refresh procedure. 1205 // 1206 // If the token can't be refreshed (e.g. the base token or the credentials were 1207 // revoked), sets the current auth token to nil and returns an error. 1208 func (a *Authenticator) refreshToken(prev *internal.Token, lifetime time.Duration) (*internal.Token, error) { 1209 return a.authToken.compareAndRefresh(a.ctx, compareAndRefreshOp{ 1210 lock: &a.lock, 1211 prev: prev, 1212 lifetime: lifetime, 1213 refreshCb: func(ctx context.Context, prev *internal.Token) (*internal.Token, error) { 1214 // In Actor mode, need to make sure we have a sufficiently fresh base 1215 // token first, since it's needed to call "acting" API to get a new auth 1216 // token for the target service account. 1 min should be more than enough 1217 // to make an RPC. 1218 var base *internal.Token 1219 if a.actingMode() != actingModeNone { 1220 var err error 1221 if base, err = a.getBaseTokenLocked(ctx, time.Minute); err != nil { 1222 return nil, err 1223 } 1224 } 1225 return a.authToken.renewToken(ctx, prev, base) 1226 }, 1227 }) 1228 } 1229 1230 // getBaseTokenLocked is used to get an actor token when running in actor mode. 1231 // 1232 // It is called with a.lock locked. 1233 func (a *Authenticator) getBaseTokenLocked(ctx context.Context, lifetime time.Duration) (*internal.Token, error) { 1234 // Already have a good token? 1235 if !internal.TokenExpiresInRnd(ctx, a.baseToken.token, lifetime) { 1236 return a.baseToken.token, nil 1237 } 1238 1239 // Need to make one. 1240 return a.baseToken.compareAndRefresh(ctx, compareAndRefreshOp{ 1241 lock: nil, // already holding the lock 1242 prev: a.baseToken.token, 1243 lifetime: lifetime, 1244 refreshCb: func(ctx context.Context, prev *internal.Token) (*internal.Token, error) { 1245 return a.baseToken.renewToken(ctx, prev, nil) 1246 }, 1247 }) 1248 } 1249 1250 //////////////////////////////////////////////////////////////////////////////// 1251 // Transport implementation. 1252 1253 // authTokenInjector injects an authentication token into request headers. 1254 // 1255 // Used as a callback for NewModifyingTransport. 1256 func (a *Authenticator) authTokenInjector(req *http.Request) error { 1257 switch tok, err := a.GetAccessToken(a.opts.MinTokenLifetime); { 1258 case err == ErrLoginRequired && a.effectiveLoginMode() == OptionalLogin: 1259 return nil // skip auth, no need for modifications 1260 case err != nil: 1261 return err 1262 default: 1263 tok.SetAuthHeader(req) 1264 return nil 1265 } 1266 } 1267 1268 //////////////////////////////////////////////////////////////////////////////// 1269 // tokenWithProvider implementation. 1270 1271 // tokenWithProvider wraps a token with provider that can update it and a cache 1272 // that stores it. 1273 type tokenWithProvider struct { 1274 token *internal.Token // in-memory cache of the token 1275 provider internal.TokenProvider // knows how to generate 'token' 1276 cache internal.TokenCache // persistent cache for the token 1277 } 1278 1279 // fetchFromCache updates 't.token' by reading it from the cache. 1280 func (t *tokenWithProvider) fetchFromCache(ctx context.Context) error { 1281 key, err := t.provider.CacheKey(ctx) 1282 if err != nil { 1283 return err 1284 } 1285 tok, err := t.cache.GetToken(key) 1286 if err != nil { 1287 return err 1288 } 1289 t.token = tok 1290 return nil 1291 } 1292 1293 // putToCache puts 't.token' value into the cache. 1294 func (t *tokenWithProvider) putToCache(ctx context.Context) error { 1295 key, err := t.provider.CacheKey(ctx) 1296 if err != nil { 1297 return err 1298 } 1299 return t.cache.PutToken(key, t.token) 1300 } 1301 1302 // purgeToken removes the token from both on-disk cache and memory. 1303 func (t *tokenWithProvider) purgeToken(ctx context.Context) error { 1304 t.token = nil 1305 key, err := t.provider.CacheKey(ctx) 1306 if err != nil { 1307 return err 1308 } 1309 return t.cache.DeleteToken(key) 1310 } 1311 1312 // compareAndRefreshOp is parameters for 'compareAndRefresh' call. 1313 type compareAndRefreshOp struct { 1314 lock sync.Locker // optional lock to grab when comparing and refreshing 1315 prev *internal.Token // previously known token (the one we are refreshing) 1316 lifetime time.Duration // minimum acceptable token lifetime or <0 to force a refresh 1317 refreshCb func(ctx context.Context, existing *internal.Token) (*internal.Token, error) 1318 } 1319 1320 // compareAndRefresh compares currently stored token to 'prev' and calls the 1321 // given callback (under the lock, if not nil) to refresh it if they are still 1322 // equal. 1323 // 1324 // Returns a refreshed token (if a refresh procedure happened) or the current 1325 // token, if it's already different from 'prev'. Acts as "Compare-And-Swap" 1326 // where "Swap" is a token refresh callback. 1327 // 1328 // If the callback returns an error (meaning the token can't be refreshed), sets 1329 // the token to nil and returns the error. 1330 func (t *tokenWithProvider) compareAndRefresh(ctx context.Context, params compareAndRefreshOp) (*internal.Token, error) { 1331 cacheKey, err := t.provider.CacheKey(ctx) 1332 if err != nil { 1333 // An error here is truly fatal. It is something like "can't read service 1334 // account JSON from disk". There's no way to refresh a token without it. 1335 return nil, err 1336 } 1337 1338 // To give a context to "Minting a new token" messages and similar below. 1339 ctx = logging.SetFields(ctx, logging.Fields{ 1340 "key": cacheKey.Key, 1341 "scopes": strings.Join(cacheKey.Scopes, " "), 1342 }) 1343 1344 // Check that the token still need a refresh and do it (under the lock). 1345 tok, cacheIt, err := func() (*internal.Token, bool, error) { 1346 if params.lock != nil { 1347 params.lock.Lock() 1348 defer params.lock.Unlock() 1349 } 1350 1351 // Some other goroutine already updated the token, just return the new one. 1352 if t.token != nil && !internal.EqualTokens(t.token, params.prev) { 1353 return t.token, false, nil 1354 } 1355 1356 // Rescan the cache. Maybe some other process updated the token. This branch 1357 // is also responsible for lazy-loading of tokens from cache for 1358 // non-interactive providers, see ensureInitialized(). 1359 if cached, _ := t.cache.GetToken(cacheKey); cached != nil { 1360 t.token = cached 1361 if !internal.EqualTokens(cached, params.prev) && params.lifetime > 0 && !internal.TokenExpiresIn(ctx, cached, params.lifetime) { 1362 return cached, false, nil 1363 } 1364 } 1365 1366 // No one updated the token yet. It should be us. Mint a new token or 1367 // refresh the existing one. 1368 start := clock.Now(ctx) 1369 newTok, err := params.refreshCb(ctx, t.token) 1370 if err != nil { 1371 t.token = nil 1372 return nil, false, err 1373 } 1374 now := clock.Now(ctx) 1375 logging.Debugf( 1376 ctx, "The token refreshed in %s, expires in %s", 1377 now.Sub(start), newTok.Expiry.Round(0).Sub(now)) 1378 t.token = newTok 1379 return newTok, true, nil 1380 }() 1381 1382 if err == internal.ErrBadRefreshToken || err == internal.ErrBadCredentials { 1383 // Do not keep the broken token in the cache. It is unusable. Do this 1384 // outside the lock to avoid blocking other callers. Note that t.token is 1385 // already nil. 1386 if err := t.cache.DeleteToken(cacheKey); err != nil { 1387 logging.Warningf(ctx, "Failed to remove broken token from the cache: %s", err) 1388 } 1389 // A bad refresh token can be fixed by interactive login, so adjust the 1390 // error accordingly in this case. 1391 if err == internal.ErrBadRefreshToken { 1392 err = ErrLoginRequired 1393 } 1394 } 1395 1396 if err != nil { 1397 return nil, err 1398 } 1399 1400 // Update the cache outside the lock, no need for callers to wait for this. 1401 // Do not die if failed, the token is still usable from the memory. 1402 if cacheIt && tok != nil { 1403 if err := t.cache.PutToken(cacheKey, tok); err != nil { 1404 logging.Warningf(ctx, "Failed to write refreshed token to the cache: %s", err) 1405 } 1406 } 1407 1408 return tok, nil 1409 } 1410 1411 // renewToken is called to mint a new token or update existing one. 1412 // 1413 // It is called from non-interactive 'refreshToken' method, and thus it can't 1414 // use interactive login flow. 1415 func (t *tokenWithProvider) renewToken(ctx context.Context, prev, base *internal.Token) (*internal.Token, error) { 1416 if prev == nil { 1417 if t.provider.RequiresInteraction() { 1418 return nil, ErrLoginRequired 1419 } 1420 logging.Debugf(ctx, "Minting a new token") 1421 tok, err := t.mintTokenWithRetries(ctx, base) 1422 if err != nil { 1423 logging.Warningf(ctx, "Failed to mint a token: %s", err) 1424 return nil, err 1425 } 1426 return tok, nil 1427 } 1428 1429 logging.Debugf(ctx, "Refreshing the token") 1430 tok, err := t.refreshTokenWithRetries(ctx, prev, base) 1431 if err != nil { 1432 logging.Warningf(ctx, "Failed to refresh the token: %s", err) 1433 return nil, err 1434 } 1435 return tok, nil 1436 } 1437 1438 // retryParams defines retry strategy for handling transient errors when minting 1439 // or refreshing tokens. 1440 func retryParams() retry.Iterator { 1441 return &retry.ExponentialBackoff{ 1442 Limited: retry.Limited{ 1443 Delay: 10 * time.Millisecond, 1444 Retries: 50, 1445 MaxTotal: 2 * time.Minute, 1446 }, 1447 Multiplier: 2, 1448 } 1449 } 1450 1451 // mintTokenWithRetries calls provider's MintToken() retrying on transient 1452 // errors a bunch of times. Called only for non-interactive providers. 1453 func (t *tokenWithProvider) mintTokenWithRetries(ctx context.Context, base *internal.Token) (tok *internal.Token, err error) { 1454 err = retry.Retry(ctx, transient.Only(retryParams), func() error { 1455 tok, err = t.provider.MintToken(ctx, base) 1456 return err 1457 }, retry.LogCallback(ctx, "token-mint")) 1458 return 1459 } 1460 1461 // refreshTokenWithRetries calls providers' RefreshToken(...) retrying on 1462 // transient errors a bunch of times. 1463 func (t *tokenWithProvider) refreshTokenWithRetries(ctx context.Context, prev, base *internal.Token) (tok *internal.Token, err error) { 1464 err = retry.Retry(ctx, transient.Only(retryParams), func() error { 1465 tok, err = t.provider.RefreshToken(ctx, prev, base) 1466 return err 1467 }, retry.LogCallback(ctx, "token-refresh")) 1468 return 1469 } 1470 1471 //////////////////////////////////////////////////////////////////////////////// 1472 // Utility functions. 1473 1474 // normalizeScopes sorts the list of scopes and removes dups. 1475 // 1476 // Doesn't modify the original slice. 1477 func normalizeScopes(s []string) []string { 1478 for i := 1; i < len(s); i++ { 1479 if s[i] <= s[i-1] { // not sorted or has dups 1480 sorted := stringset.NewFromSlice(s...) 1481 return sorted.ToSortedSlice() 1482 } 1483 } 1484 return s // already sorted and dedupped 1485 } 1486 1487 // prepPhonyIDTokenScope checks `useIDTokens`. 1488 // 1489 // If it is true, requires the audience to be set and replaces scopes with 1490 // a phony "audience:<value>" scope to be used as a cache key (and ignored by 1491 // the providers, since they don't use OAuth2 scopes when minting ID tokens). 1492 // See also comment for Scopes in internal.CacheKey. 1493 // 1494 // If `useIDTokens` is false, clears `audience`. 1495 // 1496 // As a result, the audience is set if and only if `useIDTokens` is true. 1497 func prepPhonyIDTokenScope(useIDTokens bool, scopes []string, audience string) (scopesOut []string, audienceOut string, err error) { 1498 if useIDTokens { 1499 if audience == "" { 1500 return nil, "", ErrAudienceRequired 1501 } 1502 return []string{"audience:" + audience}, audience, nil 1503 } 1504 return scopes, "", nil 1505 } 1506 1507 // makeBaseTokenProvider creates TokenProvider implementation based on options. 1508 // 1509 // opts.Scopes and opts.UseIDTokens are ignored, `scopes` and `useIDTokens` are 1510 // used instead. This is used in actor mode to supply parameters necessary to 1511 // use an "acting" API: they generally do not match what's in `opts`. 1512 // 1513 // Called by ensureInitialized. 1514 func makeBaseTokenProvider(ctx context.Context, opts *Options, scopes []string, useIDTokens bool) (internal.TokenProvider, error) { 1515 if opts.testingBaseTokenProvider != nil { 1516 return opts.testingBaseTokenProvider, nil 1517 } 1518 1519 // Only UserCredentialsMethod can generate ID tokens and access tokens at 1520 // the same time. All other methods can do only ID tokens or only access 1521 // tokens. prepPhonyIDTokenScope checks/mutates the parameters accordingly, 1522 // see its doc. 1523 audience := opts.Audience 1524 if opts.Method != UserCredentialsMethod { 1525 var err error 1526 scopes, audience, err = prepPhonyIDTokenScope(useIDTokens, scopes, audience) 1527 if err != nil { 1528 return nil, err 1529 } 1530 } 1531 1532 switch opts.Method { 1533 case UserCredentialsMethod: 1534 if opts.ClientID == "" || opts.ClientSecret == "" { 1535 return nil, errors.Reason("OAuth client is not configured, can't use interactive login").Err() 1536 } 1537 // Note: both LoginSessionTokenProvider and UserAuthTokenProvider support 1538 // ID tokens and OAuth access tokens at the same time. They ignore audience 1539 // (it always matches ClientID). 1540 if opts.LoginSessionsHost != "" { 1541 if internal.NewLoginSessionTokenProvider == nil { 1542 return nil, errors.New("support for interactive login flow is not compiled into this binary") 1543 } 1544 return internal.NewLoginSessionTokenProvider( 1545 ctx, 1546 opts.LoginSessionsHost, 1547 opts.ClientID, 1548 opts.ClientSecret, 1549 scopes, 1550 opts.Transport) 1551 } 1552 return internal.NewUserAuthTokenProvider( 1553 ctx, 1554 opts.ClientID, 1555 opts.ClientSecret, 1556 scopes) 1557 case ServiceAccountMethod: 1558 serviceAccountPath := "" 1559 if len(opts.ServiceAccountJSON) == 0 { 1560 serviceAccountPath = opts.ServiceAccountJSONPath 1561 } 1562 return internal.NewServiceAccountTokenProvider( 1563 ctx, 1564 opts.ServiceAccountJSON, 1565 serviceAccountPath, 1566 scopes, 1567 audience) 1568 case GCEMetadataMethod: 1569 return internal.NewGCETokenProvider( 1570 ctx, 1571 opts.GCEAccountName, 1572 scopes, 1573 audience) 1574 case LUCIContextMethod: 1575 return internal.NewLUCIContextTokenProvider( 1576 ctx, 1577 scopes, 1578 audience, 1579 opts.Transport) 1580 default: 1581 return nil, errors.Reason("unrecognized authentication method: %s", opts.Method).Err() 1582 } 1583 } 1584 1585 // makeIAMTokenProvider creates TokenProvider to use in actingModeIAM mode. 1586 // 1587 // Called by ensureInitialized. 1588 func makeIAMTokenProvider(ctx context.Context, opts *Options) (internal.TokenProvider, error) { 1589 if opts.testingIAMTokenProvider != nil { 1590 return opts.testingIAMTokenProvider, nil 1591 } 1592 scopes, audience, err := prepPhonyIDTokenScope(opts.UseIDTokens, opts.Scopes, opts.Audience) 1593 if err != nil { 1594 return nil, err 1595 } 1596 return internal.NewIAMTokenProvider( 1597 ctx, 1598 opts.ActAsServiceAccount, 1599 scopes, 1600 audience, 1601 opts.Transport) 1602 } 1603 1604 // makeLUCITokenProvider creates TokenProvider to use in actingModeLUCI mode. 1605 // 1606 // Called by ensureInitialized. 1607 func makeLUCITokenProvider(ctx context.Context, opts *Options) (internal.TokenProvider, error) { 1608 if opts.TokenServerHost == "" { 1609 return nil, ErrBadOptions 1610 } 1611 if internal.NewLUCITSTokenProvider == nil { 1612 return nil, errors.New("support for impersonation through LUCI is not compiled into this binary") 1613 } 1614 scopes, audience, err := prepPhonyIDTokenScope(opts.UseIDTokens, opts.Scopes, opts.Audience) 1615 if err != nil { 1616 return nil, err 1617 } 1618 return internal.NewLUCITSTokenProvider( 1619 ctx, 1620 opts.TokenServerHost, 1621 opts.ActAsServiceAccount, 1622 opts.ActViaLUCIRealm, 1623 scopes, 1624 audience, 1625 opts.Transport) 1626 }