github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/cloud/aws/scanner_test.go (about)

     1  package aws
     2  
     3  import (
     4  	"context"
     5  	"io/fs"
     6  	"testing"
     7  
     8  	"github.com/khulnasoft-lab/defsec/pkg/providers/azure"
     9  	"github.com/khulnasoft-lab/defsec/pkg/providers/azure/authorization"
    10  
    11  	"github.com/khulnasoft-lab/defsec/pkg/providers/aws/iam"
    12  
    13  	"github.com/khulnasoft-lab/defsec/pkg/framework"
    14  	"github.com/khulnasoft-lab/defsec/pkg/providers/aws"
    15  	"github.com/khulnasoft-lab/defsec/pkg/providers/aws/rds"
    16  	"github.com/khulnasoft-lab/defsec/pkg/scanners/options"
    17  	"github.com/khulnasoft-lab/defsec/pkg/state"
    18  	defsecTypes "github.com/khulnasoft-lab/defsec/pkg/types"
    19  	"github.com/khulnasoft-lab/defsec/test/testutil"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  func TestScanner_GetRegisteredRules(t *testing.T) {
    25  	testCases := []struct {
    26  		name    string
    27  		scanner *Scanner
    28  	}{
    29  		{
    30  			name: "get framework rules",
    31  			scanner: &Scanner{
    32  				frameworks: []framework.Framework{framework.CIS_AWS_1_2},
    33  			},
    34  		},
    35  		{
    36  			name: "get spec rules",
    37  			scanner: &Scanner{
    38  				spec: "awscis1.2",
    39  			},
    40  		},
    41  		{
    42  			name: "invalid spec",
    43  			scanner: &Scanner{
    44  				spec: "invalid spec",
    45  				// we still expect default rules to work
    46  			},
    47  		},
    48  	}
    49  
    50  	for _, tc := range testCases {
    51  		t.Run(tc.name, func(t *testing.T) {
    52  			for _, i := range tc.scanner.getRegisteredRules() {
    53  				if _, ok := i.Rule().Frameworks[framework.CIS_AWS_1_2]; !ok {
    54  					assert.FailNow(t, "unexpected rule found: ", i.Rule().AVDID, tc.name)
    55  				}
    56  			}
    57  		})
    58  	}
    59  }
    60  
    61  func Test_AWSInputSelectors(t *testing.T) {
    62  	testCases := []struct {
    63  		name            string
    64  		srcFS           fs.FS
    65  		dataFS          fs.FS
    66  		state           state.State
    67  		expectedResults struct {
    68  			totalResults int
    69  			summaries    []string
    70  		}
    71  	}{
    72  		{
    73  			name: "selector is not defined",
    74  			srcFS: testutil.CreateFS(t, map[string]string{
    75  				"policies/rds_policy.rego": `# METADATA
    76  # title: "RDS Publicly Accessible"
    77  # custom:
    78  #   input:
    79  package builtin.aws.rds.aws0999
    80  
    81  deny[res] {
    82  	res := true
    83  }
    84  `,
    85  				"policies/cloudtrail_policy.rego": `# METADATA
    86  # title: "CloudTrail Bucket Delete Policy"
    87  # custom:
    88  #   input:
    89  package builtin.aws.cloudtrail.aws0888
    90  
    91  deny[res] {
    92  	res := true
    93  }
    94  `,
    95  			}),
    96  			state: state.State{AWS: aws.AWS{
    97  				RDS: rds.RDS{
    98  					Instances: []rds.Instance{
    99  						{Metadata: defsecTypes.Metadata{},
   100  							PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
   101  						},
   102  					},
   103  				},
   104  				// note: there is no CloudTrail resource in our AWS state (so we expect no results for it)
   105  			}},
   106  			expectedResults: struct {
   107  				totalResults int
   108  				summaries    []string
   109  			}{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "CloudTrail Bucket Delete Policy"}},
   110  		},
   111  		{
   112  			name: "selector is empty",
   113  			srcFS: testutil.CreateFS(t, map[string]string{
   114  				"policies/rds_policy.rego": `# METADATA
   115  # title: "RDS Publicly Accessible"
   116  # custom:
   117  #   input:
   118  #     selector:
   119          
   120  package builtin.aws.rds.aws0999
   121  
   122  deny[res] {
   123  	res := true
   124  }
   125  `,
   126  				"policies/cloudtrail_policy.rego": `# METADATA
   127  # title: "CloudTrail Bucket Delete Policy"
   128  # custom:
   129  #   input:
   130  #     selector:
   131  package builtin.aws.cloudtrail.aws0888
   132  
   133  deny[res] {
   134  	res := true
   135  }
   136  `,
   137  			}),
   138  			state: state.State{AWS: aws.AWS{
   139  				RDS: rds.RDS{
   140  					Instances: []rds.Instance{
   141  						{Metadata: defsecTypes.Metadata{},
   142  							PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
   143  						},
   144  					},
   145  				},
   146  			}},
   147  			expectedResults: struct {
   148  				totalResults int
   149  				summaries    []string
   150  			}{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "CloudTrail Bucket Delete Policy"}},
   151  		},
   152  		{
   153  			name: "selector without subtype",
   154  			srcFS: testutil.CreateFS(t, map[string]string{
   155  				"policies/rds_policy.rego": `# METADATA
   156  # title: "RDS Publicly Accessible"
   157  # custom:
   158  #   input:
   159  #     selector:
   160  #     - type: cloud
   161  package builtin.aws.rds.aws0999
   162  
   163  deny[res] {
   164  	res := true
   165  }
   166  `,
   167  				"policies/cloudtrail_policy.rego": `# METADATA
   168  # title: "CloudTrail Bucket Delete Policy"
   169  # custom:
   170  #   input:
   171  #     selector:
   172  #     - type: cloud
   173  package builtin.aws.cloudtrail.aws0888
   174  
   175  deny[res] {
   176  	res := true
   177  }
   178  `,
   179  			}),
   180  			state: state.State{AWS: aws.AWS{
   181  				RDS: rds.RDS{
   182  					Instances: []rds.Instance{
   183  						{Metadata: defsecTypes.Metadata{},
   184  							PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
   185  						},
   186  					},
   187  				},
   188  				// note: there is no CloudTrail resource in our AWS state (so we expect no results for it)
   189  			}},
   190  			expectedResults: struct {
   191  				totalResults int
   192  				summaries    []string
   193  			}{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "CloudTrail Bucket Delete Policy"}},
   194  		},
   195  		{
   196  			name: "conflicting selectors",
   197  			srcFS: testutil.CreateFS(t, map[string]string{
   198  				"policies/rds_policy.rego": `# METADATA
   199  # title: "RDS Publicly Accessible"
   200  # custom:
   201  #   provider: aws
   202  #   service: rds
   203  #   input:
   204  #     selector:
   205  #     - type: cloud
   206  #       subtypes:
   207  #         - provider: aws
   208  #           service: ec2
   209  package builtin.aws.rds.aws0999
   210  
   211  deny[res] {
   212  	res := true
   213  }
   214  `,
   215  			}),
   216  
   217  			state: state.State{AWS: aws.AWS{
   218  				RDS: rds.RDS{
   219  					Instances: []rds.Instance{
   220  						{Metadata: defsecTypes.Metadata{},
   221  							PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
   222  						},
   223  					},
   224  				},
   225  			}},
   226  			expectedResults: struct {
   227  				totalResults int
   228  				summaries    []string
   229  			}{totalResults: 0},
   230  		},
   231  		{
   232  			name: "selector is defined with empty subtype",
   233  			srcFS: testutil.CreateFS(t, map[string]string{
   234  				"policies/rds_policy.rego": `# METADATA
   235  # title: "RDS Publicly Accessible"
   236  # custom:
   237  #   input:
   238  #     selector:
   239  #     - type: cloud
   240  #       subtypes:
   241  
   242  package builtin.aws.rds.aws0999
   243  
   244  deny[res] {
   245  	res := true
   246  }
   247  `,
   248  				"policies/cloudtrail_policy.rego": `# METADATA
   249  # title: "CloudTrail Bucket Delete Policy"
   250  # custom:
   251  #   input:
   252  #     selector:
   253  #     - type: cloud
   254  #       subtypes:
   255  package builtin.aws.cloudtrail.aws0888
   256  
   257  deny[res] {
   258  	res := true
   259  }
   260  `,
   261  			}),
   262  			state: state.State{AWS: aws.AWS{
   263  				RDS: rds.RDS{
   264  					Instances: []rds.Instance{
   265  						{Metadata: defsecTypes.Metadata{},
   266  							PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
   267  						},
   268  					},
   269  				},
   270  				// note: there is no CloudTrail resource in our AWS state (so we expect no results for it)
   271  			}},
   272  			expectedResults: struct {
   273  				totalResults int
   274  				summaries    []string
   275  			}{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "CloudTrail Bucket Delete Policy"}},
   276  		},
   277  		{
   278  			name: "single cloud, single selector",
   279  			srcFS: testutil.CreateFS(t, map[string]string{
   280  				"policies/rds_policy.rego": `# METADATA
   281  # title: "RDS Publicly Accessible"
   282  # description: "Ensures RDS instances are not launched into the public cloud."
   283  # scope: package
   284  # schemas:
   285  # - input: schema.input
   286  # related_resources:
   287  # - http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.html
   288  # custom:
   289  #   avd_id: AVD-AWS-0999
   290  #   provider: aws
   291  #   service: rds
   292  #   severity: HIGH
   293  #   short_code: enable-public-access
   294  #   recommended_action: "Remove the public endpoint from the RDS instance'"
   295  #   input:
   296  #     selector:
   297  #     - type: cloud
   298  #       subtypes:
   299  #         - provider: aws
   300  #           service: rds
   301  package builtin.aws.rds.aws0999
   302  
   303  deny[res] {
   304  	instance := input.aws.rds.instances[_]
   305  	instance.publicaccess.value
   306  	res := result.new("Instance has Public Access enabled", instance.publicaccess)
   307  }
   308  `,
   309  				"policies/cloudtrail_policy.rego": `# METADATA
   310  # title: "CloudTrail Bucket Delete Policy"
   311  # description: "Ensures CloudTrail logging bucket has a policy to prevent deletion of logs without an MFA token"
   312  # scope: package
   313  # schemas:
   314  # - input: schema.input
   315  # related_resources:
   316  # - http://docs.aws.amazon.com/AmazonS3/latest/dev/Versioning.html#MultiFactorAuthenticationDelete
   317  # custom:
   318  #   avd_id: AVD-AWS-0888
   319  #   provider: aws
   320  #   service: cloudtrail
   321  #   severity: HIGH
   322  #   short_code: bucket_delete
   323  #   recommended_action: "Enable MFA delete on the CloudTrail bucket"
   324  #   input:
   325  #     selector:
   326  #     - type: cloud
   327  #       subtypes: 
   328  #         - provider: aws 
   329  #           service: cloudtrail
   330  package builtin.aws.cloudtrail.aws0888
   331  
   332  deny[res] {
   333  	trail := input.aws.cloudtrail.trails[_]
   334  	trail.bucketname.value != ""
   335      bucket := input.aws.s3.buckets[_]
   336      bucket.name.value == trail.bucketname.value
   337      not bucket.versioning.mfadelete.value
   338  	res := result.new("Bucket has MFA delete disabled", bucket.name)
   339  }
   340  `,
   341  			}),
   342  			state: state.State{AWS: aws.AWS{
   343  				RDS: rds.RDS{
   344  					Instances: []rds.Instance{
   345  						{Metadata: defsecTypes.Metadata{},
   346  							PublicAccess: defsecTypes.Bool(true, defsecTypes.NewTestMetadata()),
   347  						},
   348  					},
   349  				},
   350  				// note: there is no CloudTrail resource in our AWS state (so we expect no results for it)
   351  			}},
   352  			expectedResults: struct {
   353  				totalResults int
   354  				summaries    []string
   355  			}{totalResults: 1, summaries: []string{"RDS Publicly Accessible"}},
   356  		},
   357  		{
   358  			name: "multi cloud, single selector, same named service",
   359  			srcFS: testutil.CreateFS(t, map[string]string{
   360  				"policies/azure_iam_policy.rego": `# METADATA
   361  # title: "Azure IAM Policy"
   362  # custom:
   363  #   input:
   364  #     selector:
   365  #     - type: cloud
   366  #       subtypes:
   367  #         - provider: azure 
   368  #           service: iam 
   369  package builtin.azure.iam.iam1234
   370  
   371  deny[res] {
   372  	res := true
   373  }
   374  `,
   375  				"policies/aws_iam_policy.rego": `# METADATA
   376  # title: "AWS IAM Policy"
   377  # custom:
   378  #   input:
   379  #     selector:
   380  #     - type: cloud
   381  #       subtypes:
   382  #         - provider: aws 
   383  #           service: iam 
   384  package builtin.aws.iam.iam5678
   385  
   386  deny[res] {
   387  	res := true
   388  }
   389  `,
   390  			}),
   391  			state: state.State{
   392  				AWS: aws.AWS{
   393  					IAM: iam.IAM{
   394  						PasswordPolicy: iam.PasswordPolicy{
   395  							MinimumLength: defsecTypes.Int(1, defsecTypes.NewTestMetadata()),
   396  						}},
   397  				},
   398  				Azure: azure.Azure{
   399  					Authorization: authorization.Authorization{
   400  						RoleDefinitions: []authorization.RoleDefinition{{
   401  							Metadata: defsecTypes.NewTestMetadata(),
   402  							Permissions: []authorization.Permission{
   403  								{
   404  									Metadata: defsecTypes.NewTestMetadata(),
   405  									Actions: []defsecTypes.StringValue{
   406  										defsecTypes.String("*", defsecTypes.NewTestMetadata()),
   407  									},
   408  								},
   409  							},
   410  							AssignableScopes: []defsecTypes.StringValue{
   411  								defsecTypes.StringUnresolvable(defsecTypes.NewTestMetadata()),
   412  							}},
   413  						}},
   414  				},
   415  				// note: there is no Azure IAM in our cloud state (so we expect no results for it)
   416  			},
   417  			expectedResults: struct {
   418  				totalResults int
   419  				summaries    []string
   420  			}{totalResults: 1, summaries: []string{"AWS IAM Policy"}},
   421  		},
   422  		{
   423  			name: "single cloud, single selector with config data",
   424  			srcFS: testutil.CreateFS(t, map[string]string{
   425  				"policies/rds_policy.rego": `# METADATA
   426  # title: "RDS Publicly Accessible"
   427  # description: "Ensures RDS instances are not launched into the public cloud."
   428  # scope: package
   429  # schemas:
   430  # - input: schema.input
   431  # related_resources:
   432  # - http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.html
   433  # custom:
   434  #   avd_id: AVD-AWS-0999
   435  #   provider: aws
   436  #   service: rds
   437  #   severity: HIGH
   438  #   short_code: enable-public-access
   439  #   recommended_action: "Remove the public endpoint from the RDS instance'"
   440  #   input:
   441  #     selector:
   442  #     - type: cloud
   443  #       subtypes:
   444  #         - provider: aws
   445  #           service: rds
   446  package builtin.aws.rds.aws0999
   447  import data.settings.DS0999.ignore_deletion_protection
   448  deny[res] {
   449  	instance := input.aws.rds.instances[_]
   450  	instance.publicaccess.value
   451  	not ignore_deletion_protection
   452  	res := result.new("Instance has Public Access enabled", instance.publicaccess)
   453  }
   454  `,
   455  				"policies/rds_cmk_encryption.rego": `# METADATA
   456  # title: "RDS CMK Encryption"
   457  # description: "Ensures RDS instances are encrypted with CMK."
   458  # scope: package
   459  # schemas:
   460  # - input: schema.input
   461  # related_resources:
   462  # - http://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/USER_VPC.html
   463  # custom:
   464  #   avd_id: AVD-AWS-0998
   465  #   provider: aws
   466  #   service: rds
   467  #   severity: HIGH
   468  #   short_code: rds_cmk_encryption
   469  #   recommended_action: "CMK Encrypt RDS instance'"
   470  #   input:
   471  #     selector:
   472  #     - type: cloud
   473  #       subtypes:
   474  #         - provider: aws
   475  #           service: rds
   476  package builtin.aws.rds.aws0998
   477  import data.settings.DS0998.rds_desired_encryption_level
   478  deny[res] {
   479  	instance := input.aws.rds.instances[_]
   480  	rds_desired_encryption_level <= 2
   481  	res := result.new("Instance is not CMK encrypted", instance.publicaccess)
   482  }
   483  `,
   484  			}),
   485  			dataFS: testutil.CreateFS(t, map[string]string{
   486  				"config-data/data.json": `{
   487      "settings": {
   488  		"DS0999": {
   489  			"ignore_deletion_protection": false
   490  		},
   491          "DS0998": {
   492              "rds_desired_encryption_level": 2
   493          }
   494      }
   495  }
   496  `,
   497  			}),
   498  			state: state.State{AWS: aws.AWS{
   499  				RDS: rds.RDS{
   500  					Instances: []rds.Instance{
   501  						{Metadata: defsecTypes.Metadata{},
   502  							PublicAccess: defsecTypes.Bool(false, defsecTypes.NewTestMetadata()),
   503  						},
   504  					},
   505  				},
   506  			}},
   507  			expectedResults: struct {
   508  				totalResults int
   509  				summaries    []string
   510  			}{totalResults: 2, summaries: []string{"RDS Publicly Accessible", "RDS CMK Encryption"}},
   511  		},
   512  	}
   513  
   514  	for _, tc := range testCases {
   515  		t.Run(tc.name, func(t *testing.T) {
   516  			var scannerOpts []options.ScannerOption
   517  			if tc.dataFS != nil {
   518  				scannerOpts = append(scannerOpts, options.ScannerWithPolicyDirs("config-data"))
   519  			}
   520  			scannerOpts = append(scannerOpts, options.ScannerWithEmbeddedPolicies(false))
   521  			scannerOpts = append(scannerOpts, options.ScannerWithPolicyFilesystem(tc.srcFS))
   522  			scannerOpts = append(scannerOpts, options.ScannerWithRegoOnly(true))
   523  			scannerOpts = append(scannerOpts, options.ScannerWithPolicyDirs("policies/"))
   524  			scanner := New(scannerOpts...)
   525  
   526  			results, err := scanner.Scan(context.TODO(), &tc.state)
   527  			require.NoError(t, err, tc.name)
   528  			require.Equal(t, tc.expectedResults.totalResults, len(results), tc.name)
   529  			for i := range results.GetFailed() {
   530  				require.Contains(t, tc.expectedResults.summaries, results.GetFailed()[i].Rule().Summary, tc.name)
   531  			}
   532  		})
   533  	}
   534  }