...

Source file src/cloud.google.com/go/auth/credentials/externalaccount/externalaccount.go

Documentation: cloud.google.com/go/auth/credentials/externalaccount

     1  // Copyright 2024 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package externalaccount
    16  
    17  import (
    18  	"context"
    19  	"fmt"
    20  	"net/http"
    21  
    22  	"cloud.google.com/go/auth"
    23  	iexacc "cloud.google.com/go/auth/credentials/internal/externalaccount"
    24  	"cloud.google.com/go/auth/internal"
    25  	"cloud.google.com/go/auth/internal/credsfile"
    26  )
    27  
    28  // Options for creating a [cloud.google.com/go/auth.Credentials].
    29  type Options struct {
    30  	// Audience is the Secure Token Service (STS) audience which contains the
    31  	// resource name for the workload identity pool or the workforce pool and
    32  	// the provider identifier in that pool. Required.
    33  	Audience string
    34  	// SubjectTokenType is the STS token type based on the Oauth2.0 token
    35  	// exchange spec. Expected values include:
    36  	// - “urn:ietf:params:oauth:token-type:jwt”
    37  	// - “urn:ietf:params:oauth:token-type:id-token”
    38  	// - “urn:ietf:params:oauth:token-type:saml2”
    39  	// - “urn:ietf:params:aws:token-type:aws4_request”
    40  	// Required.
    41  	SubjectTokenType string
    42  	// TokenURL is the STS token exchange endpoint. If not provided, will
    43  	// default to https://sts.UNIVERSE_DOMAIN/v1/token, with UNIVERSE_DOMAIN set
    44  	// to the default service domain googleapis.com unless UniverseDomain is
    45  	// set. Optional.
    46  	TokenURL string
    47  	// TokenInfoURL is the token_info endpoint used to retrieve the account
    48  	// related information (user attributes like account identifier, eg. email,
    49  	// username, uid, etc). This is needed for gCloud session account
    50  	// identification. Optional.
    51  	TokenInfoURL string
    52  	// ServiceAccountImpersonationURL is the URL for the service account
    53  	// impersonation request. This is only required for workload identity pools
    54  	// when APIs to be accessed have not integrated with UberMint.
    55  	ServiceAccountImpersonationURL string
    56  	// ServiceAccountImpersonationLifetimeSeconds is the number of seconds the
    57  	// service account impersonation token will be valid for.
    58  	ServiceAccountImpersonationLifetimeSeconds int
    59  	// ClientSecret is currently only required if token_info endpoint also
    60  	// needs to be called with the generated GCP access token. When provided,
    61  	// STS will be called with additional basic authentication using client_id
    62  	// as username and client_secret as password. Optional.
    63  	ClientSecret string
    64  	// ClientID is only required in conjunction with ClientSecret, as described
    65  	// above. Optional.
    66  	ClientID string
    67  	// CredentialSource contains the necessary information to retrieve the token
    68  	// itself, as well as some environmental information. Optional.
    69  	CredentialSource *CredentialSource
    70  	// QuotaProjectID is injected by gCloud. If the value is non-empty, the Auth
    71  	// libraries will set the x-goog-user-project which overrides the project
    72  	// associated with the credentials. Optional.
    73  	QuotaProjectID string
    74  	// Scopes contains the desired scopes for the returned access token.
    75  	// Optional.
    76  	Scopes []string
    77  	// WorkforcePoolUserProject should be set when it is a workforce pool and
    78  	// not a workload identity pool. The underlying principal must still have
    79  	// serviceusage.services.use IAM permission to use the project for
    80  	// billing/quota. Optional.
    81  	WorkforcePoolUserProject string
    82  	// UniverseDomain is the default service domain for a given Cloud universe.
    83  	// This value will be used in the default STS token URL. The default value
    84  	// is "googleapis.com". It will not be used if TokenURL is set. Optional.
    85  	UniverseDomain string
    86  	// SubjectTokenProvider is an optional token provider for OIDC/SAML
    87  	// credentials. One of SubjectTokenProvider, AWSSecurityCredentialProvider
    88  	// or CredentialSource must be provided. Optional.
    89  	SubjectTokenProvider SubjectTokenProvider
    90  	// AwsSecurityCredentialsProvider is an AWS Security Credential provider
    91  	// for AWS credentials. One of SubjectTokenProvider,
    92  	// AWSSecurityCredentialProvider or CredentialSource must be provided. Optional.
    93  	AwsSecurityCredentialsProvider AwsSecurityCredentialsProvider
    94  
    95  	// Client configures the underlying client used to make network requests
    96  	// when fetching tokens. Optional.
    97  	Client *http.Client
    98  }
    99  
   100  // CredentialSource stores the information necessary to retrieve the credentials for the STS exchange.
   101  type CredentialSource struct {
   102  	// File is the location for file sourced credentials.
   103  	// One field amongst File, URL, Executable, or EnvironmentID should be
   104  	// provided, depending on the kind of credential in question.
   105  	File string
   106  	// Url is the URL to call for URL sourced credentials.
   107  	// One field amongst File, URL, Executable, or EnvironmentID should be
   108  	// provided, depending on the kind of credential in question.
   109  	URL string
   110  	// Executable is the configuration object for executable sourced credentials.
   111  	// One field amongst File, URL, Executable, or EnvironmentID should be
   112  	// provided, depending on the kind of credential in question.
   113  	Executable *ExecutableConfig
   114  	// EnvironmentID is the EnvironmentID used for AWS sourced credentials.
   115  	// This should start with "AWS".
   116  	// One field amongst File, URL, Executable, or EnvironmentID should be provided, depending on the kind of credential in question.
   117  	EnvironmentID string
   118  
   119  	// Headers are the headers to attach to the request for URL sourced
   120  	// credentials.
   121  	Headers map[string]string
   122  	// RegionURL is the metadata URL to retrieve the region from for EC2 AWS
   123  	// credentials.
   124  	RegionURL string
   125  	// RegionalCredVerificationURL is the AWS regional credential verification
   126  	// URL, will default to `https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15`
   127  	// if not provided.
   128  	RegionalCredVerificationURL string
   129  	// IMDSv2SessionTokenURL is the URL to retrieve the session token when using
   130  	// IMDSv2 in AWS.
   131  	IMDSv2SessionTokenURL string
   132  	// Format is the format type for the subject token. Used for File and URL
   133  	// sourced credentials.
   134  	Format *Format
   135  }
   136  
   137  // Format contains information needed to retrieve a subject token for URL or
   138  // File sourced credentials.
   139  type Format struct {
   140  	// Type should be either "text" or "json". This determines whether the file
   141  	// or URL sourced credentials expect a simple text subject token or if the
   142  	// subject token will be contained in a JSON object. When not provided
   143  	// "text" type is assumed.
   144  	Type string
   145  	// SubjectTokenFieldName is only required for JSON format. This is the field
   146  	// name that the credentials will check for the subject token in the file or
   147  	// URL response. This would be "access_token" for azure.
   148  	SubjectTokenFieldName string
   149  }
   150  
   151  // ExecutableConfig contains information needed for executable sourced credentials.
   152  type ExecutableConfig struct {
   153  	// Command is the the full command to run to retrieve the subject token.
   154  	// This can include arguments. Must be an absolute path for the program. Required.
   155  	Command string
   156  	// TimeoutMillis is the timeout duration, in milliseconds. Defaults to 30000 milliseconds when not provided. Optional.
   157  	TimeoutMillis int
   158  	// OutputFile is the absolute path to the output file where the executable will cache the response.
   159  	// If specified the auth libraries will first check this location before running the executable. Optional.
   160  	OutputFile string
   161  }
   162  
   163  // SubjectTokenProvider can be used to supply a subject token to exchange for a
   164  // GCP access token.
   165  type SubjectTokenProvider interface {
   166  	// SubjectToken should return a valid subject token or an error.
   167  	// The external account token provider does not cache the returned subject
   168  	// token, so caching logic should be implemented in the provider to prevent
   169  	// multiple requests for the same subject token.
   170  	SubjectToken(ctx context.Context, opts *RequestOptions) (string, error)
   171  }
   172  
   173  // RequestOptions contains information about the requested subject token or AWS
   174  // security credentials from the Google external account credential.
   175  type RequestOptions struct {
   176  	// Audience is the requested audience for the external account credential.
   177  	Audience string
   178  	// Subject token type is the requested subject token type for the external
   179  	// account credential. Expected values include:
   180  	// “urn:ietf:params:oauth:token-type:jwt”
   181  	// “urn:ietf:params:oauth:token-type:id-token”
   182  	// “urn:ietf:params:oauth:token-type:saml2”
   183  	// “urn:ietf:params:aws:token-type:aws4_request”
   184  	SubjectTokenType string
   185  }
   186  
   187  // AwsSecurityCredentialsProvider can be used to supply AwsSecurityCredentials
   188  // and an AWS Region to exchange for a GCP access token.
   189  type AwsSecurityCredentialsProvider interface {
   190  	// AwsRegion should return the AWS region or an error.
   191  	AwsRegion(ctx context.Context, opts *RequestOptions) (string, error)
   192  	// GetAwsSecurityCredentials should return a valid set of
   193  	// AwsSecurityCredentials or an error. The external account token provider
   194  	// does not cache the returned security credentials, so caching logic should
   195  	// be implemented in the provider to prevent multiple requests for the
   196  	// same security credentials.
   197  	AwsSecurityCredentials(ctx context.Context, opts *RequestOptions) (*AwsSecurityCredentials, error)
   198  }
   199  
   200  // AwsSecurityCredentials models AWS security credentials.
   201  type AwsSecurityCredentials struct {
   202  	// AccessKeyId is the AWS Access Key ID - Required.
   203  	AccessKeyID string `json:"AccessKeyID"`
   204  	// SecretAccessKey is the AWS Secret Access Key - Required.
   205  	SecretAccessKey string `json:"SecretAccessKey"`
   206  	// SessionToken is the AWS Session token. This should be provided for
   207  	// temporary AWS security credentials - Optional.
   208  	SessionToken string `json:"Token"`
   209  }
   210  
   211  func (o *Options) validate() error {
   212  	if o == nil {
   213  		return fmt.Errorf("externalaccount: options must be provided")
   214  	}
   215  	return nil
   216  }
   217  
   218  func (o *Options) client() *http.Client {
   219  	if o.Client != nil {
   220  		return o.Client
   221  	}
   222  	return internal.CloneDefaultClient()
   223  }
   224  
   225  func (o *Options) toInternalOpts() *iexacc.Options {
   226  	if o == nil {
   227  		return nil
   228  	}
   229  	iOpts := &iexacc.Options{
   230  		Audience:                       o.Audience,
   231  		SubjectTokenType:               o.SubjectTokenType,
   232  		TokenURL:                       o.TokenURL,
   233  		TokenInfoURL:                   o.TokenInfoURL,
   234  		ServiceAccountImpersonationURL: o.ServiceAccountImpersonationURL,
   235  		ServiceAccountImpersonationLifetimeSeconds: o.ServiceAccountImpersonationLifetimeSeconds,
   236  		ClientSecret:                   o.ClientSecret,
   237  		ClientID:                       o.ClientID,
   238  		QuotaProjectID:                 o.QuotaProjectID,
   239  		Scopes:                         o.Scopes,
   240  		WorkforcePoolUserProject:       o.WorkforcePoolUserProject,
   241  		UniverseDomain:                 o.UniverseDomain,
   242  		SubjectTokenProvider:           toInternalSubjectTokenProvider(o.SubjectTokenProvider),
   243  		AwsSecurityCredentialsProvider: toInternalAwsSecurityCredentialsProvider(o.AwsSecurityCredentialsProvider),
   244  		Client:                         o.client(),
   245  	}
   246  	if o.CredentialSource != nil {
   247  		cs := o.CredentialSource
   248  		iOpts.CredentialSource = &credsfile.CredentialSource{
   249  			File:                        cs.File,
   250  			URL:                         cs.URL,
   251  			Headers:                     cs.Headers,
   252  			EnvironmentID:               cs.EnvironmentID,
   253  			RegionURL:                   cs.RegionURL,
   254  			RegionalCredVerificationURL: cs.RegionalCredVerificationURL,
   255  			CredVerificationURL:         cs.URL,
   256  			IMDSv2SessionTokenURL:       cs.IMDSv2SessionTokenURL,
   257  		}
   258  		if cs.Executable != nil {
   259  			cse := cs.Executable
   260  			iOpts.CredentialSource.Executable = &credsfile.ExecutableConfig{
   261  				Command:       cse.Command,
   262  				TimeoutMillis: cse.TimeoutMillis,
   263  				OutputFile:    cse.OutputFile,
   264  			}
   265  		}
   266  		if cs.Format != nil {
   267  			csf := cs.Format
   268  			iOpts.CredentialSource.Format = &credsfile.Format{
   269  				Type:                  csf.Type,
   270  				SubjectTokenFieldName: csf.SubjectTokenFieldName,
   271  			}
   272  		}
   273  	}
   274  	return iOpts
   275  }
   276  
   277  // NewCredentials returns a [cloud.google.com/go/auth.Credentials] configured
   278  // with the provided options.
   279  func NewCredentials(opts *Options) (*auth.Credentials, error) {
   280  	if err := opts.validate(); err != nil {
   281  		return nil, err
   282  	}
   283  
   284  	tp, err := iexacc.NewTokenProvider(opts.toInternalOpts())
   285  	if err != nil {
   286  		return nil, err
   287  	}
   288  
   289  	var udp, qpp auth.CredentialsPropertyProvider
   290  	if opts.UniverseDomain != "" {
   291  		udp = internal.StaticCredentialsProperty(opts.UniverseDomain)
   292  	}
   293  	if opts.QuotaProjectID != "" {
   294  		qpp = internal.StaticCredentialsProperty(opts.QuotaProjectID)
   295  	}
   296  	return auth.NewCredentials(&auth.CredentialsOptions{
   297  		TokenProvider:          auth.NewCachedTokenProvider(tp, nil),
   298  		UniverseDomainProvider: udp,
   299  		QuotaProjectIDProvider: qpp,
   300  	}), nil
   301  }
   302  
   303  func toInternalSubjectTokenProvider(stp SubjectTokenProvider) iexacc.SubjectTokenProvider {
   304  	if stp == nil {
   305  		return nil
   306  	}
   307  	return &subjectTokenProviderAdapter{stp: stp}
   308  }
   309  
   310  func toInternalAwsSecurityCredentialsProvider(scp AwsSecurityCredentialsProvider) iexacc.AwsSecurityCredentialsProvider {
   311  	if scp == nil {
   312  		return nil
   313  	}
   314  	return &awsSecurityCredentialsAdapter{scp: scp}
   315  }
   316  
   317  func toInternalAwsSecurityCredentials(sc *AwsSecurityCredentials) *iexacc.AwsSecurityCredentials {
   318  	if sc == nil {
   319  		return nil
   320  	}
   321  	return &iexacc.AwsSecurityCredentials{
   322  		AccessKeyID:     sc.AccessKeyID,
   323  		SecretAccessKey: sc.SecretAccessKey,
   324  		SessionToken:    sc.SessionToken,
   325  	}
   326  }
   327  
   328  func toRequestOptions(opts *iexacc.RequestOptions) *RequestOptions {
   329  	if opts == nil {
   330  		return nil
   331  	}
   332  	return &RequestOptions{
   333  		Audience:         opts.Audience,
   334  		SubjectTokenType: opts.SubjectTokenType,
   335  	}
   336  }
   337  
   338  // subjectTokenProviderAdapter is an adapter to convert the user supplied
   339  // interface to its internal counterpart.
   340  type subjectTokenProviderAdapter struct {
   341  	stp SubjectTokenProvider
   342  }
   343  
   344  func (tp *subjectTokenProviderAdapter) SubjectToken(ctx context.Context, opts *iexacc.RequestOptions) (string, error) {
   345  	return tp.stp.SubjectToken(ctx, toRequestOptions(opts))
   346  }
   347  
   348  // awsSecurityCredentialsAdapter is an adapter to convert the user supplied
   349  // interface to its internal counterpart.
   350  type awsSecurityCredentialsAdapter struct {
   351  	scp AwsSecurityCredentialsProvider
   352  }
   353  
   354  func (sc *awsSecurityCredentialsAdapter) AwsRegion(ctx context.Context, opts *iexacc.RequestOptions) (string, error) {
   355  	return sc.scp.AwsRegion(ctx, toRequestOptions(opts))
   356  }
   357  
   358  func (sc *awsSecurityCredentialsAdapter) AwsSecurityCredentials(ctx context.Context, opts *iexacc.RequestOptions) (*iexacc.AwsSecurityCredentials, error) {
   359  	resp, err := sc.scp.AwsSecurityCredentials(ctx, toRequestOptions(opts))
   360  	if err != nil {
   361  		return nil, err
   362  	}
   363  	return toInternalAwsSecurityCredentials(resp), nil
   364  }
   365  

View as plain text