summaryrefslogtreecommitdiff
path: root/vendor/cloud.google.com/go/auth/credentials
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/cloud.google.com/go/auth/credentials')
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/compute.go102
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/detect.go317
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/doc.go45
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/filetypes.go233
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go531
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go284
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go431
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go78
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go74
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go30
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go93
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go220
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go115
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go191
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go105
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go156
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go167
-rw-r--r--vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go89
18 files changed, 3261 insertions, 0 deletions
diff --git a/vendor/cloud.google.com/go/auth/credentials/compute.go b/vendor/cloud.google.com/go/auth/credentials/compute.go
new file mode 100644
index 0000000..e4a8078
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/compute.go
@@ -0,0 +1,102 @@
+// 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 (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/url"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/compute/metadata"
+)
+
+var (
+ computeTokenMetadata = map[string]interface{}{
+ "auth.google.tokenSource": "compute-metadata",
+ "auth.google.serviceAccount": "default",
+ }
+ computeTokenURI = "instance/service-accounts/default/token"
+)
+
+// computeTokenProvider creates a [cloud.google.com/go/auth.TokenProvider] that
+// uses the metadata service to retrieve tokens.
+func computeTokenProvider(opts *DetectOptions, client *metadata.Client) auth.TokenProvider {
+ return auth.NewCachedTokenProvider(&computeProvider{
+ scopes: opts.Scopes,
+ client: client,
+ tokenBindingType: opts.TokenBindingType,
+ }, &auth.CachedTokenProviderOptions{
+ ExpireEarly: opts.EarlyTokenRefresh,
+ DisableAsyncRefresh: opts.DisableAsyncRefresh,
+ })
+}
+
+// computeProvider fetches tokens from the google cloud metadata service.
+type computeProvider struct {
+ scopes []string
+ client *metadata.Client
+ tokenBindingType TokenBindingType
+}
+
+type metadataTokenResp struct {
+ AccessToken string `json:"access_token"`
+ ExpiresInSec int `json:"expires_in"`
+ TokenType string `json:"token_type"`
+}
+
+func (cs *computeProvider) Token(ctx context.Context) (*auth.Token, error) {
+ tokenURI, err := url.Parse(computeTokenURI)
+ if err != nil {
+ return nil, err
+ }
+ hasScopes := len(cs.scopes) > 0
+ if hasScopes || cs.tokenBindingType != NoBinding {
+ v := url.Values{}
+ if hasScopes {
+ v.Set("scopes", strings.Join(cs.scopes, ","))
+ }
+ switch cs.tokenBindingType {
+ case MTLSHardBinding:
+ v.Set("transport", "mtls")
+ v.Set("binding-enforcement", "on")
+ case ALTSHardBinding:
+ v.Set("transport", "alts")
+ }
+ tokenURI.RawQuery = v.Encode()
+ }
+ tokenJSON, err := cs.client.GetWithContext(ctx, tokenURI.String())
+ if err != nil {
+ return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
+ }
+ var res metadataTokenResp
+ if err := json.NewDecoder(strings.NewReader(tokenJSON)).Decode(&res); err != nil {
+ return nil, fmt.Errorf("credentials: invalid token JSON from metadata: %w", err)
+ }
+ if res.ExpiresInSec == 0 || res.AccessToken == "" {
+ return nil, errors.New("credentials: incomplete token received from metadata")
+ }
+ return &auth.Token{
+ Value: res.AccessToken,
+ Type: res.TokenType,
+ Expiry: time.Now().Add(time.Duration(res.ExpiresInSec) * time.Second),
+ Metadata: computeTokenMetadata,
+ }, nil
+
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/detect.go b/vendor/cloud.google.com/go/auth/credentials/detect.go
new file mode 100644
index 0000000..ad3267e
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/detect.go
@@ -0,0 +1,317 @@
+// 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 (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "os"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/internal"
+ "cloud.google.com/go/auth/internal/credsfile"
+ "cloud.google.com/go/compute/metadata"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+const (
+ // jwtTokenURL is Google's OAuth 2.0 token URL to use with the JWT(2LO) flow.
+ jwtTokenURL = "https://oauth2.googleapis.com/token"
+
+ // Google's OAuth 2.0 default endpoints.
+ googleAuthURL = "https://accounts.google.com/o/oauth2/auth"
+ googleTokenURL = "https://oauth2.googleapis.com/token"
+
+ // GoogleMTLSTokenURL is Google's default OAuth2.0 mTLS endpoint.
+ GoogleMTLSTokenURL = "https://oauth2.mtls.googleapis.com/token"
+
+ // Help on default credentials
+ adcSetupURL = "https://cloud.google.com/docs/authentication/external/set-up-adc"
+)
+
+var (
+ // for testing
+ allowOnGCECheck = true
+)
+
+// TokenBindingType specifies the type of binding used when requesting a token
+// whether to request a hard-bound token using mTLS or an instance identity
+// bound token using ALTS.
+type TokenBindingType int
+
+const (
+ // NoBinding specifies that requested tokens are not required to have a
+ // binding. This is the default option.
+ NoBinding TokenBindingType = iota
+ // MTLSHardBinding specifies that a hard-bound token should be requested
+ // using an mTLS with S2A channel.
+ MTLSHardBinding
+ // ALTSHardBinding specifies that an instance identity bound token should
+ // be requested using an ALTS channel.
+ ALTSHardBinding
+)
+
+// OnGCE reports whether this process is running in Google Cloud.
+func OnGCE() bool {
+ // TODO(codyoss): once all libs use this auth lib move metadata check here
+ return allowOnGCECheck && metadata.OnGCE()
+}
+
+// DetectDefault searches for "Application Default Credentials" and returns
+// a credential based on the [DetectOptions] provided.
+//
+// It looks for credentials in the following places, preferring the first
+// location found:
+//
+// - 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.
+// - 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.
+// - On Google Compute Engine, Google App Engine standard second generation
+// runtimes, and Google App Engine flexible environment, it fetches
+// credentials from the metadata server.
+func DetectDefault(opts *DetectOptions) (*auth.Credentials, error) {
+ if err := opts.validate(); err != nil {
+ return nil, err
+ }
+ if len(opts.CredentialsJSON) > 0 {
+ return readCredentialsFileJSON(opts.CredentialsJSON, opts)
+ }
+ if opts.CredentialsFile != "" {
+ return readCredentialsFile(opts.CredentialsFile, opts)
+ }
+ if filename := os.Getenv(credsfile.GoogleAppCredsEnvVar); filename != "" {
+ creds, err := readCredentialsFile(filename, opts)
+ if err != nil {
+ return nil, err
+ }
+ return creds, nil
+ }
+
+ fileName := credsfile.GetWellKnownFileName()
+ if b, err := os.ReadFile(fileName); err == nil {
+ return readCredentialsFileJSON(b, opts)
+ }
+
+ if OnGCE() {
+ metadataClient := metadata.NewWithOptions(&metadata.Options{
+ Logger: opts.logger(),
+ UseDefaultClient: true,
+ })
+ return auth.NewCredentials(&auth.CredentialsOptions{
+ TokenProvider: computeTokenProvider(opts, metadataClient),
+ ProjectIDProvider: auth.CredentialsPropertyFunc(func(ctx context.Context) (string, error) {
+ return metadataClient.ProjectIDWithContext(ctx)
+ }),
+ UniverseDomainProvider: &internal.ComputeUniverseDomainProvider{
+ MetadataClient: metadataClient,
+ },
+ }), nil
+ }
+
+ return nil, fmt.Errorf("credentials: could not find default credentials. See %v for more information", adcSetupURL)
+}
+
+// DetectOptions provides configuration for [DetectDefault].
+type DetectOptions struct {
+ // Scopes that credentials tokens should have. Example:
+ // https://www.googleapis.com/auth/cloud-platform. Required if Audience is
+ // not provided.
+ Scopes []string
+ // TokenBindingType specifies the type of binding used when requesting a
+ // token whether to request a hard-bound token using mTLS or an instance
+ // identity bound token using ALTS. Optional.
+ TokenBindingType TokenBindingType
+ // Audience that credentials tokens should have. Only applicable for 2LO
+ // flows with service accounts. If specified, scopes should not be provided.
+ Audience string
+ // Subject is the user email used for [domain wide delegation](https://developers.google.com/identity/protocols/oauth2/service-account#delegatingauthority).
+ // Optional.
+ Subject string
+ // EarlyTokenRefresh configures how early before a token expires that it
+ // should be refreshed. Once the token’s time until expiration has entered
+ // this refresh window the token is considered valid but stale. If unset,
+ // the default value is 3 minutes and 45 seconds. Optional.
+ EarlyTokenRefresh time.Duration
+ // DisableAsyncRefresh configures a synchronous workflow that refreshes
+ // stale tokens while blocking. The default is false. Optional.
+ DisableAsyncRefresh bool
+ // AuthHandlerOptions configures an authorization handler and other options
+ // for 3LO flows. It is required, and only used, for client credential
+ // flows.
+ AuthHandlerOptions *auth.AuthorizationHandlerOptions
+ // TokenURL allows to set the token endpoint for user credential flows. If
+ // unset the default value is: https://oauth2.googleapis.com/token.
+ // Optional.
+ TokenURL string
+ // STSAudience is the audience sent to when retrieving an STS token.
+ // Currently this only used for GDCH auth flow, for which it is required.
+ STSAudience string
+ // CredentialsFile overrides detection logic and sources a credential file
+ // from the provided filepath. If provided, CredentialsJSON must not be.
+ // Optional.
+ //
+ // Important: If you accept a credential configuration (credential
+ // JSON/File/Stream) from an external source for authentication to Google
+ // Cloud Platform, you must validate it before providing it to any Google
+ // API or library. Providing an unvalidated credential configuration to
+ // Google APIs can compromise the security of your systems and data. For
+ // more information, refer to [Validate credential configurations from
+ // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
+ CredentialsFile string
+ // CredentialsJSON overrides detection logic and uses the JSON bytes as the
+ // source for the credential. If provided, CredentialsFile must not be.
+ // Optional.
+ //
+ // Important: If you accept a credential configuration (credential
+ // JSON/File/Stream) from an external source for authentication to Google
+ // Cloud Platform, you must validate it before providing it to any Google
+ // API or library. Providing an unvalidated credential configuration to
+ // Google APIs can compromise the security of your systems and data. For
+ // more information, refer to [Validate credential configurations from
+ // external sources](https://cloud.google.com/docs/authentication/external/externally-sourced-credentials).
+ CredentialsJSON []byte
+ // UseSelfSignedJWT directs service account based credentials to create a
+ // self-signed JWT with the private key found in the file, skipping any
+ // network requests that would normally be made. Optional.
+ UseSelfSignedJWT bool
+ // Client configures the underlying client used to make network requests
+ // when fetching tokens. Optional.
+ Client *http.Client
+ // UniverseDomain is the default service domain for a given Cloud universe.
+ // The default value is "googleapis.com". This option is ignored for
+ // authentication flows that do not support universe domain. Optional.
+ UniverseDomain string
+ // Logger is used for debug logging. If provided, logging will be enabled
+ // at the loggers configured level. By default logging is disabled unless
+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
+ // logger will be used. Optional.
+ Logger *slog.Logger
+}
+
+func (o *DetectOptions) validate() error {
+ if o == nil {
+ return errors.New("credentials: options must be provided")
+ }
+ if len(o.Scopes) > 0 && o.Audience != "" {
+ return errors.New("credentials: both scopes and audience were provided")
+ }
+ if len(o.CredentialsJSON) > 0 && o.CredentialsFile != "" {
+ return errors.New("credentials: both credentials file and JSON were provided")
+ }
+ return nil
+}
+
+func (o *DetectOptions) tokenURL() string {
+ if o.TokenURL != "" {
+ return o.TokenURL
+ }
+ return googleTokenURL
+}
+
+func (o *DetectOptions) scopes() []string {
+ scopes := make([]string, len(o.Scopes))
+ copy(scopes, o.Scopes)
+ return scopes
+}
+
+func (o *DetectOptions) client() *http.Client {
+ if o.Client != nil {
+ return o.Client
+ }
+ return internal.DefaultClient()
+}
+
+func (o *DetectOptions) logger() *slog.Logger {
+ return internallog.New(o.Logger)
+}
+
+func readCredentialsFile(filename string, opts *DetectOptions) (*auth.Credentials, error) {
+ b, err := os.ReadFile(filename)
+ if err != nil {
+ return nil, err
+ }
+ return readCredentialsFileJSON(b, opts)
+}
+
+func readCredentialsFileJSON(b []byte, opts *DetectOptions) (*auth.Credentials, error) {
+ // attempt to parse jsonData as a Google Developers Console client_credentials.json.
+ config := clientCredConfigFromJSON(b, opts)
+ if config != nil {
+ if config.AuthHandlerOpts == nil {
+ return nil, errors.New("credentials: auth handler must be specified for this credential filetype")
+ }
+ tp, err := auth.New3LOTokenProvider(config)
+ if err != nil {
+ return nil, err
+ }
+ return auth.NewCredentials(&auth.CredentialsOptions{
+ TokenProvider: tp,
+ JSON: b,
+ }), nil
+ }
+ return fileCredentials(b, opts)
+}
+
+func clientCredConfigFromJSON(b []byte, opts *DetectOptions) *auth.Options3LO {
+ var creds credsfile.ClientCredentialsFile
+ var c *credsfile.Config3LO
+ if err := json.Unmarshal(b, &creds); err != nil {
+ return nil
+ }
+ switch {
+ case creds.Web != nil:
+ c = creds.Web
+ case creds.Installed != nil:
+ c = creds.Installed
+ default:
+ return nil
+ }
+ if len(c.RedirectURIs) < 1 {
+ return nil
+ }
+ var handleOpts *auth.AuthorizationHandlerOptions
+ if opts.AuthHandlerOptions != nil {
+ handleOpts = &auth.AuthorizationHandlerOptions{
+ Handler: opts.AuthHandlerOptions.Handler,
+ State: opts.AuthHandlerOptions.State,
+ PKCEOpts: opts.AuthHandlerOptions.PKCEOpts,
+ }
+ }
+ return &auth.Options3LO{
+ ClientID: c.ClientID,
+ ClientSecret: c.ClientSecret,
+ RedirectURL: c.RedirectURIs[0],
+ Scopes: opts.scopes(),
+ AuthURL: c.AuthURI,
+ TokenURL: c.TokenURI,
+ Client: opts.client(),
+ Logger: opts.logger(),
+ EarlyTokenExpiry: opts.EarlyTokenRefresh,
+ AuthHandlerOpts: handleOpts,
+ // TODO(codyoss): refactor this out. We need to add in auto-detection
+ // for this use case.
+ AuthStyle: auth.StyleInParams,
+ }
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/doc.go b/vendor/cloud.google.com/go/auth/credentials/doc.go
new file mode 100644
index 0000000..1dbb286
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/doc.go
@@ -0,0 +1,45 @@
+// 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 provides support for making OAuth2 authorized and
+// authenticated HTTP requests to Google APIs. It supports the Web server flow,
+// client-side credentials, service accounts, Google Compute Engine service
+// accounts, Google App Engine service accounts and workload identity federation
+// from non-Google cloud platforms.
+//
+// A brief overview of the package follows. For more information, please read
+// https://developers.google.com/accounts/docs/OAuth2
+// and
+// https://developers.google.com/accounts/docs/application-default-credentials.
+// For more information on using workload identity federation, refer to
+// https://cloud.google.com/iam/docs/how-to#using-workload-identity-federation.
+//
+// # Credentials
+//
+// The [cloud.google.com/go/auth.Credentials] type represents Google
+// credentials, including Application Default Credentials.
+//
+// Use [DetectDefault] to obtain Application Default Credentials.
+//
+// Application Default Credentials support workload identity federation to
+// access Google Cloud resources from non-Google Cloud platforms including Amazon
+// Web Services (AWS), Microsoft Azure or any identity provider that supports
+// OpenID Connect (OIDC). Workload identity federation is recommended for
+// non-Google Cloud environments as it avoids the need to download, manage, and
+// store service account private keys locally.
+//
+// # Workforce Identity Federation
+//
+// For more information on this feature see [cloud.google.com/go/auth/credentials/externalaccount].
+package credentials
diff --git a/vendor/cloud.google.com/go/auth/credentials/filetypes.go b/vendor/cloud.google.com/go/auth/credentials/filetypes.go
new file mode 100644
index 0000000..8605e52
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/filetypes.go
@@ -0,0 +1,233 @@
+// 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, universeDomain string
+ var tp auth.TokenProvider
+ switch fileType {
+ case credsfile.UnknownCredType:
+ return nil, errors.New("credentials: unsupported unidentified file type")
+ 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
+ }
+ 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
+ }
+ 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
+ }
+ 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),
+ // TODO(codyoss): only set quota project here if there was a user override
+ 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) {
+ ud := resolveUniverseDomain(opts.UniverseDomain, f.UniverseDomain)
+ if opts.UseSelfSignedJWT {
+ return configureSelfSignedJWT(f, opts)
+ } else if ud != "" && ud != internalauth.DefaultUniverseDomain {
+ // For non-GDU universe domains, token exchange is impossible and services
+ // must support self-signed JWTs.
+ opts.UseSelfSignedJWT = true
+ 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(),
+ Logger: opts.logger(),
+ }
+ 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(),
+ Logger: opts.logger(),
+ }
+ 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(),
+ Logger: opts.logger(),
+ IsDefaultClient: opts.Client == nil,
+ }
+ 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(),
+ Logger: opts.logger(),
+ }
+ 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(),
+ Logger: opts.logger(),
+ })
+}
+
+func handleGDCHServiceAccount(f *credsfile.GDCHServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+ return gdch.NewTokenProvider(f, &gdch.Options{
+ STSAudience: opts.STSAudience,
+ Client: opts.client(),
+ Logger: opts.logger(),
+ })
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
new file mode 100644
index 0000000..9ecd1f6
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/aws_provider.go
@@ -0,0 +1,531 @@
+// 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 externalaccount
+
+import (
+ "bytes"
+ "context"
+ "crypto/hmac"
+ "crypto/sha256"
+ "encoding/hex"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "os"
+ "path"
+ "sort"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth/internal"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+var (
+ // getenv aliases os.Getenv for testing
+ getenv = os.Getenv
+)
+
+const (
+ // AWS Signature Version 4 signing algorithm identifier.
+ awsAlgorithm = "AWS4-HMAC-SHA256"
+
+ // The termination string for the AWS credential scope value as defined in
+ // https://docs.aws.amazon.com/general/latest/gr/sigv4-create-string-to-sign.html
+ awsRequestType = "aws4_request"
+
+ // The AWS authorization header name for the security session token if available.
+ awsSecurityTokenHeader = "x-amz-security-token"
+
+ // The name of the header containing the session token for metadata endpoint calls
+ awsIMDSv2SessionTokenHeader = "X-aws-ec2-metadata-token"
+
+ awsIMDSv2SessionTTLHeader = "X-aws-ec2-metadata-token-ttl-seconds"
+
+ awsIMDSv2SessionTTL = "300"
+
+ // The AWS authorization header name for the auto-generated date.
+ awsDateHeader = "x-amz-date"
+
+ defaultRegionalCredentialVerificationURL = "https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15"
+
+ // Supported AWS configuration environment variables.
+ awsAccessKeyIDEnvVar = "AWS_ACCESS_KEY_ID"
+ awsDefaultRegionEnvVar = "AWS_DEFAULT_REGION"
+ awsRegionEnvVar = "AWS_REGION"
+ awsSecretAccessKeyEnvVar = "AWS_SECRET_ACCESS_KEY"
+ awsSessionTokenEnvVar = "AWS_SESSION_TOKEN"
+
+ awsTimeFormatLong = "20060102T150405Z"
+ awsTimeFormatShort = "20060102"
+ awsProviderType = "aws"
+)
+
+type awsSubjectProvider struct {
+ EnvironmentID string
+ RegionURL string
+ RegionalCredVerificationURL string
+ CredVerificationURL string
+ IMDSv2SessionTokenURL string
+ TargetResource string
+ requestSigner *awsRequestSigner
+ region string
+ securityCredentialsProvider AwsSecurityCredentialsProvider
+ reqOpts *RequestOptions
+
+ Client *http.Client
+ logger *slog.Logger
+}
+
+func (sp *awsSubjectProvider) subjectToken(ctx context.Context) (string, error) {
+ // Set Defaults
+ if sp.RegionalCredVerificationURL == "" {
+ sp.RegionalCredVerificationURL = defaultRegionalCredentialVerificationURL
+ }
+ headers := make(map[string]string)
+ if sp.shouldUseMetadataServer() {
+ awsSessionToken, err := sp.getAWSSessionToken(ctx)
+ if err != nil {
+ return "", err
+ }
+
+ if awsSessionToken != "" {
+ headers[awsIMDSv2SessionTokenHeader] = awsSessionToken
+ }
+ }
+
+ awsSecurityCredentials, err := sp.getSecurityCredentials(ctx, headers)
+ if err != nil {
+ return "", err
+ }
+ if sp.region, err = sp.getRegion(ctx, headers); err != nil {
+ return "", err
+ }
+ sp.requestSigner = &awsRequestSigner{
+ RegionName: sp.region,
+ AwsSecurityCredentials: awsSecurityCredentials,
+ }
+
+ // Generate the signed request to AWS STS GetCallerIdentity API.
+ // Use the required regional endpoint. Otherwise, the request will fail.
+ req, err := http.NewRequestWithContext(ctx, "POST", strings.Replace(sp.RegionalCredVerificationURL, "{region}", sp.region, 1), nil)
+ if err != nil {
+ return "", err
+ }
+ // The full, canonical resource name of the workload identity pool
+ // provider, with or without the HTTPS prefix.
+ // Including this header as part of the signature is recommended to
+ // ensure data integrity.
+ if sp.TargetResource != "" {
+ req.Header.Set("x-goog-cloud-target-resource", sp.TargetResource)
+ }
+ sp.requestSigner.signRequest(req)
+
+ /*
+ The GCP STS endpoint expects the headers to be formatted as:
+ # [
+ # {key: 'x-amz-date', value: '...'},
+ # {key: 'Authorization', value: '...'},
+ # ...
+ # ]
+ # And then serialized as:
+ # quote(json.dumps({
+ # url: '...',
+ # method: 'POST',
+ # headers: [{key: 'x-amz-date', value: '...'}, ...]
+ # }))
+ */
+
+ awsSignedReq := awsRequest{
+ URL: req.URL.String(),
+ Method: "POST",
+ }
+ for headerKey, headerList := range req.Header {
+ for _, headerValue := range headerList {
+ awsSignedReq.Headers = append(awsSignedReq.Headers, awsRequestHeader{
+ Key: headerKey,
+ Value: headerValue,
+ })
+ }
+ }
+ sort.Slice(awsSignedReq.Headers, func(i, j int) bool {
+ headerCompare := strings.Compare(awsSignedReq.Headers[i].Key, awsSignedReq.Headers[j].Key)
+ if headerCompare == 0 {
+ return strings.Compare(awsSignedReq.Headers[i].Value, awsSignedReq.Headers[j].Value) < 0
+ }
+ return headerCompare < 0
+ })
+
+ result, err := json.Marshal(awsSignedReq)
+ if err != nil {
+ return "", err
+ }
+ return url.QueryEscape(string(result)), nil
+}
+
+func (sp *awsSubjectProvider) providerType() string {
+ if sp.securityCredentialsProvider != nil {
+ return programmaticProviderType
+ }
+ return awsProviderType
+}
+
+func (sp *awsSubjectProvider) getAWSSessionToken(ctx context.Context) (string, error) {
+ if sp.IMDSv2SessionTokenURL == "" {
+ return "", nil
+ }
+ req, err := http.NewRequestWithContext(ctx, "PUT", sp.IMDSv2SessionTokenURL, nil)
+ if err != nil {
+ return "", err
+ }
+ req.Header.Set(awsIMDSv2SessionTTLHeader, awsIMDSv2SessionTTL)
+
+ sp.logger.DebugContext(ctx, "aws session token request", "request", internallog.HTTPRequest(req, nil))
+ resp, body, err := internal.DoRequest(sp.Client, req)
+ if err != nil {
+ return "", err
+ }
+ sp.logger.DebugContext(ctx, "aws session token response", "response", internallog.HTTPResponse(resp, body))
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("credentials: unable to retrieve AWS session token: %s", body)
+ }
+ return string(body), nil
+}
+
+func (sp *awsSubjectProvider) getRegion(ctx context.Context, headers map[string]string) (string, error) {
+ if sp.securityCredentialsProvider != nil {
+ return sp.securityCredentialsProvider.AwsRegion(ctx, sp.reqOpts)
+ }
+ if canRetrieveRegionFromEnvironment() {
+ if envAwsRegion := getenv(awsRegionEnvVar); envAwsRegion != "" {
+ return envAwsRegion, nil
+ }
+ return getenv(awsDefaultRegionEnvVar), nil
+ }
+
+ if sp.RegionURL == "" {
+ return "", errors.New("credentials: unable to determine AWS region")
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "GET", sp.RegionURL, nil)
+ if err != nil {
+ return "", err
+ }
+
+ for name, value := range headers {
+ req.Header.Add(name, value)
+ }
+ sp.logger.DebugContext(ctx, "aws region request", "request", internallog.HTTPRequest(req, nil))
+ resp, body, err := internal.DoRequest(sp.Client, req)
+ if err != nil {
+ return "", err
+ }
+ sp.logger.DebugContext(ctx, "aws region response", "response", internallog.HTTPResponse(resp, body))
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("credentials: unable to retrieve AWS region - %s", body)
+ }
+
+ // This endpoint will return the region in format: us-east-2b.
+ // Only the us-east-2 part should be used.
+ bodyLen := len(body)
+ if bodyLen == 0 {
+ return "", nil
+ }
+ return string(body[:bodyLen-1]), nil
+}
+
+func (sp *awsSubjectProvider) getSecurityCredentials(ctx context.Context, headers map[string]string) (result *AwsSecurityCredentials, err error) {
+ if sp.securityCredentialsProvider != nil {
+ return sp.securityCredentialsProvider.AwsSecurityCredentials(ctx, sp.reqOpts)
+ }
+ if canRetrieveSecurityCredentialFromEnvironment() {
+ return &AwsSecurityCredentials{
+ AccessKeyID: getenv(awsAccessKeyIDEnvVar),
+ SecretAccessKey: getenv(awsSecretAccessKeyEnvVar),
+ SessionToken: getenv(awsSessionTokenEnvVar),
+ }, nil
+ }
+
+ roleName, err := sp.getMetadataRoleName(ctx, headers)
+ if err != nil {
+ return
+ }
+ credentials, err := sp.getMetadataSecurityCredentials(ctx, roleName, headers)
+ if err != nil {
+ return
+ }
+
+ if credentials.AccessKeyID == "" {
+ return result, errors.New("credentials: missing AccessKeyId credential")
+ }
+ if credentials.SecretAccessKey == "" {
+ return result, errors.New("credentials: missing SecretAccessKey credential")
+ }
+
+ return credentials, nil
+}
+
+func (sp *awsSubjectProvider) getMetadataSecurityCredentials(ctx context.Context, roleName string, headers map[string]string) (*AwsSecurityCredentials, error) {
+ var result *AwsSecurityCredentials
+
+ req, err := http.NewRequestWithContext(ctx, "GET", fmt.Sprintf("%s/%s", sp.CredVerificationURL, roleName), nil)
+ if err != nil {
+ return result, err
+ }
+ for name, value := range headers {
+ req.Header.Add(name, value)
+ }
+ sp.logger.DebugContext(ctx, "aws security credential request", "request", internallog.HTTPRequest(req, nil))
+ resp, body, err := internal.DoRequest(sp.Client, req)
+ if err != nil {
+ return result, err
+ }
+ sp.logger.DebugContext(ctx, "aws security credential response", "response", internallog.HTTPResponse(resp, body))
+ if resp.StatusCode != http.StatusOK {
+ return result, fmt.Errorf("credentials: unable to retrieve AWS security credentials - %s", body)
+ }
+ if err := json.Unmarshal(body, &result); err != nil {
+ return nil, err
+ }
+ return result, nil
+}
+
+func (sp *awsSubjectProvider) getMetadataRoleName(ctx context.Context, headers map[string]string) (string, error) {
+ if sp.CredVerificationURL == "" {
+ return "", errors.New("credentials: unable to determine the AWS metadata server security credentials endpoint")
+ }
+ req, err := http.NewRequestWithContext(ctx, "GET", sp.CredVerificationURL, nil)
+ if err != nil {
+ return "", err
+ }
+ for name, value := range headers {
+ req.Header.Add(name, value)
+ }
+
+ sp.logger.DebugContext(ctx, "aws metadata role request", "request", internallog.HTTPRequest(req, nil))
+ resp, body, err := internal.DoRequest(sp.Client, req)
+ if err != nil {
+ return "", err
+ }
+ sp.logger.DebugContext(ctx, "aws metadata role response", "response", internallog.HTTPResponse(resp, body))
+ if resp.StatusCode != http.StatusOK {
+ return "", fmt.Errorf("credentials: unable to retrieve AWS role name - %s", body)
+ }
+ return string(body), nil
+}
+
+// awsRequestSigner is a utility class to sign http requests using a AWS V4 signature.
+type awsRequestSigner struct {
+ RegionName string
+ AwsSecurityCredentials *AwsSecurityCredentials
+}
+
+// signRequest adds the appropriate headers to an http.Request
+// or returns an error if something prevented this.
+func (rs *awsRequestSigner) signRequest(req *http.Request) error {
+ // req is assumed non-nil
+ signedRequest := cloneRequest(req)
+ timestamp := Now()
+ signedRequest.Header.Set("host", requestHost(req))
+ if rs.AwsSecurityCredentials.SessionToken != "" {
+ signedRequest.Header.Set(awsSecurityTokenHeader, rs.AwsSecurityCredentials.SessionToken)
+ }
+ if signedRequest.Header.Get("date") == "" {
+ signedRequest.Header.Set(awsDateHeader, timestamp.Format(awsTimeFormatLong))
+ }
+ authorizationCode, err := rs.generateAuthentication(signedRequest, timestamp)
+ if err != nil {
+ return err
+ }
+ signedRequest.Header.Set("Authorization", authorizationCode)
+ req.Header = signedRequest.Header
+ return nil
+}
+
+func (rs *awsRequestSigner) generateAuthentication(req *http.Request, timestamp time.Time) (string, error) {
+ canonicalHeaderColumns, canonicalHeaderData := canonicalHeaders(req)
+ dateStamp := timestamp.Format(awsTimeFormatShort)
+ serviceName := ""
+
+ if splitHost := strings.Split(requestHost(req), "."); len(splitHost) > 0 {
+ serviceName = splitHost[0]
+ }
+ credentialScope := strings.Join([]string{dateStamp, rs.RegionName, serviceName, awsRequestType}, "/")
+ requestString, err := canonicalRequest(req, canonicalHeaderColumns, canonicalHeaderData)
+ if err != nil {
+ return "", err
+ }
+ requestHash, err := getSha256([]byte(requestString))
+ if err != nil {
+ return "", err
+ }
+
+ stringToSign := strings.Join([]string{awsAlgorithm, timestamp.Format(awsTimeFormatLong), credentialScope, requestHash}, "\n")
+ signingKey := []byte("AWS4" + rs.AwsSecurityCredentials.SecretAccessKey)
+ for _, signingInput := range []string{
+ dateStamp, rs.RegionName, serviceName, awsRequestType, stringToSign,
+ } {
+ signingKey, err = getHmacSha256(signingKey, []byte(signingInput))
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return fmt.Sprintf("%s Credential=%s/%s, SignedHeaders=%s, Signature=%s", awsAlgorithm, rs.AwsSecurityCredentials.AccessKeyID, credentialScope, canonicalHeaderColumns, hex.EncodeToString(signingKey)), nil
+}
+
+func getSha256(input []byte) (string, error) {
+ hash := sha256.New()
+ if _, err := hash.Write(input); err != nil {
+ return "", err
+ }
+ return hex.EncodeToString(hash.Sum(nil)), nil
+}
+
+func getHmacSha256(key, input []byte) ([]byte, error) {
+ hash := hmac.New(sha256.New, key)
+ if _, err := hash.Write(input); err != nil {
+ return nil, err
+ }
+ return hash.Sum(nil), nil
+}
+
+func cloneRequest(r *http.Request) *http.Request {
+ r2 := new(http.Request)
+ *r2 = *r
+ if r.Header != nil {
+ r2.Header = make(http.Header, len(r.Header))
+
+ // Find total number of values.
+ headerCount := 0
+ for _, headerValues := range r.Header {
+ headerCount += len(headerValues)
+ }
+ copiedHeaders := make([]string, headerCount) // shared backing array for headers' values
+
+ for headerKey, headerValues := range r.Header {
+ headerCount = copy(copiedHeaders, headerValues)
+ r2.Header[headerKey] = copiedHeaders[:headerCount:headerCount]
+ copiedHeaders = copiedHeaders[headerCount:]
+ }
+ }
+ return r2
+}
+
+func canonicalPath(req *http.Request) string {
+ result := req.URL.EscapedPath()
+ if result == "" {
+ return "/"
+ }
+ return path.Clean(result)
+}
+
+func canonicalQuery(req *http.Request) string {
+ queryValues := req.URL.Query()
+ for queryKey := range queryValues {
+ sort.Strings(queryValues[queryKey])
+ }
+ return queryValues.Encode()
+}
+
+func canonicalHeaders(req *http.Request) (string, string) {
+ // Header keys need to be sorted alphabetically.
+ var headers []string
+ lowerCaseHeaders := make(http.Header)
+ for k, v := range req.Header {
+ k := strings.ToLower(k)
+ if _, ok := lowerCaseHeaders[k]; ok {
+ // include additional values
+ lowerCaseHeaders[k] = append(lowerCaseHeaders[k], v...)
+ } else {
+ headers = append(headers, k)
+ lowerCaseHeaders[k] = v
+ }
+ }
+ sort.Strings(headers)
+
+ var fullHeaders bytes.Buffer
+ for _, header := range headers {
+ headerValue := strings.Join(lowerCaseHeaders[header], ",")
+ fullHeaders.WriteString(header)
+ fullHeaders.WriteRune(':')
+ fullHeaders.WriteString(headerValue)
+ fullHeaders.WriteRune('\n')
+ }
+
+ return strings.Join(headers, ";"), fullHeaders.String()
+}
+
+func requestDataHash(req *http.Request) (string, error) {
+ var requestData []byte
+ if req.Body != nil {
+ requestBody, err := req.GetBody()
+ if err != nil {
+ return "", err
+ }
+ defer requestBody.Close()
+
+ requestData, err = internal.ReadAll(requestBody)
+ if err != nil {
+ return "", err
+ }
+ }
+
+ return getSha256(requestData)
+}
+
+func requestHost(req *http.Request) string {
+ if req.Host != "" {
+ return req.Host
+ }
+ return req.URL.Host
+}
+
+func canonicalRequest(req *http.Request, canonicalHeaderColumns, canonicalHeaderData string) (string, error) {
+ dataHash, err := requestDataHash(req)
+ if err != nil {
+ return "", err
+ }
+ return fmt.Sprintf("%s\n%s\n%s\n%s\n%s\n%s", req.Method, canonicalPath(req), canonicalQuery(req), canonicalHeaderData, canonicalHeaderColumns, dataHash), nil
+}
+
+type awsRequestHeader struct {
+ Key string `json:"key"`
+ Value string `json:"value"`
+}
+
+type awsRequest struct {
+ URL string `json:"url"`
+ Method string `json:"method"`
+ Headers []awsRequestHeader `json:"headers"`
+}
+
+// The AWS region can be provided through AWS_REGION or AWS_DEFAULT_REGION. Only one is
+// required.
+func canRetrieveRegionFromEnvironment() bool {
+ return getenv(awsRegionEnvVar) != "" || getenv(awsDefaultRegionEnvVar) != ""
+}
+
+// Check if both AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY are available.
+func canRetrieveSecurityCredentialFromEnvironment() bool {
+ return getenv(awsAccessKeyIDEnvVar) != "" && getenv(awsSecretAccessKeyEnvVar) != ""
+}
+
+func (sp *awsSubjectProvider) shouldUseMetadataServer() bool {
+ return sp.securityCredentialsProvider == nil && (!canRetrieveRegionFromEnvironment() || !canRetrieveSecurityCredentialFromEnvironment())
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
new file mode 100644
index 0000000..d5765c4
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/executable_provider.go
@@ -0,0 +1,284 @@
+// 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 externalaccount
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "os"
+ "os/exec"
+ "regexp"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth/internal"
+)
+
+const (
+ executableSupportedMaxVersion = 1
+ executableDefaultTimeout = 30 * time.Second
+ executableSource = "response"
+ executableProviderType = "executable"
+ outputFileSource = "output file"
+
+ allowExecutablesEnvVar = "GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES"
+
+ jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
+ idTokenType = "urn:ietf:params:oauth:token-type:id_token"
+ saml2TokenType = "urn:ietf:params:oauth:token-type:saml2"
+)
+
+var (
+ serviceAccountImpersonationRE = regexp.MustCompile(`https://iamcredentials..+/v1/projects/-/serviceAccounts/(.*@.*):generateAccessToken`)
+)
+
+type nonCacheableError struct {
+ message string
+}
+
+func (nce nonCacheableError) Error() string {
+ return nce.message
+}
+
+// environment is a contract for testing
+type environment interface {
+ existingEnv() []string
+ getenv(string) string
+ run(ctx context.Context, command string, env []string) ([]byte, error)
+ now() time.Time
+}
+
+type runtimeEnvironment struct{}
+
+func (r runtimeEnvironment) existingEnv() []string {
+ return os.Environ()
+}
+func (r runtimeEnvironment) getenv(key string) string {
+ return os.Getenv(key)
+}
+func (r runtimeEnvironment) now() time.Time {
+ return time.Now().UTC()
+}
+
+func (r runtimeEnvironment) run(ctx context.Context, command string, env []string) ([]byte, error) {
+ splitCommand := strings.Fields(command)
+ cmd := exec.CommandContext(ctx, splitCommand[0], splitCommand[1:]...)
+ cmd.Env = env
+
+ var stdout, stderr bytes.Buffer
+ cmd.Stdout = &stdout
+ cmd.Stderr = &stderr
+
+ if err := cmd.Run(); err != nil {
+ if ctx.Err() == context.DeadlineExceeded {
+ return nil, context.DeadlineExceeded
+ }
+ if exitError, ok := err.(*exec.ExitError); ok {
+ return nil, exitCodeError(exitError)
+ }
+ return nil, executableError(err)
+ }
+
+ bytesStdout := bytes.TrimSpace(stdout.Bytes())
+ if len(bytesStdout) > 0 {
+ return bytesStdout, nil
+ }
+ return bytes.TrimSpace(stderr.Bytes()), nil
+}
+
+type executableSubjectProvider struct {
+ Command string
+ Timeout time.Duration
+ OutputFile string
+ client *http.Client
+ opts *Options
+ env environment
+}
+
+type executableResponse struct {
+ Version int `json:"version,omitempty"`
+ Success *bool `json:"success,omitempty"`
+ TokenType string `json:"token_type,omitempty"`
+ ExpirationTime int64 `json:"expiration_time,omitempty"`
+ IDToken string `json:"id_token,omitempty"`
+ SamlResponse string `json:"saml_response,omitempty"`
+ Code string `json:"code,omitempty"`
+ Message string `json:"message,omitempty"`
+}
+
+func (sp *executableSubjectProvider) parseSubjectTokenFromSource(response []byte, source string, now int64) (string, error) {
+ var result executableResponse
+ if err := json.Unmarshal(response, &result); err != nil {
+ return "", jsonParsingError(source, string(response))
+ }
+ // Validate
+ if result.Version == 0 {
+ return "", missingFieldError(source, "version")
+ }
+ if result.Success == nil {
+ return "", missingFieldError(source, "success")
+ }
+ if !*result.Success {
+ if result.Code == "" || result.Message == "" {
+ return "", malformedFailureError()
+ }
+ return "", userDefinedError(result.Code, result.Message)
+ }
+ if result.Version > executableSupportedMaxVersion || result.Version < 0 {
+ return "", unsupportedVersionError(source, result.Version)
+ }
+ if result.ExpirationTime == 0 && sp.OutputFile != "" {
+ return "", missingFieldError(source, "expiration_time")
+ }
+ if result.TokenType == "" {
+ return "", missingFieldError(source, "token_type")
+ }
+ if result.ExpirationTime != 0 && result.ExpirationTime < now {
+ return "", tokenExpiredError()
+ }
+
+ switch result.TokenType {
+ case jwtTokenType, idTokenType:
+ if result.IDToken == "" {
+ return "", missingFieldError(source, "id_token")
+ }
+ return result.IDToken, nil
+ case saml2TokenType:
+ if result.SamlResponse == "" {
+ return "", missingFieldError(source, "saml_response")
+ }
+ return result.SamlResponse, nil
+ default:
+ return "", tokenTypeError(source)
+ }
+}
+
+func (sp *executableSubjectProvider) subjectToken(ctx context.Context) (string, error) {
+ if token, err := sp.getTokenFromOutputFile(); token != "" || err != nil {
+ return token, err
+ }
+ return sp.getTokenFromExecutableCommand(ctx)
+}
+
+func (sp *executableSubjectProvider) providerType() string {
+ return executableProviderType
+}
+
+func (sp *executableSubjectProvider) getTokenFromOutputFile() (token string, err error) {
+ if sp.OutputFile == "" {
+ // This ExecutableCredentialSource doesn't use an OutputFile.
+ return "", nil
+ }
+
+ file, err := os.Open(sp.OutputFile)
+ if err != nil {
+ // No OutputFile found. Hasn't been created yet, so skip it.
+ return "", nil
+ }
+ defer file.Close()
+
+ data, err := internal.ReadAll(file)
+ if err != nil || len(data) == 0 {
+ // Cachefile exists, but no data found. Get new credential.
+ return "", nil
+ }
+
+ token, err = sp.parseSubjectTokenFromSource(data, outputFileSource, sp.env.now().Unix())
+ if err != nil {
+ if _, ok := err.(nonCacheableError); ok {
+ // If the cached token is expired we need a new token,
+ // and if the cache contains a failure, we need to try again.
+ return "", nil
+ }
+
+ // There was an error in the cached token, and the developer should be aware of it.
+ return "", err
+ }
+ // Token parsing succeeded. Use found token.
+ return token, nil
+}
+
+func (sp *executableSubjectProvider) executableEnvironment() []string {
+ result := sp.env.existingEnv()
+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_AUDIENCE=%v", sp.opts.Audience))
+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_TOKEN_TYPE=%v", sp.opts.SubjectTokenType))
+ result = append(result, "GOOGLE_EXTERNAL_ACCOUNT_INTERACTIVE=0")
+ if sp.opts.ServiceAccountImpersonationURL != "" {
+ matches := serviceAccountImpersonationRE.FindStringSubmatch(sp.opts.ServiceAccountImpersonationURL)
+ if matches != nil {
+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_IMPERSONATED_EMAIL=%v", matches[1]))
+ }
+ }
+ if sp.OutputFile != "" {
+ result = append(result, fmt.Sprintf("GOOGLE_EXTERNAL_ACCOUNT_OUTPUT_FILE=%v", sp.OutputFile))
+ }
+ return result
+}
+
+func (sp *executableSubjectProvider) getTokenFromExecutableCommand(ctx context.Context) (string, error) {
+ // For security reasons, we need our consumers to set this environment variable to allow executables to be run.
+ if sp.env.getenv(allowExecutablesEnvVar) != "1" {
+ return "", errors.New("credentials: executables need to be explicitly allowed (set GOOGLE_EXTERNAL_ACCOUNT_ALLOW_EXECUTABLES to '1') to run")
+ }
+
+ ctx, cancel := context.WithDeadline(ctx, sp.env.now().Add(sp.Timeout))
+ defer cancel()
+
+ output, err := sp.env.run(ctx, sp.Command, sp.executableEnvironment())
+ if err != nil {
+ return "", err
+ }
+ return sp.parseSubjectTokenFromSource(output, executableSource, sp.env.now().Unix())
+}
+
+func missingFieldError(source, field string) error {
+ return fmt.Errorf("credentials: %q missing %q field", source, field)
+}
+
+func jsonParsingError(source, data string) error {
+ return fmt.Errorf("credentials: unable to parse %q: %v", source, data)
+}
+
+func malformedFailureError() error {
+ return nonCacheableError{"credentials: response must include `error` and `message` fields when unsuccessful"}
+}
+
+func userDefinedError(code, message string) error {
+ return nonCacheableError{fmt.Sprintf("credentials: response contains unsuccessful response: (%v) %v", code, message)}
+}
+
+func unsupportedVersionError(source string, version int) error {
+ return fmt.Errorf("credentials: %v contains unsupported version: %v", source, version)
+}
+
+func tokenExpiredError() error {
+ return nonCacheableError{"credentials: the token returned by the executable is expired"}
+}
+
+func tokenTypeError(source string) error {
+ return fmt.Errorf("credentials: %v contains unsupported token type", source)
+}
+
+func exitCodeError(err *exec.ExitError) error {
+ return fmt.Errorf("credentials: executable command failed with exit code %v: %w", err.ExitCode(), err)
+}
+
+func executableError(err error) error {
+ return fmt.Errorf("credentials: executable command failed: %w", err)
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
new file mode 100644
index 0000000..f4f49f1
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/externalaccount.go
@@ -0,0 +1,431 @@
+// 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 externalaccount
+
+import (
+ "context"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "regexp"
+ "strconv"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/credentials/internal/impersonate"
+ "cloud.google.com/go/auth/credentials/internal/stsexchange"
+ "cloud.google.com/go/auth/internal/credsfile"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+const (
+ timeoutMinimum = 5 * time.Second
+ timeoutMaximum = 120 * time.Second
+
+ universeDomainPlaceholder = "UNIVERSE_DOMAIN"
+ defaultTokenURL = "https://sts.UNIVERSE_DOMAIN/v1/token"
+ defaultUniverseDomain = "googleapis.com"
+)
+
+var (
+ // Now aliases time.Now for testing
+ Now = func() time.Time {
+ return time.Now().UTC()
+ }
+ validWorkforceAudiencePattern *regexp.Regexp = regexp.MustCompile(`//iam\.googleapis\.com/locations/[^/]+/workforcePools/`)
+)
+
+// Options stores the configuration for fetching tokens with external credentials.
+type Options struct {
+ // Audience is the Secure Token Service (STS) audience which contains the resource name for the workload
+ // identity pool or the workforce pool and the provider identifier in that pool.
+ Audience string
+ // SubjectTokenType is the STS token type based on the Oauth2.0 token exchange spec
+ // e.g. `urn:ietf:params:oauth:token-type:jwt`.
+ SubjectTokenType string
+ // TokenURL is the STS token exchange endpoint.
+ TokenURL string
+ // TokenInfoURL is the token_info endpoint used to retrieve the account related information (
+ // user attributes like account identifier, eg. email, username, uid, etc). This is
+ // needed for gCloud session account identification.
+ TokenInfoURL string
+ // ServiceAccountImpersonationURL is the URL for the service account impersonation request. This is only
+ // required for workload identity pools when APIs to be accessed have not integrated with UberMint.
+ ServiceAccountImpersonationURL string
+ // ServiceAccountImpersonationLifetimeSeconds is the number of seconds the service account impersonation
+ // token will be valid for.
+ ServiceAccountImpersonationLifetimeSeconds int
+ // ClientSecret is currently only required if token_info endpoint also
+ // needs to be called with the generated GCP access token. When provided, STS will be
+ // called with additional basic authentication using client_id as username and client_secret as password.
+ ClientSecret string
+ // ClientID is only required in conjunction with ClientSecret, as described above.
+ ClientID string
+ // CredentialSource contains the necessary information to retrieve the token itself, as well
+ // as some environmental information.
+ CredentialSource *credsfile.CredentialSource
+ // QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth libraries
+ // will set the x-goog-user-project which overrides the project associated with the credentials.
+ QuotaProjectID string
+ // Scopes contains the desired scopes for the returned access token.
+ Scopes []string
+ // WorkforcePoolUserProject should be set when it is a workforce pool and
+ // not a workload identity pool. The underlying principal must still have
+ // serviceusage.services.use IAM permission to use the project for
+ // billing/quota. Optional.
+ WorkforcePoolUserProject string
+ // UniverseDomain is the default service domain for a given Cloud universe.
+ // This value will be used in the default STS token URL. The default value
+ // is "googleapis.com". It will not be used if TokenURL is set. Optional.
+ UniverseDomain string
+ // SubjectTokenProvider is an optional token provider for OIDC/SAML
+ // credentials. One of SubjectTokenProvider, AWSSecurityCredentialProvider
+ // or CredentialSource must be provided. Optional.
+ SubjectTokenProvider SubjectTokenProvider
+ // AwsSecurityCredentialsProvider is an AWS Security Credential provider
+ // for AWS credentials. One of SubjectTokenProvider,
+ // AWSSecurityCredentialProvider or CredentialSource must be provided. Optional.
+ AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider
+ // Client for token request.
+ Client *http.Client
+ // IsDefaultClient marks whether the client passed in is a default client that can be overriden.
+ // This is important for X509 credentials which should create a new client if the default was used
+ // but should respect a client explicitly passed in by the user.
+ IsDefaultClient bool
+ // Logger is used for debug logging. If provided, logging will be enabled
+ // at the loggers configured level. By default logging is disabled unless
+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
+ // logger will be used. Optional.
+ Logger *slog.Logger
+}
+
+// SubjectTokenProvider can be used to supply a subject token to exchange for a
+// GCP access token.
+type SubjectTokenProvider interface {
+ // SubjectToken should return a valid subject token or an error.
+ // The external account token provider does not cache the returned subject
+ // token, so caching logic should be implemented in the provider to prevent
+ // multiple requests for the same subject token.
+ SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
+}
+
+// RequestOptions contains information about the requested subject token or AWS
+// security credentials from the Google external account credential.
+type RequestOptions struct {
+ // Audience is the requested audience for the external account credential.
+ Audience string
+ // Subject token type is the requested subject token type for the external
+ // account credential. Expected values include:
+ // “urn:ietf:params:oauth:token-type:jwt”
+ // “urn:ietf:params:oauth:token-type:id-token”
+ // “urn:ietf:params:oauth:token-type:saml2”
+ // “urn:ietf:params:aws:token-type:aws4_request”
+ SubjectTokenType string
+}
+
+// AwsSecurityCredentialsProvider can be used to supply AwsSecurityCredentials
+// and an AWS Region to exchange for a GCP access token.
+type AwsSecurityCredentialsProvider interface {
+ // AwsRegion should return the AWS region or an error.
+ AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
+ // GetAwsSecurityCredentials should return a valid set of
+ // AwsSecurityCredentials or an error. The external account token provider
+ // does not cache the returned security credentials, so caching logic should
+ // be implemented in the provider to prevent multiple requests for the
+ // same security credentials.
+ AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
+}
+
+// AwsSecurityCredentials models AWS security credentials.
+type AwsSecurityCredentials struct {
+ // AccessKeyId is the AWS Access Key ID - Required.
+ AccessKeyID string `json:"AccessKeyID"`
+ // SecretAccessKey is the AWS Secret Access Key - Required.
+ SecretAccessKey string `json:"SecretAccessKey"`
+ // SessionToken is the AWS Session token. This should be provided for
+ // temporary AWS security credentials - Optional.
+ SessionToken string `json:"Token"`
+}
+
+func (o *Options) validate() error {
+ if o.Audience == "" {
+ return fmt.Errorf("externalaccount: Audience must be set")
+ }
+ if o.SubjectTokenType == "" {
+ return fmt.Errorf("externalaccount: Subject token type must be set")
+ }
+ if o.WorkforcePoolUserProject != "" {
+ if valid := validWorkforceAudiencePattern.MatchString(o.Audience); !valid {
+ return fmt.Errorf("externalaccount: workforce_pool_user_project should not be set for non-workforce pool credentials")
+ }
+ }
+ count := 0
+ if o.CredentialSource != nil {
+ count++
+ }
+ if o.SubjectTokenProvider != nil {
+ count++
+ }
+ if o.AwsSecurityCredentialsProvider != nil {
+ count++
+ }
+ if count == 0 {
+ return fmt.Errorf("externalaccount: one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
+ }
+ if count > 1 {
+ return fmt.Errorf("externalaccount: only one of CredentialSource, SubjectTokenProvider, or AwsSecurityCredentialsProvider must be set")
+ }
+ return nil
+}
+
+// client returns the http client that should be used for the token exchange. If a non-default client
+// is provided, then the client configured in the options will always be returned. If a default client
+// is provided and the options are configured for X509 credentials, a new client will be created.
+func (o *Options) client() (*http.Client, error) {
+ // If a client was provided and no override certificate config location was provided, use the provided client.
+ if o.CredentialSource == nil || o.CredentialSource.Certificate == nil || (!o.IsDefaultClient && o.CredentialSource.Certificate.CertificateConfigLocation == "") {
+ return o.Client, nil
+ }
+
+ // If a new client should be created, validate and use the certificate source to create a new mTLS client.
+ cert := o.CredentialSource.Certificate
+ if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
+ return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
+ }
+ if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
+ return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
+ }
+ return createX509Client(cert.CertificateConfigLocation)
+}
+
+// resolveTokenURL sets the default STS token endpoint with the configured
+// universe domain.
+func (o *Options) resolveTokenURL() {
+ if o.TokenURL != "" {
+ return
+ } else if o.UniverseDomain != "" {
+ o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, o.UniverseDomain, 1)
+ } else {
+ o.TokenURL = strings.Replace(defaultTokenURL, universeDomainPlaceholder, defaultUniverseDomain, 1)
+ }
+}
+
+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
+// configured with the provided options.
+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
+ if err := opts.validate(); err != nil {
+ return nil, err
+ }
+ opts.resolveTokenURL()
+ logger := internallog.New(opts.Logger)
+ stp, err := newSubjectTokenProvider(opts)
+ if err != nil {
+ return nil, err
+ }
+
+ client, err := opts.client()
+ if err != nil {
+ return nil, err
+ }
+
+ tp := &tokenProvider{
+ client: client,
+ opts: opts,
+ stp: stp,
+ logger: logger,
+ }
+
+ if opts.ServiceAccountImpersonationURL == "" {
+ return auth.NewCachedTokenProvider(tp, nil), nil
+ }
+
+ scopes := make([]string, len(opts.Scopes))
+ copy(scopes, opts.Scopes)
+ // needed for impersonation
+ tp.opts.Scopes = []string{"https://www.googleapis.com/auth/cloud-platform"}
+ imp, err := impersonate.NewTokenProvider(&impersonate.Options{
+ Client: client,
+ URL: opts.ServiceAccountImpersonationURL,
+ Scopes: scopes,
+ Tp: auth.NewCachedTokenProvider(tp, nil),
+ TokenLifetimeSeconds: opts.ServiceAccountImpersonationLifetimeSeconds,
+ Logger: logger,
+ })
+ if err != nil {
+ return nil, err
+ }
+ return auth.NewCachedTokenProvider(imp, nil), nil
+}
+
+type subjectTokenProvider interface {
+ subjectToken(ctx context.Context) (string, error)
+ providerType() string
+}
+
+// tokenProvider is the provider that handles external credentials. It is used to retrieve Tokens.
+type tokenProvider struct {
+ client *http.Client
+ logger *slog.Logger
+ opts *Options
+ stp subjectTokenProvider
+}
+
+func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
+ subjectToken, err := tp.stp.subjectToken(ctx)
+ if err != nil {
+ return nil, err
+ }
+
+ stsRequest := &stsexchange.TokenRequest{
+ GrantType: stsexchange.GrantType,
+ Audience: tp.opts.Audience,
+ Scope: tp.opts.Scopes,
+ RequestedTokenType: stsexchange.TokenType,
+ SubjectToken: subjectToken,
+ SubjectTokenType: tp.opts.SubjectTokenType,
+ }
+ header := make(http.Header)
+ header.Set("Content-Type", "application/x-www-form-urlencoded")
+ header.Add("x-goog-api-client", getGoogHeaderValue(tp.opts, tp.stp))
+ clientAuth := stsexchange.ClientAuthentication{
+ AuthStyle: auth.StyleInHeader,
+ ClientID: tp.opts.ClientID,
+ ClientSecret: tp.opts.ClientSecret,
+ }
+ var options map[string]interface{}
+ // Do not pass workforce_pool_user_project when client authentication is used.
+ // The client ID is sufficient for determining the user project.
+ if tp.opts.WorkforcePoolUserProject != "" && tp.opts.ClientID == "" {
+ options = map[string]interface{}{
+ "userProject": tp.opts.WorkforcePoolUserProject,
+ }
+ }
+ stsResp, err := stsexchange.ExchangeToken(ctx, &stsexchange.Options{
+ Client: tp.client,
+ Endpoint: tp.opts.TokenURL,
+ Request: stsRequest,
+ Authentication: clientAuth,
+ Headers: header,
+ ExtraOpts: options,
+ Logger: tp.logger,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ tok := &auth.Token{
+ Value: stsResp.AccessToken,
+ Type: stsResp.TokenType,
+ }
+ // The RFC8693 doesn't define the explicit 0 of "expires_in" field behavior.
+ if stsResp.ExpiresIn <= 0 {
+ return nil, fmt.Errorf("credentials: got invalid expiry from security token service")
+ }
+ tok.Expiry = Now().Add(time.Duration(stsResp.ExpiresIn) * time.Second)
+ return tok, nil
+}
+
+// newSubjectTokenProvider determines the type of credsfile.CredentialSource needed to create a
+// subjectTokenProvider
+func newSubjectTokenProvider(o *Options) (subjectTokenProvider, error) {
+ logger := internallog.New(o.Logger)
+ reqOpts := &RequestOptions{Audience: o.Audience, SubjectTokenType: o.SubjectTokenType}
+ if o.AwsSecurityCredentialsProvider != nil {
+ return &awsSubjectProvider{
+ securityCredentialsProvider: o.AwsSecurityCredentialsProvider,
+ TargetResource: o.Audience,
+ reqOpts: reqOpts,
+ logger: logger,
+ }, nil
+ } else if o.SubjectTokenProvider != nil {
+ return &programmaticProvider{stp: o.SubjectTokenProvider, opts: reqOpts}, nil
+ } else if len(o.CredentialSource.EnvironmentID) > 3 && o.CredentialSource.EnvironmentID[:3] == "aws" {
+ if awsVersion, err := strconv.Atoi(o.CredentialSource.EnvironmentID[3:]); err == nil {
+ if awsVersion != 1 {
+ return nil, fmt.Errorf("credentials: aws version '%d' is not supported in the current build", awsVersion)
+ }
+
+ awsProvider := &awsSubjectProvider{
+ EnvironmentID: o.CredentialSource.EnvironmentID,
+ RegionURL: o.CredentialSource.RegionURL,
+ RegionalCredVerificationURL: o.CredentialSource.RegionalCredVerificationURL,
+ CredVerificationURL: o.CredentialSource.URL,
+ TargetResource: o.Audience,
+ Client: o.Client,
+ logger: logger,
+ }
+ if o.CredentialSource.IMDSv2SessionTokenURL != "" {
+ awsProvider.IMDSv2SessionTokenURL = o.CredentialSource.IMDSv2SessionTokenURL
+ }
+
+ return awsProvider, nil
+ }
+ } else if o.CredentialSource.File != "" {
+ return &fileSubjectProvider{File: o.CredentialSource.File, Format: o.CredentialSource.Format}, nil
+ } else if o.CredentialSource.URL != "" {
+ return &urlSubjectProvider{
+ URL: o.CredentialSource.URL,
+ Headers: o.CredentialSource.Headers,
+ Format: o.CredentialSource.Format,
+ Client: o.Client,
+ Logger: logger,
+ }, nil
+ } else if o.CredentialSource.Executable != nil {
+ ec := o.CredentialSource.Executable
+ if ec.Command == "" {
+ return nil, errors.New("credentials: missing `command` field — executable command must be provided")
+ }
+
+ execProvider := &executableSubjectProvider{}
+ execProvider.Command = ec.Command
+ if ec.TimeoutMillis == 0 {
+ execProvider.Timeout = executableDefaultTimeout
+ } else {
+ execProvider.Timeout = time.Duration(ec.TimeoutMillis) * time.Millisecond
+ if execProvider.Timeout < timeoutMinimum || execProvider.Timeout > timeoutMaximum {
+ return nil, fmt.Errorf("credentials: invalid `timeout_millis` field — executable timeout must be between %v and %v seconds", timeoutMinimum.Seconds(), timeoutMaximum.Seconds())
+ }
+ }
+ execProvider.OutputFile = ec.OutputFile
+ execProvider.client = o.Client
+ execProvider.opts = o
+ execProvider.env = runtimeEnvironment{}
+ return execProvider, nil
+ } else if o.CredentialSource.Certificate != nil {
+ cert := o.CredentialSource.Certificate
+ if !cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation == "" {
+ return nil, errors.New("credentials: \"certificate\" object must either specify a certificate_config_location or use_default_certificate_config should be true")
+ }
+ if cert.UseDefaultCertificateConfig && cert.CertificateConfigLocation != "" {
+ return nil, errors.New("credentials: \"certificate\" object cannot specify both a certificate_config_location and use_default_certificate_config=true")
+ }
+ return &x509Provider{
+ TrustChainPath: o.CredentialSource.Certificate.TrustChainPath,
+ ConfigFilePath: o.CredentialSource.Certificate.CertificateConfigLocation,
+ }, nil
+ }
+ return nil, errors.New("credentials: unable to parse credential source")
+}
+
+func getGoogHeaderValue(conf *Options, p subjectTokenProvider) string {
+ return fmt.Sprintf("gl-go/%s auth/%s google-byoid-sdk source/%s sa-impersonation/%t config-lifetime/%t",
+ goVersion(),
+ "unknown",
+ p.providerType(),
+ conf.ServiceAccountImpersonationURL != "",
+ conf.ServiceAccountImpersonationLifetimeSeconds != 0)
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
new file mode 100644
index 0000000..8186939
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/file_provider.go
@@ -0,0 +1,78 @@
+// 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 externalaccount
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "os"
+
+ "cloud.google.com/go/auth/internal"
+ "cloud.google.com/go/auth/internal/credsfile"
+)
+
+const (
+ fileProviderType = "file"
+)
+
+type fileSubjectProvider struct {
+ File string
+ Format *credsfile.Format
+}
+
+func (sp *fileSubjectProvider) subjectToken(context.Context) (string, error) {
+ tokenFile, err := os.Open(sp.File)
+ if err != nil {
+ return "", fmt.Errorf("credentials: failed to open credential file %q: %w", sp.File, err)
+ }
+ defer tokenFile.Close()
+ tokenBytes, err := internal.ReadAll(tokenFile)
+ if err != nil {
+ return "", fmt.Errorf("credentials: failed to read credential file: %w", err)
+ }
+ tokenBytes = bytes.TrimSpace(tokenBytes)
+
+ if sp.Format == nil {
+ return string(tokenBytes), nil
+ }
+ switch sp.Format.Type {
+ case fileTypeJSON:
+ jsonData := make(map[string]interface{})
+ err = json.Unmarshal(tokenBytes, &jsonData)
+ if err != nil {
+ return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
+ }
+ val, ok := jsonData[sp.Format.SubjectTokenFieldName]
+ if !ok {
+ return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
+ }
+ token, ok := val.(string)
+ if !ok {
+ return "", errors.New("credentials: improperly formatted subject token")
+ }
+ return token, nil
+ case fileTypeText:
+ return string(tokenBytes), nil
+ default:
+ return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
+ }
+}
+
+func (sp *fileSubjectProvider) providerType() string {
+ return fileProviderType
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
new file mode 100644
index 0000000..8e4b437
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/info.go
@@ -0,0 +1,74 @@
+// 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 externalaccount
+
+import (
+ "runtime"
+ "strings"
+ "unicode"
+)
+
+var (
+ // version is a package internal global variable for testing purposes.
+ version = runtime.Version
+)
+
+// versionUnknown is only used when the runtime version cannot be determined.
+const versionUnknown = "UNKNOWN"
+
+// goVersion returns a Go runtime version derived from the runtime environment
+// that is modified to be suitable for reporting in a header, meaning it has no
+// whitespace. If it is unable to determine the Go runtime version, it returns
+// versionUnknown.
+func goVersion() string {
+ const develPrefix = "devel +"
+
+ s := version()
+ if strings.HasPrefix(s, develPrefix) {
+ s = s[len(develPrefix):]
+ if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
+ s = s[:p]
+ }
+ return s
+ } else if p := strings.IndexFunc(s, unicode.IsSpace); p >= 0 {
+ s = s[:p]
+ }
+
+ notSemverRune := func(r rune) bool {
+ return !strings.ContainsRune("0123456789.", r)
+ }
+
+ if strings.HasPrefix(s, "go1") {
+ s = s[2:]
+ var prerelease string
+ if p := strings.IndexFunc(s, notSemverRune); p >= 0 {
+ s, prerelease = s[:p], s[p:]
+ }
+ if strings.HasSuffix(s, ".") {
+ s += "0"
+ } else if strings.Count(s, ".") < 2 {
+ s += ".0"
+ }
+ if prerelease != "" {
+ // Some release candidates already have a dash in them.
+ if !strings.HasPrefix(prerelease, "-") {
+ prerelease = "-" + prerelease
+ }
+ s += prerelease
+ }
+ return s
+ }
+ return versionUnknown
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
new file mode 100644
index 0000000..be3c873
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/programmatic_provider.go
@@ -0,0 +1,30 @@
+// Copyright 2024 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 externalaccount
+
+import "context"
+
+type programmaticProvider struct {
+ opts *RequestOptions
+ stp SubjectTokenProvider
+}
+
+func (pp *programmaticProvider) providerType() string {
+ return programmaticProviderType
+}
+
+func (pp *programmaticProvider) subjectToken(ctx context.Context) (string, error) {
+ return pp.stp.SubjectToken(ctx, pp.opts)
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
new file mode 100644
index 0000000..754ecf4
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/url_provider.go
@@ -0,0 +1,93 @@
+// 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 externalaccount
+
+import (
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/http"
+
+ "cloud.google.com/go/auth/internal"
+ "cloud.google.com/go/auth/internal/credsfile"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+const (
+ fileTypeText = "text"
+ fileTypeJSON = "json"
+ urlProviderType = "url"
+ programmaticProviderType = "programmatic"
+ x509ProviderType = "x509"
+)
+
+type urlSubjectProvider struct {
+ URL string
+ Headers map[string]string
+ Format *credsfile.Format
+ Client *http.Client
+ Logger *slog.Logger
+}
+
+func (sp *urlSubjectProvider) subjectToken(ctx context.Context) (string, error) {
+ req, err := http.NewRequestWithContext(ctx, "GET", sp.URL, nil)
+ if err != nil {
+ return "", fmt.Errorf("credentials: HTTP request for URL-sourced credential failed: %w", err)
+ }
+
+ for key, val := range sp.Headers {
+ req.Header.Add(key, val)
+ }
+ sp.Logger.DebugContext(ctx, "url subject token request", "request", internallog.HTTPRequest(req, nil))
+ resp, body, err := internal.DoRequest(sp.Client, req)
+ if err != nil {
+ return "", fmt.Errorf("credentials: invalid response when retrieving subject token: %w", err)
+ }
+ sp.Logger.DebugContext(ctx, "url subject token response", "response", internallog.HTTPResponse(resp, body))
+ if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
+ return "", fmt.Errorf("credentials: status code %d: %s", c, body)
+ }
+
+ if sp.Format == nil {
+ return string(body), nil
+ }
+ switch sp.Format.Type {
+ case "json":
+ jsonData := make(map[string]interface{})
+ err = json.Unmarshal(body, &jsonData)
+ if err != nil {
+ return "", fmt.Errorf("credentials: failed to unmarshal subject token file: %w", err)
+ }
+ val, ok := jsonData[sp.Format.SubjectTokenFieldName]
+ if !ok {
+ return "", errors.New("credentials: provided subject_token_field_name not found in credentials")
+ }
+ token, ok := val.(string)
+ if !ok {
+ return "", errors.New("credentials: improperly formatted subject token")
+ }
+ return token, nil
+ case fileTypeText:
+ return string(body), nil
+ default:
+ return "", errors.New("credentials: invalid credential_source file format type: " + sp.Format.Type)
+ }
+}
+
+func (sp *urlSubjectProvider) providerType() string {
+ return urlProviderType
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
new file mode 100644
index 0000000..d86ca59
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccount/x509_provider.go
@@ -0,0 +1,220 @@
+// Copyright 2024 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 externalaccount
+
+import (
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/base64"
+ "encoding/json"
+ "encoding/pem"
+ "errors"
+ "fmt"
+ "io/fs"
+ "net/http"
+ "os"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth/internal/transport/cert"
+)
+
+// x509Provider implements the subjectTokenProvider type for x509 workload
+// identity credentials. This provider retrieves and formats a JSON array
+// containing the leaf certificate and trust chain (if provided) as
+// base64-encoded strings. This JSON array serves as the subject token for
+// mTLS authentication.
+type x509Provider struct {
+ // TrustChainPath is the path to the file containing the trust chain certificates.
+ // The file should contain one or more PEM-encoded certificates.
+ TrustChainPath string
+ // ConfigFilePath is the path to the configuration file containing the path
+ // to the leaf certificate file.
+ ConfigFilePath string
+}
+
+const pemCertificateHeader = "-----BEGIN CERTIFICATE-----"
+
+func (xp *x509Provider) providerType() string {
+ return x509ProviderType
+}
+
+// loadLeafCertificate loads and parses the leaf certificate from the specified
+// configuration file. It retrieves the certificate path from the config file,
+// reads the certificate file, and parses the certificate data.
+func loadLeafCertificate(configFilePath string) (*x509.Certificate, error) {
+ // Get the path to the certificate file from the configuration file.
+ path, err := cert.GetCertificatePath(configFilePath)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get certificate path from config file: %w", err)
+ }
+ leafCertBytes, err := os.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("failed to read leaf certificate file: %w", err)
+ }
+ // Parse the certificate bytes.
+ return parseCertificate(leafCertBytes)
+}
+
+// encodeCert encodes a x509.Certificate to a base64 string.
+func encodeCert(cert *x509.Certificate) string {
+ // cert.Raw contains the raw DER-encoded certificate. Encode the raw certificate bytes to base64.
+ return base64.StdEncoding.EncodeToString(cert.Raw)
+}
+
+// parseCertificate parses a PEM-encoded certificate from the given byte slice.
+func parseCertificate(certData []byte) (*x509.Certificate, error) {
+ if len(certData) == 0 {
+ return nil, errors.New("invalid certificate data: empty input")
+ }
+ // Decode the PEM-encoded data.
+ block, _ := pem.Decode(certData)
+ if block == nil {
+ return nil, errors.New("invalid PEM-encoded certificate data: no PEM block found")
+ }
+ if block.Type != "CERTIFICATE" {
+ return nil, fmt.Errorf("invalid PEM-encoded certificate data: expected CERTIFICATE block type, got %s", block.Type)
+ }
+ // Parse the DER-encoded certificate.
+ certificate, err := x509.ParseCertificate(block.Bytes)
+ if err != nil {
+ return nil, fmt.Errorf("failed to parse certificate: %w", err)
+ }
+ return certificate, nil
+}
+
+// readTrustChain reads a file of PEM-encoded X.509 certificates and returns a slice of parsed certificates.
+// It splits the file content into PEM certificate blocks and parses each one.
+func readTrustChain(trustChainPath string) ([]*x509.Certificate, error) {
+ certificateTrustChain := []*x509.Certificate{}
+
+ // If no trust chain path is provided, return an empty slice.
+ if trustChainPath == "" {
+ return certificateTrustChain, nil
+ }
+
+ // Read the trust chain file.
+ trustChainData, err := os.ReadFile(trustChainPath)
+ if err != nil {
+ if errors.Is(err, fs.ErrNotExist) {
+ return nil, fmt.Errorf("trust chain file not found: %w", err)
+ }
+ return nil, fmt.Errorf("failed to read trust chain file: %w", err)
+ }
+
+ // Split the file content into PEM certificate blocks.
+ certBlocks := strings.Split(string(trustChainData), pemCertificateHeader)
+
+ // Iterate over each certificate block.
+ for _, certBlock := range certBlocks {
+ // Trim whitespace from the block.
+ certBlock = strings.TrimSpace(certBlock)
+
+ if certBlock != "" {
+ // Add the PEM header to the block.
+ certData := pemCertificateHeader + "\n" + certBlock
+
+ // Parse the certificate data.
+ cert, err := parseCertificate([]byte(certData))
+ if err != nil {
+ return nil, fmt.Errorf("error parsing certificate from trust chain file: %w", err)
+ }
+
+ // Append the certificate to the trust chain.
+ certificateTrustChain = append(certificateTrustChain, cert)
+ }
+ }
+
+ return certificateTrustChain, nil
+}
+
+// subjectToken retrieves the X.509 subject token. It loads the leaf
+// certificate and, if a trust chain path is configured, the trust chain
+// certificates. It then constructs a JSON array containing the base64-encoded
+// leaf certificate and each base64-encoded certificate in the trust chain.
+// The leaf certificate must be at the top of the trust chain file. This JSON
+// array is used as the subject token for mTLS authentication.
+func (xp *x509Provider) subjectToken(context.Context) (string, error) {
+ // Load the leaf certificate.
+ leafCert, err := loadLeafCertificate(xp.ConfigFilePath)
+ if err != nil {
+ return "", fmt.Errorf("failed to load leaf certificate: %w", err)
+ }
+
+ // Read the trust chain.
+ trustChain, err := readTrustChain(xp.TrustChainPath)
+ if err != nil {
+ return "", fmt.Errorf("failed to read trust chain: %w", err)
+ }
+
+ // Initialize the certificate chain with the leaf certificate.
+ certChain := []string{encodeCert(leafCert)}
+
+ // If there is a trust chain, add certificates to the certificate chain.
+ if len(trustChain) > 0 {
+ firstCert := encodeCert(trustChain[0])
+
+ // If the first certificate in the trust chain is not the same as the leaf certificate, add it to the chain.
+ if firstCert != certChain[0] {
+ certChain = append(certChain, firstCert)
+ }
+
+ // Iterate over the remaining certificates in the trust chain.
+ for i := 1; i < len(trustChain); i++ {
+ encoded := encodeCert(trustChain[i])
+
+ // Return an error if the current certificate is the same as the leaf certificate.
+ if encoded == certChain[0] {
+ return "", errors.New("the leaf certificate must be at the top of the trust chain file")
+ }
+
+ // Add the current certificate to the chain.
+ certChain = append(certChain, encoded)
+ }
+ }
+
+ // Convert the certificate chain to a JSON array of base64-encoded strings.
+ jsonChain, err := json.Marshal(certChain)
+ if err != nil {
+ return "", fmt.Errorf("failed to format certificate data: %w", err)
+ }
+
+ // Return the JSON-formatted certificate chain.
+ return string(jsonChain), nil
+
+}
+
+// createX509Client creates a new client that is configured with mTLS, using the
+// certificate configuration specified in the credential source.
+func createX509Client(certificateConfigLocation string) (*http.Client, error) {
+ certProvider, err := cert.NewWorkloadX509CertProvider(certificateConfigLocation)
+ if err != nil {
+ return nil, err
+ }
+ trans := http.DefaultTransport.(*http.Transport).Clone()
+
+ trans.TLSClientConfig = &tls.Config{
+ GetClientCertificate: certProvider,
+ }
+
+ // Create a client with default settings plus the X509 workload cert and key.
+ client := &http.Client{
+ Transport: trans,
+ Timeout: 30 * time.Second,
+ }
+
+ return client, nil
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
new file mode 100644
index 0000000..ae39206
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/externalaccountuser/externalaccountuser.go
@@ -0,0 +1,115 @@
+// 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 externalaccountuser
+
+import (
+ "context"
+ "errors"
+ "log/slog"
+ "net/http"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/credentials/internal/stsexchange"
+ "cloud.google.com/go/auth/internal"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+// Options stores the configuration for fetching tokens with external authorized
+// user credentials.
+type Options struct {
+ // Audience is the Secure Token Service (STS) audience which contains the
+ // resource name for the workforce pool and the provider identifier in that
+ // pool.
+ Audience string
+ // RefreshToken is the OAuth 2.0 refresh token.
+ RefreshToken string
+ // TokenURL is the STS token exchange endpoint for refresh.
+ TokenURL string
+ // TokenInfoURL is the STS endpoint URL for token introspection. Optional.
+ TokenInfoURL string
+ // ClientID is only required in conjunction with ClientSecret, as described
+ // below.
+ ClientID string
+ // ClientSecret is currently only required if token_info endpoint also needs
+ // to be called with the generated a cloud access token. When provided, STS
+ // will be called with additional basic authentication using client_id as
+ // username and client_secret as password.
+ ClientSecret string
+ // Scopes contains the desired scopes for the returned access token.
+ Scopes []string
+
+ // Client for token request.
+ Client *http.Client
+ // Logger for logging.
+ Logger *slog.Logger
+}
+
+func (c *Options) validate() bool {
+ return c.ClientID != "" && c.ClientSecret != "" && c.RefreshToken != "" && c.TokenURL != ""
+}
+
+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider]
+// configured with the provided options.
+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
+ if !opts.validate() {
+ return nil, errors.New("credentials: invalid external_account_authorized_user configuration")
+ }
+
+ tp := &tokenProvider{
+ o: opts,
+ }
+ return auth.NewCachedTokenProvider(tp, nil), nil
+}
+
+type tokenProvider struct {
+ o *Options
+}
+
+func (tp *tokenProvider) Token(ctx context.Context) (*auth.Token, error) {
+ opts := tp.o
+
+ clientAuth := stsexchange.ClientAuthentication{
+ AuthStyle: auth.StyleInHeader,
+ ClientID: opts.ClientID,
+ ClientSecret: opts.ClientSecret,
+ }
+ headers := make(http.Header)
+ headers.Set("Content-Type", "application/x-www-form-urlencoded")
+ stsResponse, err := stsexchange.RefreshAccessToken(ctx, &stsexchange.Options{
+ Client: opts.Client,
+ Endpoint: opts.TokenURL,
+ RefreshToken: opts.RefreshToken,
+ Authentication: clientAuth,
+ Headers: headers,
+ Logger: internallog.New(tp.o.Logger),
+ })
+ if err != nil {
+ return nil, err
+ }
+ if stsResponse.ExpiresIn < 0 {
+ return nil, errors.New("credentials: invalid expiry from security token service")
+ }
+
+ // guarded by the wrapping with CachedTokenProvider
+ if stsResponse.RefreshToken != "" {
+ opts.RefreshToken = stsResponse.RefreshToken
+ }
+ return &auth.Token{
+ Value: stsResponse.AccessToken,
+ Expiry: time.Now().UTC().Add(time.Duration(stsResponse.ExpiresIn) * time.Second),
+ Type: internal.TokenTypeBearer,
+ }, nil
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go b/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
new file mode 100644
index 0000000..c2d320f
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/gdch/gdch.go
@@ -0,0 +1,191 @@
+// 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 gdch
+
+import (
+ "context"
+ "crypto"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "os"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/internal"
+ "cloud.google.com/go/auth/internal/credsfile"
+ "cloud.google.com/go/auth/internal/jwt"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+const (
+ // GrantType is the grant type for the token request.
+ GrantType = "urn:ietf:params:oauth:token-type:token-exchange"
+ requestTokenType = "urn:ietf:params:oauth:token-type:access_token"
+ subjectTokenType = "urn:k8s:params:oauth:token-type:serviceaccount"
+)
+
+var (
+ gdchSupportFormatVersions map[string]bool = map[string]bool{
+ "1": true,
+ }
+)
+
+// Options for [NewTokenProvider].
+type Options struct {
+ STSAudience string
+ Client *http.Client
+ Logger *slog.Logger
+}
+
+// NewTokenProvider returns a [cloud.google.com/go/auth.TokenProvider] from a
+// GDCH cred file.
+func NewTokenProvider(f *credsfile.GDCHServiceAccountFile, o *Options) (auth.TokenProvider, error) {
+ if !gdchSupportFormatVersions[f.FormatVersion] {
+ return nil, fmt.Errorf("credentials: unsupported gdch_service_account format %q", f.FormatVersion)
+ }
+ if o.STSAudience == "" {
+ return nil, errors.New("credentials: STSAudience must be set for the GDCH auth flows")
+ }
+ signer, err := internal.ParseKey([]byte(f.PrivateKey))
+ if err != nil {
+ return nil, err
+ }
+ certPool, err := loadCertPool(f.CertPath)
+ if err != nil {
+ return nil, err
+ }
+
+ tp := gdchProvider{
+ serviceIdentity: fmt.Sprintf("system:serviceaccount:%s:%s", f.Project, f.Name),
+ tokenURL: f.TokenURL,
+ aud: o.STSAudience,
+ signer: signer,
+ pkID: f.PrivateKeyID,
+ certPool: certPool,
+ client: o.Client,
+ logger: internallog.New(o.Logger),
+ }
+ return tp, nil
+}
+
+func loadCertPool(path string) (*x509.CertPool, error) {
+ pool := x509.NewCertPool()
+ pem, err := os.ReadFile(path)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: failed to read certificate: %w", err)
+ }
+ pool.AppendCertsFromPEM(pem)
+ return pool, nil
+}
+
+type gdchProvider struct {
+ serviceIdentity string
+ tokenURL string
+ aud string
+ signer crypto.Signer
+ pkID string
+ certPool *x509.CertPool
+
+ client *http.Client
+ logger *slog.Logger
+}
+
+func (g gdchProvider) Token(ctx context.Context) (*auth.Token, error) {
+ addCertToTransport(g.client, g.certPool)
+ iat := time.Now()
+ exp := iat.Add(time.Hour)
+ claims := jwt.Claims{
+ Iss: g.serviceIdentity,
+ Sub: g.serviceIdentity,
+ Aud: g.tokenURL,
+ Iat: iat.Unix(),
+ Exp: exp.Unix(),
+ }
+ h := jwt.Header{
+ Algorithm: jwt.HeaderAlgRSA256,
+ Type: jwt.HeaderType,
+ KeyID: string(g.pkID),
+ }
+ payload, err := jwt.EncodeJWS(&h, &claims, g.signer)
+ if err != nil {
+ return nil, err
+ }
+ v := url.Values{}
+ v.Set("grant_type", GrantType)
+ v.Set("audience", g.aud)
+ v.Set("requested_token_type", requestTokenType)
+ v.Set("subject_token", payload)
+ v.Set("subject_token_type", subjectTokenType)
+
+ req, err := http.NewRequestWithContext(ctx, "POST", g.tokenURL, strings.NewReader(v.Encode()))
+ if err != nil {
+ return nil, err
+ }
+ req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
+ g.logger.DebugContext(ctx, "gdch token request", "request", internallog.HTTPRequest(req, []byte(v.Encode())))
+ resp, body, err := internal.DoRequest(g.client, req)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
+ }
+ g.logger.DebugContext(ctx, "gdch token response", "response", internallog.HTTPResponse(resp, body))
+ if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
+ return nil, &auth.Error{
+ Response: resp,
+ Body: body,
+ }
+ }
+
+ var tokenRes struct {
+ AccessToken string `json:"access_token"`
+ TokenType string `json:"token_type"`
+ ExpiresIn int64 `json:"expires_in"` // relative seconds from now
+ }
+ if err := json.Unmarshal(body, &tokenRes); err != nil {
+ return nil, fmt.Errorf("credentials: cannot fetch token: %w", err)
+ }
+ token := &auth.Token{
+ Value: tokenRes.AccessToken,
+ Type: tokenRes.TokenType,
+ }
+ raw := make(map[string]interface{})
+ json.Unmarshal(body, &raw) // no error checks for optional fields
+ token.Metadata = raw
+
+ if secs := tokenRes.ExpiresIn; secs > 0 {
+ token.Expiry = time.Now().Add(time.Duration(secs) * time.Second)
+ }
+ return token, nil
+}
+
+// addCertToTransport makes a best effort attempt at adding in the cert info to
+// the client. It tries to keep all configured transport settings if the
+// underlying transport is an http.Transport. Or else it overwrites the
+// transport with defaults adding in the certs.
+func addCertToTransport(hc *http.Client, certPool *x509.CertPool) {
+ trans, ok := hc.Transport.(*http.Transport)
+ if !ok {
+ trans = http.DefaultTransport.(*http.Transport).Clone()
+ }
+ trans.TLSClientConfig = &tls.Config{
+ RootCAs: certPool,
+ }
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go b/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go
new file mode 100644
index 0000000..705462c
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/idtoken.go
@@ -0,0 +1,105 @@
+// Copyright 2025 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 impersonate
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/internal"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+var (
+ universeDomainPlaceholder = "UNIVERSE_DOMAIN"
+ iamCredentialsUniverseDomainEndpoint = "https://iamcredentials.UNIVERSE_DOMAIN"
+)
+
+// IDTokenIAMOptions provides configuration for [IDTokenIAMOptions.Token].
+type IDTokenIAMOptions struct {
+ // Client is required.
+ Client *http.Client
+ // Logger is required.
+ Logger *slog.Logger
+ UniverseDomain auth.CredentialsPropertyProvider
+ ServiceAccountEmail string
+ GenerateIDTokenRequest
+}
+
+// GenerateIDTokenRequest holds the request to the IAM generateIdToken RPC.
+type GenerateIDTokenRequest struct {
+ Audience string `json:"audience"`
+ IncludeEmail bool `json:"includeEmail"`
+ // Delegates are the ordered, fully-qualified resource name for service
+ // accounts in a delegation chain. Each service account must be granted
+ // roles/iam.serviceAccountTokenCreator on the next service account in the
+ // chain. The delegates must have the following format:
+ // projects/-/serviceAccounts/{ACCOUNT_EMAIL_OR_UNIQUEID}. The - wildcard
+ // character is required; replacing it with a project ID is invalid.
+ // Optional.
+ Delegates []string `json:"delegates,omitempty"`
+}
+
+// GenerateIDTokenResponse holds the response from the IAM generateIdToken RPC.
+type GenerateIDTokenResponse struct {
+ Token string `json:"token"`
+}
+
+// Token call IAM generateIdToken with the configuration provided in [IDTokenIAMOptions].
+func (o IDTokenIAMOptions) Token(ctx context.Context) (*auth.Token, error) {
+ universeDomain, err := o.UniverseDomain.GetProperty(ctx)
+ if err != nil {
+ return nil, err
+ }
+ endpoint := strings.Replace(iamCredentialsUniverseDomainEndpoint, universeDomainPlaceholder, universeDomain, 1)
+ url := fmt.Sprintf("%s/v1/%s:generateIdToken", endpoint, internal.FormatIAMServiceAccountResource(o.ServiceAccountEmail))
+
+ bodyBytes, err := json.Marshal(o.GenerateIDTokenRequest)
+ if err != nil {
+ return nil, fmt.Errorf("impersonate: unable to marshal request: %w", err)
+ }
+
+ req, err := http.NewRequestWithContext(ctx, "POST", url, bytes.NewReader(bodyBytes))
+ if err != nil {
+ return nil, fmt.Errorf("impersonate: unable to create request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+ o.Logger.DebugContext(ctx, "impersonated idtoken request", "request", internallog.HTTPRequest(req, bodyBytes))
+ resp, body, err := internal.DoRequest(o.Client, req)
+ if err != nil {
+ return nil, fmt.Errorf("impersonate: unable to generate ID token: %w", err)
+ }
+ o.Logger.DebugContext(ctx, "impersonated idtoken response", "response", internallog.HTTPResponse(resp, body))
+ if c := resp.StatusCode; c < 200 || c > 299 {
+ return nil, fmt.Errorf("impersonate: status code %d: %s", c, body)
+ }
+
+ var tokenResp GenerateIDTokenResponse
+ if err := json.Unmarshal(body, &tokenResp); err != nil {
+ return nil, fmt.Errorf("impersonate: unable to parse response: %w", err)
+ }
+ return &auth.Token{
+ Value: tokenResp.Token,
+ // Generated ID tokens are good for one hour.
+ Expiry: time.Now().Add(1 * time.Hour),
+ }, nil
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go b/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
new file mode 100644
index 0000000..b3a9926
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/impersonate/impersonate.go
@@ -0,0 +1,156 @@
+// 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 impersonate
+
+import (
+ "bytes"
+ "context"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/internal"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+const (
+ defaultTokenLifetime = "3600s"
+ authHeaderKey = "Authorization"
+)
+
+// generateAccesstokenReq is used for service account impersonation
+type generateAccessTokenReq struct {
+ Delegates []string `json:"delegates,omitempty"`
+ Lifetime string `json:"lifetime,omitempty"`
+ Scope []string `json:"scope,omitempty"`
+}
+
+type impersonateTokenResponse struct {
+ AccessToken string `json:"accessToken"`
+ ExpireTime string `json:"expireTime"`
+}
+
+// NewTokenProvider uses a source credential, stored in Ts, to request an access token to the provided URL.
+// Scopes can be defined when the access token is requested.
+func NewTokenProvider(opts *Options) (auth.TokenProvider, error) {
+ if err := opts.validate(); err != nil {
+ return nil, err
+ }
+ return opts, nil
+}
+
+// Options for [NewTokenProvider].
+type Options struct {
+ // Tp is the source credential used to generate a token on the
+ // impersonated service account. Required.
+ Tp auth.TokenProvider
+
+ // URL is the endpoint to call to generate a token
+ // on behalf of the service account. Required.
+ URL string
+ // Scopes that the impersonated credential should have. Required.
+ Scopes []string
+ // Delegates are the service account email addresses in a delegation chain.
+ // Each service account must be granted roles/iam.serviceAccountTokenCreator
+ // on the next service account in the chain. Optional.
+ Delegates []string
+ // TokenLifetimeSeconds is the number of seconds the impersonation token will
+ // be valid for. Defaults to 1 hour if unset. Optional.
+ TokenLifetimeSeconds int
+ // Client configures the underlying client used to make network requests
+ // when fetching tokens. Required.
+ Client *http.Client
+ // Logger is used for debug logging. If provided, logging will be enabled
+ // at the loggers configured level. By default logging is disabled unless
+ // enabled by setting GOOGLE_SDK_GO_LOGGING_LEVEL in which case a default
+ // logger will be used. Optional.
+ Logger *slog.Logger
+}
+
+func (o *Options) validate() error {
+ if o.Tp == nil {
+ return errors.New("credentials: missing required 'source_credentials' field in impersonated credentials")
+ }
+ if o.URL == "" {
+ return errors.New("credentials: missing required 'service_account_impersonation_url' field in impersonated credentials")
+ }
+ return nil
+}
+
+// Token performs the exchange to get a temporary service account token to allow access to GCP.
+func (o *Options) Token(ctx context.Context) (*auth.Token, error) {
+ logger := internallog.New(o.Logger)
+ lifetime := defaultTokenLifetime
+ if o.TokenLifetimeSeconds != 0 {
+ lifetime = fmt.Sprintf("%ds", o.TokenLifetimeSeconds)
+ }
+ reqBody := generateAccessTokenReq{
+ Lifetime: lifetime,
+ Scope: o.Scopes,
+ Delegates: o.Delegates,
+ }
+ b, err := json.Marshal(reqBody)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: unable to marshal request: %w", err)
+ }
+ req, err := http.NewRequestWithContext(ctx, "POST", o.URL, bytes.NewReader(b))
+ if err != nil {
+ return nil, fmt.Errorf("credentials: unable to create impersonation request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+ if err := setAuthHeader(ctx, o.Tp, req); err != nil {
+ return nil, err
+ }
+ logger.DebugContext(ctx, "impersonated token request", "request", internallog.HTTPRequest(req, b))
+ resp, body, err := internal.DoRequest(o.Client, req)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: unable to generate access token: %w", err)
+ }
+ logger.DebugContext(ctx, "impersonated token response", "response", internallog.HTTPResponse(resp, body))
+ if c := resp.StatusCode; c < http.StatusOK || c >= http.StatusMultipleChoices {
+ return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
+ }
+
+ var accessTokenResp impersonateTokenResponse
+ if err := json.Unmarshal(body, &accessTokenResp); err != nil {
+ return nil, fmt.Errorf("credentials: unable to parse response: %w", err)
+ }
+ expiry, err := time.Parse(time.RFC3339, accessTokenResp.ExpireTime)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: unable to parse expiry: %w", err)
+ }
+ return &auth.Token{
+ Value: accessTokenResp.AccessToken,
+ Expiry: expiry,
+ Type: internal.TokenTypeBearer,
+ }, nil
+}
+
+func setAuthHeader(ctx context.Context, tp auth.TokenProvider, r *http.Request) error {
+ t, err := tp.Token(ctx)
+ if err != nil {
+ return err
+ }
+ typ := t.Type
+ if typ == "" {
+ typ = internal.TokenTypeBearer
+ }
+ r.Header.Set(authHeaderKey, typ+" "+t.Value)
+ return nil
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go b/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
new file mode 100644
index 0000000..e1d2b15
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/internal/stsexchange/sts_exchange.go
@@ -0,0 +1,167 @@
+// 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 stsexchange
+
+import (
+ "context"
+ "encoding/base64"
+ "encoding/json"
+ "fmt"
+ "log/slog"
+ "net/http"
+ "net/url"
+ "strconv"
+ "strings"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/internal"
+ "github.com/googleapis/gax-go/v2/internallog"
+)
+
+const (
+ // GrantType for a sts exchange.
+ GrantType = "urn:ietf:params:oauth:grant-type:token-exchange"
+ // TokenType for a sts exchange.
+ TokenType = "urn:ietf:params:oauth:token-type:access_token"
+
+ jwtTokenType = "urn:ietf:params:oauth:token-type:jwt"
+)
+
+// Options stores the configuration for making an sts exchange request.
+type Options struct {
+ Client *http.Client
+ Logger *slog.Logger
+ Endpoint string
+ Request *TokenRequest
+ Authentication ClientAuthentication
+ Headers http.Header
+ // ExtraOpts are optional fields marshalled into the `options` field of the
+ // request body.
+ ExtraOpts map[string]interface{}
+ RefreshToken string
+}
+
+// RefreshAccessToken performs the token exchange using a refresh token flow.
+func RefreshAccessToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
+ data := url.Values{}
+ data.Set("grant_type", "refresh_token")
+ data.Set("refresh_token", opts.RefreshToken)
+ return doRequest(ctx, opts, data)
+}
+
+// ExchangeToken performs an oauth2 token exchange with the provided endpoint.
+func ExchangeToken(ctx context.Context, opts *Options) (*TokenResponse, error) {
+ data := url.Values{}
+ data.Set("audience", opts.Request.Audience)
+ data.Set("grant_type", GrantType)
+ data.Set("requested_token_type", TokenType)
+ data.Set("subject_token_type", opts.Request.SubjectTokenType)
+ data.Set("subject_token", opts.Request.SubjectToken)
+ data.Set("scope", strings.Join(opts.Request.Scope, " "))
+ if opts.ExtraOpts != nil {
+ opts, err := json.Marshal(opts.ExtraOpts)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: failed to marshal additional options: %w", err)
+ }
+ data.Set("options", string(opts))
+ }
+ return doRequest(ctx, opts, data)
+}
+
+func doRequest(ctx context.Context, opts *Options, data url.Values) (*TokenResponse, error) {
+ opts.Authentication.InjectAuthentication(data, opts.Headers)
+ encodedData := data.Encode()
+ logger := internallog.New(opts.Logger)
+
+ req, err := http.NewRequestWithContext(ctx, "POST", opts.Endpoint, strings.NewReader(encodedData))
+ if err != nil {
+ return nil, fmt.Errorf("credentials: failed to properly build http request: %w", err)
+
+ }
+ for key, list := range opts.Headers {
+ for _, val := range list {
+ req.Header.Add(key, val)
+ }
+ }
+ req.Header.Set("Content-Length", strconv.Itoa(len(encodedData)))
+
+ logger.DebugContext(ctx, "sts token request", "request", internallog.HTTPRequest(req, []byte(encodedData)))
+ resp, body, err := internal.DoRequest(opts.Client, req)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: invalid response from Secure Token Server: %w", err)
+ }
+ logger.DebugContext(ctx, "sts token response", "response", internallog.HTTPResponse(resp, body))
+ if c := resp.StatusCode; c < http.StatusOK || c > http.StatusMultipleChoices {
+ return nil, fmt.Errorf("credentials: status code %d: %s", c, body)
+ }
+ var stsResp TokenResponse
+ if err := json.Unmarshal(body, &stsResp); err != nil {
+ return nil, fmt.Errorf("credentials: failed to unmarshal response body from Secure Token Server: %w", err)
+ }
+
+ return &stsResp, nil
+}
+
+// TokenRequest contains fields necessary to make an oauth2 token
+// exchange.
+type TokenRequest struct {
+ ActingParty struct {
+ ActorToken string
+ ActorTokenType string
+ }
+ GrantType string
+ Resource string
+ Audience string
+ Scope []string
+ RequestedTokenType string
+ SubjectToken string
+ SubjectTokenType string
+}
+
+// TokenResponse is used to decode the remote server response during
+// an oauth2 token exchange.
+type TokenResponse struct {
+ AccessToken string `json:"access_token"`
+ IssuedTokenType string `json:"issued_token_type"`
+ TokenType string `json:"token_type"`
+ ExpiresIn int `json:"expires_in"`
+ Scope string `json:"scope"`
+ RefreshToken string `json:"refresh_token"`
+}
+
+// ClientAuthentication represents an OAuth client ID and secret and the
+// mechanism for passing these credentials as stated in rfc6749#2.3.1.
+type ClientAuthentication struct {
+ AuthStyle auth.Style
+ ClientID string
+ ClientSecret string
+}
+
+// InjectAuthentication is used to add authentication to a Secure Token Service
+// exchange request. It modifies either the passed url.Values or http.Header
+// depending on the desired authentication format.
+func (c *ClientAuthentication) InjectAuthentication(values url.Values, headers http.Header) {
+ if c.ClientID == "" || c.ClientSecret == "" || values == nil || headers == nil {
+ return
+ }
+ switch c.AuthStyle {
+ case auth.StyleInHeader:
+ plainHeader := c.ClientID + ":" + c.ClientSecret
+ headers.Set("Authorization", "Basic "+base64.StdEncoding.EncodeToString([]byte(plainHeader)))
+ default:
+ values.Set("client_id", c.ClientID)
+ values.Set("client_secret", c.ClientSecret)
+ }
+}
diff --git a/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go b/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
new file mode 100644
index 0000000..8d335cc
--- /dev/null
+++ b/vendor/cloud.google.com/go/auth/credentials/selfsignedjwt.go
@@ -0,0 +1,89 @@
+// 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 (
+ "context"
+ "crypto"
+ "errors"
+ "fmt"
+ "log/slog"
+ "strings"
+ "time"
+
+ "cloud.google.com/go/auth"
+ "cloud.google.com/go/auth/internal"
+ "cloud.google.com/go/auth/internal/credsfile"
+ "cloud.google.com/go/auth/internal/jwt"
+)
+
+var (
+ // for testing
+ now func() time.Time = time.Now
+)
+
+// configureSelfSignedJWT uses the private key in the service account to create
+// a JWT without making a network call.
+func configureSelfSignedJWT(f *credsfile.ServiceAccountFile, opts *DetectOptions) (auth.TokenProvider, error) {
+ if len(opts.scopes()) == 0 && opts.Audience == "" {
+ return nil, errors.New("credentials: both scopes and audience are empty")
+ }
+ signer, err := internal.ParseKey([]byte(f.PrivateKey))
+ if err != nil {
+ return nil, fmt.Errorf("credentials: could not parse key: %w", err)
+ }
+ return &selfSignedTokenProvider{
+ email: f.ClientEmail,
+ audience: opts.Audience,
+ scopes: opts.scopes(),
+ signer: signer,
+ pkID: f.PrivateKeyID,
+ logger: opts.logger(),
+ }, nil
+}
+
+type selfSignedTokenProvider struct {
+ email string
+ audience string
+ scopes []string
+ signer crypto.Signer
+ pkID string
+ logger *slog.Logger
+}
+
+func (tp *selfSignedTokenProvider) Token(context.Context) (*auth.Token, error) {
+ iat := now()
+ exp := iat.Add(time.Hour)
+ scope := strings.Join(tp.scopes, " ")
+ c := &jwt.Claims{
+ Iss: tp.email,
+ Sub: tp.email,
+ Aud: tp.audience,
+ Scope: scope,
+ Iat: iat.Unix(),
+ Exp: exp.Unix(),
+ }
+ h := &jwt.Header{
+ Algorithm: jwt.HeaderAlgRSA256,
+ Type: jwt.HeaderType,
+ KeyID: string(tp.pkID),
+ }
+ tok, err := jwt.EncodeJWS(h, c, tp.signer)
+ if err != nil {
+ return nil, fmt.Errorf("credentials: could not encode JWT: %w", err)
+ }
+ tp.logger.Debug("created self-signed JWT", "token", tok)
+ return &auth.Token{Value: tok, Type: internal.TokenTypeBearer, Expiry: exp}, nil
+}