...

Source file src/cloud.google.com/go/auth/credentials/detect.go

Documentation: cloud.google.com/go/auth/credentials

     1  // Copyright 2023 Google LLC
     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 credentials
    16  
    17  import (
    18  	"context"
    19  	"encoding/json"
    20  	"errors"
    21  	"fmt"
    22  	"net/http"
    23  	"os"
    24  	"time"
    25  
    26  	"cloud.google.com/go/auth"
    27  	"cloud.google.com/go/auth/internal"
    28  	"cloud.google.com/go/auth/internal/credsfile"
    29  	"cloud.google.com/go/compute/metadata"
    30  )
    31  
    32  const (
    33  	// jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
    34  	jwtTokenURL = "https://oauth2.googleapis.com/token"
    35  
    36  	// Google's OAuth 2.0 default endpoints.
    37  	googleAuthURL  = "https://accounts.google.com/o/oauth2/auth"
    38  	googleTokenURL = "https://oauth2.googleapis.com/token"
    39  
    40  	// Help on default credentials
    41  	adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
    42  )
    43  
    44  var (
    45  	// for testing
    46  	allowOnGCECheck = true
    47  )
    48  
    49  // OnGCE reports whether this process is running in Google Cloud.
    50  func OnGCE() bool {
    51  	// TODO(codyoss): once all libs use this auth lib move metadata check here
    52  	return allowOnGCECheck && metadata.OnGCE()
    53  }
    54  
    55  // DetectDefault searches for "Application Default Credentials" and returns
    56  // a credential based on the [DetectOptions] provided.
    57  //
    58  // It looks for credentials in the following places, preferring the first
    59  // location found:
    60  //
    61  //   - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
    62  //     environment variable. For workload identity federation, refer to
    63  //     https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
    64  //     on how to generate the JSON configuration file for on-prem/non-Google
    65  //     cloud platforms.
    66  //   - A JSON file in a location known to the gcloud command-line tool. On
    67  //     Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
    68  //     other systems, $HOME/.config/gcloud/application_default_credentials.json.
    69  //   - On Google Compute Engine, Google App Engine standard second generation
    70  //     runtimes, and Google App Engine flexible environment, it fetches
    71  //     credentials from the metadata server.
    72  func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
    73  	if err := opts.validate(); err != nil {
    74  		return nil, err
    75  	}
    76  	if opts.CredentialsJSON != nil {
    77  		return readCredentialsFileJSON(opts.CredentialsJSON, opts)
    78  	}
    79  	if opts.CredentialsFile != "" {
    80  		return readCredentialsFile(opts.CredentialsFile, opts)
    81  	}
    82  	if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
    83  		if creds, err := readCredentialsFile(filename, opts); err == nil {
    84  			return creds, err
    85  		}
    86  	}
    87  
    88  	fileName := credsfile.GetWellKnownFileName()
    89  	if b, err := os.ReadFile(fileName); err == nil {
    90  		return readCredentialsFileJSON(b, opts)
    91  	}
    92  
    93  	if OnGCE() {
    94  		return auth.NewCredentials(&auth.CredentialsOptions{
    95  			TokenProvider: computeTokenProvider(opts.EarlyTokenRefresh, opts.Scopes...),
    96  			ProjectIDProvider: auth.CredentialsPropertyFunc(func(context.Context) (string, error) {
    97  				return metadata.ProjectID()
    98  			}),
    99  			UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{},
   100  		}), nil
   101  	}
   102  
   103  	return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
   104  }
   105  
   106  // DetectOptions provides configuration for [DetectDefault].
   107  type DetectOptions struct {
   108  	// Scopes that credentials tokens should have. Example:
   109  	// https://www.googleapis.com/auth/cloud-platform. Required if Audience is
   110  	// not provided.
   111  	Scopes []string
   112  	// Audience that credentials tokens should have. Only applicable for 2LO
   113  	// flows with service accounts. If specified, scopes should not be provided.
   114  	Audience string
   115  	// Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
   116  	// Optional.
   117  	Subject string
   118  	// EarlyTokenRefresh configures how early before a token expires that it
   119  	// should be refreshed.
   120  	EarlyTokenRefresh time.Duration
   121  	// AuthHandlerOptions configures an authorization handler and other options
   122  	// for 3LO flows. It is required, and only used, for client credential
   123  	// flows.
   124  	AuthHandlerOptions *auth.AuthorizationHandlerOptions
   125  	// TokenURL allows to set the token endpoint for user credential flows. If
   126  	// unset the default value is: https://oauth2.googleapis.com/token.
   127  	// Optional.
   128  	TokenURL string
   129  	// STSAudience is the audience sent to when retrieving an STS token.
   130  	// Currently this only used for GDCH auth flow, for which it is required.
   131  	STSAudience string
   132  	// CredentialsFile overrides detection logic and sources a credential file
   133  	// from the provided filepath. If provided, CredentialsJSON must not be.
   134  	// Optional.
   135  	CredentialsFile string
   136  	// CredentialsJSON overrides detection logic and uses the JSON bytes as the
   137  	// source for the credential. If provided, CredentialsFile must not be.
   138  	// Optional.
   139  	CredentialsJSON []byte
   140  	// UseSelfSignedJWT directs service account based credentials to create a
   141  	// self-signed JWT with the private key found in the file, skipping any
   142  	// network requests that would normally be made. Optional.
   143  	UseSelfSignedJWT bool
   144  	// Client configures the underlying client used to make network requests
   145  	// when fetching tokens. Optional.
   146  	Client *http.Client
   147  	// UniverseDomain is the default service domain for a given Cloud universe.
   148  	// The default value is "googleapis.com". This option is ignored for
   149  	// authentication flows that do not support universe domain. Optional.
   150  	UniverseDomain string
   151  }
   152  
   153  func (o *DetectOptions) validate() error {
   154  	if o == nil {
   155  		return errors.New("credentials: options must be provided")
   156  	}
   157  	if len(o.Scopes) > 0 && o.Audience != "" {
   158  		return errors.New("credentials: both scopes and audience were provided")
   159  	}
   160  	if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
   161  		return errors.New("credentials: both credentials file and JSON were provided")
   162  	}
   163  	return nil
   164  }
   165  
   166  func (o *DetectOptions) tokenURL() string {
   167  	if o.TokenURL != "" {
   168  		return o.TokenURL
   169  	}
   170  	return googleTokenURL
   171  }
   172  
   173  func (o *DetectOptions) scopes() []string {
   174  	scopes := make([]string, len(o.Scopes))
   175  	copy(scopes, o.Scopes)
   176  	return scopes
   177  }
   178  
   179  func (o *DetectOptions) client() *http.Client {
   180  	if o.Client != nil {
   181  		return o.Client
   182  	}
   183  	return internal.CloneDefaultClient()
   184  }
   185  
   186  func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
   187  	b, err := os.ReadFile(filename)
   188  	if err != nil {
   189  		return nil, err
   190  	}
   191  	return readCredentialsFileJSON(b, opts)
   192  }
   193  
   194  func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
   195  	// attempt to parse jsonData as a Google Developers Console client_credentials.json.
   196  	config := clientCredConfigFromJSON(b, opts)
   197  	if config != nil {
   198  		if config.AuthHandlerOpts == nil {
   199  			return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
   200  		}
   201  		tp, err := auth.New3LOTokenProvider(config)
   202  		if err != nil {
   203  			return nil, err
   204  		}
   205  		return auth.NewCredentials(&auth.CredentialsOptions{
   206  			TokenProvider: tp,
   207  			JSON:          b,
   208  		}), nil
   209  	}
   210  	return fileCredentials(b, opts)
   211  }
   212  
   213  func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
   214  	var creds credsfile.ClientCredentialsFile
   215  	var c *credsfile.Config3LO
   216  	if err := json.Unmarshal(b, &creds); err != nil {
   217  		return nil
   218  	}
   219  	switch {
   220  	case creds.Web != nil:
   221  		c = creds.Web
   222  	case creds.Installed != nil:
   223  		c = creds.Installed
   224  	default:
   225  		return nil
   226  	}
   227  	if len(c.RedirectURIs) < 1 {
   228  		return nil
   229  	}
   230  	var handleOpts *auth.AuthorizationHandlerOptions
   231  	if opts.AuthHandlerOptions != nil {
   232  		handleOpts = &auth.AuthorizationHandlerOptions{
   233  			Handler:  opts.AuthHandlerOptions.Handler,
   234  			State:    opts.AuthHandlerOptions.State,
   235  			PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
   236  		}
   237  	}
   238  	return &auth.Options3LO{
   239  		ClientID:         c.ClientID,
   240  		ClientSecret:     c.ClientSecret,
   241  		RedirectURL:      c.RedirectURIs[0],
   242  		Scopes:           opts.scopes(),
   243  		AuthURL:          c.AuthURI,
   244  		TokenURL:         c.TokenURI,
   245  		Client:           opts.client(),
   246  		EarlyTokenExpiry: opts.EarlyTokenRefresh,
   247  		AuthHandlerOpts:  handleOpts,
   248  		// TODO(codyoss): refactor this out. We need to add in auto-detection
   249  		// for this use case.
   250  		AuthStyle: auth.StyleInParams,
   251  	}
   252  }
   253  

View as plain text