From 2c85f3b5640b03ddb086e4075022dec8cf9d7185 Mon Sep 17 00:00:00 2001 From: nikpivkin Date: Fri, 15 Mar 2024 15:46:16 +0700 Subject: [PATCH] test(cloudformation): add CF tests --- .../aws/accessanalyzer/accessanalyzer_test.go | 54 +++++ .../aws/apigateway/apigateway_test.go | 84 +++++++ .../cloudformation/aws/apigateway/stage.go | 12 +- .../cloudformation/aws/athena/athena_test.go | 61 +++++ .../aws/cloudfront/cloudfront_test.go | 68 ++++++ .../aws/cloudfront/distribution.go | 22 +- .../aws/cloudtrail/cloudtrail_test.go | 64 ++++++ .../aws/cloudwatch/cloudwatch.go | 1 - .../aws/cloudwatch/cloudwatch_test.go | 57 +++++ .../aws/cloudwatch/log_group.go | 5 +- .../aws/codebuild/codebuild_test.go | 68 ++++++ .../cloudformation/aws/codebuild/project.go | 8 +- .../cloudformation/aws/config/adapt_test.go | 21 +- .../cloudformation/aws/config/aggregator.go | 6 +- .../cloudformation/aws/documentdb/cluster.go | 10 +- .../aws/documentdb/documentdb_test.go | 79 +++++++ .../aws/dynamodb/dynamodb_test.go | 55 +++++ .../cloudformation/aws/ec2/adapt_test.go | 184 +++++++++++---- .../cloudformation/aws/ec2/instance.go | 10 +- .../aws/ec2/launch_configuration.go | 4 +- .../cloudformation/aws/ec2/launch_template.go | 6 +- .../cloudformation/aws/ec2/security_group.go | 8 +- .../cloudformation/aws/ecr/ecr_test.go | 102 +++++++++ .../cloudformation/aws/ecr/repository.go | 13 +- .../cloudformation/aws/ecs/cluster.go | 8 +- .../cloudformation/aws/ecs/ecs_test.go | 108 +++++++++ .../cloudformation/aws/ecs/task_definition.go | 26 +-- .../cloudformation/aws/efs/efs_test.go | 52 +++++ .../cloudformation/aws/eks/cluster.go | 10 +- .../cloudformation/aws/eks/eks_test.go | 45 ++++ .../aws/elasticache/elasticache_test.go | 82 +++++++ .../aws/elasticsearch/domain.go | 20 +- .../aws/elasticsearch/elasticsearch_test.go | 88 ++++++++ .../cloudformation/aws/elb/adapt_test.go | 47 ++-- .../cloudformation/aws/elb/loadbalancer.go | 14 +- .../cloudformation/aws/iam/iam_test.go | 189 ++++++++++++++++ .../adapters/cloudformation/aws/iam/policy.go | 17 +- .../aws/kinesis/kinesis_test.go | 57 +++++ .../cloudformation/aws/kinesis/stream.go | 6 - .../cloudformation/aws/lambda/function.go | 15 +- .../cloudformation/aws/lambda/lambda_test.go | 76 +++++++ .../adapters/cloudformation/aws/mq/mq_test.go | 59 +++++ .../cloudformation/aws/msk/msk_test.go | 87 +++++++ .../cloudformation/aws/neptune/cluster.go | 6 +- .../aws/neptune/neptune_test.go | 59 +++++ .../cloudformation/aws/rds/adapt_test.go | 124 +++++----- .../cloudformation/aws/rds/instance.go | 19 +- .../aws/rds/parameter_groups.go | 8 +- .../cloudformation/aws/redshift/cluster.go | 13 +- .../aws/redshift/redshift_test.go | 111 +++++++++ .../adapters/cloudformation/aws/s3/bucket.go | 13 +- .../adapters/cloudformation/aws/s3/s3_test.go | 60 +++-- .../adapters/cloudformation/aws/sam/api.go | 22 +- .../cloudformation/aws/sam/function.go | 8 +- .../cloudformation/aws/sam/http_api.go | 11 +- .../cloudformation/aws/sam/sam_test.go | 213 ++++++++++++++++++ .../cloudformation/aws/sam/state_machines.go | 9 +- .../adapters/cloudformation/aws/sam/tables.go | 23 +- .../cloudformation/aws/sns/sns_test.go | 54 +++++ .../adapters/cloudformation/aws/sqs/queue.go | 1 - .../cloudformation/aws/sqs/sqs_test.go | 86 +++++++ .../cloudformation/aws/ssm/ssm_test.go | 53 +++++ .../aws/workspaces/workspaces_test.go | 62 +++++ .../cloudformation/testutil/testutil.go | 25 ++ pkg/iac/providers/aws/ecs/ecs.go | 8 +- pkg/iac/types/bool.go | 4 + pkg/iac/types/int.go | 4 + pkg/iac/types/string.go | 5 + 68 files changed, 2660 insertions(+), 349 deletions(-) create mode 100644 pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/athena/athena_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/efs/efs_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/eks/eks_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/iam/iam_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/mq/mq_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/msk/msk_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/sam/sam_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/sns/sns_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go create mode 100644 pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go create mode 100644 pkg/iac/adapters/cloudformation/testutil/testutil.go diff --git a/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go new file mode 100644 index 000000000000..04e67c2b6818 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/accessanalyzer/accessanalyzer_test.go @@ -0,0 +1,54 @@ +package accessanalyzer + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/accessanalyzer" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected accessanalyzer.AccessAnalyzer + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Analyzer: + Type: 'AWS::AccessAnalyzer::Analyzer' + Properties: + AnalyzerName: MyAccountAnalyzer +`, + expected: accessanalyzer.AccessAnalyzer{ + Analyzers: []accessanalyzer.Analyzer{ + { + Name: types.StringTest("MyAccountAnalyzer"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Analyzer: + Type: 'AWS::AccessAnalyzer::Analyzer' +`, + expected: accessanalyzer.AccessAnalyzer{ + Analyzers: []accessanalyzer.Analyzer{ + {}, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go new file mode 100644 index 000000000000..8f9e55ef8abd --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/apigateway_test.go @@ -0,0 +1,84 @@ +package apigateway + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway" + v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected apigateway.APIGateway + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyApi: + Type: 'AWS::ApiGatewayV2::Api' + Properties: + Name: MyApi + ProtocolType: WEBSOCKET + MyStage: + Type: 'AWS::ApiGatewayV2::Stage' + Properties: + StageName: Prod + ApiId: !Ref MyApi + AccessLogSettings: + DestinationArn: some-arn +`, + expected: apigateway.APIGateway{ + V2: v2.APIGateway{ + APIs: []v2.API{ + { + Name: types.StringTest("MyApi"), + ProtocolType: types.StringTest("WEBSOCKET"), + Stages: []v2.Stage{ + { + Name: types.StringTest("Prod"), + AccessLogging: v2.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("some-arn"), + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyApi: + Type: 'AWS::ApiGatewayV2::Api' + MyStage: + Type: 'AWS::ApiGatewayV2::Stage' + MyStage2: + Type: 'AWS::ApiGatewayV2::Stage' + Properties: + ApiId: !Ref MyApi +`, + expected: apigateway.APIGateway{ + V2: v2.APIGateway{ + APIs: []v2.API{ + { + Stages: []v2.Stage{{}}, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go b/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go index c79f89fda5ea..8e9497a91ec3 100644 --- a/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go +++ b/pkg/iac/adapters/cloudformation/aws/apigateway/stage.go @@ -2,18 +2,18 @@ package apigateway import ( v2 "github.com/aquasecurity/trivy/pkg/iac/providers/aws/apigateway/v2" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getApis(cfFile parser2.FileContext) (apis []v2.API) { +func getApis(cfFile parser.FileContext) (apis []v2.API) { apiResources := cfFile.GetResourcesByType("AWS::ApiGatewayV2::Api") for _, apiRes := range apiResources { api := v2.API{ Metadata: apiRes.Metadata(), - Name: types.StringDefault("", apiRes.Metadata()), - ProtocolType: types.StringDefault("", apiRes.Metadata()), + Name: apiRes.GetStringProperty("Name"), + ProtocolType: apiRes.GetStringProperty("ProtocolType"), Stages: getStages(apiRes.ID(), cfFile), } apis = append(apis, api) @@ -22,7 +22,7 @@ func getApis(cfFile parser2.FileContext) (apis []v2.API) { return apis } -func getStages(apiId string, cfFile parser2.FileContext) []v2.Stage { +func getStages(apiId string, cfFile parser.FileContext) []v2.Stage { var apiStages []v2.Stage stageResources := cfFile.GetResourcesByType("AWS::ApiGatewayV2::Stage") @@ -43,7 +43,7 @@ func getStages(apiId string, cfFile parser2.FileContext) []v2.Stage { return apiStages } -func getAccessLogging(r *parser2.Resource) v2.AccessLogging { +func getAccessLogging(r *parser.Resource) v2.AccessLogging { loggingProp := r.GetProperty("AccessLogSettings") if loggingProp.IsNil() { diff --git a/pkg/iac/adapters/cloudformation/aws/athena/athena_test.go b/pkg/iac/adapters/cloudformation/aws/athena/athena_test.go new file mode 100644 index 000000000000..097de6fa303d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/athena/athena_test.go @@ -0,0 +1,61 @@ +package athena + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/athena" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected athena.Athena + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyAthenaWorkGroup: + Type: AWS::Athena::WorkGroup + Properties: + Name: MyCustomWorkGroup + WorkGroupConfiguration: + EnforceWorkGroupConfiguration: true + ResultConfiguration: + EncryptionOption: SSE_KMS +`, + expected: athena.Athena{ + Workgroups: []athena.Workgroup{ + { + Name: types.StringTest("MyCustomWorkGroup"), + EnforceConfiguration: types.BoolTest(true), + Encryption: athena.EncryptionConfiguration{ + Type: types.StringTest("SSE_KMS"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyAthenaWorkGroup: + Type: AWS::Athena::WorkGroup +`, + expected: athena.Athena{ + Workgroups: []athena.Workgroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } + +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go b/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go new file mode 100644 index 000000000000..6c0ec7348b33 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudfront/cloudfront_test.go @@ -0,0 +1,68 @@ +package cloudfront + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected cloudfront.Cloudfront + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cloudfrontdistribution: + Type: AWS::CloudFront::Distribution + Properties: + DistributionConfig: + WebACLId: "a1b2c3d4-5678-90ab-cdef-EXAMPLE11111" + Logging: + Bucket: "myawslogbucket.s3.amazonaws.com" + ViewerCertificate: + MinimumProtocolVersion: SSLv3 + DefaultCacheBehavior: + ViewerProtocolPolicy: "redirect-to-https" +`, + expected: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{ + { + WAFID: types.StringTest("a1b2c3d4-5678-90ab-cdef-EXAMPLE11111"), + Logging: cloudfront.Logging{ + Bucket: types.StringTest("myawslogbucket.s3.amazonaws.com"), + }, + ViewerCertificate: cloudfront.ViewerCertificate{ + MinimumProtocolVersion: types.StringTest("SSLv3"), + }, + DefaultCacheBehaviour: cloudfront.CacheBehaviour{ + ViewerProtocolPolicy: types.StringTest("redirect-to-https"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cloudfrontdistribution: + Type: AWS::CloudFront::Distribution +`, + expected: cloudfront.Cloudfront{ + Distributions: []cloudfront.Distribution{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go b/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go index 0364dc82d052..70c5052bcd55 100644 --- a/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go +++ b/pkg/iac/adapters/cloudformation/aws/cloudfront/distribution.go @@ -2,11 +2,10 @@ package cloudfront import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudfront" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" - "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" ) -func getDistributions(ctx parser2.FileContext) (distributions []cloudfront.Distribution) { +func getDistributions(ctx parser.FileContext) (distributions []cloudfront.Distribution) { distributionResources := ctx.GetResourcesByType("AWS::CloudFront::Distribution") @@ -32,24 +31,15 @@ func getDistributions(ctx parser2.FileContext) (distributions []cloudfront.Distr return distributions } -func getDefaultCacheBehaviour(r *parser2.Resource) cloudfront.CacheBehaviour { +func getDefaultCacheBehaviour(r *parser.Resource) cloudfront.CacheBehaviour { defaultCache := r.GetProperty("DistributionConfig.DefaultCacheBehavior") if defaultCache.IsNil() { return cloudfront.CacheBehaviour{ - Metadata: r.Metadata(), - ViewerProtocolPolicy: types.StringDefault("allow-all", r.Metadata()), - } - } - protoProp := r.GetProperty("DistributionConfig.DefaultCacheBehavior.ViewerProtocolPolicy") - if protoProp.IsNotString() { - return cloudfront.CacheBehaviour{ - Metadata: r.Metadata(), - ViewerProtocolPolicy: types.StringDefault("allow-all", r.Metadata()), + Metadata: r.Metadata(), } } - return cloudfront.CacheBehaviour{ - Metadata: r.Metadata(), - ViewerProtocolPolicy: protoProp.AsStringValue(), + Metadata: defaultCache.Metadata(), + ViewerProtocolPolicy: defaultCache.GetStringProperty("ViewerProtocolPolicy"), } } diff --git a/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go b/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go new file mode 100644 index 000000000000..5dcebb291035 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudtrail/cloudtrail_test.go @@ -0,0 +1,64 @@ +package cloudtrail + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudtrail" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected cloudtrail.CloudTrail + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Trail: + Type: AWS::CloudTrail::Trail + Properties: + S3BucketName: MyBucket + IsLogging: true + TrailName: MyTrail + EnableLogFileValidation: true + IsMultiRegionTrail: true + CloudWatchLogsLogGroupArn: cw-arn + KmsKeyId: my-kms-key +`, + expected: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{ + { + Name: types.StringTest("MyTrail"), + BucketName: types.StringTest("MyBucket"), + IsLogging: types.BoolTest(true), + IsMultiRegion: types.BoolTest(true), + EnableLogFileValidation: types.BoolTest(true), + CloudWatchLogsLogGroupArn: types.StringTest("cw-arn"), + KMSKeyID: types.StringTest("my-kms-key"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Trail: + Type: AWS::CloudTrail::Trail + `, + expected: cloudtrail.CloudTrail{ + Trails: []cloudtrail.Trail{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go index 1c6efa85a891..0c4a59e43189 100644 --- a/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go +++ b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch.go @@ -9,6 +9,5 @@ import ( func Adapt(cfFile parser.FileContext) cloudwatch.CloudWatch { return cloudwatch.CloudWatch{ LogGroups: getLogGroups(cfFile), - Alarms: nil, } } diff --git a/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go new file mode 100644 index 000000000000..c8a7bd95c9a3 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/cloudwatch/cloudwatch_test.go @@ -0,0 +1,57 @@ +package cloudwatch + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected cloudwatch.CloudWatch + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myLogGroup: + Type: AWS::Logs::LogGroup + Properties: + LogGroupName: my-log-group + RetentionInDays: 7 + KmsKeyId: my-kms + +`, + expected: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{ + { + Name: types.StringTest("my-log-group"), + RetentionInDays: types.IntTest(7), + KMSKeyID: types.StringTest("my-kms"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myLogGroup: + Type: AWS::Logs::LogGroup + `, + expected: cloudwatch.CloudWatch{ + LogGroups: []cloudwatch.LogGroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go b/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go index 81730f050ecf..09e039129781 100644 --- a/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go +++ b/pkg/iac/adapters/cloudformation/aws/cloudwatch/log_group.go @@ -3,7 +3,6 @@ package cloudwatch import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/cloudwatch" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func getLogGroups(ctx parser.FileContext) (logGroups []cloudwatch.LogGroup) { @@ -13,11 +12,9 @@ func getLogGroups(ctx parser.FileContext) (logGroups []cloudwatch.LogGroup) { for _, r := range logGroupResources { group := cloudwatch.LogGroup{ Metadata: r.Metadata(), - Arn: types.StringDefault("", r.Metadata()), Name: r.GetStringProperty("LogGroupName"), KMSKeyID: r.GetStringProperty("KmsKeyId"), - RetentionInDays: r.GetIntProperty("RetentionInDays", 0), - MetricFilters: nil, + RetentionInDays: r.GetIntProperty("RetentionInDays"), } logGroups = append(logGroups, group) } diff --git a/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go b/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go new file mode 100644 index 000000000000..06eaa19402e6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/codebuild/codebuild_test.go @@ -0,0 +1,68 @@ +package codebuild + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected codebuild.CodeBuild + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Project: + Type: AWS::CodeBuild::Project + Properties: + Artifacts: + EncryptionDisabled: true + SecondaryArtifacts: + - EncryptionDisabled: true +`, + expected: codebuild.CodeBuild{ + Projects: []codebuild.Project{ + { + ArtifactSettings: codebuild.ArtifactSettings{ + EncryptionEnabled: types.BoolTest(false), + }, + SecondaryArtifactSettings: []codebuild.ArtifactSettings{ + { + EncryptionEnabled: types.BoolTest(false), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + Project: + Type: AWS::CodeBuild::Project + `, + expected: codebuild.CodeBuild{ + Projects: []codebuild.Project{ + { + ArtifactSettings: codebuild.ArtifactSettings{ + EncryptionEnabled: types.BoolTest(true), + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/codebuild/project.go b/pkg/iac/adapters/cloudformation/aws/codebuild/project.go index 9c0541831223..554fc8afecea 100644 --- a/pkg/iac/adapters/cloudformation/aws/codebuild/project.go +++ b/pkg/iac/adapters/cloudformation/aws/codebuild/project.go @@ -2,11 +2,11 @@ package codebuild import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/codebuild" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getProjects(ctx parser2.FileContext) (projects []codebuild.Project) { +func getProjects(ctx parser.FileContext) (projects []codebuild.Project) { projectResources := ctx.GetResourcesByType("AWS::CodeBuild::Project") @@ -23,7 +23,7 @@ func getProjects(ctx parser2.FileContext) (projects []codebuild.Project) { return projects } -func getSecondaryArtifactSettings(r *parser2.Resource) (secondaryArtifacts []codebuild.ArtifactSettings) { +func getSecondaryArtifactSettings(r *parser.Resource) (secondaryArtifacts []codebuild.ArtifactSettings) { secondaryArtifactsList := r.GetProperty("SecondaryArtifacts") if secondaryArtifactsList.IsNil() || !secondaryArtifactsList.IsList() { return @@ -44,7 +44,7 @@ func getSecondaryArtifactSettings(r *parser2.Resource) (secondaryArtifacts []cod return secondaryArtifacts } -func getArtifactSettings(r *parser2.Resource) codebuild.ArtifactSettings { +func getArtifactSettings(r *parser.Resource) codebuild.ArtifactSettings { settings := codebuild.ArtifactSettings{ Metadata: r.Metadata(), diff --git a/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go index 1a8f30e018f6..e6dc652da7b1 100644 --- a/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/config/adapt_test.go @@ -1,14 +1,11 @@ package config import ( - "context" "testing" - "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" ) func TestAdapt(t *testing.T) { @@ -29,8 +26,7 @@ Resources: `, expected: config.Config{ ConfigurationAggregrator: config.ConfigurationAggregrator{ - Metadata: types.NewTestMetadata(), - SourceAllRegions: types.Bool(true, types.NewTestMetadata()), + SourceAllRegions: types.BoolTest(true), }, }, }, @@ -46,8 +42,7 @@ Resources: `, expected: config.Config{ ConfigurationAggregrator: config.ConfigurationAggregrator{ - Metadata: types.NewTestMetadata(), - SourceAllRegions: types.Bool(true, types.NewTestMetadata()), + SourceAllRegions: types.BoolTest(true), }, }, }, @@ -55,15 +50,7 @@ Resources: 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)) + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) }) } diff --git a/pkg/iac/adapters/cloudformation/aws/config/aggregator.go b/pkg/iac/adapters/cloudformation/aws/config/aggregator.go index 1f34c21591b0..72447398b80f 100644 --- a/pkg/iac/adapters/cloudformation/aws/config/aggregator.go +++ b/pkg/iac/adapters/cloudformation/aws/config/aggregator.go @@ -2,11 +2,11 @@ package config import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/config" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getConfigurationAggregator(ctx parser2.FileContext) config.ConfigurationAggregrator { +func getConfigurationAggregator(ctx parser.FileContext) config.ConfigurationAggregrator { aggregator := config.ConfigurationAggregrator{ Metadata: iacTypes.NewUnmanagedMetadata(), @@ -25,7 +25,7 @@ func getConfigurationAggregator(ctx parser2.FileContext) config.ConfigurationAgg } } -func isSourcingAllRegions(r *parser2.Resource) iacTypes.BoolValue { +func isSourcingAllRegions(r *parser.Resource) iacTypes.BoolValue { accountProp := r.GetProperty("AccountAggregationSources") if accountProp.IsNotNil() && accountProp.IsList() { diff --git a/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go b/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go index 568fcfb44f72..f37467dc4100 100644 --- a/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go +++ b/pkg/iac/adapters/cloudformation/aws/documentdb/cluster.go @@ -2,11 +2,11 @@ package documentdb import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getClusters(ctx parser2.FileContext) (clusters []documentdb.Cluster) { +func getClusters(ctx parser.FileContext) (clusters []documentdb.Cluster) { clusterResources := ctx.GetResourcesByType("AWS::DocDB::DBCluster") @@ -28,13 +28,13 @@ func getClusters(ctx parser2.FileContext) (clusters []documentdb.Cluster) { return clusters } -func updateInstancesOnCluster(cluster *documentdb.Cluster, ctx parser2.FileContext) { +func updateInstancesOnCluster(cluster *documentdb.Cluster, ctx parser.FileContext) { instanceResources := ctx.GetResourcesByType("AWS::DocDB::DBInstance") for _, r := range instanceResources { clusterIdentifier := r.GetStringProperty("DBClusterIdentifier") - if clusterIdentifier == cluster.Identifier { + if cluster.Identifier.EqualTo(clusterIdentifier.Value()) { cluster.Instances = append(cluster.Instances, documentdb.Instance{ Metadata: r.Metadata(), KMSKeyID: cluster.KMSKeyID, @@ -43,7 +43,7 @@ func updateInstancesOnCluster(cluster *documentdb.Cluster, ctx parser2.FileConte } } -func getLogExports(r *parser2.Resource) (logExports []types.StringValue) { +func getLogExports(r *parser.Resource) (logExports []types.StringValue) { exportsList := r.GetProperty("EnableCloudwatchLogsExports") diff --git a/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go b/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go new file mode 100644 index 000000000000..3e60155e9dfb --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/documentdb/documentdb_test.go @@ -0,0 +1,79 @@ +package documentdb + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/documentdb" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected documentdb.DocumentDB + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + myDBCluster: + Type: 'AWS::DocDB::DBCluster' + Properties: + BackupRetentionPeriod: 8 + DBClusterIdentifier: sample-cluster + KmsKeyId: your-kms-key-id + StorageEncrypted: true + EnableCloudwatchLogsExports: + - audit + - general + myDBInstance: + Type: 'AWS::DocDB::DBInstance' + Properties: + DBClusterIdentifier: sample-cluster + KmsKeyId: your-kms-key-id +`, + expected: documentdb.DocumentDB{ + Clusters: []documentdb.Cluster{ + { + Identifier: types.StringTest("sample-cluster"), + BackupRetentionPeriod: types.IntTest(8), + KMSKeyID: types.StringTest("your-kms-key-id"), + StorageEncrypted: types.BoolTest(true), + EnabledLogExports: []types.StringValue{ + types.StringTest("audit"), + types.StringTest("general"), + }, + Instances: []documentdb.Instance{ + { + KMSKeyID: types.StringTest("your-kms-key-id"), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myDBCluster: + Type: 'AWS::DocDB::DBCluster' + `, + expected: documentdb.DocumentDB{ + Clusters: []documentdb.Cluster{ + { + BackupRetentionPeriod: types.IntTest(1), + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go b/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go new file mode 100644 index 000000000000..ce62e85cde5e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/dynamodb/dynamodb_test.go @@ -0,0 +1,55 @@ +package dynamodb + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/dynamodb" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected dynamodb.DynamoDB + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + daxCluster: + Type: AWS::DAX::Cluster + Properties: + SSESpecification: + SSEEnabled: true +`, + expected: dynamodb.DynamoDB{ + DAXClusters: []dynamodb.DAXCluster{ + { + ServerSideEncryption: dynamodb.ServerSideEncryption{ + Enabled: types.BoolTest(true), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + daxCluster: + Type: AWS::DAX::Cluster + `, + expected: dynamodb.DynamoDB{ + DAXClusters: []dynamodb.DAXCluster{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go index 7e7ece3df765..ac05f8f7b263 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/adapt_test.go @@ -1,14 +1,11 @@ package ec2 import ( - "context" "testing" - "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" ) func TestAdapt(t *testing.T) { @@ -18,7 +15,7 @@ func TestAdapt(t *testing.T) { expected ec2.EC2 }{ { - name: "ec2 instance", + name: "complete", source: `AWSTemplateFormatVersion: 2010-09-09 Resources: MyEC2Instance: @@ -36,27 +33,155 @@ Resources: Encrypted: true - DeviceName: "/dev/sdk" NoDevice: {} + NewVolume: + Type: AWS::EC2::Volume + Properties: + KmsKeyId: alias/my_cmk + Encrypted: true + mySubnet: + Type: AWS::EC2::Subnet + Properties: + MapPublicIpOnLaunch: true + InstanceSecurityGroup: + Type: AWS::EC2::SecurityGroup + Properties: + GroupName: default + GroupDescription: Allow http to client host + VpcId: vpc-id + SecurityGroupIngress: + - IpProtocol: tcp + Description: ingress + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + SecurityGroupEgress: + - IpProtocol: tcp + Description: egress + FromPort: 80 + ToPort: 80 + CidrIp: 0.0.0.0/0 + myNetworkAcl: + Type: AWS::EC2::NetworkAcl + Properties: + VpcId: vpc-1122334455aabbccd + InboundRule: + Type: AWS::EC2::NetworkAclEntry + Properties: + NetworkAclId: + Ref: myNetworkAcl + Egress: true + Protocol: 6 + RuleAction: allow + CidrBlock: 172.16.0.0/24 + myLaunchConfig: + Type: AWS::AutoScaling::LaunchConfiguration + Properties: + LaunchConfigurationName: test-cfg + InstanceId: !Ref MyEC2Instance + AssociatePublicIpAddress: true + SecurityGroups: + - !Ref InstanceSecurityGroup + UserData: test + BlockDeviceMappings: + - DeviceName: /dev/sda1 + Ebs: + VolumeSize: '30' + VolumeType: gp3 + Encrypted: true + - DeviceName: /dev/sdm + Ebs: + VolumeSize: '100' + DeleteOnTermination: false + MetadataOptions: + HttpTokens: required + HttpEndpoint: disabled `, 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.BoolDefault(true, types.NewTestMetadata()), }, EBSBlockDevices: []*ec2.BlockDevice{ { - Metadata: types.NewTestMetadata(), Encrypted: types.BoolDefault(false, types.NewTestMetadata()), }, }, }, }, + Volumes: []ec2.Volume{ + { + Encryption: ec2.Encryption{ + KMSKeyID: types.StringTest("alias/my_cmk"), + Enabled: types.BoolTest(true), + }, + }, + }, + Subnets: []ec2.Subnet{ + { + MapPublicIpOnLaunch: types.BoolTest(true), + }, + }, + SecurityGroups: []ec2.SecurityGroup{ + { + IsDefault: types.BoolTest(true), + Description: types.StringTest("Allow http to client host"), + VPCID: types.StringTest("vpc-id"), + IngressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("ingress"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + EgressRules: []ec2.SecurityGroupRule{ + { + Description: types.StringTest("egress"), + CIDRs: []types.StringValue{ + types.StringTest("0.0.0.0/0"), + }, + }, + }, + }, + }, + NetworkACLs: []ec2.NetworkACL{ + { + Rules: []ec2.NetworkACLRule{ + { + Type: types.StringTest(ec2.TypeEgress), + Action: types.StringTest(ec2.ActionAllow), + Protocol: types.StringTest("6"), + CIDRs: []types.StringValue{ + types.StringTest("172.16.0.0/24"), + }, + }, + }, + }, + }, + LaunchConfigurations: []ec2.LaunchConfiguration{ + { + Name: types.StringTest("test-cfg"), + AssociatePublicIP: types.BoolTest(true), + RootBlockDevice: &ec2.BlockDevice{ + Encrypted: types.BoolTest(true), + }, + EBSBlockDevices: []*ec2.BlockDevice{ + { + Encrypted: types.BoolTest(false), + }, + }, + UserData: types.StringTest("test"), + MetadataOptions: ec2.MetadataOptions{ + HttpTokens: types.StringTest("required"), + HttpEndpoint: types.StringTest("disabled"), + }, + }, + }, }, }, { @@ -81,27 +206,23 @@ Resources: expected: ec2.EC2{ LaunchTemplates: []ec2.LaunchTemplate{ { - Metadata: types.NewTestMetadata(), - Name: types.String("MyTemplate", types.NewTestMetadata()), + Name: types.StringTest("MyTemplate"), Instance: ec2.Instance{ - Metadata: types.NewTestMetadata(), MetadataOptions: ec2.MetadataOptions{ - HttpEndpoint: types.String("enabled", types.NewTestMetadata()), - HttpTokens: types.String("required", types.NewTestMetadata()), + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), }, }, }, }, Instances: []ec2.Instance{ { - Metadata: types.NewTestMetadata(), MetadataOptions: ec2.MetadataOptions{ - HttpEndpoint: types.String("enabled", types.NewTestMetadata()), - HttpTokens: types.String("required", types.NewTestMetadata()), + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), }, RootBlockDevice: &ec2.BlockDevice{ - Metadata: types.NewTestMetadata(), - Encrypted: types.Bool(false, types.NewTestMetadata()), + Encrypted: types.BoolTest(false), }, }, }, @@ -129,27 +250,23 @@ Resources: expected: ec2.EC2{ LaunchTemplates: []ec2.LaunchTemplate{ { - Metadata: types.NewTestMetadata(), - Name: types.String("MyTemplate", types.NewTestMetadata()), + Name: types.StringTest("MyTemplate"), Instance: ec2.Instance{ - Metadata: types.NewTestMetadata(), MetadataOptions: ec2.MetadataOptions{ - HttpEndpoint: types.String("enabled", types.NewTestMetadata()), - HttpTokens: types.String("required", types.NewTestMetadata()), + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), }, }, }, }, Instances: []ec2.Instance{ { - Metadata: types.NewTestMetadata(), MetadataOptions: ec2.MetadataOptions{ - HttpEndpoint: types.String("enabled", types.NewTestMetadata()), - HttpTokens: types.String("required", types.NewTestMetadata()), + HttpEndpoint: types.StringTest("enabled"), + HttpTokens: types.StringTest("required"), }, RootBlockDevice: &ec2.BlockDevice{ - Metadata: types.NewTestMetadata(), - Encrypted: types.Bool(false, types.NewTestMetadata()), + Encrypted: types.BoolTest(false), }, }, }, @@ -159,16 +276,7 @@ Resources: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - fsys := testutil.CreateFS(t, map[string]string{ - "main.yaml": tt.source, - }) - - fctx, err := parser.New().ParseFile(context.TODO(), fsys, "main.yaml") - require.NoError(t, err) - - adapted := Adapt(*fctx) - testutil.AssertDefsecEqual(t, tt.expected, adapted) + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) }) } diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/instance.go b/pkg/iac/adapters/cloudformation/aws/ec2/instance.go index 8a7952f9b809..7b6f149e0168 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/instance.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/instance.go @@ -2,11 +2,11 @@ package ec2 import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getInstances(ctx parser2.FileContext) (instances []ec2.Instance) { +func getInstances(ctx parser.FileContext) (instances []ec2.Instance) { instanceResources := ctx.GetResourcesByType("AWS::EC2::Instance") for _, r := range instanceResources { @@ -48,7 +48,7 @@ func getInstances(ctx parser2.FileContext) (instances []ec2.Instance) { return instances } -func findRelatedLaunchTemplate(fctx parser2.FileContext, r *parser2.Resource) (ec2.LaunchTemplate, bool) { +func findRelatedLaunchTemplate(fctx parser.FileContext, r *parser.Resource) (ec2.LaunchTemplate, bool) { launchTemplateRef := r.GetProperty("LaunchTemplate.LaunchTemplateName") if launchTemplateRef.IsString() { res := findLaunchTemplateByName(fctx, launchTemplateRef) @@ -69,7 +69,7 @@ func findRelatedLaunchTemplate(fctx parser2.FileContext, r *parser2.Resource) (e return adaptLaunchTemplate(resource), true } -func findLaunchTemplateByName(fctx parser2.FileContext, prop *parser2.Property) *parser2.Resource { +func findLaunchTemplateByName(fctx parser.FileContext, prop *parser.Property) *parser.Resource { for _, res := range fctx.GetResourcesByType("AWS::EC2::LaunchTemplate") { templateName := res.GetProperty("LaunchTemplateName") if templateName.IsNotString() { @@ -84,7 +84,7 @@ func findLaunchTemplateByName(fctx parser2.FileContext, prop *parser2.Property) return nil } -func getBlockDevices(r *parser2.Resource) []*ec2.BlockDevice { +func getBlockDevices(r *parser.Resource) []*ec2.BlockDevice { var blockDevices []*ec2.BlockDevice devicesProp := r.GetProperty("BlockDeviceMappings") diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go b/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go index 9dcd80f5d47f..e99459b5d4f0 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/launch_configuration.go @@ -13,14 +13,14 @@ func getLaunchConfigurations(file parser.FileContext) (launchConfigurations []ec launchConfig := ec2.LaunchConfiguration{ Metadata: r.Metadata(), - Name: r.GetStringProperty("Name"), + Name: r.GetStringProperty("LaunchConfigurationName"), AssociatePublicIP: r.GetBoolProperty("AssociatePublicIpAddress"), MetadataOptions: ec2.MetadataOptions{ Metadata: r.Metadata(), HttpTokens: types.StringDefault("optional", r.Metadata()), HttpEndpoint: types.StringDefault("enabled", r.Metadata()), }, - UserData: r.GetStringProperty("UserData", ""), + UserData: r.GetStringProperty("UserData"), } if opts := r.GetProperty("MetadataOptions"); opts.IsNotNil() { diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go b/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go index e22ac9abed3d..c138ed3284e1 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/launch_template.go @@ -2,11 +2,11 @@ package ec2 import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getLaunchTemplates(file parser2.FileContext) (templates []ec2.LaunchTemplate) { +func getLaunchTemplates(file parser.FileContext) (templates []ec2.LaunchTemplate) { launchConfigResources := file.GetResourcesByType("AWS::EC2::LaunchTemplate") for _, r := range launchConfigResources { @@ -15,7 +15,7 @@ func getLaunchTemplates(file parser2.FileContext) (templates []ec2.LaunchTemplat return templates } -func adaptLaunchTemplate(r *parser2.Resource) ec2.LaunchTemplate { +func adaptLaunchTemplate(r *parser.Resource) ec2.LaunchTemplate { launchTemplate := ec2.LaunchTemplate{ Metadata: r.Metadata(), Name: r.GetStringProperty("LaunchTemplateName", ""), diff --git a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go index 687fd12d4366..72546aa116e0 100644 --- a/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go +++ b/pkg/iac/adapters/cloudformation/aws/ec2/security_group.go @@ -2,11 +2,11 @@ package ec2 import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ec2" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getSecurityGroups(ctx parser2.FileContext) (groups []ec2.SecurityGroup) { +func getSecurityGroups(ctx parser.FileContext) (groups []ec2.SecurityGroup) { for _, r := range ctx.GetResourcesByType("AWS::EC2::SecurityGroup") { group := ec2.SecurityGroup{ Metadata: r.Metadata(), @@ -22,7 +22,7 @@ func getSecurityGroups(ctx parser2.FileContext) (groups []ec2.SecurityGroup) { return groups } -func getIngressRules(r *parser2.Resource) (sgRules []ec2.SecurityGroupRule) { +func getIngressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { if ingressProp := r.GetProperty("SecurityGroupIngress"); ingressProp.IsList() { for _, ingress := range ingressProp.AsList() { rule := ec2.SecurityGroupRule{ @@ -45,7 +45,7 @@ func getIngressRules(r *parser2.Resource) (sgRules []ec2.SecurityGroupRule) { return sgRules } -func getEgressRules(r *parser2.Resource) (sgRules []ec2.SecurityGroupRule) { +func getEgressRules(r *parser.Resource) (sgRules []ec2.SecurityGroupRule) { if egressProp := r.GetProperty("SecurityGroupEgress"); egressProp.IsList() { for _, egress := range egressProp.AsList() { rule := ec2.SecurityGroupRule{ diff --git a/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go b/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go new file mode 100644 index 000000000000..cb3e4b6b4b8d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecr/ecr_test.go @@ -0,0 +1,102 @@ +package ecr + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ecr.ECR + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + +`, + expected: ecr.ECR{}, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyRepository: + Type: AWS::ECR::Repository + Properties: + RepositoryName: "test-repository" + ImageScanningConfiguration: + ScanOnPush: true + EncryptionConfiguration: + EncryptionType: KMS + KmsKey: mykey + ImageTagMutability: IMMUTABLE + RepositoryPolicyText: + Version: "2012-10-17" + Statement: + - + Sid: AllowPushPull + Effect: Allow + Principal: + AWS: + - "arn:aws:iam::123456789012:user/Alice" + Action: + - "ecr:GetDownloadUrlForLayer" + - "ecr:BatchGetImage" + `, + expected: ecr.ECR{ + Repositories: []ecr.Repository{ + { + ImageTagsImmutable: types.BoolTest(true), + ImageScanning: ecr.ImageScanning{ + ScanOnPush: types.BoolTest(true), + }, + Encryption: ecr.Encryption{ + Type: types.StringTest("KMS"), + KMSKeyID: types.StringTest("mykey"), + }, + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithSid("AllowPushPull"). + WithEffect("Allow"). + WithAWSPrincipals( + []string{"arn:aws:iam::123456789012:user/Alice"}, + ). + WithActions( + []string{ + "ecr:GetDownloadUrlForLayer", + "ecr:BatchGetImage", + }, + ). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecr/repository.go b/pkg/iac/adapters/cloudformation/aws/ecr/repository.go index 886be64037a3..2c08d57a29c6 100644 --- a/pkg/iac/adapters/cloudformation/aws/ecr/repository.go +++ b/pkg/iac/adapters/cloudformation/aws/ecr/repository.go @@ -7,11 +7,11 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecr" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getRepositories(ctx parser2.FileContext) (repositories []ecr.Repository) { +func getRepositories(ctx parser.FileContext) (repositories []ecr.Repository) { repositoryResources := ctx.GetResourcesByType("AWS::ECR::Repository") @@ -57,7 +57,7 @@ func getRepositories(ctx parser2.FileContext) (repositories []ecr.Repository) { return repositories } -func getPolicy(r *parser2.Resource) (*iam.Policy, error) { +func getPolicy(r *parser.Resource) (*iam.Policy, error) { policyProp := r.GetProperty("RepositoryPolicyText") if policyProp.IsNil() { return nil, fmt.Errorf("missing policy") @@ -79,13 +79,10 @@ func getPolicy(r *parser2.Resource) (*iam.Policy, error) { }, nil } -func hasImmutableImageTags(r *parser2.Resource) iacTypes.BoolValue { +func hasImmutableImageTags(r *parser.Resource) iacTypes.BoolValue { mutabilityProp := r.GetProperty("ImageTagMutability") if mutabilityProp.IsNil() { return iacTypes.BoolDefault(false, r.Metadata()) } - if !mutabilityProp.EqualTo("IMMUTABLE") { - return iacTypes.Bool(false, mutabilityProp.Metadata()) - } - return iacTypes.Bool(true, mutabilityProp.Metadata()) + return iacTypes.Bool(mutabilityProp.EqualTo("IMMUTABLE"), mutabilityProp.Metadata()) } diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go b/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go index 6359dbc4cc93..e3964076d25e 100644 --- a/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go +++ b/pkg/iac/adapters/cloudformation/aws/ecs/cluster.go @@ -2,11 +2,11 @@ package ecs import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getClusters(ctx parser2.FileContext) (clusters []ecs.Cluster) { +func getClusters(ctx parser.FileContext) (clusters []ecs.Cluster) { clusterResources := ctx.GetResourcesByType("AWS::ECS::Cluster") @@ -24,7 +24,7 @@ func getClusters(ctx parser2.FileContext) (clusters []ecs.Cluster) { return clusters } -func getClusterSettings(r *parser2.Resource) ecs.ClusterSettings { +func getClusterSettings(r *parser.Resource) ecs.ClusterSettings { clusterSettings := ecs.ClusterSettings{ Metadata: r.Metadata(), @@ -45,7 +45,7 @@ func getClusterSettings(r *parser2.Resource) ecs.ClusterSettings { return clusterSettings } -func checkProperty(setting *parser2.Property, clusterSettings *ecs.ClusterSettings) { +func checkProperty(setting *parser.Property, clusterSettings *ecs.ClusterSettings) { settingMap := setting.AsMap() name := settingMap["Name"] if name.IsNotNil() && name.EqualTo("containerInsights") { diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go b/pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go new file mode 100644 index 000000000000..c6323a1df926 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ecs/ecs_test.go @@ -0,0 +1,108 @@ +package ecs + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ecs.ECS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + ECSCluster: + Type: 'AWS::ECS::Cluster' + Properties: + ClusterName: MyFargateCluster + ClusterSettings: + - Name: containerInsights + Value: enabled + taskdefinition: + Type: AWS::ECS::TaskDefinition + Properties: + ContainerDefinitions: + - + Name: "busybox" + Image: "busybox" + Cpu: 256 + Memory: 512 + Essential: true + Privileged: true + Environment: + - Name: entryPoint + Value: 'sh, -c' + Volumes: + - + Host: + SourcePath: "/var/lib/docker/vfs/dir/" + Name: "my-vol" + EFSVolumeConfiguration: + TransitEncryption: enabled +`, + expected: ecs.ECS{ + Clusters: []ecs.Cluster{ + { + Settings: ecs.ClusterSettings{ + ContainerInsightsEnabled: types.BoolTest(true), + }, + }, + }, + TaskDefinitions: []ecs.TaskDefinition{ + { + Volumes: []ecs.Volume{ + { + EFSVolumeConfiguration: ecs.EFSVolumeConfiguration{ + TransitEncryptionEnabled: types.BoolTest(true), + }, + }, + }, + ContainerDefinitions: []ecs.ContainerDefinition{ + { + Name: types.StringTest("busybox"), + Image: types.StringTest("busybox"), + CPU: types.IntTest(256), + Memory: types.IntTest(512), + Essential: types.BoolTest(true), + Privileged: types.BoolTest(true), + Environment: []ecs.EnvVar{ + { + Name: "entryPoint", + Value: "sh, -c", + }, + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ECSCluster: + Type: 'AWS::ECS::Cluster' + taskdefinition: + Type: AWS::ECS::TaskDefinition + `, + expected: ecs.ECS{ + Clusters: []ecs.Cluster{{}}, + TaskDefinitions: []ecs.TaskDefinition{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go b/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go index cdb9ae08ab45..9c2e342bb6f3 100644 --- a/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go +++ b/pkg/iac/adapters/cloudformation/aws/ecs/task_definition.go @@ -2,11 +2,11 @@ package ecs import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ecs" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getTaskDefinitions(ctx parser2.FileContext) (taskDefinitions []ecs.TaskDefinition) { +func getTaskDefinitions(ctx parser.FileContext) (taskDefinitions []ecs.TaskDefinition) { taskDefResources := ctx.GetResourcesByType("AWS::ECS::TaskDefinition") @@ -23,7 +23,7 @@ func getTaskDefinitions(ctx parser2.FileContext) (taskDefinitions []ecs.TaskDefi return taskDefinitions } -func getContainerDefinitions(r *parser2.Resource) ([]ecs.ContainerDefinition, error) { +func getContainerDefinitions(r *parser.Resource) ([]ecs.ContainerDefinition, error) { var definitions []ecs.ContainerDefinition containerDefs := r.GetProperty("ContainerDefinitions") if containerDefs.IsNil() || containerDefs.IsNotList() { @@ -36,19 +36,19 @@ func getContainerDefinitions(r *parser2.Resource) ([]ecs.ContainerDefinition, er if envVarsList.IsNotNil() && envVarsList.IsList() { for _, envVar := range envVarsList.AsList() { envVars = append(envVars, ecs.EnvVar{ - Name: envVar.GetStringProperty("Name", "").Value(), - Value: envVar.GetStringProperty("Value", "").Value(), + Name: envVar.GetStringProperty("Name").Value(), + Value: envVar.GetStringProperty("Value").Value(), }) } } definition := ecs.ContainerDefinition{ Metadata: containerDef.Metadata(), - Name: containerDef.GetStringProperty("Name", ""), - Image: containerDef.GetStringProperty("Image", ""), - CPU: containerDef.GetIntProperty("CPU", 1), - Memory: containerDef.GetIntProperty("Memory", 128), - Essential: containerDef.GetBoolProperty("Essential", false), - Privileged: containerDef.GetBoolProperty("Privileged", false), + Name: containerDef.GetStringProperty("Name"), + Image: containerDef.GetStringProperty("Image"), + CPU: containerDef.GetIntProperty("Cpu"), + Memory: containerDef.GetIntProperty("Memory"), + Essential: containerDef.GetBoolProperty("Essential"), + Privileged: containerDef.GetBoolProperty("Privileged"), Environment: envVars, PortMappings: nil, } @@ -60,7 +60,7 @@ func getContainerDefinitions(r *parser2.Resource) ([]ecs.ContainerDefinition, er return definitions, nil } -func getVolumes(r *parser2.Resource) (volumes []ecs.Volume) { +func getVolumes(r *parser.Resource) (volumes []ecs.Volume) { volumesList := r.GetProperty("Volumes") if volumesList.IsNil() || volumesList.IsNotList() { @@ -76,7 +76,7 @@ func getVolumes(r *parser2.Resource) (volumes []ecs.Volume) { }, } transitProp := v.GetProperty("EFSVolumeConfiguration.TransitEncryption") - if transitProp.IsNotNil() && transitProp.EqualTo("enabled", parser2.IgnoreCase) { + if transitProp.IsNotNil() && transitProp.EqualTo("enabled", parser.IgnoreCase) { volume.EFSVolumeConfiguration.TransitEncryptionEnabled = types.Bool(true, transitProp.Metadata()) } diff --git a/pkg/iac/adapters/cloudformation/aws/efs/efs_test.go b/pkg/iac/adapters/cloudformation/aws/efs/efs_test.go new file mode 100644 index 000000000000..a22d769020b6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/efs/efs_test.go @@ -0,0 +1,52 @@ +package efs + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/efs" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected efs.EFS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + FileSystemResource: + Type: 'AWS::EFS::FileSystem' + Properties: + Encrypted: true +`, + expected: efs.EFS{ + FileSystems: []efs.FileSystem{ + { + Encrypted: types.BoolTest(true), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + FileSystemResource: + Type: 'AWS::EFS::FileSystem' + `, + expected: efs.EFS{ + FileSystems: []efs.FileSystem{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/eks/cluster.go b/pkg/iac/adapters/cloudformation/aws/eks/cluster.go index 07adedf06c21..d4c80e72dbd4 100644 --- a/pkg/iac/adapters/cloudformation/aws/eks/cluster.go +++ b/pkg/iac/adapters/cloudformation/aws/eks/cluster.go @@ -2,11 +2,11 @@ package eks import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getClusters(ctx parser2.FileContext) (clusters []eks.Cluster) { +func getClusters(ctx parser.FileContext) (clusters []eks.Cluster) { clusterResources := ctx.GetResourcesByType("AWS::EKS::Cluster") @@ -14,6 +14,7 @@ func getClusters(ctx parser2.FileContext) (clusters []eks.Cluster) { cluster := eks.Cluster{ Metadata: r.Metadata(), // Logging not supported for cloudformation https://github.com/aws/containers-roadmap/issues/242 + // TODO: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-logging Logging: eks.Logging{ Metadata: r.Metadata(), API: iacTypes.BoolUnresolvable(r.Metadata()), @@ -24,6 +25,7 @@ func getClusters(ctx parser2.FileContext) (clusters []eks.Cluster) { }, Encryption: getEncryptionConfig(r), // endpoint protection not supported - https://github.com/aws/containers-roadmap/issues/242 + // TODO: https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-resourcesvpcconfig PublicAccessEnabled: iacTypes.BoolUnresolvable(r.Metadata()), PublicAccessCIDRs: nil, } @@ -33,7 +35,7 @@ func getClusters(ctx parser2.FileContext) (clusters []eks.Cluster) { return clusters } -func getEncryptionConfig(r *parser2.Resource) eks.Encryption { +func getEncryptionConfig(r *parser.Resource) eks.Encryption { encryption := eks.Encryption{ Metadata: r.Metadata(), @@ -41,6 +43,8 @@ func getEncryptionConfig(r *parser2.Resource) eks.Encryption { KMSKeyID: iacTypes.StringDefault("", r.Metadata()), } + // TODO: EncryptionConfig is a list + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-eks-cluster.html#cfn-eks-cluster-encryptionconfig if encProp := r.GetProperty("EncryptionConfig"); encProp.IsNotNil() { encryption.Metadata = encProp.Metadata() encryption.KMSKeyID = encProp.GetStringProperty("Provider.KeyArn") diff --git a/pkg/iac/adapters/cloudformation/aws/eks/eks_test.go b/pkg/iac/adapters/cloudformation/aws/eks/eks_test.go new file mode 100644 index 000000000000..84095c3b6592 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/eks/eks_test.go @@ -0,0 +1,45 @@ +package eks + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/eks" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected eks.EKS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + EKSCluster: + Type: AWS::EKS::Cluster +`, + expected: eks.EKS{ + Clusters: []eks.Cluster{{}}, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + EKSCluster: + Type: AWS::EKS::Cluster + `, + expected: eks.EKS{ + Clusters: []eks.Cluster{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go b/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go new file mode 100644 index 000000000000..e7e3d018b14c --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticache/elasticache_test.go @@ -0,0 +1,82 @@ +package elasticache + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticache" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected elasticache.ElastiCache + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + Properties: + Engine: memcached + CacheNodeType: cache.t2.micro + SnapshotRetentionLimit: 5 + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + Properties: + TransitEncryptionEnabled: true + AtRestEncryptionEnabled: true + mySecGroup: + Type: AWS::ElastiCache::SecurityGroup + Properties: + Description: test +`, + expected: elasticache.ElastiCache{ + Clusters: []elasticache.Cluster{ + { + Engine: types.StringTest("memcached"), + NodeType: types.StringTest("cache.t2.micro"), + SnapshotRetentionLimit: types.IntTest(5), + }, + }, + ReplicationGroups: []elasticache.ReplicationGroup{ + { + TransitEncryptionEnabled: types.BoolTest(true), + AtRestEncryptionEnabled: types.BoolTest(true), + }, + }, + SecurityGroups: []elasticache.SecurityGroup{ + { + Description: types.StringTest("test"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ElasticacheCluster: + Type: 'AWS::ElastiCache::CacheCluster' + myReplicationGroup: + Type: 'AWS::ElastiCache::ReplicationGroup' + mySecGroup: + Type: AWS::ElastiCache::SecurityGroup + `, + expected: elasticache.ElastiCache{ + Clusters: []elasticache.Cluster{{}}, + ReplicationGroups: []elasticache.ReplicationGroup{{}}, + SecurityGroups: []elasticache.SecurityGroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go b/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go index 5ff46bc41cbd..26747e44999b 100644 --- a/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go +++ b/pkg/iac/adapters/cloudformation/aws/elasticsearch/domain.go @@ -13,9 +13,10 @@ func getDomains(ctx parser.FileContext) (domains []elasticsearch.Domain) { for _, r := range domainResources { domain := elasticsearch.Domain{ - Metadata: r.Metadata(), - DomainName: r.GetStringProperty("DomainName"), - AccessPolicies: r.GetStringProperty("AccessPolicies"), + Metadata: r.Metadata(), + DomainName: r.GetStringProperty("DomainName"), + AccessPolicies: r.GetStringProperty("AccessPolicies"), + // TODO: ElasticsearchClusterConfig changed to ClusterConfig DedicatedMasterEnabled: r.GetBoolProperty("ElasticsearchClusterConfig.DedicatedMasterEnabled"), VpcId: iacTypes.String("", r.Metadata()), LogPublishing: elasticsearch.LogPublishing{ @@ -35,7 +36,6 @@ func getDomains(ctx parser.FileContext) (domains []elasticsearch.Domain) { Endpoint: elasticsearch.Endpoint{ Metadata: r.Metadata(), EnforceHTTPS: iacTypes.BoolDefault(false, r.Metadata()), - TLSPolicy: iacTypes.StringDefault("Policy-Min-TLS-1-0-2019-07", r.Metadata()), }, ServiceSoftwareOptions: elasticsearch.ServiceSoftwareOptions{ Metadata: r.Metadata(), @@ -49,22 +49,22 @@ func getDomains(ctx parser.FileContext) (domains []elasticsearch.Domain) { if prop := r.GetProperty("LogPublishingOptions"); prop.IsNotNil() { domain.LogPublishing = elasticsearch.LogPublishing{ Metadata: prop.Metadata(), - AuditEnabled: prop.GetBoolProperty("AUDIT_LOGS.Enabled", false), - CloudWatchLogGroupArn: prop.GetStringProperty("CloudWatchLogsLogGroupArn"), + AuditEnabled: prop.GetBoolProperty("AUDIT_LOGS.Enabled"), + CloudWatchLogGroupArn: prop.GetStringProperty("AUDIT_LOGS.CloudWatchLogsLogGroupArn"), } } if prop := r.GetProperty("NodeToNodeEncryptionOptions"); prop.IsNotNil() { domain.TransitEncryption = elasticsearch.TransitEncryption{ Metadata: prop.Metadata(), - Enabled: prop.GetBoolProperty("Enabled", false), + Enabled: prop.GetBoolProperty("Enabled"), } } if prop := r.GetProperty("EncryptionAtRestOptions"); prop.IsNotNil() { domain.AtRestEncryption = elasticsearch.AtRestEncryption{ Metadata: prop.Metadata(), - Enabled: prop.GetBoolProperty("Enabled", false), + Enabled: prop.GetBoolProperty("Enabled"), KmsKeyId: prop.GetStringProperty("KmsKeyId"), } } @@ -72,8 +72,8 @@ func getDomains(ctx parser.FileContext) (domains []elasticsearch.Domain) { if prop := r.GetProperty("DomainEndpointOptions"); prop.IsNotNil() { domain.Endpoint = elasticsearch.Endpoint{ Metadata: prop.Metadata(), - EnforceHTTPS: prop.GetBoolProperty("EnforceHTTPS", false), - TLSPolicy: prop.GetStringProperty("TLSSecurityPolicy", "Policy-Min-TLS-1-0-2019-07"), + EnforceHTTPS: prop.GetBoolProperty("EnforceHTTPS"), + TLSPolicy: prop.GetStringProperty("TLSSecurityPolicy"), } } diff --git a/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go b/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go new file mode 100644 index 000000000000..514c689b8d28 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/elasticsearch/elasticsearch_test.go @@ -0,0 +1,88 @@ +package elasticsearch + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elasticsearch" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected elasticsearch.Elasticsearch + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + OpenSearchServiceDomain: + Type: AWS::OpenSearchService::Domain + Properties: + DomainName: 'test' + NodeToNodeEncryptionOptions: + Enabled: true + EncryptionAtRestOptions: + Enabled: true + KmsKeyId: mykey + DomainEndpointOptions: + EnforceHTTPS: true + TLSSecurityPolicy: Policy-Min-TLS-1-0-2019-07 + AccessPolicies: + Version: '2012-10-17' + Statement: + - + Effect: 'Allow' + Principal: + AWS: 'arn:aws:iam::123456789012:user/opensearch-user' + Action: 'es:*' + Resource: 'arn:aws:es:us-east-1:846973539254:domain/test/*' + LogPublishingOptions: + AUDIT_LOGS: + CloudWatchLogsLogGroupArn: 'arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearch/domains/opensearch-application-logs' + Enabled: true +`, + expected: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{ + { + DomainName: types.StringTest("test"), + LogPublishing: elasticsearch.LogPublishing{ + AuditEnabled: types.BoolTest(true), + CloudWatchLogGroupArn: types.StringTest("arn:aws:logs:us-east-1:123456789012:log-group:/aws/opensearch/domains/opensearch-application-logs"), + }, + TransitEncryption: elasticsearch.TransitEncryption{ + Enabled: types.BoolTest(true), + }, + AtRestEncryption: elasticsearch.AtRestEncryption{ + Enabled: types.BoolTest(true), + KmsKeyId: types.StringTest("mykey"), + }, + Endpoint: elasticsearch.Endpoint{ + EnforceHTTPS: types.BoolTest(true), + TLSPolicy: types.StringTest("Policy-Min-TLS-1-0-2019-07"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + OpenSearchServiceDomain: + Type: AWS::OpenSearchService::Domain + `, + expected: elasticsearch.Elasticsearch{ + Domains: []elasticsearch.Domain{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go index ca8fd631fa39..2c5ee494e66d 100644 --- a/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/elb/adapt_test.go @@ -1,14 +1,11 @@ package elb import ( - "context" "testing" - "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" ) func TestAdapt(t *testing.T) { @@ -18,7 +15,7 @@ func TestAdapt(t *testing.T) { expected elb.ELB }{ { - name: "LoadBalancer", + name: "complete", source: `AWSTemplateFormatVersion: "2010-09-09" Resources: LoadBalancer: @@ -27,6 +24,7 @@ Resources: - ALBLogsBucketPermission Properties: Name: "k8s-dev" + Scheme: internal IpAddressType: ipv4 LoadBalancerAttributes: - Key: routing.http2.enabled @@ -43,13 +41,36 @@ Resources: - Key: elbv2.k8s.aws/cluster Value: "biomage-dev" Type: application + Listener: + Type: AWS::ElasticLoadBalancingV2::Listener + Properties: + DefaultActions: + - Type: 'redirect' + RedirectConfig: + Port: 443 + Protocol: HTTPS + StatusCode: HTTP_302 + LoadBalancerArn: !Ref LoadBalancer + Protocol: HTTPS + SslPolicy: "ELBSecurityPolicy-TLS-1-2-2017-01" `, expected: elb.ELB{ LoadBalancers: []elb.LoadBalancer{ { - Metadata: types.NewTestMetadata(), - Type: types.String("application", types.NewTestMetadata()), - DropInvalidHeaderFields: types.Bool(true, types.NewTestMetadata()), + Type: types.StringTest("application"), + DropInvalidHeaderFields: types.BoolTest(true), + Internal: types.Bool(true, types.NewTestMetadata()), + Listeners: []elb.Listener{ + { + Protocol: types.StringTest("HTTPS"), + TLSPolicy: types.StringTest("ELBSecurityPolicy-TLS-1-2-2017-01"), + DefaultActions: []elb.Action{ + { + Type: types.StringTest("redirect"), + }, + }, + }, + }, }, }, }, @@ -58,15 +79,7 @@ Resources: 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)) + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) }) } } diff --git a/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go b/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go index 50b8f26275d5..002b6487ba43 100644 --- a/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go +++ b/pkg/iac/adapters/cloudformation/aws/elb/loadbalancer.go @@ -2,11 +2,11 @@ package elb import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/elb" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getLoadBalancers(ctx parser2.FileContext) (loadbalancers []elb.LoadBalancer) { +func getLoadBalancers(ctx parser.FileContext) (loadbalancers []elb.LoadBalancer) { loadBalanacerResources := ctx.GetResourcesByType("AWS::ElasticLoadBalancingV2::LoadBalancer") @@ -24,7 +24,7 @@ func getLoadBalancers(ctx parser2.FileContext) (loadbalancers []elb.LoadBalancer return loadbalancers } -func getListeners(lbr *parser2.Resource, ctx parser2.FileContext) (listeners []elb.Listener) { +func getListeners(lbr *parser.Resource, ctx parser.FileContext) (listeners []elb.Listener) { listenerResources := ctx.GetResourcesByType("AWS::ElasticLoadBalancingV2::Listener") @@ -43,7 +43,7 @@ func getListeners(lbr *parser2.Resource, ctx parser2.FileContext) (listeners []e return listeners } -func getDefaultListenerActions(r *parser2.Resource) (actions []elb.Action) { +func getDefaultListenerActions(r *parser.Resource) (actions []elb.Action) { defaultActionsProp := r.GetProperty("DefaultActions") if defaultActionsProp.IsNotList() { return actions @@ -57,15 +57,15 @@ func getDefaultListenerActions(r *parser2.Resource) (actions []elb.Action) { return actions } -func isInternal(r *parser2.Resource) types.BoolValue { +func isInternal(r *parser.Resource) types.BoolValue { schemeProp := r.GetProperty("Scheme") if schemeProp.IsNotString() { return r.BoolDefault(false) } - return types.Bool(schemeProp.EqualTo("internal", parser2.IgnoreCase), schemeProp.Metadata()) + return types.Bool(schemeProp.EqualTo("internal", parser.IgnoreCase), schemeProp.Metadata()) } -func checkForDropInvalidHeaders(r *parser2.Resource) types.BoolValue { +func checkForDropInvalidHeaders(r *parser.Resource) types.BoolValue { attributesProp := r.GetProperty("LoadBalancerAttributes") if attributesProp.IsNotList() { return types.BoolDefault(false, r.Metadata()) diff --git a/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go b/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go new file mode 100644 index 000000000000..3e548dec0cf7 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/iam/iam_test.go @@ -0,0 +1,189 @@ +package iam + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected iam.IAM + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + myIAMPolicy: + Type: 'AWS::IAM::Policy' + Properties: + PolicyName: TestPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - 'cloudformation:Describe*' + Resource: '*' + Groups: + - !Ref MyGroup + Users: + - !Ref PublishUser + Roles: + - !Ref MyRole + MyGroup: + Type: AWS::IAM::Group + Properties: + GroupName: TestGroup + Policies: + - PolicyName: TestGroupPolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Resource: arn:*:cloudfront::*:distribution/* + Action: + - cloudfront:CreateDistribution + MyUser: + Type: AWS::IAM::User + Properties: + UserName: TestUser + Policies: + - PolicyName: TestUserPolicy + PolicyDocument: + Statement: + - Action: 's3:*' + Effect: Allow + Resource: + - 'arn:aws:s3:::testbucket' + MyRole: + Type: 'AWS::IAM::Role' + Properties: + RoleName: TestRole + Policies: + - PolicyName: TestRolePolicy + PolicyDocument: + Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - 'sts:AssumeRole' + AccessKey: + Type: AWS::IAM::AccessKey + Properties: + UserName: !Ref MyUser + Status: Active +`, + expected: iam.IAM{ + Policies: []iam.Policy{ + { + Name: types.StringTest("TestPolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"cloudformation:Describe*"}). + WithResources([]string{"*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + Users: []iam.User{ + { + Name: types.StringTest("TestUser"), + Policies: []iam.Policy{ + { + Name: types.StringTest("TestUserPolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"s3:*"}). + WithResources([]string{"arn:aws:s3:::testbucket"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + Groups: []iam.Group{ + { + Name: types.StringTest("TestGroup"), + Policies: []iam.Policy{ + { + Name: types.StringTest("TestGroupPolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"cloudfront:CreateDistribution"}). + WithResources([]string{"arn:*:cloudfront::*:distribution/*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + Roles: []iam.Role{ + { + Name: types.StringTest("TestRole"), + Policies: []iam.Policy{ + { + Name: types.StringTest("TestRolePolicy"), + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"sts:AssumeRole"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + + `, + expected: iam.IAM{}, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/iam/policy.go b/pkg/iac/adapters/cloudformation/aws/iam/policy.go index 9843c8cdaa43..f83771f882d2 100644 --- a/pkg/iac/adapters/cloudformation/aws/iam/policy.go +++ b/pkg/iac/adapters/cloudformation/aws/iam/policy.go @@ -4,11 +4,11 @@ import ( "github.com/liamg/iamgo" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getPolicies(ctx parser2.FileContext) (policies []iam.Policy) { +func getPolicies(ctx parser.FileContext) (policies []iam.Policy) { for _, policyResource := range ctx.GetResourcesByType("AWS::IAM::Policy") { policy := iam.Policy{ @@ -34,7 +34,7 @@ func getPolicies(ctx parser2.FileContext) (policies []iam.Policy) { return policies } -func getRoles(ctx parser2.FileContext) (roles []iam.Role) { +func getRoles(ctx parser.FileContext) (roles []iam.Role) { for _, roleResource := range ctx.GetResourcesByType("AWS::IAM::Role") { policyProp := roleResource.GetProperty("Policies") roleName := roleResource.GetStringProperty("RoleName") @@ -48,10 +48,10 @@ func getRoles(ctx parser2.FileContext) (roles []iam.Role) { return roles } -func getUsers(ctx parser2.FileContext) (users []iam.User) { +func getUsers(ctx parser.FileContext) (users []iam.User) { for _, userResource := range ctx.GetResourcesByType("AWS::IAM::User") { policyProp := userResource.GetProperty("Policies") - userName := userResource.GetStringProperty("GroupName") + userName := userResource.GetStringProperty("UserName") users = append(users, iam.User{ Metadata: userResource.Metadata(), @@ -64,7 +64,8 @@ func getUsers(ctx parser2.FileContext) (users []iam.User) { return users } -func getAccessKeys(ctx parser2.FileContext, username string) (accessKeys []iam.AccessKey) { +func getAccessKeys(ctx parser.FileContext, username string) (accessKeys []iam.AccessKey) { + // TODO: also search for a key by the logical id of the resource for _, keyResource := range ctx.GetResourcesByType("AWS::IAM::AccessKey") { keyUsername := keyResource.GetStringProperty("UserName") if !keyUsername.EqualTo(username) { @@ -86,7 +87,7 @@ func getAccessKeys(ctx parser2.FileContext, username string) (accessKeys []iam.A return accessKeys } -func getGroups(ctx parser2.FileContext) (groups []iam.Group) { +func getGroups(ctx parser.FileContext) (groups []iam.Group) { for _, groupResource := range ctx.GetResourcesByType("AWS::IAM::Group") { policyProp := groupResource.GetProperty("Policies") groupName := groupResource.GetStringProperty("GroupName") @@ -100,7 +101,7 @@ func getGroups(ctx parser2.FileContext) (groups []iam.Group) { return groups } -func getPoliciesDocs(policiesProp *parser2.Property) []iam.Policy { +func getPoliciesDocs(policiesProp *parser.Property) []iam.Policy { var policies []iam.Policy for _, policy := range policiesProp.AsList() { diff --git a/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go b/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go new file mode 100644 index 000000000000..ce38afadf806 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/kinesis/kinesis_test.go @@ -0,0 +1,57 @@ +package kinesis + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected kinesis.Kinesis + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyStream: + Type: 'AWS::Kinesis::Stream' + Properties: + StreamEncryption: + EncryptionType: KMS + KeyId: key +`, + expected: kinesis.Kinesis{ + Streams: []kinesis.Stream{ + { + Encryption: kinesis.Encryption{ + Type: types.StringTest("KMS"), + KMSKeyID: types.StringTest("key"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyStream: + Type: 'AWS::Kinesis::Stream' + `, + expected: kinesis.Kinesis{ + Streams: []kinesis.Stream{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go b/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go index b2bc8bac3411..6c10ec6134c0 100644 --- a/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go +++ b/pkg/iac/adapters/cloudformation/aws/kinesis/stream.go @@ -3,7 +3,6 @@ package kinesis import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/kinesis" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func getStreams(ctx parser.FileContext) (streams []kinesis.Stream) { @@ -14,11 +13,6 @@ func getStreams(ctx parser.FileContext) (streams []kinesis.Stream) { stream := kinesis.Stream{ Metadata: r.Metadata(), - Encryption: kinesis.Encryption{ - Metadata: r.Metadata(), - Type: types.StringDefault("KMS", r.Metadata()), - KMSKeyID: types.StringDefault("", r.Metadata()), - }, } if prop := r.GetProperty("StreamEncryption"); prop.IsNotNil() { diff --git a/pkg/iac/adapters/cloudformation/aws/lambda/function.go b/pkg/iac/adapters/cloudformation/aws/lambda/function.go index 02bde4b903ff..f91a565d193a 100644 --- a/pkg/iac/adapters/cloudformation/aws/lambda/function.go +++ b/pkg/iac/adapters/cloudformation/aws/lambda/function.go @@ -2,29 +2,24 @@ package lambda import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" - "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" ) -func getFunctions(ctx parser2.FileContext) (functions []lambda.Function) { +func getFunctions(ctx parser.FileContext) (functions []lambda.Function) { functionResources := ctx.GetResourcesByType("AWS::Lambda::Function") for _, r := range functionResources { function := lambda.Function{ - Metadata: r.Metadata(), - Tracing: lambda.Tracing{ - Metadata: r.Metadata(), - Mode: types.StringDefault("PassThrough", r.Metadata()), - }, + Metadata: r.Metadata(), Permissions: getPermissions(r, ctx), } if prop := r.GetProperty("TracingConfig"); prop.IsNotNil() { function.Tracing = lambda.Tracing{ Metadata: prop.Metadata(), - Mode: prop.GetStringProperty("Mode", "PassThrough"), + Mode: prop.GetStringProperty("Mode"), } } @@ -34,7 +29,7 @@ func getFunctions(ctx parser2.FileContext) (functions []lambda.Function) { return functions } -func getPermissions(funcR *parser2.Resource, ctx parser2.FileContext) (perms []lambda.Permission) { +func getPermissions(funcR *parser.Resource, ctx parser.FileContext) (perms []lambda.Permission) { permissionResources := ctx.GetResourcesByType("AWS::Lambda::Permission") diff --git a/pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go b/pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go new file mode 100644 index 000000000000..4262181f89ee --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/lambda/lambda_test.go @@ -0,0 +1,76 @@ +package lambda + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/lambda" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected lambda.Lambda + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + lambdaFunction: + Type: AWS::Lambda::Function + Properties: + TracingConfig: + Mode: Active + permission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref lambdaFunction + Action: lambda:InvokeFunction + Principal: s3.amazonaws.com + SourceArn: arn +`, + expected: lambda.Lambda{ + Functions: []lambda.Function{ + { + Tracing: lambda.Tracing{ + Mode: types.StringTest("Active"), + }, + Permissions: []lambda.Permission{ + { + Principal: types.StringTest("s3.amazonaws.com"), + SourceARN: types.StringTest("arn"), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + lambdaFunction: + Type: AWS::Lambda::Function + permission: + Type: AWS::Lambda::Permission + Properties: + FunctionName: !Ref lambdaFunction + `, + expected: lambda.Lambda{ + Functions: []lambda.Function{ + { + Permissions: []lambda.Permission{{}}, + }, + }, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/mq/mq_test.go b/pkg/iac/adapters/cloudformation/aws/mq/mq_test.go new file mode 100644 index 000000000000..b4f1d5048898 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/mq/mq_test.go @@ -0,0 +1,59 @@ +package mq + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/mq" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected mq.MQ + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + BasicBroker: + Type: "AWS::AmazonMQ::Broker" + Properties: + PubliclyAccessible: true + Logs: + Audit: true + General: true +`, + expected: mq.MQ{ + Brokers: []mq.Broker{ + { + PublicAccess: types.BoolTest(true), + Logging: mq.Logging{ + Audit: types.BoolTest(true), + General: types.BoolTest(true), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + BasicBroker: + Type: "AWS::AmazonMQ::Broker" + `, + expected: mq.MQ{ + Brokers: []mq.Broker{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/msk/msk_test.go b/pkg/iac/adapters/cloudformation/aws/msk/msk_test.go new file mode 100644 index 000000000000..2cc5a6cd0945 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/msk/msk_test.go @@ -0,0 +1,87 @@ +package msk + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/msk" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected msk.MSK + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + cluster: + Type: AWS::MSK::Cluster + Properties: + EncryptionInfo: + EncryptionInTransit: + ClientBroker: 'PLAINTEXT' + EncryptionAtRest: + DataVolumeKMSKeyId: key + LoggingInfo: + BrokerLogs: + S3: + Enabled: true + CloudWatchLogs: + Enabled: true + Firehose: + Enabled: true +`, + expected: msk.MSK{ + Clusters: []msk.Cluster{ + { + EncryptionInTransit: msk.EncryptionInTransit{ + ClientBroker: types.StringTest("PLAINTEXT"), + }, + EncryptionAtRest: msk.EncryptionAtRest{ + KMSKeyARN: types.StringTest("key"), + Enabled: types.BoolTest(true), + }, + Logging: msk.Logging{ + Broker: msk.BrokerLogging{ + S3: msk.S3Logging{ + Enabled: types.BoolTest(true), + }, + Firehose: msk.FirehoseLogging{ + Enabled: types.BoolTest(true), + }, + Cloudwatch: msk.CloudwatchLogging{ + Enabled: types.BoolTest(true), + }, + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cluster: + Type: AWS::MSK::Cluster + `, + expected: msk.MSK{ + Clusters: []msk.Cluster{{ + EncryptionInTransit: msk.EncryptionInTransit{ + ClientBroker: types.StringTest("TLS"), + }, + }}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go b/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go index 3685a655aee2..33012cbd2236 100644 --- a/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go +++ b/pkg/iac/adapters/cloudformation/aws/neptune/cluster.go @@ -2,11 +2,11 @@ package neptune import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getClusters(ctx parser2.FileContext) (clusters []neptune.Cluster) { +func getClusters(ctx parser.FileContext) (clusters []neptune.Cluster) { for _, r := range ctx.GetResourcesByType("AWS::Neptune::DBCluster") { cluster := neptune.Cluster{ @@ -23,7 +23,7 @@ func getClusters(ctx parser2.FileContext) (clusters []neptune.Cluster) { return clusters } -func getAuditLog(r *parser2.Resource) types.BoolValue { +func getAuditLog(r *parser.Resource) types.BoolValue { if logsProp := r.GetProperty("EnableCloudwatchLogsExports"); logsProp.IsList() { if logsProp.Contains("audit") { return types.Bool(true, logsProp.Metadata()) diff --git a/pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go b/pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go new file mode 100644 index 000000000000..8e63a481ff2e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/neptune/neptune_test.go @@ -0,0 +1,59 @@ +package neptune + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/neptune" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected neptune.Neptune + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + cluster: + Type: AWS::Neptune::DBCluster + Properties: + StorageEncrypted: true + KmsKeyId: key + EnableCloudwatchLogsExports: + - audit +`, + expected: neptune.Neptune{ + Clusters: []neptune.Cluster{ + { + StorageEncrypted: types.BoolTest(true), + KMSKeyID: types.StringTest("key"), + Logging: neptune.Logging{ + Audit: types.BoolTest(true), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + cluster: + Type: AWS::Neptune::DBCluster + `, + expected: neptune.Neptune{ + Clusters: []neptune.Cluster{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go b/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go index 7685c3118a0e..4875395c8506 100644 --- a/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go +++ b/pkg/iac/adapters/cloudformation/aws/rds/adapt_test.go @@ -1,14 +1,11 @@ package rds import ( - "context" "testing" - "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" ) func TestAdapt(t *testing.T) { @@ -18,7 +15,7 @@ func TestAdapt(t *testing.T) { expected rds.RDS }{ { - name: "cluster with instances", + name: "complete", source: `AWSTemplateFormatVersion: 2010-09-09 Resources: RDSCluster: @@ -65,92 +62,113 @@ Resources: Properties: Description: "CloudFormation Sample MySQL Parameter Group" DBParameterGroupName: "testgroup" + Parameters: + sql_mode: IGNORE_SPACE + DbSecurityByEC2SecurityGroup: + Type: AWS::RDS::DBSecurityGroup + Properties: + GroupDescription: "Ingress for Amazon EC2 security group" `, expected: rds.RDS{ + Classic: rds.Classic{ + DBSecurityGroups: []rds.DBSecurityGroup{{}}, + }, ParameterGroups: []rds.ParameterGroups{ { - Metadata: types.NewTestMetadata(), - DBParameterGroupName: types.String("testgroup", types.NewTestMetadata()), + DBParameterGroupName: types.StringTest("testgroup"), }, }, Clusters: []rds.Cluster{ { - Metadata: types.NewTestMetadata(), - BackupRetentionPeriodDays: types.Int(2, types.NewTestMetadata()), - Engine: types.String("aurora-postgresql", types.NewTestMetadata()), + BackupRetentionPeriodDays: types.IntTest(2), + Engine: types.StringTest("aurora-postgresql"), Encryption: rds.Encryption{ - EncryptStorage: types.Bool(true, types.NewTestMetadata()), - KMSKeyID: types.String("your-kms-key-id", types.NewTestMetadata()), + EncryptStorage: types.BoolTest(true), + KMSKeyID: types.StringTest("your-kms-key-id"), }, PerformanceInsights: rds.PerformanceInsights{ - Metadata: types.NewTestMetadata(), - Enabled: types.Bool(true, types.NewTestMetadata()), - KMSKeyID: types.String("test-kms-key-id", types.NewTestMetadata()), + Enabled: types.BoolTest(true), + KMSKeyID: types.StringTest("test-kms-key-id"), }, - PublicAccess: types.Bool(false, types.NewTestMetadata()), - DeletionProtection: types.Bool(true, types.NewTestMetadata()), + PublicAccess: types.BoolTest(false), + DeletionProtection: types.BoolTest(true), Instances: []rds.ClusterInstance{ { Instance: rds.Instance{ - Metadata: types.NewTestMetadata(), - StorageEncrypted: types.Bool(true, types.NewTestMetadata()), + StorageEncrypted: types.BoolTest(true), Encryption: rds.Encryption{ - EncryptStorage: types.Bool(true, types.NewTestMetadata()), - KMSKeyID: types.String("your-kms-key-id", types.NewTestMetadata()), + EncryptStorage: types.BoolTest(true), + KMSKeyID: types.StringTest("your-kms-key-id"), }, - 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()), + DBInstanceIdentifier: types.StringTest("test"), + PubliclyAccessible: types.BoolTest(false), + PublicAccess: types.BoolTest(false), + BackupRetentionPeriodDays: types.IntTest(1), + Engine: types.StringTest("aurora-mysql"), + EngineVersion: types.StringTest("5.7.12"), + MultiAZ: types.BoolTest(true), + AutoMinorVersionUpgrade: types.BoolTest(true), + DBInstanceArn: types.StringTest("arn:aws:rds:us-east-2:123456789012:db:my-mysql-instance-1"), + IAMAuthEnabled: types.BoolTest(true), PerformanceInsights: rds.PerformanceInsights{ - Metadata: types.NewTestMetadata(), - Enabled: types.Bool(true, types.NewTestMetadata()), - KMSKeyID: types.String("test-kms-key-id2", types.NewTestMetadata()), + Enabled: types.BoolTest(true), + KMSKeyID: types.StringTest("test-kms-key-id2"), }, EnabledCloudwatchLogsExports: []types.StringValue{ - types.String("error", types.NewTestMetadata()), - types.String("general", types.NewTestMetadata()), + types.StringTest("error"), + types.StringTest("general"), }, DBParameterGroups: []rds.DBParameterGroupsList{ { - DBParameterGroupName: types.String("testgroup", types.NewTestMetadata()), + DBParameterGroupName: types.StringTest("testgroup"), }, }, TagList: []rds.TagList{ - { - Metadata: types.NewTestMetadata(), - }, - { - Metadata: types.NewTestMetadata(), - }, + {}, + {}, }, }, - ClusterIdentifier: types.String("RDSCluster", types.NewTestMetadata()), + ClusterIdentifier: types.StringTest("RDSCluster"), }, }, }, }, }, }, + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + RDSCluster: + Type: 'AWS::RDS::DBCluster' + RDSDBInstance1: + Type: 'AWS::RDS::DBInstance' + RDSDBParameterGroup: + Type: 'AWS::RDS::DBParameterGroup' + DbSecurityByEC2SecurityGroup: + Type: AWS::RDS::DBSecurityGroup +`, + expected: rds.RDS{ + Classic: rds.Classic{ + DBSecurityGroups: []rds.DBSecurityGroup{{}}, + }, + ParameterGroups: []rds.ParameterGroups{{}}, + Clusters: []rds.Cluster{{ + Engine: types.StringTest("aurora"), + BackupRetentionPeriodDays: types.IntTest(1), + }}, + Instances: []rds.Instance{{ + BackupRetentionPeriodDays: types.IntTest(1), + PublicAccess: types.BoolTest(true), + DBParameterGroups: []rds.DBParameterGroupsList{{}}, + }}, + }, + }, } 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)) + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) }) } diff --git a/pkg/iac/adapters/cloudformation/aws/rds/instance.go b/pkg/iac/adapters/cloudformation/aws/rds/instance.go index 6b4a39e7acf7..256eada02aac 100644 --- a/pkg/iac/adapters/cloudformation/aws/rds/instance.go +++ b/pkg/iac/adapters/cloudformation/aws/rds/instance.go @@ -2,11 +2,11 @@ package rds import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getClustersAndInstances(ctx parser2.FileContext) ([]rds.Cluster, []rds.Instance) { +func getClustersAndInstances(ctx parser.FileContext) ([]rds.Cluster, []rds.Instance) { clusterMap := getClusters(ctx) @@ -68,12 +68,15 @@ func getClustersAndInstances(ctx parser2.FileContext) ([]rds.Cluster, []rds.Inst return clusters, orphans } -func getDBParameterGroups(ctx parser2.FileContext, r *parser2.Resource) (dbParameterGroup []rds.DBParameterGroupsList) { +func getDBParameterGroups(ctx parser.FileContext, r *parser.Resource) (dbParameterGroup []rds.DBParameterGroupsList) { + + var parameterGroupList []rds.DBParameterGroupsList dbParameterGroupName := r.GetStringProperty("DBParameterGroupName") for _, r := range ctx.GetResourcesByType("AWS::RDS::DBParameterGroup") { name := r.GetStringProperty("DBParameterGroupName") + // TODO: find by resource logical id if !dbParameterGroupName.EqualTo(name.Value()) { continue } @@ -82,13 +85,13 @@ func getDBParameterGroups(ctx parser2.FileContext, r *parser2.Resource) (dbParam DBParameterGroupName: name, KMSKeyID: types.StringUnresolvable(r.Metadata()), } - dbParameterGroup = append(dbParameterGroup, dbpmgl) + parameterGroupList = append(dbParameterGroup, dbpmgl) } - return dbParameterGroup + return parameterGroupList } -func getEnabledCloudwatchLogsExports(r *parser2.Resource) (enabledcloudwatchlogexportslist []types.StringValue) { +func getEnabledCloudwatchLogsExports(r *parser.Resource) (enabledcloudwatchlogexportslist []types.StringValue) { enabledCloudwatchLogExportList := r.GetProperty("EnableCloudwatchLogsExports") if enabledCloudwatchLogExportList.IsNil() || enabledCloudwatchLogExportList.IsNotList() { @@ -101,7 +104,7 @@ func getEnabledCloudwatchLogsExports(r *parser2.Resource) (enabledcloudwatchloge return enabledcloudwatchlogexportslist } -func getTagList(r *parser2.Resource) (taglist []rds.TagList) { +func getTagList(r *parser.Resource) (taglist []rds.TagList) { tagLists := r.GetProperty("Tags") if tagLists.IsNil() || tagLists.IsNotList() { @@ -116,7 +119,7 @@ func getTagList(r *parser2.Resource) (taglist []rds.TagList) { return taglist } -func getReadReplicaDBInstanceIdentifiers(r *parser2.Resource) (readreplicadbidentifier []types.StringValue) { +func getReadReplicaDBInstanceIdentifiers(r *parser.Resource) (readreplicadbidentifier []types.StringValue) { readReplicaDBIdentifier := r.GetProperty("SourceDBInstanceIdentifier") if readReplicaDBIdentifier.IsNil() || readReplicaDBIdentifier.IsNotList() { diff --git a/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go b/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go index 98df5187401b..f47c2f70a706 100644 --- a/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go +++ b/pkg/iac/adapters/cloudformation/aws/rds/parameter_groups.go @@ -2,11 +2,11 @@ package rds import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/rds" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getParameterGroups(ctx parser2.FileContext) (parametergroups []rds.ParameterGroups) { +func getParameterGroups(ctx parser.FileContext) (parametergroups []rds.ParameterGroups) { for _, r := range ctx.GetResourcesByType("AWS::RDS::DBParameterGroup") { @@ -23,10 +23,12 @@ func getParameterGroups(ctx parser2.FileContext) (parametergroups []rds.Paramete return parametergroups } -func getParameters(r *parser2.Resource) (parameters []rds.Parameters) { +func getParameters(r *parser.Resource) (parameters []rds.Parameters) { dBParam := r.GetProperty("Parameters") + // TODO: parameters is JSON + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-rds-dbparametergroup.html#cfn-rds-dbparametergroup-parameters if dBParam.IsNil() || dBParam.IsNotList() { return parameters } diff --git a/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go b/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go index 6aac98978b94..c8acf8997af0 100644 --- a/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go +++ b/pkg/iac/adapters/cloudformation/aws/redshift/cluster.go @@ -3,7 +3,6 @@ package redshift import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" - "github.com/aquasecurity/trivy/pkg/iac/types" ) func getClusters(ctx parser.FileContext) (clusters []redshift.Cluster) { @@ -12,14 +11,12 @@ func getClusters(ctx parser.FileContext) (clusters []redshift.Cluster) { cluster := redshift.Cluster{ Metadata: r.Metadata(), ClusterIdentifier: r.GetStringProperty("ClusterIdentifier"), - AllowVersionUpgrade: r.GetBoolProperty("AllowVersionUpgrade"), + AllowVersionUpgrade: r.GetBoolProperty("AllowVersionUpgrade", true), NodeType: r.GetStringProperty("NodeType"), - NumberOfNodes: r.GetIntProperty("NumberOfNodes"), + NumberOfNodes: r.GetIntProperty("NumberOfNodes", 1), PubliclyAccessible: r.GetBoolProperty("PubliclyAccessible"), MasterUsername: r.GetStringProperty("MasterUsername"), - VpcId: types.String("", r.Metadata()), - LoggingEnabled: types.Bool(false, r.Metadata()), - AutomatedSnapshotRetentionPeriod: r.GetIntProperty("AutomatedSnapshotRetentionPeriod"), + AutomatedSnapshotRetentionPeriod: r.GetIntProperty("AutomatedSnapshotRetentionPeriod", 1), Encryption: redshift.Encryption{ Metadata: r.Metadata(), Enabled: r.GetBoolProperty("Encrypted"), @@ -38,10 +35,8 @@ func getClusters(ctx parser.FileContext) (clusters []redshift.Cluster) { } func getParameters(ctx parser.FileContext) (parameter []redshift.ClusterParameter) { - - paraRes := ctx.GetResourcesByType("AWS::Redshift::ClusterParameterGroup") var parameters []redshift.ClusterParameter - for _, r := range paraRes { + for _, r := range ctx.GetResourcesByType("AWS::Redshift::ClusterParameterGroup") { for _, par := range r.GetProperty("Parameters").AsList() { parameters = append(parameters, redshift.ClusterParameter{ Metadata: par.Metadata(), diff --git a/pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go b/pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go new file mode 100644 index 000000000000..a14117a21961 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/redshift/redshift_test.go @@ -0,0 +1,111 @@ +package redshift + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/redshift" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected redshift.Redshift + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + myCluster: + Type: "AWS::Redshift::Cluster" + Properties: + DBName: "mydb" + ClusterIdentifier: myexamplecluster + AllowVersionUpgrade: false + MasterUsername: "master" + NodeType: "ds2.xlarge" + NumberOfNodes: 2 + PubliclyAccessible: true + AutomatedSnapshotRetentionPeriod: 2 + Encrypted: true + KmsKeyId: key + Endpoint: + Port: 2000 + ClusterSubnetGroupName: test + myClusterParameterGroup: + Type: "AWS::Redshift::ClusterParameterGroup" + Properties: + Parameters: + - + ParameterName: "enable_user_activity_logging" + ParameterValue: "true" + mySecGroup: + Type: AWS::Redshift::ClusterSecurityGroup + Properties: + Description: test + `, + expected: redshift.Redshift{ + Clusters: []redshift.Cluster{ + { + ClusterIdentifier: types.StringTest("myexamplecluster"), + AllowVersionUpgrade: types.BoolTest(false), + MasterUsername: types.StringTest("master"), + NodeType: types.StringTest("ds2.xlarge"), + NumberOfNodes: types.IntTest(2), + PubliclyAccessible: types.BoolTest(true), + AutomatedSnapshotRetentionPeriod: types.IntTest(2), + Encryption: redshift.Encryption{ + Enabled: types.BoolTest(true), + KMSKeyID: types.StringTest("key"), + }, + EndPoint: redshift.EndPoint{ + Port: types.IntTest(2000), + }, + SubnetGroupName: types.StringTest("test"), + }, + }, + ClusterParameters: []redshift.ClusterParameter{ + { + ParameterName: types.StringTest("enable_user_activity_logging"), + ParameterValue: types.StringTest("true"), + }, + }, + SecurityGroups: []redshift.SecurityGroup{ + { + Description: types.StringTest("test"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + myCluster: + Type: "AWS::Redshift::Cluster" + mySecGroup: + Type: AWS::Redshift::ClusterSecurityGroup + myClusterParameterGroup: + Type: "AWS::Redshift::ClusterParameterGroup" +`, + expected: redshift.Redshift{ + Clusters: []redshift.Cluster{ + { + AllowVersionUpgrade: types.BoolTest(true), + AutomatedSnapshotRetentionPeriod: types.IntTest(1), + NumberOfNodes: types.IntTest(1), + }, + }, + SecurityGroups: []redshift.SecurityGroup{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go index 35b95520fd9c..5f5329fc6714 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/bucket.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/bucket.go @@ -50,16 +50,17 @@ func getBuckets(cfFile parser.FileContext) []s3.Bucket { } func getPublicAccessBlock(r *parser.Resource) *s3.PublicAccessBlock { - if block := r.GetProperty("PublicAccessBlockConfiguration"); block.IsNil() { + block := r.GetProperty("PublicAccessBlockConfiguration") + if block.IsNil() { return nil } return &s3.PublicAccessBlock{ - Metadata: r.Metadata(), - BlockPublicACLs: r.GetBoolProperty("PublicAccessBlockConfiguration.BlockPublicAcls"), - BlockPublicPolicy: r.GetBoolProperty("PublicAccessBlockConfiguration.BlockPublicPolicy"), - IgnorePublicACLs: r.GetBoolProperty("PublicAccessBlockConfiguration.IgnorePublicAcls"), - RestrictPublicBuckets: r.GetBoolProperty("PublicAccessBlockConfiguration.RestrictPublicBuckets"), + Metadata: block.Metadata(), + BlockPublicACLs: block.GetBoolProperty("BlockPublicAcls"), + BlockPublicPolicy: block.GetBoolProperty("BlockPublicPolicy"), + IgnorePublicACLs: block.GetBoolProperty("IgnorePublicAcls"), + RestrictPublicBuckets: block.GetBoolProperty("RestrictPublicBuckets"), } } diff --git a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go index f4139c7ad15b..ee8fffb39f75 100644 --- a/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go +++ b/pkg/iac/adapters/cloudformation/aws/s3/s3_test.go @@ -1,14 +1,11 @@ package s3 import ( - "context" "testing" - "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/s3" - "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" - "github.com/stretchr/testify/require" ) func TestAdapt(t *testing.T) { @@ -56,36 +53,44 @@ Resources: ExpirationInDays: 365 AccelerateConfiguration: AccelerationStatus: Enabled + VersioningConfiguration: + Status: Enabled + WebsiteConfiguration: + IndexDocument: index.html `, expected: s3.S3{ Buckets: []s3.Bucket{ { - Name: types.String("logging-bucket", types.NewTestMetadata()), + Name: types.StringTest("logging-bucket"), }, { - Name: types.String("test-bucket", types.NewTestMetadata()), + Name: types.StringTest("test-bucket"), Encryption: s3.Encryption{ - Enabled: types.Bool(true, types.NewTestMetadata()), - Algorithm: types.String("aws:kms", types.NewTestMetadata()), - KMSKeyId: types.String("Key", types.NewTestMetadata()), + Enabled: types.BoolTest(true), + Algorithm: types.StringTest("aws:kms"), + KMSKeyId: types.StringTest("Key"), }, - ACL: types.String("aws-exec-read", types.NewTestMetadata()), + ACL: types.StringTest("aws-exec-read"), PublicAccessBlock: &s3.PublicAccessBlock{ - BlockPublicACLs: types.Bool(true, types.NewTestMetadata()), - BlockPublicPolicy: types.Bool(true, types.NewTestMetadata()), - IgnorePublicACLs: types.Bool(true, types.NewTestMetadata()), - RestrictPublicBuckets: types.Bool(true, types.NewTestMetadata()), + BlockPublicACLs: types.BoolTest(true), + BlockPublicPolicy: types.BoolTest(true), + IgnorePublicACLs: types.BoolTest(true), + RestrictPublicBuckets: types.BoolTest(true), }, Logging: s3.Logging{ - TargetBucket: types.String("LoggingBucket", types.NewTestMetadata()), - Enabled: types.Bool(true, types.NewTestMetadata()), + TargetBucket: types.StringTest("LoggingBucket"), + Enabled: types.BoolTest(true), }, LifecycleConfiguration: []s3.Rules{ { - Status: types.String("Enabled", types.NewTestMetadata()), + Status: types.StringTest("Enabled"), }, }, - AccelerateConfigurationStatus: types.String("Enabled", types.NewTestMetadata()), + AccelerateConfigurationStatus: types.StringTest("Enabled"), + Versioning: s3.Versioning{ + Enabled: types.BoolTest(true), + }, + Website: &s3.Website{}, }, }, }, @@ -101,7 +106,7 @@ Resources: expected: s3.S3{ Buckets: []s3.Bucket{ { - Name: types.String("test-bucket", types.NewTestMetadata()), + Name: types.StringTest("test-bucket"), Encryption: s3.Encryption{ Enabled: types.BoolDefault(false, types.NewTestMetadata()), }, @@ -126,11 +131,11 @@ Resources: expected: s3.S3{ Buckets: []s3.Bucket{ { - Name: types.String("test-bucket", types.NewTestMetadata()), + Name: types.StringTest("test-bucket"), Encryption: s3.Encryption{ Enabled: types.BoolDefault(false, types.NewTestMetadata()), - KMSKeyId: types.String("alias/my-key", types.NewTestMetadata()), - Algorithm: types.String("aes256", types.NewTestMetadata()), + KMSKeyId: types.StringTest("alias/my-key"), + Algorithm: types.StringTest("aes256"), }, }, }, @@ -140,16 +145,7 @@ Resources: for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - - fsys := testutil.CreateFS(t, map[string]string{ - "main.yaml": tt.source, - }) - - fctx, err := parser.New().ParseFile(context.TODO(), fsys, "main.yaml") - require.NoError(t, err) - - adapted := Adapt(*fctx) - testutil.AssertDefsecEqual(t, tt.expected, adapted) + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) }) } diff --git a/pkg/iac/adapters/cloudformation/aws/sam/api.go b/pkg/iac/adapters/cloudformation/aws/sam/api.go index d42010166914..4d4f04e6e83a 100644 --- a/pkg/iac/adapters/cloudformation/aws/sam/api.go +++ b/pkg/iac/adapters/cloudformation/aws/sam/api.go @@ -2,11 +2,11 @@ package sam import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getApis(cfFile parser2.FileContext) (apis []sam.API) { +func getApis(cfFile parser.FileContext) (apis []sam.API) { apiResources := cfFile.GetResourcesByType("AWS::Serverless::Api") for _, r := range apiResources { @@ -25,7 +25,7 @@ func getApis(cfFile parser2.FileContext) (apis []sam.API) { return apis } -func getRestMethodSettings(r *parser2.Resource) sam.RESTMethodSettings { +func getRestMethodSettings(r *parser.Resource) sam.RESTMethodSettings { settings := sam.RESTMethodSettings{ Metadata: r.Metadata(), @@ -35,6 +35,8 @@ func getRestMethodSettings(r *parser2.Resource) sam.RESTMethodSettings { MetricsEnabled: iacTypes.BoolDefault(false, r.Metadata()), } + // TODO: MethodSettings is list + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-apigateway-stage.html#cfn-apigateway-stage-methodsettings settingsProp := r.GetProperty("MethodSettings") if settingsProp.IsNotNil() { @@ -47,7 +49,7 @@ func getRestMethodSettings(r *parser2.Resource) sam.RESTMethodSettings { } if loggingLevel := settingsProp.GetProperty("LoggingLevel"); loggingLevel.IsNotNil() { - if loggingLevel.EqualTo("OFF", parser2.IgnoreCase) { + if loggingLevel.EqualTo("OFF", parser.IgnoreCase) { settings.LoggingEnabled = iacTypes.Bool(false, loggingLevel.Metadata()) } else { settings.LoggingEnabled = iacTypes.Bool(true, loggingLevel.Metadata()) @@ -58,7 +60,7 @@ func getRestMethodSettings(r *parser2.Resource) sam.RESTMethodSettings { return settings } -func getAccessLogging(r *parser2.Resource) sam.AccessLogging { +func getAccessLogging(r *parser.Resource) sam.AccessLogging { logging := sam.AccessLogging{ Metadata: r.Metadata(), @@ -75,19 +77,17 @@ func getAccessLogging(r *parser2.Resource) sam.AccessLogging { return logging } -func getDomainConfiguration(r *parser2.Resource) sam.DomainConfiguration { +func getDomainConfiguration(r *parser.Resource) sam.DomainConfiguration { domainConfig := sam.DomainConfiguration{ - Metadata: r.Metadata(), - Name: iacTypes.StringDefault("", r.Metadata()), - SecurityPolicy: iacTypes.StringDefault("TLS_1_0", r.Metadata()), + Metadata: r.Metadata(), } if domain := r.GetProperty("Domain"); domain.IsNotNil() { domainConfig = sam.DomainConfiguration{ Metadata: domain.Metadata(), - Name: domain.GetStringProperty("DomainName", ""), - SecurityPolicy: domain.GetStringProperty("SecurityPolicy", "TLS_1_0"), + Name: domain.GetStringProperty("DomainName"), + SecurityPolicy: domain.GetStringProperty("SecurityPolicy"), } } diff --git a/pkg/iac/adapters/cloudformation/aws/sam/function.go b/pkg/iac/adapters/cloudformation/aws/sam/function.go index f6f2cfd747a6..161b078bf681 100644 --- a/pkg/iac/adapters/cloudformation/aws/sam/function.go +++ b/pkg/iac/adapters/cloudformation/aws/sam/function.go @@ -5,18 +5,18 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getFunctions(cfFile parser2.FileContext) (functions []sam.Function) { +func getFunctions(cfFile parser.FileContext) (functions []sam.Function) { functionResources := cfFile.GetResourcesByType("AWS::Serverless::Function") for _, r := range functionResources { function := sam.Function{ Metadata: r.Metadata(), FunctionName: r.GetStringProperty("FunctionName"), - Tracing: r.GetStringProperty("Tracing", sam.TracingModePassThrough), + Tracing: r.GetStringProperty("Tracing"), ManagedPolicies: nil, Policies: nil, } @@ -28,7 +28,7 @@ func getFunctions(cfFile parser2.FileContext) (functions []sam.Function) { return functions } -func setFunctionPolicies(r *parser2.Resource, function *sam.Function) { +func setFunctionPolicies(r *parser.Resource, function *sam.Function) { policies := r.GetProperty("Policies") if policies.IsNotNil() { if policies.IsString() { diff --git a/pkg/iac/adapters/cloudformation/aws/sam/http_api.go b/pkg/iac/adapters/cloudformation/aws/sam/http_api.go index c51c3efb8913..02f9ba6c5ef9 100644 --- a/pkg/iac/adapters/cloudformation/aws/sam/http_api.go +++ b/pkg/iac/adapters/cloudformation/aws/sam/http_api.go @@ -2,11 +2,11 @@ package sam import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getHttpApis(cfFile parser2.FileContext) (apis []sam.HttpAPI) { +func getHttpApis(cfFile parser.FileContext) (apis []sam.HttpAPI) { apiResources := cfFile.GetResourcesByType("AWS::Serverless::HttpApi") for _, r := range apiResources { @@ -24,7 +24,7 @@ func getHttpApis(cfFile parser2.FileContext) (apis []sam.HttpAPI) { return apis } -func getAccessLoggingV2(r *parser2.Resource) sam.AccessLogging { +func getAccessLoggingV2(r *parser.Resource) sam.AccessLogging { logging := sam.AccessLogging{ Metadata: r.Metadata(), @@ -41,7 +41,7 @@ func getAccessLoggingV2(r *parser2.Resource) sam.AccessLogging { return logging } -func getRouteSettings(r *parser2.Resource) sam.RouteSettings { +func getRouteSettings(r *parser.Resource) sam.RouteSettings { routeSettings := sam.RouteSettings{ Metadata: r.Metadata(), @@ -52,7 +52,8 @@ func getRouteSettings(r *parser2.Resource) sam.RouteSettings { if route := r.GetProperty("DefaultRouteSettings"); route.IsNotNil() { routeSettings = sam.RouteSettings{ - Metadata: route.Metadata(), + Metadata: route.Metadata(), + // TODO: LoggingLevel is string LoggingEnabled: route.GetBoolProperty("LoggingLevel"), DataTraceEnabled: route.GetBoolProperty("DataTraceEnabled"), DetailedMetricsEnabled: route.GetBoolProperty("DetailedMetricsEnabled"), diff --git a/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go b/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go new file mode 100644 index 000000000000..ec2fed201ea6 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sam/sam_test.go @@ -0,0 +1,213 @@ +package sam + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected sam.SAM + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + ApiGatewayApi: + Type: AWS::Serverless::Api + Properties: + StageName: prod + Name: test + TracingEnabled: true + Domain: + DomainName: domain + SecurityPolicy: "TLS_1_2" + MethodSettings: + - DataTraceEnabled: true + CacheDataEncrypted: true + MetricsEnabled: true + LoggingLevel: INFO + AccessLogSetting: + DestinationArn: 'arn:aws:logs:us-east-1:123456789:log-group:my-log-group' + HttpApi: + Type: AWS::Serverless::HttpApi + Properties: + Name: test + Domain: + DomainName: test + SecurityPolicy: "TLS_1_2" + AccessLogSettings: + DestinationArn: 'arn:aws:logs:us-east-1:123456789:log-group:my-log-group' + DefaultRouteSettings: + LoggingLevel: INFO + DataTraceEnabled: true + DetailedMetricsEnabled: true + myFunction: + Type: AWS::Serverless::Function + Properties: + FunctionName: test + Tracing: Active + Policies: + - AWSLambdaExecute + - Version: '2012-10-17' + Statement: + - Effect: Allow + Action: + - s3:GetObject + Resource: 'arn:aws:s3:::my-bucket/*' + MySampleStateMachine: + Type: AWS::Serverless::StateMachine + Properties: + Logging: + Level: ALL + Tracing: + Enabled: true + Policies: + - Version: "2012-10-17" + Statement: + - Effect: Allow + Action: + - "cloudwatch:*" + Resource: "*" + myTable: + Type: AWS::Serverless::SimpleTable + Properties: + TableName: my-table + SSESpecification: + SSEEnabled: "true" + KMSMasterKeyId: "kmskey" +`, + expected: sam.SAM{ + APIs: []sam.API{ + { + Name: types.StringTest("test"), + TracingEnabled: types.BoolTest(true), + DomainConfiguration: sam.DomainConfiguration{ + Name: types.StringTest("domain"), + SecurityPolicy: types.StringTest("TLS_1_2"), + }, + AccessLogging: sam.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("arn:aws:logs:us-east-1:123456789:log-group:my-log-group"), + }, + }, + }, + HttpAPIs: []sam.HttpAPI{ + { + Name: types.StringTest("test"), + DomainConfiguration: sam.DomainConfiguration{ + Name: types.StringTest("test"), + SecurityPolicy: types.StringTest("TLS_1_2"), + }, + AccessLogging: sam.AccessLogging{ + CloudwatchLogGroupARN: types.StringTest("arn:aws:logs:us-east-1:123456789:log-group:my-log-group"), + }, + DefaultRouteSettings: sam.RouteSettings{ + DataTraceEnabled: types.BoolTest(true), + DetailedMetricsEnabled: types.BoolTest(true), + }, + }, + }, + Functions: []sam.Function{ + { + FunctionName: types.StringTest("test"), + Tracing: types.StringTest("Active"), + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"s3:GetObject"}). + WithResources([]string{"arn:aws:s3:::my-bucket/*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + ManagedPolicies: []types.StringValue{ + types.StringTest("AWSLambdaExecute"), + }, + }, + }, + StateMachines: []sam.StateMachine{ + { + LoggingConfiguration: sam.LoggingConfiguration{ + LoggingEnabled: types.BoolTest(true), + }, + Tracing: sam.TracingConfiguration{ + Enabled: types.BoolTest(true), + }, + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithVersion("2012-10-17"). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"cloudwatch:*"}). + WithResources([]string{"*"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + SimpleTables: []sam.SimpleTable{ + { + TableName: types.StringTest("my-table"), + SSESpecification: sam.SSESpecification{ + Enabled: types.BoolTest(true), + KMSMasterKeyID: types.StringTest("kmskey"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + ApiGatewayApi: + Type: AWS::Serverless::Api + HttpApi: + Type: AWS::Serverless::HttpApi + myFunction: + Type: AWS::Serverless::Function + MySampleStateMachine: + Type: AWS::Serverless::StateMachine + myTable: + Type: AWS::Serverless::SimpleTable +`, + expected: sam.SAM{ + APIs: []sam.API{{}}, + HttpAPIs: []sam.HttpAPI{{}}, + Functions: []sam.Function{{}}, + StateMachines: []sam.StateMachine{{}}, + SimpleTables: []sam.SimpleTable{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go b/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go index efcaf3772be4..2a57afd2bdb6 100644 --- a/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go +++ b/pkg/iac/adapters/cloudformation/aws/sam/state_machines.go @@ -5,11 +5,11 @@ import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getStateMachines(cfFile parser2.FileContext) (stateMachines []sam.StateMachine) { +func getStateMachines(cfFile parser.FileContext) (stateMachines []sam.StateMachine) { stateMachineResources := cfFile.GetResourcesByType("AWS::Serverless::StateMachine") for _, r := range stateMachineResources { @@ -25,6 +25,7 @@ func getStateMachines(cfFile parser2.FileContext) (stateMachines []sam.StateMach Tracing: getTracingConfiguration(r), } + // TODO: By default, the level is set to OFF if logging := r.GetProperty("Logging"); logging.IsNotNil() { stateMachine.LoggingConfiguration.Metadata = logging.Metadata() if level := logging.GetProperty("Level"); level.IsNotNil() { @@ -39,7 +40,7 @@ func getStateMachines(cfFile parser2.FileContext) (stateMachines []sam.StateMach return stateMachines } -func getTracingConfiguration(r *parser2.Resource) sam.TracingConfiguration { +func getTracingConfiguration(r *parser.Resource) sam.TracingConfiguration { tracing := r.GetProperty("Tracing") if tracing.IsNil() { return sam.TracingConfiguration{ @@ -54,7 +55,7 @@ func getTracingConfiguration(r *parser2.Resource) sam.TracingConfiguration { } } -func setStateMachinePolicies(r *parser2.Resource, stateMachine *sam.StateMachine) { +func setStateMachinePolicies(r *parser.Resource, stateMachine *sam.StateMachine) { policies := r.GetProperty("Policies") if policies.IsNotNil() { if policies.IsString() { diff --git a/pkg/iac/adapters/cloudformation/aws/sam/tables.go b/pkg/iac/adapters/cloudformation/aws/sam/tables.go index 713f723bf319..89e66acdf514 100644 --- a/pkg/iac/adapters/cloudformation/aws/sam/tables.go +++ b/pkg/iac/adapters/cloudformation/aws/sam/tables.go @@ -2,11 +2,11 @@ package sam import ( "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sam" - parser2 "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" iacTypes "github.com/aquasecurity/trivy/pkg/iac/types" ) -func getSimpleTables(cfFile parser2.FileContext) (tables []sam.SimpleTable) { +func getSimpleTables(cfFile parser.FileContext) (tables []sam.SimpleTable) { tableResources := cfFile.GetResourcesByType("AWS::Serverless::SimpleTable") for _, r := range tableResources { @@ -22,21 +22,18 @@ func getSimpleTables(cfFile parser2.FileContext) (tables []sam.SimpleTable) { return tables } -func getSSESpecification(r *parser2.Resource) sam.SSESpecification { - - spec := sam.SSESpecification{ - Metadata: r.Metadata(), - Enabled: iacTypes.BoolDefault(false, r.Metadata()), - KMSMasterKeyID: iacTypes.StringDefault("", r.Metadata()), - } - +func getSSESpecification(r *parser.Resource) sam.SSESpecification { if sse := r.GetProperty("SSESpecification"); sse.IsNotNil() { - spec = sam.SSESpecification{ + return sam.SSESpecification{ Metadata: sse.Metadata(), Enabled: sse.GetBoolProperty("SSEEnabled"), - KMSMasterKeyID: sse.GetStringProperty("KMSMasterKeyID"), + KMSMasterKeyID: sse.GetStringProperty("KMSMasterKeyId"), } } - return spec + return sam.SSESpecification{ + Metadata: r.Metadata(), + Enabled: iacTypes.BoolDefault(false, r.Metadata()), + KMSMasterKeyID: iacTypes.StringDefault("", r.Metadata()), + } } diff --git a/pkg/iac/adapters/cloudformation/aws/sns/sns_test.go b/pkg/iac/adapters/cloudformation/aws/sns/sns_test.go new file mode 100644 index 000000000000..25f271db2073 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sns/sns_test.go @@ -0,0 +1,54 @@ +package sns + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sns" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected sns.SNS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MySNSTopic: + Type: AWS::SNS::Topic + Properties: + KmsMasterKeyId: mykey +`, + expected: sns.SNS{ + Topics: []sns.Topic{ + { + Encryption: sns.Encryption{ + KMSKeyID: types.StringTest("mykey"), + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySNSTopic: + Type: AWS::SNS::Topic + `, + expected: sns.SNS{ + Topics: []sns.Topic{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/sqs/queue.go b/pkg/iac/adapters/cloudformation/aws/sqs/queue.go index 2670dc299663..555fd54efd90 100644 --- a/pkg/iac/adapters/cloudformation/aws/sqs/queue.go +++ b/pkg/iac/adapters/cloudformation/aws/sqs/queue.go @@ -21,7 +21,6 @@ func getQueues(ctx parser.FileContext) (queues []sqs.Queue) { ManagedEncryption: iacTypes.Bool(false, r.Metadata()), KMSKeyID: r.GetStringProperty("KmsMasterKeyId"), }, - Policies: []iam.Policy{}, } if policy, err := getPolicy(r.ID(), ctx); err == nil { queue.Policies = append(queue.Policies, *policy) diff --git a/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go b/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go new file mode 100644 index 000000000000..8abeff2aca3e --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/sqs/sqs_test.go @@ -0,0 +1,86 @@ +package sqs + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/iam" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/sqs" + "github.com/aquasecurity/trivy/pkg/iac/types" + "github.com/liamg/iamgo" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected sqs.SQS + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyQueue: + Type: AWS::SQS::Queue + Properties: + QueueName: "SampleQueue" + KmsMasterKeyId: mykey + SampleSQSPolicy: + Type: AWS::SQS::QueuePolicy + Properties: + Queues: + - !Ref MyQueue + PolicyDocument: + Statement: + - + Action: + - "SQS:SendMessage" + Effect: "Allow" + Resource: "arn:aws:sqs:us-east-2:444455556666:queue2" +`, + expected: sqs.SQS{ + Queues: []sqs.Queue{ + { + Encryption: sqs.Encryption{ + KMSKeyID: types.StringTest("mykey"), + }, + Policies: []iam.Policy{ + { + Document: func() iam.Document { + return iam.Document{ + Parsed: iamgo.NewPolicyBuilder(). + WithStatement( + iamgo.NewStatementBuilder(). + WithEffect("Allow"). + WithActions([]string{"SQS:SendMessage"}). + WithResources([]string{"arn:aws:sqs:us-east-2:444455556666:queue2"}). + Build(), + ). + Build(), + } + }(), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySNSTopic: + Type: AWS::SQS::Queue + `, + expected: sqs.SQS{ + Queues: []sqs.Queue{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go b/pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go new file mode 100644 index 000000000000..9709207fec66 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/ssm/ssm_test.go @@ -0,0 +1,53 @@ +package ssm + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/ssm" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected ssm.SSM + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MySecretA: + Type: 'AWS::SecretsManager::Secret' + Properties: + Name: MySecretForAppA + KmsKeyId: alias/exampleAlias +`, + expected: ssm.SSM{ + Secrets: []ssm.Secret{ + { + KMSKeyID: types.StringTest("alias/exampleAlias"), + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MySecretA: + Type: 'AWS::SecretsManager::Secret' + `, + expected: ssm.SSM{ + Secrets: []ssm.Secret{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go b/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go new file mode 100644 index 000000000000..41e821e6466d --- /dev/null +++ b/pkg/iac/adapters/cloudformation/aws/workspaces/workspaces_test.go @@ -0,0 +1,62 @@ +package workspaces + +import ( + "testing" + + "github.com/aquasecurity/trivy/pkg/iac/adapters/cloudformation/testutil" + "github.com/aquasecurity/trivy/pkg/iac/providers/aws/workspaces" + "github.com/aquasecurity/trivy/pkg/iac/types" +) + +func TestAdapt(t *testing.T) { + tests := []struct { + name string + source string + expected workspaces.WorkSpaces + }{ + { + name: "complete", + source: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyWorkSpace: + Type: AWS::WorkSpaces::Workspace + Properties: + RootVolumeEncryptionEnabled: true + UserVolumeEncryptionEnabled: true +`, + expected: workspaces.WorkSpaces{ + WorkSpaces: []workspaces.WorkSpace{ + { + RootVolume: workspaces.Volume{ + Encryption: workspaces.Encryption{ + Enabled: types.BoolTest(true), + }, + }, + UserVolume: workspaces.Volume{ + Encryption: workspaces.Encryption{ + Enabled: types.BoolTest(true), + }, + }, + }, + }, + }, + }, + { + name: "empty", + source: `AWSTemplateFormatVersion: 2010-09-09 +Resources: + MyWorkSpace: + Type: AWS::WorkSpaces::Workspace + `, + expected: workspaces.WorkSpaces{ + WorkSpaces: []workspaces.WorkSpace{{}}, + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + testutil.AdaptAndCompare(t, tt.source, tt.expected, Adapt) + }) + } +} diff --git a/pkg/iac/adapters/cloudformation/testutil/testutil.go b/pkg/iac/adapters/cloudformation/testutil/testutil.go new file mode 100644 index 000000000000..f908519d4106 --- /dev/null +++ b/pkg/iac/adapters/cloudformation/testutil/testutil.go @@ -0,0 +1,25 @@ +package testutil + +import ( + "context" + "testing" + + "github.com/stretchr/testify/require" + + "github.com/aquasecurity/trivy/internal/testutil" + "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation/parser" +) + +type adaptFn[T any] func(fctx parser.FileContext) T + +func AdaptAndCompare[T any](t *testing.T, source string, expected any, fn adaptFn[T]) { + fsys := testutil.CreateFS(t, map[string]string{ + "main.yaml": source, + }) + + fctx, err := parser.New().ParseFile(context.TODO(), fsys, "main.yaml") + require.NoError(t, err) + + adapted := fn(*fctx) + testutil.AssertDefsecEqual(t, expected, adapted) +} diff --git a/pkg/iac/providers/aws/ecs/ecs.go b/pkg/iac/providers/aws/ecs/ecs.go index 181c4a2ac90a..b0728c2bbf7f 100755 --- a/pkg/iac/providers/aws/ecs/ecs.go +++ b/pkg/iac/providers/aws/ecs/ecs.go @@ -87,9 +87,11 @@ func (j containerDefinitionJSON) convert(metadata iacTypes.Metadata) ContainerDe } type ContainerDefinition struct { - Metadata iacTypes.Metadata - Name iacTypes.StringValue - Image iacTypes.StringValue + Metadata iacTypes.Metadata + Name iacTypes.StringValue + Image iacTypes.StringValue + // TODO: CPU and Memory are strings + // https://docs.aws.amazon.com/AWSCloudFormation/latest/UserGuide/aws-resource-ecs-taskdefinition.html#cfn-ecs-taskdefinition-cpu CPU iacTypes.IntValue Memory iacTypes.IntValue Essential iacTypes.BoolValue diff --git a/pkg/iac/types/bool.go b/pkg/iac/types/bool.go index 66179d2c1e5e..c897206b8b19 100755 --- a/pkg/iac/types/bool.go +++ b/pkg/iac/types/bool.go @@ -45,6 +45,10 @@ func Bool(value bool, metadata Metadata) BoolValue { } } +func BoolTest(value bool) BoolValue { + return Bool(value, NewTestMetadata()) +} + func BoolDefault(value bool, metadata Metadata) BoolValue { b := Bool(value, metadata) b.BaseAttribute.metadata.isDefault = true diff --git a/pkg/iac/types/int.go b/pkg/iac/types/int.go index 551c2e34bdf6..34ffc05c49c3 100755 --- a/pkg/iac/types/int.go +++ b/pkg/iac/types/int.go @@ -45,6 +45,10 @@ func Int(value int, m Metadata) IntValue { } } +func IntTest(value int) IntValue { + return Int(value, NewTestMetadata()) +} + func IntFromInt32(value int32, m Metadata) IntValue { return Int(int(value), m) } diff --git a/pkg/iac/types/string.go b/pkg/iac/types/string.go index 8c04d967be0b..1db865740cd3 100755 --- a/pkg/iac/types/string.go +++ b/pkg/iac/types/string.go @@ -19,6 +19,7 @@ func String(str string, m Metadata) StringValue { BaseAttribute: BaseAttribute{metadata: m}, } } + func StringDefault(value string, m Metadata) StringValue { b := String(value, m) b.BaseAttribute.metadata.isDefault = true @@ -37,6 +38,10 @@ func StringExplicit(value string, m Metadata) StringValue { return b } +func StringTest(value string) StringValue { + return String(value, NewTestMetadata()) +} + type StringValueList []StringValue type StringValue struct {