blob: 5e42e36fd30cdd917d34a4639bf18aa419bed70e [file] [log] [blame]
// Copyright (c) HashiCorp, Inc.
// SPDX-License-Identifier: BUSL-1.1
package s3
import (
"context"
"fmt"
"os"
"path/filepath"
"regexp"
"testing"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/google/go-cmp/cmp"
"github.com/google/go-cmp/cmp/cmpopts"
configtesting "github.com/hashicorp/aws-sdk-go-base/v2/configtesting"
"github.com/hashicorp/aws-sdk-go-base/v2/mockdata"
"github.com/hashicorp/aws-sdk-go-base/v2/servicemocks"
"github.com/hashicorp/terraform/internal/configs/hcl2shim"
"github.com/hashicorp/terraform/internal/tfdiags"
"github.com/zclconf/go-cty/cty"
)
const (
// Shockingly, this is not defined in the SDK
sharedConfigCredentialsProvider = "SharedConfigCredentials"
)
type DiagsValidator func(*testing.T, tfdiags.Diagnostics)
func ExpectNoDiags(t *testing.T, diags tfdiags.Diagnostics) {
t.Helper()
expectDiagsCount(t, diags, 0)
}
func expectDiagsCount(t *testing.T, diags tfdiags.Diagnostics, c int) {
t.Helper()
if l := len(diags); l != c {
t.Fatalf("Diagnostics: expected %d element, got %d\n%s", c, l, diagnosticsString(diags))
}
}
func ExpectDiagsEqual(expected tfdiags.Diagnostics) DiagsValidator {
return func(t *testing.T, diags tfdiags.Diagnostics) {
t.Helper()
if diff := cmp.Diff(diags, expected, tfdiags.DiagnosticComparer); diff != "" {
t.Fatalf("unexpected diagnostics difference: %s", diff)
}
}
}
// ExpectDiagMatching returns a validator expeceting a single Diagnostic with fields matching the expectation
func ExpectDiagMatching(severity tfdiags.Severity, summary matcher, detail matcher) DiagsValidator {
return ExpectDiags(
diagMatching(severity, summary, detail),
)
}
type diagValidator func(*testing.T, tfdiags.Diagnostic)
func ExpectDiags(validators ...diagValidator) DiagsValidator {
return func(t *testing.T, diags tfdiags.Diagnostics) {
t.Helper()
count := len(validators)
if l := len(diags); l < count {
count = l
}
for i := 0; i < count; i++ {
validators[i](t, diags[i])
}
expectDiagsCount(t, diags, len(validators))
}
}
func diagMatching(severity tfdiags.Severity, summary matcher, detail matcher) diagValidator {
return func(t *testing.T, diag tfdiags.Diagnostic) {
t.Helper()
if severity != diag.Severity() || !summary.Match(diag.Description().Summary) || !detail.Match(diag.Description().Detail) {
t.Errorf("expected Diagnostic matching %#v, got %#v",
tfdiags.Sourceless(
severity,
summary.String(),
detail.String(),
),
diag,
)
}
}
}
type matcher interface {
fmt.Stringer
Match(string) bool
}
type equalsMatcher string
func (m equalsMatcher) Match(s string) bool {
return string(m) == s
}
func (m equalsMatcher) String() string {
return string(m)
}
type regexpMatcher struct {
re *regexp.Regexp
}
func newRegexpMatcher(re string) regexpMatcher {
return regexpMatcher{
re: regexp.MustCompile(re),
}
}
func (m regexpMatcher) Match(s string) bool {
return m.re.MatchString(s)
}
func (m regexpMatcher) String() string {
return m.re.String()
}
type ignoreMatcher struct{}
func (m ignoreMatcher) Match(s string) bool {
return true
}
func (m ignoreMatcher) String() string {
return "ignored"
}
// Corrected from aws-sdk-go-base v1 & v2
const mockStsAssumeRolePolicy = `{
"Version": "2012-10-17",
"Statement": {
"Effect": "Allow",
"Action": "*",
"Resource": "*"
}
}`
func TestBackendConfig_Authentication(t *testing.T) {
testCases := map[string]struct {
config map[string]any
EnableEc2MetadataServer bool
EnableEcsCredentialsServer bool
EnableWebIdentityEnvVars bool
// EnableWebIdentityConfig bool // Not supported
EnvironmentVariables map[string]string
ExpectedCredentialsValue aws.Credentials
MockStsEndpoints []*servicemocks.MockEndpoint
SharedConfigurationFile string
SharedCredentialsFile string
ValidateDiags DiagsValidator
}{
"empty config": {
config: map[string]any{},
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("No valid credential sources found"),
ignoreMatcher{},
),
},
"config AccessKey": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
ValidateDiags: ExpectNoDiags,
},
"config Profile shared credentials profile aws_access_key_id": {
config: map[string]any{
"profile": "SharedCredentialsProfile",
},
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "ProfileSharedCredentialsAccessKey",
SecretAccessKey: "ProfileSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
[SharedCredentialsProfile]
aws_access_key_id = ProfileSharedCredentialsAccessKey
aws_secret_access_key = ProfileSharedCredentialsSecretKey
`,
ValidateDiags: ExpectNoDiags,
},
"environment AWS_ACCESS_KEY_ID does not override config Profile": {
config: map[string]any{
"profile": "SharedCredentialsProfile",
},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "ProfileSharedCredentialsAccessKey",
SecretAccessKey: "ProfileSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
[SharedCredentialsProfile]
aws_access_key_id = ProfileSharedCredentialsAccessKey
aws_secret_access_key = ProfileSharedCredentialsSecretKey
`,
ValidateDiags: ExpectNoDiags,
},
"environment AWS_ACCESS_KEY_ID": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: mockdata.MockEnvCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
ValidateDiags: ExpectNoDiags,
},
"environment AWS_PROFILE shared credentials profile aws_access_key_id": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_PROFILE": "SharedCredentialsProfile",
},
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "ProfileSharedCredentialsAccessKey",
SecretAccessKey: "ProfileSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
[SharedCredentialsProfile]
aws_access_key_id = ProfileSharedCredentialsAccessKey
aws_secret_access_key = ProfileSharedCredentialsSecretKey
`,
ValidateDiags: ExpectNoDiags,
},
"environment AWS_SESSION_TOKEN": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
"AWS_SESSION_TOKEN": servicemocks.MockEnvSessionToken,
},
ExpectedCredentialsValue: mockdata.MockEnvCredentialsWithSessionToken,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"shared credentials default aws_access_key_id": {
config: map[string]any{},
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
},
"web identity token access key": {
config: map[string]any{},
EnableWebIdentityEnvVars: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"EC2 metadata access key": {
config: map[string]any{},
EnableEc2MetadataServer: true,
ExpectedCredentialsValue: mockdata.MockEc2MetadataCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
ValidateDiags: ExpectNoDiags,
},
"ECS credentials access key": {
config: map[string]any{},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: mockdata.MockEcsCredentialsCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"config AccessKey over environment AWS_ACCESS_KEY_ID": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
ValidateDiags: ExpectNoDiags,
},
"config AccessKey over shared credentials default aws_access_key_id": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectNoDiags,
},
"config AccessKey over EC2 metadata access key": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"config AccessKey over ECS credentials access key": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"environment AWS_ACCESS_KEY_ID over shared credentials default aws_access_key_id": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: mockdata.MockEnvCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectNoDiags,
},
"environment AWS_ACCESS_KEY_ID over EC2 metadata access key": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: mockdata.MockEnvCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"environment AWS_ACCESS_KEY_ID over ECS credentials access key": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: mockdata.MockEnvCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"shared credentials default aws_access_key_id over EC2 metadata access key": {
config: map[string]any{},
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
},
"shared credentials default aws_access_key_id over ECS credentials access key": {
config: map[string]any{},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
},
"ECS credentials access key over EC2 metadata access key": {
config: map[string]any{},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: mockdata.MockEcsCredentialsCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"retrieve region from shared configuration file": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStaticCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedConfigurationFile: `
[default]
region = us-east-1
`,
},
"skip EC2 Metadata API check": {
config: map[string]any{
"skip_metadata_api_check": true,
},
// The IMDS server must be enabled so that auth will succeed if the IMDS is called
EnableEc2MetadataServer: true,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("No valid credential sources found"),
ignoreMatcher{},
),
},
"invalid profile name from envvar": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_PROFILE": "no-such-profile",
},
SharedCredentialsFile: `
[some-profile]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("failed to get shared config profile, no-such-profile"),
equalsMatcher(""),
),
},
"invalid profile name from config": {
config: map[string]any{
"profile": "no-such-profile",
},
SharedCredentialsFile: `
[some-profile]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("failed to get shared config profile, no-such-profile"),
equalsMatcher(""),
),
},
"AWS_ACCESS_KEY_ID overrides AWS_PROFILE": {
config: map[string]any{},
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,
ValidateDiags: ExpectNoDiags,
},
"AWS_ACCESS_KEY_ID does not override invalid profile name from envvar": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
"AWS_PROFILE": "no-such-profile",
},
SharedCredentialsFile: `
[some-profile]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
ValidateDiags: ExpectDiagMatching(
tfdiags.Error,
equalsMatcher("failed to get shared config profile, no-such-profile"),
equalsMatcher(""),
),
},
}
for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
servicemocks.InitSessionTestEnv(t)
ctx := context.TODO()
// Populate required fields
tc.config["region"] = "us-east-1"
tc.config["bucket"] = "bucket"
tc.config["key"] = "key"
if tc.ValidateDiags == nil {
tc.ValidateDiags = ExpectNoDiags
}
if tc.EnableEc2MetadataServer {
closeEc2Metadata := servicemocks.AwsMetadataApiMock(append(
servicemocks.Ec2metadata_securityCredentialsEndpoints,
servicemocks.Ec2metadata_instanceIdEndpoint,
servicemocks.Ec2metadata_iamInfoEndpoint,
))
defer closeEc2Metadata()
}
if tc.EnableEcsCredentialsServer {
closeEcsCredentials := servicemocks.EcsCredentialsApiMock()
defer closeEcsCredentials()
}
if tc.EnableWebIdentityEnvVars /*|| tc.EnableWebIdentityConfig*/ {
file, err := os.CreateTemp("", "aws-sdk-go-base-web-identity-token-file")
if err != nil {
t.Fatalf("unexpected error creating temporary web identity token file: %s", err)
}
defer os.Remove(file.Name())
err = os.WriteFile(file.Name(), []byte(servicemocks.MockWebIdentityToken), 0600)
if err != nil {
t.Fatalf("unexpected error writing web identity token file: %s", err)
}
if tc.EnableWebIdentityEnvVars {
t.Setenv("AWS_ROLE_ARN", servicemocks.MockStsAssumeRoleWithWebIdentityArn)
t.Setenv("AWS_ROLE_SESSION_NAME", servicemocks.MockStsAssumeRoleWithWebIdentitySessionName)
t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", file.Name())
} /*else if tc.EnableWebIdentityConfig {
tc.Config.AssumeRoleWithWebIdentity = &AssumeRoleWithWebIdentity{
RoleARN: servicemocks.MockStsAssumeRoleWithWebIdentityArn,
SessionName: servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
WebIdentityTokenFile: file.Name(),
}
}*/
}
ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints)
defer ts.Close()
tc.config["endpoints"] = map[string]any{
"sts": ts.URL,
}
if tc.SharedConfigurationFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file")
if err != nil {
t.Fatalf("unexpected error creating temporary shared configuration file: %s", err)
}
defer os.Remove(file.Name())
err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600)
if err != nil {
t.Fatalf("unexpected error writing shared configuration file: %s", err)
}
setSharedConfigFile(t, file.Name())
}
if tc.SharedCredentialsFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-credentials-file")
if err != nil {
t.Fatalf("unexpected error creating temporary shared credentials file: %s", err)
}
defer os.Remove(file.Name())
err = os.WriteFile(file.Name(), []byte(tc.SharedCredentialsFile), 0600)
if err != nil {
t.Fatalf("unexpected error writing shared credentials file: %s", err)
}
tc.config["shared_credentials_files"] = []interface{}{file.Name()}
if tc.ExpectedCredentialsValue.Source == sharedConfigCredentialsProvider {
tc.ExpectedCredentialsValue.Source = sharedConfigCredentialsSource(file.Name())
}
}
for k, v := range tc.EnvironmentVariables {
t.Setenv(k, v)
}
b, diags := configureBackend(t, tc.config)
tc.ValidateDiags(t, diags)
if diags.HasErrors() {
return
}
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials")
}
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
}
}
func TestBackendConfig_Authentication_AssumeRoleNested(t *testing.T) {
testCases := map[string]struct {
config map[string]any
EnableEc2MetadataServer bool
EnableEcsCredentialsServer bool
EnvironmentVariables map[string]string
ExpectedCredentialsValue aws.Credentials
MockStsEndpoints []*servicemocks.MockEndpoint
SharedConfigurationFile string
SharedCredentialsFile string
ValidateDiags DiagsValidator
}{
// WAS: "config AccessKey config AssumeRoleARN access key"
"from config access_key": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "environment AWS_ACCESS_KEY_ID config AssumeRoleARN access key"
"from environment AWS_ACCESS_KEY_ID": {
config: map[string]any{
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
},
EnvironmentVariables: map[string]string{
"AWS_ACCESS_KEY_ID": servicemocks.MockEnvAccessKey,
"AWS_SECRET_ACCESS_KEY": servicemocks.MockEnvSecretKey,
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config Profile shared configuration credential_source Ec2InstanceMetadata"
"from config Profile with Ec2InstanceMetadata source": {
config: map[string]any{
"profile": "SharedConfigurationProfile",
},
EnableEc2MetadataServer: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedConfigurationFile: fmt.Sprintf(`
[profile SharedConfigurationProfile]
credential_source = Ec2InstanceMetadata
role_arn = %[1]s
role_session_name = %[2]s
`, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName),
},
// WAS: "environment AWS_PROFILE shared configuration credential_source Ec2InstanceMetadata"
"from environment AWS_PROFILE with Ec2InstanceMetadata source": {
config: map[string]any{},
EnableEc2MetadataServer: true,
EnvironmentVariables: map[string]string{
"AWS_PROFILE": "SharedConfigurationProfile",
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedConfigurationFile: fmt.Sprintf(`
[profile SharedConfigurationProfile]
credential_source = Ec2InstanceMetadata
role_arn = %[1]s
role_session_name = %[2]s
`, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName),
},
// WAS: "config Profile shared configuration source_profile"
"from config Profile with source profile": {
config: map[string]any{
"profile": "SharedConfigurationProfile",
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedConfigurationFile: fmt.Sprintf(`
[profile SharedConfigurationProfile]
role_arn = %[1]s
role_session_name = %[2]s
source_profile = SharedConfigurationSourceProfile
[profile SharedConfigurationSourceProfile]
aws_access_key_id = SharedConfigurationSourceAccessKey
aws_secret_access_key = SharedConfigurationSourceSecretKey
`, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName),
},
// WAS: "environment AWS_PROFILE shared configuration source_profile"
"from environment AWS_PROFILE with source profile": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_PROFILE": "SharedConfigurationProfile",
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedConfigurationFile: fmt.Sprintf(`
[profile SharedConfigurationProfile]
role_arn = %[1]s
role_session_name = %[2]s
source_profile = SharedConfigurationSourceProfile
[profile SharedConfigurationSourceProfile]
aws_access_key_id = SharedConfigurationSourceAccessKey
aws_secret_access_key = SharedConfigurationSourceSecretKey
`, servicemocks.MockStsAssumeRoleArn, servicemocks.MockStsAssumeRoleSessionName),
},
// WAS: "shared credentials default aws_access_key_id config AssumeRoleARN access key"
"from default profile": {
config: map[string]any{
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
SharedCredentialsFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
`,
},
// WAS: "EC2 metadata access key config AssumeRoleARN access key"
"from EC2 metadata": {
config: map[string]any{
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
},
EnableEc2MetadataServer: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "ECS credentials access key config AssumeRoleARN access key"
"from ECS credentials": {
config: map[string]any{
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
},
EnableEcsCredentialsServer: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRoleDuration"
"with duration": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"duration": "1h",
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"DurationSeconds": "3600"}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRoleExternalID"
"with external ID": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"external_id": servicemocks.MockStsAssumeRoleExternalId,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"ExternalId": servicemocks.MockStsAssumeRoleExternalId}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRolePolicy"
"with policy": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"policy": mockStsAssumeRolePolicy,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Policy": mockStsAssumeRolePolicy}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRolePolicyARNs"
"with policy ARNs": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"policy_arns": []any{servicemocks.MockStsAssumeRolePolicyArn},
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"PolicyArns.member.1.arn": servicemocks.MockStsAssumeRolePolicyArn}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRoleTags"
"with tags": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"tags": map[string]any{
servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue,
},
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Tags.member.1.Key": servicemocks.MockStsAssumeRoleTagKey, "Tags.member.1.Value": servicemocks.MockStsAssumeRoleTagValue}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRoleTransitiveTagKeys"
"with transitive tags": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"tags": map[string]any{
servicemocks.MockStsAssumeRoleTagKey: servicemocks.MockStsAssumeRoleTagValue,
},
"transitive_tag_keys": []any{servicemocks.MockStsAssumeRoleTagKey},
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"Tags.member.1.Key": servicemocks.MockStsAssumeRoleTagKey, "Tags.member.1.Value": servicemocks.MockStsAssumeRoleTagValue, "TransitiveTagKeys.member.1": servicemocks.MockStsAssumeRoleTagKey}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "config AssumeRoleSourceIdentity"
"with source identity": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
"source_identity": servicemocks.MockStsAssumeRoleSourceIdentity,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleValidEndpointWithOptions(map[string]string{"SourceIdentity": servicemocks.MockStsAssumeRoleSourceIdentity}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// WAS: "assume role error"
"error": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleArn,
"session_name": servicemocks.MockStsAssumeRoleSessionName,
},
},
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleInvalidEndpointInvalidClientTokenId,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
ValidateDiags: ExpectDiags(
diagMatching(
tfdiags.Error,
equalsMatcher("Cannot assume IAM Role"),
newRegexpMatcher(`IAM Role \(.+\) cannot be assumed.`),
),
),
},
"empty role_arn": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": "",
},
},
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Invalid Value",
"The value cannot be empty or all whitespace",
cty.GetAttrPath("assume_role").GetAttr("role_arn"),
),
}),
},
"nil role_arn": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"assume_role": map[string]any{
"role_arn": nil,
},
},
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Missing Required Value",
`The attribute "assume_role.role_arn" is required by the backend.`+"\n\n"+
"Refer to the backend documentation for additional information which attributes are required.",
cty.GetAttrPath("assume_role").GetAttr("role_arn"),
),
}),
},
}
for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
servicemocks.InitSessionTestEnv(t)
ctx := context.TODO()
// Populate required fields
tc.config["region"] = "us-east-1"
tc.config["bucket"] = "bucket"
tc.config["key"] = "key"
if tc.ValidateDiags == nil {
tc.ValidateDiags = ExpectNoDiags
}
if tc.EnableEc2MetadataServer {
closeEc2Metadata := servicemocks.AwsMetadataApiMock(append(
servicemocks.Ec2metadata_securityCredentialsEndpoints,
servicemocks.Ec2metadata_instanceIdEndpoint,
servicemocks.Ec2metadata_iamInfoEndpoint,
))
defer closeEc2Metadata()
}
if tc.EnableEcsCredentialsServer {
closeEcsCredentials := servicemocks.EcsCredentialsApiMock()
defer closeEcsCredentials()
}
ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints)
defer ts.Close()
tc.config["endpoints"] = map[string]any{
"sts": ts.URL,
}
if tc.SharedConfigurationFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file")
if err != nil {
t.Fatalf("unexpected error creating temporary shared configuration file: %s", err)
}
defer os.Remove(file.Name())
err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600)
if err != nil {
t.Fatalf("unexpected error writing shared configuration file: %s", err)
}
setSharedConfigFile(t, file.Name())
}
if tc.SharedCredentialsFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-credentials-file")
if err != nil {
t.Fatalf("unexpected error creating temporary shared credentials file: %s", err)
}
defer os.Remove(file.Name())
err = os.WriteFile(file.Name(), []byte(tc.SharedCredentialsFile), 0600)
if err != nil {
t.Fatalf("unexpected error writing shared credentials file: %s", err)
}
tc.config["shared_credentials_files"] = []interface{}{file.Name()}
if tc.ExpectedCredentialsValue.Source == sharedConfigCredentialsProvider {
tc.ExpectedCredentialsValue.Source = sharedConfigCredentialsSource(file.Name())
}
}
for k, v := range tc.EnvironmentVariables {
t.Setenv(k, v)
}
b, diags := configureBackend(t, tc.config)
tc.ValidateDiags(t, diags)
if diags.HasErrors() {
return
}
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials: %s", err)
}
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
}
}
func TestBackendConfig_Authentication_AssumeRoleWithWebIdentity(t *testing.T) {
testCases := map[string]struct {
config map[string]any
SetConfig bool
ExpandEnvVars bool
EnvironmentVariables map[string]string
SetTokenFileEnvironmentVariable bool
SharedConfigurationFile string
SetSharedConfigurationFile bool
ExpectedCredentialsValue aws.Credentials
ValidateDiags DiagsValidator
MockStsEndpoints []*servicemocks.MockEndpoint
}{
"config with inline token": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"config with token file": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
},
},
SetConfig: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"config with expanded path": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
},
},
SetConfig: true,
ExpandEnvVars: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"envvar": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ROLE_ARN": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"AWS_ROLE_SESSION_NAME": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
},
SetTokenFileEnvironmentVariable: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"shared configuration file": {
config: map[string]any{},
SharedConfigurationFile: fmt.Sprintf(`
[default]
role_arn = %[1]s
role_session_name = %[2]s
`, servicemocks.MockStsAssumeRoleWithWebIdentityArn, servicemocks.MockStsAssumeRoleWithWebIdentitySessionName),
SetSharedConfigurationFile: true,
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"config overrides envvar": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
},
},
EnvironmentVariables: map[string]string{
"AWS_ROLE_ARN": servicemocks.MockStsAssumeRoleWithWebIdentityAlternateArn,
"AWS_ROLE_SESSION_NAME": servicemocks.MockStsAssumeRoleWithWebIdentityAlternateSessionName,
"AWS_WEB_IDENTITY_TOKEN_FILE": "no-such-file",
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
// "config with file envvar": {
// config: map[string]any{
// "assume_role_with_web_identity": map[string]any{
// "role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
// "session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
// },
// },
// SetTokenFileEnvironmentVariable: true,
// ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
// MockStsEndpoints: []*servicemocks.MockEndpoint{
// servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
// servicemocks.MockStsGetCallerIdentityValidEndpoint,
// },
// },
"envvar overrides shared configuration": {
config: map[string]any{},
EnvironmentVariables: map[string]string{
"AWS_ROLE_ARN": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"AWS_ROLE_SESSION_NAME": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
},
SetTokenFileEnvironmentVariable: true,
SharedConfigurationFile: fmt.Sprintf(`
[default]
role_arn = %[1]s
role_session_name = %[2]s
web_identity_token_file = no-such-file
`, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateArn, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateSessionName),
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"config overrides shared configuration": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
},
},
SharedConfigurationFile: fmt.Sprintf(`
[default]
role_arn = %[1]s
role_session_name = %[2]s
web_identity_token_file = no-such-file
`, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateArn, servicemocks.MockStsAssumeRoleWithWebIdentityAlternateSessionName),
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidEndpoint,
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"with duration": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
"duration": "1h",
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidWithOptions(map[string]string{"DurationSeconds": "3600"}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"with policy": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
"policy": "{}",
},
},
ExpectedCredentialsValue: mockdata.MockStsAssumeRoleWithWebIdentityCredentials,
MockStsEndpoints: []*servicemocks.MockEndpoint{
servicemocks.MockStsAssumeRoleWithWebIdentityValidWithOptions(map[string]string{"Policy": "{}"}),
servicemocks.MockStsGetCallerIdentityValidEndpoint,
},
},
"invalid empty config": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{},
},
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Missing Required Value",
`Exactly one of web_identity_token, web_identity_token_file must be set.`,
cty.GetAttrPath("assume_role_with_web_identity"),
),
attributeErrDiag(
"Missing Required Value",
`The attribute "assume_role_with_web_identity.role_arn" is required by the backend.`+"\n\n"+
"Refer to the backend documentation for additional information which attributes are required.",
cty.GetAttrPath("assume_role_with_web_identity").GetAttr("role_arn"),
),
}),
},
"invalid no token": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
},
},
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Missing Required Value",
`Exactly one of web_identity_token, web_identity_token_file must be set.`,
cty.GetAttrPath("assume_role_with_web_identity"),
),
}),
},
"invalid token config conflict": {
config: map[string]any{
"assume_role_with_web_identity": map[string]any{
"role_arn": servicemocks.MockStsAssumeRoleWithWebIdentityArn,
"session_name": servicemocks.MockStsAssumeRoleWithWebIdentitySessionName,
"web_identity_token": servicemocks.MockWebIdentityToken,
},
},
SetConfig: true,
ValidateDiags: ExpectDiagsEqual(tfdiags.Diagnostics{
attributeErrDiag(
"Invalid Attribute Combination",
`Only one of web_identity_token, web_identity_token_file can be set.`,
cty.GetAttrPath("assume_role_with_web_identity"),
),
}),
},
}
for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
servicemocks.InitSessionTestEnv(t)
ctx := context.TODO()
// Populate required fields
tc.config["region"] = "us-east-1"
tc.config["bucket"] = "bucket"
tc.config["key"] = "key"
if tc.ValidateDiags == nil {
tc.ValidateDiags = ExpectNoDiags
}
for k, v := range tc.EnvironmentVariables {
t.Setenv(k, v)
}
ts := servicemocks.MockAwsApiServer("STS", tc.MockStsEndpoints)
defer ts.Close()
tc.config["endpoints"] = map[string]any{
"sts": ts.URL,
}
tempdir, err := os.MkdirTemp("", "temp")
if err != nil {
t.Fatalf("error creating temp dir: %s", err)
}
defer os.Remove(tempdir)
t.Setenv("TMPDIR", tempdir)
tokenFile, err := os.CreateTemp("", "aws-sdk-go-base-web-identity-token-file")
if err != nil {
t.Fatalf("unexpected error creating temporary web identity token file: %s", err)
}
tokenFileName := tokenFile.Name()
defer os.Remove(tokenFileName)
err = os.WriteFile(tokenFileName, []byte(servicemocks.MockWebIdentityToken), 0600)
if err != nil {
t.Fatalf("unexpected error writing web identity token file: %s", err)
}
if tc.ExpandEnvVars {
tmpdir := os.Getenv("TMPDIR")
rel, err := filepath.Rel(tmpdir, tokenFileName)
if err != nil {
t.Fatalf("error making path relative: %s", err)
}
t.Logf("relative: %s", rel)
tokenFileName = filepath.Join("$TMPDIR", rel)
t.Logf("env tempfile: %s", tokenFileName)
}
if tc.SetConfig {
ar := tc.config["assume_role_with_web_identity"].(map[string]any)
ar["web_identity_token_file"] = tokenFileName
}
if tc.SetTokenFileEnvironmentVariable {
t.Setenv("AWS_WEB_IDENTITY_TOKEN_FILE", tokenFileName)
}
if tc.SharedConfigurationFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file")
if err != nil {
t.Fatalf("unexpected error creating temporary shared configuration file: %s", err)
}
defer os.Remove(file.Name())
if tc.SetSharedConfigurationFile {
tc.SharedConfigurationFile += fmt.Sprintf("web_identity_token_file = %s\n", tokenFileName)
}
err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600)
if err != nil {
t.Fatalf("unexpected error writing shared configuration file: %s", err)
}
tc.config["shared_config_files"] = []any{file.Name()}
}
tc.config["skip_credentials_validation"] = true
b, diags := configureBackend(t, tc.config)
tc.ValidateDiags(t, diags)
if diags.HasErrors() {
return
}
credentials, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("Error when requesting credentials: %s", err)
}
if diff := cmp.Diff(credentials, tc.ExpectedCredentialsValue, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
}
}
var _ configtesting.TestDriver = &testDriver{}
type testDriver struct {
mode configtesting.TestMode
}
func (t *testDriver) Init(mode configtesting.TestMode) {
t.mode = mode
}
func (t testDriver) TestCase() configtesting.TestCaseDriver {
return &testCaseDriver{
mode: t.mode,
}
}
var _ configtesting.TestCaseDriver = &testCaseDriver{}
type testCaseDriver struct {
mode configtesting.TestMode
config configurer
}
func (d *testCaseDriver) Configuration(fs []configtesting.ConfigFunc) configtesting.Configurer {
config := d.configuration()
for _, f := range fs {
f(config)
}
return config
}
func (d *testCaseDriver) configuration() *configurer {
if d.config == nil {
d.config = make(configurer, 0)
}
return &d.config
}
func (d *testCaseDriver) Setup(t *testing.T) {
ts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
})
t.Cleanup(func() {
ts.Close()
})
d.config.AddEndpoint("sts", ts.URL)
}
func (d testCaseDriver) Apply(ctx context.Context, t *testing.T) (context.Context, configtesting.Thing) {
t.Helper()
// Populate required fields
d.config.SetRegion("us-east-1")
d.config.setBucket("bucket")
d.config.setKey("key")
if d.mode == configtesting.TestModeLocal {
d.config.SetSkipCredsValidation(true)
d.config.SetSkipRequestingAccountId(true)
}
b, diags := configureBackend(t, map[string]any(d.config))
var expected tfdiags.Diagnostics
if diff := cmp.Diff(diags, expected, tfdiags.DiagnosticComparer); diff != "" {
t.Errorf("unexpected diagnostics difference: %s", diff)
}
return ctx, thing(b.awsConfig)
}
var _ configtesting.Configurer = &configurer{}
type configurer map[string]any
func (c configurer) AddEndpoint(k, v string) {
if endpoints, ok := c["endpoints"]; ok {
m := endpoints.(map[string]any)
m[k] = v
} else {
c["endpoints"] = map[string]any{
k: v,
}
}
}
func (c configurer) AddSharedConfigFile(f string) {
x := c["shared_config_files"]
if x == nil {
c["shared_config_files"] = []any{f}
} else {
files := x.([]any)
files = append(files, f)
c["shared_config_files"] = files
}
}
func (c configurer) setBucket(s string) {
c["bucket"] = s
}
func (c configurer) setKey(s string) {
c["key"] = s
}
func (c configurer) SetAccessKey(s string) {
c["access_key"] = s
}
func (c configurer) SetSecretKey(s string) {
c["secret_key"] = s
}
func (c configurer) SetProfile(s string) {
c["profile"] = s
}
func (c configurer) SetRegion(s string) {
c["region"] = s
}
func (c configurer) SetUseFIPSEndpoint(b bool) {
c["use_fips_endpoint"] = b
}
func (c configurer) SetSkipCredsValidation(b bool) {
c["skip_credentials_validation"] = b
}
func (c configurer) SetSkipRequestingAccountId(b bool) {
c["skip_requesting_account_id"] = b
}
var _ configtesting.Thing = thing{}
type thing aws.Config
func (t thing) GetCredentials() aws.CredentialsProvider {
return t.Credentials
}
func (t thing) GetRegion() string {
return t.Region
}
func TestBackendConfig_Authentication_SSO(t *testing.T) {
configtesting.SSO(t, &testDriver{})
}
func TestBackendConfig_Authentication_LegacySSO(t *testing.T) {
configtesting.LegacySSO(t, &testDriver{})
}
func TestBackendConfig_Region(t *testing.T) {
testCases := map[string]struct {
config map[string]any
EnvironmentVariables map[string]string
IMDSRegion string
SharedConfigurationFile string
ExpectedRegion string
}{
// NOT SUPPORTED: region is required
// "no configuration": {
// config: map[string]any{
// "access_key": servicemocks.MockStaticAccessKey,
// "secret_key": servicemocks.MockStaticSecretKey,
// },
// ExpectedRegion: "",
// },
"config": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-east-1",
},
ExpectedRegion: "us-east-1",
},
"AWS_REGION": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_REGION": "us-east-1",
},
ExpectedRegion: "us-east-1",
},
"AWS_DEFAULT_REGION": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_DEFAULT_REGION": "us-east-1",
},
ExpectedRegion: "us-east-1",
},
"AWS_REGION overrides AWS_DEFAULT_REGION": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_REGION": "us-east-1",
"AWS_DEFAULT_REGION": "us-west-2",
},
ExpectedRegion: "us-east-1",
},
// NOT SUPPORTED: region from shared configuration file
// "shared configuration file": {
// config: map[string]any{
// "access_key": servicemocks.MockStaticAccessKey,
// "secret_key": servicemocks.MockStaticSecretKey,
// },
// SharedConfigurationFile: `
// [default]
// region = us-east-1
// `,
// ExpectedRegion: "us-east-1",
// },
// NOT SUPPORTED: region from IMDS
// "IMDS": {
// config: map[string]any{},
// IMDSRegion: "us-east-1",
// ExpectedRegion: "us-east-1",
// },
"config overrides AWS_REGION": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-east-1",
},
EnvironmentVariables: map[string]string{
"AWS_REGION": "us-west-2",
},
ExpectedRegion: "us-east-1",
},
"config overrides AWS_DEFAULT_REGION": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-east-1",
},
EnvironmentVariables: map[string]string{
"AWS_DEFAULT_REGION": "us-west-2",
},
ExpectedRegion: "us-east-1",
},
"config overrides IMDS": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
"region": "us-west-2",
},
IMDSRegion: "us-east-1",
ExpectedRegion: "us-west-2",
},
"AWS_REGION overrides shared configuration": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_REGION": "us-east-1",
},
SharedConfigurationFile: `
[default]
region = us-west-2
`,
ExpectedRegion: "us-east-1",
},
"AWS_DEFAULT_REGION overrides shared configuration": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_DEFAULT_REGION": "us-east-1",
},
SharedConfigurationFile: `
[default]
region = us-west-2
`,
ExpectedRegion: "us-east-1",
},
"AWS_REGION overrides IMDS": {
config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
EnvironmentVariables: map[string]string{
"AWS_REGION": "us-east-1",
},
IMDSRegion: "us-west-2",
ExpectedRegion: "us-east-1",
},
}
for name, tc := range testCases {
tc := tc
t.Run(name, func(t *testing.T) {
servicemocks.InitSessionTestEnv(t)
// Populate required fields
tc.config["bucket"] = "bucket"
tc.config["key"] = "key"
for k, v := range tc.EnvironmentVariables {
t.Setenv(k, v)
}
if tc.IMDSRegion != "" {
closeEc2Metadata := servicemocks.AwsMetadataApiMock(append(
servicemocks.Ec2metadata_securityCredentialsEndpoints,
servicemocks.Ec2metadata_instanceIdEndpoint,
servicemocks.Ec2metadata_iamInfoEndpoint,
servicemocks.Ec2metadata_instanceIdentityEndpoint(tc.IMDSRegion),
))
defer closeEc2Metadata()
}
ts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
})
defer ts.Close()
tc.config["endpoints"] = map[string]any{
"sts": ts.URL,
}
if tc.SharedConfigurationFile != "" {
file, err := os.CreateTemp("", "aws-sdk-go-base-shared-configuration-file")
if err != nil {
t.Fatalf("unexpected error creating temporary shared configuration file: %s", err)
}
defer os.Remove(file.Name())
err = os.WriteFile(file.Name(), []byte(tc.SharedConfigurationFile), 0600)
if err != nil {
t.Fatalf("unexpected error writing shared configuration file: %s", err)
}
setSharedConfigFile(t, file.Name())
}
tc.config["skip_credentials_validation"] = true
b, diags := configureBackend(t, tc.config)
if diags.HasErrors() {
t.Fatalf("configuring backend: %s", diagnosticsString(diags))
}
if a, e := b.awsConfig.Region, tc.ExpectedRegion; a != e {
t.Errorf("expected Region %q, got: %q", e, a)
}
})
}
}
func setSharedConfigFile(t *testing.T, filename string) {
t.Setenv("AWS_SDK_LOAD_CONFIG", "1")
t.Setenv("AWS_CONFIG_FILE", filename)
}
func configureBackend(t *testing.T, config map[string]any) (*Backend, tfdiags.Diagnostics) {
b := New().(*Backend)
configSchema := populateSchema(t, b.ConfigSchema(), hcl2shim.HCL2ValueFromConfigValue(config))
configSchema, diags := b.PrepareConfig(configSchema)
if diags.HasErrors() {
return b, diags
}
confDiags := b.Configure(configSchema)
diags = diags.Append(confDiags)
return b, diags
}
func sharedConfigCredentialsSource(filename string) string {
return fmt.Sprintf(sharedConfigCredentialsProvider+": %s", filename)
}
func TestStsEndpoint(t *testing.T) {
type settype int
const (
setNone settype = iota
setValid
setInvalid
)
testcases := map[string]struct {
Config map[string]any
SetServiceEndpoint settype
SetServiceEndpointLegacy settype
SetEnv string
SetInvalidEnv string
// Use string at index 1 for valid endpoint url and index 2 for invalid endpoint url
ConfigFile string
ExpectedCredentials aws.Credentials
}{
// Service Config
"service config": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpoint: setValid,
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config overrides service envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpoint: setValid,
SetInvalidEnv: "AWS_ENDPOINT_URL_STS",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config overrides service envvar legacy": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpoint: setValid,
SetInvalidEnv: "AWS_STS_ENDPOINT",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config overrides base envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpoint: setValid,
SetInvalidEnv: "AWS_ENDPOINT_URL",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config overrides service config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
[services sts-test]
sts =
endpoint_url = %[2]s
`,
SetServiceEndpoint: setValid,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"service config overrides base config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
endpoint_url = %[2]s
`,
SetServiceEndpoint: setValid,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
// Service Config Legacy
"service config legacy": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpointLegacy: setValid,
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config legacy overrides service envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpointLegacy: setValid,
SetInvalidEnv: "AWS_ENDPOINT_URL_STS",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config legacy overrides service envvar legacy": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpointLegacy: setValid,
SetInvalidEnv: "AWS_STS_ENDPOINT",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config legacy overrides base envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetServiceEndpointLegacy: setValid,
SetInvalidEnv: "AWS_ENDPOINT_URL",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service config legacy overrides service config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
[services sts-test]
sts =
endpoint_url = %[2]s
`,
SetServiceEndpointLegacy: setValid,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"service config legacy overrides base config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
endpoint_url = %[2]s
`,
SetServiceEndpointLegacy: setValid,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
// Service Envvar
"service envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetEnv: "AWS_ENDPOINT_URL_STS",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service envvar overrides base envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetEnv: "AWS_ENDPOINT_URL_STS",
SetInvalidEnv: "AWS_ENDPOINT_URL",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service envvar overrides service config_file": {
Config: map[string]any{
"profile": "default",
},
SetEnv: "AWS_ENDPOINT_URL_STS",
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
[services sts-test]
sts =
endpoint_url = %[2]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"service envvar overrides base config_file": {
Config: map[string]any{
"profile": "default",
},
SetEnv: "AWS_ENDPOINT_URL_STS",
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
endpoint_url = %[2]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"service envvar overrides service envvar legacy": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetEnv: "AWS_ENDPOINT_URL_STS",
SetInvalidEnv: "AWS_STS_ENDPOINT",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
// Service Envvar Legacy
"service envvar legacy": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetEnv: "AWS_STS_ENDPOINT",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service envvar legacy overrides base envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetEnv: "AWS_STS_ENDPOINT",
SetInvalidEnv: "AWS_ENDPOINT_URL",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"service envvar legacy overrides service config_file": {
Config: map[string]any{
"profile": "default",
},
SetEnv: "AWS_STS_ENDPOINT",
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
[services sts-test]
sts =
endpoint_url = %[2]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"service envvar legacy overrides base config_file": {
Config: map[string]any{
"profile": "default",
},
SetEnv: "AWS_STS_ENDPOINT",
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
endpoint_url = %[2]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
// Service Config File
"service config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
[services sts-test]
sts =
endpoint_url = %[1]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"service config_file overrides base config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
endpoint_url = %[2]s
[services sts-test]
sts =
endpoint_url = %[1]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
// Base envvar
"base envvar": {
Config: map[string]any{
"access_key": servicemocks.MockStaticAccessKey,
"secret_key": servicemocks.MockStaticSecretKey,
},
SetEnv: "AWS_ENDPOINT_URL",
ExpectedCredentials: mockdata.MockStaticCredentials,
},
"base envvar overrides service config_file": {
Config: map[string]any{
"profile": "default",
},
SetEnv: "AWS_ENDPOINT_URL",
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
services = sts-test
[services sts-test]
sts =
endpoint_url = %[2]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"base config_file": {
Config: map[string]any{
"profile": "default",
},
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
endpoint_url = %[1]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
"base envvar overrides base config_file": {
Config: map[string]any{
"profile": "default",
},
SetEnv: "AWS_ENDPOINT_URL",
ConfigFile: `
[default]
aws_access_key_id = DefaultSharedCredentialsAccessKey
aws_secret_access_key = DefaultSharedCredentialsSecretKey
endpoint_url = %[2]s
`,
ExpectedCredentials: aws.Credentials{
AccessKeyID: "DefaultSharedCredentialsAccessKey",
SecretAccessKey: "DefaultSharedCredentialsSecretKey",
Source: sharedConfigCredentialsProvider,
},
},
}
for name, testcase := range testcases {
testcase := testcase
t.Run(name, func(t *testing.T) {
servicemocks.InitSessionTestEnv(t)
// Populate required fields
testcase.Config["bucket"] = "bucket"
testcase.Config["key"] = "key"
testcase.Config["region"] = "us-west-2"
ts := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityValidEndpoint,
})
defer ts.Close()
stsEndpoint := ts.URL
invalidTS := servicemocks.MockAwsApiServer("STS", []*servicemocks.MockEndpoint{
servicemocks.MockStsGetCallerIdentityInvalidEndpointAccessDenied,
})
defer invalidTS.Close()
stsInvalidEndpoint := invalidTS.URL
if testcase.SetServiceEndpoint == setValid {
testcase.Config["endpoints"] = map[string]any{
"sts": stsEndpoint,
}
}
if testcase.SetServiceEndpointLegacy == setValid {
testcase.Config["sts_endpoint"] = stsEndpoint
}
if testcase.SetEnv != "" {
t.Setenv(testcase.SetEnv, stsEndpoint)
}
if testcase.SetInvalidEnv != "" {
t.Setenv(testcase.SetInvalidEnv, stsInvalidEndpoint)
}
if testcase.ConfigFile != "" {
tempDir := t.TempDir()
filename := writeSharedConfigFile(t, testcase.Config, tempDir, fmt.Sprintf(testcase.ConfigFile, stsEndpoint, stsInvalidEndpoint))
testcase.ExpectedCredentials.Source = sharedConfigCredentialsSource(filename)
}
b, diags := configureBackend(t, testcase.Config)
if diags.HasErrors() {
t.Fatalf("configuring backend: %s", diagnosticsString(diags))
}
ctx := context.TODO()
credentialsValue, err := b.awsConfig.Credentials.Retrieve(ctx)
if err != nil {
t.Fatalf("unexpected credentials Retrieve() error: %s", err)
}
if diff := cmp.Diff(credentialsValue, testcase.ExpectedCredentials, cmpopts.IgnoreFields(aws.Credentials{}, "Expires")); diff != "" {
t.Fatalf("unexpected credentials: (- got, + expected)\n%s", diff)
}
})
}
}
func writeSharedConfigFile(t *testing.T, config map[string]any, tempDir, content string) string {
t.Helper()
file, err := os.Create(filepath.Join(tempDir, "aws-sdk-go-base-shared-configuration-file"))
if err != nil {
t.Fatalf("creating shared configuration file: %s", err)
}
_, err = file.WriteString(content)
if err != nil {
t.Fatalf(" writing shared configuration file: %s", err)
}
config["shared_config_files"] = []any{file.Name()}
return file.Name()
}