golang.org/x/oauth2@v0.18.0/google/default.go (about)

     1  // Copyright 2015 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package google
     6  
     7  import (
     8  	"context"
     9  	"encoding/json"
    10  	"fmt"
    11  	"net/http"
    12  	"os"
    13  	"path/filepath"
    14  	"runtime"
    15  	"sync"
    16  	"time"
    17  
    18  	"cloud.google.com/go/compute/metadata"
    19  	"golang.org/x/oauth2"
    20  	"golang.org/x/oauth2/authhandler"
    21  )
    22  
    23  const (
    24  	adcSetupURL           = "https://cloud.google.com/docs/authentication/external/set-up-adc"
    25  	defaultUniverseDomain = "googleapis.com"
    26  )
    27  
    28  // Credentials holds Google credentials, including "Application Default Credentials".
    29  // For more details, see:
    30  // https://developers.google.com/accounts/docs/application-default-credentials
    31  // Credentials from external accounts (workload identity federation) are used to
    32  // identify a particular application from an on-prem or non-Google Cloud platform
    33  // including Amazon Web Services (AWS), Microsoft Azure or any identity provider
    34  // that supports OpenID Connect (OIDC).
    35  type Credentials struct {
    36  	ProjectID   string // may be empty
    37  	TokenSource oauth2.TokenSource
    38  
    39  	// JSON contains the raw bytes from a JSON credentials file.
    40  	// This field may be nil if authentication is provided by the
    41  	// environment and not with a credentials file, e.g. when code is
    42  	// running on Google Cloud Platform.
    43  	JSON []byte
    44  
    45  	udMu sync.Mutex // guards universeDomain
    46  	// universeDomain is the default service domain for a given Cloud universe.
    47  	universeDomain string
    48  }
    49  
    50  // UniverseDomain returns the default service domain for a given Cloud universe.
    51  //
    52  // The default value is "googleapis.com".
    53  //
    54  // Deprecated: Use instead (*Credentials).GetUniverseDomain(), which supports
    55  // obtaining the universe domain when authenticating via the GCE metadata server.
    56  // Unlike GetUniverseDomain, this method, UniverseDomain, will always return the
    57  // default value when authenticating via the GCE metadata server.
    58  // See also [The attached service account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa).
    59  func (c *Credentials) UniverseDomain() string {
    60  	if c.universeDomain == "" {
    61  		return defaultUniverseDomain
    62  	}
    63  	return c.universeDomain
    64  }
    65  
    66  // GetUniverseDomain returns the default service domain for a given Cloud
    67  // universe.
    68  //
    69  // The default value is "googleapis.com".
    70  //
    71  // It obtains the universe domain from the attached service account on GCE when
    72  // authenticating via the GCE metadata server. See also [The attached service
    73  // account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa).
    74  // If the GCE metadata server returns a 404 error, the default value is
    75  // returned. If the GCE metadata server returns an error other than 404, the
    76  // error is returned.
    77  func (c *Credentials) GetUniverseDomain() (string, error) {
    78  	c.udMu.Lock()
    79  	defer c.udMu.Unlock()
    80  	if c.universeDomain == "" && metadata.OnGCE() {
    81  		// If we're on Google Compute Engine, an App Engine standard second
    82  		// generation runtime, or App Engine flexible, use the metadata server.
    83  		err := c.computeUniverseDomain()
    84  		if err != nil {
    85  			return "", err
    86  		}
    87  	}
    88  	// If not on Google Compute Engine, or in case of any non-error path in
    89  	// computeUniverseDomain that did not set universeDomain, set the default
    90  	// universe domain.
    91  	if c.universeDomain == "" {
    92  		c.universeDomain = defaultUniverseDomain
    93  	}
    94  	return c.universeDomain, nil
    95  }
    96  
    97  // computeUniverseDomain fetches the default service domain for a given Cloud
    98  // universe from Google Compute Engine (GCE)'s metadata server. It's only valid
    99  // to use this method if your program is running on a GCE instance.
   100  func (c *Credentials) computeUniverseDomain() error {
   101  	var err error
   102  	c.universeDomain, err = metadata.Get("universe/universe_domain")
   103  	if err != nil {
   104  		if _, ok := err.(metadata.NotDefinedError); ok {
   105  			// http.StatusNotFound (404)
   106  			c.universeDomain = defaultUniverseDomain
   107  			return nil
   108  		} else {
   109  			return err
   110  		}
   111  	}
   112  	return nil
   113  }
   114  
   115  // DefaultCredentials is the old name of Credentials.
   116  //
   117  // Deprecated: use Credentials instead.
   118  type DefaultCredentials = Credentials
   119  
   120  // CredentialsParams holds user supplied parameters that are used together
   121  // with a credentials file for building a Credentials object.
   122  type CredentialsParams struct {
   123  	// Scopes is the list OAuth scopes. Required.
   124  	// Example: https://www.googleapis.com/auth/cloud-platform
   125  	Scopes []string
   126  
   127  	// Subject is the user email used for domain wide delegation (see
   128  	// https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
   129  	// Optional.
   130  	Subject string
   131  
   132  	// AuthHandler is the AuthorizationHandler used for 3-legged OAuth flow. Required for 3LO flow.
   133  	AuthHandler authhandler.AuthorizationHandler
   134  
   135  	// State is a unique string used with AuthHandler. Required for 3LO flow.
   136  	State string
   137  
   138  	// PKCE is used to support PKCE flow. Optional for 3LO flow.
   139  	PKCE *authhandler.PKCEParams
   140  
   141  	// The OAuth2 TokenURL default override. This value overrides the default TokenURL,
   142  	// unless explicitly specified by the credentials config file. Optional.
   143  	TokenURL string
   144  
   145  	// EarlyTokenRefresh is the amount of time before a token expires that a new
   146  	// token will be preemptively fetched. If unset the default value is 10
   147  	// seconds.
   148  	//
   149  	// Note: This option is currently only respected when using credentials
   150  	// fetched from the GCE metadata server.
   151  	EarlyTokenRefresh time.Duration
   152  
   153  	// UniverseDomain is the default service domain for a given Cloud universe.
   154  	// Only supported in authentication flows that support universe domains.
   155  	// This value takes precedence over a universe domain explicitly specified
   156  	// in a credentials config file or by the GCE metadata server. Optional.
   157  	UniverseDomain string
   158  }
   159  
   160  func (params CredentialsParams) deepCopy() CredentialsParams {
   161  	paramsCopy := params
   162  	paramsCopy.Scopes = make([]string, len(params.Scopes))
   163  	copy(paramsCopy.Scopes, params.Scopes)
   164  	return paramsCopy
   165  }
   166  
   167  // DefaultClient returns an HTTP Client that uses the
   168  // DefaultTokenSource to obtain authentication credentials.
   169  func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) {
   170  	ts, err := DefaultTokenSource(ctx, scope...)
   171  	if err != nil {
   172  		return nil, err
   173  	}
   174  	return oauth2.NewClient(ctx, ts), nil
   175  }
   176  
   177  // DefaultTokenSource returns the token source for
   178  // "Application Default Credentials".
   179  // It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource.
   180  func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) {
   181  	creds, err := FindDefaultCredentials(ctx, scope...)
   182  	if err != nil {
   183  		return nil, err
   184  	}
   185  	return creds.TokenSource, nil
   186  }
   187  
   188  // FindDefaultCredentialsWithParams searches for "Application Default Credentials".
   189  //
   190  // It looks for credentials in the following places,
   191  // preferring the first location found:
   192  //
   193  //  1. A JSON file whose path is specified by the
   194  //     GOOGLE_APPLICATION_CREDENTIALS environment variable.
   195  //     For workload identity federation, refer to
   196  //     https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation on
   197  //     how to generate the JSON configuration file for on-prem/non-Google cloud
   198  //     platforms.
   199  //  2. A JSON file in a location known to the gcloud command-line tool.
   200  //     On Windows, this is %APPDATA%/gcloud/application_default_credentials.json.
   201  //     On other systems, $HOME/.config/gcloud/application_default_credentials.json.
   202  //  3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses
   203  //     the appengine.AccessToken function.
   204  //  4. On Google Compute Engine, Google App Engine standard second generation runtimes
   205  //     (>= Go 1.11), and Google App Engine flexible environment, it fetches
   206  //     credentials from the metadata server.
   207  func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsParams) (*Credentials, error) {
   208  	// Make defensive copy of the slices in params.
   209  	params = params.deepCopy()
   210  
   211  	// First, try the environment variable.
   212  	const envVar = "GOOGLE_APPLICATION_CREDENTIALS"
   213  	if filename := os.Getenv(envVar); filename != "" {
   214  		creds, err := readCredentialsFile(ctx, filename, params)
   215  		if err != nil {
   216  			return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err)
   217  		}
   218  		return creds, nil
   219  	}
   220  
   221  	// Second, try a well-known file.
   222  	filename := wellKnownFile()
   223  	if b, err := os.ReadFile(filename); err == nil {
   224  		return CredentialsFromJSONWithParams(ctx, b, params)
   225  	}
   226  
   227  	// Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9)
   228  	// use those credentials. App Engine standard second generation runtimes (>= Go 1.11)
   229  	// and App Engine flexible use ComputeTokenSource and the metadata server.
   230  	if appengineTokenFunc != nil {
   231  		return &Credentials{
   232  			ProjectID:   appengineAppIDFunc(ctx),
   233  			TokenSource: AppEngineTokenSource(ctx, params.Scopes...),
   234  		}, nil
   235  	}
   236  
   237  	// Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime,
   238  	// or App Engine flexible, use the metadata server.
   239  	if metadata.OnGCE() {
   240  		id, _ := metadata.ProjectID()
   241  		return &Credentials{
   242  			ProjectID:      id,
   243  			TokenSource:    computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...),
   244  			universeDomain: params.UniverseDomain,
   245  		}, nil
   246  	}
   247  
   248  	// None are found; return helpful error.
   249  	return nil, fmt.Errorf("google: could not find default credentials. See %v for more information", adcSetupURL)
   250  }
   251  
   252  // FindDefaultCredentials invokes FindDefaultCredentialsWithParams with the specified scopes.
   253  func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) {
   254  	var params CredentialsParams
   255  	params.Scopes = scopes
   256  	return FindDefaultCredentialsWithParams(ctx, params)
   257  }
   258  
   259  // CredentialsFromJSONWithParams obtains Google credentials from a JSON value. The JSON can
   260  // represent either a Google Developers Console client_credentials.json file (as in ConfigFromJSON),
   261  // a Google Developers service account key file, a gcloud user credentials file (a.k.a. refresh
   262  // token JSON), or the JSON configuration file for workload identity federation in non-Google cloud
   263  // platforms (see https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation).
   264  func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params CredentialsParams) (*Credentials, error) {
   265  	// Make defensive copy of the slices in params.
   266  	params = params.deepCopy()
   267  
   268  	// First, attempt to parse jsonData as a Google Developers Console client_credentials.json.
   269  	config, _ := ConfigFromJSON(jsonData, params.Scopes...)
   270  	if config != nil {
   271  		return &Credentials{
   272  			ProjectID:   "",
   273  			TokenSource: authhandler.TokenSourceWithPKCE(ctx, config, params.State, params.AuthHandler, params.PKCE),
   274  			JSON:        jsonData,
   275  		}, nil
   276  	}
   277  
   278  	// Otherwise, parse jsonData as one of the other supported credentials files.
   279  	var f credentialsFile
   280  	if err := json.Unmarshal(jsonData, &f); err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	universeDomain := f.UniverseDomain
   285  	if params.UniverseDomain != "" {
   286  		universeDomain = params.UniverseDomain
   287  	}
   288  	// Authorized user credentials are only supported in the googleapis.com universe.
   289  	if f.Type == userCredentialsKey {
   290  		universeDomain = defaultUniverseDomain
   291  	}
   292  
   293  	ts, err := f.tokenSource(ctx, params)
   294  	if err != nil {
   295  		return nil, err
   296  	}
   297  	ts = newErrWrappingTokenSource(ts)
   298  	return &Credentials{
   299  		ProjectID:      f.ProjectID,
   300  		TokenSource:    ts,
   301  		JSON:           jsonData,
   302  		universeDomain: universeDomain,
   303  	}, nil
   304  }
   305  
   306  // CredentialsFromJSON invokes CredentialsFromJSONWithParams with the specified scopes.
   307  func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) {
   308  	var params CredentialsParams
   309  	params.Scopes = scopes
   310  	return CredentialsFromJSONWithParams(ctx, jsonData, params)
   311  }
   312  
   313  func wellKnownFile() string {
   314  	const f = "application_default_credentials.json"
   315  	if runtime.GOOS == "windows" {
   316  		return filepath.Join(os.Getenv("APPDATA"), "gcloud", f)
   317  	}
   318  	return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f)
   319  }
   320  
   321  func readCredentialsFile(ctx context.Context, filename string, params CredentialsParams) (*Credentials, error) {
   322  	b, err := os.ReadFile(filename)
   323  	if err != nil {
   324  		return nil, err
   325  	}
   326  	return CredentialsFromJSONWithParams(ctx, b, params)
   327  }