...

Source file src/github.com/aws/aws-sdk-go-v2/config/resolve_credentials.go

Documentation: github.com/aws/aws-sdk-go-v2/config

     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  	// valid credential source values
    27  	credSourceEc2Metadata      = "Ec2InstanceMetadata"
    28  	credSourceEnvironment      = "Environment"
    29  	credSourceECSContainer     = "EcsContainer"
    30  	httpProviderAuthFileEnvVar = "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE"
    31  )
    32  
    33  // direct representation of the IPv4 address for the ECS container
    34  // "169.254.170.2"
    35  var ecsContainerIPv4 net.IP = []byte{
    36  	169, 254, 170, 2,
    37  }
    38  
    39  // direct representation of the IPv4 address for the EKS container
    40  // "169.254.170.23"
    41  var eksContainerIPv4 net.IP = []byte{
    42  	169, 254, 170, 23,
    43  }
    44  
    45  // direct representation of the IPv6 address for the EKS container
    46  // "fd00:ec2::23"
    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" // not constant to allow for swapping during unit-testing
    56  )
    57  
    58  // resolveCredentials extracts a credential provider from slice of config
    59  // sources.
    60  //
    61  // If an explicit credential provider is not found the resolver will fallback
    62  // to resolving credentials by extracting a credential provider from EnvConfig
    63  // and SharedConfig.
    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  // resolveCredentialProvider extracts the first instance of Credentials from the
    74  // config slices.
    75  //
    76  // The resolved CredentialProvider will be wrapped in a cache to ensure the
    77  // credentials are only refreshed when needed. This also protects the
    78  // credential provider to be used concurrently.
    79  //
    80  // Config providers used:
    81  // * credentialsProviderProvider
    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  // resolveCredentialChain resolves a credential provider chain using EnvConfig
    97  // and SharedConfig if present in the slice of provided configs.
    98  //
    99  // The resolved CredentialProvider will be wrapped in a cache to ensure the
   100  // credentials are only refreshed when needed. This also protects the
   101  // credential provider to be used concurrently.
   102  func resolveCredentialChain(ctx context.Context, cfg *aws.Config, configs configs) (err error) {
   103  	envConfig, sharedConfig, other := getAWSConfigSources(configs)
   104  
   105  	// When checking if a profile was specified programmatically we should only consider the "other"
   106  	// configuration sources that have been provided. This ensures we correctly honor the expected credential
   107  	// hierarchy.
   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  	// Wrap the resolved provider in a cache so the SDK will cache credentials.
   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  		// Assume IAM role with credentials source from a different profile.
   141  		err = resolveCredsFromProfile(ctx, cfg, envConfig, sharedConfig.Source, configs)
   142  
   143  	case sharedConfig.Credentials.HasKeys():
   144  		// Static Credentials from Shared Config/Credentials file.
   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  		// Credentials from Assume Web Identity token require an IAM Role, and
   154  		// that roll will be assumed. May be wrapped with another assume role
   155  		// via SourceProfile.
   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  		// Get credentials from CredentialProcess
   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  // isAllowedHost allows host to be loopback or known ECS/EKS container IPs
   251  //
   252  // host can either be an IP address OR an unresolved hostname - resolution will
   253  // be automatically performed in the latter case
   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  		// Only define a client from config if not already defined.
   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  // AssumeRoleTokenProviderNotSetError is an error returned when creating a
   440  // session when the MFAToken option is not set when shared config is configured
   441  // load assume a role with an MFA token.
   442  type AssumeRoleTokenProviderNotSetError struct{}
   443  
   444  // Error is the error message
   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  			// Assume role with external ID
   503  			if len(sharedCfg.ExternalID) > 0 {
   504  				options.ExternalID = aws.String(sharedCfg.ExternalID)
   505  			}
   506  
   507  			// Assume role with MFA
   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  		// Synthesize options early to validate configuration errors sooner to ensure a token provider
   524  		// is present if the SerialNumber was set.
   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  // wrapWithCredentialsCache will wrap provider with an aws.CredentialsCache
   540  // with the provided options if the provider is not already a
   541  // aws.CredentialsCache.
   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  	// force allocation of a new slice if the additional options are
   559  	// needed, to prevent overwriting the passed in slice of options.
   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