| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262 |
- // Copyright 2023 Google LLC
- //
- // 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 credentials
- import (
- "context"
- "encoding/json"
- "errors"
- "fmt"
- "net/http"
- "os"
- "time"
- "cloud.google.com/go/auth"
- "cloud.google.com/go/auth/internal"
- "cloud.google.com/go/auth/internal/credsfile"
- "cloud.google.com/go/compute/metadata"
- )
- const (
- // jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
- jwtTokenURL = "https://oauth2.googleapis.com/token"
- // Google's OAuth 2.0 default endpoints.
- googleAuthURL = "https://accounts.google.com/o/oauth2/auth"
- googleTokenURL = "https://oauth2.googleapis.com/token"
- // GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
- GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
- // Help on default credentials
- adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
- )
- var (
- // for testing
- allowOnGCECheck = true
- )
- // OnGCE reports whether this process is running in Google Cloud.
- func OnGCE() bool {
- // TODO(codyoss): once all libs use this auth lib move metadata check here
- return allowOnGCECheck && metadata.OnGCE()
- }
- // DetectDefault searches for "Application Default Credentials" and returns
- // a credential based on the [DetectOptions] provided.
- //
- // It looks for credentials in the following places, preferring the first
- // location found:
- //
- // - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
- // environment variable. For workload identity federation, refer to
- // https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
- // on how to generate the JSON configuration file for on-prem/non-Google
- // cloud platforms.
- // - A JSON file in a location known to the gcloud command-line tool. On
- // Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
- // other systems, $HOME/.config/gcloud/application_default_credentials.json.
- // - On Google Compute Engine, Google App Engine standard second generation
- // runtimes, and Google App Engine flexible environment, it fetches
- // credentials from the metadata server.
- func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
- if err := opts.validate(); err != nil {
- return nil, err
- }
- if len(opts.CredentialsJSON) > 0 {
- return readCredentialsFileJSON(opts.CredentialsJSON, opts)
- }
- if opts.CredentialsFile != "" {
- return readCredentialsFile(opts.CredentialsFile, opts)
- }
- if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
- creds, err := readCredentialsFile(filename, opts)
- if err != nil {
- return nil, err
- }
- return creds, nil
- }
- fileName := credsfile.GetWellKnownFileName()
- if b, err := os.ReadFile(fileName); err == nil {
- return readCredentialsFileJSON(b, opts)
- }
- if OnGCE() {
- return auth.NewCredentials(&auth.CredentialsOptions{
- TokenProvider: computeTokenProvider(opts),
- ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
- return metadata.ProjectIDWithContext(ctx)
- }),
- UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{},
- }), nil
- }
- return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
- }
- // DetectOptions provides configuration for [DetectDefault].
- type DetectOptions struct {
- // Scopes that credentials tokens should have. Example:
- // https://www.googleapis.com/auth/cloud-platform. Required if Audience is
- // not provided.
- Scopes []string
- // Audience that credentials tokens should have. Only applicable for 2LO
- // flows with service accounts. If specified, scopes should not be provided.
- Audience string
- // Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
- // Optional.
- Subject string
- // EarlyTokenRefresh configures how early before a token expires that it
- // should be refreshed. Once the token’s time until expiration has entered
- // this refresh window the token is considered valid but stale. If unset,
- // the default value is 3 minutes and 45 seconds. Optional.
- EarlyTokenRefresh time.Duration
- // DisableAsyncRefresh configures a synchronous workflow that refreshes
- // stale tokens while blocking. The default is false. Optional.
- DisableAsyncRefresh bool
- // AuthHandlerOptions configures an authorization handler and other options
- // for 3LO flows. It is required, and only used, for client credential
- // flows.
- AuthHandlerOptions *auth.AuthorizationHandlerOptions
- // TokenURL allows to set the token endpoint for user credential flows. If
- // unset the default value is: https://oauth2.googleapis.com/token.
- // Optional.
- TokenURL string
- // STSAudience is the audience sent to when retrieving an STS token.
- // Currently this only used for GDCH auth flow, for which it is required.
- STSAudience string
- // CredentialsFile overrides detection logic and sources a credential file
- // from the provided filepath. If provided, CredentialsJSON must not be.
- // Optional.
- CredentialsFile string
- // CredentialsJSON overrides detection logic and uses the JSON bytes as the
- // source for the credential. If provided, CredentialsFile must not be.
- // Optional.
- CredentialsJSON []byte
- // UseSelfSignedJWT directs service account based credentials to create a
- // self-signed JWT with the private key found in the file, skipping any
- // network requests that would normally be made. Optional.
- UseSelfSignedJWT bool
- // Client configures the underlying client used to make network requests
- // when fetching tokens. Optional.
- Client *http.Client
- // UniverseDomain is the default service domain for a given Cloud universe.
- // The default value is "googleapis.com". This option is ignored for
- // authentication flows that do not support universe domain. Optional.
- UniverseDomain string
- }
- func (o *DetectOptions) validate() error {
- if o == nil {
- return errors.New("credentials: options must be provided")
- }
- if len(o.Scopes) > 0 && o.Audience != "" {
- return errors.New("credentials: both scopes and audience were provided")
- }
- if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
- return errors.New("credentials: both credentials file and JSON were provided")
- }
- return nil
- }
- func (o *DetectOptions) tokenURL() string {
- if o.TokenURL != "" {
- return o.TokenURL
- }
- return googleTokenURL
- }
- func (o *DetectOptions) scopes() []string {
- scopes := make([]string, len(o.Scopes))
- copy(scopes, o.Scopes)
- return scopes
- }
- func (o *DetectOptions) client() *http.Client {
- if o.Client != nil {
- return o.Client
- }
- return internal.DefaultClient()
- }
- func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
- b, err := os.ReadFile(filename)
- if err != nil {
- return nil, err
- }
- return readCredentialsFileJSON(b, opts)
- }
- func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
- // attempt to parse jsonData as a Google Developers Console client_credentials.json.
- config := clientCredConfigFromJSON(b, opts)
- if config != nil {
- if config.AuthHandlerOpts == nil {
- return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
- }
- tp, err := auth.New3LOTokenProvider(config)
- if err != nil {
- return nil, err
- }
- return auth.NewCredentials(&auth.CredentialsOptions{
- TokenProvider: tp,
- JSON: b,
- }), nil
- }
- return fileCredentials(b, opts)
- }
- func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
- var creds credsfile.ClientCredentialsFile
- var c *credsfile.Config3LO
- if err := json.Unmarshal(b, &creds); err != nil {
- return nil
- }
- switch {
- case creds.Web != nil:
- c = creds.Web
- case creds.Installed != nil:
- c = creds.Installed
- default:
- return nil
- }
- if len(c.RedirectURIs) < 1 {
- return nil
- }
- var handleOpts *auth.AuthorizationHandlerOptions
- if opts.AuthHandlerOptions != nil {
- handleOpts = &auth.AuthorizationHandlerOptions{
- Handler: opts.AuthHandlerOptions.Handler,
- State: opts.AuthHandlerOptions.State,
- PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
- }
- }
- return &auth.Options3LO{
- ClientID: c.ClientID,
- ClientSecret: c.ClientSecret,
- RedirectURL: c.RedirectURIs[0],
- Scopes: opts.scopes(),
- AuthURL: c.AuthURI,
- TokenURL: c.TokenURI,
- Client: opts.client(),
- EarlyTokenExpiry: opts.EarlyTokenRefresh,
- AuthHandlerOpts: handleOpts,
- // TODO(codyoss): refactor this out. We need to add in auto-detection
- // for this use case.
- AuthStyle: auth.StyleInParams,
- }
- }
|