carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1package rootaws
2
3import (
4 "bytes"
5 "context"
6 "log"
7 "os"
8 "path/filepath"
9 "strings"
10 "testing"
11
12 awsconfig "github.com/aws/aws-sdk-go-v2/config"
13 "github.com/aws/aws-sdk-go-v2/credentials"
14)
15
16func TestParseStaticCredentials(t *testing.T) {
17 valid := "# comment\n[default]\naws_access_key_id = ACCESS\naws_secret_access_key = SECRET/+==\naws_session_token = TOKEN/+==\n"
18 got, err := ParseStaticCredentials([]byte(valid))
19 if err != nil || got.AccessKeyID != "ACCESS" || got.SecretAccessKey != "SECRET/+==" || got.SessionToken != "TOKEN/+==" {
20 t.Fatalf("static parse failed: %v", err)
21 }
22 for name, body := range map[string]string{
23 "process": valid + "credential_process = touch /never-run\n",
24 "unknown": valid + "region = eu-west-1\n",
25 "other-profile": valid + "[other]\ncredential_process = bad\n",
26 "duplicate-profile": valid + "[default]\n",
27 "duplicate-key": valid + "aws_access_key_id = SECOND\n",
28 "missing-secret": "[default]\naws_access_key_id=ACCESS\n",
29 "no-profile": "aws_access_key_id=ACCESS\naws_secret_access_key=SECRET\n",
30 "empty": "",
31 "nul": valid + "\x00",
32 "oversize": strings.Repeat("#", MaxCredentialsSize+1),
33 } {
34 t.Run(name, func(t *testing.T) {
35 if _, err := ParseStaticCredentials([]byte(body)); err == nil {
36 t.Fatal("accepted invalid static credentials")
37 }
38 })
39 }
40}
41
42func TestParseStaticCredentialsSDKSemantics(t *testing.T) {
43 parse := func(access, secret string) (string, string, error) {
44 got, err := ParseStaticCredentials([]byte("[default]\naws_access_key_id = " + access + "\naws_secret_access_key = " + secret + "\n"))
45 return got.AccessKeyID, got.SecretAccessKey, err
46 }
47 for name, tc := range map[string]struct{ access, secret, wantAccess, wantSecret string }{
48 "embedded-hash": {"ACC#ESS", "SEC#RET", "ACC#ESS", "SEC#RET"},
49 "embedded-semicolon": {"ACC;ESS", "SEC;RET", "ACC;ESS", "SEC;RET"},
50 "space-hash-comment": {"ACCESS # note", "SECRET ; note", "ACCESS", "SECRET"},
51 "tab-comment": {"ACCESS\t# note", "SECRET\t; note", "ACCESS", "SECRET"},
52 "double-quotes": {`"ACCESS"`, `"SEC#RET"`, "ACCESS", "SEC#RET"},
53 "single-quotes": {`'ACCESS'`, `'SEC;RET'`, "ACCESS", "SEC;RET"},
54 "quotes-and-comments": {`"ACCESS" # note`, `'SECRET' ; note`, "ACCESS", "SECRET"},
55 "one-pair-only": {`""ACCESS""`, `'"SECRET"'`, `"ACCESS"`, `"SECRET"`},
56 "mismatched-quotes": {`"ACCESS'`, `'SECRET`, `"ACCESS'`, `'SECRET`},
57 "lone-quote": {`"`, `'`, `"`, `'`},
58 } {
59 t.Run(name, func(t *testing.T) {
60 access, secret, err := parse(tc.access, tc.secret)
61 if err != nil || access != tc.wantAccess || secret != tc.wantSecret {
62 t.Fatalf("parsed %q %q, %v", access, secret, err)
63 }
64 })
65 }
66 for name, body := range map[string]string{
67 "empty-double-quotes": "[default]\naws_access_key_id = \"\"\naws_secret_access_key = SECRET\n",
68 "empty-single-quotes": "[default]\naws_access_key_id = ACCESS\naws_secret_access_key = ''\n",
69 "comment-only-value": "[default]\naws_access_key_id = # note\naws_secret_access_key = SECRET\n",
70 "quoted-spaces": "[default]\naws_access_key_id = \"AC CESS\"\naws_secret_access_key = SECRET\n",
71 "process-with-quotes": "[default]\naws_access_key_id = ACCESS\naws_secret_access_key = SECRET\n\"credential_process\" = bad\n",
72 } {
73 t.Run(name, func(t *testing.T) {
74 if _, err := ParseStaticCredentials([]byte(body)); err == nil {
75 t.Fatal("accepted invalid static credentials")
76 }
77 })
78 }
79}
80
81func TestRootConfigEnvironmentKeysIgnoreMalformedFile(t *testing.T) {
82 for _, token := range []string{"", "ENV_TOKEN"} {
83 t.Run("token="+token, func(t *testing.T) {
84 isolatedAWSEnv(t)
85 t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS")
86 t.Setenv("AWS_SECRET_ACCESS_KEY", "ENV_SECRET")
87 t.Setenv("AWS_SESSION_TOKEN", token)
88 path := filepath.Join(t.TempDir(), "credentials")
89 if err := os.WriteFile(path, []byte("[default]\ncredential_process = touch /never-run\n[other]\n"), 0600); err != nil {
90 t.Fatal(err)
91 }
92 cfg, err := loadRootConfig(context.Background(), path)
93 if err != nil {
94 t.Fatalf("malformed unused file made config fatal: %v", err)
95 }
96 got, err := cfg.Credentials.Retrieve(context.Background())
97 if err != nil || got.AccessKeyID != "ENV_ACCESS" || got.SecretAccessKey != "ENV_SECRET" || got.SessionToken != token {
98 t.Fatalf("environment credentials failed: %v", err)
99 }
100 })
101 }
102}
103
104func TestRootConfigIncompleteEnvironmentKeysStillValidateFile(t *testing.T) {
105 isolatedAWSEnv(t)
106 t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS")
107 path := filepath.Join(t.TempDir(), "credentials")
108 if err := os.WriteFile(path, []byte("[default]\ncredential_process = bad\n"), 0600); err != nil {
109 t.Fatal(err)
110 }
111 cfg, err := loadRootConfig(context.Background(), path)
112 if err != nil {
113 t.Fatalf("unused unsafe credentials made config loading fatal: %v", err)
114 }
115 if _, err := cfg.Credentials.Retrieve(context.Background()); err == nil {
116 t.Fatal("malformed file accepted without a complete environment key pair")
117 }
118}
119
120func TestRootConfigEnvironmentKeysBeatCallerCredentials(t *testing.T) {
121 isolatedAWSEnv(t)
122 t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS")
123 t.Setenv("AWS_SECRET_ACCESS_KEY", "ENV_SECRET")
124 caller := awsconfig.WithCredentialsProvider(credentials.NewStaticCredentialsProvider("CALLER", "CALLER_SECRET", ""))
125 cfg, err := loadRootConfig(context.Background(), filepath.Join(t.TempDir(), "absent"), caller)
126 if err != nil {
127 t.Fatal(err)
128 }
129 got, err := cfg.Credentials.Retrieve(context.Background())
130 if err != nil || got.AccessKeyID != "ENV_ACCESS" || got.SecretAccessKey != "ENV_SECRET" {
131 t.Fatalf("caller credentials overrode the environment: %v", err)
132 }
133}
134
135func isolatedAWSEnv(t *testing.T) {
136 t.Helper()
137 for _, key := range []string{"AWS_PROFILE", "AWS_DEFAULT_PROFILE", "AWS_ACCESS_KEY_ID", "AWS_ACCESS_KEY", "AWS_SECRET_ACCESS_KEY", "AWS_SECRET_KEY", "AWS_SESSION_TOKEN", "AWS_WEB_IDENTITY_TOKEN_FILE", "AWS_ROLE_ARN", "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", "AWS_CONTAINER_CREDENTIALS_FULL_URI"} {
138 t.Setenv(key, "")
139 }
140 t.Setenv("AWS_EC2_METADATA_DISABLED", "true")
141 t.Setenv("AWS_REGION", "eu-west-1")
142}
143
144func TestRootConfigNeverLoadsSharedFiles(t *testing.T) {
145 for _, selector := range []string{"HOME", "AWS_CONFIG_FILE", "AWS_SHARED_CREDENTIALS_FILE", "options", "profile"} {
146 t.Run(selector, func(t *testing.T) {
147 isolatedAWSEnv(t)
148 dir := t.TempDir()
149 t.Setenv("HOME", dir)
150 t.Setenv("AWS_CONFIG_FILE", filepath.Join(dir, "missing-config"))
151 t.Setenv("AWS_SHARED_CREDENTIALS_FILE", filepath.Join(dir, "missing-keys"))
152 marker := filepath.Join(dir, "executed")
153 // If the SDK ever selects this profile, retrieval executes touch.
154 body := "[default]\ncredential_process = touch " + marker + "\n"
155 bad := filepath.Join(dir, "untrusted")
156 if err := os.WriteFile(bad, []byte(body), 0600); err != nil {
157 t.Fatal(err)
158 }
159 var opts []func(*awsconfig.LoadOptions) error
160 switch selector {
161 case "HOME":
162 if err := os.Mkdir(filepath.Join(dir, ".aws"), 0700); err != nil {
163 t.Fatal(err)
164 }
165 for _, name := range []string{"config", "credentials"} {
166 if err := os.WriteFile(filepath.Join(dir, ".aws", name), []byte(body), 0600); err != nil {
167 t.Fatal(err)
168 }
169 }
170 t.Setenv("AWS_CONFIG_FILE", "")
171 t.Setenv("AWS_SHARED_CREDENTIALS_FILE", "")
172 case "options":
173 opts = append(opts, awsconfig.WithSharedConfigFiles([]string{bad}), awsconfig.WithSharedCredentialsFiles([]string{bad}))
174 case "profile":
175 t.Setenv("AWS_PROFILE", "default")
176 t.Setenv("AWS_CONFIG_FILE", bad)
177 default:
178 t.Setenv(selector, bad)
179 }
180 cfg, err := loadRootConfig(context.Background(), filepath.Join(dir, "absent"), opts...)
181 if err == nil {
182 if _, err := cfg.Credentials.Retrieve(context.Background()); err == nil {
183 t.Fatal("unexpected credentials without an allowed source")
184 }
185 } else if selector != "profile" {
186 t.Fatal(err)
187 }
188 if _, err := os.Stat(marker); !os.IsNotExist(err) {
189 t.Fatal("credential_process executed")
190 }
191 })
192 }
193}
194
195func TestRootConfigEnvironmentCredentials(t *testing.T) {
196 isolatedAWSEnv(t)
197 t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS")
198 t.Setenv("AWS_SECRET_ACCESS_KEY", "ENV_SECRET")
199 t.Setenv("AWS_SESSION_TOKEN", "ENV_TOKEN")
200 cfg, err := loadRootConfig(context.Background(), filepath.Join(t.TempDir(), "absent"))
201 if err != nil {
202 t.Fatal(err)
203 }
204 got, err := cfg.Credentials.Retrieve(context.Background())
205 if err != nil || got.AccessKeyID != "ENV_ACCESS" || got.SecretAccessKey != "ENV_SECRET" || got.SessionToken != "ENV_TOKEN" {
206 t.Fatalf("environment credentials failed: %v", err)
207 }
208}
209
210func TestRootConfigRefusesProfilesExplicitly(t *testing.T) {
211 for _, name := range []string{"AWS_PROFILE", "AWS_DEFAULT_PROFILE"} {
212 t.Run(name, func(t *testing.T) {
213 isolatedAWSEnv(t)
214 t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS")
215 t.Setenv("AWS_SECRET_ACCESS_KEY", "ENV_SECRET")
216 t.Setenv(name, "default")
217 var logs bytes.Buffer
218 old := log.Writer()
219 log.SetOutput(&logs)
220 defer log.SetOutput(old)
221 _, err := loadRootConfig(context.Background(), filepath.Join(t.TempDir(), "absent"))
222 if err == nil || !strings.Contains(err.Error(), "do not support") || !strings.Contains(logs.String(), "ALERT:") {
223 t.Fatalf("profile not explicitly refused with an alert: %v", err)
224 }
225 })
226 }
227}
228
229func TestNonRootConfigKeepsSharedCredentials(t *testing.T) {
230 if os.Geteuid() == 0 {
231 t.Skip("requires non-root to exercise unchanged SDK behavior")
232 }
233 isolatedAWSEnv(t)
234 dir := t.TempDir()
235 path := filepath.Join(dir, "credentials")
236 if err := os.WriteFile(path, []byte("[default]\naws_access_key_id=SHARED\naws_secret_access_key=SECRET\n"), 0600); err != nil {
237 t.Fatal(err)
238 }
239 t.Setenv("AWS_SHARED_CREDENTIALS_FILE", path)
240 t.Setenv("AWS_CONFIG_FILE", filepath.Join(dir, "absent"))
241 cfg, err := LoadDefaultConfig(context.Background())
242 if err != nil {
243 t.Fatal(err)
244 }
245 got, err := cfg.Credentials.Retrieve(context.Background())
246 if err != nil || got.AccessKeyID != "SHARED" {
247 t.Fatalf("non-root shared credentials changed: %v", err)
248 }
249}
250
251func TestRootConfigUnsafeFileRefusesOnUseWithoutFallback(t *testing.T) {
252 isolatedAWSEnv(t)
253 path := filepath.Join(t.TempDir(), "credentials")
254 if err := os.WriteFile(path, []byte("[default]\ncredential_process=bad\n"), 0600); err != nil {
255 t.Fatal(err)
256 }
257 fallback := credentials.NewStaticCredentialsProvider("OTHER_IDENTITY", "SECRET", "")
258 cfg, err := loadRootConfig(context.Background(), path, awsconfig.WithCredentialsProvider(fallback))
259 if err != nil {
260 t.Fatalf("opening an unused store would abort adoption: %v", err)
261 }
262 if got, err := cfg.Credentials.Retrieve(context.Background()); err == nil || got.AccessKeyID != "" {
263 t.Fatal("unsafe file fell through to another identity")
264 }
265}
266
267func TestRootCredentialsOffcloudSelection(t *testing.T) {
268 isolatedAWSEnv(t)
269 path := filepath.Join(t.TempDir(), "credentials")
270 got, err := offcloudCredentials(path)
271 if err != nil || got.HasKeys() {
272 t.Fatalf("absent file: credentials=%v err=%v", got.HasKeys(), err)
273 }
274 if err := os.WriteFile(path, []byte("[default]\ncredential_process=bad\n"), 0600); err != nil {
275 t.Fatal(err)
276 }
277 if _, err := offcloudCredentials(path); err == nil {
278 t.Fatal("unsafe file was accepted")
279 }
280 t.Run("incomplete environment keys", func(t *testing.T) {
281 t.Setenv("AWS_ACCESS_KEY_ID", "ENV_ACCESS")
282 if _, err := offcloudCredentials(path); err == nil {
283 t.Fatal("incomplete environment keys bypassed the unsafe file")
284 }
285 })
286 for _, names := range [][2]string{{"AWS_ACCESS_KEY_ID", "AWS_SECRET_ACCESS_KEY"}, {"AWS_ACCESS_KEY", "AWS_SECRET_KEY"}} {
287 t.Run(names[0], func(t *testing.T) {
288 t.Setenv(names[0], "ENV_ACCESS")
289 t.Setenv(names[1], "ENV_SECRET")
290 t.Setenv("AWS_SESSION_TOKEN", "ENV_TOKEN")
291 got, err := offcloudCredentials(path)
292 if err != nil || got.HasKeys() || got.SessionToken != "" {
293 t.Fatalf("environment keys must bypass file and leave static fields unset: %v", err)
294 }
295 })
296 }
297}
298