Skip to content

Commit 1282c53

Browse files
authored
Read AWS_CONTAINER_CREDENTIALS_FULL_URI env variable if set when reading a profile with credential_source. (#2790)
* Read `AWS_CONTAINER_CREDENTIALS_FULL_URI` env variable if set when reading a profile with `credential_source`. Also ensure `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` is always read before it
1 parent 171151b commit 1282c53

File tree

3 files changed

+165
-8
lines changed

3 files changed

+165
-8
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"id": "3230f94a-d781-4d24-b10b-eaed0739d43c",
3+
"type": "bugfix",
4+
"description": "Read `AWS_CONTAINER_CREDENTIALS_FULL_URI` env variable if set when reading a profile with `credential_source`. Also ensure `AWS_CONTAINER_CREDENTIALS_RELATIVE_URI` is always read before it",
5+
"modules": [
6+
"config"
7+
]
8+
}

config/resolve_credentials.go

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -162,12 +162,12 @@ func resolveCredsFromProfile(ctx context.Context, cfg *aws.Config, envConfig *En
162162
// Get credentials from CredentialProcess
163163
err = processCredentials(ctx, cfg, sharedConfig, configs)
164164

165-
case len(envConfig.ContainerCredentialsEndpoint) != 0:
166-
err = resolveLocalHTTPCredProvider(ctx, cfg, envConfig.ContainerCredentialsEndpoint, envConfig.ContainerAuthorizationToken, configs)
167-
168165
case len(envConfig.ContainerCredentialsRelativePath) != 0:
169166
err = resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
170167

168+
case len(envConfig.ContainerCredentialsEndpoint) != 0:
169+
err = resolveLocalHTTPCredProvider(ctx, cfg, envConfig.ContainerCredentialsEndpoint, envConfig.ContainerAuthorizationToken, configs)
170+
171171
default:
172172
err = resolveEC2RoleCredentials(ctx, cfg, configs)
173173
}
@@ -355,10 +355,13 @@ func resolveCredsFromSource(ctx context.Context, cfg *aws.Config, envConfig *Env
355355
cfg.Credentials = credentials.StaticCredentialsProvider{Value: envConfig.Credentials}
356356

357357
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")
358+
if len(envConfig.ContainerCredentialsRelativePath) != 0 {
359+
return resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
360+
}
361+
if len(envConfig.ContainerCredentialsEndpoint) != 0 {
362+
return resolveLocalHTTPCredProvider(ctx, cfg, envConfig.ContainerCredentialsEndpoint, envConfig.ContainerAuthorizationToken, configs)
360363
}
361-
return resolveHTTPCredProvider(ctx, cfg, ecsContainerURI(envConfig.ContainerCredentialsRelativePath), envConfig.ContainerAuthorizationToken, configs)
364+
return fmt.Errorf("EcsContainer was specified as the credential_source, but neither 'AWS_CONTAINER_CREDENTIALS_RELATIVE_URI' or AWS_CONTAINER_CREDENTIALS_FULL_URI' was set")
362365

363366
default:
364367
return fmt.Errorf("credential_source values must be EcsContainer, Ec2InstanceMetadata, or Environment")

config/resolve_credentials_test.go

Lines changed: 148 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,51 @@ func swapECSContainerURI(path string) func() {
3333
}
3434
}
3535

36-
func setupCredentialsEndpoints(t *testing.T) (aws.EndpointResolverWithOptions, func()) {
36+
const ecsFullPathResponse = `{
37+
"Code": "Success",
38+
"Type": "AWS-HMAC",
39+
"AccessKeyId": "ecs-full-path-access-key",
40+
"SecretAccessKey": "ecs-full-path-ecs-secret-key",
41+
"Token": "ecs-full-path-token",
42+
"Expiration": "2100-01-01T00:00:00Z",
43+
"LastUpdated": "2009-11-23T00:00:00Z"
44+
}`
45+
46+
const assumeRoleRespEcsFullPathMsg = `
47+
<AssumeRoleResponse xmlns="https://sts.amazonaws.com/doc/2011-06-15/">
48+
<AssumeRoleResult>
49+
<AssumedRoleUser>
50+
<Arn>arn:aws:sts::account_id:assumed-role/role/session_name</Arn>
51+
<AssumedRoleId>AKID:session_name</AssumedRoleId>
52+
</AssumedRoleUser>
53+
<Credentials>
54+
<AccessKeyId>AKID-Full-Path</AccessKeyId>
55+
<SecretAccessKey>SECRET-Full-Path</SecretAccessKey>
56+
<SessionToken>SESSION_TOKEN-Full-Path</SessionToken>
57+
<Expiration>%s</Expiration>
58+
</Credentials>
59+
</AssumeRoleResult>
60+
<ResponseMetadata>
61+
<RequestId>request-id</RequestId>
62+
</ResponseMetadata>
63+
</AssumeRoleResponse>
64+
`
65+
66+
var ecsMetadataServerURL string
67+
68+
func setupCredentialsEndpoints() (aws.EndpointResolverWithOptions, func()) {
3769
ecsMetadataServer := httptest.NewServer(http.HandlerFunc(
3870
func(w http.ResponseWriter, r *http.Request) {
3971
if r.URL.Path == "/ECS" {
4072
w.Write([]byte(ecsResponse))
73+
// Used when we specify a full path instead of relative path
74+
} else if r.URL.Path == "/ECSFullPath" {
75+
w.Write([]byte(ecsFullPathResponse))
4176
} else {
4277
w.Write([]byte(""))
4378
}
4479
}))
80+
ecsMetadataServerURL = ecsMetadataServer.URL
4581
resetECSEndpoint := swapECSContainerURI(ecsMetadataServer.URL)
4682

4783
ec2MetadataServer := httptest.NewServer(http.HandlerFunc(
@@ -74,6 +110,15 @@ func setupCredentialsEndpoints(t *testing.T) (aws.EndpointResolverWithOptions, f
74110

75111
switch form.Get("Action") {
76112
case "AssumeRole":
113+
if val, ok := r.Header["X-Amz-Security-Token"]; ok {
114+
if val[0] == "ecs-full-path-token" {
115+
w.Write([]byte(fmt.Sprintf(
116+
assumeRoleRespEcsFullPathMsg,
117+
smithytime.FormatDateTime(time.Now().
118+
Add(15*time.Minute)))))
119+
return
120+
}
121+
}
77122
w.Write([]byte(fmt.Sprintf(
78123
assumeRoleRespMsg,
79124
smithytime.FormatDateTime(time.Now().
@@ -394,7 +439,7 @@ func TestSharedConfigCredentialSource(t *testing.T) {
394439
os.Setenv("AWS_PROFILE", c.envProfile)
395440
}
396441

397-
endpointResolver, cleanupFn := setupCredentialsEndpoints(t)
442+
endpointResolver, cleanupFn := setupCredentialsEndpoints()
398443
defer cleanupFn()
399444

400445
var cleanup func()
@@ -604,6 +649,107 @@ func TestResolveCredentialsIMDSClient(t *testing.T) {
604649
}
605650
}
606651

652+
func TestResolveCredentialsEcsContainer(t *testing.T) {
653+
testCases := map[string]struct {
654+
expectedAccessKey string
655+
expectedSecretKey string
656+
envVar map[string]string
657+
configFile string
658+
}{
659+
"only relative ECS URI set": {
660+
expectedAccessKey: "ecs-access-key",
661+
expectedSecretKey: "ecs-secret-key",
662+
envVar: map[string]string{
663+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/ECS",
664+
},
665+
},
666+
"only full ECS URI set": {
667+
expectedAccessKey: "ecs-full-path-access-key",
668+
expectedSecretKey: "ecs-full-path-ecs-secret-key",
669+
envVar: map[string]string{
670+
"AWS_CONTAINER_CREDENTIALS_FULL_URI": "placeholder-replaced-at-runtime",
671+
},
672+
},
673+
"relative ECS URI has precedence over full": {
674+
expectedAccessKey: "ecs-access-key",
675+
expectedSecretKey: "ecs-secret-key",
676+
envVar: map[string]string{
677+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/ECS",
678+
"AWS_CONTAINER_CREDENTIALS_FULL_URI": "placeholder-replaced-at-runtime",
679+
},
680+
},
681+
"credential source only relative ECS URI set": {
682+
expectedAccessKey: "AKID",
683+
expectedSecretKey: "SECRET",
684+
envVar: map[string]string{
685+
"AWS_PROFILE": "ecscontainer",
686+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/ECS",
687+
},
688+
configFile: filepath.Join("testdata", "config_source_shared"),
689+
},
690+
"credential source only full ECS URI set": {
691+
expectedAccessKey: "AKID-Full-Path",
692+
expectedSecretKey: "SECRET-Full-Path",
693+
envVar: map[string]string{
694+
"AWS_CONTAINER_CREDENTIALS_FULL_URI": "placeholder-replaced-at-runtime",
695+
"AWS_PROFILE": "ecscontainer",
696+
},
697+
configFile: filepath.Join("testdata", "config_source_shared"),
698+
},
699+
"credential source relative ECS URI has precedence over full": {
700+
expectedAccessKey: "AKID",
701+
expectedSecretKey: "SECRET",
702+
envVar: map[string]string{
703+
"AWS_CONTAINER_CREDENTIALS_RELATIVE_URI": "/ECS",
704+
"AWS_CONTAINER_CREDENTIALS_FULL_URI": "placeholder-replaced-at-runtime",
705+
"AWS_PROFILE": "ecscontainer",
706+
},
707+
configFile: filepath.Join("testdata", "config_source_shared"),
708+
},
709+
}
710+
711+
for name, tc := range testCases {
712+
t.Run(name, func(t *testing.T) {
713+
endpointResolver, cleanupFn := setupCredentialsEndpoints()
714+
defer cleanupFn()
715+
restoreEnv := awstesting.StashEnv()
716+
defer awstesting.PopEnv(restoreEnv)
717+
var sharedConfigFiles []string
718+
if tc.configFile != "" {
719+
sharedConfigFiles = append(sharedConfigFiles, tc.configFile)
720+
}
721+
opts := []func(*LoadOptions) error{
722+
WithEndpointResolverWithOptions(endpointResolver),
723+
WithRetryer(func() aws.Retryer { return aws.NopRetryer{} }),
724+
WithSharedConfigFiles(sharedConfigFiles),
725+
WithSharedCredentialsFiles([]string{}),
726+
}
727+
for k, v := range tc.envVar {
728+
// since we don't know the value of this until the server starts
729+
if k == "AWS_CONTAINER_CREDENTIALS_FULL_URI" {
730+
v = ecsMetadataServerURL + "/ECSFullPath"
731+
}
732+
os.Setenv(k, v)
733+
}
734+
cfg, err := LoadDefaultConfig(context.TODO(), opts...)
735+
if err != nil {
736+
t.Fatalf("could not load config: %s", err)
737+
}
738+
actual, err := cfg.Credentials.Retrieve(context.TODO())
739+
if err != nil {
740+
t.Fatalf("could not retrieve credentials: %s", err)
741+
}
742+
if actual.AccessKeyID != tc.expectedAccessKey {
743+
t.Errorf("expected access key to be %s, got %s", tc.expectedAccessKey, actual.AccessKeyID)
744+
}
745+
if actual.SecretAccessKey != tc.expectedSecretKey {
746+
t.Errorf("expected secret key to be %s, got %s", tc.expectedSecretKey, actual.SecretAccessKey)
747+
}
748+
})
749+
}
750+
751+
}
752+
607753
type stubErrorClient struct {
608754
err error
609755
}

0 commit comments

Comments
 (0)