diff --git a/aws_config.go b/aws_config.go index de5d89c9..f63c8622 100644 --- a/aws_config.go +++ b/aws_config.go @@ -35,10 +35,12 @@ func GetAwsConfig(ctx context.Context, c *Config) (aws.Config, error) { } } - credentialsProvider, source, err := getCredentialsProvider(ctx, c) + credentialsProvider, initialSource, err := getCredentialsProvider(ctx, c) if err != nil { return aws.Config{}, err } + creds, _ := credentialsProvider.Retrieve(ctx) + log.Printf("[INFO] Retrieved credentials from %q", creds.Source) var retryer aws.Retryer retryer = retry.NewStandard() @@ -65,7 +67,7 @@ func GetAwsConfig(ctx context.Context, c *Config) (aws.Config, error) { return retryer }), ) - if source == ec2rolecreds.ProviderName { + if initialSource == ec2rolecreds.ProviderName { loadOptions = append( loadOptions, config.WithEC2IMDSRegion(), diff --git a/aws_config_test.go b/aws_config_test.go index ec815f0a..073b54b7 100644 --- a/aws_config_test.go +++ b/aws_config_test.go @@ -845,6 +845,48 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey [some-profile] aws_access_key_id = DefaultSharedCredentialsAccessKey aws_secret_access_key = DefaultSharedCredentialsSecretKey +`, + }, + { + Config: &Config{}, + Description: "AWS_ACCESS_KEY_ID overrides AWS_PROFILE", + EnvironmentVariables: map[string]string{ + "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, + "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, + "AWS_PROFILE": "SharedCredentialsProfile", + }, + SharedCredentialsFile: ` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey + +[SharedCredentialsProfile] +aws_access_key_id = ProfileSharedCredentialsAccessKey +aws_secret_access_key = ProfileSharedCredentialsSecretKey +`, + MockStsEndpoints: []*servicemocks.MockEndpoint{ + servicemocks.MockStsGetCallerIdentityValidEndpoint, + }, + ExpectedCredentialsValue: mockdata.MockEnvCredentials, + }, + { + Config: &Config{ + Region: "us-east-1", + }, + Description: "AWS_ACCESS_KEY_ID does not override invalid profile name from envvar", + EnvironmentVariables: map[string]string{ + "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, + "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, + "AWS_PROFILE": "no-such-profile", + }, + ExpectedError: func(err error) bool { + var e config.SharedConfigProfileNotExistError + return errors.As(err, &e) + }, + SharedCredentialsFile: ` +[some-profile] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey `, }, } diff --git a/credentials.go b/credentials.go index 6b7419f3..83302d93 100644 --- a/credentials.go +++ b/credentials.go @@ -4,6 +4,8 @@ import ( "context" "fmt" "log" + "os" + "strings" "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/config" @@ -33,45 +35,83 @@ func getCredentialsProvider(ctx context.Context, c *Config) (aws.CredentialsProv return nil, "", err } + if c.Profile != "" && os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" { + log.Printf(`[WARN] A Profile was specified along with the environment variables "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY". ` + + "The Profile is now used instead of the environment variable credentials. This may lead to unexpected behavior.") + } + + // The default AWS SDK authentication flow silently ignores invalid Profiles. Pre-validate that the Profile exists + // https://github.com/aws/aws-sdk-go-v2/issues/1591 profile := c.Profile if profile == "" { profile = envConfig.SharedConfigProfile } - sharedCredentialsFiles, err := c.ResolveSharedCredentialsFiles() - if err != nil { - return nil, "", err - } - if len(sharedCredentialsFiles) == 0 { - sharedCredentialsFiles = []string{envConfig.SharedCredentialsFile} - } + if profile != "" { + sharedCredentialsFiles, err := c.ResolveSharedCredentialsFiles() + if err != nil { + return nil, "", err + } + if len(sharedCredentialsFiles) != 0 { + f := make([]string, len(sharedCredentialsFiles)) + for i, v := range sharedCredentialsFiles { + f[i] = fmt.Sprintf(`"%s"`, v) + } + log.Printf("[DEBUG] Using shared credentials files from configuration: [%s]", strings.Join(f, ", ")) + } else { + if envConfig.SharedCredentialsFile != "" { + log.Printf("[DEBUG] Using shared credentials file environment variables: %q", envConfig.SharedCredentialsFile) + sharedCredentialsFiles = []string{envConfig.SharedCredentialsFile} + } + } - sharedConfigFiles, err := c.ResolveSharedConfigFiles() - if err != nil { - return nil, "", err - } - if len(sharedConfigFiles) == 0 { - sharedConfigFiles = []string{envConfig.SharedConfigFile} - } + sharedConfigFiles, err := c.ResolveSharedConfigFiles() + if err != nil { + return nil, "", err + } + if len(sharedConfigFiles) != 0 { + f := make([]string, len(sharedConfigFiles)) + for i, v := range sharedConfigFiles { + f[i] = fmt.Sprintf(`"%s"`, v) + } + log.Printf("[DEBUG] Using shared configuration files from configuration: %v", strings.Join(f, ", ")) + } else { + if envConfig.SharedConfigFile != "" { + log.Printf("[DEBUG] Using shared configuration file environment variables: %s", envConfig.SharedConfigFile) + sharedConfigFiles = []string{envConfig.SharedConfigFile} + } + } - // The default AWS SDK authentication flow silently ignores invalid Profiles. Pre-validate that the Profile exists - // https://github.com/aws/aws-sdk-go-v2/issues/1591 - if profile != "" { - _, err := config.LoadSharedConfigProfile(ctx, profile, func(opts *config.LoadSharedConfigOptions) { + _, err = config.LoadSharedConfigProfile(ctx, profile, func(opts *config.LoadSharedConfigOptions) { opts.CredentialsFiles = sharedCredentialsFiles opts.ConfigFiles = sharedConfigFiles }) if err != nil { return nil, "", err } + } + // We need to validate both the configured and envvar named profiles for validity, + // but to use proper precedence, we only set the configured named profile + if c.Profile != "" { + log.Printf("[DEBUG] Using profile from configuration: %q", c.Profile) loadOptions = append( loadOptions, config.WithSharedConfigProfile(c.Profile), ) - } if c.AccessKey != "" || c.SecretKey != "" || c.Token != "" { + params := make([]string, 0, 3) //nolint:gomnd + if c.AccessKey != "" { + params = append(params, "access key") + } + if c.SecretKey != "" { + params = append(params, "secret key") + } + if c.Token != "" { + params = append(params, "token") + } + log.Printf("[DEBUG] Using %s from configuration", strings.Join(params, ", ")) loadOptions = append( loadOptions, config.WithCredentialsProvider( @@ -91,6 +131,11 @@ func getCredentialsProvider(ctx context.Context, c *Config) (aws.CredentialsProv creds, err := cfg.Credentials.Retrieve(ctx) if err != nil { + if c.Profile != "" && os.Getenv("AWS_ACCESS_KEY_ID") != "" && os.Getenv("AWS_SECRET_ACCESS_KEY") != "" { + err = fmt.Errorf(`A Profile was specified along with the environment variables "AWS_ACCESS_KEY_ID" and "AWS_SECRET_ACCESS_KEY". The Profile is now used instead of the environment variable credentials. + +Error: %w`, err) + } return nil, "", c.NewNoValidCredentialSourcesError(err) } @@ -98,6 +143,7 @@ func getCredentialsProvider(ctx context.Context, c *Config) (aws.CredentialsProv return cfg.Credentials, creds.Source, nil } + log.Printf("[INFO] Retrieved initial credentials from %q", creds.Source) provider, err := assumeRoleCredentialsProvider(ctx, cfg, c) return provider, creds.Source, err @@ -106,8 +152,7 @@ func getCredentialsProvider(ctx context.Context, c *Config) (aws.CredentialsProv func assumeRoleCredentialsProvider(ctx context.Context, awsConfig aws.Config, c *Config) (aws.CredentialsProvider, error) { ar := c.AssumeRole // When assuming a role, we need to first authenticate the base credentials above, then assume the desired role - log.Printf("[INFO] Attempting to AssumeRole %s (SessionName: %q, ExternalId: %q)", - ar.RoleARN, ar.SessionName, ar.ExternalID) + log.Printf("[INFO] Assuming IAM Role %q (SessionName: %q, ExternalId: %q)", ar.RoleARN, ar.SessionName, ar.ExternalID) client := stsClient(awsConfig, c) diff --git a/v2/awsv1shim/session_test.go b/v2/awsv1shim/session_test.go index e1c5b99a..98ee810e 100644 --- a/v2/awsv1shim/session_test.go +++ b/v2/awsv1shim/session_test.go @@ -890,6 +890,48 @@ aws_secret_access_key = DefaultSharedCredentialsSecretKey [some-profile] aws_access_key_id = DefaultSharedCredentialsAccessKey aws_secret_access_key = DefaultSharedCredentialsSecretKey +`, + }, + { + Config: &awsbase.Config{}, + Description: "AWS_ACCESS_KEY_ID overrides AWS_PROFILE", + EnvironmentVariables: map[string]string{ + "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, + "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, + "AWS_PROFILE": "SharedCredentialsProfile", + }, + SharedCredentialsFile: ` +[default] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey + +[SharedCredentialsProfile] +aws_access_key_id = ProfileSharedCredentialsAccessKey +aws_secret_access_key = ProfileSharedCredentialsSecretKey +`, + MockStsEndpoints: []*servicemocks.MockEndpoint{ + servicemocks.MockStsGetCallerIdentityValidEndpoint, + }, + ExpectedCredentialsValue: mockdata.MockEnvCredentials, + }, + { + Config: &awsbase.Config{ + Region: "us-east-1", + }, + Description: "AWS_ACCESS_KEY_ID does not override invalid profile name from envvar", + EnvironmentVariables: map[string]string{ + "AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey, + "AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey, + "AWS_PROFILE": "no-such-profile", + }, + ExpectedError: func(err error) bool { + var e configv2.SharedConfigProfileNotExistError + return errors.As(err, &e) + }, + SharedCredentialsFile: ` +[some-profile] +aws_access_key_id = DefaultSharedCredentialsAccessKey +aws_secret_access_key = DefaultSharedCredentialsSecretKey `, }, }