mirror of
https://github.com/aquasecurity/trivy.git
synced 2025-12-05 20:40:16 -08:00
Co-authored-by: knqyf263 <knqyf263@users.noreply.github.com> Co-authored-by: nikpivkin <nikita.pivkin@smartforce.io>
461 lines
9.2 KiB
Go
461 lines
9.2 KiB
Go
package parser
|
|
|
|
import (
|
|
"encoding/json/jsontext"
|
|
"encoding/json/v2"
|
|
"fmt"
|
|
"io/fs"
|
|
"reflect"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gopkg.in/yaml.v3"
|
|
|
|
"github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes"
|
|
iacTypes "github.com/aquasecurity/trivy/pkg/iac/types"
|
|
xjson "github.com/aquasecurity/trivy/pkg/x/json"
|
|
)
|
|
|
|
type EqualityOptions = int
|
|
|
|
const (
|
|
IgnoreCase EqualityOptions = iota
|
|
)
|
|
|
|
type Property struct {
|
|
xjson.Location
|
|
ctx *FileContext
|
|
Type cftypes.CfType
|
|
Value any `json:"Value" yaml:"Value"`
|
|
name string
|
|
comment string
|
|
rng iacTypes.Range
|
|
parentRange iacTypes.Range
|
|
logicalId string
|
|
unresolved bool
|
|
}
|
|
|
|
func (p *Property) Comment() string {
|
|
return p.comment
|
|
}
|
|
|
|
func (p *Property) setName(name string) {
|
|
p.name = name
|
|
if p.Type == cftypes.Map {
|
|
for n, subProp := range p.AsMap() {
|
|
if subProp == nil {
|
|
continue
|
|
}
|
|
subProp.setName(n)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Property) setContext(ctx *FileContext) {
|
|
p.ctx = ctx
|
|
|
|
if p.IsMap() {
|
|
for _, subProp := range p.AsMap() {
|
|
if subProp == nil {
|
|
continue
|
|
}
|
|
subProp.setContext(ctx)
|
|
}
|
|
}
|
|
|
|
if p.IsList() {
|
|
for _, subProp := range p.AsList() {
|
|
subProp.setContext(ctx)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRange iacTypes.Range) {
|
|
p.rng = iacTypes.NewRange(filepath, p.StartLine, p.EndLine, p.rng.GetSourcePrefix(), target)
|
|
p.parentRange = parentRange
|
|
|
|
switch p.Type {
|
|
case cftypes.Map:
|
|
for _, subProp := range p.AsMap() {
|
|
if subProp == nil {
|
|
continue
|
|
}
|
|
subProp.setFileAndParentRange(target, filepath, parentRange)
|
|
}
|
|
case cftypes.List:
|
|
for _, subProp := range p.AsList() {
|
|
if subProp == nil {
|
|
continue
|
|
}
|
|
subProp.setFileAndParentRange(target, filepath, parentRange)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Property) UnmarshalYAML(node *yaml.Node) error {
|
|
p.StartLine = node.Line
|
|
p.EndLine = calculateEndLine(node)
|
|
p.comment = node.LineComment
|
|
return setPropertyValueFromYaml(node, p)
|
|
}
|
|
|
|
func (p *Property) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
|
|
var valPtr any
|
|
var nodeType cftypes.CfType
|
|
|
|
switch k := dec.PeekKind(); k {
|
|
case 't', 'f':
|
|
valPtr = new(bool)
|
|
nodeType = cftypes.Bool
|
|
case '"':
|
|
valPtr = new(string)
|
|
nodeType = cftypes.String
|
|
case '0':
|
|
return p.parseNumericValue(dec)
|
|
case '[', 'n':
|
|
valPtr = new([]*Property)
|
|
nodeType = cftypes.List
|
|
case '{':
|
|
valPtr = new(map[string]*Property)
|
|
nodeType = cftypes.Map
|
|
case 0:
|
|
return dec.SkipValue()
|
|
default:
|
|
return fmt.Errorf("unexpected token kind %q at %d", k.String(), dec.InputOffset())
|
|
}
|
|
|
|
if err := json.UnmarshalDecode(dec, valPtr); err != nil {
|
|
return err
|
|
}
|
|
|
|
p.Value = reflect.ValueOf(valPtr).Elem().Interface()
|
|
p.Type = nodeType
|
|
return nil
|
|
}
|
|
|
|
func (p *Property) parseNumericValue(dec *jsontext.Decoder) error {
|
|
raw, err := dec.ReadValue()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
strVal := string(raw)
|
|
|
|
if v, err := strconv.ParseInt(strVal, 10, 64); err == nil {
|
|
p.Value = int(v)
|
|
p.Type = cftypes.Int
|
|
return nil
|
|
}
|
|
if v, err := strconv.ParseFloat(strVal, 64); err == nil {
|
|
p.Value = v
|
|
p.Type = cftypes.Float64
|
|
return nil
|
|
}
|
|
return fmt.Errorf("invalid numeric value: %q", strVal)
|
|
}
|
|
|
|
func (p *Property) Metadata() iacTypes.Metadata {
|
|
return iacTypes.NewMetadata(p.rng, p.name).
|
|
WithParent(iacTypes.NewMetadata(p.parentRange, p.logicalId))
|
|
}
|
|
|
|
func (p *Property) isFunction() bool {
|
|
if p == nil {
|
|
return false
|
|
}
|
|
if p.Type == cftypes.Map {
|
|
for n := range p.AsMap() {
|
|
return IsIntrinsic(n)
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (p *Property) RawValue() any {
|
|
return p.Value
|
|
}
|
|
|
|
func (p *Property) AsRawStrings() ([]string, error) {
|
|
if len(p.ctx.lines) < p.rng.GetEndLine() {
|
|
return p.ctx.lines, nil
|
|
}
|
|
return p.ctx.lines[p.rng.GetStartLine()-1 : p.rng.GetEndLine()], nil
|
|
}
|
|
|
|
func (p *Property) resolveValue() (*Property, bool) {
|
|
if !p.isFunction() || p.IsUnresolved() {
|
|
return p, true
|
|
}
|
|
|
|
resolved, ok := ResolveIntrinsicFunc(p)
|
|
if ok {
|
|
return resolved, true
|
|
}
|
|
|
|
p.unresolved = true
|
|
return p, false
|
|
}
|
|
|
|
func (p *Property) GetStringProperty(path string, defaultValue ...string) iacTypes.StringValue {
|
|
defVal := ""
|
|
if len(defaultValue) > 0 {
|
|
defVal = defaultValue[0]
|
|
}
|
|
|
|
if p.IsUnresolved() {
|
|
return iacTypes.StringUnresolvable(p.Metadata())
|
|
}
|
|
|
|
prop := p.GetProperty(path)
|
|
if prop.IsNotString() {
|
|
return p.StringDefault(defVal)
|
|
}
|
|
return prop.AsStringValue()
|
|
}
|
|
|
|
func (p *Property) StringDefault(defaultValue string) iacTypes.StringValue {
|
|
return iacTypes.StringDefault(defaultValue, p.Metadata())
|
|
}
|
|
|
|
func (p *Property) GetBoolProperty(path string, defaultValue ...bool) iacTypes.BoolValue {
|
|
defVal := false
|
|
if len(defaultValue) > 0 {
|
|
defVal = defaultValue[0]
|
|
}
|
|
|
|
if p.IsUnresolved() {
|
|
return iacTypes.BoolUnresolvable(p.Metadata())
|
|
}
|
|
|
|
prop := p.GetProperty(path)
|
|
|
|
if prop.isFunction() {
|
|
prop, _ = prop.resolveValue()
|
|
}
|
|
|
|
if prop.IsNotBool() {
|
|
return p.inferBool(prop, defVal)
|
|
}
|
|
return prop.AsBoolValue()
|
|
}
|
|
|
|
func (p *Property) GetIntProperty(path string, defaultValue ...int) iacTypes.IntValue {
|
|
defVal := 0
|
|
if len(defaultValue) > 0 {
|
|
defVal = defaultValue[0]
|
|
}
|
|
|
|
if p.IsUnresolved() {
|
|
return iacTypes.IntUnresolvable(p.Metadata())
|
|
}
|
|
|
|
prop := p.GetProperty(path)
|
|
|
|
if prop.IsNotInt() {
|
|
return p.IntDefault(defVal)
|
|
}
|
|
return prop.AsIntValue()
|
|
}
|
|
|
|
func (p *Property) BoolDefault(defaultValue bool) iacTypes.BoolValue {
|
|
return iacTypes.BoolDefault(defaultValue, p.Metadata())
|
|
}
|
|
|
|
func (p *Property) IntDefault(defaultValue int) iacTypes.IntValue {
|
|
return iacTypes.IntDefault(defaultValue, p.Metadata())
|
|
}
|
|
|
|
func (p *Property) GetProperty(path string) *Property {
|
|
pathParts := strings.Split(path, ".")
|
|
|
|
first := pathParts[0]
|
|
property := p
|
|
|
|
if p.isFunction() {
|
|
property, _ = p.resolveValue()
|
|
}
|
|
|
|
if property.IsNotMap() {
|
|
return nil
|
|
}
|
|
|
|
for n, p := range property.AsMap() {
|
|
if n == first {
|
|
property = p
|
|
break
|
|
}
|
|
}
|
|
|
|
if len(pathParts) == 1 || property == nil {
|
|
return property
|
|
}
|
|
|
|
if nestedProperty := property.GetProperty(strings.Join(pathParts[1:], ".")); nestedProperty != nil {
|
|
if nestedProperty.isFunction() {
|
|
resolved, _ := nestedProperty.resolveValue()
|
|
return resolved
|
|
}
|
|
return nestedProperty
|
|
}
|
|
|
|
return &Property{}
|
|
}
|
|
|
|
func (p *Property) deriveResolved(propType cftypes.CfType, propValue any) *Property {
|
|
return &Property{
|
|
Location: p.Location,
|
|
Value: propValue,
|
|
Type: propType,
|
|
ctx: p.ctx,
|
|
name: p.name,
|
|
comment: p.comment,
|
|
rng: p.rng,
|
|
parentRange: p.parentRange,
|
|
logicalId: p.logicalId,
|
|
}
|
|
}
|
|
|
|
func (p *Property) ParentRange() iacTypes.Range {
|
|
return p.parentRange
|
|
}
|
|
|
|
func (p *Property) inferBool(prop *Property, defaultValue bool) iacTypes.BoolValue {
|
|
if prop.IsString() {
|
|
if prop.EqualTo("true", IgnoreCase) {
|
|
return iacTypes.Bool(true, prop.Metadata())
|
|
}
|
|
if prop.EqualTo("yes", IgnoreCase) {
|
|
return iacTypes.Bool(true, prop.Metadata())
|
|
}
|
|
if prop.EqualTo("1", IgnoreCase) {
|
|
return iacTypes.Bool(true, prop.Metadata())
|
|
}
|
|
if prop.EqualTo("false", IgnoreCase) {
|
|
return iacTypes.Bool(false, prop.Metadata())
|
|
}
|
|
if prop.EqualTo("no", IgnoreCase) {
|
|
return iacTypes.Bool(false, prop.Metadata())
|
|
}
|
|
if prop.EqualTo("0", IgnoreCase) {
|
|
return iacTypes.Bool(false, prop.Metadata())
|
|
}
|
|
}
|
|
|
|
if prop.IsInt() {
|
|
if prop.EqualTo(0) {
|
|
return iacTypes.Bool(false, prop.Metadata())
|
|
}
|
|
if prop.EqualTo(1) {
|
|
return iacTypes.Bool(true, prop.Metadata())
|
|
}
|
|
}
|
|
|
|
return p.BoolDefault(defaultValue)
|
|
}
|
|
|
|
func (p *Property) String() string {
|
|
r := ""
|
|
switch p.Type {
|
|
case cftypes.String:
|
|
r = p.AsString()
|
|
case cftypes.Int:
|
|
r = strconv.Itoa(p.AsInt())
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (p *Property) setLogicalResource(id string) {
|
|
p.logicalId = id
|
|
|
|
if p.isFunction() {
|
|
return
|
|
}
|
|
|
|
if p.IsMap() {
|
|
for _, subProp := range p.AsMap() {
|
|
if subProp == nil {
|
|
continue
|
|
}
|
|
subProp.setLogicalResource(id)
|
|
}
|
|
}
|
|
|
|
if p.IsList() {
|
|
for _, subProp := range p.AsList() {
|
|
subProp.setLogicalResource(id)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (p *Property) GetJsonBytes(squashList ...bool) []byte {
|
|
if p.IsNil() {
|
|
return []byte{}
|
|
}
|
|
lines, err := p.AsRawStrings()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
if p.ctx.SourceFormat == JsonSourceFormat {
|
|
return []byte(strings.Join(lines, " "))
|
|
}
|
|
|
|
if len(squashList) > 0 {
|
|
lines[0] = strings.Replace(lines[0], "-", " ", 1)
|
|
}
|
|
|
|
lines = removeLeftMargin(lines)
|
|
|
|
yamlContent := strings.Join(lines, "\n")
|
|
var body any
|
|
if err := yaml.Unmarshal([]byte(yamlContent), &body); err != nil {
|
|
return nil
|
|
}
|
|
jsonBody := convert(body)
|
|
policyJson, err := json.Marshal(jsonBody)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
return policyJson
|
|
}
|
|
|
|
func (p *Property) GetJsonBytesAsString(squashList ...bool) string {
|
|
return string(p.GetJsonBytes(squashList...))
|
|
}
|
|
|
|
func removeLeftMargin(lines []string) []string {
|
|
if len(lines) == 0 {
|
|
return lines
|
|
}
|
|
prefixSpace := len(lines[0]) - len(strings.TrimLeft(lines[0], " "))
|
|
|
|
for i, line := range lines {
|
|
if len(line) >= prefixSpace {
|
|
lines[i] = line[prefixSpace:]
|
|
}
|
|
}
|
|
return lines
|
|
}
|
|
|
|
func convert(input any) any {
|
|
switch x := input.(type) {
|
|
case map[any]any:
|
|
outpMap := make(map[string]any)
|
|
for k, v := range x {
|
|
outpMap[k.(string)] = convert(v)
|
|
}
|
|
return outpMap
|
|
case []any:
|
|
for i, v := range x {
|
|
x[i] = convert(v)
|
|
}
|
|
}
|
|
return input
|
|
}
|
|
|
|
func (p *Property) inferType() {
|
|
typ := cftypes.TypeFromGoValue(p.Value)
|
|
if typ == cftypes.Unknown {
|
|
return
|
|
}
|
|
p.Type = typ
|
|
}
|