github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/cloudformation/parser/property.go (about)

     1  package parser
     2  
     3  import (
     4  	"encoding/json"
     5  	"io/fs"
     6  	"strconv"
     7  	"strings"
     8  
     9  	defsecTypes "github.com/aquasecurity/defsec/pkg/types"
    10  
    11  	"github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/cftypes"
    12  
    13  	"github.com/liamg/jfather"
    14  	"gopkg.in/yaml.v3"
    15  )
    16  
    17  type EqualityOptions = int
    18  
    19  const (
    20  	IgnoreCase EqualityOptions = iota
    21  )
    22  
    23  type Property struct {
    24  	ctx         *FileContext
    25  	name        string
    26  	comment     string
    27  	rng         defsecTypes.Range
    28  	parentRange defsecTypes.Range
    29  	Inner       PropertyInner
    30  	logicalId   string
    31  	unresolved  bool
    32  }
    33  
    34  type PropertyInner struct {
    35  	Type  cftypes.CfType
    36  	Value interface{} `json:"Value" yaml:"Value"`
    37  }
    38  
    39  func (p *Property) Comment() string {
    40  	return p.comment
    41  }
    42  
    43  func (p *Property) setName(name string) {
    44  	p.name = name
    45  	if p.Type() == cftypes.Map {
    46  		for n, subProp := range p.AsMap() {
    47  			if subProp == nil {
    48  				continue
    49  			}
    50  			subProp.setName(n)
    51  		}
    52  	}
    53  }
    54  
    55  func (p *Property) setContext(ctx *FileContext) {
    56  	p.ctx = ctx
    57  
    58  	if p.IsMap() {
    59  		for _, subProp := range p.AsMap() {
    60  			if subProp == nil {
    61  				continue
    62  			}
    63  			subProp.setContext(ctx)
    64  		}
    65  	}
    66  
    67  	if p.IsList() {
    68  		for _, subProp := range p.AsList() {
    69  			subProp.setContext(ctx)
    70  		}
    71  	}
    72  }
    73  
    74  func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRange defsecTypes.Range) {
    75  	p.rng = defsecTypes.NewRange(filepath, p.rng.GetStartLine(), p.rng.GetEndLine(), p.rng.GetSourcePrefix(), target)
    76  	p.parentRange = parentRange
    77  
    78  	switch p.Type() {
    79  	case cftypes.Map:
    80  		for _, subProp := range p.AsMap() {
    81  			if subProp == nil {
    82  				continue
    83  			}
    84  			subProp.setFileAndParentRange(target, filepath, parentRange)
    85  		}
    86  	case cftypes.List:
    87  		for _, subProp := range p.AsList() {
    88  			if subProp == nil {
    89  				continue
    90  			}
    91  			subProp.setFileAndParentRange(target, filepath, parentRange)
    92  		}
    93  	}
    94  }
    95  
    96  func (p *Property) UnmarshalYAML(node *yaml.Node) error {
    97  	p.rng = defsecTypes.NewRange("", node.Line, calculateEndLine(node), "", nil)
    98  
    99  	p.comment = node.LineComment
   100  	return setPropertyValueFromYaml(node, &p.Inner)
   101  }
   102  
   103  func (p *Property) UnmarshalJSONWithMetadata(node jfather.Node) error {
   104  	p.rng = defsecTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil)
   105  	return setPropertyValueFromJson(node, &p.Inner)
   106  }
   107  
   108  func (p *Property) Type() cftypes.CfType {
   109  	return p.Inner.Type
   110  }
   111  
   112  func (p *Property) Range() defsecTypes.Range {
   113  	return p.rng
   114  }
   115  
   116  func (p *Property) Metadata() defsecTypes.Metadata {
   117  	base := p
   118  	if p.isFunction() {
   119  		if resolved, ok := p.resolveValue(); ok {
   120  			base = resolved
   121  		}
   122  	}
   123  	ref := NewCFReferenceWithValue(p.parentRange, *base, p.logicalId)
   124  	return defsecTypes.NewMetadata(p.Range(), ref.String())
   125  }
   126  
   127  func (p *Property) MetadataWithValue(resolvedValue *Property) defsecTypes.Metadata {
   128  	ref := NewCFReferenceWithValue(p.parentRange, *resolvedValue, p.logicalId)
   129  	return defsecTypes.NewMetadata(p.Range(), ref.String())
   130  }
   131  
   132  func (p *Property) isFunction() bool {
   133  	if p == nil {
   134  		return false
   135  	}
   136  	if p.Type() == cftypes.Map {
   137  		for n := range p.AsMap() {
   138  			return IsIntrinsic(n)
   139  		}
   140  	}
   141  	return false
   142  }
   143  
   144  func (p *Property) RawValue() interface{} {
   145  	return p.Inner.Value
   146  }
   147  
   148  func (p *Property) AsRawStrings() ([]string, error) {
   149  
   150  	if len(p.ctx.lines) < p.rng.GetEndLine() {
   151  		return p.ctx.lines, nil
   152  	}
   153  	return p.ctx.lines[p.rng.GetStartLine()-1 : p.rng.GetEndLine()], nil
   154  }
   155  
   156  func (p *Property) resolveValue() (*Property, bool) {
   157  	if !p.isFunction() || p.IsUnresolved() {
   158  		return p, true
   159  	}
   160  
   161  	resolved, ok := ResolveIntrinsicFunc(p)
   162  	if ok {
   163  		return resolved, true
   164  	}
   165  
   166  	p.unresolved = true
   167  	return p, false
   168  }
   169  
   170  func (p *Property) GetStringProperty(path string, defaultValue ...string) defsecTypes.StringValue {
   171  	defVal := ""
   172  	if len(defaultValue) > 0 {
   173  		defVal = defaultValue[0]
   174  	}
   175  
   176  	if p.IsUnresolved() {
   177  		return defsecTypes.StringUnresolvable(p.Metadata())
   178  	}
   179  
   180  	prop := p.GetProperty(path)
   181  	if prop.IsNotString() {
   182  		return p.StringDefault(defVal)
   183  	}
   184  	return prop.AsStringValue()
   185  }
   186  
   187  func (p *Property) StringDefault(defaultValue string) defsecTypes.StringValue {
   188  	return defsecTypes.StringDefault(defaultValue, p.Metadata())
   189  }
   190  
   191  func (p *Property) GetBoolProperty(path string, defaultValue ...bool) defsecTypes.BoolValue {
   192  	defVal := false
   193  	if len(defaultValue) > 0 {
   194  		defVal = defaultValue[0]
   195  	}
   196  
   197  	if p.IsUnresolved() {
   198  		return defsecTypes.BoolUnresolvable(p.Metadata())
   199  	}
   200  
   201  	prop := p.GetProperty(path)
   202  
   203  	if prop.isFunction() {
   204  		prop, _ = prop.resolveValue()
   205  	}
   206  
   207  	if prop.IsNotBool() {
   208  		return p.inferBool(prop, defVal)
   209  	}
   210  	return prop.AsBoolValue()
   211  }
   212  
   213  func (p *Property) GetIntProperty(path string, defaultValue ...int) defsecTypes.IntValue {
   214  	defVal := 0
   215  	if len(defaultValue) > 0 {
   216  		defVal = defaultValue[0]
   217  	}
   218  
   219  	if p.IsUnresolved() {
   220  		return defsecTypes.IntUnresolvable(p.Metadata())
   221  	}
   222  
   223  	prop := p.GetProperty(path)
   224  
   225  	if prop.IsNotInt() {
   226  		return p.IntDefault(defVal)
   227  	}
   228  	return prop.AsIntValue()
   229  }
   230  
   231  func (p *Property) BoolDefault(defaultValue bool) defsecTypes.BoolValue {
   232  	return defsecTypes.BoolDefault(defaultValue, p.Metadata())
   233  }
   234  
   235  func (p *Property) IntDefault(defaultValue int) defsecTypes.IntValue {
   236  	return defsecTypes.IntDefault(defaultValue, p.Metadata())
   237  }
   238  
   239  func (p *Property) GetProperty(path string) *Property {
   240  
   241  	pathParts := strings.Split(path, ".")
   242  
   243  	first := pathParts[0]
   244  	property := p
   245  
   246  	if p.isFunction() {
   247  		property, _ = p.resolveValue()
   248  	}
   249  
   250  	if property.IsNotMap() {
   251  		return nil
   252  	}
   253  
   254  	for n, p := range property.AsMap() {
   255  		if n == first {
   256  			property = p
   257  			break
   258  		}
   259  	}
   260  
   261  	if len(pathParts) == 1 || property == nil {
   262  		return property
   263  	}
   264  
   265  	if nestedProperty := property.GetProperty(strings.Join(pathParts[1:], ".")); nestedProperty != nil {
   266  		if nestedProperty.isFunction() {
   267  			resolved, _ := nestedProperty.resolveValue()
   268  			return resolved
   269  		} else {
   270  			return nestedProperty
   271  		}
   272  	}
   273  
   274  	return &Property{}
   275  }
   276  
   277  func (p *Property) deriveResolved(propType cftypes.CfType, propValue interface{}) *Property {
   278  	return &Property{
   279  		ctx:         p.ctx,
   280  		name:        p.name,
   281  		comment:     p.comment,
   282  		rng:         p.rng,
   283  		parentRange: p.parentRange,
   284  		logicalId:   p.logicalId,
   285  		Inner: PropertyInner{
   286  			Type:  propType,
   287  			Value: propValue,
   288  		},
   289  	}
   290  }
   291  
   292  func (p *Property) ParentRange() defsecTypes.Range {
   293  	return p.parentRange
   294  }
   295  
   296  func (p *Property) inferBool(prop *Property, defaultValue bool) defsecTypes.BoolValue {
   297  	if prop.IsString() {
   298  		if prop.EqualTo("true", IgnoreCase) {
   299  			return defsecTypes.Bool(true, prop.Metadata())
   300  		}
   301  		if prop.EqualTo("yes", IgnoreCase) {
   302  			return defsecTypes.Bool(true, prop.Metadata())
   303  		}
   304  		if prop.EqualTo("1", IgnoreCase) {
   305  			return defsecTypes.Bool(true, prop.Metadata())
   306  		}
   307  		if prop.EqualTo("false", IgnoreCase) {
   308  			return defsecTypes.Bool(false, prop.Metadata())
   309  		}
   310  		if prop.EqualTo("no", IgnoreCase) {
   311  			return defsecTypes.Bool(false, prop.Metadata())
   312  		}
   313  		if prop.EqualTo("0", IgnoreCase) {
   314  			return defsecTypes.Bool(false, prop.Metadata())
   315  		}
   316  	}
   317  
   318  	if prop.IsInt() {
   319  		if prop.EqualTo(0) {
   320  			return defsecTypes.Bool(false, prop.Metadata())
   321  		}
   322  		if prop.EqualTo(1) {
   323  			return defsecTypes.Bool(true, prop.Metadata())
   324  		}
   325  	}
   326  
   327  	return p.BoolDefault(defaultValue)
   328  }
   329  
   330  func (p *Property) String() string {
   331  	r := ""
   332  	switch p.Type() {
   333  	case cftypes.String:
   334  		r = p.AsString()
   335  	case cftypes.Int:
   336  		r = strconv.Itoa(p.AsInt())
   337  	}
   338  	return r
   339  }
   340  
   341  func (p *Property) SetLogicalResource(id string) {
   342  	p.logicalId = id
   343  
   344  	if p.isFunction() {
   345  		return
   346  	}
   347  
   348  	if p.IsMap() {
   349  		for _, subProp := range p.AsMap() {
   350  			if subProp == nil {
   351  				continue
   352  			}
   353  			subProp.SetLogicalResource(id)
   354  		}
   355  	}
   356  
   357  	if p.IsList() {
   358  		for _, subProp := range p.AsList() {
   359  			subProp.SetLogicalResource(id)
   360  		}
   361  	}
   362  
   363  }
   364  
   365  func (p *Property) GetJsonBytes(squashList ...bool) []byte {
   366  	if p.IsNil() {
   367  		return []byte{}
   368  	}
   369  	lines, err := p.AsRawStrings()
   370  	if err != nil {
   371  		return nil
   372  	}
   373  	if p.ctx.SourceFormat == JsonSourceFormat {
   374  		return []byte(strings.Join(lines, " "))
   375  	}
   376  
   377  	if len(squashList) > 0 {
   378  		lines[0] = strings.Replace(lines[0], "-", " ", 1)
   379  	}
   380  
   381  	lines = removeLeftMargin(lines)
   382  
   383  	yamlContent := strings.Join(lines, "\n")
   384  	var body interface{}
   385  	if err := yaml.Unmarshal([]byte(yamlContent), &body); err != nil {
   386  		return nil
   387  	}
   388  	jsonBody := convert(body)
   389  	policyJson, err := json.Marshal(jsonBody)
   390  	if err != nil {
   391  		return nil
   392  	}
   393  	return policyJson
   394  }
   395  
   396  func (p *Property) GetJsonBytesAsString(squashList ...bool) string {
   397  	return string(p.GetJsonBytes(squashList...))
   398  }
   399  
   400  func removeLeftMargin(lines []string) []string {
   401  	if len(lines) == 0 {
   402  		return lines
   403  	}
   404  	prefixSpace := len(lines[0]) - len(strings.TrimLeft(lines[0], " "))
   405  
   406  	for i, line := range lines {
   407  		if len(line) >= prefixSpace {
   408  			lines[i] = line[prefixSpace:]
   409  		}
   410  	}
   411  	return lines
   412  }
   413  
   414  func convert(input interface{}) interface{} {
   415  	switch x := input.(type) {
   416  	case map[interface{}]interface{}:
   417  		outpMap := map[string]interface{}{}
   418  		for k, v := range x {
   419  			outpMap[k.(string)] = convert(v)
   420  		}
   421  		return outpMap
   422  	case []interface{}:
   423  		for i, v := range x {
   424  			x[i] = convert(v)
   425  		}
   426  	}
   427  	return input
   428  }