1 package config
2
3 import (
4 "context"
5 "fmt"
6 "io/ioutil"
7 "net"
8 "net/url"
9 "os"
10 "time"
11
12 "github.com/aws/aws-sdk-go-v2/aws"
13 "github.com/aws/aws-sdk-go-v2/credentials"
14 "github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds"
15 "github.com/aws/aws-sdk-go-v2/credentials/endpointcreds"
16 "github.com/aws/aws-sdk-go-v2/credentials/processcreds"
17 "github.com/aws/aws-sdk-go-v2/credentials/ssocreds"
18 "github.com/aws/aws-sdk-go-v2/credentials/stscreds"
19 "github.com/aws/aws-sdk-go-v2/feature/ec2/imds"
20 "github.com/aws/aws-sdk-go-v2/service/sso"
21 "github.com/aws/aws-sdk-go-v2/service/ssooidc"
22 "github.com/aws/aws-sdk-go-v2/service/sts"
23 )
24
25 const (
26
27 credSourceEc2Metadata = "Ec2InstanceMetadata"
28 credSourceEnvironment = "Environment"
29 credSourceECSContainer = "EcsContainer"
30 httpProviderAuthFileEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"
31 )
32
33
34
35 var ecsContainerIPv4 net.IP = []byte{
36 169, 254, 170, 2,
37 }
38
39
40
41 var eksContainerIPv4 net.IP = []byte{
42 169, 254, 170, 23,
43 }
44
45
46
47 var eksContainerIPv6 net.IP = []byte{
48 0xFD, 0, 0xE, 0xC2,
49 0, 0, 0, 0,
50 0, 0, 0, 0,
51 0, 0, 0, 0x23,
52 }
53
54 var (
55 ecsContainerEndpoint = "http://169.254.170.2"
56 )
57
58
59
60
61
62
63
64 func resolveCredentials(ctx context.Context, cfg *aws.Config, configs configs) error {
65 found, err := resolveCredentialProvider(ctx, cfg, configs)
66 if found || err != nil {
67 return err
68 }
69
70 return resolveCredentialChain(ctx, cfg, configs)
71 }
72
73
74
75
76
77
78
79
80
81
82 func resolveCredentialProvider(ctx context.Context, cfg *aws.Config, configs configs) (bool, error) {
83 credProvider, found, err := getCredentialsProvider(ctx, configs)
84 if !found || err != nil {
85 return false, err
86 }
87
88 cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, credProvider)
89 if err != nil {
90 return false, err
91 }
92
93 return true, nil
94 }
95
96
97
98
99
100
101
102 func resolveCredentialChain(ctx context.Context, cfg *aws.Config, configs configs) (err error) {
103 envConfig, sharedConfig, other := getAWSConfigSources(configs)
104
105
106
107
108 _, sharedProfileSet, err := getSharedConfigProfile(ctx, other)
109 if err != nil {
110 return err
111 }
112
113 switch {
114 case sharedProfileSet:
115 err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig, other)
116 case envConfig.Credentials.HasKeys():
117 cfg.Credentials = credentials.StaticCredentialsProvider{Value: envConfig.Credentials}
118 case len(envConfig.WebIdentityTokenFilePath) > 0:
119 err = assumeWebIdentity(ctx, cfg, envConfig.WebIdentityTokenFilePath, envConfig.RoleARN, envConfig.RoleSessionName, configs)
120 default:
121 err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig, other)
122 }
123 if err != nil {
124 return err
125 }
126
127
128 cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, cfg.Credentials)
129 if err != nil {
130 return err
131 }
132
133 return nil
134 }
135
136 func resolveCredsFromProfile(ctx context.Context, cfg *aws.Config, envConfig *EnvConfig, sharedConfig *SharedConfig, configs configs) (err error) {
137
138 switch {
139 case sharedConfig.Source != nil:
140
141 err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig.Source, configs)
142
143 case sharedConfig.Credentials.HasKeys():
144
145 cfg.Credentials = credentials.StaticCredentialsProvider{
146 Value: sharedConfig.Credentials,
147 }
148
149 case len(sharedConfig.CredentialSource) != 0:
150 err = resolveCredsFromSource(ctx, cfg, envConfig, sharedConfig, configs)
151
152 case len(sharedConfig.WebIdentityTokenFile) != 0:
153
154
155
156 return assumeWebIdentity(ctx, cfg, sharedConfig.WebIdentityTokenFile, sharedConfig.RoleARN, sharedConfig.RoleSessionName, configs)
157
158 case sharedConfig.hasSSOConfiguration():
159 err = resolveSSOCredentials(ctx, cfg, sharedConfig, configs)
160
161 case len(sharedConfig.CredentialProcess) != 0:
162
163 err = processCredentials(ctx, cfg, sharedConfig, configs)
164
165 case len(envConfig.ContainerCredentialsEndpoint) != 0:
166 err = resolveLocalHTTPCredProvider(ctx, cfg, envConfig.ContainerCredentialsEndpoint, envConfig.ContainerAuthorizationToken, configs)
167
168 case len(envConfig.ContainerCredentialsRelativePath) != 0:
169 err = resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
170
171 default:
172 err = resolveEC2RoleCredentials(ctx, cfg, configs)
173 }
174 if err != nil {
175 return err
176 }
177
178 if len(sharedConfig.RoleARN) > 0 {
179 return credsFromAssumeRole(ctx, cfg, sharedConfig, configs)
180 }
181
182 return nil
183 }
184
185 func resolveSSOCredentials(ctx context.Context, cfg *aws.Config, sharedConfig *SharedConfig, configs configs) error {
186 if err := sharedConfig.validateSSOConfiguration(); err != nil {
187 return err
188 }
189
190 var options []func(*ssocreds.Options)
191 v, found, err := getSSOProviderOptions(ctx, configs)
192 if err != nil {
193 return err
194 }
195 if found {
196 options = append(options, v)
197 }
198
199 cfgCopy := cfg.Copy()
200
201 if sharedConfig.SSOSession != nil {
202 ssoTokenProviderOptionsFn, found, err := getSSOTokenProviderOptions(ctx, configs)
203 if err != nil {
204 return fmt.Errorf("failed to get SSOTokenProviderOptions from config sources, %w", err)
205 }
206 var optFns []func(*ssocreds.SSOTokenProviderOptions)
207 if found {
208 optFns = append(optFns, ssoTokenProviderOptionsFn)
209 }
210 cfgCopy.Region = sharedConfig.SSOSession.SSORegion
211 cachedPath, err := ssocreds.StandardCachedTokenFilepath(sharedConfig.SSOSession.Name)
212 if err != nil {
213 return err
214 }
215 oidcClient := ssooidc.NewFromConfig(cfgCopy)
216 tokenProvider := ssocreds.NewSSOTokenProvider(oidcClient, cachedPath, optFns...)
217 options = append(options, func(o *ssocreds.Options) {
218 o.SSOTokenProvider = tokenProvider
219 o.CachedTokenFilepath = cachedPath
220 })
221 } else {
222 cfgCopy.Region = sharedConfig.SSORegion
223 }
224
225 cfg.Credentials = ssocreds.New(sso.NewFromConfig(cfgCopy), sharedConfig.SSOAccountID, sharedConfig.SSORoleName, sharedConfig.SSOStartURL, options...)
226
227 return nil
228 }
229
230 func ecsContainerURI(path string) string {
231 return fmt.Sprintf("%s%s", ecsContainerEndpoint, path)
232 }
233
234 func processCredentials(ctx context.Context, cfg *aws.Config, sharedConfig *SharedConfig, configs configs) error {
235 var opts []func(*processcreds.Options)
236
237 options, found, err := getProcessCredentialOptions(ctx, configs)
238 if err != nil {
239 return err
240 }
241 if found {
242 opts = append(opts, options)
243 }
244
245 cfg.Credentials = processcreds.NewProvider(sharedConfig.CredentialProcess, opts...)
246
247 return nil
248 }
249
250
251
252
253
254 func isAllowedHost(host string) (bool, error) {
255 if ip := net.ParseIP(host); ip != nil {
256 return isIPAllowed(ip), nil
257 }
258
259 addrs, err := lookupHostFn(host)
260 if err != nil {
261 return false, err
262 }
263
264 for _, addr := range addrs {
265 if ip := net.ParseIP(addr); ip == nil || !isIPAllowed(ip) {
266 return false, nil
267 }
268 }
269
270 return true, nil
271 }
272
273 func isIPAllowed(ip net.IP) bool {
274 return ip.IsLoopback() ||
275 ip.Equal(ecsContainerIPv4) ||
276 ip.Equal(eksContainerIPv4) ||
277 ip.Equal(eksContainerIPv6)
278 }
279
280 func resolveLocalHTTPCredProvider(ctx context.Context, cfg *aws.Config, endpointURL, authToken string, configs configs) error {
281 var resolveErr error
282
283 parsed, err := url.Parse(endpointURL)
284 if err != nil {
285 resolveErr = fmt.Errorf("invalid URL, %w", err)
286 } else {
287 host := parsed.Hostname()
288 if len(host) == 0 {
289 resolveErr = fmt.Errorf("unable to parse host from local HTTP cred provider URL")
290 } else if parsed.Scheme == "http" {
291 if isAllowedHost, allowHostErr := isAllowedHost(host); allowHostErr != nil {
292 resolveErr = fmt.Errorf("failed to resolve host %q, %v", host, allowHostErr)
293 } else if !isAllowedHost {
294 resolveErr = fmt.Errorf("invalid endpoint host, %q, only loopback/ecs/eks hosts are allowed", host)
295 }
296 }
297 }
298
299 if resolveErr != nil {
300 return resolveErr
301 }
302
303 return resolveHTTPCredProvider(ctx, cfg, endpointURL, authToken, configs)
304 }
305
306 func resolveHTTPCredProvider(ctx context.Context, cfg *aws.Config, url, authToken string, configs configs) error {
307 optFns := []func(*endpointcreds.Options){
308 func(options *endpointcreds.Options) {
309 if len(authToken) != 0 {
310 options.AuthorizationToken = authToken
311 }
312 if authFilePath := os.Getenv(httpProviderAuthFileEnvVar); authFilePath != "" {
313 options.AuthorizationTokenProvider = endpointcreds.TokenProviderFunc(func() (string, error) {
314 var contents []byte
315 var err error
316 if contents, err = ioutil.ReadFile(authFilePath); err != nil {
317 return "", fmt.Errorf("failed to read authorization token from %v: %v", authFilePath, err)
318 }
319 return string(contents), nil
320 })
321 }
322 options.APIOptions = cfg.APIOptions
323 if cfg.Retryer != nil {
324 options.Retryer = cfg.Retryer()
325 }
326 },
327 }
328
329 optFn, found, err := getEndpointCredentialProviderOptions(ctx, configs)
330 if err != nil {
331 return err
332 }
333 if found {
334 optFns = append(optFns, optFn)
335 }
336
337 provider := endpointcreds.New(url, optFns...)
338
339 cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, provider, func(options *aws.CredentialsCacheOptions) {
340 options.ExpiryWindow = 5 * time.Minute
341 })
342 if err != nil {
343 return err
344 }
345
346 return nil
347 }
348
349 func resolveCredsFromSource(ctx context.Context, cfg *aws.Config, envConfig *EnvConfig, sharedCfg *SharedConfig, configs configs) (err error) {
350 switch sharedCfg.CredentialSource {
351 case credSourceEc2Metadata:
352 return resolveEC2RoleCredentials(ctx, cfg, configs)
353
354 case credSourceEnvironment:
355 cfg.Credentials = credentials.StaticCredentialsProvider{Value: envConfig.Credentials}
356
357 case credSourceECSContainer:
358 if len(envConfig.ContainerCredentialsRelativePath) == 0 {
359 return fmt.Errorf("EcsContainer was specified as the credential_source, but 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' was not set")
360 }
361 return resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
362
363 default:
364 return fmt.Errorf("credential_source values must be EcsContainer, Ec2InstanceMetadata, or Environment")
365 }
366
367 return nil
368 }
369
370 func resolveEC2RoleCredentials(ctx context.Context, cfg *aws.Config, configs configs) error {
371 optFns := make([]func(*ec2rolecreds.Options), 0, 2)
372
373 optFn, found, err := getEC2RoleCredentialProviderOptions(ctx, configs)
374 if err != nil {
375 return err
376 }
377 if found {
378 optFns = append(optFns, optFn)
379 }
380
381 optFns = append(optFns, func(o *ec2rolecreds.Options) {
382
383 if o.Client == nil {
384 o.Client = imds.NewFromConfig(*cfg)
385 }
386 })
387
388 provider := ec2rolecreds.New(optFns...)
389
390 cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, provider)
391 if err != nil {
392 return err
393 }
394
395 return nil
396 }
397
398 func getAWSConfigSources(cfgs configs) (*EnvConfig, *SharedConfig, configs) {
399 var (
400 envConfig *EnvConfig
401 sharedConfig *SharedConfig
402 other configs
403 )
404
405 for i := range cfgs {
406 switch c := cfgs[i].(type) {
407 case EnvConfig:
408 if envConfig == nil {
409 envConfig = &c
410 }
411 case *EnvConfig:
412 if envConfig == nil {
413 envConfig = c
414 }
415 case SharedConfig:
416 if sharedConfig == nil {
417 sharedConfig = &c
418 }
419 case *SharedConfig:
420 if envConfig == nil {
421 sharedConfig = c
422 }
423 default:
424 other = append(other, c)
425 }
426 }
427
428 if envConfig == nil {
429 envConfig = &EnvConfig{}
430 }
431
432 if sharedConfig == nil {
433 sharedConfig = &SharedConfig{}
434 }
435
436 return envConfig, sharedConfig, other
437 }
438
439
440
441
442 type AssumeRoleTokenProviderNotSetError struct{}
443
444
445 func (e AssumeRoleTokenProviderNotSetError) Error() string {
446 return fmt.Sprintf("assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.")
447 }
448
449 func assumeWebIdentity(ctx context.Context, cfg *aws.Config, filepath string, roleARN, sessionName string, configs configs) error {
450 if len(filepath) == 0 {
451 return fmt.Errorf("token file path is not set")
452 }
453
454 optFns := []func(*stscreds.WebIdentityRoleOptions){
455 func(options *stscreds.WebIdentityRoleOptions) {
456 options.RoleSessionName = sessionName
457 },
458 }
459
460 optFn, found, err := getWebIdentityCredentialProviderOptions(ctx, configs)
461 if err != nil {
462 return err
463 }
464
465 if found {
466 optFns = append(optFns, optFn)
467 }
468
469 opts := stscreds.WebIdentityRoleOptions{
470 RoleARN: roleARN,
471 }
472
473 for _, fn := range optFns {
474 fn(&opts)
475 }
476
477 if len(opts.RoleARN) == 0 {
478 return fmt.Errorf("role ARN is not set")
479 }
480
481 client := opts.Client
482 if client == nil {
483 client = sts.NewFromConfig(*cfg)
484 }
485
486 provider := stscreds.NewWebIdentityRoleProvider(client, roleARN, stscreds.IdentityTokenFile(filepath), optFns...)
487
488 cfg.Credentials = provider
489
490 return nil
491 }
492
493 func credsFromAssumeRole(ctx context.Context, cfg *aws.Config, sharedCfg *SharedConfig, configs configs) (err error) {
494 optFns := []func(*stscreds.AssumeRoleOptions){
495 func(options *stscreds.AssumeRoleOptions) {
496 options.RoleSessionName = sharedCfg.RoleSessionName
497 if sharedCfg.RoleDurationSeconds != nil {
498 if *sharedCfg.RoleDurationSeconds/time.Minute > 15 {
499 options.Duration = *sharedCfg.RoleDurationSeconds
500 }
501 }
502
503 if len(sharedCfg.ExternalID) > 0 {
504 options.ExternalID = aws.String(sharedCfg.ExternalID)
505 }
506
507
508 if len(sharedCfg.MFASerial) != 0 {
509 options.SerialNumber = aws.String(sharedCfg.MFASerial)
510 }
511 },
512 }
513
514 optFn, found, err := getAssumeRoleCredentialProviderOptions(ctx, configs)
515 if err != nil {
516 return err
517 }
518 if found {
519 optFns = append(optFns, optFn)
520 }
521
522 {
523
524
525 var o stscreds.AssumeRoleOptions
526 for _, fn := range optFns {
527 fn(&o)
528 }
529 if o.TokenProvider == nil && o.SerialNumber != nil {
530 return AssumeRoleTokenProviderNotSetError{}
531 }
532 }
533
534 cfg.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(*cfg), sharedCfg.RoleARN, optFns...)
535
536 return nil
537 }
538
539
540
541
542 func wrapWithCredentialsCache(
543 ctx context.Context,
544 cfgs configs,
545 provider aws.CredentialsProvider,
546 optFns ...func(options *aws.CredentialsCacheOptions),
547 ) (aws.CredentialsProvider, error) {
548 _, ok := provider.(*aws.CredentialsCache)
549 if ok {
550 return provider, nil
551 }
552
553 credCacheOptions, optionsFound, err := getCredentialsCacheOptionsProvider(ctx, cfgs)
554 if err != nil {
555 return nil, err
556 }
557
558
559
560 optFns = optFns[:len(optFns):len(optFns)]
561 if optionsFound {
562 optFns = append(optFns, credCacheOptions)
563 }
564
565 return aws.NewCredentialsCache(provider, optFns...), nil
566 }
567
View as plain text