| // Copyright 2023 Google LLC |
| // |
| // Licensed under the Apache License, Version 2.0 (the "License"); |
| // you may not use this file except in compliance with the License. |
| // You may obtain a copy of the License at |
| // |
| // http://www.apache.org/licenses/LICENSE-2.0 |
| // |
| // Unless required by applicable law or agreed to in writing, software |
| // distributed under the License is distributed on an "AS IS" BASIS, |
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| // See the License for the specific language governing permissions and |
| // limitations under the License. |
| |
| package credentials |
| |
| import ( |
| "errors" |
| "fmt" |
| |
| "cloud.google.com/go/auth" |
| "cloud.google.com/go/auth/credentials/internal/externalaccount" |
| "cloud.google.com/go/auth/credentials/internal/externalaccountuser" |
| "cloud.google.com/go/auth/credentials/internal/gdch" |
| "cloud.google.com/go/auth/credentials/internal/impersonate" |
| internalauth "cloud.google.com/go/auth/internal" |
| "cloud.google.com/go/auth/internal/credsfile" |
| ) |
| |
| func fileCredentials(b []byte, opts *DetectOptions) (*auth.Credentials, error) { |
| fileType, err := credsfile.ParseFileType(b) |
| if err != nil { |
| return nil, err |
| } |
| |
| var projectID, quotaProjectID, universeDomain string |
| var tp auth.TokenProvider |
| switch fileType { |
| case credsfile.ServiceAccountKey: |
| f, err := credsfile.ParseServiceAccount(b) |
| if err != nil { |
| return nil, err |
| } |
| tp, err = handleServiceAccount(f, opts) |
| if err != nil { |
| return nil, err |
| } |
| projectID = f.ProjectID |
| universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain) |
| case credsfile.UserCredentialsKey: |
| f, err := credsfile.ParseUserCredentials(b) |
| if err != nil { |
| return nil, err |
| } |
| tp, err = handleUserCredential(f, opts) |
| if err != nil { |
| return nil, err |
| } |
| quotaProjectID = f.QuotaProjectID |
| universeDomain = f.UniverseDomain |
| case credsfile.ExternalAccountKey: |
| f, err := credsfile.ParseExternalAccount(b) |
| if err != nil { |
| return nil, err |
| } |
| tp, err = handleExternalAccount(f, opts) |
| if err != nil { |
| return nil, err |
| } |
| quotaProjectID = f.QuotaProjectID |
| universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain) |
| case credsfile.ExternalAccountAuthorizedUserKey: |
| f, err := credsfile.ParseExternalAccountAuthorizedUser(b) |
| if err != nil { |
| return nil, err |
| } |
| tp, err = handleExternalAccountAuthorizedUser(f, opts) |
| if err != nil { |
| return nil, err |
| } |
| quotaProjectID = f.QuotaProjectID |
| universeDomain = f.UniverseDomain |
| case credsfile.ImpersonatedServiceAccountKey: |
| f, err := credsfile.ParseImpersonatedServiceAccount(b) |
| if err != nil { |
| return nil, err |
| } |
| tp, err = handleImpersonatedServiceAccount(f, opts) |
| if err != nil { |
| return nil, err |
| } |
| universeDomain = resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain) |
| case credsfile.GDCHServiceAccountKey: |
| f, err := credsfile.ParseGDCHServiceAccount(b) |
| if err != nil { |
| return nil, err |
| } |
| tp, err = handleGDCHServiceAccount(f, opts) |
| if err != nil { |
| return nil, err |
| } |
| projectID = f.Project |
| universeDomain = f.UniverseDomain |
| default: |
| return nil, fmt.Errorf("credentials: unsupported filetype %q", fileType) |
| } |
| return auth.NewCredentials(&auth.CredentialsOptions{ |
| TokenProvider: auth.NewCachedTokenProvider(tp, &auth.CachedTokenProviderOptions{ |
| ExpireEarly: opts.EarlyTokenRefresh, |
| }), |
| JSON: b, |
| ProjectIDProvider: internalauth.StaticCredentialsProperty(projectID), |
| QuotaProjectIDProvider: internalauth.StaticCredentialsProperty(quotaProjectID), |
| UniverseDomainProvider: internalauth.StaticCredentialsProperty(universeDomain), |
| }), nil |
| } |
| |
| // resolveUniverseDomain returns optsUniverseDomain if non-empty, in order to |
| // support configuring universe-specific credentials in code. Auth flows |
| // unsupported for universe domain should not use this func, but should instead |
| // simply set the file universe domain on the credentials. |
| func resolveUniverseDomain(optsUniverseDomain, fileUniverseDomain string) string { |
| if optsUniverseDomain != "" { |
| return optsUniverseDomain |
| } |
| return fileUniverseDomain |
| } |
| |
| func handleServiceAccount(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) { |
| if opts.UseSelfSignedJWT { |
| return configureSelfSignedJWT(f, opts) |
| } |
| opts2LO := &auth.Options2LO{ |
| Email: f.ClientEmail, |
| PrivateKey: []byte(f.PrivateKey), |
| PrivateKeyID: f.PrivateKeyID, |
| Scopes: opts.scopes(), |
| TokenURL: f.TokenURL, |
| Subject: opts.Subject, |
| Client: opts.client(), |
| } |
| if opts2LO.TokenURL == "" { |
| opts2LO.TokenURL = jwtTokenURL |
| } |
| return auth.New2LOTokenProvider(opts2LO) |
| } |
| |
| func handleUserCredential(f *credsfile.UserCredentialsFile, opts *DetectOptions) (auth.TokenProvider, error) { |
| opts3LO := &auth.Options3LO{ |
| ClientID: f.ClientID, |
| ClientSecret: f.ClientSecret, |
| Scopes: opts.scopes(), |
| AuthURL: googleAuthURL, |
| TokenURL: opts.tokenURL(), |
| AuthStyle: auth.StyleInParams, |
| EarlyTokenExpiry: opts.EarlyTokenRefresh, |
| RefreshToken: f.RefreshToken, |
| Client: opts.client(), |
| } |
| return auth.New3LOTokenProvider(opts3LO) |
| } |
| |
| func handleExternalAccount(f *credsfile.ExternalAccountFile, opts *DetectOptions) (auth.TokenProvider, error) { |
| externalOpts := &externalaccount.Options{ |
| Audience: f.Audience, |
| SubjectTokenType: f.SubjectTokenType, |
| TokenURL: f.TokenURL, |
| TokenInfoURL: f.TokenInfoURL, |
| ServiceAccountImpersonationURL: f.ServiceAccountImpersonationURL, |
| ClientSecret: f.ClientSecret, |
| ClientID: f.ClientID, |
| CredentialSource: f.CredentialSource, |
| QuotaProjectID: f.QuotaProjectID, |
| Scopes: opts.scopes(), |
| WorkforcePoolUserProject: f.WorkforcePoolUserProject, |
| Client: opts.client(), |
| } |
| if f.ServiceAccountImpersonation != nil { |
| externalOpts.ServiceAccountImpersonationLifetimeSeconds = f.ServiceAccountImpersonation.TokenLifetimeSeconds |
| } |
| return externalaccount.NewTokenProvider(externalOpts) |
| } |
| |
| func handleExternalAccountAuthorizedUser(f *credsfile.ExternalAccountAuthorizedUserFile, opts *DetectOptions) (auth.TokenProvider, error) { |
| externalOpts := &externalaccountuser.Options{ |
| Audience: f.Audience, |
| RefreshToken: f.RefreshToken, |
| TokenURL: f.TokenURL, |
| TokenInfoURL: f.TokenInfoURL, |
| ClientID: f.ClientID, |
| ClientSecret: f.ClientSecret, |
| Scopes: opts.scopes(), |
| Client: opts.client(), |
| } |
| return externalaccountuser.NewTokenProvider(externalOpts) |
| } |
| |
| func handleImpersonatedServiceAccount(f *credsfile.ImpersonatedServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) { |
| if f.ServiceAccountImpersonationURL == "" || f.CredSource == nil { |
| return nil, errors.New("missing 'source_credentials' field or 'service_account_impersonation_url' in credentials") |
| } |
| |
| tp, err := fileCredentials(f.CredSource, opts) |
| if err != nil { |
| return nil, err |
| } |
| return impersonate.NewTokenProvider(&impersonate.Options{ |
| URL: f.ServiceAccountImpersonationURL, |
| Scopes: opts.scopes(), |
| Tp: tp, |
| Delegates: f.Delegates, |
| Client: opts.client(), |
| }) |
| } |
| |
| func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) { |
| return gdch.NewTokenProvider(f, &gdch.Options{ |
| STSAudience: opts.STSAudience, |
| Client: opts.client(), |
| }) |
| } |