diff --git a/pkg/rego/scanner.go b/pkg/rego/scanner.go index 9959a89dd..abfc370da 100644 --- a/pkg/rego/scanner.go +++ b/pkg/rego/scanner.go @@ -285,6 +285,10 @@ func isPolicyWithSubtype(sourceType types.Source) bool { } func checkSubtype(ii map[string]interface{}, provider string, subTypes []SubType) bool { + if len(subTypes) == 0 { // policy always applies if no subtypes + return true + } + for _, st := range subTypes { switch services := ii[provider].(type) { case map[string]interface{}: // cloud @@ -310,7 +314,7 @@ func isPolicyApplicable(staticMetadata *StaticMetadata, inputs ...Input) bool { if ii, ok := input.Contents.(map[string]interface{}); ok { for provider := range ii { // TODO(simar): Add other providers - if !strings.Contains(strings.Join([]string{"kind", "aws"}, ","), provider) { + if !strings.Contains(strings.Join([]string{"kind", "aws", "azure"}, ","), provider) { continue } diff --git a/pkg/scanners/cloud/aws/scanner_test.go b/pkg/scanners/cloud/aws/scanner_test.go index 8bc03a715..938a1491b 100644 --- a/pkg/scanners/cloud/aws/scanner_test.go +++ b/pkg/scanners/cloud/aws/scanner_test.go @@ -2,11 +2,16 @@ package aws import ( "context" + "io/fs" "testing" + "github.com/aquasecurity/defsec/pkg/providers/azure" + "github.com/aquasecurity/defsec/pkg/providers/azure/authorization" + + "github.com/aquasecurity/defsec/pkg/providers/aws/iam" + "github.com/aquasecurity/defsec/pkg/framework" "github.com/aquasecurity/defsec/pkg/providers/aws" - "github.com/aquasecurity/defsec/pkg/providers/aws/iam" "github.com/aquasecurity/defsec/pkg/providers/aws/rds" "github.com/aquasecurity/defsec/pkg/scanners/options" "github.com/aquasecurity/defsec/pkg/state" @@ -53,10 +58,189 @@ func TestScanner_GetRegisteredRules(t *testing.T) { } } -func Test_checkPolicyIsApplicable(t *testing.T) { - t.Run("single cloud", func(t *testing.T) { - srcFS := testutil.CreateFS(t, map[string]string{ - "policies/rds_policy.rego": `# METADATA +func Test_AWSInputSelectors(t *testing.T) { + testCases := []struct { + name string + srcFS fs.FS + state state.State + expectedResults struct { + totalResults int + summaries []string + } + }{ + { + name: "selector is not defined", + srcFS: testutil.CreateFS(t, map[string]string{ + "policies/rds_policy.rego": `# METADATA +# title: "RDS Publicly Accessible" +# custom: +# input: +package builtin.aws.rds.aws0999 + +deny[res] { + res := true +} +`, + "policies/cloudtrail_policy.rego": `# METADATA +# title: "CloudTrail Bucket Delete Policy" +# custom: +# input: +package builtin.aws.cloudtrail.aws0888 + +deny[res] { + res := true +} +`, + }), + state: state.State{AWS: aws.AWS{ + RDS: rds.RDS{ + Instances: []rds.Instance{ + {Metadata: defsecTypes.Metadata{}, + PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()), + }, + }, + }, + // note: there is no CloudTrail resource in our AWS state (so we expect no results for it) + }}, + expectedResults: struct { + totalResults int + summaries []string + }{totalResults: 0}, + }, + { + name: "selector is empty", + srcFS: testutil.CreateFS(t, map[string]string{ + "policies/rds_policy.rego": `# METADATA +# title: "RDS Publicly Accessible" +# custom: +# input: +# selector: + +package builtin.aws.rds.aws0999 + +deny[res] { + res := true +} +`, + "policies/cloudtrail_policy.rego": `# METADATA +# title: "CloudTrail Bucket Delete Policy" +# custom: +# input: +# selector: +package builtin.aws.cloudtrail.aws0888 + +deny[res] { + res := true +} +`, + }), + state: state.State{AWS: aws.AWS{ + RDS: rds.RDS{ + Instances: []rds.Instance{ + {Metadata: defsecTypes.Metadata{}, + PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()), + }, + }, + }, + }}, + expectedResults: struct { + totalResults int + summaries []string + }{totalResults: 0}, + }, + { + name: "selector without subtype", + srcFS: testutil.CreateFS(t, map[string]string{ + "policies/rds_policy.rego": `# METADATA +# title: "RDS Publicly Accessible" +# custom: +# input: +# selector: +# - type: cloud +package builtin.aws.rds.aws0999 + +deny[res] { + res := true +} +`, + "policies/cloudtrail_policy.rego": `# METADATA +# title: "CloudTrail Bucket Delete Policy" +# custom: +# input: +# selector: +# - type: cloud +package builtin.aws.cloudtrail.aws0888 + +deny[res] { + res := true +} +`, + }), + state: state.State{AWS: aws.AWS{ + RDS: rds.RDS{ + Instances: []rds.Instance{ + {Metadata: defsecTypes.Metadata{}, + PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()), + }, + }, + }, + // note: there is no CloudTrail resource in our AWS state (so we expect no results for it) + }}, + expectedResults: struct { + totalResults int + summaries []string + }{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "CloudTrail Bucket Delete Policy"}}, + }, + { + name: "selector is defined with empty subtype", + srcFS: testutil.CreateFS(t, map[string]string{ + "policies/rds_policy.rego": `# METADATA +# title: "RDS Publicly Accessible" +# custom: +# input: +# selector: +# - type: cloud +# subtypes: + +package builtin.aws.rds.aws0999 + +deny[res] { + res := true +} +`, + "policies/cloudtrail_policy.rego": `# METADATA +# title: "CloudTrail Bucket Delete Policy" +# custom: +# input: +# selector: +# - type: cloud +# subtypes: +package builtin.aws.cloudtrail.aws0888 + +deny[res] { + res := true +} +`, + }), + state: state.State{AWS: aws.AWS{ + RDS: rds.RDS{ + Instances: []rds.Instance{ + {Metadata: defsecTypes.Metadata{}, + PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()), + }, + }, + }, + // note: there is no CloudTrail resource in our AWS state (so we expect no results for it) + }}, + expectedResults: struct { + totalResults int + summaries []string + }{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "CloudTrail Bucket Delete Policy"}}, + }, + { + name: "single cloud, single selector", + srcFS: testutil.CreateFS(t, map[string]string{ + "policies/rds_policy.rego": `# METADATA # title: "RDS Publicly Accessible" # description: "Ensures RDS instances are not launched into the public cloud." # scope: package @@ -85,7 +269,7 @@ deny[res] { res := result.new("Instance has Public Access enabled", instance.publicaccess) } `, - "policies/cloudtrail_policy.rego": `# METADATA + "policies/cloudtrail_policy.rego": `# METADATA # title: "CloudTrail Bucket Delete Policy" # description: "Ensures CloudTrail logging bucket has a policy to prevent deletion of logs without an MFA token" # scope: package @@ -117,33 +301,26 @@ deny[res] { res := result.new("Bucket has MFA delete disabled", bucket.name) } `, - }) - scanner := New( - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithPolicyFilesystem(srcFS), - options.ScannerWithRegoOnly(true), - options.ScannerWithPolicyDirs("policies/")) - - st := state.State{AWS: aws.AWS{ - RDS: rds.RDS{ - Instances: []rds.Instance{ - {Metadata: defsecTypes.Metadata{}, - PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()), + }), + state: state.State{AWS: aws.AWS{ + RDS: rds.RDS{ + Instances: []rds.Instance{ + {Metadata: defsecTypes.Metadata{}, + PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()), + }, }, }, - }, - // note: there is no CloudTrail resource in our AWS state (so we expect no results for it) - }} - - results, err := scanner.Scan(context.TODO(), &st) - require.NoError(t, err) - require.Equal(t, 1, len(results)) - require.Equal(t, "RDS Publicly Accessible", results.GetFailed()[0].Rule().Summary) - }) - - t.Run("multi cloud with similarly named services", func(t *testing.T) { - srcFS := testutil.CreateFS(t, map[string]string{ - "policies/azure_iam_policy.rego": `# METADATA + // note: there is no CloudTrail resource in our AWS state (so we expect no results for it) + }}, + expectedResults: struct { + totalResults int + summaries []string + }{totalResults: 1, summaries: []string{"RDS Publicly Accessible"}}, + }, + { + name: "multi cloud, single selector, same named service", + srcFS: testutil.CreateFS(t, map[string]string{ + "policies/azure_iam_policy.rego": `# METADATA # title: "Azure IAM Policy" # custom: # input: @@ -158,7 +335,7 @@ deny[res] { res := true } `, - "policies/aws_iam_policy.rego": `# METADATA + "policies/aws_iam_policy.rego": `# METADATA # title: "AWS IAM Policy" # custom: # input: @@ -173,23 +350,54 @@ deny[res] { res := true } `, - }) - scanner := New( - options.ScannerWithEmbeddedPolicies(false), - options.ScannerWithPolicyFilesystem(srcFS), - options.ScannerWithRegoOnly(true), - options.ScannerWithPolicyDirs("policies/")) - - st := state.State{AWS: aws.AWS{ - IAM: iam.IAM{ - PasswordPolicy: iam.PasswordPolicy{MinimumLength: defsecTypes.Int(1, defsecTypes.NewTestMetadata())}, + }), + state: state.State{ + AWS: aws.AWS{ + IAM: iam.IAM{ + PasswordPolicy: iam.PasswordPolicy{ + MinimumLength: defsecTypes.Int(1, defsecTypes.NewTestMetadata()), + }}, + }, + Azure: azure.Azure{ + Authorization: authorization.Authorization{ + []authorization.RoleDefinition{{ + Metadata: defsecTypes.NewTestMetadata(), + Permissions: []authorization.Permission{ + { + Metadata: defsecTypes.NewTestMetadata(), + Actions: []defsecTypes.StringValue{ + defsecTypes.String("*", defsecTypes.NewTestMetadata()), + }, + }, + }, + AssignableScopes: []defsecTypes.StringValue{ + defsecTypes.StringUnresolvable(defsecTypes.NewTestMetadata()), + }}, + }}, + }, + // note: there is no Azure IAM in our cloud state (so we expect no results for it) }, - // note: there is no Azure IAM in our cloud state (so we expect no results for it) - }} - - results, err := scanner.Scan(context.TODO(), &st) - require.NoError(t, err) - require.Equal(t, 1, len(results)) - require.Equal(t, "AWS IAM Policy", results.GetFailed()[0].Rule().Summary) - }) + expectedResults: struct { + totalResults int + summaries []string + }{totalResults: 1, summaries: []string{"AWS IAM Policy"}}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + scanner := New( + options.ScannerWithEmbeddedPolicies(false), + options.ScannerWithPolicyFilesystem(tc.srcFS), + options.ScannerWithRegoOnly(true), + options.ScannerWithPolicyDirs("policies/")) + + results, err := scanner.Scan(context.TODO(), &tc.state) + require.NoError(t, err, tc.name) + require.Equal(t, tc.expectedResults.totalResults, len(results), tc.name) + for i := range results.GetFailed() { + require.Contains(t, tc.expectedResults.summaries, results.GetFailed()[i].Rule().Summary, tc.name) + } + }) + } }