carlos / platform Public

rootaws: root keeps an inherited proxy but always bypasses it for IMDS (#267)

NO_PROXY and no_proxy gain 169.254.169.254 and fd00:ec2::254 for root,
so a proxy can never answer for the instance metadata service, root's
EC2 credential source. Off-cloud proxies keep working.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Paul Campbell pushed by claude agent, operated by paul@keymail.dev fdc8c3c3d9445c07738f1ab708efa0fa9e116850 parent 3eeb9cc
2 files changed, +52 −0
  • internal/rootaws/environment.go +25 −0
  • internal/rootaws/environment_test.go +27 −0
diff --git a/internal/rootaws/environment.go b/internal/rootaws/environment.go
index d4a7055aa..343ea793b 100644
--- a/internal/rootaws/environment.go
+++ b/internal/rootaws/environment.go
@@ -62,6 +62,12 @@ func scrubEnvironment(euid int) {
}
_ = os.Unsetenv(key)
}
+ // A proxy may be needed off-cloud, so it stays, but it never answers for
+ // the instance metadata service: that is where root's EC2 credentials
+ // come from.
+ for _, key := range []string{"NO_PROXY", "no_proxy"} {
+ _ = os.Setenv(key, withIMDSBypass(os.Getenv(key)))
+ }
// Root never reads shared config, not even under its own HOME: a
// root-owned HOME can still hold a .aws the service user can write.
_ = os.Setenv("AWS_CONFIG_FILE", disabledSharedFile)
@@ -70,3 +76,22 @@ func scrubEnvironment(euid int) {
log.Print("ALERT: root AWS file, profile, endpoint and trust-store environment overrides ignored")
}
}
+
+// imdsHosts are the IPv4 and IPv6 instance metadata addresses.
+var imdsHosts = []string{"169.254.169.254", "fd00:ec2::254"}
+
+func withIMDSBypass(noProxy string) string {
+ entries := strings.FieldsFunc(noProxy, func(r rune) bool { return r == ',' })
+ for _, host := range imdsHosts {
+ found := false
+ for _, entry := range entries {
+ if strings.TrimSpace(entry) == host {
+ found = true
+ }
+ }
+ if !found {
+ entries = append(entries, host)
+ }
+ }
+ return strings.Join(entries, ",")
+}
diff --git a/internal/rootaws/environment_test.go b/internal/rootaws/environment_test.go
index 1f56a3d98..3d4dc9774 100644
--- a/internal/rootaws/environment_test.go
+++ b/internal/rootaws/environment_test.go
@@ -122,3 +122,30 @@ func TestScrubEnvironmentBlocksDependencySharedConfig(t *testing.T) {
t.Fatal("disabled path exists")
}
}
+
+func TestScrubEnvironmentKeepsProxyButBypassesIMDS(t *testing.T) {
+ for _, tc := range []struct{ in, want string }{
+ {"", "169.254.169.254,fd00:ec2::254"},
+ {"example.com", "example.com,169.254.169.254,fd00:ec2::254"},
+ {"169.254.169.254,example.com", "169.254.169.254,example.com,fd00:ec2::254"},
+ } {
+ t.Setenv("HTTP_PROXY", "http://proxy.example.com:3128")
+ t.Setenv("NO_PROXY", tc.in)
+ t.Setenv("no_proxy", tc.in)
+ scrubEnvironment(0)
+ if os.Getenv("HTTP_PROXY") != "http://proxy.example.com:3128" {
+ t.Fatal("proxy removed")
+ }
+ if got := os.Getenv("NO_PROXY"); got != tc.want {
+ t.Errorf("NO_PROXY(%q) = %q, want %q", tc.in, got, tc.want)
+ }
+ if got := os.Getenv("no_proxy"); got != tc.want {
+ t.Errorf("no_proxy(%q) = %q, want %q", tc.in, got, tc.want)
+ }
+ }
+ t.Setenv("NO_PROXY", "")
+ scrubEnvironment(1001)
+ if os.Getenv("NO_PROXY") != "" {
+ t.Fatal("non-root NO_PROXY changed")
+ }
+}