src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

resolve_credentials.go (20771B)


      1 package config
      2 
      3 import (
      4 	"context"
      5 	"fmt"
      6 	"net"
      7 	"net/url"
      8 	"os"
      9 	"time"
     10 
     11 	"github.com/aws/aws-sdk-go-v2/aws"
     12 	"github.com/aws/aws-sdk-go-v2/credentials"
     13 	"github.com/aws/aws-sdk-go-v2/credentials/ec2rolecreds"
     14 	"github.com/aws/aws-sdk-go-v2/credentials/endpointcreds"
     15 	"github.com/aws/aws-sdk-go-v2/credentials/logincreds"
     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/signin"
     21 	"github.com/aws/aws-sdk-go-v2/service/sso"
     22 	"github.com/aws/aws-sdk-go-v2/service/ssooidc"
     23 	"github.com/aws/aws-sdk-go-v2/service/sts"
     24 )
     25 
     26 const (
     27 	// valid credential source values
     28 	credSourceEc2Metadata      = "Ec2InstanceMetadata"
     29 	credSourceEnvironment      = "Environment"
     30 	credSourceECSContainer     = "EcsContainer"
     31 	httpProviderAuthFileEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"
     32 )
     33 
     34 // direct representation of the IPv4 address for the ECS container
     35 // "169.254.170.2"
     36 var ecsContainerIPv4 net.IP = []byte{
     37 	169, 254, 170, 2,
     38 }
     39 
     40 // direct representation of the IPv4 address for the EKS container
     41 // "169.254.170.23"
     42 var eksContainerIPv4 net.IP = []byte{
     43 	169, 254, 170, 23,
     44 }
     45 
     46 // direct representation of the IPv6 address for the EKS container
     47 // "fd00:ec2::23"
     48 var eksContainerIPv6 net.IP = []byte{
     49 	0xFD, 0, 0xE, 0xC2,
     50 	0, 0, 0, 0,
     51 	0, 0, 0, 0,
     52 	0, 0, 0, 0x23,
     53 }
     54 
     55 var (
     56 	ecsContainerEndpoint = "http://169.254.170.2" // not constant to allow for swapping during unit-testing
     57 )
     58 
     59 // resolveCredentials extracts a credential provider from slice of config
     60 // sources.
     61 //
     62 // If an explicit credential provider is not found the resolver will fallback
     63 // to resolving credentials by extracting a credential provider from EnvConfig
     64 // and SharedConfig.
     65 func resolveCredentials(ctx context.Context, cfg *aws.Config, configs configs) error {
     66 	found, err := resolveCredentialProvider(ctx, cfg, configs)
     67 	if found || err != nil {
     68 		return err
     69 	}
     70 
     71 	return resolveCredentialChain(ctx, cfg, configs)
     72 }
     73 
     74 // resolveCredentialProvider extracts the first instance of Credentials from the
     75 // config slices.
     76 //
     77 // The resolved CredentialProvider will be wrapped in a cache to ensure the
     78 // credentials are only refreshed when needed. This also protects the
     79 // credential provider to be used concurrently.
     80 //
     81 // Config providers used:
     82 // * credentialsProviderProvider
     83 func resolveCredentialProvider(ctx context.Context, cfg *aws.Config, configs configs) (bool, error) {
     84 	credProvider, found, err := getCredentialsProvider(ctx, configs)
     85 	if !found || err != nil {
     86 		return false, err
     87 	}
     88 
     89 	cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, credProvider)
     90 	if err != nil {
     91 		return false, err
     92 	}
     93 
     94 	return true, nil
     95 }
     96 
     97 // resolveCredentialChain resolves a credential provider chain using EnvConfig
     98 // and SharedConfig if present in the slice of provided configs.
     99 //
    100 // The resolved CredentialProvider will be wrapped in a cache to ensure the
    101 // credentials are only refreshed when needed. This also protects the
    102 // credential provider to be used concurrently.
    103 func resolveCredentialChain(ctx context.Context, cfg *aws.Config, configs configs) (err error) {
    104 	envConfig, sharedConfig, other := getAWSConfigSources(configs)
    105 
    106 	// When checking if a profile was specified programmatically we should only consider the "other"
    107 	// configuration sources that have been provided. This ensures we correctly honor the expected credential
    108 	// hierarchy.
    109 	_, sharedProfileSet, err := getSharedConfigProfile(ctx, other)
    110 	if err != nil {
    111 		return err
    112 	}
    113 
    114 	switch {
    115 	case sharedProfileSet:
    116 		ctx, err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig, other)
    117 	case envConfig.Credentials.HasKeys():
    118 		ctx = addCredentialSource(ctx, aws.CredentialSourceEnvVars)
    119 		cfg.Credentials = credentials.StaticCredentialsProvider{Value: envConfig.Credentials, Source: getCredentialSources(ctx)}
    120 	case len(envConfig.WebIdentityTokenFilePath) > 0:
    121 		ctx = addCredentialSource(ctx, aws.CredentialSourceEnvVarsSTSWebIDToken)
    122 		err = assumeWebIdentity(ctx, cfg, envConfig.WebIdentityTokenFilePath, envConfig.RoleARN, envConfig.RoleSessionName, configs)
    123 	default:
    124 		ctx, err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig, other)
    125 	}
    126 	if err != nil {
    127 		return err
    128 	}
    129 
    130 	// Wrap the resolved provider in a cache so the SDK will cache credentials.
    131 	cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, cfg.Credentials)
    132 	if err != nil {
    133 		return err
    134 	}
    135 
    136 	return nil
    137 }
    138 
    139 func resolveCredsFromProfile(ctx context.Context, cfg *aws.Config, envConfig *EnvConfig, sharedConfig *SharedConfig, configs configs) (ctx2 context.Context, err error) {
    140 	switch {
    141 	case sharedConfig.Source != nil:
    142 		ctx = addCredentialSource(ctx, aws.CredentialSourceProfileSourceProfile)
    143 		// Assume IAM role with credentials source from a different profile.
    144 		ctx, err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig.Source, configs)
    145 
    146 	case sharedConfig.Credentials.HasKeys():
    147 		// Static Credentials from Shared Config/Credentials file.
    148 		ctx = addCredentialSource(ctx, aws.CredentialSourceProfile)
    149 		cfg.Credentials = credentials.StaticCredentialsProvider{
    150 			Value:  sharedConfig.Credentials,
    151 			Source: getCredentialSources(ctx),
    152 		}
    153 
    154 	case len(sharedConfig.CredentialSource) != 0:
    155 		ctx = addCredentialSource(ctx, aws.CredentialSourceProfileNamedProvider)
    156 		ctx, err = resolveCredsFromSource(ctx, cfg, envConfig, sharedConfig, configs)
    157 
    158 	case len(sharedConfig.WebIdentityTokenFile) != 0:
    159 		// Credentials from Assume Web Identity token require an IAM Role, and
    160 		// that roll will be assumed. May be wrapped with another assume role
    161 		// via SourceProfile.
    162 		ctx = addCredentialSource(ctx, aws.CredentialSourceProfileSTSWebIDToken)
    163 		return ctx, assumeWebIdentity(ctx, cfg, sharedConfig.WebIdentityTokenFile, sharedConfig.RoleARN, sharedConfig.RoleSessionName, configs)
    164 
    165 	case sharedConfig.hasSSOConfiguration():
    166 		if sharedConfig.hasLegacySSOConfiguration() {
    167 			ctx = addCredentialSource(ctx, aws.CredentialSourceProfileSSOLegacy)
    168 			ctx = addCredentialSource(ctx, aws.CredentialSourceSSOLegacy)
    169 		} else {
    170 			ctx = addCredentialSource(ctx, aws.CredentialSourceSSO)
    171 		}
    172 		if sharedConfig.SSOSession != nil {
    173 			ctx = addCredentialSource(ctx, aws.CredentialSourceProfileSSO)
    174 		}
    175 		err = resolveSSOCredentials(ctx, cfg, sharedConfig, configs)
    176 	case len(sharedConfig.LoginSession) > 0:
    177 		ctx = addCredentialSource(ctx, aws.CredentialSourceProfileLogin)
    178 		ctx = addCredentialSource(ctx, aws.CredentialSourceLogin)
    179 		err = resolveLoginCredentials(ctx, cfg, sharedConfig, configs)
    180 	case len(sharedConfig.CredentialProcess) != 0:
    181 		// Get credentials from CredentialProcess
    182 		ctx = addCredentialSource(ctx, aws.CredentialSourceProfileProcess)
    183 		ctx = addCredentialSource(ctx, aws.CredentialSourceProcess)
    184 		err = processCredentials(ctx, cfg, sharedConfig, configs)
    185 
    186 	case len(envConfig.ContainerCredentialsRelativePath) != 0:
    187 		ctx = addCredentialSource(ctx, aws.CredentialSourceHTTP)
    188 		err = resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
    189 
    190 	case len(envConfig.ContainerCredentialsEndpoint) != 0:
    191 		ctx = addCredentialSource(ctx, aws.CredentialSourceHTTP)
    192 		err = resolveLocalHTTPCredProvider(ctx, cfg, envConfig.ContainerCredentialsEndpoint, envConfig.ContainerAuthorizationToken, configs)
    193 
    194 	default:
    195 		ctx = addCredentialSource(ctx, aws.CredentialSourceIMDS)
    196 		err = resolveEC2RoleCredentials(ctx, cfg, configs)
    197 	}
    198 	if err != nil {
    199 		return ctx, err
    200 	}
    201 
    202 	if len(sharedConfig.RoleARN) > 0 {
    203 		return ctx, credsFromAssumeRole(ctx, cfg, sharedConfig, configs)
    204 	}
    205 
    206 	return ctx, nil
    207 }
    208 
    209 func resolveSSOCredentials(ctx context.Context, cfg *aws.Config, sharedConfig *SharedConfig, configs configs) error {
    210 	if err := sharedConfig.validateSSOConfiguration(); err != nil {
    211 		return err
    212 	}
    213 
    214 	var options []func(*ssocreds.Options)
    215 	v, found, err := getSSOProviderOptions(ctx, configs)
    216 	if err != nil {
    217 		return err
    218 	}
    219 	if found {
    220 		options = append(options, v)
    221 	}
    222 
    223 	cfgCopy := cfg.Copy()
    224 
    225 	options = append(options, func(o *ssocreds.Options) {
    226 		o.CredentialSources = getCredentialSources(ctx)
    227 	})
    228 
    229 	if sharedConfig.SSOSession != nil {
    230 		ssoTokenProviderOptionsFn, found, err := getSSOTokenProviderOptions(ctx, configs)
    231 		if err != nil {
    232 			return fmt.Errorf("failed to get SSOTokenProviderOptions from config sources, %w", err)
    233 		}
    234 		var optFns []func(*ssocreds.SSOTokenProviderOptions)
    235 		if found {
    236 			optFns = append(optFns, ssoTokenProviderOptionsFn)
    237 		}
    238 		cfgCopy.Region = sharedConfig.SSOSession.SSORegion
    239 		cachedPath, err := ssocreds.StandardCachedTokenFilepath(sharedConfig.SSOSession.Name)
    240 		if err != nil {
    241 			return err
    242 		}
    243 		oidcClient := ssooidc.NewFromConfig(cfgCopy)
    244 		tokenProvider := ssocreds.NewSSOTokenProvider(oidcClient, cachedPath, optFns...)
    245 		options = append(options, func(o *ssocreds.Options) {
    246 			o.SSOTokenProvider = tokenProvider
    247 			o.CachedTokenFilepath = cachedPath
    248 		})
    249 	} else {
    250 		cfgCopy.Region = sharedConfig.SSORegion
    251 	}
    252 
    253 	cfg.Credentials = ssocreds.New(sso.NewFromConfig(cfgCopy), sharedConfig.SSOAccountID, sharedConfig.SSORoleName, sharedConfig.SSOStartURL, options...)
    254 
    255 	return nil
    256 }
    257 
    258 func ecsContainerURI(path string) string {
    259 	return fmt.Sprintf("%s%s", ecsContainerEndpoint, path)
    260 }
    261 
    262 func processCredentials(ctx context.Context, cfg *aws.Config, sharedConfig *SharedConfig, configs configs) error {
    263 	var opts []func(*processcreds.Options)
    264 
    265 	options, found, err := getProcessCredentialOptions(ctx, configs)
    266 	if err != nil {
    267 		return err
    268 	}
    269 	if found {
    270 		opts = append(opts, options)
    271 	}
    272 
    273 	opts = append(opts, func(o *processcreds.Options) {
    274 		o.CredentialSources = getCredentialSources(ctx)
    275 	})
    276 
    277 	cfg.Credentials = processcreds.NewProvider(sharedConfig.CredentialProcess, opts...)
    278 
    279 	return nil
    280 }
    281 
    282 // isAllowedHost allows host to be loopback or known ECS/EKS container IPs
    283 //
    284 // host can either be an IP address OR an unresolved hostname - resolution will
    285 // be automatically performed in the latter case
    286 func isAllowedHost(host string) (bool, error) {
    287 	if ip := net.ParseIP(host); ip != nil {
    288 		return isIPAllowed(ip), nil
    289 	}
    290 
    291 	addrs, err := lookupHostFn(host)
    292 	if err != nil {
    293 		return false, err
    294 	}
    295 
    296 	for _, addr := range addrs {
    297 		if ip := net.ParseIP(addr); ip == nil || !isIPAllowed(ip) {
    298 			return false, nil
    299 		}
    300 	}
    301 
    302 	return true, nil
    303 }
    304 
    305 func isIPAllowed(ip net.IP) bool {
    306 	return ip.IsLoopback() ||
    307 		ip.Equal(ecsContainerIPv4) ||
    308 		ip.Equal(eksContainerIPv4) ||
    309 		ip.Equal(eksContainerIPv6)
    310 }
    311 
    312 func resolveLocalHTTPCredProvider(ctx context.Context, cfg *aws.Config, endpointURL, authToken string, configs configs) error {
    313 	var resolveErr error
    314 
    315 	parsed, err := url.Parse(endpointURL)
    316 	if err != nil {
    317 		resolveErr = fmt.Errorf("invalid URL, %w", err)
    318 	} else {
    319 		host := parsed.Hostname()
    320 		if len(host) == 0 {
    321 			resolveErr = fmt.Errorf("unable to parse host from local HTTP cred provider URL")
    322 		} else if parsed.Scheme == "http" {
    323 			if isAllowedHost, allowHostErr := isAllowedHost(host); allowHostErr != nil {
    324 				resolveErr = fmt.Errorf("failed to resolve host %q, %v", host, allowHostErr)
    325 			} else if !isAllowedHost {
    326 				resolveErr = fmt.Errorf("invalid endpoint host, %q, only loopback/ecs/eks hosts are allowed", host)
    327 			}
    328 		}
    329 	}
    330 
    331 	if resolveErr != nil {
    332 		return resolveErr
    333 	}
    334 
    335 	return resolveHTTPCredProvider(ctx, cfg, endpointURL, authToken, configs)
    336 }
    337 
    338 func resolveHTTPCredProvider(ctx context.Context, cfg *aws.Config, url, authToken string, configs configs) error {
    339 	optFns := []func(*endpointcreds.Options){
    340 		func(options *endpointcreds.Options) {
    341 			if len(authToken) != 0 {
    342 				options.AuthorizationToken = authToken
    343 			}
    344 			if authFilePath := os.Getenv(httpProviderAuthFileEnvVar); authFilePath != "" {
    345 				options.AuthorizationTokenProvider = endpointcreds.TokenProviderFunc(func() (string, error) {
    346 					var contents []byte
    347 					var err error
    348 					if contents, err = os.ReadFile(authFilePath); err != nil {
    349 						return "", fmt.Errorf("failed to read authorization token from %v: %v", authFilePath, err)
    350 					}
    351 					return string(contents), nil
    352 				})
    353 			}
    354 			options.APIOptions = cfg.APIOptions
    355 			if cfg.Retryer != nil {
    356 				options.Retryer = cfg.Retryer()
    357 			}
    358 			options.CredentialSources = getCredentialSources(ctx)
    359 		},
    360 	}
    361 
    362 	optFn, found, err := getEndpointCredentialProviderOptions(ctx, configs)
    363 	if err != nil {
    364 		return err
    365 	}
    366 	if found {
    367 		optFns = append(optFns, optFn)
    368 	}
    369 
    370 	provider := endpointcreds.New(url, optFns...)
    371 
    372 	cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, provider, func(options *aws.CredentialsCacheOptions) {
    373 		options.ExpiryWindow = 5 * time.Minute
    374 	})
    375 	if err != nil {
    376 		return err
    377 	}
    378 
    379 	return nil
    380 }
    381 
    382 func resolveCredsFromSource(ctx context.Context, cfg *aws.Config, envConfig *EnvConfig, sharedCfg *SharedConfig, configs configs) (context.Context, error) {
    383 	switch sharedCfg.CredentialSource {
    384 	case credSourceEc2Metadata:
    385 		ctx = addCredentialSource(ctx, aws.CredentialSourceIMDS)
    386 		return ctx, resolveEC2RoleCredentials(ctx, cfg, configs)
    387 
    388 	case credSourceEnvironment:
    389 		ctx = addCredentialSource(ctx, aws.CredentialSourceHTTP)
    390 		cfg.Credentials = credentials.StaticCredentialsProvider{Value: envConfig.Credentials, Source: getCredentialSources(ctx)}
    391 
    392 	case credSourceECSContainer:
    393 		ctx = addCredentialSource(ctx, aws.CredentialSourceHTTP)
    394 		if len(envConfig.ContainerCredentialsRelativePath) != 0 {
    395 			return ctx, resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
    396 		}
    397 		if len(envConfig.ContainerCredentialsEndpoint) != 0 {
    398 			return ctx, resolveLocalHTTPCredProvider(ctx, cfg, envConfig.ContainerCredentialsEndpoint, envConfig.ContainerAuthorizationToken, configs)
    399 		}
    400 		return ctx, fmt.Errorf("EcsContainer was specified as the credential_source, but neither 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' or AWS_CONTAINER_CREDENTIALS_FULL_URI' was set")
    401 
    402 	default:
    403 		return ctx, fmt.Errorf("credential_source values must be EcsContainer, Ec2InstanceMetadata, or Environment")
    404 	}
    405 
    406 	return ctx, nil
    407 }
    408 
    409 func resolveEC2RoleCredentials(ctx context.Context, cfg *aws.Config, configs configs) error {
    410 	optFns := make([]func(*ec2rolecreds.Options), 0, 2)
    411 
    412 	optFn, found, err := getEC2RoleCredentialProviderOptions(ctx, configs)
    413 	if err != nil {
    414 		return err
    415 	}
    416 	if found {
    417 		optFns = append(optFns, optFn)
    418 	}
    419 
    420 	optFns = append(optFns, func(o *ec2rolecreds.Options) {
    421 		// Only define a client from config if not already defined.
    422 		if o.Client == nil {
    423 			o.Client = imds.NewFromConfig(*cfg)
    424 		}
    425 		o.CredentialSources = getCredentialSources(ctx)
    426 	})
    427 
    428 	provider := ec2rolecreds.New(optFns...)
    429 
    430 	cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, provider)
    431 	if err != nil {
    432 		return err
    433 	}
    434 	return nil
    435 }
    436 
    437 func getAWSConfigSources(cfgs configs) (*EnvConfig, *SharedConfig, configs) {
    438 	var (
    439 		envConfig    *EnvConfig
    440 		sharedConfig *SharedConfig
    441 		other        configs
    442 	)
    443 
    444 	for i := range cfgs {
    445 		switch c := cfgs[i].(type) {
    446 		case EnvConfig:
    447 			if envConfig == nil {
    448 				envConfig = &c
    449 			}
    450 		case *EnvConfig:
    451 			if envConfig == nil {
    452 				envConfig = c
    453 			}
    454 		case SharedConfig:
    455 			if sharedConfig == nil {
    456 				sharedConfig = &c
    457 			}
    458 		case *SharedConfig:
    459 			if envConfig == nil {
    460 				sharedConfig = c
    461 			}
    462 		default:
    463 			other = append(other, c)
    464 		}
    465 	}
    466 
    467 	if envConfig == nil {
    468 		envConfig = &EnvConfig{}
    469 	}
    470 
    471 	if sharedConfig == nil {
    472 		sharedConfig = &SharedConfig{}
    473 	}
    474 
    475 	return envConfig, sharedConfig, other
    476 }
    477 
    478 // AssumeRoleTokenProviderNotSetError is an error returned when creating a
    479 // session when the MFAToken option is not set when shared config is configured
    480 // load assume a role with an MFA token.
    481 type AssumeRoleTokenProviderNotSetError struct{}
    482 
    483 // Error is the error message
    484 func (e AssumeRoleTokenProviderNotSetError) Error() string {
    485 	return fmt.Sprintf("assume role with MFA enabled, but AssumeRoleTokenProvider session option not set.")
    486 }
    487 
    488 func assumeWebIdentity(ctx context.Context, cfg *aws.Config, filepath string, roleARN, sessionName string, configs configs) error {
    489 	if len(filepath) == 0 {
    490 		return fmt.Errorf("token file path is not set")
    491 	}
    492 
    493 	optFns := []func(*stscreds.WebIdentityRoleOptions){
    494 		func(options *stscreds.WebIdentityRoleOptions) {
    495 			options.RoleSessionName = sessionName
    496 		},
    497 	}
    498 
    499 	optFn, found, err := getWebIdentityCredentialProviderOptions(ctx, configs)
    500 	if err != nil {
    501 		return err
    502 	}
    503 
    504 	if found {
    505 		optFns = append(optFns, optFn)
    506 	}
    507 
    508 	opts := stscreds.WebIdentityRoleOptions{
    509 		RoleARN: roleARN,
    510 	}
    511 
    512 	optFns = append(optFns, func(options *stscreds.WebIdentityRoleOptions) {
    513 		options.CredentialSources = getCredentialSources(ctx)
    514 	})
    515 
    516 	for _, fn := range optFns {
    517 		fn(&opts)
    518 	}
    519 
    520 	if len(opts.RoleARN) == 0 {
    521 		return fmt.Errorf("role ARN is not set")
    522 	}
    523 
    524 	client := opts.Client
    525 	if client == nil {
    526 		client = sts.NewFromConfig(*cfg)
    527 	}
    528 
    529 	provider := stscreds.NewWebIdentityRoleProvider(client, roleARN, stscreds.IdentityTokenFile(filepath), optFns...)
    530 
    531 	cfg.Credentials = provider
    532 
    533 	return nil
    534 }
    535 
    536 func credsFromAssumeRole(ctx context.Context, cfg *aws.Config, sharedCfg *SharedConfig, configs configs) (err error) {
    537 	// resolve credentials early
    538 	credentialSources := getCredentialSources(ctx)
    539 	optFns := []func(*stscreds.AssumeRoleOptions){
    540 		func(options *stscreds.AssumeRoleOptions) {
    541 			options.RoleSessionName = sharedCfg.RoleSessionName
    542 			if sharedCfg.RoleDurationSeconds != nil {
    543 				if *sharedCfg.RoleDurationSeconds/time.Minute > 15 {
    544 					options.Duration = *sharedCfg.RoleDurationSeconds
    545 				}
    546 			}
    547 			// Assume role with external ID
    548 			if len(sharedCfg.ExternalID) > 0 {
    549 				options.ExternalID = aws.String(sharedCfg.ExternalID)
    550 			}
    551 
    552 			// Assume role with MFA
    553 			if len(sharedCfg.MFASerial) != 0 {
    554 				options.SerialNumber = aws.String(sharedCfg.MFASerial)
    555 			}
    556 
    557 			// add existing credential chain
    558 			options.CredentialSources = credentialSources
    559 		},
    560 	}
    561 
    562 	optFn, found, err := getAssumeRoleCredentialProviderOptions(ctx, configs)
    563 	if err != nil {
    564 		return err
    565 	}
    566 	if found {
    567 		optFns = append(optFns, optFn)
    568 	}
    569 
    570 	{
    571 		// Synthesize options early to validate configuration errors sooner to ensure a token provider
    572 		// is present if the SerialNumber was set.
    573 		var o stscreds.AssumeRoleOptions
    574 		for _, fn := range optFns {
    575 			fn(&o)
    576 		}
    577 		if o.TokenProvider == nil && o.SerialNumber != nil {
    578 			return AssumeRoleTokenProviderNotSetError{}
    579 		}
    580 	}
    581 	cfg.Credentials = stscreds.NewAssumeRoleProvider(sts.NewFromConfig(*cfg), sharedCfg.RoleARN, optFns...)
    582 
    583 	return nil
    584 }
    585 
    586 // wrapWithCredentialsCache will wrap provider with an aws.CredentialsCache
    587 // with the provided options if the provider is not already a
    588 // aws.CredentialsCache.
    589 func wrapWithCredentialsCache(
    590 	ctx context.Context,
    591 	cfgs configs,
    592 	provider aws.CredentialsProvider,
    593 	optFns ...func(options *aws.CredentialsCacheOptions),
    594 ) (aws.CredentialsProvider, error) {
    595 	_, ok := provider.(*aws.CredentialsCache)
    596 	if ok {
    597 		return provider, nil
    598 	}
    599 
    600 	credCacheOptions, optionsFound, err := getCredentialsCacheOptionsProvider(ctx, cfgs)
    601 	if err != nil {
    602 		return nil, err
    603 	}
    604 
    605 	// force allocation of a new slice if the additional options are
    606 	// needed, to prevent overwriting the passed in slice of options.
    607 	optFns = optFns[:len(optFns):len(optFns)]
    608 	if optionsFound {
    609 		optFns = append(optFns, credCacheOptions)
    610 	}
    611 
    612 	return aws.NewCredentialsCache(provider, optFns...), nil
    613 }
    614 
    615 // credentialSource stores the chain of providers that was used to create an instance of
    616 // a credentials provider on the context
    617 type credentialSource struct{}
    618 
    619 func addCredentialSource(ctx context.Context, source aws.CredentialSource) context.Context {
    620 	existing, ok := ctx.Value(credentialSource{}).([]aws.CredentialSource)
    621 	if !ok {
    622 		existing = []aws.CredentialSource{source}
    623 	} else {
    624 		existing = append(existing, source)
    625 	}
    626 	return context.WithValue(ctx, credentialSource{}, existing)
    627 }
    628 
    629 func getCredentialSources(ctx context.Context) []aws.CredentialSource {
    630 	return ctx.Value(credentialSource{}).([]aws.CredentialSource)
    631 }
    632 
    633 func resolveLoginCredentials(ctx context.Context, cfg *aws.Config, sharedCfg *SharedConfig, configs configs) error {
    634 	cacheDir := os.Getenv("AWS_LOGIN_CACHE_DIRECTORY")
    635 	tokenPath, err := logincreds.StandardCachedTokenFilepath(sharedCfg.LoginSession, cacheDir)
    636 	if err != nil {
    637 		return err
    638 	}
    639 
    640 	svc := signin.NewFromConfig(*cfg)
    641 	provider := logincreds.New(svc, tokenPath, func(o *logincreds.Options) {
    642 		o.CredentialSources = getCredentialSources(ctx)
    643 		o.RestrictPermissions = cfg.RestrictFilePermissions != aws.RestrictFilePermissionsUnrestricted
    644 	})
    645 	cfg.Credentials, err = wrapWithCredentialsCache(ctx, configs, provider)
    646 	if err != nil {
    647 		return err
    648 	}
    649 	return nil
    650 }