github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/terraform/block.go (about) 1 package terraform 2 3 import ( 4 "fmt" 5 "io/fs" 6 "strings" 7 8 defsecTypes "github.com/khulnasoft-lab/defsec/pkg/types" 9 10 "github.com/khulnasoft-lab/defsec/pkg/scanners/terraform/context" 11 12 "github.com/google/uuid" 13 "github.com/hashicorp/hcl/v2" 14 "github.com/hashicorp/hcl/v2/hclsyntax" 15 "github.com/zclconf/go-cty/cty" 16 "github.com/zclconf/go-cty/cty/gocty" 17 ) 18 19 type Block struct { 20 id string 21 hclBlock *hcl.Block 22 context *context.Context 23 moduleBlock *Block 24 parentBlock *Block 25 expanded bool 26 cloneIndex int 27 childBlocks []*Block 28 attributes []*Attribute 29 metadata defsecTypes.Metadata 30 moduleSource string 31 moduleFS fs.FS 32 reference Reference 33 } 34 35 func NewBlock(hclBlock *hcl.Block, ctx *context.Context, moduleBlock *Block, parentBlock *Block, moduleSource string, 36 moduleFS fs.FS, index ...cty.Value) *Block { 37 if ctx == nil { 38 ctx = context.NewContext(&hcl.EvalContext{}, nil) 39 } 40 41 var r hcl.Range 42 switch body := hclBlock.Body.(type) { 43 case *hclsyntax.Body: 44 r = body.SrcRange 45 default: 46 r = hclBlock.DefRange 47 r.End = hclBlock.Body.MissingItemRange().End 48 } 49 moduleName := "root" 50 if moduleBlock != nil { 51 moduleName = moduleBlock.FullName() 52 } 53 rng := defsecTypes.NewRange( 54 r.Filename, 55 r.Start.Line, 56 r.End.Line, 57 moduleSource, 58 moduleFS, 59 ) 60 61 var parts []string 62 // if there are no labels then use the block type 63 // this is for the case where "special" keywords like "resource" are used 64 // as normal block names in top level blocks - see issue terrasec#1528 for an example 65 if hclBlock.Type != "resource" || len(hclBlock.Labels) == 0 { 66 parts = append(parts, hclBlock.Type) 67 } 68 parts = append(parts, hclBlock.Labels...) 69 70 var parent string 71 if moduleBlock != nil { 72 parent = moduleBlock.FullName() 73 } 74 ref, _ := newReference(parts, parent) 75 if len(index) > 0 { 76 key := index[0] 77 if !key.IsNull() { 78 ref.SetKey(key) 79 } 80 } 81 82 metadata := defsecTypes.NewMetadata(rng, ref.String()) 83 84 if parentBlock != nil { 85 metadata = metadata.WithParent(parentBlock.metadata) 86 } else if moduleBlock != nil { 87 metadata = metadata.WithParent(moduleBlock.GetMetadata()) 88 } 89 90 b := Block{ 91 id: uuid.New().String(), 92 context: ctx, 93 hclBlock: hclBlock, 94 moduleBlock: moduleBlock, 95 moduleSource: moduleSource, 96 moduleFS: moduleFS, 97 parentBlock: parentBlock, 98 metadata: metadata, 99 reference: *ref, 100 } 101 102 var children Blocks 103 switch body := hclBlock.Body.(type) { 104 case *hclsyntax.Body: 105 for _, b2 := range body.Blocks { 106 children = append(children, NewBlock(b2.AsHCLBlock(), ctx, moduleBlock, &b, moduleSource, moduleFS)) 107 } 108 default: 109 content, _, diag := hclBlock.Body.PartialContent(Schema) 110 if diag == nil { 111 for _, hb := range content.Blocks { 112 children = append(children, NewBlock(hb, ctx, moduleBlock, &b, moduleSource, moduleFS)) 113 } 114 } 115 } 116 117 b.childBlocks = children 118 119 for _, attr := range b.createAttributes() { 120 b.attributes = append(b.attributes, NewAttribute(attr, ctx, moduleName, metadata, *ref, moduleSource, moduleFS)) 121 } 122 123 return &b 124 } 125 126 func (b *Block) ID() string { 127 return b.id 128 } 129 130 func (b *Block) Reference() Reference { 131 return b.reference 132 } 133 134 func (b *Block) GetMetadata() defsecTypes.Metadata { 135 return b.metadata 136 } 137 138 func (b *Block) GetRawValue() interface{} { 139 return nil 140 } 141 142 func (b *Block) InjectBlock(block *Block, name string) { 143 block.hclBlock.Labels = []string{} 144 block.hclBlock.Type = name 145 for attrName, attr := range block.Attributes() { 146 b.context.Root().SetByDot(attr.Value(), fmt.Sprintf("%s.%s.%s", b.reference.String(), name, attrName)) 147 } 148 b.childBlocks = append(b.childBlocks, block) 149 } 150 151 func (b *Block) markCountExpanded() { 152 b.expanded = true 153 } 154 155 func (b *Block) IsCountExpanded() bool { 156 return b.expanded 157 } 158 159 func (b *Block) Clone(index cty.Value) *Block { 160 var childCtx *context.Context 161 if b.context != nil { 162 childCtx = b.context.NewChild() 163 } else { 164 childCtx = context.NewContext(&hcl.EvalContext{}, nil) 165 } 166 167 cloneHCL := *b.hclBlock 168 169 clone := NewBlock(&cloneHCL, childCtx, b.moduleBlock, b.parentBlock, b.moduleSource, b.moduleFS, index) 170 if len(clone.hclBlock.Labels) > 0 { 171 position := len(clone.hclBlock.Labels) - 1 172 labels := make([]string, len(clone.hclBlock.Labels)) 173 for i := 0; i < len(labels); i++ { 174 labels[i] = clone.hclBlock.Labels[i] 175 } 176 if index.IsKnown() && !index.IsNull() { 177 switch index.Type() { 178 case cty.Number: 179 f, _ := index.AsBigFloat().Float64() 180 labels[position] = fmt.Sprintf("%s[%d]", clone.hclBlock.Labels[position], int(f)) 181 case cty.String: 182 labels[position] = fmt.Sprintf("%s[%q]", clone.hclBlock.Labels[position], index.AsString()) 183 default: 184 labels[position] = fmt.Sprintf("%s[%#v]", clone.hclBlock.Labels[position], index) 185 } 186 } else { 187 labels[position] = fmt.Sprintf("%s[%d]", clone.hclBlock.Labels[position], b.cloneIndex) 188 } 189 clone.hclBlock.Labels = labels 190 } 191 indexVal, _ := gocty.ToCtyValue(index, cty.Number) 192 clone.context.SetByDot(indexVal, "count.index") 193 clone.markCountExpanded() 194 b.cloneIndex++ 195 return clone 196 } 197 198 func (b *Block) Context() *context.Context { 199 return b.context 200 } 201 202 func (b *Block) OverrideContext(ctx *context.Context) { 203 b.context = ctx 204 for _, block := range b.childBlocks { 205 block.OverrideContext(ctx.NewChild()) 206 } 207 for _, attr := range b.attributes { 208 attr.ctx = ctx 209 } 210 } 211 212 func (b *Block) Type() string { 213 return b.hclBlock.Type 214 } 215 216 func (b *Block) Labels() []string { 217 return b.hclBlock.Labels 218 } 219 220 func (b *Block) GetFirstMatchingBlock(names ...string) *Block { 221 var returnBlock *Block 222 for _, name := range names { 223 childBlock := b.GetBlock(name) 224 if childBlock.IsNotNil() { 225 return childBlock 226 } 227 } 228 return returnBlock 229 } 230 231 func (b *Block) createAttributes() hcl.Attributes { 232 switch body := b.hclBlock.Body.(type) { 233 case *hclsyntax.Body: 234 attributes := make(hcl.Attributes) 235 for _, a := range body.Attributes { 236 attributes[a.Name] = a.AsHCLAttribute() 237 } 238 return attributes 239 default: 240 _, body, diag := b.hclBlock.Body.PartialContent(Schema) 241 if diag != nil { 242 return nil 243 } 244 attrs, diag := body.JustAttributes() 245 if diag != nil { 246 return nil 247 } 248 return attrs 249 } 250 } 251 252 func (b *Block) GetBlock(name string) *Block { 253 var returnBlock *Block 254 if b == nil || b.hclBlock == nil { 255 return returnBlock 256 } 257 for _, child := range b.childBlocks { 258 if child.Type() == name { 259 return child 260 } 261 } 262 return returnBlock 263 } 264 265 func (b *Block) AllBlocks() Blocks { 266 if b == nil || b.hclBlock == nil { 267 return nil 268 } 269 return b.childBlocks 270 } 271 272 func (b *Block) GetBlocks(name string) Blocks { 273 if b == nil || b.hclBlock == nil { 274 return nil 275 } 276 var results []*Block 277 for _, child := range b.childBlocks { 278 if child.Type() == name { 279 results = append(results, child) 280 } 281 } 282 return results 283 } 284 285 func (b *Block) GetAttributes() []*Attribute { 286 if b == nil { 287 return nil 288 } 289 return b.attributes 290 } 291 292 func (b *Block) GetAttribute(name string) *Attribute { 293 if b == nil || b.hclBlock == nil { 294 return nil 295 } 296 for _, attr := range b.attributes { 297 if attr.Name() == name { 298 return attr 299 } 300 } 301 return nil 302 } 303 304 func (b *Block) GetNestedAttribute(name string) *Attribute { 305 306 parts := strings.Split(name, ".") 307 blocks := parts[:len(parts)-1] 308 attrName := parts[len(parts)-1] 309 310 working := b 311 for _, subBlock := range blocks { 312 if checkBlock := working.GetBlock(subBlock); checkBlock == nil { 313 return nil 314 } else { 315 working = checkBlock 316 } 317 } 318 319 if working != nil { 320 return working.GetAttribute(attrName) 321 } 322 323 return nil 324 } 325 326 // LocalName is the name relative to the current module 327 func (b *Block) LocalName() string { 328 return b.reference.String() 329 } 330 331 func (b *Block) FullName() string { 332 333 if b.moduleBlock != nil { 334 return fmt.Sprintf( 335 "%s.%s", 336 b.moduleBlock.FullName(), 337 b.LocalName(), 338 ) 339 } 340 341 return b.LocalName() 342 } 343 344 func (b *Block) ModuleName() string { 345 name := strings.TrimPrefix(b.LocalName(), "module.") 346 if b.moduleBlock != nil { 347 module := strings.TrimPrefix(b.moduleBlock.FullName(), "module.") 348 name = fmt.Sprintf( 349 "%s.%s", 350 module, 351 name, 352 ) 353 } 354 var parts []string 355 for _, part := range strings.Split(name, ".") { 356 part = strings.Split(part, "[")[0] 357 parts = append(parts, part) 358 } 359 return strings.Join(parts, ".") 360 } 361 362 func (b *Block) UniqueName() string { 363 if b.moduleBlock != nil { 364 return fmt.Sprintf("%s:%s:%s", b.FullName(), b.metadata.Range().GetFilename(), b.moduleBlock.UniqueName()) 365 } 366 return fmt.Sprintf("%s:%s", b.FullName(), b.metadata.Range().GetFilename()) 367 } 368 369 func (b *Block) TypeLabel() string { 370 if len(b.Labels()) > 0 { 371 return b.Labels()[0] 372 } 373 return "" 374 } 375 376 func (b *Block) NameLabel() string { 377 if len(b.Labels()) > 1 { 378 return b.Labels()[1] 379 } 380 return "" 381 } 382 383 func (b *Block) HasChild(childElement string) bool { 384 return b.GetAttribute(childElement).IsNotNil() || b.GetBlock(childElement).IsNotNil() 385 } 386 387 func (b *Block) MissingChild(childElement string) bool { 388 if b == nil { 389 return true 390 } 391 392 return !b.HasChild(childElement) 393 } 394 395 func (b *Block) MissingNestedChild(name string) bool { 396 if b == nil { 397 return true 398 } 399 400 parts := strings.Split(name, ".") 401 blocks := parts[:len(parts)-1] 402 last := parts[len(parts)-1] 403 404 working := b 405 for _, subBlock := range blocks { 406 if checkBlock := working.GetBlock(subBlock); checkBlock == nil { 407 return true 408 } else { 409 working = checkBlock 410 } 411 } 412 return !working.HasChild(last) 413 414 } 415 416 func (b *Block) InModule() bool { 417 if b == nil { 418 return false 419 } 420 return b.moduleBlock != nil 421 } 422 423 func (b *Block) Label() string { 424 return strings.Join(b.hclBlock.Labels, ".") 425 } 426 427 func (b *Block) IsResourceType(resourceType string) bool { 428 return b.TypeLabel() == resourceType 429 } 430 431 func (b *Block) IsEmpty() bool { 432 return len(b.AllBlocks()) == 0 && len(b.GetAttributes()) == 0 433 } 434 435 func (b *Block) Attributes() map[string]*Attribute { 436 attributes := make(map[string]*Attribute) 437 for _, attr := range b.GetAttributes() { 438 attributes[attr.Name()] = attr 439 } 440 return attributes 441 } 442 443 func (b *Block) Values() cty.Value { 444 values := createPresetValues(b) 445 for _, attribute := range b.GetAttributes() { 446 values[attribute.Name()] = attribute.Value() 447 } 448 return cty.ObjectVal(postProcessValues(b, values)) 449 } 450 451 func (b *Block) IsNil() bool { 452 return b == nil 453 } 454 455 func (b *Block) IsNotNil() bool { 456 return !b.IsNil() 457 }