diff --git a/internal/adapters/cloudformation/aws/config/adapt_test.go b/internal/adapters/cloudformation/aws/config/adapt_test.go new file mode 100644 index 00000000..b4603701 --- /dev/null +++ b/internal/adapters/cloudformation/aws/config/adapt_test.go @@ -0,0 +1,71 @@ +package config + +import ( + "context" + "testing" + + "github.com/aquasecurity/defsec/pkg/providers/aws/config" + "github.com/aquasecurity/defsec/pkg/types" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy-iac/test/testutil" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected config.Config + }{ + { + name: "Config aggregator with AccountAggregationSources", + source: `AWSTemplateFormatVersion: "2010-09-09" +Resources: + ConfigurationAggregator: + Type: AWS::Config::ConfigurationAggregator + Properties: + AccountAggregationSources: + - AllAwsRegions: "true" +`, + expected: config.Config{ + ConfigurationAggregrator: config.ConfigurationAggregrator{ + Metadata: types.NewTestMetadata(), + SourceAllRegions: types.Bool(true, types.NewTestMetadata()), + }, + }, + }, + { + name: "Config aggregator with OrganizationAggregationSource", + source: `AWSTemplateFormatVersion: "2010-09-09" +Resources: + ConfigurationAggregator: + Type: AWS::Config::ConfigurationAggregator + Properties: + OrganizationAggregationSource: + AllAwsRegions: "true" +`, + expected: config.Config{ + ConfigurationAggregrator: config.ConfigurationAggregrator{ + Metadata: types.NewTestMetadata(), + SourceAllRegions: types.Bool(true, types.NewTestMetadata()), + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "template.yaml": tt.source, + }) + + p := parser.New() + fctx, err := p.ParseFile(context.TODO(), fs, "template.yaml") + require.NoError(t, err) + + testutil.AssertDefsecEqual(t, tt.expected, Adapt(*fctx)) + }) + } + +} diff --git a/internal/adapters/cloudformation/aws/config/aggregator.go b/internal/adapters/cloudformation/aws/config/aggregator.go index 615c96d2..4f261567 100644 --- a/internal/adapters/cloudformation/aws/config/aggregator.go +++ b/internal/adapters/cloudformation/aws/config/aggregator.go @@ -27,24 +27,15 @@ func getConfigurationAggregator(ctx parser.FileContext) config.ConfigurationAggr func isSourcingAllRegions(r *parser.Resource) defsecTypes.BoolValue { accountProp := r.GetProperty("AccountAggregationSources") - orgProp := r.GetProperty("OrganizationAggregationSource") if accountProp.IsNotNil() && accountProp.IsList() { for _, a := range accountProp.AsList() { regionsProp := a.GetProperty("AllAwsRegions") - if regionsProp.IsNil() || regionsProp.IsBool() { - return regionsProp.AsBoolValue() + if regionsProp.IsNotNil() { + return a.GetBoolProperty("AllAwsRegions") } } } - if orgProp.IsNotNil() { - regionsProp := orgProp.GetProperty("AllAwsRegions") - if regionsProp.IsBool() { - return regionsProp.AsBoolValue() - } - } - - // nothing is set or resolvable so its got to be false - return defsecTypes.BoolDefault(false, r.Metadata()) + return r.GetBoolProperty("OrganizationAggregationSource.AllAwsRegions") } diff --git a/internal/adapters/cloudformation/aws/documentdb/cluster.go b/internal/adapters/cloudformation/aws/documentdb/cluster.go index f4b1a7e2..139eb6b2 100644 --- a/internal/adapters/cloudformation/aws/documentdb/cluster.go +++ b/internal/adapters/cloudformation/aws/documentdb/cluster.go @@ -16,7 +16,7 @@ func getClusters(ctx parser.FileContext) (clusters []documentdb.Cluster) { Identifier: r.GetStringProperty("DBClusterIdentifier"), EnabledLogExports: getLogExports(r), Instances: nil, - BackupRetentionPeriod: r.GetIntProperty("BackupRetentionPeriod"), + BackupRetentionPeriod: r.GetIntProperty("BackupRetentionPeriod", 1), StorageEncrypted: r.GetBoolProperty("StorageEncrypted"), KMSKeyID: r.GetStringProperty("KmsKeyId"), } diff --git a/internal/adapters/cloudformation/aws/ec2/adapt_test.go b/internal/adapters/cloudformation/aws/ec2/adapt_test.go new file mode 100644 index 00000000..2875fcf7 --- /dev/null +++ b/internal/adapters/cloudformation/aws/ec2/adapt_test.go @@ -0,0 +1,66 @@ +package ec2 + +import ( + "context" + "testing" + + "github.com/aquasecurity/defsec/pkg/providers/aws/ec2" + "github.com/aquasecurity/defsec/pkg/types" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy-iac/test/testutil" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ec2.EC2 + }{ + { + name: "EC2 instance", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyEC2Instance: + Type: AWS::EC2::Instance + Properties: + ImageId: "ami-79fd7eee" + KeyName: "testkey" + BlockDeviceMappings: + - DeviceName: "/dev/sdm" + Ebs: + VolumeType: "io1" + Iops: "200" + DeleteOnTermination: "false" + VolumeSize: "20" + Encrypted: "true"`, + expected: ec2.EC2{ + Instances: []ec2.Instance{ + { + Metadata: types.NewTestMetadata(), + MetadataOptions: ec2.MetadataOptions{ + HttpEndpoint: types.StringDefault("enabled", types.NewTestMetadata()), + HttpTokens: types.StringDefault("optional", types.NewTestMetadata()), + }, + RootBlockDevice: &ec2.BlockDevice{ + Metadata: types.NewTestMetadata(), + Encrypted: types.Bool(true, types.NewTestMetadata()), + }, + }, + }, + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "template.yaml": tt.source, + }) + p := parser.New() + fctx, err := p.ParseFile(context.TODO(), fs, "template.yaml") + require.NoError(t, err) + testutil.AssertDefsecEqual(t, tt.expected, Adapt(*fctx)) + }) + } +} diff --git a/internal/adapters/cloudformation/aws/ec2/instance.go b/internal/adapters/cloudformation/aws/ec2/instance.go index 78de5d89..2454199b 100644 --- a/internal/adapters/cloudformation/aws/ec2/instance.go +++ b/internal/adapters/cloudformation/aws/ec2/instance.go @@ -50,17 +50,9 @@ func getBlockDevices(r *parser.Resource) []*ec2.BlockDevice { } for _, d := range devicesProp.AsList() { - encrypted := d.GetProperty("Ebs.Encrypted") - var result defsecTypes.BoolValue - if encrypted.IsNil() { - result = defsecTypes.BoolDefault(false, d.Metadata()) - } else { - result = encrypted.AsBoolValue() - } - device := &ec2.BlockDevice{ Metadata: d.Metadata(), - Encrypted: result, + Encrypted: d.GetBoolProperty("Ebs.Encrypted"), } blockDevices = append(blockDevices, device) diff --git a/internal/adapters/cloudformation/aws/elb/adapt_test.go b/internal/adapters/cloudformation/aws/elb/adapt_test.go new file mode 100644 index 00000000..c5c5c1ff --- /dev/null +++ b/internal/adapters/cloudformation/aws/elb/adapt_test.go @@ -0,0 +1,73 @@ +package elb + +import ( + "context" + "testing" + + "github.com/aquasecurity/defsec/pkg/providers/aws/elb" + "github.com/aquasecurity/defsec/pkg/types" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy-iac/test/testutil" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected elb.ELB + }{ + { + name: "LoadBalancer", + source: `AWSTemplateFormatVersion: "2010-09-09" +Resources: + LoadBalancer: + Type: AWS::ElasticLoadBalancingV2::LoadBalancer + DependsOn: + - ALBLogsBucketPermission + Properties: + Name: "k8s-dev" + IpAddressType: ipv4 + LoadBalancerAttributes: + - Key: routing.http2.enabled + Value: "true" + - Key: deletion_protection.enabled + Value: "true" + - Key: routing.http.drop_invalid_header_fields.enabled + Value: "true" + - Key: access_logs.s3.enabled + Value: "true" + Tags: + - Key: ingress.k8s.aws/resource + Value: LoadBalancer + - Key: elbv2.k8s.aws/cluster + Value: "biomage-dev" + Type: application +`, + expected: elb.ELB{ + LoadBalancers: []elb.LoadBalancer{ + { + Metadata: types.NewTestMetadata(), + Type: types.String("application", types.NewTestMetadata()), + DropInvalidHeaderFields: types.Bool(true, types.NewTestMetadata()), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "template.yaml": tt.source, + }) + + p := parser.New() + fctx, err := p.ParseFile(context.TODO(), fs, "template.yaml") + require.NoError(t, err) + + testutil.AssertDefsecEqual(t, tt.expected, Adapt(*fctx)) + }) + } +} diff --git a/internal/adapters/cloudformation/aws/elb/loadbalancer.go b/internal/adapters/cloudformation/aws/elb/loadbalancer.go index ab00dbf8..fe05de9d 100644 --- a/internal/adapters/cloudformation/aws/elb/loadbalancer.go +++ b/internal/adapters/cloudformation/aws/elb/loadbalancer.go @@ -72,16 +72,8 @@ func checkForDropInvalidHeaders(r *parser.Resource) types.BoolValue { } for _, attr := range attributesProp.AsList() { - if attr.IsNotMap() { - continue - } - - if attr.AsMap()["Key"].AsString() == "routing.http.drop_invalid_header_fields.enabled" { - val := attr.AsMap()["Value"] - if val.IsBool() { - return val.AsBoolValue() - } - + if attr.GetStringProperty("Key").Value() == "routing.http.drop_invalid_header_fields.enabled" { + return attr.GetBoolProperty("Value") } } diff --git a/internal/adapters/cloudformation/aws/rds/adapt_test.go b/internal/adapters/cloudformation/aws/rds/adapt_test.go new file mode 100644 index 00000000..3da3ffd0 --- /dev/null +++ b/internal/adapters/cloudformation/aws/rds/adapt_test.go @@ -0,0 +1,158 @@ +package rds + +import ( + "context" + "testing" + + "github.com/aquasecurity/defsec/pkg/providers/aws/rds" + "github.com/aquasecurity/defsec/pkg/types" + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy-iac/test/testutil" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected rds.RDS + }{ + { + name: "cluster with instances", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + RDSCluster: + Type: 'AWS::RDS::DBCluster' + Properties: + DBClusterIdentifier: my-cluster1 + Engine: aurora-postgresql + StorageEncrypted: true + KmsKeyId: "your-kms-key-id" + PerformanceInsightsEnabled: true + PerformanceInsightsKmsKeyId: "test-kms-key-id" + PublicAccess: true + DeletionProtection: true + BackupRetentionPeriod: 2 + RDSDBInstance1: + Type: 'AWS::RDS::DBInstance' + Properties: + Engine: aurora-mysql + EngineVersion: "5.7.12" + DBInstanceIdentifier: test + DBClusterIdentifier: + Ref: RDSCluster + PubliclyAccessible: 'false' + DBInstanceClass: db.r3.xlarge + StorageEncrypted: true + KmsKeyId: "your-kms-key-id" + EnablePerformanceInsights: true + PerformanceInsightsKMSKeyId: "test-kms-key-id2" + MultiAZ: true + AutoMinorVersionUpgrade: true + DBInstanceArn: "arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance-1" + EnableIAMDatabaseAuthentication: true + EnableCloudwatchLogsExports: + - "error" + - "general" + DBParameterGroupName: "testgroup" + Tags: + - Key: "keyname1" + Value: "value1" + - Key: "keyname2" + Value: "value2" + RDSDBParameterGroup: + Type: 'AWS::RDS::DBParameterGroup' + Properties: + Description: "CloudFormation Sample MySQL Parameter Group" + DBParameterGroupName: "testgroup" +`, + expected: rds.RDS{ + ParameterGroups: []rds.ParameterGroups{ + { + Metadata: types.NewTestMetadata(), + DBParameterGroupName: types.String("testgroup", types.NewTestMetadata()), + }, + }, + Clusters: []rds.Cluster{ + { + Metadata: types.NewTestMetadata(), + BackupRetentionPeriodDays: types.Int(2, types.NewTestMetadata()), + Engine: types.String("aurora-postgresql", types.NewTestMetadata()), + Encryption: rds.Encryption{ + EncryptStorage: types.Bool(true, types.NewTestMetadata()), + KMSKeyID: types.String("your-kms-key-id", types.NewTestMetadata()), + }, + PerformanceInsights: rds.PerformanceInsights{ + Metadata: types.NewTestMetadata(), + Enabled: types.Bool(true, types.NewTestMetadata()), + KMSKeyID: types.String("test-kms-key-id", types.NewTestMetadata()), + }, + PublicAccess: types.Bool(false, types.NewTestMetadata()), + DeletionProtection: types.Bool(true, types.NewTestMetadata()), + Instances: []rds.ClusterInstance{ + { + Instance: rds.Instance{ + Metadata: types.NewTestMetadata(), + StorageEncrypted: types.Bool(true, types.NewTestMetadata()), + Encryption: rds.Encryption{ + EncryptStorage: types.Bool(true, types.NewTestMetadata()), + KMSKeyID: types.String("your-kms-key-id", types.NewTestMetadata()), + }, + DBInstanceIdentifier: types.String("test", types.NewTestMetadata()), + PubliclyAccessible: types.Bool(false, types.NewTestMetadata()), + PublicAccess: types.BoolDefault(false, types.NewTestMetadata()), + BackupRetentionPeriodDays: types.IntDefault(1, types.NewTestMetadata()), + Engine: types.StringDefault("aurora-mysql", types.NewTestMetadata()), + EngineVersion: types.String("5.7.12", types.NewTestMetadata()), + MultiAZ: types.Bool(true, types.NewTestMetadata()), + AutoMinorVersionUpgrade: types.Bool(true, types.NewTestMetadata()), + DBInstanceArn: types.String("arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance-1", types.NewTestMetadata()), + IAMAuthEnabled: types.Bool(true, types.NewTestMetadata()), + PerformanceInsights: rds.PerformanceInsights{ + Metadata: types.NewTestMetadata(), + Enabled: types.Bool(true, types.NewTestMetadata()), + KMSKeyID: types.String("test-kms-key-id2", types.NewTestMetadata()), + }, + EnabledCloudwatchLogsExports: []types.StringValue{ + types.String("error", types.NewTestMetadata()), + types.String("general", types.NewTestMetadata()), + }, + DBParameterGroups: []rds.DBParameterGroupsList{ + { + DBParameterGroupName: types.String("testgroup", types.NewTestMetadata()), + }, + }, + TagList: []rds.TagList{ + { + Metadata: types.NewTestMetadata(), + }, + { + Metadata: types.NewTestMetadata(), + }, + }, + }, + ClusterIdentifier: types.String("RDSCluster", types.NewTestMetadata()), + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fs := testutil.CreateFS(t, map[string]string{ + "template.yaml": tt.source, + }) + + p := parser.New() + fctx, err := p.ParseFile(context.TODO(), fs, "template.yaml") + require.NoError(t, err) + + testutil.AssertDefsecEqual(t, tt.expected, Adapt(*fctx)) + }) + } + +} diff --git a/internal/adapters/cloudformation/aws/rds/cluster.go b/internal/adapters/cloudformation/aws/rds/cluster.go index 953e2988..9a7f59a5 100644 --- a/internal/adapters/cloudformation/aws/rds/cluster.go +++ b/internal/adapters/cloudformation/aws/rds/cluster.go @@ -9,56 +9,24 @@ import ( func getClusters(ctx parser.FileContext) (clusters map[string]rds.Cluster) { clusters = make(map[string]rds.Cluster) for _, clusterResource := range ctx.GetResourcesByType("AWS::RDS::DBCluster") { - cluster := rds.Cluster{ + clusters[clusterResource.ID()] = rds.Cluster{ Metadata: clusterResource.Metadata(), - BackupRetentionPeriodDays: defsecTypes.IntDefault(1, clusterResource.Metadata()), - ReplicationSourceARN: defsecTypes.StringDefault("", clusterResource.Metadata()), + BackupRetentionPeriodDays: clusterResource.GetIntProperty("BackupRetentionPeriod", 1), PerformanceInsights: rds.PerformanceInsights{ Metadata: clusterResource.Metadata(), - Enabled: defsecTypes.BoolDefault(false, clusterResource.Metadata()), - KMSKeyID: defsecTypes.StringDefault("", clusterResource.Metadata()), + Enabled: clusterResource.GetBoolProperty("PerformanceInsightsEnabled"), + KMSKeyID: clusterResource.GetStringProperty("PerformanceInsightsKmsKeyId"), }, - Instances: nil, Encryption: rds.Encryption{ Metadata: clusterResource.Metadata(), - EncryptStorage: defsecTypes.BoolDefault(false, clusterResource.Metadata()), - KMSKeyID: defsecTypes.StringDefault("", clusterResource.Metadata()), + EncryptStorage: clusterResource.GetBoolProperty("StorageEncrypted"), + KMSKeyID: clusterResource.GetStringProperty("KmsKeyId"), }, PublicAccess: defsecTypes.BoolDefault(false, clusterResource.Metadata()), - Engine: defsecTypes.StringDefault(rds.EngineAurora, clusterResource.Metadata()), + Engine: clusterResource.GetStringProperty("Engine", rds.EngineAurora), LatestRestorableTime: defsecTypes.TimeUnresolvable(clusterResource.Metadata()), - DeletionProtection: defsecTypes.BoolDefault(false, clusterResource.Metadata()), + DeletionProtection: clusterResource.GetBoolProperty("DeletionProtection"), } - - if engineProp := clusterResource.GetProperty("Engine"); engineProp.IsString() { - cluster.Engine = engineProp.AsStringValue() - } - - if backupProp := clusterResource.GetProperty("BackupRetentionPeriod"); backupProp.IsInt() { - cluster.BackupRetentionPeriodDays = backupProp.AsIntValue() - } - - if replicaProp := clusterResource.GetProperty("SourceDBInstanceIdentifier"); replicaProp.IsString() { - cluster.ReplicationSourceARN = replicaProp.AsStringValue() - } - - if piProp := clusterResource.GetProperty("EnablePerformanceInsights"); piProp.IsBool() { - cluster.PerformanceInsights.Enabled = piProp.AsBoolValue() - } - - if insightsKeyProp := clusterResource.GetProperty("PerformanceInsightsKMSKeyId"); insightsKeyProp.IsString() { - cluster.PerformanceInsights.KMSKeyID = insightsKeyProp.AsStringValue() - } - - if encryptedProp := clusterResource.GetProperty("StorageEncrypted"); encryptedProp.IsBool() { - cluster.Encryption.EncryptStorage = encryptedProp.AsBoolValue() - } - - if keyProp := clusterResource.GetProperty("KmsKeyId"); keyProp.IsString() { - cluster.Encryption.KMSKeyID = keyProp.AsStringValue() - } - - clusters[clusterResource.ID()] = cluster } return clusters } diff --git a/internal/adapters/cloudformation/aws/rds/instance.go b/internal/adapters/cloudformation/aws/rds/instance.go index 5b3a76af..3ff3eb9b 100644 --- a/internal/adapters/cloudformation/aws/rds/instance.go +++ b/internal/adapters/cloudformation/aws/rds/instance.go @@ -6,10 +6,12 @@ import ( "github.com/aquasecurity/trivy-iac/pkg/scanners/cloudformation/parser" ) -func getClustersAndInstances(ctx parser.FileContext) (clusters []rds.Cluster, orphans []rds.Instance) { +func getClustersAndInstances(ctx parser.FileContext) ([]rds.Cluster, []rds.Instance) { clusterMap := getClusters(ctx) + var orphans []rds.Instance + for _, r := range ctx.GetResourcesByType("AWS::RDS::DBInstance") { instance := rds.Instance{ @@ -45,26 +47,20 @@ func getClustersAndInstances(ctx parser.FileContext) (clusters []rds.Cluster, or } if clusterID := r.GetProperty("DBClusterIdentifier"); clusterID.IsString() { - var found bool - for key, cluster := range clusterMap { - if key == clusterID.AsString() { - cluster.Instances = append(cluster.Instances, rds.ClusterInstance{ - Instance: instance, - ClusterIdentifier: clusterID.AsStringValue(), - }) - clusterMap[key] = cluster - found = true - break - } - } - if found { - continue + if cluster, exist := clusterMap[clusterID.AsString()]; exist { + cluster.Instances = append(cluster.Instances, rds.ClusterInstance{ + Instance: instance, + ClusterIdentifier: clusterID.AsStringValue(), + }) + clusterMap[clusterID.AsString()] = cluster } + } else { + orphans = append(orphans, instance) } - - orphans = append(orphans, instance) } + clusters := make([]rds.Cluster, 0, len(clusterMap)) + for _, cluster := range clusterMap { clusters = append(clusters, cluster) } @@ -74,10 +70,16 @@ func getClustersAndInstances(ctx parser.FileContext) (clusters []rds.Cluster, or func getDBParameterGroups(ctx parser.FileContext, r *parser.Resource) (dbParameterGroup []rds.DBParameterGroupsList) { - for _, r := range ctx.GetResourcesByType("DBParameterGroups") { + dbParameterGroupName := r.GetStringProperty("DBParameterGroupName") + + for _, r := range ctx.GetResourcesByType("AWS::RDS::DBParameterGroup") { + name := r.GetStringProperty("DBParameterGroupName") + if !dbParameterGroupName.EqualTo(name.Value()) { + continue + } dbpmgl := rds.DBParameterGroupsList{ Metadata: r.Metadata(), - DBParameterGroupName: r.GetStringProperty("DBParameterGroupName"), + DBParameterGroupName: name, KMSKeyID: types.StringUnresolvable(r.Metadata()), } dbParameterGroup = append(dbParameterGroup, dbpmgl) @@ -100,7 +102,7 @@ func getEnabledCloudwatchLogsExports(r *parser.Resource) (enabledcloudwatchlogex } func getTagList(r *parser.Resource) (taglist []rds.TagList) { - tagLists := r.GetProperty("tags") + tagLists := r.GetProperty("Tags") if tagLists.IsNil() || tagLists.IsNotList() { return taglist @@ -115,7 +117,7 @@ func getTagList(r *parser.Resource) (taglist []rds.TagList) { } func getReadReplicaDBInstanceIdentifiers(r *parser.Resource) (readreplicadbidentifier []types.StringValue) { - readReplicaDBIdentifier := r.GetProperty("EnableCloudwatchLogsExports") + readReplicaDBIdentifier := r.GetProperty("SourceDBInstanceIdentifier") if readReplicaDBIdentifier.IsNil() || readReplicaDBIdentifier.IsNotList() { return readreplicadbidentifier