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 func CredentialsFromJSONWithParams(ctx context.Context, jsonData []byte, params CredentialsParams) (*Credentials, error) { 255 // Make defensive copy of the slices in params. 256 params = params.deepCopy() 257 258 // First, attempt to parse jsonData as a Google Developers Console client_credentials.json. 259 config, _ := ConfigFromJSON(jsonData, params.Scopes...) 260 if config != nil { 261 return &Credentials{ 262 ProjectID: "", 263 TokenSource: authhandler.TokenSourceWithPKCE(ctx, config, params.State, params.AuthHandler, params.PKCE), 264 JSON: jsonData, 265 }, nil 266 } 267 268 // Otherwise, parse jsonData as one of the other supported credentials files. 269 var f credentialsFile 270 if err := json.Unmarshal(jsonData, &f); err != nil { 271 return nil, err 272 } 273 274 universeDomain := f.UniverseDomain 275 if params.UniverseDomain != "" { 276 universeDomain = params.UniverseDomain 277 } 278 // Authorized user credentials are only supported in the googleapis.com universe. 279 if f.Type == userCredentialsKey { 280 universeDomain = defaultUniverseDomain 281 } 282 283 ts, err := f.tokenSource(ctx, params) 284 if err != nil { 285 return nil, err 286 } 287 ts = newErrWrappingTokenSource(ts) 288 return &Credentials{ 289 ProjectID: f.ProjectID, 290 TokenSource: ts, 291 JSON: jsonData, 292 universeDomain: universeDomain, 293 }, nil 294 } 295 296 // CredentialsFromJSON invokes CredentialsFromJSONWithParams with the specified scopes. 297 func CredentialsFromJSON(ctx context.Context, jsonData []byte, scopes ...string) (*Credentials, error) { 298 var params CredentialsParams 299 params.Scopes = scopes 300 return CredentialsFromJSONWithParams(ctx, jsonData, params) 301 } 302 303 func wellKnownFile() string { 304 const f = "application_default_credentials.json" 305 if runtime.GOOS == "windows" { 306 return filepath.Join(os.Getenv("APPDATA"), "gcloud", f) 307 } 308 return filepath.Join(guessUnixHomeDir(), ".config", "gcloud", f) 309 } 310 311 func readCredentialsFile(ctx context.Context, filename string, params CredentialsParams) (*Credentials, error) { 312 b, err := os.ReadFile(filename) 313 if err != nil { 314 return nil, err 315 } 316 return CredentialsFromJSONWithParams(ctx, b, params) 317 } 318