Skip to content

Commit 7bd1c9a

Browse files
authored
fix: support all standard AWS credential sources (#219)
Fixes: #218
1 parent c603b97 commit 7bd1c9a

File tree

5 files changed

+121
-54
lines changed

5 files changed

+121
-54
lines changed

env.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,10 +199,11 @@ func getEcrToken(provider *ecrContext) (username, password string, err error) {
199199
_, _ = fmt.Fprintf(os.Stderr, "AWS profile %q (Account: %s)\n", profile, provider.AccountID)
200200
}
201201
extraOpts = append(extraOpts, config.WithSharedConfigProfile(profile))
202-
} else {
203-
// If no profile is specified, use the ecrContext provider to load credentials
202+
} else if provider.HasAccountSuffixedCredentials() {
203+
// Only use custom provider if account-suffixed access-key credentials exist
204204
extraOpts = append(extraOpts, config.WithCredentialsProvider(aws.NewCredentialsCache(provider)))
205205
}
206+
// If neither profile nor account-suffixed credentials, use default AWS credential chain
206207

207208
ctx, cancel := context.WithTimeout(context.Background(), time.Second*30)
208209
defer cancel()

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ require (
2525
github.com/aws/smithy-go v1.22.5 // indirect
2626
github.com/pkg/errors v0.9.1 // indirect
2727
github.com/sirupsen/logrus v1.9.3 // indirect
28-
golang.org/x/sys v0.33.0 // indirect
28+
golang.org/x/sys v0.35.0 // indirect
2929
gopkg.in/yaml.v3 v3.0.1 // indirect
3030
gotest.tools/v3 v3.5.2 // indirect
3131
)

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,8 @@ github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+
4747
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
4848
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
4949
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
50-
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
51-
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
50+
golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI=
51+
golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
5252
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
5353
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
5454
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

provider_aws.go

Lines changed: 23 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,23 @@ type ecrContext struct {
2424
Region string
2525
}
2626

27-
// Retrieve fetches AWS credentials either from account-specific environment variables
28-
// or falls back to standard AWS environment variables if account-specific ones are not found.
27+
// HasAccountSuffixedCredentials checks if account-specific environment variables exist.
28+
// Returns true if `_ACCOUNT_ID`-suffixed AWS credential environment variables are found.
29+
func (p *ecrContext) HasAccountSuffixedCredentials() bool {
30+
if p.AccountID == "" {
31+
return false
32+
}
33+
34+
suffix := "_" + p.AccountID
35+
36+
// Check for any suffixed environment variables
37+
_, hasAccessKey := os.LookupEnv(envAwsAccessKeyID + suffix)
38+
_, hasSecretKey := os.LookupEnv(envAwsSecretAccessKey + suffix)
39+
40+
return hasAccessKey && hasSecretKey
41+
}
42+
43+
// Retrieve fetches AWS credentials from account-specific environment variables.
2944
// This method implements the aws.CredentialsProvider interface.
3045
func (p *ecrContext) Retrieve(_ context.Context) (out aws.Credentials, err error) {
3146
if p.AccountID == "" {
@@ -49,44 +64,20 @@ func (p *ecrContext) Retrieve(_ context.Context) (out aws.Credentials, err error
4964
secretAccessKey := os.Getenv(envAwsSecretAccessKey + suffix)
5065
sessionToken := os.Getenv(envAwsSessionToken + suffix)
5166

52-
// If ANY suffixed credentials exist, require ALL mandatory suffixed credentials
53-
if accessKeyID != "" || secretAccessKey != "" || sessionToken != "" {
54-
// If using suffixed credentials, both the access-key and secret key must be present
55-
if accessKeyID == "" {
56-
return aws.Credentials{}, fmt.Errorf("ecrContext: environment variable %s not found", envAwsAccessKeyID+suffix)
57-
}
58-
if secretAccessKey == "" {
59-
return aws.Credentials{}, fmt.Errorf("ecrContext: environment variable %s not found", envAwsSecretAccessKey+suffix)
60-
}
61-
62-
// Use only the suffixed credentials
63-
out = aws.Credentials{
64-
AccessKeyID: accessKeyID,
65-
SecretAccessKey: secretAccessKey,
66-
SessionToken: sessionToken, // Session token is optional, can be empty
67-
Source: fmt.Sprintf("Suffixed AWS Environment (Account: %s)", p.AccountID),
68-
}
69-
return out, nil
70-
}
71-
72-
// No suffixed credentials found, fall back to standard AWS credentials
73-
accessKeyID = os.Getenv(envAwsAccessKeyID)
74-
secretAccessKey = os.Getenv(envAwsSecretAccessKey)
75-
sessionToken = os.Getenv(envAwsSessionToken)
76-
77-
// Check if standard credentials are available
67+
// If using suffixed credentials, both the access-key and secret key must be present
7868
if accessKeyID == "" {
79-
return aws.Credentials{}, errors.New("ecrContext: no account credentials found and standard AWS_ACCESS_KEY_ID not found")
69+
return aws.Credentials{}, fmt.Errorf("ecrContext: environment variable %s not found", envAwsAccessKeyID+suffix)
8070
}
8171
if secretAccessKey == "" {
82-
return aws.Credentials{}, errors.New("ecrContext: no account credentials found and standard AWS_SECRET_ACCESS_KEY not found")
72+
return aws.Credentials{}, fmt.Errorf("ecrContext: environment variable %s not found", envAwsSecretAccessKey+suffix)
8373
}
8474

75+
// Use only the suffixed credentials
8576
out = aws.Credentials{
8677
AccessKeyID: accessKeyID,
8778
SecretAccessKey: secretAccessKey,
88-
SessionToken: sessionToken,
89-
Source: fmt.Sprintf("Standard AWS Environment (Account: %s)", p.AccountID),
79+
SessionToken: sessionToken, // Session token is optional, can be empty
80+
Source: fmt.Sprintf("Suffixed AWS Environment (Account: %s)", p.AccountID),
9081
}
9182
return out, nil
9283
}

provider_aws_test.go

Lines changed: 92 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package main
22

33
import (
44
"errors"
5+
"fmt"
56
"testing"
67
)
78

@@ -30,13 +31,13 @@ func TestECRContext_Retrieve(t *testing.T) {
3031
},
3132
},
3233
{
33-
name: "Missing access key with session token present",
34-
accountID: "123456789012",
35-
expectedErr: errors.New("ecrContext: environment variable AWS_ACCESS_KEY_ID_123456789012 not found"),
34+
name: "Missing access key with session token present",
35+
accountID: "123456789012",
3636
envVars: map[string]string{
3737
"AWS_SESSION_TOKEN_123456789012": "AQoEXAMPLEH4...",
3838
"AWS_SECRET_ACCESS_KEY_123456789012": "wJalr...",
3939
},
40+
expectedErr: errors.New("ecrContext: environment variable AWS_ACCESS_KEY_ID_123456789012 not found"),
4041
},
4142
{
4243
name: "Missing secret key with access key present",
@@ -47,25 +48,13 @@ func TestECRContext_Retrieve(t *testing.T) {
4748
},
4849
},
4950
{
50-
name: "Missing both keys - fallback to standard AWS credentials",
51-
accountID: "123456789012",
52-
expectedErr: errors.New("ecrContext: no account credentials found and standard AWS_ACCESS_KEY_ID not found"),
53-
},
54-
{
55-
name: "Valid credentials in FedRAMP",
56-
accountID: "123456789012",
57-
envVars: map[string]string{
58-
"AWS_ACCESS_KEY_ID_123456789012": "AKIA...",
59-
"AWS_SECRET_ACCESS_KEY_123456789012": "wJalr...",
60-
},
61-
},
62-
{
63-
name: "Standard AWS credentials when no suffixed vars exist",
51+
name: "No suffixed credentials",
6452
accountID: "123456789012",
6553
envVars: map[string]string{
6654
"AWS_ACCESS_KEY_ID": "STD-AKIA...",
6755
"AWS_SECRET_ACCESS_KEY": "STD-wJalr...",
6856
},
57+
expectedErr: fmt.Errorf("ecrContext: environment variable %s not found", envAwsAccessKeyID+"_123456789012"),
6958
},
7059
}
7160
for _, tc := range useCases {
@@ -122,3 +111,89 @@ func TestECRContext_Retrieve(t *testing.T) {
122111
})
123112
}
124113
}
114+
115+
func TestECRContext_HasAccountSuffixedCredentials(t *testing.T) {
116+
useCases := []struct {
117+
name string
118+
accountID string
119+
envVars map[string]string
120+
expected bool
121+
}{
122+
{
123+
name: "Has suffixed credentials for account",
124+
accountID: "123456789012",
125+
envVars: map[string]string{
126+
"AWS_ACCESS_KEY_ID_123456789012": "AKIA...",
127+
"AWS_SECRET_ACCESS_KEY_123456789012": "wJalr...",
128+
},
129+
expected: true,
130+
},
131+
{
132+
name: "No credentials",
133+
accountID: "123456789012",
134+
envVars: map[string]string{},
135+
expected: false,
136+
},
137+
{
138+
name: "Has suffixed access key only",
139+
accountID: "123456789012",
140+
envVars: map[string]string{
141+
"AWS_ACCESS_KEY_ID_123456789012": "AKIA...",
142+
},
143+
expected: false,
144+
},
145+
{
146+
name: "Has suffixed secret key only",
147+
accountID: "123456789012",
148+
envVars: map[string]string{
149+
"AWS_SECRET_ACCESS_KEY_123456789012": "wJalr...",
150+
},
151+
expected: false,
152+
},
153+
{
154+
name: "Has non-suffixed credentials for account",
155+
accountID: "123456789012",
156+
envVars: map[string]string{
157+
"AWS_ACCESS_KEY_ID": "AKIA...",
158+
"AWS_SECRET_ACCESS_KEY": "wJalr...",
159+
},
160+
expected: false,
161+
},
162+
{
163+
name: "Has suffixed credentials for no account",
164+
accountID: "",
165+
envVars: map[string]string{
166+
"AWS_ACCESS_KEY_ID_123456789012": "AKIA...",
167+
"AWS_SECRET_ACCESS_KEY_123456789012": "wJalr...",
168+
},
169+
expected: false,
170+
},
171+
{
172+
name: "Has suffixed credentials for different account",
173+
accountID: "987654321098",
174+
envVars: map[string]string{
175+
"AWS_ACCESS_KEY_ID_123456789012": "AKIA...",
176+
"AWS_SECRET_ACCESS_KEY_123456789012": "wJalr...",
177+
},
178+
expected: false,
179+
},
180+
}
181+
182+
for _, tc := range useCases {
183+
t.Run(tc.name, func(t *testing.T) {
184+
// Set environment variables
185+
for k, v := range tc.envVars {
186+
t.Setenv(k, v)
187+
}
188+
189+
provider := &ecrContext{
190+
AccountID: tc.accountID,
191+
}
192+
193+
result := provider.HasAccountSuffixedCredentials()
194+
if result != tc.expected {
195+
t.Errorf("expected %v but got %v", tc.expected, result)
196+
}
197+
})
198+
}
199+
}

0 commit comments

Comments
 (0)