refactor(misconf): switch to x/json (#8719)

Signed-off-by: nikpivkin <nikita.pivkin@smartforce.io>
This commit is contained in:
Nikita Pivkin
2025-04-12 09:11:11 +06:00
committed by GitHub
parent 9a5383e993
commit 195880be60
31 changed files with 787 additions and 1122 deletions

2
go.mod
View File

@@ -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

View File

@@ -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()
})
}

View File

@@ -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,
},
},
},

View File

@@ -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",
},
},
}

View File

@@ -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)

View File

@@ -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",
},
},
},

View File

@@ -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",
},
},
},

View File

@@ -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
}

View File

@@ -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",
},
},
},

View File

@@ -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",
},
},
},
},
},

View File

@@ -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",
},
},
},

View File

@@ -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,
},
},
},

View File

@@ -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,
},
},
},

View File

@@ -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

View File

@@ -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",
},
},
},

View File

@@ -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()

View File

@@ -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, &params); err != nil {
if err := json.UnmarshalDecode(d, &params); 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, &params); err == nil {
jval, err := d.ReadValue()
if err != nil {
return err
}
if err := json.Unmarshal(jval, &params); 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, &parameters); err != nil {
return nil, err
}
return parameters, nil
}

View File

@@ -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), &params)
params, err := ParseParameters(strings.NewReader(tt.source))
if tt.wantErr {
require.Error(t, err)
return

View File

@@ -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(&parameters); err != nil {
return nil, err
}
return parameters, nil
}

View File

@@ -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
}

View File

@@ -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)

View File

@@ -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) {

View File

@@ -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,
},

View File

@@ -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"},

View File

@@ -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
}

View File

@@ -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",
},
},
},

View File

@@ -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

View File

@@ -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
}

View File

@@ -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)
}
}
}

View File

@@ -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{

View File

@@ -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