default.go raw

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