Skip to content

Commit

Permalink
feat: Add state.Merge() for merging two states (#965)
Browse files Browse the repository at this point in the history
Signed-off-by: Liam Galvin <[email protected]>
  • Loading branch information
liamg authored Sep 29, 2022
1 parent 078532c commit fb009fa
Show file tree
Hide file tree
Showing 3 changed files with 386 additions and 2 deletions.
43 changes: 43 additions & 0 deletions pkg/state/merge.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package state

import (
"reflect"
)

// Merge merges the states of the services that have been scanned into a single state.
// if a service has data on both a and b, the service data from b will be preferred.
func (a *State) Merge(b *State) (*State, error) {
var output State

aVal := reflect.ValueOf(a).Elem()
bVal := reflect.ValueOf(b).Elem()
outputVal := reflect.ValueOf(&output).Elem()

stateType := reflect.ValueOf(a).Elem().Type()
for i := 0; i < stateType.NumField(); i++ {
field := stateType.Field(i)
if !field.IsExported() {
continue
}
if field.Type.Kind() != reflect.Struct {
continue
}
for j := 0; j < field.Type.NumField(); j++ {
serviceField := field.Type.Field(j)
if !serviceField.IsExported() {
continue
}
if serviceField.Type.Kind() != reflect.Struct {
continue
}
if !bVal.Field(i).Field(j).IsZero() {
outputVal.Field(i).Field(j).Set(bVal.Field(i).Field(j))
} else {
outputVal.Field(i).Field(j).Set(aVal.Field(i).Field(j))
}
}
}

normalised := outputVal.Interface().(State)
return &normalised, nil
}
341 changes: 341 additions & 0 deletions pkg/state/merge_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,341 @@
package state

import (
"testing"

"github.com/aquasecurity/defsec/pkg/providers/aws/ec2"

defsecTypes "github.com/aquasecurity/defsec/pkg/types"

"github.com/aquasecurity/defsec/pkg/providers/aws"
"github.com/aquasecurity/defsec/pkg/providers/aws/rds"

"github.com/stretchr/testify/assert"
)

func Test_Merging(t *testing.T) {
tests := []struct {
name string
a, b, expected State
}{
{
name: "both empty",
},
{
name: "a empty, b has a service",
b: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
expected: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
},
{
name: "b empty, a has a service",
a: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
expected: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
},
{
name: "both have differing versions of same service",
a: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
b: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever:B", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere:B", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere:B", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
expected: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever:B", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere:B", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere:B", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
},
{
name: "each has a different service",
a: State{
AWS: aws.AWS{
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
b: State{
AWS: aws.AWS{
EC2: ec2.EC2{
Instances: []ec2.Instance{
{
Metadata: defsecTypes.Metadata{},
MetadataOptions: ec2.MetadataOptions{
Metadata: defsecTypes.Metadata{},
HttpTokens: defsecTypes.String("something", defsecTypes.Metadata{}),
HttpEndpoint: defsecTypes.String("something", defsecTypes.Metadata{}),
},
UserData: defsecTypes.String("something", defsecTypes.Metadata{}),
SecurityGroups: []ec2.SecurityGroup{
{
Metadata: defsecTypes.Metadata{},
IsDefault: defsecTypes.Bool(true, defsecTypes.Metadata{}),
Description: defsecTypes.String("something", defsecTypes.Metadata{}),
IngressRules: []ec2.SecurityGroupRule{
{
Metadata: defsecTypes.Metadata{},
Description: defsecTypes.String("something", defsecTypes.Metadata{}),
CIDRs: []defsecTypes.StringValue{
defsecTypes.String("something", defsecTypes.Metadata{}),
},
},
},
EgressRules: nil,
VPCID: defsecTypes.String("something", defsecTypes.Metadata{}),
},
},
RootBlockDevice: &ec2.BlockDevice{
Metadata: defsecTypes.Metadata{},
Encrypted: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
EBSBlockDevices: []*ec2.BlockDevice{
{
Metadata: defsecTypes.Metadata{},
Encrypted: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
},
},
expected: State{
AWS: aws.AWS{
EC2: ec2.EC2{
Instances: []ec2.Instance{
{
Metadata: defsecTypes.Metadata{},
MetadataOptions: ec2.MetadataOptions{
Metadata: defsecTypes.Metadata{},
HttpTokens: defsecTypes.String("something", defsecTypes.Metadata{}),
HttpEndpoint: defsecTypes.String("something", defsecTypes.Metadata{}),
},
UserData: defsecTypes.String("something", defsecTypes.Metadata{}),
SecurityGroups: []ec2.SecurityGroup{
{
Metadata: defsecTypes.Metadata{},
IsDefault: defsecTypes.Bool(true, defsecTypes.Metadata{}),
Description: defsecTypes.String("something", defsecTypes.Metadata{}),
IngressRules: []ec2.SecurityGroupRule{
{
Metadata: defsecTypes.Metadata{},
Description: defsecTypes.String("something", defsecTypes.Metadata{}),
CIDRs: []defsecTypes.StringValue{
defsecTypes.String("something", defsecTypes.Metadata{}),
},
},
},
EgressRules: nil,
VPCID: defsecTypes.String("something", defsecTypes.Metadata{}),
},
},
RootBlockDevice: &ec2.BlockDevice{
Metadata: defsecTypes.Metadata{},
Encrypted: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
EBSBlockDevices: []*ec2.BlockDevice{
{
Metadata: defsecTypes.Metadata{},
Encrypted: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
RDS: rds.RDS{
Instances: []rds.Instance{
{
BackupRetentionPeriodDays: defsecTypes.Int(1, defsecTypes.Metadata{}),
ReplicationSourceARN: defsecTypes.String("arn:whatever", defsecTypes.Metadata{}),
PerformanceInsights: rds.PerformanceInsights{
Metadata: defsecTypes.Metadata{},
Enabled: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
Encryption: rds.Encryption{
Metadata: defsecTypes.Metadata{},
EncryptStorage: defsecTypes.Bool(true, defsecTypes.Metadata{}),
KMSKeyID: defsecTypes.String("keyidhere", defsecTypes.Metadata{}),
},
PublicAccess: defsecTypes.Bool(true, defsecTypes.Metadata{}),
},
},
},
},
},
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
actual, err := test.a.Merge(&test.b)
if err != nil {
t.Fatal(err)
}
assert.Equal(t, test.expected, *actual)
})
}

}
4 changes: 2 additions & 2 deletions pkg/state/state.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,6 @@ type State struct {
Oracle oracle.Oracle
}

func (s *State) ToRego() interface{} {
return convert.StructToRego(reflect.ValueOf(s))
func (a *State) ToRego() interface{} {
return convert.StructToRego(reflect.ValueOf(a))
}

0 comments on commit fb009fa

Please sign in to comment.