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  }