Allow for_each arguments containing sensitive values if they aren't keys (#27247)

* Add test for existing behavior, when a value contains a marked value

* Allow some marked values as for_each arguments

Rather than disallow values that have any marks
as for_each arguments, this makes the check more
nuanced to disallow cases where the whole value
is marked (a whole map, or any set). This allows
cases where a user may pass a map that has marked
values, but the keys are not sensitive
This commit is contained in:
Pam Selle 2020-12-17 11:27:12 -05:00 committed by GitHub
parent 1970c14a53
commit 428d404d92
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 37 additions and 4 deletions

View File

@ -22,7 +22,7 @@ func evaluateForEachExpression(expr hcl.Expression, ctx EvalContext) (forEach ma
// forEachVal might be unknown, but if it is then there should already
// be an error about it in diags, which we'll return below.
if forEachVal.IsNull() || !forEachVal.IsKnown() || forEachVal.LengthInt() == 0 {
if forEachVal.IsNull() || !forEachVal.IsKnown() || markSafeLengthInt(forEachVal) == 0 {
// we check length, because an empty set return a nil map
return map[string]cty.Value{}, diags
}
@ -58,16 +58,20 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext, allowU
forEachVal, forEachDiags := expr.Value(hclCtx)
diags = diags.Append(forEachDiags)
if forEachVal.ContainsMarked() {
// If a whole map is marked, or a set contains marked values (which means the set is then marked)
// give an error diagnostic as this value cannot be used in for_each
if forEachVal.IsMarked() {
diags = diags.Append(&hcl.Diagnostic{
Severity: hcl.DiagError,
Summary: "Invalid for_each argument",
Detail: "Sensitive variables, or values derived from sensitive variables, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
Detail: "Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
Subject: expr.Range().Ptr(),
Expression: expr,
EvalContext: hclCtx,
})
}
if diags.HasErrors() {
return nullMap, diags
}
@ -109,7 +113,7 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext, allowU
})
return nullMap, diags
case forEachVal.LengthInt() == 0:
case markSafeLengthInt(forEachVal) == 0:
// If the map is empty ({}), return an empty map, because cty will
// return nil when representing {} AsValueMap. This also covers an empty
// set (toset([]))
@ -168,3 +172,9 @@ func evaluateForEachExpressionValue(expr hcl.Expression, ctx EvalContext, allowU
}
const errInvalidForEachUnknownDetail = `The "for_each" value depends on resource attributes that cannot be determined until apply, so Terraform cannot predict how many instances will be created. To work around this, use the -target argument to first apply only the resources that the for_each depends on.`
// markSafeLengthInt allows calling LengthInt on marked values safely
func markSafeLengthInt(val cty.Value) int {
v, _ := val.UnmarkDeep()
return v.LengthInt()
}

View File

@ -52,6 +52,16 @@ func TestEvaluateForEachExpression_valid(t *testing.T) {
"b": cty.UnknownVal(cty.Bool),
},
},
"map containing sensitive values, but strings are literal": {
hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
"a": cty.BoolVal(true).Mark("sensitive"),
"b": cty.BoolVal(false),
})),
map[string]cty.Value{
"a": cty.BoolVal(true).Mark("sensitive"),
"b": cty.BoolVal(false),
},
},
}
for name, test := range tests {
@ -110,6 +120,14 @@ func TestEvaluateForEachExpression_errors(t *testing.T) {
"Invalid for_each argument",
"depends on resource attributes that cannot be determined until apply",
},
"marked map": {
hcltest.MockExprLiteral(cty.MapVal(map[string]cty.Value{
"a": cty.BoolVal(true),
"b": cty.BoolVal(false),
}).Mark("sensitive")),
"Invalid for_each argument",
"Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
},
"set containing booleans": {
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.BoolVal(true)})),
"Invalid for_each set argument",
@ -130,6 +148,11 @@ func TestEvaluateForEachExpression_errors(t *testing.T) {
"Invalid for_each argument",
"depends on resource attributes that cannot be determined until apply",
},
"set containing marked values": {
hcltest.MockExprLiteral(cty.SetVal([]cty.Value{cty.StringVal("beep").Mark("sensitive"), cty.StringVal("boop")})),
"Invalid for_each argument",
"Sensitive values, or values derived from sensitive values, cannot be used as for_each arguments. If used, the sensitive value could be exposed as a resource instance key.",
},
}
for name, test := range tests {