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 }