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