// Copyright 2015 The Go Authors. All rights reserved. // Use of this source code is governed by a BSD-style // license that can be found in the LICENSE file. package google import ( "context" "encoding/json" "fmt" "net/http" "os" "path/filepath" "runtime" "sync" "time" "cloud.google.com/go/compute/metadata" "golang.org/x/oauth2" "golang.org/x/oauth2/authhandler" ) const ( adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc" defaultUniverseDomain = "googleapis.com" ) // Credentials holds Google credentials, including "Application Default Credentials". // For more details, see: // https://developers.google.com/accounts/docs/application-default-credentials // Credentials from external accounts (workload identity federation) are used to // identify a particular application from an on-prem or non-Google Cloud platform // including Amazon Web Services (AWS), Microsoft Azure or any identity provider // that supports OpenID Connect (OIDC). type Credentials struct { ProjectID string // may be empty TokenSource oauth2.TokenSource // JSON contains the raw bytes from a JSON credentials file. // This field may be nil if authentication is provided by the // environment and not with a credentials file, e.g. when code is // running on Google Cloud Platform. JSON []byte udMu sync.Mutex // guards universeDomain // universeDomain is the default service domain for a given Cloud universe. universeDomain string } // UniverseDomain returns the default service domain for a given Cloud universe. // // The default value is "googleapis.com". // // Deprecated: Use instead (*Credentials).GetUniverseDomain(), which supports // obtaining the universe domain when authenticating via the GCE metadata server. // Unlike GetUniverseDomain, this method, UniverseDomain, will always return the // default value when authenticating via the GCE metadata server. // See also [The attached service account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa). func (c *Credentials) UniverseDomain() string { if c.universeDomain == "" { return defaultUniverseDomain } return c.universeDomain } // GetUniverseDomain returns the default service domain for a given Cloud // universe. // // The default value is "googleapis.com". // // It obtains the universe domain from the attached service account on GCE when // authenticating via the GCE metadata server. See also [The attached service // account](https://cloud.google.com/docs/authentication/application-default-credentials#attached-sa). // If the GCE metadata server returns a 404 error, the default value is // returned. If the GCE metadata server returns an error other than 404, the // error is returned. func (c *Credentials) GetUniverseDomain() (string, error) { c.udMu.Lock() defer c.udMu.Unlock() if c.universeDomain == "" && metadata.OnGCE() { // If we're on Google Compute Engine, an App Engine standard second // generation runtime, or App Engine flexible, use the metadata server. err := c.computeUniverseDomain() if err != nil { return "", err } } // If not on Google Compute Engine, or in case of any non-error path in // computeUniverseDomain that did not set universeDomain, set the default // universe domain. if c.universeDomain == "" { c.universeDomain = defaultUniverseDomain } return c.universeDomain, nil } // computeUniverseDomain fetches the default service domain for a given Cloud // universe from Google Compute Engine (GCE)'s metadata server. It's only valid // to use this method if your program is running on a GCE instance. func (c *Credentials) computeUniverseDomain() error { var err error c.universeDomain, err = metadata.Get("universe/universe_domain") if err != nil { if _, ok := err.(metadata.NotDefinedError); ok { // http.StatusNotFound (404) c.universeDomain = defaultUniverseDomain return nil } else { return err } } return nil } // DefaultCredentials is the old name of Credentials. // // Deprecated: use Credentials instead. type DefaultCredentials = Credentials // CredentialsParams holds user supplied parameters that are used together // with a credentials file for building a Credentials object. type CredentialsParams struct { // Scopes is the list OAuth scopes. Required. // Example: https://www.googleapis.com/auth/cloud-platform Scopes []string // Subject is the user email used for domain wide delegation (see // https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority). // Optional. Subject string // AuthHandler is the AuthorizationHandler used for 3-legged OAuth flow. Required for 3LO flow. AuthHandler authhandler.AuthorizationHandler // State is a unique string used with AuthHandler. Required for 3LO flow. State string // PKCE is used to support PKCE flow. Optional for 3LO flow. PKCE *authhandler.PKCEParams // The OAuth2 TokenURL default override. This value overrides the default TokenURL, // unless explicitly specified by the credentials config file. Optional. TokenURL string // EarlyTokenRefresh is the amount of time before a token expires that a new // token will be preemptively fetched. If unset the default value is 10 // seconds. // // Note: This option is currently only respected when using credentials // fetched from the GCE metadata server. EarlyTokenRefresh time.Duration // UniverseDomain is the default service domain for a given Cloud universe. // Only supported in authentication flows that support universe domains. // This value takes precedence over a universe domain explicitly specified // in a credentials config file or by the GCE metadata server. Optional. UniverseDomain string } func (params CredentialsParams) deepCopy() CredentialsParams { paramsCopy := params paramsCopy.Scopes = make([]string, len(params.Scopes)) copy(paramsCopy.Scopes, params.Scopes) return paramsCopy } // DefaultClient returns an HTTP Client that uses the // DefaultTokenSource to obtain authentication credentials. func DefaultClient(ctx context.Context, scope ...string) (*http.Client, error) { ts, err := DefaultTokenSource(ctx, scope...) if err != nil { return nil, err } return oauth2.NewClient(ctx, ts), nil } // DefaultTokenSource returns the token source for // "Application Default Credentials". // It is a shortcut for FindDefaultCredentials(ctx, scope).TokenSource. func DefaultTokenSource(ctx context.Context, scope ...string) (oauth2.TokenSource, error) { creds, err := FindDefaultCredentials(ctx, scope...) if err != nil { return nil, err } return creds.TokenSource, nil } // FindDefaultCredentialsWithParams searches for "Application Default Credentials". // // It looks for credentials in the following places, // preferring the first location found: // // 1. 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. // 2. 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. // 3. On Google App Engine standard first generation runtimes (<= Go 1.9) it uses // the appengine.AccessToken function. // 4. On Google Compute Engine, Google App Engine standard second generation runtimes // (>= Go 1.11), and Google App Engine flexible environment, it fetches // credentials from the metadata server. func FindDefaultCredentialsWithParams(ctx context.Context, params CredentialsParams) (*Credentials, error) { // Make defensive copy of the slices in params. params = params.deepCopy() // First, try the environment variable. const envVar = "GOOGLE_APPLICATION_CREDENTIALS" if filename := os.Getenv(envVar); filename != "" { creds, err := readCredentialsFile(ctx, filename, params) if err != nil { return nil, fmt.Errorf("google: error getting credentials using %v environment variable: %v", envVar, err) } return creds, nil } // Second, try a well-known file. filename := wellKnownFile() if b, err := os.ReadFile(filename); err == nil { return CredentialsFromJSONWithParams(ctx, b, params) } // Third, if we're on a Google App Engine standard first generation runtime (<= Go 1.9) // use those credentials. App Engine standard second generation runtimes (>= Go 1.11) // and App Engine flexible use ComputeTokenSource and the metadata server. if appengineTokenFunc != nil { return &Credentials{ ProjectID: appengineAppIDFunc(ctx), TokenSource: AppEngineTokenSource(ctx, params.Scopes...), }, nil } // Fourth, if we're on Google Compute Engine, an App Engine standard second generation runtime, // or App Engine flexible, use the metadata server. if metadata.OnGCE() { id, _ := metadata.ProjectID() return &Credentials{ ProjectID: id, TokenSource: computeTokenSource("", params.EarlyTokenRefresh, params.Scopes...), universeDomain: params.UniverseDomain, }, nil } // None are found; return helpful error. return nil, fmt.Errorf("google: could not find default credentials. See %v for more information", adcSetupURL) } // FindDefaultCredentials invokes FindDefaultCredentialsWithParams with the specified scopes. func FindDefaultCredentials(ctx context.Context, scopes ...string) (*Credentials, error) { var params CredentialsParams params.Scopes = scopes return FindDefaultCredentialsWithParams(ctx, params) } // CredentialsFromJSONWithParams obtains Google credentials from a JSON value. The JSON can // represent either a Google Developers Console client_credentials.json file (as in ConfigFromJSON), // a Google Developers service account key file, a gcloud user credentials file (a.k.a. refresh // token JSON), or the JSON configuration file for workload identity federation in non-Google cloud // platforms (see https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation). func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params CredentialsParams) (*Credentials, error) { // Make defensive copy of the slices in params. params = params.deepCopy() // First, attempt to parse jsonData as a Google Developers Console client_credentials.json. config, _ := ConfigFromJSON(jsonData, params.Scopes...) if config != nil { return &Credentials{ ProjectID: "", TokenSource: authhandler.TokenSourceWithPKCE(ctx, config, params.State, params.AuthHandler, params.PKCE), JSON: jsonData, }, nil } // Otherwise, parse jsonData as one of the other supported credentials files. var f credentialsFile if err := json.Unmarshal(jsonData, &f); err != nil { return nil, err } universeDomain := f.UniverseDomain if params.UniverseDomain != "" { universeDomain = params.UniverseDomain } // Authorized user credentials are only supported in the googleapis.com universe. if f.Type == userCredentialsKey { universeDomain = defaultUniverseDomain } ts, err := f.tokenSource(ctx, params) if err != nil { return nil, err } ts = newErrWrappingTokenSource(ts) return &Credentials{ ProjectID: f.ProjectID, TokenSource: ts, JSON: jsonData, universeDomain: universeDomain, }, nil } // CredentialsFromJSON invokes CredentialsFromJSONWithParams with the specified scopes. func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) { var params CredentialsParams params.Scopes = scopes return CredentialsFromJSONWithParams(ctx, jsonData, params) } func wellKnownFile() string { const f = "application_default_credentials.json" if runtime.GOOS == "windows" { return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) } return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) } func readCredentialsFile(ctx context.Context, filename string, params CredentialsParams) (*Credentials, error) { b, err := os.ReadFile(filename) if err != nil { return nil, err } return CredentialsFromJSONWithParams(ctx, b, params) }