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 "log/slog"
23 "net/http"
24 "os"
25 "time"
26 27 "cloud.google.com/go/auth"
28 "cloud.google.com/go/auth/internal"
29 "cloud.google.com/go/auth/internal/credsfile"
30 "cloud.google.com/go/auth/internal/trustboundary"
31 "cloud.google.com/go/compute/metadata"
32 "github.com/googleapis/gax-go/v2/internallog"
33 )
34 35 const (
36 // jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
37 jwtTokenURL = "https://oauth2.googleapis.com/token"
38 39 // Google's OAuth 2.0 default endpoints.
40 googleAuthURL = "https://accounts.google.com/o/oauth2/auth"
41 googleTokenURL = "https://oauth2.googleapis.com/token"
42 43 // GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
44 GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
45 46 // Help on default credentials
47 adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
48 )
49 50 var (
51 // for testing
52 allowOnGCECheck = true
53 )
54 55 // CredType specifies the type of JSON credentials being provided
56 // to a loading function such as [NewCredentialsFromFile] or
57 // [NewCredentialsFromJSON].
58 type CredType string
59 60 const (
61 // ServiceAccount represents a service account file type.
62 ServiceAccount CredType = "service_account"
63 // AuthorizedUser represents a user credentials file type.
64 AuthorizedUser CredType = "authorized_user"
65 // ExternalAccount represents an external account file type.
66 //
67 // IMPORTANT:
68 // This credential type does not validate the credential configuration. A security
69 // risk occurs when a credential configuration configured with malicious urls
70 // is used.
71 // You should validate credential configurations provided by untrusted sources.
72 // See [Security requirements when using credential configurations from an external
73 // source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
74 // for more details.
75 ExternalAccount CredType = "external_account"
76 // ImpersonatedServiceAccount represents an impersonated service account file type.
77 //
78 // IMPORTANT:
79 // This credential type does not validate the credential configuration. A security
80 // risk occurs when a credential configuration configured with malicious urls
81 // is used.
82 // You should validate credential configurations provided by untrusted sources.
83 // See [Security requirements when using credential configurations from an external
84 // source] https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
85 // for more details.
86 ImpersonatedServiceAccount CredType = "impersonated_service_account"
87 // GDCHServiceAccount represents a GDCH service account credentials.
88 GDCHServiceAccount CredType = "gdch_service_account"
89 // ExternalAccountAuthorizedUser represents an external account authorized user credentials.
90 ExternalAccountAuthorizedUser CredType = "external_account_authorized_user"
91 )
92 93 // TokenBindingType specifies the type of binding used when requesting a token
94 // whether to request a hard-bound token using mTLS or an instance identity
95 // bound token using ALTS.
96 type TokenBindingType int
97 98 const (
99 // NoBinding specifies that requested tokens are not required to have a
100 // binding. This is the default option.
101 NoBinding TokenBindingType = iota
102 // MTLSHardBinding specifies that a hard-bound token should be requested
103 // using an mTLS with S2A channel.
104 MTLSHardBinding
105 // ALTSHardBinding specifies that an instance identity bound token should
106 // be requested using an ALTS channel.
107 ALTSHardBinding
108 )
109 110 // OnGCE reports whether this process is running in Google Cloud.
111 func OnGCE() bool {
112 // TODO(codyoss): once all libs use this auth lib move metadata check here
113 return allowOnGCECheck && metadata.OnGCE()
114 }
115 116 // DetectDefault searches for "Application Default Credentials" and returns
117 // a credential based on the [DetectOptions] provided.
118 //
119 // It looks for credentials in the following places, preferring the first
120 // location found:
121 //
122 // - A JSON file whose path is specified by the GOOGLE_APPLICATION_CREDENTIALS
123 // environment variable. For workload identity federation, refer to
124 // https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation
125 // on how to generate the JSON configuration file for on-prem/non-Google
126 // cloud platforms.
127 // - A JSON file in a location known to the gcloud command-line tool. On
128 // Windows, this is %APPDATA%/gcloud/application_default_credentials.json. On
129 // other systems, $HOME/.config/gcloud/application_default_credentials.json.
130 // - On Google Compute Engine, Google App Engine standard second generation
131 // runtimes, and Google App Engine flexible environment, it fetches
132 // credentials from the metadata server.
133 //
134 // Important: If you accept a credential configuration (credential
135 // JSON/File/Stream) from an external source for authentication to Google
136 // Cloud Platform, you must validate it before providing it to any Google
137 // API or library. Providing an unvalidated credential configuration to
138 // Google APIs can compromise the security of your systems and data. For
139 // more information, refer to [Validate credential configurations from
140 // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
141 func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
142 if err := opts.validate(); err != nil {
143 return nil, err
144 }
145 trustBoundaryEnabled, err := trustboundary.IsEnabled()
146 if err != nil {
147 return nil, err
148 }
149 if len(opts.CredentialsJSON) > 0 {
150 return readCredentialsFileJSON(opts.CredentialsJSON, opts)
151 }
152 if opts.CredentialsFile != "" {
153 return readCredentialsFile(opts.CredentialsFile, opts)
154 }
155 if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
156 creds, err := readCredentialsFile(filename, opts)
157 if err != nil {
158 return nil, err
159 }
160 return creds, nil
161 }
162 163 fileName := credsfile.GetWellKnownFileName()
164 if b, err := os.ReadFile(fileName); err == nil {
165 return readCredentialsFileJSON(b, opts)
166 }
167 168 if OnGCE() {
169 metadataClient := metadata.NewWithOptions(&metadata.Options{
170 Logger: opts.logger(),
171 UseDefaultClient: true,
172 })
173 gceUniverseDomainProvider := &internal.ComputeUniverseDomainProvider{
174 MetadataClient: metadataClient,
175 }
176 177 tp := computeTokenProvider(opts, metadataClient)
178 if trustBoundaryEnabled {
179 gceConfigProvider := trustboundary.NewGCEConfigProvider(gceUniverseDomainProvider)
180 var err error
181 tp, err = trustboundary.NewProvider(opts.client(), gceConfigProvider, opts.logger(), tp)
182 if err != nil {
183 return nil, fmt.Errorf("credentials: failed to initialize GCE trust boundary provider: %w", err)
184 }
185 186 }
187 return auth.NewCredentials(&auth.CredentialsOptions{
188 TokenProvider: tp,
189 ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
190 return metadataClient.ProjectIDWithContext(ctx)
191 }),
192 UniverseDomainProvider: gceUniverseDomainProvider,
193 }), nil
194 }
195 196 return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
197 }
198 199 // DetectOptions provides configuration for [DetectDefault].
200 type DetectOptions struct {
201 // Scopes that credentials tokens should have. Example:
202 // https://www.googleapis.com/auth/cloud-platform. Required if Audience is
203 // not provided.
204 Scopes []string
205 // TokenBindingType specifies the type of binding used when requesting a
206 // token whether to request a hard-bound token using mTLS or an instance
207 // identity bound token using ALTS. Optional.
208 TokenBindingType TokenBindingType
209 // Audience that credentials tokens should have. Only applicable for 2LO
210 // flows with service accounts. If specified, scopes should not be provided.
211 Audience string
212 // Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
213 // Optional.
214 Subject string
215 // EarlyTokenRefresh configures how early before a token expires that it
216 // should be refreshed. Once the token’s time until expiration has entered
217 // this refresh window the token is considered valid but stale. If unset,
218 // the default value is 3 minutes and 45 seconds. Optional.
219 EarlyTokenRefresh time.Duration
220 // DisableAsyncRefresh configures a synchronous workflow that refreshes
221 // stale tokens while blocking. The default is false. Optional.
222 DisableAsyncRefresh bool
223 // AuthHandlerOptions configures an authorization handler and other options
224 // for 3LO flows. It is required, and only used, for client credential
225 // flows.
226 AuthHandlerOptions *auth.AuthorizationHandlerOptions
227 // TokenURL allows to set the token endpoint for user credential flows. If
228 // unset the default value is: https://oauth2.googleapis.com/token.
229 // Optional.
230 TokenURL string
231 // STSAudience is the audience sent to when retrieving an STS token.
232 // Currently this only used for GDCH auth flow, for which it is required.
233 STSAudience string
234 // CredentialsFile overrides detection logic and sources a credential file
235 // from the provided filepath. If provided, CredentialsJSON must not be.
236 // Optional.
237 //
238 // Deprecated: This field is deprecated because of a potential security risk.
239 // It does not validate the credential configuration. The security risk occurs
240 // when a credential configuration is accepted from a source that is not
241 // under your control and used without validation on your side.
242 //
243 // If you know that you will be loading credential configurations of a
244 // specific type, it is recommended to use a credential-type-specific
245 // NewCredentialsFromFile method. This will ensure that an unexpected
246 // credential type with potential for malicious intent is not loaded
247 // unintentionally. You might still have to do validation for certain
248 // credential types. Please follow the recommendation for that method. For
249 // example, if you want to load only service accounts, you can use
250 //
251 // creds, err := credentials.NewCredentialsFromFile(ctx, credentials.ServiceAccount, filename, opts)
252 //
253 // If you are loading your credential configuration from an untrusted source
254 // and have not mitigated the risks (e.g. by validating the configuration
255 // yourself), make these changes as soon as possible to prevent security
256 // risks to your environment.
257 //
258 // Regardless of the method used, it is always your responsibility to
259 // validate configurations received from external sources.
260 //
261 // For more details see:
262 // https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
263 CredentialsFile string
264 // CredentialsJSON overrides detection logic and uses the JSON bytes as the
265 // source for the credential. If provided, CredentialsFile must not be.
266 // Optional.
267 //
268 // Deprecated: This field is deprecated because of a potential security risk.
269 // It does not validate the credential configuration. The security risk occurs
270 // when a credential configuration is accepted from a source that is not
271 // under your control and used without validation on your side.
272 //
273 // If you know that you will be loading credential configurations of a
274 // specific type, it is recommended to use a credential-type-specific
275 // NewCredentialsFromJSON method. This will ensure that an unexpected
276 // credential type with potential for malicious intent is not loaded
277 // unintentionally. You might still have to do validation for certain
278 // credential types. Please follow the recommendation for that method. For
279 // example, if you want to load only service accounts, you can use
280 //
281 // creds, err := credentials.NewCredentialsFromJSON(ctx, credentials.ServiceAccount, json, opts)
282 //
283 // If you are loading your credential configuration from an untrusted source
284 // and have not mitigated the risks (e.g. by validating the configuration
285 // yourself), make these changes as soon as possible to prevent security
286 // risks to your environment.
287 //
288 // Regardless of the method used, it is always your responsibility to
289 // validate configurations received from external sources.
290 //
291 // For more details see:
292 // https://cloud.google.com/docs/authentication/external/externally-sourced-credentials
293 CredentialsJSON []byte
294 // UseSelfSignedJWT directs service account based credentials to create a
295 // self-signed JWT with the private key found in the file, skipping any
296 // network requests that would normally be made. Optional.
297 UseSelfSignedJWT bool
298 // Client configures the underlying client used to make network requests
299 // when fetching tokens. Optional.
300 Client *http.Client
301 // UniverseDomain is the default service domain for a given Cloud universe.
302 // The default value is "googleapis.com". This option is ignored for
303 // authentication flows that do not support universe domain. Optional.
304 UniverseDomain string
305 // Logger is used for debug logging. If provided, logging will be enabled
306 // at the loggers configured level. By default logging is disabled unless
307 // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
308 // logger will be used. Optional.
309 Logger *slog.Logger
310 }
311 312 // NewCredentialsFromFile creates a [cloud.google.com/go/auth.Credentials] from
313 // the provided file. The credType argument specifies the expected credential
314 // type. If the file content does not match the expected type, an error is
315 // returned.
316 //
317 // Important: If you accept a credential configuration (credential
318 // JSON/File/Stream) from an external source for authentication to Google
319 // Cloud Platform, you must validate it before providing it to any Google
320 // API or library. Providing an unvalidated credential configuration to
321 // Google APIs can compromise the security of your systems and data. For
322 // more information, refer to [Validate credential configurations from
323 // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
324 func NewCredentialsFromFile(credType CredType, filename string, opts *DetectOptions) (*auth.Credentials, error) {
325 b, err := os.ReadFile(filename)
326 if err != nil {
327 return nil, err
328 }
329 return NewCredentialsFromJSON(credType, b, opts)
330 }
331 332 // NewCredentialsFromJSON creates a [cloud.google.com/go/auth.Credentials] from
333 // the provided JSON bytes. The credType argument specifies the expected
334 // credential type. If the JSON does not match the expected type, an error is
335 // returned.
336 //
337 // Important: If you accept a credential configuration (credential
338 // JSON/File/Stream) from an external source for authentication to Google
339 // Cloud Platform, you must validate it before providing it to any Google
340 // API or library. Providing an unvalidated credential configuration to
341 // Google APIs can compromise the security of your systems and data. For
342 // more information, refer to [Validate credential configurations from
343 // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
344 func NewCredentialsFromJSON(credType CredType, b []byte, opts *DetectOptions) (*auth.Credentials, error) {
345 if err := checkCredentialType(b, credType); err != nil {
346 return nil, err
347 }
348 // We can't use readCredentialsFileJSON because it does auto-detection
349 // for client_credentials.json which we don't support here (no type field).
350 // Instead, we call fileCredentials just as readCredentialsFileJSON does
351 // when it doesn't detect client_credentials.json.
352 return fileCredentials(b, opts)
353 }
354 355 func checkCredentialType(b []byte, expected CredType) error {
356 357 fileType, err := credsfile.ParseFileType(b)
358 if err != nil {
359 return err
360 }
361 if CredType(fileType) != expected {
362 return fmt.Errorf("credentials: expected type %q, found %q", expected, fileType)
363 }
364 return nil
365 }
366 367 func (o *DetectOptions) validate() error {
368 if o == nil {
369 return errors.New("credentials: options must be provided")
370 }
371 if len(o.Scopes) > 0 && o.Audience != "" {
372 return errors.New("credentials: both scopes and audience were provided")
373 }
374 if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
375 return errors.New("credentials: both credentials file and JSON were provided")
376 }
377 return nil
378 }
379 380 func (o *DetectOptions) tokenURL() string {
381 if o.TokenURL != "" {
382 return o.TokenURL
383 }
384 return googleTokenURL
385 }
386 387 func (o *DetectOptions) scopes() []string {
388 scopes := make([]string, len(o.Scopes))
389 copy(scopes, o.Scopes)
390 return scopes
391 }
392 393 func (o *DetectOptions) client() *http.Client {
394 if o.Client != nil {
395 return o.Client
396 }
397 return internal.DefaultClient()
398 }
399 400 func (o *DetectOptions) logger() *slog.Logger {
401 return internallog.New(o.Logger)
402 }
403 404 func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
405 b, err := os.ReadFile(filename)
406 if err != nil {
407 return nil, err
408 }
409 return readCredentialsFileJSON(b, opts)
410 }
411 412 func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
413 // attempt to parse jsonData as a Google Developers Console client_credentials.json.
414 config := clientCredConfigFromJSON(b, opts)
415 if config != nil {
416 if config.AuthHandlerOpts == nil {
417 return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
418 }
419 tp, err := auth.New3LOTokenProvider(config)
420 if err != nil {
421 return nil, err
422 }
423 return auth.NewCredentials(&auth.CredentialsOptions{
424 TokenProvider: tp,
425 JSON: b,
426 }), nil
427 }
428 return fileCredentials(b, opts)
429 }
430 431 func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
432 var creds credsfile.ClientCredentialsFile
433 var c *credsfile.Config3LO
434 if err := json.Unmarshal(b, &creds); err != nil {
435 return nil
436 }
437 switch {
438 case creds.Web != nil:
439 c = creds.Web
440 case creds.Installed != nil:
441 c = creds.Installed
442 default:
443 return nil
444 }
445 if len(c.RedirectURIs) < 1 {
446 return nil
447 }
448 var handleOpts *auth.AuthorizationHandlerOptions
449 if opts.AuthHandlerOptions != nil {
450 handleOpts = &auth.AuthorizationHandlerOptions{
451 Handler: opts.AuthHandlerOptions.Handler,
452 State: opts.AuthHandlerOptions.State,
453 PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
454 }
455 }
456 return &auth.Options3LO{
457 ClientID: c.ClientID,
458 ClientSecret: c.ClientSecret,
459 RedirectURL: c.RedirectURIs[0],
460 Scopes: opts.scopes(),
461 AuthURL: c.AuthURI,
462 TokenURL: c.TokenURI,
463 Client: opts.client(),
464 Logger: opts.logger(),
465 EarlyTokenExpiry: opts.EarlyTokenRefresh,
466 AuthHandlerOpts: handleOpts,
467 // TODO(codyoss): refactor this out. We need to add in auto-detection
468 // for this use case.
469 AuthStyle: auth.StyleInParams,
470 }
471 }
472