resolve_credentials.go raw

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