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)
14
15func TestScrubEnvironment(t *testing.T) {
16 for _, tc := range []struct {
17 name string
18 euid int
19 }{
20 {"root", 0},
21 {"non-root", 1001},
22 } {
23 t.Run(tc.name, func(t *testing.T) {
24 for _, key := range rootScrubbedEnvironment {
25 t.Setenv(key, "untrusted")
26 }
27 t.Setenv("AWS_ENDPOINT_URL_S3", "untrusted")
28 t.Setenv("AWS_ENDPOINT_URL_STS", "untrusted")
29 t.Setenv("AWS_ACCESS_KEY_ID", "KEEP")
30 t.Setenv("AWS_SECRET_ACCESS_KEY", "KEEP_SECRET")
31 t.Setenv("AWS_SESSION_TOKEN", "KEEP_TOKEN")
32 var logs bytes.Buffer
33 old := log.Writer()
34 log.SetOutput(&logs)
35 defer log.SetOutput(old)
36 scrubEnvironment(tc.euid)
37 for _, key := range rootScrubbedEnvironment {
38 got, set := os.LookupEnv(key)
39 want := "untrusted"
40 if tc.euid == 0 {
41 want = ""
42 if key == "AWS_CONFIG_FILE" || key == "AWS_SHARED_CREDENTIALS_FILE" {
43 want = disabledSharedFile
44 }
45 }
46 if got != want || set != (want != "") {
47 t.Errorf("%s = %q, set=%v; want %q", key, got, set, want)
48 }
49 }
50 for _, key := range []string{"AWS_ENDPOINT_URL_S3", "AWS_ENDPOINT_URL_STS"} {
51 if _, set := os.LookupEnv(key); set != (tc.euid != 0) {
52 t.Errorf("%s set=%v for euid %d", key, set, tc.euid)
53 }
54 }
55 wantLogs := 0
56 if tc.euid == 0 {
57 wantLogs = 1
58 }
59 if strings.Count(logs.String(), "ALERT:") != wantLogs {
60 t.Fatalf("alert count: %q", logs.String())
61 }
62 if os.Getenv("AWS_ACCESS_KEY_ID") != "KEEP" || os.Getenv("AWS_SECRET_ACCESS_KEY") != "KEEP_SECRET" || os.Getenv("AWS_SESSION_TOKEN") != "KEEP_TOKEN" {
63 t.Fatal("environment credentials changed")
64 }
65 })
66 }
67}
68
69func TestScrubEnvironmentDisablesSharedFilesWithNothingSet(t *testing.T) {
70 for _, key := range rootScrubbedEnvironment {
71 t.Setenv(key, "")
72 os.Unsetenv(key)
73 }
74 var logs bytes.Buffer
75 old := log.Writer()
76 log.SetOutput(&logs)
77 defer log.SetOutput(old)
78 scrubEnvironment(0)
79 if os.Getenv("AWS_CONFIG_FILE") != disabledSharedFile || os.Getenv("AWS_SHARED_CREDENTIALS_FILE") != disabledSharedFile {
80 t.Fatal("shared files not disabled for root")
81 }
82 if logs.Len() != 0 {
83 t.Fatalf("alert without overrides: %q", logs.String())
84 }
85}
86
87func TestScrubEnvironmentBlocksDependencySharedConfig(t *testing.T) {
88 isolatedAWSEnv(t)
89 dir := t.TempDir()
90 marker := filepath.Join(dir, "executed")
91 // The planted config sits under HOME, where the SDK looks by default, and
92 // is also named explicitly.
93 if err := os.Mkdir(filepath.Join(dir, ".aws"), 0o700); err != nil {
94 t.Fatal(err)
95 }
96 path := filepath.Join(dir, ".aws", "config")
97 if err := os.WriteFile(path, []byte("[default]\ncredential_process = touch "+marker+"\n"), 0600); err != nil {
98 t.Fatal(err)
99 }
100 if err := os.WriteFile(filepath.Join(dir, ".aws", "credentials"), []byte("[default]\ncredential_process = touch "+marker+"\n"), 0600); err != nil {
101 t.Fatal(err)
102 }
103 t.Setenv("HOME", dir)
104 t.Setenv("AWS_CONFIG_FILE", path)
105 t.Setenv("AWS_SHARED_CREDENTIALS_FILE", path)
106 t.Setenv("AWS_PROFILE", "default")
107 t.Setenv("AWS_DEFAULT_PROFILE", "default")
108 t.Setenv("AWS_SDK_LOAD_CONFIG", "1")
109 scrubEnvironment(0)
110 // This deliberately bypasses rootaws, as a third-party SDK call would.
111 cfg, err := awsconfig.LoadDefaultConfig(context.Background())
112 if err != nil {
113 t.Fatal(err)
114 }
115 if _, err := cfg.Credentials.Retrieve(context.Background()); err == nil {
116 t.Fatal("unexpected credentials")
117 }
118 if _, err := os.Stat(marker); !os.IsNotExist(err) {
119 t.Fatal("credential_process executed")
120 }
121 if _, err := os.Stat(disabledSharedFile); err == nil {
122 t.Fatal("disabled path exists")
123 }
124}
125
126func TestScrubEnvironmentKeepsProxyButBypassesIMDS(t *testing.T) {
127 for _, tc := range []struct{ in, want string }{
128 {"", "169.254.169.254,fd00:ec2::254"},
129 {"example.com", "example.com,169.254.169.254,fd00:ec2::254"},
130 {"169.254.169.254,example.com", "169.254.169.254,example.com,fd00:ec2::254"},
131 } {
132 t.Setenv("HTTP_PROXY", "http://proxy.example.com:3128")
133 t.Setenv("NO_PROXY", tc.in)
134 t.Setenv("no_proxy", tc.in)
135 scrubEnvironment(0)
136 if os.Getenv("HTTP_PROXY") != "http://proxy.example.com:3128" {
137 t.Fatal("proxy removed")
138 }
139 if got := os.Getenv("NO_PROXY"); got != tc.want {
140 t.Errorf("NO_PROXY(%q) = %q, want %q", tc.in, got, tc.want)
141 }
142 if got := os.Getenv("no_proxy"); got != tc.want {
143 t.Errorf("no_proxy(%q) = %q, want %q", tc.in, got, tc.want)
144 }
145 }
146 t.Setenv("NO_PROXY", "")
147 scrubEnvironment(1001)
148 if os.Getenv("NO_PROXY") != "" {
149 t.Fatal("non-root NO_PROXY changed")
150 }
151}
152
153// A root child of a root process inherits the parent's own disabled shared
154// files. That is not an override: it must stay silent, or a guarded updater
155// that reads a candidate's `version` with its output combined refuses the
156// candidate (an armed box could then take no router update at all).
157func TestScrubEnvironmentInheritedSentinelIsNotAnOverride(t *testing.T) {
158 for _, key := range rootScrubbedEnvironment {
159 t.Setenv(key, "")
160 os.Unsetenv(key)
161 }
162 t.Setenv("AWS_CONFIG_FILE", disabledSharedFile)
163 t.Setenv("AWS_SHARED_CREDENTIALS_FILE", disabledSharedFile)
164 var logs bytes.Buffer
165 old := log.Writer()
166 log.SetOutput(&logs)
167 defer log.SetOutput(old)
168 scrubEnvironment(0)
169 if logs.Len() != 0 {
170 t.Fatalf("alert for the inherited sentinel: %q", logs.String())
171 }
172 if os.Getenv("AWS_CONFIG_FILE") != disabledSharedFile || os.Getenv("AWS_SHARED_CREDENTIALS_FILE") != disabledSharedFile {
173 t.Fatal("shared files not disabled for root")
174 }
175 // Any other value for either key is still an override.
176 for _, key := range []string{"AWS_CONFIG_FILE", "AWS_SHARED_CREDENTIALS_FILE"} {
177 logs.Reset()
178 t.Setenv("AWS_CONFIG_FILE", disabledSharedFile)
179 t.Setenv("AWS_SHARED_CREDENTIALS_FILE", disabledSharedFile)
180 t.Setenv(key, disabledSharedFile+"x")
181 scrubEnvironment(0)
182 if strings.Count(logs.String(), "ALERT:") != 1 {
183 t.Fatalf("%s set to another file did not alert: %q", key, logs.String())
184 }
185 if os.Getenv(key) != disabledSharedFile {
186 t.Fatalf("%s not reset to the sentinel", key)
187 }
188 }
189}
190