| // 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" |
| "fmt" |
| "net/http" |
| |
| "cloud.google.com/go/auth" |
| iexacc "cloud.google.com/go/auth/credentials/internal/externalaccount" |
| "cloud.google.com/go/auth/internal" |
| "cloud.google.com/go/auth/internal/credsfile" |
| ) |
| |
| // Options for creating a [cloud.google.com/go/auth.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. Required. |
| Audience string |
| // SubjectTokenType is the STS token type based on the Oauth2.0 token |
| // exchange spec. 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” |
| // Required. |
| SubjectTokenType string |
| // TokenURL is the STS token exchange endpoint. If not provided, will |
| // default to https://sts.UNIVERSE_DOMAIN/v1/token, with UNIVERSE_DOMAIN set |
| // to the default service domain googleapis.com unless UniverseDomain is |
| // set. Optional. |
| 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. Optional. |
| 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. Optional. |
| ClientSecret string |
| // ClientID is only required in conjunction with ClientSecret, as described |
| // above. Optional. |
| ClientID string |
| // CredentialSource contains the necessary information to retrieve the token |
| // itself, as well as some environmental information. Optional. |
| CredentialSource *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. Optional. |
| QuotaProjectID string |
| // Scopes contains the desired scopes for the returned access token. |
| // Optional. |
| 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 configures the underlying client used to make network requests |
| // when fetching tokens. Optional. |
| Client *http.Client |
| } |
| |
| // CredentialSource stores the information necessary to retrieve the credentials for the STS exchange. |
| type CredentialSource struct { |
| // File is the location for file sourced credentials. |
| // One field amongst File, URL, Executable, or EnvironmentID should be |
| // provided, depending on the kind of credential in question. |
| File string |
| // Url is the URL to call for URL sourced credentials. |
| // One field amongst File, URL, Executable, or EnvironmentID should be |
| // provided, depending on the kind of credential in question. |
| URL string |
| // Executable is the configuration object for executable sourced credentials. |
| // One field amongst File, URL, Executable, or EnvironmentID should be |
| // provided, depending on the kind of credential in question. |
| Executable *ExecutableConfig |
| // EnvironmentID is the EnvironmentID used for AWS sourced credentials. |
| // This should start with "AWS". |
| // One field amongst File, URL, Executable, or EnvironmentID should be provided, depending on the kind of credential in question. |
| EnvironmentID string |
| |
| // Headers are the headers to attach to the request for URL sourced |
| // credentials. |
| Headers map[string]string |
| // RegionURL is the metadata URL to retrieve the region from for EC2 AWS |
| // credentials. |
| RegionURL string |
| // RegionalCredVerificationURL is the AWS regional credential verification |
| // URL, will default to `https://sts.{region}.amazonaws.com?Action=GetCallerIdentity&Version=2011-06-15` |
| // if not provided. |
| RegionalCredVerificationURL string |
| // IMDSv2SessionTokenURL is the URL to retrieve the session token when using |
| // IMDSv2 in AWS. |
| IMDSv2SessionTokenURL string |
| // Format is the format type for the subject token. Used for File and URL |
| // sourced credentials. |
| Format *Format |
| } |
| |
| // Format contains information needed to retrieve a subject token for URL or |
| // File sourced credentials. |
| type Format struct { |
| // Type should be either "text" or "json". This determines whether the file |
| // or URL sourced credentials expect a simple text subject token or if the |
| // subject token will be contained in a JSON object. When not provided |
| // "text" type is assumed. |
| Type string |
| // SubjectTokenFieldName is only required for JSON format. This is the field |
| // name that the credentials will check for the subject token in the file or |
| // URL response. This would be "access_token" for azure. |
| SubjectTokenFieldName string |
| } |
| |
| // ExecutableConfig contains information needed for executable sourced credentials. |
| type ExecutableConfig struct { |
| // Command is the the full command to run to retrieve the subject token. |
| // This can include arguments. Must be an absolute path for the program. Required. |
| Command string |
| // TimeoutMillis is the timeout duration, in milliseconds. Defaults to 30000 milliseconds when not provided. Optional. |
| TimeoutMillis int |
| // OutputFile is the absolute path to the output file where the executable will cache the response. |
| // If specified the auth libraries will first check this location before running the executable. Optional. |
| OutputFile string |
| } |
| |
| // 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 == nil { |
| return fmt.Errorf("externalaccount: options must be provided") |
| } |
| return nil |
| } |
| |
| func (o *Options) client() *http.Client { |
| if o.Client != nil { |
| return o.Client |
| } |
| return internal.CloneDefaultClient() |
| } |
| |
| func (o *Options) toInternalOpts() *iexacc.Options { |
| if o == nil { |
| return nil |
| } |
| iOpts := &iexacc.Options{ |
| Audience: o.Audience, |
| SubjectTokenType: o.SubjectTokenType, |
| TokenURL: o.TokenURL, |
| TokenInfoURL: o.TokenInfoURL, |
| ServiceAccountImpersonationURL: o.ServiceAccountImpersonationURL, |
| ServiceAccountImpersonationLifetimeSeconds: o.ServiceAccountImpersonationLifetimeSeconds, |
| ClientSecret: o.ClientSecret, |
| ClientID: o.ClientID, |
| QuotaProjectID: o.QuotaProjectID, |
| Scopes: o.Scopes, |
| WorkforcePoolUserProject: o.WorkforcePoolUserProject, |
| UniverseDomain: o.UniverseDomain, |
| SubjectTokenProvider: toInternalSubjectTokenProvider(o.SubjectTokenProvider), |
| AwsSecurityCredentialsProvider: toInternalAwsSecurityCredentialsProvider(o.AwsSecurityCredentialsProvider), |
| Client: o.client(), |
| } |
| if o.CredentialSource != nil { |
| cs := o.CredentialSource |
| iOpts.CredentialSource = &credsfile.CredentialSource{ |
| File: cs.File, |
| URL: cs.URL, |
| Headers: cs.Headers, |
| EnvironmentID: cs.EnvironmentID, |
| RegionURL: cs.RegionURL, |
| RegionalCredVerificationURL: cs.RegionalCredVerificationURL, |
| CredVerificationURL: cs.URL, |
| IMDSv2SessionTokenURL: cs.IMDSv2SessionTokenURL, |
| } |
| if cs.Executable != nil { |
| cse := cs.Executable |
| iOpts.CredentialSource.Executable = &credsfile.ExecutableConfig{ |
| Command: cse.Command, |
| TimeoutMillis: cse.TimeoutMillis, |
| OutputFile: cse.OutputFile, |
| } |
| } |
| if cs.Format != nil { |
| csf := cs.Format |
| iOpts.CredentialSource.Format = &credsfile.Format{ |
| Type: csf.Type, |
| SubjectTokenFieldName: csf.SubjectTokenFieldName, |
| } |
| } |
| } |
| return iOpts |
| } |
| |
| // NewCredentials returns a [cloud.google.com/go/auth.Credentials] configured |
| // with the provided options. |
| func NewCredentials(opts *Options) (*auth.Credentials, error) { |
| if err := opts.validate(); err != nil { |
| return nil, err |
| } |
| |
| tp, err := iexacc.NewTokenProvider(opts.toInternalOpts()) |
| if err != nil { |
| return nil, err |
| } |
| |
| var udp, qpp auth.CredentialsPropertyProvider |
| if opts.UniverseDomain != "" { |
| udp = internal.StaticCredentialsProperty(opts.UniverseDomain) |
| } |
| if opts.QuotaProjectID != "" { |
| qpp = internal.StaticCredentialsProperty(opts.QuotaProjectID) |
| } |
| return auth.NewCredentials(&auth.CredentialsOptions{ |
| TokenProvider: auth.NewCachedTokenProvider(tp, nil), |
| UniverseDomainProvider: udp, |
| QuotaProjectIDProvider: qpp, |
| }), nil |
| } |
| |
| func toInternalSubjectTokenProvider(stp SubjectTokenProvider) iexacc.SubjectTokenProvider { |
| if stp == nil { |
| return nil |
| } |
| return &subjectTokenProviderAdapter{stp: stp} |
| } |
| |
| func toInternalAwsSecurityCredentialsProvider(scp AwsSecurityCredentialsProvider) iexacc.AwsSecurityCredentialsProvider { |
| if scp == nil { |
| return nil |
| } |
| return &awsSecurityCredentialsAdapter{scp: scp} |
| } |
| |
| func toInternalAwsSecurityCredentials(sc *AwsSecurityCredentials) *iexacc.AwsSecurityCredentials { |
| if sc == nil { |
| return nil |
| } |
| return &iexacc.AwsSecurityCredentials{ |
| AccessKeyID: sc.AccessKeyID, |
| SecretAccessKey: sc.SecretAccessKey, |
| SessionToken: sc.SessionToken, |
| } |
| } |
| |
| func toRequestOptions(opts *iexacc.RequestOptions) *RequestOptions { |
| if opts == nil { |
| return nil |
| } |
| return &RequestOptions{ |
| Audience: opts.Audience, |
| SubjectTokenType: opts.SubjectTokenType, |
| } |
| } |
| |
| // subjectTokenProviderAdapter is an adapter to convert the user supplied |
| // interface to its internal counterpart. |
| type subjectTokenProviderAdapter struct { |
| stp SubjectTokenProvider |
| } |
| |
| func (tp *subjectTokenProviderAdapter) SubjectToken(ctx context.Context, opts *iexacc.RequestOptions) (string, error) { |
| return tp.stp.SubjectToken(ctx, toRequestOptions(opts)) |
| } |
| |
| // awsSecurityCredentialsAdapter is an adapter to convert the user supplied |
| // interface to its internal counterpart. |
| type awsSecurityCredentialsAdapter struct { |
| scp AwsSecurityCredentialsProvider |
| } |
| |
| func (sc *awsSecurityCredentialsAdapter) AwsRegion(ctx context.Context, opts *iexacc.RequestOptions) (string, error) { |
| return sc.scp.AwsRegion(ctx, toRequestOptions(opts)) |
| } |
| |
| func (sc *awsSecurityCredentialsAdapter) AwsSecurityCredentials(ctx context.Context, opts *iexacc.RequestOptions) (*iexacc.AwsSecurityCredentials, error) { |
| resp, err := sc.scp.AwsSecurityCredentials(ctx, toRequestOptions(opts)) |
| if err != nil { |
| return nil, err |
| } |
| return toInternalAwsSecurityCredentials(resp), nil |
| } |