diff --git a/pkg/scanners/cloudformation/parser/fn_length.go b/pkg/scanners/cloudformation/parser/fn_length.go new file mode 100644 index 000000000..0c71f1f3e --- /dev/null +++ b/pkg/scanners/cloudformation/parser/fn_length.go @@ -0,0 +1,24 @@ +package parser + +import "github.com/aquasecurity/defsec/pkg/scanners/cloudformation/cftypes" + +func ResolveLength(property *Property) (*Property, bool) { + if !property.isFunction() { + return property, true + } + + val := property.AsMap()["Fn::Length"] + if val.IsList() { + return property.deriveResolved(cftypes.Int, val.Len()), true + } else if val.IsMap() { + resolved, _ := val.resolveValue() + + if resolved.IsList() { + return property.deriveResolved(cftypes.Int, resolved.Len()), true + } + return resolved, false + } + + return property, false + +} diff --git a/pkg/scanners/cloudformation/parser/fn_length_test.go b/pkg/scanners/cloudformation/parser/fn_length_test.go new file mode 100644 index 000000000..c4e3dddc1 --- /dev/null +++ b/pkg/scanners/cloudformation/parser/fn_length_test.go @@ -0,0 +1,99 @@ +package parser + +import ( + "testing" + + "github.com/aquasecurity/defsec/pkg/scanners/cloudformation/cftypes" + "github.com/stretchr/testify/require" +) + +func Test_ResolveLength_WhenPropIsArray(t *testing.T) { + prop := &Property{ + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.Int, + Value: 1, + }, + }, + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "IntParameter", + }, + }, + }, + }, + }, + }, + }, + } + resolved, ok := ResolveIntrinsicFunc(prop) + require.True(t, ok) + require.True(t, resolved.IsInt()) + require.Equal(t, 2, resolved.AsInt()) +} + +func Test_ResolveLength_WhenPropIsIntrinsicFunction(t *testing.T) { + fctx := &FileContext{ + Parameters: map[string]*Parameter{ + "SomeParameter": { + inner: parameterInner{ + Type: "string", + Default: "a|b|c|d", + }, + }, + }, + } + prop := &Property{ + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Inner: PropertyInner{ + Type: cftypes.List, + Value: []*Property{ + { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "|", + }, + }, + { + ctx: fctx, + Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Inner: PropertyInner{ + Type: cftypes.String, + Value: "SomeParameter", + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + }, + } + resolved, ok := ResolveIntrinsicFunc(prop) + require.True(t, ok) + require.True(t, resolved.IsInt()) + require.Equal(t, 4, resolved.AsInt()) +} diff --git a/pkg/scanners/cloudformation/parser/intrinsics.go b/pkg/scanners/cloudformation/parser/intrinsics.go index 877457005..014e37beb 100644 --- a/pkg/scanners/cloudformation/parser/intrinsics.go +++ b/pkg/scanners/cloudformation/parser/intrinsics.go @@ -27,6 +27,7 @@ func init() { "Fn::And": ResolveAnd, "Fn::Or": ResolveOr, "Fn::Not": ResolveNot, + "Fn::Length": ResolveLength, } } diff --git a/pkg/scanners/cloudformation/parser/property_helpers.go b/pkg/scanners/cloudformation/parser/property_helpers.go index 19ebdcc7e..2f6e7ef3e 100644 --- a/pkg/scanners/cloudformation/parser/property_helpers.go +++ b/pkg/scanners/cloudformation/parser/property_helpers.go @@ -166,6 +166,10 @@ func (p *Property) AsList() []*Property { return nil } +func (p *Property) Len() int { + return len(p.AsList()) +} + func (p *Property) EqualTo(checkValue interface{}, equalityOptions ...EqualityOptions) bool { var ignoreCase bool for _, option := range equalityOptions {