Skip to content

Commit 64926d8

Browse files
authored
fix(terraform): do not re-expand dynamic blocks (aquasecurity#6151)
1 parent eb54bb5 commit 64926d8

File tree

4 files changed

+335
-155
lines changed

4 files changed

+335
-155
lines changed

pkg/iac/scanners/terraform/parser/evaluator.go

+32-51
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package parser
33
import (
44
"context"
55
"errors"
6-
"fmt"
76
"io/fs"
87
"reflect"
98
"time"
@@ -148,7 +147,8 @@ func (e *evaluator) EvaluateAll(ctx context.Context) (terraform.Modules, map[str
148147
}
149148
}
150149

151-
// expand out resources and modules via count (not a typo, we do this twice so every order is processed)
150+
// expand out resources and modules via count, for-each and dynamic
151+
// (not a typo, we do this twice so every order is processed)
152152
e.blocks = e.expandBlocks(e.blocks)
153153
e.blocks = e.expandBlocks(e.blocks)
154154

@@ -204,7 +204,7 @@ func (e *evaluator) isModuleLocal() bool {
204204
}
205205

206206
func (e *evaluator) expandBlocks(blocks terraform.Blocks) terraform.Blocks {
207-
return e.expandDynamicBlocks(e.expandBlockForEaches(e.expandBlockCounts(blocks))...)
207+
return e.expandDynamicBlocks(e.expandBlockForEaches(e.expandBlockCounts(blocks), false)...)
208208
}
209209

210210
func (e *evaluator) expandDynamicBlocks(blocks ...*terraform.Block) terraform.Blocks {
@@ -219,103 +219,84 @@ func (e *evaluator) expandDynamicBlock(b *terraform.Block) {
219219
e.expandDynamicBlock(sub)
220220
}
221221
for _, sub := range b.AllBlocks().OfType("dynamic") {
222+
if sub.IsExpanded() {
223+
continue
224+
}
222225
blockName := sub.TypeLabel()
223-
expanded := e.expandBlockForEaches(terraform.Blocks{sub})
226+
expanded := e.expandBlockForEaches(terraform.Blocks{sub}, true)
224227
for _, ex := range expanded {
225228
if content := ex.GetBlock("content"); content.IsNotNil() {
226229
_ = e.expandDynamicBlocks(content)
227230
b.InjectBlock(content, blockName)
228231
}
229232
}
233+
sub.MarkExpanded()
230234
}
231235
}
232236

233-
func validateForEachArg(arg cty.Value) error {
234-
if arg.IsNull() {
235-
return errors.New("arg is null")
236-
}
237-
238-
ty := arg.Type()
239-
240-
if !arg.IsKnown() || ty.Equals(cty.DynamicPseudoType) || arg.LengthInt() == 0 {
241-
return nil
242-
}
243-
244-
if !(ty.IsSetType() || ty.IsObjectType() || ty.IsMapType()) {
245-
return fmt.Errorf("%s type is not supported: arg is not set or map", ty.FriendlyName())
246-
}
247-
248-
if ty.IsSetType() {
249-
if !ty.ElementType().Equals(cty.String) {
250-
return errors.New("arg is not set of strings")
251-
}
252-
253-
it := arg.ElementIterator()
254-
for it.Next() {
255-
key, _ := it.Element()
256-
if key.IsNull() {
257-
return errors.New("arg is set of strings, but contains null")
258-
}
259-
260-
if !key.IsKnown() {
261-
return errors.New("arg is set of strings, but contains unknown value")
262-
}
263-
}
264-
}
265-
266-
return nil
267-
}
268-
269237
func isBlockSupportsForEachMetaArgument(block *terraform.Block) bool {
270238
return slices.Contains([]string{"module", "resource", "data", "dynamic"}, block.Type())
271239
}
272240

273-
func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks) terraform.Blocks {
241+
func (e *evaluator) expandBlockForEaches(blocks terraform.Blocks, isDynamic bool) terraform.Blocks {
274242
var forEachFiltered terraform.Blocks
275243

276244
for _, block := range blocks {
277245

278246
forEachAttr := block.GetAttribute("for_each")
279247

280-
if forEachAttr.IsNil() || block.IsCountExpanded() || !isBlockSupportsForEachMetaArgument(block) {
248+
if forEachAttr.IsNil() || block.IsExpanded() || !isBlockSupportsForEachMetaArgument(block) {
281249
forEachFiltered = append(forEachFiltered, block)
282250
continue
283251
}
284252

285253
forEachVal := forEachAttr.Value()
286254

287-
if err := validateForEachArg(forEachVal); err != nil {
288-
e.debug.Log(`"for_each" argument is invalid: %s`, err.Error())
255+
if forEachVal.IsNull() || !forEachVal.IsKnown() || !forEachAttr.IsIterable() {
289256
continue
290257
}
291258

292259
clones := make(map[string]cty.Value)
293260
_ = forEachAttr.Each(func(key cty.Value, val cty.Value) {
294261

295-
if !key.Type().Equals(cty.String) {
262+
// instances are identified by a map key (or set member) from the value provided to for_each
263+
idx, err := convert.Convert(key, cty.String)
264+
if err != nil {
296265
e.debug.Log(
297266
`Invalid "for-each" argument: map key (or set value) is not a string, but %s`,
298267
key.Type().FriendlyName(),
299268
)
300269
return
301270
}
302271

303-
clone := block.Clone(key)
272+
// if the argument is a collection but not a map, then the resource identifier
273+
// is the value of the collection. The exception is the use of for-each inside a dynamic block,
274+
// because in this case the collection element may not be a primitive value.
275+
if (forEachVal.Type().IsCollectionType() || forEachVal.Type().IsTupleType()) &&
276+
!forEachVal.Type().IsMapType() && !isDynamic {
277+
stringVal, err := convert.Convert(val, cty.String)
278+
if err != nil {
279+
e.debug.Log("Failed to convert for-each arg %v to string", val)
280+
return
281+
}
282+
idx = stringVal
283+
}
284+
285+
clone := block.Clone(idx)
304286

305287
ctx := clone.Context()
306288

307289
e.copyVariables(block, clone)
308290

309-
ctx.SetByDot(key, "each.key")
291+
ctx.SetByDot(idx, "each.key")
310292
ctx.SetByDot(val, "each.value")
311-
312-
ctx.Set(key, block.TypeLabel(), "key")
293+
ctx.Set(idx, block.TypeLabel(), "key")
313294
ctx.Set(val, block.TypeLabel(), "value")
314295

315296
forEachFiltered = append(forEachFiltered, clone)
316297

317298
values := clone.Values()
318-
clones[key.AsString()] = values
299+
clones[idx.AsString()] = values
319300
e.ctx.SetByDot(values, clone.GetMetadata().Reference())
320301
})
321302

@@ -341,7 +322,7 @@ func (e *evaluator) expandBlockCounts(blocks terraform.Blocks) terraform.Blocks
341322
var countFiltered terraform.Blocks
342323
for _, block := range blocks {
343324
countAttr := block.GetAttribute("count")
344-
if countAttr.IsNil() || block.IsCountExpanded() || !isBlockSupportsCountMetaArgument(block) {
325+
if countAttr.IsNil() || block.IsExpanded() || !isBlockSupportsCountMetaArgument(block) {
345326
countFiltered = append(countFiltered, block)
346327
continue
347328
}

pkg/iac/scanners/terraform/parser/evaluator_test.go

-94
This file was deleted.

0 commit comments

Comments
 (0)