diff --git a/go.mod b/go.mod index 0423ed47cc..2802b7a7db 100644 --- a/go.mod +++ b/go.mod @@ -20,7 +20,7 @@ require ( github.com/aquasecurity/go-pep440-version v0.0.1 github.com/aquasecurity/go-version v0.0.1 github.com/aquasecurity/iamgo v0.0.10 - github.com/aquasecurity/jfather v0.0.8 + github.com/aquasecurity/jfather v0.0.8 // indirect github.com/aquasecurity/table v1.8.0 github.com/aquasecurity/testdocker v0.0.0-20240730042311-4642e94c7fc8 github.com/aquasecurity/tml v0.6.1 diff --git a/pkg/iac/scanners/cloudformation/parser/file_context.go b/pkg/iac/scanners/cloudformation/parser/file_context.go index e1c8cfa87f..0ff3c18d46 100644 --- a/pkg/iac/scanners/cloudformation/parser/file_context.go +++ b/pkg/iac/scanners/cloudformation/parser/file_context.go @@ -42,7 +42,6 @@ func (t *FileContext) GetResourcesByType(names ...string) []*Resource { for _, r := range t.Resources { for _, name := range names { if name == r.Type() { - // resources = append(resources, r) } } @@ -56,6 +55,7 @@ func (t *FileContext) Metadata() iacTypes.Metadata { return iacTypes.NewMetadata(rng, NewCFReference("Template", rng).String()) } +// TODO: use map[string]string func (t *FileContext) overrideParameters(params map[string]any) { for key := range t.Parameters { if val, ok := params[key]; ok { @@ -76,7 +76,7 @@ func (t *FileContext) missingParameterValues() []string { func (t *FileContext) stripNullProperties() { for _, resource := range t.Resources { - resource.Inner.Properties = lo.OmitBy(resource.Inner.Properties, func(k string, v *Property) bool { + resource.properties = lo.OmitBy(resource.properties, func(k string, v *Property) bool { return v.IsNil() }) } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go index 5222decef0..a014583d5e 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_and_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_and_test.go @@ -7,35 +7,24 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_and_value(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, @@ -43,49 +32,33 @@ func Test_resolve_and_value(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } andProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::And": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::And": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, @@ -100,29 +73,19 @@ func Test_resolve_and_value(t *testing.T) { func Test_resolve_and_value_not_the_same(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -130,49 +93,33 @@ func Test_resolve_and_value_not_the_same(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } andProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::And": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::And": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go index 7fdc77b30f..71a2ba2bdf 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_base64_test.go @@ -7,24 +7,17 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_base64_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Base64": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "HelloWorld", - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Base64": { + Type: cftypes.String, + Value: "HelloWorld", }, }, } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go index c5fbce41b4..bbfa1cbeb3 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_builtin_test.go @@ -13,10 +13,8 @@ func Test_cidr_generator(t *testing.T) { ctx: nil, name: "cidr", comment: "", - Inner: PropertyInner{ - Type: "", - Value: nil, - }, + Type: "", + Value: nil, } ranges, err := calculateCidrs("10.1.0.0/16", 4, 4, original) @@ -40,10 +38,8 @@ func Test_cidr_generator_8_bits(t *testing.T) { ctx: nil, name: "cidr", comment: "", - Inner: PropertyInner{ - Type: "", - Value: nil, - }, + Type: "", + Value: nil, } ranges, err := calculateCidrs("10.1.0.0/16", 4, 8, original) diff --git a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go index c5f0af721c..d4150dcee6 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_condition_test.go @@ -14,78 +14,56 @@ func Test_resolve_condition_value(t *testing.T) { fctx := new(FileContext) fctx.Conditions = map[string]Property{ "SomeCondition": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "some val", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "some val", - }, - }, - }, + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + ctx: fctx, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "some val", + }, + { + Type: cftypes.String, + Value: "some val", }, }, }, }, }, "EnableVersioning": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Condition": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "SomeCondition", - }, - }, + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Condition": { + Type: cftypes.String, + Value: "SomeCondition", }, }, }, } property := &Property{ - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::If": { - ctx: fctx, - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "EnableVersioning", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Enabled", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Suspended", - }, - }, - }, + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + ctx: fctx, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "EnableVersioning", + }, + { + Type: cftypes.String, + Value: "Enabled", + }, + { + Type: cftypes.String, + Value: "Suspended", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go index 0e550d1629..a83dc049d7 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_equals_test.go @@ -7,35 +7,24 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_equals_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, @@ -51,29 +40,19 @@ func Test_resolve_equals_value(t *testing.T) { func Test_resolve_equals_value_to_false(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -89,29 +68,19 @@ func Test_resolve_equals_value_to_false(t *testing.T) { func Test_resolve_equals_value_to_true_when_boolean(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: true, + }, + { + Type: cftypes.Bool, + Value: true, }, }, }, @@ -127,43 +96,31 @@ func Test_resolve_equals_value_when_one_is_a_reference(t *testing.T) { property := &Property{ name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "staging", + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "staging", + }, + { + ctx: &FileContext{ + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", + }, }, }, - { - ctx: &FileContext{ - filepath: "", - Parameters: map[string]*Parameter{ - "Environment": { - inner: parameterInner{ - Type: "string", - Default: "staging", - }, - }, - }, - }, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Environment", - }, - }, - }, - }, + }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "Environment", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go b/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go index f6754d16a9..d3deb3fd49 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_get_attr.go @@ -42,5 +42,5 @@ func ResolveGetAtt(property *Property) (resolved *Property, success bool) { return property.deriveResolved(cftypes.String, referencedResource.ID()), true } - return property.deriveResolved(referencedProperty.Type(), referencedProperty.RawValue()), true + return property.deriveResolved(referencedProperty.Type, referencedProperty.RawValue()), true } diff --git a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go index 4ecd5f9483..f4043adcb5 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_if_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_if_test.go @@ -7,41 +7,28 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_if_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: true, + }, + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go index 628ade9a1a..74df00f4f6 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_join_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_join_test.go @@ -7,52 +7,35 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_join_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Join": { - Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Join": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "::", + }, + { Type: cftypes.List, Value: []*Property{ { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "::", - }, + Type: cftypes.String, + Value: "s3", }, { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "s3", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "part1", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "part2", - }, - }, - }, - }, + Type: cftypes.String, + Value: "part1", + }, + { + Type: cftypes.String, + Value: "part2", }, }, }, @@ -70,7 +53,6 @@ func Test_resolve_join_value_with_reference(t *testing.T) { property := &Property{ ctx: &FileContext{ - filepath: "", Parameters: map[string]*Parameter{ "Environment": { inner: parameterInner{ @@ -81,62 +63,44 @@ func Test_resolve_join_value_with_reference(t *testing.T) { }, }, name: "EnvironmentBucket", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Join": { - Inner: PropertyInner{ + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Join": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "::", + }, + { Type: cftypes.List, Value: []*Property{ { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "::", - }, + Type: cftypes.String, + Value: "s3", }, { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "s3", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "part1", - }, - }, - { - ctx: &FileContext{ - filepath: "", - Parameters: map[string]*Parameter{ - "Environment": { - inner: parameterInner{ - Type: "string", - Default: "staging", - }, - }, - }, - }, - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Environment", - }, - }, - }, + Type: cftypes.String, + Value: "part1", + }, + { + ctx: &FileContext{ + Parameters: map[string]*Parameter{ + "Environment": { + inner: parameterInner{ + Type: "string", + Default: "staging", }, }, }, }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "Environment", + }, + }, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go index 402211bdcd..252a2026ff 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_length_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_length_test.go @@ -10,26 +10,18 @@ import ( 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", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Int, + Value: 1, + }, + { + Type: cftypes.String, + Value: "IntParameter", }, }, }, @@ -53,37 +45,25 @@ func Test_ResolveLength_WhenPropIsIntrinsicFunction(t *testing.T) { }, } 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", - }, - }, - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Length": { + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "|", + }, + { + ctx: fctx, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "SomeParameter", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go index 563964c322..88c673bfde 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_not_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_not_test.go @@ -7,34 +7,23 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_not_value(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -42,19 +31,13 @@ func Test_resolve_not_value(t *testing.T) { } notProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Not": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Not": { + Type: cftypes.List, + Value: []*Property{ + property1, }, }, }, @@ -68,29 +51,19 @@ func Test_resolve_not_value(t *testing.T) { func Test_resolve_not_value_when_true(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, @@ -98,19 +71,13 @@ func Test_resolve_not_value_when_true(t *testing.T) { } notProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Not": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Not": { + Type: cftypes.List, + Value: []*Property{ + property1, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go index 7e0e1dcf0f..69ede90c5f 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_or_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_or_test.go @@ -7,34 +7,23 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func Test_resolve_or_value(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -42,49 +31,33 @@ func Test_resolve_or_value(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } orProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Or": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Or": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, @@ -98,29 +71,19 @@ func Test_resolve_or_value(t *testing.T) { func Test_resolve_or_value_when_neither_true(t *testing.T) { property1 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "bar", }, }, }, @@ -128,49 +91,33 @@ func Test_resolve_or_value_when_neither_true(t *testing.T) { } property2 := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bar", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Equals": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "bar", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, } orProperty := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Or": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - property1, - property2, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Or": { + Type: cftypes.List, + Value: []*Property{ + property1, + property2, }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go index 7f0e141b96..7eca1418fb 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_ref_test.go @@ -14,7 +14,6 @@ func Test_resolve_referenced_value(t *testing.T) { property := &Property{ ctx: &FileContext{ - filepath: "", Parameters: map[string]*Parameter{ "BucketName": { inner: parameterInner{ @@ -25,16 +24,11 @@ func Test_resolve_referenced_value(t *testing.T) { }, }, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Ref": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "BucketName", - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Ref": { + Type: cftypes.String, + Value: "BucketName", }, }, } @@ -48,15 +42,10 @@ func Test_resolve_referenced_value(t *testing.T) { func Test_property_value_correct_when_not_reference(t *testing.T) { property := &Property{ - ctx: &FileContext{ - filepath: "", - }, - name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.String, - Value: "someBucketName", - }, + name: "BucketName", + rng: types.NewRange("testfile", 1, 1, "", nil), + Type: cftypes.String, + Value: "someBucketName", } // should fail when trying to resolve function that is not in fact a function diff --git a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go index 33ca111c15..2cc79c8fbf 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_split_test.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_split_test.go @@ -7,7 +7,6 @@ import ( "github.com/stretchr/testify/require" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" - "github.com/aquasecurity/trivy/pkg/iac/types" ) /* @@ -18,29 +17,19 @@ import ( func Test_resolve_split_value(t *testing.T) { property := &Property{ - ctx: &FileContext{}, name: "BucketName", - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::Split": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "::", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "s3::bucket::to::split", - }, - }, - }, + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::Split": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "::", + }, + { + Type: cftypes.String, + Value: "s3::bucket::to::split", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/fn_sub.go b/pkg/iac/scanners/cloudformation/parser/fn_sub.go index ad990bba3b..8664f406f2 100644 --- a/pkg/iac/scanners/cloudformation/parser/fn_sub.go +++ b/pkg/iac/scanners/cloudformation/parser/fn_sub.go @@ -37,7 +37,7 @@ func resolveMapSub(refValue, original *Property) (*Property, bool) { for k, v := range components { replacement := "[failed to resolve]" - switch v.Type() { + switch v.Type { case cftypes.Map: resolved, _ := ResolveIntrinsicFunc(v) replacement = resolved.AsString() diff --git a/pkg/iac/scanners/cloudformation/parser/parameter.go b/pkg/iac/scanners/cloudformation/parser/parameter.go index 4cfdfd1705..5efa726514 100644 --- a/pkg/iac/scanners/cloudformation/parser/parameter.go +++ b/pkg/iac/scanners/cloudformation/parser/parameter.go @@ -1,20 +1,22 @@ package parser import ( - "bytes" - "encoding/json" "errors" "fmt" + "io" + "maps" "strconv" "strings" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" ) type Parameter struct { + // TODO: remove inner inner parameterInner } @@ -27,27 +29,34 @@ func (p *Parameter) UnmarshalYAML(node *yaml.Node) error { return node.Decode(&p.inner) } -func (p *Parameter) UnmarshalJSONWithMetadata(node jfather.Node) error { +func (p *Parameter) UnmarshalJSONFrom(dec *jsontext.Decoder) error { var inner parameterInner - if err := node.Decode(&inner); err != nil { + if err := json.UnmarshalDecode(dec, &inner, + json.WithUnmarshalers(json.UnmarshalFromFunc(unmarshalIntFirst)), + ); err != nil { return err } - // jfather parses Number without fraction as int64 - // https://github.com/liamg/jfather/blob/4ef05d70c05af167226d3333a4ec7d8ac3c9c281/parse_number.go#L33-L42 - switch v := inner.Default.(type) { - case int64: - inner.Default = int(v) - default: - inner.Default = v - } - p.inner = inner return nil } +func unmarshalIntFirst(dec *jsontext.Decoder, v *any) error { + if dec.PeekKind() == '0' { + if jval, err := dec.ReadValue(); err != nil { + return err + } else if v1, err := strconv.ParseInt(string(jval), 10, 64); err == nil { + *v = int(v1) + } else if v1, err := strconv.ParseFloat(string(jval), 64); err == nil { + *v = v1 + } + return nil + } + return json.SkipFunc +} + func (p *Parameter) Type() cftypes.CfType { switch p.inner.Type { case "Boolean": @@ -83,35 +92,34 @@ func (p *Parameter) UpdateDefault(inVal any) { type Parameters map[string]any func (p *Parameters) Merge(other Parameters) { - for k, v := range other { - (*p)[k] = v - } + maps.Copy((*p), other) } -func (p *Parameters) UnmarshalJSON(data []byte) error { +func (p *Parameters) UnmarshalJSONFrom(d *jsontext.Decoder) error { (*p) = make(Parameters) - if len(data) == 0 { - return nil - } - - switch { - case data[0] == '{' && data[len(data)-1] == '}': // object + switch d.PeekKind() { + case '{': // CodePipeline like format var params struct { Params map[string]any `json:"Parameters"` } - if err := json.Unmarshal(data, ¶ms); err != nil { + if err := json.UnmarshalDecode(d, ¶ms); err != nil { return err } (*p) = params.Params - case data[0] == '[' && data[len(data)-1] == ']': // array + case '[': // Original format var params []string - if err := json.Unmarshal(data, ¶ms); err == nil { + jval, err := d.ReadValue() + if err != nil { + return err + } + + if err := json.Unmarshal(jval, ¶ms); err == nil { for _, param := range params { parts := strings.Split(param, "=") if len(parts) != 2 { @@ -128,9 +136,7 @@ func (p *Parameters) UnmarshalJSON(data []byte) error { ParameterValue string `json:"ParameterValue"` } - d := json.NewDecoder(bytes.NewReader(data)) - d.DisallowUnknownFields() - if err := d.Decode(&cfparams); err != nil { + if err := json.Unmarshal(jval, &cfparams, json.RejectUnknownMembers(true)); err != nil { return err } @@ -143,3 +149,11 @@ func (p *Parameters) UnmarshalJSON(data []byte) error { return nil } + +func ParseParameters(r io.Reader) (Parameters, error) { + var parameters Parameters + if err := json.UnmarshalRead(r, ¶meters); err != nil { + return nil, err + } + return parameters, nil +} diff --git a/pkg/iac/scanners/cloudformation/parser/parameters_test.go b/pkg/iac/scanners/cloudformation/parser/parameters_test.go index 703f07f5fe..320ba9bc6d 100644 --- a/pkg/iac/scanners/cloudformation/parser/parameters_test.go +++ b/pkg/iac/scanners/cloudformation/parser/parameters_test.go @@ -1,7 +1,7 @@ package parser import ( - "encoding/json" + "strings" "testing" "github.com/stretchr/testify/assert" @@ -75,9 +75,7 @@ func TestParameters_UnmarshalJSON(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - var params Parameters - - err := json.Unmarshal([]byte(tt.source), ¶ms) + params, err := ParseParameters(strings.NewReader(tt.source)) if tt.wantErr { require.Error(t, err) return diff --git a/pkg/iac/scanners/cloudformation/parser/parser.go b/pkg/iac/scanners/cloudformation/parser/parser.go index eda88408ec..8ac14b3270 100644 --- a/pkg/iac/scanners/cloudformation/parser/parser.go +++ b/pkg/iac/scanners/cloudformation/parser/parser.go @@ -2,7 +2,6 @@ package parser import ( "context" - "encoding/json" "fmt" "io" "io/fs" @@ -13,9 +12,9 @@ import ( "github.com/hashicorp/go-multierror" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/ignore" "github.com/aquasecurity/trivy/pkg/log" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type Parser struct { @@ -136,7 +135,7 @@ func (p *Parser) ParseFile(ctx context.Context, fsys fs.FS, filePath string) (fc } fctx.Ignores = ignore.Parse(string(content), filePath, "") case JsonSourceFormat: - if err := jfather.Unmarshal(content, fctx); err != nil { + if err := xjson.Unmarshal(content, fctx); err != nil { return nil, NewErrInvalidContent(filePath, err) } } @@ -176,11 +175,18 @@ func (p *Parser) parseParams() error { var errs error for _, path := range p.parameterFiles { - if parameters, err := p.parseParametersFile(path); err != nil { + f, err := p.configsFS.Open(path) + if err != nil { + errs = multierror.Append(errs, fmt.Errorf("open file: %w", err)) + continue + } + + if parameters, err := ParseParameters(f); err != nil { errs = multierror.Append(errs, err) } else { params.Merge(parameters) } + _ = f.Close() } if errs != nil { @@ -192,16 +198,3 @@ func (p *Parser) parseParams() error { p.overridedParameters = params return nil } - -func (p *Parser) parseParametersFile(filePath string) (Parameters, error) { - f, err := p.configsFS.Open(filePath) - if err != nil { - return nil, fmt.Errorf("parameters file %q open error: %w", filePath, err) - } - - var parameters Parameters - if err := json.NewDecoder(f).Decode(¶meters); err != nil { - return nil, err - } - return parameters, nil -} diff --git a/pkg/iac/scanners/cloudformation/parser/property.go b/pkg/iac/scanners/cloudformation/parser/property.go index 606b9d28b3..57d342bb57 100644 --- a/pkg/iac/scanners/cloudformation/parser/property.go +++ b/pkg/iac/scanners/cloudformation/parser/property.go @@ -1,16 +1,19 @@ package parser import ( - "encoding/json" + "fmt" "io/fs" + "reflect" "strconv" "strings" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "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 @@ -20,28 +23,25 @@ const ( ) 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 - Inner PropertyInner logicalId string unresolved bool } -type PropertyInner struct { - Type cftypes.CfType - Value any `json:"Value" yaml:"Value"` -} - func (p *Property) Comment() string { return p.comment } func (p *Property) setName(name string) { p.name = name - if p.Type() == cftypes.Map { + if p.Type == cftypes.Map { for n, subProp := range p.AsMap() { if subProp == nil { continue @@ -71,10 +71,10 @@ func (p *Property) setContext(ctx *FileContext) { } func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRange iacTypes.Range) { - p.rng = iacTypes.NewRange(filepath, p.rng.GetStartLine(), p.rng.GetEndLine(), p.rng.GetSourcePrefix(), target) + p.rng = iacTypes.NewRange(filepath, p.StartLine, p.EndLine, p.rng.GetSourcePrefix(), target) p.parentRange = parentRange - switch p.Type() { + switch p.Type { case cftypes.Map: for _, subProp := range p.AsMap() { if subProp == nil { @@ -93,27 +93,71 @@ func (p *Property) setFileAndParentRange(target fs.FS, filepath string, parentRa } func (p *Property) UnmarshalYAML(node *yaml.Node) error { - p.rng = iacTypes.NewRange("", node.Line, calculateEndLine(node), "", nil) - + p.StartLine = node.Line + p.EndLine = calculateEndLine(node) p.comment = node.LineComment - return setPropertyValueFromYaml(node, &p.Inner) + if err := setPropertyValueFromYaml(node, p); err != nil { + return err + } + return nil } -func (p *Property) UnmarshalJSONWithMetadata(node jfather.Node) error { - p.rng = iacTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil) - return setPropertyValueFromJson(node, &p.Inner) +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) Type() cftypes.CfType { - return p.Inner.Type -} +func (p *Property) parseNumericValue(dec *jsontext.Decoder) error { + raw, err := dec.ReadValue() + if err != nil { + return err + } + strVal := string(raw) -func (p *Property) Range() iacTypes.Range { - return p.rng + 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.Range(), p.name). + return iacTypes.NewMetadata(p.rng, p.name). WithParent(iacTypes.NewMetadata(p.parentRange, p.logicalId)) } @@ -121,7 +165,7 @@ func (p *Property) isFunction() bool { if p == nil { return false } - if p.Type() == cftypes.Map { + if p.Type == cftypes.Map { for n := range p.AsMap() { return IsIntrinsic(n) } @@ -130,7 +174,7 @@ func (p *Property) isFunction() bool { } func (p *Property) RawValue() any { - return p.Inner.Value + return p.Value } func (p *Property) AsRawStrings() ([]string, error) { @@ -264,16 +308,15 @@ func (p *Property) GetProperty(path string) *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, - Inner: PropertyInner{ - Type: propType, - Value: propValue, - }, } } @@ -317,7 +360,7 @@ func (p *Property) inferBool(prop *Property, defaultValue bool) iacTypes.BoolVal func (p *Property) String() string { r := "" - switch p.Type() { + switch p.Type { case cftypes.String: r = p.AsString() case cftypes.Int: @@ -326,7 +369,7 @@ func (p *Property) String() string { return r } -func (p *Property) SetLogicalResource(id string) { +func (p *Property) setLogicalResource(id string) { p.logicalId = id if p.isFunction() { @@ -338,13 +381,13 @@ func (p *Property) SetLogicalResource(id string) { if subProp == nil { continue } - subProp.SetLogicalResource(id) + subProp.setLogicalResource(id) } } if p.IsList() { for _, subProp := range p.AsList() { - subProp.SetLogicalResource(id) + subProp.setLogicalResource(id) } } @@ -416,9 +459,9 @@ func convert(input any) any { } func (p *Property) inferType() { - typ := cftypes.TypeFromGoValue(p.Inner.Value) + typ := cftypes.TypeFromGoValue(p.Value) if typ == cftypes.Unknown { return } - p.Inner.Type = typ + p.Type = typ } diff --git a/pkg/iac/scanners/cloudformation/parser/property_conversion.go b/pkg/iac/scanners/cloudformation/parser/property_conversion.go index 1053afa1b3..66a3ff8f0f 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_conversion.go +++ b/pkg/iac/scanners/cloudformation/parser/property_conversion.go @@ -26,7 +26,7 @@ func (p *Property) IsConvertableTo(conversionType cftypes.CfType) bool { } func (p *Property) isConvertableToString() bool { - switch p.Type() { + switch p.Type { case cftypes.Map: return false case cftypes.List: @@ -40,7 +40,7 @@ func (p *Property) isConvertableToString() bool { } func (p *Property) isConvertableToBool() bool { - switch p.Type() { + switch p.Type { case cftypes.String: return p.EqualTo("true", IgnoreCase) || p.EqualTo("false", IgnoreCase) || p.EqualTo("1", IgnoreCase) || p.EqualTo("0", IgnoreCase) @@ -54,7 +54,7 @@ func (p *Property) isConvertableToBool() bool { } func (p *Property) isConvertableToInt() bool { - switch p.Type() { + switch p.Type { case cftypes.String: if _, err := strconv.Atoi(p.AsString()); err == nil { return true @@ -70,15 +70,15 @@ func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { return nil } - if p.Type() == conversionType { + if p.Type == conversionType { return p } if !p.IsConvertableTo(conversionType) { log.Debug("Failed to convert property", - log.String("from", string(p.Type())), + log.String("from", string(p.Type)), log.String("to", string(conversionType)), - log.Any("range", p.Range().String()), + log.Any("range", p.rng.String()), ) return p } @@ -94,7 +94,7 @@ func (p *Property) ConvertTo(conversionType cftypes.CfType) *Property { } func (p *Property) convertToString() *Property { - switch p.Type() { + switch p.Type { case cftypes.Int: return p.deriveResolved(cftypes.String, strconv.Itoa(p.AsInt())) case cftypes.Bool: @@ -110,7 +110,7 @@ func (p *Property) convertToString() *Property { } func (p *Property) convertToBool() *Property { - switch p.Type() { + switch p.Type { case cftypes.String: if p.EqualTo("true", IgnoreCase) || p.EqualTo("1") { return p.deriveResolved(cftypes.Bool, true) @@ -130,8 +130,7 @@ func (p *Property) convertToBool() *Property { } func (p *Property) convertToInt() *Property { - // - switch p.Type() { + switch p.Type { case cftypes.String: if val, err := strconv.Atoi(p.AsString()); err == nil { return p.deriveResolved(cftypes.Int, val) diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers.go b/pkg/iac/scanners/cloudformation/parser/property_helpers.go index 868f4d9231..2887b68bf5 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers.go @@ -9,7 +9,7 @@ import ( ) func (p *Property) IsNil() bool { - return p == nil || p.Inner.Value == nil + return p == nil || p.Value == nil } func (p *Property) IsNotNil() bool { @@ -25,7 +25,7 @@ func (p *Property) Is(t cftypes.CfType) bool { return prop.Is(t) } } - return p.Inner.Type == t + return p.Type == t } func (p *Property) IsString() bool { @@ -48,7 +48,7 @@ func (p *Property) IsMap() bool { if p.IsNil() || p.IsUnresolved() { return false } - return p.Inner.Type == cftypes.Map + return p.Type == cftypes.Map } func (p *Property) IsNotMap() bool { @@ -89,7 +89,7 @@ func (p *Property) AsString() string { return "" } - return p.Inner.Value.(string) + return p.Value.(string) } func (p *Property) AsStringValue() iacTypes.StringValue { @@ -113,7 +113,7 @@ func (p *Property) AsInt() int { return 0 } - return p.Inner.Value.(int) + return p.Value.(int) } func (p *Property) AsIntValue() iacTypes.IntValue { @@ -133,7 +133,7 @@ func (p *Property) AsBool() bool { if !p.IsBool() { return false } - return p.Inner.Value.(bool) + return p.Value.(bool) } func (p *Property) AsBoolValue() iacTypes.BoolValue { @@ -144,7 +144,7 @@ func (p *Property) AsBoolValue() iacTypes.BoolValue { } func (p *Property) AsMap() map[string]*Property { - val, ok := p.Inner.Value.(map[string]*Property) + val, ok := p.Value.(map[string]*Property) if !ok { return nil } @@ -159,7 +159,7 @@ func (p *Property) AsList() []*Property { return []*Property{} } - if list, ok := p.Inner.Value.([]*Property); ok { + if list, ok := p.Value.([]*Property); ok { return list } return nil @@ -183,23 +183,23 @@ func (p *Property) EqualTo(checkValue any, equalityOptions ...EqualityOptions) b return false } - if p.Inner.Type == cftypes.String || p.IsString() { + if p.Type == cftypes.String || p.IsString() { if ignoreCase { return strings.EqualFold(p.AsString(), checkerVal) } return p.AsString() == checkerVal - } else if p.Inner.Type == cftypes.Int || p.IsInt() { + } else if p.Type == cftypes.Int || p.IsInt() { if val, err := strconv.Atoi(checkerVal); err == nil { return p.AsInt() == val } } return false case bool: - if p.Inner.Type == cftypes.Bool || p.IsBool() { + if p.Type == cftypes.Bool || p.IsBool() { return p.AsBool() == checkerVal } case int: - if p.Inner.Type == cftypes.Int || p.IsInt() { + if p.Type == cftypes.Int || p.IsInt() { return p.AsInt() == checkerVal } } @@ -225,7 +225,7 @@ func (p *Property) IsEmpty() bool { return false } - switch p.Inner.Type { + switch p.Type { case cftypes.String: return p.AsString() == "" case cftypes.List, cftypes.Map: @@ -240,7 +240,7 @@ func (p *Property) Contains(checkVal any) bool { return false } - switch p.Type() { + switch p.Type { case cftypes.List: for _, p := range p.AsList() { if p.EqualTo(checkVal) { diff --git a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go index 36f2cba892..69e3290ad6 100644 --- a/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go +++ b/pkg/iac/scanners/cloudformation/parser/property_helpers_test.go @@ -9,15 +9,6 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/types" ) -func newProp(inner PropertyInner) *Property { - return &Property{ - name: "test_prop", - ctx: &FileContext{}, - rng: types.NewRange("testfile", 1, 1, "", nil), - Inner: inner, - } -} - func Test_EqualTo(t *testing.T) { tests := []struct { name string @@ -34,155 +25,136 @@ func Test_EqualTo(t *testing.T) { }, { name: "compare strings", - property: newProp(PropertyInner{ + property: &Property{ + name: "test_prop", + ctx: &FileContext{}, + rng: types.NewRange("testfile", 1, 1, "", nil), Type: cftypes.String, Value: "is str", - }), + }, checkValue: "is str", isEqual: true, }, { name: "compare strings ignoring case", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.String, Value: "is str", - }), + }, opts: []EqualityOptions{IgnoreCase}, checkValue: "Is StR", isEqual: true, }, { name: "strings ate not equal", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.String, Value: "some value", - }), + }, checkValue: "some other value", isEqual: false, }, { name: "compare prop with a int represented by a string", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Int, Value: 147, - }), + }, checkValue: "147", isEqual: true, }, { name: "compare ints", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Int, Value: 701, - }), + }, checkValue: 701, isEqual: true, }, { name: "compare bools", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Bool, Value: true, - }), + }, checkValue: true, isEqual: true, }, { name: "prop is string fn", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Map, Value: map[string]*Property{ "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: false, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "bad", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "some value", - }, - }, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: false, + }, + { + Type: cftypes.String, + Value: "bad", + }, + { + Type: cftypes.String, + Value: "some value", }, }, }, }, - }), + }, checkValue: "some value", isEqual: true, }, { name: "prop is int fn", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Map, Value: map[string]*Property{ "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: true, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Int, - Value: 121, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Int, - Value: -1, - }, - }, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: true, + }, + { + Type: cftypes.Int, + Value: 121, + }, + { + Type: cftypes.Int, + Value: -1, }, }, }, }, - }), + }, checkValue: 121, isEqual: true, }, { name: "prop is bool fn", - property: newProp(PropertyInner{ + property: &Property{ Type: cftypes.Map, Value: map[string]*Property{ "Fn::Equals": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "foo", - }, - }, + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.String, + Value: "foo", + }, + { + Type: cftypes.String, + Value: "foo", }, }, }, }, - }), + }, checkValue: true, isEqual: true, }, diff --git a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go index 814ba52ee6..243b7ca348 100644 --- a/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go +++ b/pkg/iac/scanners/cloudformation/parser/pseudo_parameters.go @@ -16,16 +16,12 @@ var pseudoParameters = map[string]pseudoParameter{ t: cftypes.List, val: []*Property{ { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "notification::arn::1", - }, + Type: cftypes.String, + Value: "notification::arn::1", }, { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "notification::arn::2", - }, + Type: cftypes.String, + Value: "notification::arn::2", }, }, raw: []string{"notification::arn::1", "notification::arn::2"}, diff --git a/pkg/iac/scanners/cloudformation/parser/resource.go b/pkg/iac/scanners/cloudformation/parser/resource.go index b5910c144b..548bd86f7e 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource.go +++ b/pkg/iac/scanners/cloudformation/parser/resource.go @@ -4,23 +4,22 @@ import ( "io/fs" "strings" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type Resource struct { - ctx *FileContext - rng iacTypes.Range - id string - comment string - Inner ResourceInner -} - -type ResourceInner struct { - Type string `json:"Type" yaml:"Type"` - Properties map[string]*Property `json:"Properties" yaml:"Properties"` + xjson.Location + typ string + properties map[string]*Property + ctx *FileContext + rng iacTypes.Range + id string + comment string } func (r *Resource) configureResource(id string, target fs.FS, filepath string, ctx *FileContext) { @@ -32,15 +31,15 @@ func (r *Resource) configureResource(id string, target fs.FS, filepath string, c func (r *Resource) setId(id string) { r.id = id - for n, p := range r.properties() { + for n, p := range r.properties { p.setName(n) } } func (r *Resource) setFile(target fs.FS, filepath string) { - r.rng = iacTypes.NewRange(filepath, r.rng.GetStartLine(), r.rng.GetEndLine(), r.rng.GetSourcePrefix(), target) + r.rng = iacTypes.NewRange(filepath, r.StartLine, r.EndLine, r.rng.GetSourcePrefix(), target) - for _, p := range r.Inner.Properties { + for _, p := range r.properties { p.setFileAndParentRange(target, filepath, r.rng) } } @@ -48,21 +47,39 @@ func (r *Resource) setFile(target fs.FS, filepath string) { func (r *Resource) setContext(ctx *FileContext) { r.ctx = ctx - for _, p := range r.Inner.Properties { - p.SetLogicalResource(r.id) + for _, p := range r.properties { + p.setLogicalResource(r.id) p.setContext(ctx) } } -func (r *Resource) UnmarshalYAML(value *yaml.Node) error { - r.rng = iacTypes.NewRange("", value.Line-1, calculateEndLine(value), "", nil) - r.comment = value.LineComment - return value.Decode(&r.Inner) +type resourceInner struct { + Type string `json:"Type" yaml:"Type"` + Properties map[string]*Property `json:"Properties" yaml:"Properties"` } -func (r *Resource) UnmarshalJSONWithMetadata(node jfather.Node) error { - r.rng = iacTypes.NewRange("", node.Range().Start.Line, node.Range().End.Line, "", nil) - return node.Decode(&r.Inner) +func (r *Resource) UnmarshalYAML(node *yaml.Node) error { + r.StartLine = node.Line - 1 + r.EndLine = calculateEndLine(node) + r.comment = node.LineComment + + var i resourceInner + if err := node.Decode(&i); err != nil { + return err + } + r.typ = i.Type + r.properties = i.Properties + return nil +} + +func (r *Resource) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + var i resourceInner + if err := json.UnmarshalDecode(dec, &i); err != nil { + return err + } + r.typ = i.Type + r.properties = i.Properties + return nil } func (r *Resource) ID() string { @@ -70,7 +87,7 @@ func (r *Resource) ID() string { } func (r *Resource) Type() string { - return r.Inner.Type + return r.typ } func (r *Resource) Range() iacTypes.Range { @@ -85,10 +102,6 @@ func (r *Resource) Metadata() iacTypes.Metadata { return iacTypes.NewMetadata(r.Range(), NewCFReference(r.id, r.rng).String()) } -func (r *Resource) properties() map[string]*Property { - return r.Inner.Properties -} - func (r *Resource) IsNil() bool { return r.id == "" } @@ -100,7 +113,7 @@ func (r *Resource) GetProperty(path string) *Property { first := pathParts[0] property := &Property{} - if p, exists := r.properties()[first]; exists { + if p, exists := r.properties[first]; exists { property = p } diff --git a/pkg/iac/scanners/cloudformation/parser/resource_test.go b/pkg/iac/scanners/cloudformation/parser/resource_test.go index 1b67fc1d77..a202b7eff7 100644 --- a/pkg/iac/scanners/cloudformation/parser/resource_test.go +++ b/pkg/iac/scanners/cloudformation/parser/resource_test.go @@ -10,55 +10,37 @@ import ( func Test_GetProperty_PropIsFunction(t *testing.T) { resource := Resource{ - Inner: ResourceInner{ - Type: "AWS::S3::Bucket", - Properties: map[string]*Property{ - "BucketName": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "mybucket", - }, - }, - "VersioningConfiguration": { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Fn::If": { - Inner: PropertyInner{ - Type: cftypes.List, - Value: []*Property{ - { - Inner: PropertyInner{ - Type: cftypes.Bool, - Value: false, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Status": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Enabled", - }, - }, - }, - }, - }, - { - Inner: PropertyInner{ - Type: cftypes.Map, - Value: map[string]*Property{ - "Status": { - Inner: PropertyInner{ - Type: cftypes.String, - Value: "Suspended", - }, - }, - }, - }, - }, + typ: "AWS::S3::Bucket", + properties: map[string]*Property{ + "BucketName": { + Type: cftypes.String, + Value: "mybucket", + }, + "VersioningConfiguration": { + Type: cftypes.Map, + Value: map[string]*Property{ + "Fn::If": { + Type: cftypes.List, + Value: []*Property{ + { + Type: cftypes.Bool, + Value: false, + }, + { + Type: cftypes.Map, + Value: map[string]*Property{ + "Status": { + Type: cftypes.String, + Value: "Enabled", + }, + }, + }, + { + Type: cftypes.Map, + Value: map[string]*Property{ + "Status": { + Type: cftypes.String, + Value: "Suspended", }, }, }, diff --git a/pkg/iac/scanners/cloudformation/parser/util.go b/pkg/iac/scanners/cloudformation/parser/util.go index c6babcb7a8..14d60c12d4 100644 --- a/pkg/iac/scanners/cloudformation/parser/util.go +++ b/pkg/iac/scanners/cloudformation/parser/util.go @@ -5,58 +5,11 @@ import ( "gopkg.in/yaml.v3" - "github.com/aquasecurity/jfather" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/cftypes" "github.com/aquasecurity/trivy/pkg/iac/scanners/kubernetes/parser" ) -func setPropertyValueFromJson(node jfather.Node, propertyData *PropertyInner) error { - - switch node.Kind() { - case jfather.KindNumber: - var val any - if err := node.Decode(&val); err != nil { - return err - } - switch v := val.(type) { - case float64: - propertyData.Type = cftypes.Float64 - propertyData.Value = v - case int64: - propertyData.Type = cftypes.Int - propertyData.Value = int(v) - } - return nil - case jfather.KindBoolean: - propertyData.Type = cftypes.Bool - return node.Decode(&propertyData.Value) - case jfather.KindString: - propertyData.Type = cftypes.String - return node.Decode(&propertyData.Value) - case jfather.KindObject: - var childData map[string]*Property - if err := node.Decode(&childData); err != nil { - return err - } - propertyData.Type = cftypes.Map - propertyData.Value = childData - return nil - case jfather.KindArray: - var childData []*Property - if err := node.Decode(&childData); err != nil { - return err - } - propertyData.Type = cftypes.List - propertyData.Value = childData - return nil - default: - propertyData.Type = cftypes.String - return node.Decode(&propertyData.Value) - } - -} - -func setPropertyValueFromYaml(node *yaml.Node, propertyData *PropertyInner) error { +func setPropertyValueFromYaml(node *yaml.Node, propertyData *Property) error { if IsIntrinsicFunc(node) { var newContent []*yaml.Node diff --git a/pkg/iac/scanners/kubernetes/parser/manifest.go b/pkg/iac/scanners/kubernetes/parser/manifest.go index 72d471ee9d..1b9d18e1f5 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest.go @@ -1,15 +1,11 @@ package parser import ( - "bytes" - "errors" "fmt" - "io" - "reflect" - "github.com/go-json-experiment/json" - "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" + + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type Manifest struct { @@ -17,6 +13,16 @@ type Manifest struct { Content *ManifestNode } +func NewManifest(path string, root *ManifestNode) *Manifest { + root.Walk(func(n *ManifestNode) { + n.Path = path + }) + return &Manifest{ + Path: path, + Content: root, + } +} + func (m *Manifest) UnmarshalYAML(value *yaml.Node) error { switch value.Tag { @@ -39,66 +45,11 @@ func (m *Manifest) ToRego() any { } func ManifestFromJSON(path string, data []byte) (*Manifest, error) { - root := &ManifestNode{ - Path: path, - } + var root = &ManifestNode{} - if err := json.Unmarshal(data, root, json.WithUnmarshalers( - json.UnmarshalFromFunc(func(dec *jsontext.Decoder, node *ManifestNode) error { - startOffset := dec.InputOffset() - if err := unmarshalManifestNode(dec, node); err != nil { - return err - } - endOffset := dec.InputOffset() - node.StartLine = 1 + countLines(data, int(startOffset)) - node.EndLine = 1 + countLines(data, int(endOffset)) - node.Path = path - return nil - })), - ); err != nil && !errors.Is(err, io.EOF) { + if err := xjson.Unmarshal(data, root); err != nil { return nil, err } - return &Manifest{ - Path: path, - Content: root, - }, nil -} - -func unmarshalManifestNode(dec *jsontext.Decoder, node *ManifestNode) error { - var valPtr any - var nodeType TagType - switch k := dec.PeekKind(); k { - case 't', 'f': - valPtr = new(bool) - nodeType = TagBool - case '"': - nodeType = TagStr - valPtr = new(string) - case '0': - nodeType = TagInt - valPtr = new(uint64) - case '[', 'n': - valPtr = new([]*ManifestNode) - nodeType = TagSlice - case '{': - valPtr = new(map[string]*ManifestNode) - nodeType = TagMap - 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 - } - - node.Value = reflect.ValueOf(valPtr).Elem().Interface() - node.Type = nodeType - return nil -} - -func countLines(data []byte, offset int) int { - return bytes.Count(data[:offset], []byte("\n")) + return NewManifest(path, root), nil } diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_node.go b/pkg/iac/scanners/kubernetes/parser/manifest_node.go index 4a047b2643..17e0be6b18 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_node.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_node.go @@ -3,12 +3,16 @@ package parser import ( "encoding/base64" "fmt" + "reflect" "strconv" "time" + "github.com/go-json-experiment/json" + "github.com/go-json-experiment/json/jsontext" "gopkg.in/yaml.v3" "github.com/aquasecurity/trivy/pkg/log" + xjson "github.com/aquasecurity/trivy/pkg/x/json" ) type TagType string @@ -26,42 +30,37 @@ const ( ) type ManifestNode struct { - StartLine int - EndLine int - Offset int - Value any - Type TagType - Path string + xjson.Location + Offset int + Value any + Type TagType + Path string } -func (r *ManifestNode) ToRego() any { - if r == nil { +func (n *ManifestNode) ToRego() any { + if n == nil { return nil } - switch r.Type { + switch n.Type { case TagBool, TagInt, TagFloat, TagString, TagStr, TagBinary: - return r.Value + return n.Value case TagTimestamp: - t, ok := r.Value.(time.Time) + t, ok := n.Value.(time.Time) if !ok { return nil } return t.Format(time.RFC3339) case TagSlice: var output []any - for _, node := range r.Value.([]*ManifestNode) { + for _, node := range n.Value.([]*ManifestNode) { output = append(output, node.ToRego()) } return output case TagMap: - output := make(map[string]any) - output["__defsec_metadata"] = map[string]any{ - "startline": r.StartLine, - "endline": r.EndLine, - "filepath": r.Path, - "offset": r.Offset, + output := map[string]any{ + "__defsec_metadata": n.metadata(), } - for key, node := range r.Value.(map[string]*ManifestNode) { + for key, node := range n.Value.(map[string]*ManifestNode) { output[key] = node.ToRego() } return output @@ -69,64 +68,73 @@ func (r *ManifestNode) ToRego() any { return nil } -func (r *ManifestNode) UnmarshalYAML(node *yaml.Node) error { - r.StartLine = node.Line - r.EndLine = node.Line - r.Type = TagType(node.Tag) +func (n *ManifestNode) metadata() map[string]any { + return map[string]any{ + "startline": n.StartLine, + "endline": n.EndLine, + "filepath": n.Path, + "offset": n.Offset, + } +} + +func (n *ManifestNode) UnmarshalYAML(node *yaml.Node) error { + n.StartLine = node.Line + n.EndLine = node.Line + n.Type = TagType(node.Tag) switch TagType(node.Tag) { case TagString, TagStr: - r.Value = node.Value + n.Value = node.Value case TagInt: val, err := strconv.Atoi(node.Value) if err != nil { return fmt.Errorf("failed to parse int: %w", err) } - r.Value = val + n.Value = val case TagFloat: val, err := strconv.ParseFloat(node.Value, 64) if err != nil { return fmt.Errorf("failed to parse float: %w", err) } - r.Value = val + n.Value = val case TagBool: val, err := strconv.ParseBool(node.Value) if err != nil { return fmt.Errorf("failed to parse bool: %w", err) } - r.Value = val + n.Value = val case TagTimestamp: var val time.Time if err := node.Decode(&val); err != nil { return fmt.Errorf("failed to decode timestamp: %w", err) } - r.Value = val + n.Value = val case TagBinary: val, err := base64.StdEncoding.DecodeString(node.Value) if err != nil { return fmt.Errorf("failed to decode binary data: %w", err) } - r.Value = val + n.Value = val case TagMap: - return r.handleMapTag(node) + return n.handleMapTag(node) case TagSlice: - return r.handleSliceTag(node) + return n.handleSliceTag(node) default: log.WithPrefix("k8s").Debug("Skipping unsupported node tag", log.String("tag", node.Tag), - log.FilePath(r.Path), + log.FilePath(n.Path), log.Int("line", node.Line), ) } return nil } -func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { +func (n *ManifestNode) handleSliceTag(node *yaml.Node) error { var nodes []*ManifestNode maxLine := node.Line for _, contentNode := range node.Content { newNode := new(ManifestNode) - newNode.Path = r.Path + newNode.Path = n.Path if err := contentNode.Decode(newNode); err != nil { return err } @@ -135,8 +143,8 @@ func (r *ManifestNode) handleSliceTag(node *yaml.Node) error { } nodes = append(nodes, newNode) } - r.EndLine = maxLine - r.Value = nodes + n.EndLine = maxLine + n.Value = nodes return nil } @@ -163,3 +171,51 @@ func (r *ManifestNode) handleMapTag(node *yaml.Node) error { r.Value = output return nil } + +func (n *ManifestNode) UnmarshalJSONFrom(dec *jsontext.Decoder) error { + var valPtr any + var nodeType TagType + switch k := dec.PeekKind(); k { + case 't', 'f': + valPtr = new(bool) + nodeType = TagBool + case '"': + nodeType = TagStr + valPtr = new(string) + case '0': + nodeType = TagInt + valPtr = new(uint64) + case '[', 'n': + valPtr = new([]*ManifestNode) + nodeType = TagSlice + case '{': + valPtr = new(map[string]*ManifestNode) + nodeType = TagMap + 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 + } + + n.Value = reflect.ValueOf(valPtr).Elem().Interface() + n.Type = nodeType + return nil +} + +func (n *ManifestNode) Walk(f func(n *ManifestNode)) { + f(n) + switch n.Type { + case TagSlice: + for _, node := range n.Value.([]*ManifestNode) { + node.Walk(f) + } + case TagMap: + for _, node := range n.Value.(map[string]*ManifestNode) { + node.Walk(f) + } + } +} diff --git a/pkg/iac/scanners/kubernetes/parser/manifest_test.go b/pkg/iac/scanners/kubernetes/parser/manifest_test.go index fd1d34f3a9..0a97abcdfb 100644 --- a/pkg/iac/scanners/kubernetes/parser/manifest_test.go +++ b/pkg/iac/scanners/kubernetes/parser/manifest_test.go @@ -66,7 +66,7 @@ func TestJsonManifestToRego(t *testing.T) { "__defsec_metadata": map[string]any{ "filepath": filePath, "offset": 0, - "startline": 8, + "startline": 9, "endline": 17, }, "command": []any{ diff --git a/pkg/iac/scanners/kubernetes/parser/parser.go b/pkg/iac/scanners/kubernetes/parser/parser.go index 5c6b2ba3fe..187957d26d 100644 --- a/pkg/iac/scanners/kubernetes/parser/parser.go +++ b/pkg/iac/scanners/kubernetes/parser/parser.go @@ -1,6 +1,7 @@ package parser import ( + "bytes" "context" "fmt" "io" @@ -20,7 +21,7 @@ func Parse(_ context.Context, r io.Reader, path string) ([]any, error) { return nil, nil } - if strings.TrimSpace(string(contents))[0] == '{' { + if bytes.TrimSpace(contents)[0] == '{' { manifest, err := ManifestFromJSON(path, contents) if err != nil { return nil, err