Skip to content

Commit d1d6240

Browse files
committed
fix: parse boolean env vars in S3 backend correctly
Fixes #37601
1 parent 7cd3a7d commit d1d6240

File tree

3 files changed

+138
-4
lines changed

3 files changed

+138
-4
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
kind: BUG FIXES
2+
body: 'backend/s3: Fixed incorrect parsing of `AWS_USE_FIPS_ENDPOINT` and `AWS_USE_DUALSTACK_ENDPOINT` environment variables where any non-empty value incorrectly enabled the feature instead of properly parsing boolean values'
3+
time: 2025-11-25T13:30:00.000000Z
4+
custom:
5+
Issue: "37601"

internal/backend/remote-state/s3/backend.go

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1331,14 +1331,21 @@ func boolAttrOk(obj cty.Value, name string) (bool, bool) {
13311331
}
13321332
}
13331333

1334-
// boolAttrDefaultEnvVarOk checks for a configured bool argument or a non-empty
1335-
// value in any of the provided environment variables. If any of the environment
1336-
// variables are non-empty, to boolean is considered true.
1334+
// boolAttrDefaultEnvVarOk checks for a configured bool argument or a boolean
1335+
// value ("true" or "false", case-insensitive) in any of the provided environment
1336+
// variables. Invalid values are treated as unset.
13371337
func boolAttrDefaultEnvVarOk(obj cty.Value, name string, envvars ...string) (bool, bool) {
13381338
if val := obj.GetAttr(name); val.IsNull() {
13391339
for _, envvar := range envvars {
13401340
if v := os.Getenv(envvar); v != "" {
1341-
return true, true
1341+
if strings.EqualFold(v, "true") {
1342+
return true, true
1343+
}
1344+
if strings.EqualFold(v, "false") {
1345+
return false, true
1346+
}
1347+
// Invalid boolean value, treat as unset
1348+
return false, false
13421349
}
13431350
}
13441351
return false, false

internal/backend/remote-state/s3/backend_test.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3963,3 +3963,125 @@ func objectLockPreCheck(t *testing.T) {
39633963
t.Skip("s3 backend tests using object lock enabled buckets require setting TF_S3_OBJECT_LOCK_TEST")
39643964
}
39653965
}
3966+
3967+
// TestBoolAttrDefaultEnvVarOk tests the boolAttrDefaultEnvVarOk helper function
3968+
// which reads boolean values from environment variables. This is a regression
3969+
// test for https://github.com/hashicorp/terraform/issues/37601 where setting
3970+
// AWS_USE_FIPS_ENDPOINT=false incorrectly enabled FIPS endpoints.
3971+
func TestBoolAttrDefaultEnvVarOk(t *testing.T) {
3972+
testCases := map[string]struct {
3973+
attrValue cty.Value
3974+
envValue string
3975+
wantBool bool
3976+
wantOk bool
3977+
}{
3978+
"attr set true": {
3979+
attrValue: cty.BoolVal(true),
3980+
envValue: "",
3981+
wantBool: true,
3982+
wantOk: true,
3983+
},
3984+
"attr set false": {
3985+
attrValue: cty.BoolVal(false),
3986+
envValue: "",
3987+
wantBool: false,
3988+
wantOk: true,
3989+
},
3990+
"attr null, env true": {
3991+
attrValue: cty.NullVal(cty.Bool),
3992+
envValue: "true",
3993+
wantBool: true,
3994+
wantOk: true,
3995+
},
3996+
"attr null, env TRUE": {
3997+
attrValue: cty.NullVal(cty.Bool),
3998+
envValue: "TRUE",
3999+
wantBool: true,
4000+
wantOk: true,
4001+
},
4002+
"attr null, env True": {
4003+
attrValue: cty.NullVal(cty.Bool),
4004+
envValue: "True",
4005+
wantBool: true,
4006+
wantOk: true,
4007+
},
4008+
"attr null, env false": {
4009+
attrValue: cty.NullVal(cty.Bool),
4010+
envValue: "false",
4011+
wantBool: false,
4012+
wantOk: true,
4013+
},
4014+
"attr null, env FALSE": {
4015+
attrValue: cty.NullVal(cty.Bool),
4016+
envValue: "FALSE",
4017+
wantBool: false,
4018+
wantOk: true,
4019+
},
4020+
"attr null, env False": {
4021+
attrValue: cty.NullVal(cty.Bool),
4022+
envValue: "False",
4023+
wantBool: false,
4024+
wantOk: true,
4025+
},
4026+
"attr null, env empty": {
4027+
attrValue: cty.NullVal(cty.Bool),
4028+
envValue: "",
4029+
wantBool: false,
4030+
wantOk: false,
4031+
},
4032+
"attr null, env invalid": {
4033+
attrValue: cty.NullVal(cty.Bool),
4034+
envValue: "invalid",
4035+
wantBool: false,
4036+
wantOk: false,
4037+
},
4038+
"attr null, env yes": {
4039+
attrValue: cty.NullVal(cty.Bool),
4040+
envValue: "yes",
4041+
wantBool: false,
4042+
wantOk: false,
4043+
},
4044+
"attr null, env 1": {
4045+
attrValue: cty.NullVal(cty.Bool),
4046+
envValue: "1",
4047+
wantBool: false,
4048+
wantOk: false,
4049+
},
4050+
"attr null, env 0": {
4051+
attrValue: cty.NullVal(cty.Bool),
4052+
envValue: "0",
4053+
wantBool: false,
4054+
wantOk: false,
4055+
},
4056+
"attr takes precedence over env": {
4057+
attrValue: cty.BoolVal(false),
4058+
envValue: "true",
4059+
wantBool: false,
4060+
wantOk: true,
4061+
},
4062+
}
4063+
4064+
for name, tc := range testCases {
4065+
t.Run(name, func(t *testing.T) {
4066+
servicemocks.StashEnv(t)
4067+
4068+
const testEnvVar = "TF_TEST_BOOL_ENV_VAR"
4069+
if tc.envValue != "" {
4070+
os.Setenv(testEnvVar, tc.envValue)
4071+
}
4072+
4073+
obj := cty.ObjectVal(map[string]cty.Value{
4074+
"test_attr": tc.attrValue,
4075+
})
4076+
4077+
gotBool, gotOk := boolAttrDefaultEnvVarOk(obj, "test_attr", testEnvVar)
4078+
4079+
if gotBool != tc.wantBool {
4080+
t.Errorf("got bool %v, want %v", gotBool, tc.wantBool)
4081+
}
4082+
if gotOk != tc.wantOk {
4083+
t.Errorf("got ok %v, want %v", gotOk, tc.wantOk)
4084+
}
4085+
})
4086+
}
4087+
}

0 commit comments

Comments
 (0)