From 02a03235acddd22d838cee65fab58b5d513703bc Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Mon, 23 Sep 2024 23:27:46 +0600 Subject: [PATCH] fix examples Signed-off-by: Nikita Pivkin --- .../aws/apigateway/no_public_access.rego | 4 +- .../aws/apigateway/no_public_access_test.rego | 2 +- .../cloudtrail/encryption_customer_key.tf.go | 11 +- .../cloudwatch/log_group_customer_key.tf.go | 11 +- .../aws/ec2/encryption_customer_key.cf.go | 8 +- checks/cloud/aws/ec2/no_default_vpc.tf.go | 3 + .../aws/ec2/no_excessive_port_access.cf.go | 2 +- .../cloud/aws/ec2/no_public_ingress_sgr.tf.go | 4 +- checks/cloud/aws/eks/encrypt_secrets.cf.go | 8 +- checks/cloud/aws/eks/encrypt_secrets.rego | 19 +- checks/cloud/aws/eks/encrypt_secrets.tf.go | 6 +- .../kinesis/enable_in_transit_encryption.rego | 19 +- checks/cloud/aws/lambda/enable_tracing.rego | 9 +- .../aws/redshift/no_classic_resources.cf.go | 7 +- .../aws/redshift/no_classic_resources.rego | 2 + .../aws/s3/enable_bucket_encryption.cf.go | 7 - checks/cloud/aws/s3/no_public_buckets.cf.go | 2 +- checks/cloud/aws/s3/no_public_buckets.rego | 2 +- .../aws/sns/enable_topic_encryption.cf.go | 2 +- .../computing/no_public_ingress_sgr.tf.go | 9 +- .../network/add_security_group_to_router.rego | 9 +- internal/checks/checks.go | 30 ++++ internal/checks/examples.go | 117 +++++++++++++ test/examples_test.go | 164 +++++------------- 24 files changed, 296 insertions(+), 161 deletions(-) create mode 100644 internal/checks/checks.go create mode 100644 internal/checks/examples.go diff --git a/checks/cloud/aws/apigateway/no_public_access.rego b/checks/cloud/aws/apigateway/no_public_access.rego index fad776c7..d992d25a 100644 --- a/checks/cloud/aws/apigateway/no_public_access.rego +++ b/checks/cloud/aws/apigateway/no_public_access.rego @@ -35,11 +35,11 @@ deny contains res if { isManaged(api) some method in api.resources[_].methods not method_is_option(method) - not is_apikey_required(api) + not is_apikey_required(method) method.authorizationtype.value == authorization_none res := result.new("Authorization is not enabled for this method.", method.authorizationtype) } method_is_option(method) := method.httpmethod.value == "OPTION" -is_apikey_required(api) := api.apikeyrequired.value +is_apikey_required(method) := method.apikeyrequired.value diff --git a/checks/cloud/aws/apigateway/no_public_access_test.rego b/checks/cloud/aws/apigateway/no_public_access_test.rego index 1fad77fd..54a55d86 100644 --- a/checks/cloud/aws/apigateway/no_public_access_test.rego +++ b/checks/cloud/aws/apigateway/no_public_access_test.rego @@ -19,7 +19,7 @@ test_allow_get_method_with_auth if { } test_allow_if_api_required if { - test.assert_empty(check.deny) with input as input_with_method({"httpmethod": {"value": "GET"}, "authorizationtype": {"value": "AWS_IAM"}}) + test.assert_empty(check.deny) with input as input_with_method({"httpmethod": {"value": "GET"}, "authorizationtype": {"value": "AWS_IAM"}, "apikeyrequired": {"value": true}}) } input_with_method(method) = {"aws": {"apigateway": {"v1": {"apis": [{"resources": [{"methods": [method]}]}]}}}} diff --git a/checks/cloud/aws/cloudtrail/encryption_customer_key.tf.go b/checks/cloud/aws/cloudtrail/encryption_customer_key.tf.go index b4a950e5..16505f66 100644 --- a/checks/cloud/aws/cloudtrail/encryption_customer_key.tf.go +++ b/checks/cloud/aws/cloudtrail/encryption_customer_key.tf.go @@ -2,10 +2,19 @@ package cloudtrail var terraformEncryptionCustomerManagedKeyGoodExamples = []string{ ` +resource "aws_kms_key" "trail" { + enable_key_rotation = true +} + +resource "aws_kms_alias" "trail" { + name = "alias/trail" + target_key_id = aws_kms_key.trail.key_id +} + resource "aws_cloudtrail" "good_example" { is_multi_region_trail = true enable_log_file_validation = true - kms_key_id = var.kms_id + kms_key_id = aws_kms_alias.trail.arn event_selector { read_write_type = "All" diff --git a/checks/cloud/aws/cloudwatch/log_group_customer_key.tf.go b/checks/cloud/aws/cloudwatch/log_group_customer_key.tf.go index ddc0b7cb..714cd700 100644 --- a/checks/cloud/aws/cloudwatch/log_group_customer_key.tf.go +++ b/checks/cloud/aws/cloudwatch/log_group_customer_key.tf.go @@ -2,10 +2,19 @@ package cloudwatch var terraformLogGroupCustomerKeyGoodExamples = []string{ ` +resource "aws_kms_key" "cloudwatch" { + enable_key_rotation = true +} + +resource "aws_kms_alias" "cloudwatch" { + name = "alias/cloudwatch" + target_key_id = aws_kms_key.cloudwatch.key_id +} + resource "aws_cloudwatch_log_group" "good_example" { name = "good_example" - kms_key_id = aws_kms_key.log_key.arn + kms_key_id = aws_kms_alias.cloudwatch.arn } `, } diff --git a/checks/cloud/aws/ec2/encryption_customer_key.cf.go b/checks/cloud/aws/ec2/encryption_customer_key.cf.go index 447530bf..2e222841 100644 --- a/checks/cloud/aws/ec2/encryption_customer_key.cf.go +++ b/checks/cloud/aws/ec2/encryption_customer_key.cf.go @@ -13,12 +13,18 @@ Resources: `, `--- Resources: + MyKey: + Type: 'AWS::KMS::Key' + Properties: + KeyPolicy: + Version: 2012-10-17 + Id: key-default-1 GoodExample: Type: AWS::EC2::Volume Properties: Size: 100 Encrypted: true - KmsKeyId: !ImportValue "MyStack:Key" + KmsKeyId: !Ref MyKey DeletionPolicy: Snapshot `, } diff --git a/checks/cloud/aws/ec2/no_default_vpc.tf.go b/checks/cloud/aws/ec2/no_default_vpc.tf.go index 39ea5152..542be6b0 100644 --- a/checks/cloud/aws/ec2/no_default_vpc.tf.go +++ b/checks/cloud/aws/ec2/no_default_vpc.tf.go @@ -3,6 +3,9 @@ package ec2 var terraformNoDefaultVpcGoodExamples = []string{ ` # no aws default vpc present +resource "aws_vpc" "main" { + cidr_block = "10.0.0.0/16" +} `, } diff --git a/checks/cloud/aws/ec2/no_excessive_port_access.cf.go b/checks/cloud/aws/ec2/no_excessive_port_access.cf.go index 32d840ac..07f3a6c3 100644 --- a/checks/cloud/aws/ec2/no_excessive_port_access.cf.go +++ b/checks/cloud/aws/ec2/no_excessive_port_access.cf.go @@ -9,7 +9,7 @@ Resources: Type: AWS::EC2::NetworkAcl Properties: VpcId: "something" - RuleAction: "allow" + RuleAction: "allow" Rule: Type: AWS::EC2::NetworkAclEntry Properties: diff --git a/checks/cloud/aws/ec2/no_public_ingress_sgr.tf.go b/checks/cloud/aws/ec2/no_public_ingress_sgr.tf.go index e5d68d5d..12d84703 100644 --- a/checks/cloud/aws/ec2/no_public_ingress_sgr.tf.go +++ b/checks/cloud/aws/ec2/no_public_ingress_sgr.tf.go @@ -8,9 +8,9 @@ var terraformNoPublicIngressSgrGoodExamples = []string{ } `, ` -resource "aws_security_group_rule" "allow_partner_rsync" { +resource "aws_security_group_rule" "example" { type = "ingress" - security_group_id = aws_security_group.….id + security_group_id = "sg-123456" from_port = 22 to_port = 22 protocol = "tcp" diff --git a/checks/cloud/aws/eks/encrypt_secrets.cf.go b/checks/cloud/aws/eks/encrypt_secrets.cf.go index 81a53bd5..9789c411 100644 --- a/checks/cloud/aws/eks/encrypt_secrets.cf.go +++ b/checks/cloud/aws/eks/encrypt_secrets.cf.go @@ -11,10 +11,10 @@ Resources: RoleArn: >- arn:aws:iam::012345678910:role/eks-service-role-good-example EncryptionConfig: - Provider: - KeyArn: alias/eks-kms - Resources: - - secrets + - Provider: + KeyArn: alias/eks-kms + Resources: + - secrets ResourcesVpcConfig: SecurityGroupIds: - sg-6979fe18 diff --git a/checks/cloud/aws/eks/encrypt_secrets.rego b/checks/cloud/aws/eks/encrypt_secrets.rego index ef4087f4..b160e387 100644 --- a/checks/cloud/aws/eks/encrypt_secrets.rego +++ b/checks/cloud/aws/eks/encrypt_secrets.rego @@ -35,13 +35,22 @@ import rego.v1 deny contains res if { some cluster in input.aws.eks.clusters - cluster.encryption.secrets.value == false - res := result.new("Cluster does not have secret encryption enabled.", cluster.encryption.secrets) + not has_secrets(cluster) + res := result.new( + "Cluster does not have secret encryption enabled.", + object.get(cluster, ["encryption", "secrets"], cluster), + ) } deny contains res if { some cluster in input.aws.eks.clusters - cluster.encryption.secrets.value == true - cluster.encryption.kmskeyid.value == "" - res := result.new("Cluster encryption requires a KMS key ID, which is missing", cluster.encryption.kmskeyid) + has_secrets(cluster) + not has_key(cluster) + res := result.new( + "Cluster encryption requires a KMS key ID, which is missing", + object.get(cluster, ["encryption", "kmskeyid"], cluster), + ) } + +has_secrets(cluster) if cluster.encryption.secrets.value +has_key(cluster) if cluster.encryption.kmskeyid.value != "" \ No newline at end of file diff --git a/checks/cloud/aws/eks/encrypt_secrets.tf.go b/checks/cloud/aws/eks/encrypt_secrets.tf.go index 07a4867d..5f41283d 100644 --- a/checks/cloud/aws/eks/encrypt_secrets.tf.go +++ b/checks/cloud/aws/eks/encrypt_secrets.tf.go @@ -2,11 +2,15 @@ package eks var terraformEncryptSecretsGoodExamples = []string{ ` +resource "aws_kms_key" "eks" { + enable_key_rotation = true +} + resource "aws_eks_cluster" "good_example" { encryption_config { resources = [ "secrets" ] provider { - key_arn = var.kms_arn + key_arn = aws_kms_key.eks.arn } } diff --git a/checks/cloud/aws/kinesis/enable_in_transit_encryption.rego b/checks/cloud/aws/kinesis/enable_in_transit_encryption.rego index 5ab7e363..63315a66 100644 --- a/checks/cloud/aws/kinesis/enable_in_transit_encryption.rego +++ b/checks/cloud/aws/kinesis/enable_in_transit_encryption.rego @@ -35,13 +35,22 @@ import rego.v1 deny contains res if { some stream in input.aws.kinesis.streams - stream.encryption.type.value != "KMS" - res := result.new("Stream does not use KMS encryption.", stream.encryption.type) + not is_kms_encryption(stream) + res := result.new( + "Stream does not use KMS encryption.", + object.get(stream, ["encryption", "type"], stream), + ) } deny contains res if { some stream in input.aws.kinesis.streams - stream.encryption.type.value == "KMS" - stream.encryption.kmskeyid.value == "" - res := result.new("Stream does not use a custom-managed KMS key.", stream.encryption.kmskeyid) + is_kms_encryption(stream) + not has_kms_key(stream) + res := result.new( + "Stream does not use a custom-managed KMS key.", + object.get(stream, ["encryption", "kmskeyid"], stream), + ) } + +is_kms_encryption(stream) if stream.encryption.type.value == "KMS" +has_kms_key(stream) if stream.encryption.kmskeyid.value != "" \ No newline at end of file diff --git a/checks/cloud/aws/lambda/enable_tracing.rego b/checks/cloud/aws/lambda/enable_tracing.rego index d92766aa..ec2d2f36 100644 --- a/checks/cloud/aws/lambda/enable_tracing.rego +++ b/checks/cloud/aws/lambda/enable_tracing.rego @@ -35,6 +35,11 @@ import rego.v1 deny contains res if { some func in input.aws.lambda.functions - func.tracing.mode.value != "Active" - res := result.new("Function does not have tracing enabled.", func.tracing.mode) + not is_active_mode(func) + res := result.new( + "Function does not have tracing enabled.", + object.get(func, ["tracing", "mode"], func), + ) } + +is_active_mode(func) if func.tracing.mode.value == "Active" diff --git a/checks/cloud/aws/redshift/no_classic_resources.cf.go b/checks/cloud/aws/redshift/no_classic_resources.cf.go index 0464a607..3a0e0593 100644 --- a/checks/cloud/aws/redshift/no_classic_resources.cf.go +++ b/checks/cloud/aws/redshift/no_classic_resources.cf.go @@ -5,7 +5,10 @@ var cloudFormationNoClassicResourcesGoodExamples = []string{ AWSTemplateFormatVersion: 2010-09-09 Description: Good example of redshift sgr Resources: - + myCluster: + Type: "AWS::Redshift::Cluster" + Properties: + DBName: "mydb" `, } @@ -14,7 +17,7 @@ var cloudFormationNoClassicResourcesBadExamples = []string{ AWSTemplateFormatVersion: 2010-09-09 Description: Bad example of redshift sgr Resources: - Queue: + SecGroup: Type: AWS::Redshift::ClusterSecurityGroup Properties: Description: "" diff --git a/checks/cloud/aws/redshift/no_classic_resources.rego b/checks/cloud/aws/redshift/no_classic_resources.rego index d0d797ae..df50531e 100644 --- a/checks/cloud/aws/redshift/no_classic_resources.rego +++ b/checks/cloud/aws/redshift/no_classic_resources.rego @@ -29,6 +29,8 @@ package builtin.aws.redshift.aws0085 import rego.v1 +# TODO: detection of classic resources needs to be improved. Most likely this check is not relevant for Rego +# https://github.com/aws-samples/ec2-classic-resource-finder/tree/main deny contains res if { some group in input.aws.redshift.securitygroups res := result.new( diff --git a/checks/cloud/aws/s3/enable_bucket_encryption.cf.go b/checks/cloud/aws/s3/enable_bucket_encryption.cf.go index 0a48940d..cef98288 100644 --- a/checks/cloud/aws/s3/enable_bucket_encryption.cf.go +++ b/checks/cloud/aws/s3/enable_bucket_encryption.cf.go @@ -19,13 +19,6 @@ var cloudFormationEnableBucketEncryptionBadExamples = []string{ Resources: BadExample: Type: AWS::S3::Bucket - Properties: - BucketEncryption: - ServerSideEncryptionConfiguration: - - BucketKeyEnabled: false - ServerSideEncryptionByDefault: - KMSMasterKeyID: alias/alias-name - SSEAlgorithm: aws:kms `, } diff --git a/checks/cloud/aws/s3/no_public_buckets.cf.go b/checks/cloud/aws/s3/no_public_buckets.cf.go index e1fc36d2..df966d61 100644 --- a/checks/cloud/aws/s3/no_public_buckets.cf.go +++ b/checks/cloud/aws/s3/no_public_buckets.cf.go @@ -17,8 +17,8 @@ Resources: var cloudFormationNoPublicBucketsBadExamples = []string{ `--- Resources: - Type: AWS::S3::Bucket BadExample: + Type: AWS::S3::Bucket Properties: AccessControl: AuthenticatedRead `, diff --git a/checks/cloud/aws/s3/no_public_buckets.rego b/checks/cloud/aws/s3/no_public_buckets.rego index c6dd3e9e..e122c532 100644 --- a/checks/cloud/aws/s3/no_public_buckets.rego +++ b/checks/cloud/aws/s3/no_public_buckets.rego @@ -54,4 +54,4 @@ deny contains res if { bucket.publicaccessblock, ), ) -} +} \ No newline at end of file diff --git a/checks/cloud/aws/sns/enable_topic_encryption.cf.go b/checks/cloud/aws/sns/enable_topic_encryption.cf.go index f3cba85b..8b16c677 100644 --- a/checks/cloud/aws/sns/enable_topic_encryption.cf.go +++ b/checks/cloud/aws/sns/enable_topic_encryption.cf.go @@ -4,7 +4,7 @@ var cloudFormationEnableTopicEncryptionGoodExamples = []string{ `--- Resources: GoodTopic: - Type: AWS::SQS::Topic + Type: AWS::SNS::Topic Properties: TopicName: blah KmsMasterKeyId: some-key diff --git a/checks/cloud/nifcloud/computing/no_public_ingress_sgr.tf.go b/checks/cloud/nifcloud/computing/no_public_ingress_sgr.tf.go index 60206563..53a4a23e 100644 --- a/checks/cloud/nifcloud/computing/no_public_ingress_sgr.tf.go +++ b/checks/cloud/nifcloud/computing/no_public_ingress_sgr.tf.go @@ -8,9 +8,14 @@ var terraformNoPublicIngressSgrGoodExamples = []string{ } `, ` -resource "nifcloud_security_group_rule" "allow_partner_rsync" { +resource "nifcloud_security_group" "example" { + group_name = "allowtcp" + availability_zone = "east-11" +} + +resource "nifcloud_security_group_rule" "example" { type = "IN" - security_group_names = [nifcloud_security_group.….group_name] + security_group_names = [nifcloud_security_group.example.group_name] from_port = 22 to_port = 22 protocol = "TCP" diff --git a/checks/cloud/nifcloud/network/add_security_group_to_router.rego b/checks/cloud/nifcloud/network/add_security_group_to_router.rego index 0bf73643..91151f7a 100644 --- a/checks/cloud/nifcloud/network/add_security_group_to_router.rego +++ b/checks/cloud/nifcloud/network/add_security_group_to_router.rego @@ -32,6 +32,11 @@ import rego.v1 deny contains res if { some router in input.nifcloud.network.routers - router.securitygroup.value == "" - res := result.new("Router does not have a securiy group.", router.securitygroup) + not has_security_group(router) + res := result.new( + "Router does not have a securiy group.", + object.get(router, "securitygroup", router), + ) } + +has_security_group(router) if router.securitygroup.value != "" \ No newline at end of file diff --git a/internal/checks/checks.go b/internal/checks/checks.go new file mode 100644 index 00000000..51c02fa7 --- /dev/null +++ b/internal/checks/checks.go @@ -0,0 +1,30 @@ +package checks + +import ( + "sort" + + "github.com/aquasecurity/trivy/pkg/iac/framework" + "github.com/aquasecurity/trivy/pkg/iac/rego" + "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy/pkg/iac/scan" +) + +func LoadRegoChecks() []scan.Rule { + // Clean up all Go checks + rules.Reset() + + // Load Rego checks + rego.LoadAndRegister() + + var res []scan.Rule + + for _, metadata := range rules.GetRegistered(framework.ALL) { + res = append(res, metadata.Rule) + } + + sort.Slice(res, func(i, j int) bool { + return res[i].AVDID < res[j].AVDID + }) + + return res +} diff --git a/internal/checks/examples.go b/internal/checks/examples.go new file mode 100644 index 00000000..15cfc94f --- /dev/null +++ b/internal/checks/examples.go @@ -0,0 +1,117 @@ +package checks + +import ( + goast "go/ast" + "go/parser" + "go/token" + "strings" + + trivy_checks "github.com/aquasecurity/trivy-checks" + "github.com/aquasecurity/trivy/pkg/iac/scan" +) + +type Provider string + +const ( + TerraformProvider Provider = "Terraform" + CloudFormationProvider Provider = "CloudFormation" +) + +func providerByFileName(n string) Provider { + switch { + case strings.HasSuffix(n, "tf.go"): + return TerraformProvider + case strings.HasSuffix(n, "cf.go"): + return CloudFormationProvider + } + + panic("unreachable") +} + +type Example struct { + Path string + Provider Provider + GoodExample bool // bad example if false + Content string +} + +func GetCheckExamples(check scan.Rule) ([]*Example, error) { + var files []string + if check.Terraform != nil { + files = append(files, check.Terraform.BadExamples...) + // files = append(files, check.Terraform.GoodExamples...) + } + + if check.CloudFormation != nil { + files = append(files, check.CloudFormation.BadExamples...) + // files = append(files, check.CloudFormation.GoodExamples...) + } + + var res []*Example + + if check.RegoPackage != "" { + for _, path := range files { + exmpls, err := parseExamplesFromFile(path) + if err != nil { + return nil, err + } + + res = append(res, exmpls...) + } + } + + return res, nil +} + +func parseExamplesFromFile(filename string) ([]*Example, error) { + r, err := trivy_checks.EmbeddedPolicyFileSystem.Open(filename) + if err != nil { + return nil, err + } + + fset := token.NewFileSet() + f, err := parser.ParseFile(fset, filename, r, parser.AllErrors) + if err != nil { + return nil, err + } + return extractExamples(f, filename), nil +} + +func extractExamples(f *goast.File, filename string) (res []*Example) { + goast.Inspect(f, func(n goast.Node) bool { + valueSpec, ok := n.(*goast.ValueSpec) + if !ok { + return true + } + + for _, id := range valueSpec.Names { + if !isExampleName(id.Name) { + continue + } + + if compositeLit, ok := valueSpec.Values[0].(*goast.CompositeLit); ok { + for _, e := range compositeLit.Elts { + if basicLit, ok := e.(*goast.BasicLit); ok { + res = append(res, &Example{ + Path: filename, + GoodExample: strings.HasSuffix(id.Name, "GoodExamples"), + Provider: providerByFileName(filename), + Content: cleanupExample(basicLit.Value), + }) + } + } + } + } + return true + }) + + return res +} + +func isExampleName(name string) bool { + return strings.HasSuffix(name, "GoodExamples") || strings.HasSuffix(name, "BadExamples") +} + +func cleanupExample(s string) string { + return strings.ReplaceAll(s, "`", "") +} diff --git a/test/examples_test.go b/test/examples_test.go index b818dc90..ecb5b327 100644 --- a/test/examples_test.go +++ b/test/examples_test.go @@ -3,75 +3,88 @@ package test import ( "context" "fmt" - goast "go/ast" - "go/parser" - "go/token" "io/fs" - "strings" "testing" "testing/fstest" - checks "github.com/aquasecurity/trivy-checks" - rules "github.com/aquasecurity/trivy/pkg/iac/rules" + "github.com/aquasecurity/trivy-checks/internal/checks" + "github.com/aquasecurity/trivy/pkg/iac/rego" "github.com/aquasecurity/trivy/pkg/iac/scan" "github.com/aquasecurity/trivy/pkg/iac/scanners/cloudformation" + "github.com/aquasecurity/trivy/pkg/iac/scanners/options" "github.com/aquasecurity/trivy/pkg/iac/scanners/terraform" "github.com/stretchr/testify/require" ) func TestCheckExamples(t *testing.T) { - tfScanner := terraform.New() - cfScanner := cloudformation.New() - - for _, registeredRule := range rules.GetRegistered() { - t.Run(registeredRule.AVDID, func(t *testing.T) { - tfexamples := getExamplesFromRule(t, registeredRule.Rule, registeredRule.Terraform) - for i, example := range tfexamples { - t.Run(fmt.Sprintf("terraform_%d", i), func(t *testing.T) { - scanExample(t, tfScanner, registeredRule.LongID(), example) - }) - } - - cfexamples := getExamplesFromRule(t, registeredRule.Rule, registeredRule.CloudFormation) - for i, example := range cfexamples { - t.Run(fmt.Sprintf("cloudformation_%d", i), func(t *testing.T) { - scanExample(t, cfScanner, registeredRule.LongID(), example) + opts := []options.ScannerOption{ + rego.WithEmbeddedLibraries(true), + rego.WithEmbeddedPolicies(true), + } + tfScanner := terraform.New(opts...) + cfScanner := cloudformation.New(opts...) + + for _, check := range checks.LoadRegoChecks() { + t.Run(check.AVDID, func(t *testing.T) { + exmpls, err := checks.GetCheckExamples(check) + require.NoError(t, err) + + for i, example := range exmpls { + s := getScannerForProvider(example.Provider, tfScanner, cfScanner) + require.NotNil(t, s) + t.Run(fmt.Sprintf("%s_%d", example.Provider, i), func(t *testing.T) { + scanExample(t, s, check.LongID(), example) }) } }) } } +func getScannerForProvider(provider checks.Provider, tfScanner, cfScanner scanner) scanner { + switch provider { + case checks.TerraformProvider: + return tfScanner + case checks.CloudFormationProvider: + return cfScanner + default: + return nil + } +} + type scanner interface { ScanFS(ctx context.Context, fsys fs.FS, dir string) (scan.Results, error) } -func scanExample(t *testing.T, s scanner, checkID string, example example) { - +func scanExample(t *testing.T, s scanner, checkID string, example *checks.Example) { var filename string - switch s.(type) { - case *terraform.Scanner: + switch example.Provider { + case checks.TerraformProvider: filename = fmt.Sprintf("%s.tf", checkID) - case *cloudformation.Scanner: + case checks.CloudFormationProvider: filename = fmt.Sprintf("%s.yaml", checkID) } fsys := fstest.MapFS{ filename: &fstest.MapFile{ - Data: []byte(example.content), + Data: []byte(example.Content), }, } res, err := s.ScanFS(context.TODO(), fsys, ".") require.NoError(t, err) - contains := resultsContainsCheck(res, checkID, example.good) + + assertResultContainsCheck(t, res, checkID, example) +} + +func assertResultContainsCheck(t *testing.T, results scan.Results, checkID string, example *checks.Example) { + contains := resultsContainsCheck(results, checkID, example.GoodExample) if !contains { exampleType := "good" - if !example.good { + if !example.GoodExample { exampleType = "bad" } - t.Fatalf("results does not contain check %q for %s example: %s", - checkID, exampleType, example.content) + t.Fatalf("results do not contain check %q for %s example: %s", + checkID, exampleType, example.Content) } } @@ -90,90 +103,3 @@ func resultsContainsCheck(results scan.Results, checkID string, good bool) bool return false } - -type example struct { - content string - good bool -} - -func getExamplesFromRule(t *testing.T, r scan.Rule, engine *scan.EngineMetadata) []example { - if engine == nil { - return nil - } - - examples := getExamplesForType(t, r, engine.GoodExamples, "GoodExamples") - examples = append(examples, getExamplesForType(t, r, engine.BadExamples, "BadExamples")...) - return examples -} - -func getExamplesForType(t *testing.T, r scan.Rule, files []string, exampleType string) []example { - var res []example - - if r.RegoPackage != "" { - for _, exampleFile := range files { - contents, err := getExampleValuesFromFile(exampleFile, exampleType) - if err != nil { - require.NoError(t, err) - } - - for _, content := range contents { - res = append(res, example{ - content: content, - good: exampleType == "GoodExamples", - }) - } - } - } else { - for _, exampleFile := range files { - res = append(res, example{ - content: exampleFile, - good: exampleType == "GoodExamples", - }) - } - } - - return res -} - -func getExampleValuesFromFile(filename string, exampleType string) ([]string, error) { - r, err := checks.EmbeddedPolicyFileSystem.Open(filename) - if err != nil { - return nil, err - } - f, err := parser.ParseFile(token.NewFileSet(), filename, r, parser.AllErrors) - if err != nil { - return nil, err - } - - res := []string{} - - for _, d := range f.Decls { - switch decl := d.(type) { - case *goast.GenDecl: - for _, spec := range decl.Specs { - switch spec := spec.(type) { - case *goast.ValueSpec: - for _, id := range spec.Names { - switch v := id.Obj.Decl.(*goast.ValueSpec).Values[0].(type) { - case *goast.CompositeLit: - for _, e := range v.Elts { - switch e := e.(type) { - case *goast.BasicLit: - if strings.HasSuffix(id.Name, exampleType) { - res = append(res, strings.ReplaceAll(e.Value, "`", "")) - } - } - } - } - } - } - } - } - } - - if len(res) == 0 { - return nil, fmt.Errorf("exampleType %s not found in file: %s", exampleType, filename) - } - - return res, nil -}