rootaws: root also drops AWS endpoint overrides (AWS_ENDPOINT_URL, AWS_ENDPOINT_URL_*, IMDS endpoint) and Go's SSL_CERT_FILE/SSL_CERT_DIR (#267)
An inherited override could redirect root's signed requests or replace IMDS as the credential source; the certificate selectors would let an unvalidated file into the TLS trust store the AWS transports use. Nothing in the platform sets any of them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2 files changed,
+28
−4
internal/rootaws/environment.go+21 −4internal/rootaws/environment_test.go+7 −0
CI passed — run details
diff --git a/internal/rootaws/environment.go b/internal/rootaws/environment.go| index 13e0c2037..d4a7055aa 100644 |
| --- a/internal/rootaws/environment.go |
| +++ b/internal/rootaws/environment.go |
| @@ -3,13 +3,14 @@ package rootaws |
| import ( |
| "log" |
| "os" |
| + "strings" |
| ) |
| // A child of a device cannot exist, even if a service user creates directories. |
| const disabledSharedFile = "/dev/null/carlos-root-aws" |
| -// rootScrubbedEnvironment names every variable that selects a file, a profile |
| -// or a credential endpoint the SDK would read on root's behalf. Root's own |
| +// rootScrubbedEnvironment names every variable that selects a file, a profile, |
| +// an endpoint or a trust store the SDK would use on root's behalf. Root's own |
| // credentials come from the environment keys, the instance profile, or the |
| // validated off-cloud file, never from these. |
| var rootScrubbedEnvironment = []string{ |
| @@ -26,8 +27,18 @@ var rootScrubbedEnvironment = []string{ |
| "AWS_CONTAINER_CREDENTIALS_RELATIVE_URI", |
| "AWS_CONTAINER_AUTHORIZATION_TOKEN", |
| "AWS_CONTAINER_AUTHORIZATION_TOKEN_FILE", |
| + "AWS_ENDPOINT_URL", |
| + "AWS_EC2_METADATA_SERVICE_ENDPOINT", |
| + "AWS_EC2_METADATA_SERVICE_ENDPOINT_MODE", |
| + // Go's TLS trust store reads these unvalidated; AWS transports use it. |
| + "SSL_CERT_FILE", |
| + "SSL_CERT_DIR", |
| } |
| +// Service-specific endpoint overrides (AWS_ENDPOINT_URL_S3 and the rest) |
| +// are matched by prefix. |
| +const rootScrubbedEndpointPrefix = "AWS_ENDPOINT_URL_" |
| + |
| // ScrubEnvironment must run before dispatch or goroutines in each executable. |
| // It also protects SDK loads in dependencies that do not call this package. |
| func ScrubEnvironment() { |
| @@ -39,7 +50,13 @@ func scrubEnvironment(euid int) { |
| return |
| } |
| changed := false |
| - for _, key := range rootScrubbedEnvironment { |
| + keys := append([]string(nil), rootScrubbedEnvironment...) |
| + for _, kv := range os.Environ() { |
| + if key, _, _ := strings.Cut(kv, "="); strings.HasPrefix(key, rootScrubbedEndpointPrefix) { |
| + keys = append(keys, key) |
| + } |
| + } |
| + for _, key := range keys { |
| if _, set := os.LookupEnv(key); set { |
| changed = true |
| } |
| @@ -50,6 +67,6 @@ func scrubEnvironment(euid int) { |
| _ = os.Setenv("AWS_CONFIG_FILE", disabledSharedFile) |
| _ = os.Setenv("AWS_SHARED_CREDENTIALS_FILE", disabledSharedFile) |
| if changed { |
| - log.Print("ALERT: root AWS file, profile and credential-endpoint environment overrides ignored") |
| + log.Print("ALERT: root AWS file, profile, endpoint and trust-store environment overrides ignored") |
| } |
| } |
diff --git a/internal/rootaws/environment_test.go b/internal/rootaws/environment_test.go| index 574556a81..1f56a3d98 100644 |
| --- a/internal/rootaws/environment_test.go |
| +++ b/internal/rootaws/environment_test.go |
| @@ -24,6 +24,8 @@ func TestScrubEnvironment(t *testing.T) { |
| for _, key := range rootScrubbedEnvironment { |
| t.Setenv(key, "untrusted") |
| } |
| + t.Setenv("AWS_ENDPOINT_URL_S3", "untrusted") |
| + t.Setenv("AWS_ENDPOINT_URL_STS", "untrusted") |
| t.Setenv("AWS_ACCESS_KEY_ID", "KEEP") |
| t.Setenv("AWS_SECRET_ACCESS_KEY", "KEEP_SECRET") |
| t.Setenv("AWS_SESSION_TOKEN", "KEEP_TOKEN") |
| @@ -45,6 +47,11 @@ func TestScrubEnvironment(t *testing.T) { |
| t.Errorf("%s = %q, set=%v; want %q", key, got, set, want) |
| } |
| } |
| + for _, key := range []string{"AWS_ENDPOINT_URL_S3", "AWS_ENDPOINT_URL_STS"} { |
| + if _, set := os.LookupEnv(key); set != (tc.euid != 0) { |
| + t.Errorf("%s set=%v for euid %d", key, set, tc.euid) |
| + } |
| + } |
| wantLogs := 0 |
| if tc.euid == 0 { |
| wantLogs = 1 |