github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/pkg/scanners/terraform/scanner_test.go (about)

     1  package terraform
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"strconv"
     8  	"testing"
     9  
    10  	"github.com/aquasecurity/defsec/pkg/providers"
    11  	"github.com/aquasecurity/defsec/pkg/rules"
    12  	"github.com/aquasecurity/defsec/pkg/scan"
    13  	"github.com/aquasecurity/defsec/pkg/scanners/options"
    14  	"github.com/aquasecurity/defsec/pkg/severity"
    15  	"github.com/aquasecurity/defsec/pkg/state"
    16  	"github.com/aquasecurity/defsec/pkg/terraform"
    17  	"github.com/stretchr/testify/assert"
    18  	"github.com/stretchr/testify/require"
    19  
    20  	"github.com/aquasecurity/trivy-iac/test/testutil"
    21  )
    22  
    23  var alwaysFailRule = scan.Rule{
    24  	Provider:  providers.AWSProvider,
    25  	Service:   "service",
    26  	ShortCode: "abc",
    27  	Severity:  severity.High,
    28  	CustomChecks: scan.CustomChecks{
    29  		Terraform: &scan.TerraformCustomCheck{
    30  			RequiredTypes:  []string{},
    31  			RequiredLabels: []string{},
    32  			Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
    33  				results.Add("oh no", resourceBlock)
    34  				return
    35  			},
    36  		},
    37  	},
    38  }
    39  
    40  const emptyBucketRule = `
    41  # METADATA
    42  # schemas:
    43  # - input: schema.input
    44  # custom:
    45  #   avd_id: AVD-AWS-0001
    46  #   input:
    47  #     selector:
    48  #     - type: cloud
    49  #       subtypes:
    50  #         - service: s3
    51  #           provider: aws
    52  package defsec.test.aws1
    53  deny[res] {
    54    bucket := input.aws.s3.buckets[_]
    55    bucket.name.value == ""
    56    res := result.new("The name of the bucket must not be empty", bucket)
    57  }
    58  `
    59  
    60  func scanWithOptions(t *testing.T, code string, opt ...options.ScannerOption) scan.Results {
    61  
    62  	fs := testutil.CreateFS(t, map[string]string{
    63  		"project/main.tf": code,
    64  	})
    65  
    66  	scanner := New(opt...)
    67  	results, _, err := scanner.ScanFSWithMetrics(context.TODO(), fs, "project")
    68  	require.NoError(t, err)
    69  	return results
    70  }
    71  
    72  func Test_OptionWithAlternativeIDProvider(t *testing.T) {
    73  	reg := rules.Register(alwaysFailRule)
    74  	defer rules.Deregister(reg)
    75  
    76  	options := []options.ScannerOption{
    77  		ScannerWithAlternativeIDProvider(func(s string) []string {
    78  			return []string{"something", "altid", "blah"}
    79  		}),
    80  	}
    81  	results := scanWithOptions(t, `
    82  //tfsec:ignore:altid
    83  resource "something" "else" {}
    84  `, options...)
    85  	require.Len(t, results.GetFailed(), 0)
    86  	require.Len(t, results.GetIgnored(), 1)
    87  
    88  }
    89  
    90  func Test_TrivyOptionWithAlternativeIDProvider(t *testing.T) {
    91  	reg := rules.Register(alwaysFailRule)
    92  	defer rules.Deregister(reg)
    93  
    94  	options := []options.ScannerOption{
    95  		ScannerWithAlternativeIDProvider(func(s string) []string {
    96  			return []string{"something", "altid", "blah"}
    97  		}),
    98  	}
    99  	results := scanWithOptions(t, `
   100  //trivy:ignore:altid
   101  resource "something" "else" {}
   102  `, options...)
   103  	require.Len(t, results.GetFailed(), 0)
   104  	require.Len(t, results.GetIgnored(), 1)
   105  
   106  }
   107  
   108  func Test_OptionWithSeverityOverrides(t *testing.T) {
   109  	reg := rules.Register(alwaysFailRule)
   110  	defer rules.Deregister(reg)
   111  
   112  	options := []options.ScannerOption{
   113  		ScannerWithSeverityOverrides(map[string]string{"aws-service-abc": "LOW"}),
   114  	}
   115  	results := scanWithOptions(t, `
   116  resource "something" "else" {}
   117  `, options...)
   118  	require.Len(t, results.GetFailed(), 1)
   119  	assert.Equal(t, severity.Low, results.GetFailed()[0].Severity())
   120  }
   121  
   122  func Test_OptionWithDebugWriter(t *testing.T) {
   123  	reg := rules.Register(alwaysFailRule)
   124  	defer rules.Deregister(reg)
   125  
   126  	buffer := bytes.NewBuffer([]byte{})
   127  
   128  	scannerOpts := []options.ScannerOption{
   129  		options.ScannerWithDebug(buffer),
   130  	}
   131  	_ = scanWithOptions(t, `
   132  resource "something" "else" {}
   133  `, scannerOpts...)
   134  	require.Greater(t, buffer.Len(), 0)
   135  }
   136  
   137  func Test_OptionNoIgnores(t *testing.T) {
   138  	reg := rules.Register(alwaysFailRule)
   139  	defer rules.Deregister(reg)
   140  
   141  	scannerOpts := []options.ScannerOption{
   142  		ScannerWithNoIgnores(),
   143  	}
   144  	results := scanWithOptions(t, `
   145  //tfsec:ignore:aws-service-abc
   146  resource "something" "else" {}
   147  `, scannerOpts...)
   148  	require.Len(t, results.GetFailed(), 1)
   149  	require.Len(t, results.GetIgnored(), 0)
   150  
   151  }
   152  
   153  func Test_OptionExcludeRules(t *testing.T) {
   154  	reg := rules.Register(alwaysFailRule)
   155  	defer rules.Deregister(reg)
   156  
   157  	options := []options.ScannerOption{
   158  		ScannerWithExcludedRules([]string{"aws-service-abc"}),
   159  	}
   160  	results := scanWithOptions(t, `
   161  resource "something" "else" {}
   162  `, options...)
   163  	require.Len(t, results.GetFailed(), 0)
   164  	require.Len(t, results.GetIgnored(), 1)
   165  
   166  }
   167  
   168  func Test_OptionIncludeRules(t *testing.T) {
   169  	reg := rules.Register(alwaysFailRule)
   170  	defer rules.Deregister(reg)
   171  
   172  	scannerOpts := []options.ScannerOption{
   173  		ScannerWithIncludedRules([]string{"this-only"}),
   174  	}
   175  	results := scanWithOptions(t, `
   176  resource "something" "else" {}
   177  `, scannerOpts...)
   178  	require.Len(t, results.GetFailed(), 0)
   179  	require.Len(t, results.GetIgnored(), 1)
   180  
   181  }
   182  
   183  func Test_OptionWithMinimumSeverity(t *testing.T) {
   184  	reg := rules.Register(alwaysFailRule)
   185  	defer rules.Deregister(reg)
   186  
   187  	scannerOpts := []options.ScannerOption{
   188  		ScannerWithMinimumSeverity(severity.Critical),
   189  	}
   190  	results := scanWithOptions(t, `
   191  resource "something" "else" {}
   192  `, scannerOpts...)
   193  	require.Len(t, results.GetFailed(), 0)
   194  	require.Len(t, results.GetIgnored(), 1)
   195  
   196  }
   197  
   198  func Test_OptionWithPolicyDirs(t *testing.T) {
   199  
   200  	fs := testutil.CreateFS(t, map[string]string{
   201  		"/code/main.tf": `
   202  resource "aws_s3_bucket" "my-bucket" {
   203  	bucket = "evil"
   204  }
   205  `,
   206  		"/rules/test.rego": `
   207  package defsec.abcdefg
   208  
   209  __rego_metadata__ := {
   210  	"id": "TEST123",
   211  	"avd_id": "AVD-TEST-0123",
   212  	"title": "Buckets should not be evil",
   213  	"short_code": "no-evil-buckets",
   214  	"severity": "CRITICAL",
   215  	"type": "DefSec Security Check",
   216  	"description": "You should not allow buckets to be evil",
   217  	"recommended_actions": "Use a good bucket instead",
   218  	"url": "https://google.com/search?q=is+my+bucket+evil",
   219  }
   220  
   221  __rego_input__ := {
   222  	"combine": false,
   223  	"selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}],
   224  }
   225  
   226  deny[cause] {
   227  	bucket := input.aws.s3.buckets[_]
   228  	bucket.name.value == "evil"
   229  	cause := bucket.name
   230  }
   231  `,
   232  	})
   233  
   234  	debugLog := bytes.NewBuffer([]byte{})
   235  	scanner := New(
   236  		options.ScannerWithDebug(debugLog),
   237  		options.ScannerWithPolicyFilesystem(fs),
   238  		options.ScannerWithPolicyDirs("rules"),
   239  		options.ScannerWithRegoOnly(true),
   240  	)
   241  
   242  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   243  	require.NoError(t, err)
   244  
   245  	require.Len(t, results.GetFailed(), 1)
   246  
   247  	failure := results.GetFailed()[0]
   248  
   249  	assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID)
   250  
   251  	actualCode, err := failure.GetCode()
   252  	require.NoError(t, err)
   253  	for i := range actualCode.Lines {
   254  		actualCode.Lines[i].Highlighted = ""
   255  	}
   256  	assert.Equal(t, []scan.Line{
   257  		{
   258  			Number:     2,
   259  			Content:    "resource \"aws_s3_bucket\" \"my-bucket\" {",
   260  			IsCause:    false,
   261  			FirstCause: false,
   262  			LastCause:  false,
   263  			Annotation: "",
   264  		},
   265  		{
   266  			Number:     3,
   267  			Content:    "\tbucket = \"evil\"",
   268  			IsCause:    true,
   269  			FirstCause: true,
   270  			LastCause:  true,
   271  			Annotation: "",
   272  		},
   273  		{
   274  			Number:     4,
   275  			Content:    "}",
   276  			IsCause:    false,
   277  			FirstCause: false,
   278  			LastCause:  false,
   279  			Annotation: "",
   280  		},
   281  	}, actualCode.Lines)
   282  
   283  	if t.Failed() {
   284  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   285  	}
   286  
   287  }
   288  
   289  func Test_OptionWithPolicyNamespaces(t *testing.T) {
   290  
   291  	tests := []struct {
   292  		includedNamespaces []string
   293  		policyNamespace    string
   294  		wantFailure        bool
   295  	}{
   296  		{
   297  			includedNamespaces: nil,
   298  			policyNamespace:    "blah",
   299  			wantFailure:        false,
   300  		},
   301  		{
   302  			includedNamespaces: nil,
   303  			policyNamespace:    "appshield.something",
   304  			wantFailure:        true,
   305  		},
   306  		{
   307  			includedNamespaces: nil,
   308  			policyNamespace:    "defsec.blah",
   309  			wantFailure:        true,
   310  		},
   311  		{
   312  			includedNamespaces: []string{"user"},
   313  			policyNamespace:    "users",
   314  			wantFailure:        false,
   315  		},
   316  		{
   317  			includedNamespaces: []string{"users"},
   318  			policyNamespace:    "something.users",
   319  			wantFailure:        false,
   320  		},
   321  		{
   322  			includedNamespaces: []string{"users"},
   323  			policyNamespace:    "users",
   324  			wantFailure:        true,
   325  		},
   326  		{
   327  			includedNamespaces: []string{"users"},
   328  			policyNamespace:    "users.my_rule",
   329  			wantFailure:        true,
   330  		},
   331  		{
   332  			includedNamespaces: []string{"a", "users", "b"},
   333  			policyNamespace:    "users",
   334  			wantFailure:        true,
   335  		},
   336  		{
   337  			includedNamespaces: []string{"user"},
   338  			policyNamespace:    "defsec",
   339  			wantFailure:        true,
   340  		},
   341  	}
   342  
   343  	for i, test := range tests {
   344  
   345  		t.Run(strconv.Itoa(i), func(t *testing.T) {
   346  
   347  			fs := testutil.CreateFS(t, map[string]string{
   348  				"/code/main.tf": `
   349  resource "aws_s3_bucket" "my-bucket" {
   350  	bucket = "evil"
   351  }
   352  `,
   353  				"/rules/test.rego": fmt.Sprintf(`
   354  # METADATA
   355  # custom:
   356  #   input:
   357  #     selector:
   358  #     - type: cloud
   359  #       subtypes:
   360  #       - service: s3
   361  #         provider: aws
   362  package %s
   363  
   364  deny[cause] {
   365  bucket := input.aws.s3.buckets[_]
   366  bucket.name.value == "evil"
   367  cause := bucket.name
   368  }
   369  
   370  				`, test.policyNamespace),
   371  			})
   372  
   373  			scanner := New(
   374  				options.ScannerWithPolicyDirs("rules"),
   375  				options.ScannerWithPolicyNamespaces(test.includedNamespaces...),
   376  			)
   377  
   378  			results, _, err := scanner.ScanFSWithMetrics(context.TODO(), fs, "code")
   379  			require.NoError(t, err)
   380  
   381  			var found bool
   382  			for _, result := range results.GetFailed() {
   383  				if result.RegoNamespace() == test.policyNamespace && result.RegoRule() == "deny" {
   384  					found = true
   385  					break
   386  				}
   387  			}
   388  			assert.Equal(t, test.wantFailure, found)
   389  
   390  		})
   391  	}
   392  
   393  }
   394  
   395  func Test_OptionWithStateFunc(t *testing.T) {
   396  
   397  	fs := testutil.CreateFS(t, map[string]string{
   398  		"code/main.tf": `
   399  resource "aws_s3_bucket" "my-bucket" {
   400  	bucket = "evil"
   401  }
   402  `,
   403  	})
   404  
   405  	var actual state.State
   406  
   407  	debugLog := bytes.NewBuffer([]byte{})
   408  	scanner := New(
   409  		options.ScannerWithDebug(debugLog),
   410  		ScannerWithStateFunc(func(s *state.State) {
   411  			require.NotNil(t, s)
   412  			actual = *s
   413  		}),
   414  	)
   415  
   416  	_, _, err := scanner.ScanFSWithMetrics(context.TODO(), fs, "code")
   417  	require.NoError(t, err)
   418  
   419  	assert.Equal(t, 1, len(actual.AWS.S3.Buckets))
   420  
   421  	if t.Failed() {
   422  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   423  	}
   424  
   425  }
   426  
   427  func Test_OptionWithRegoOnly(t *testing.T) {
   428  
   429  	fs := testutil.CreateFS(t, map[string]string{
   430  		"/code/main.tf": `
   431  resource "aws_s3_bucket" "my-bucket" {
   432  	bucket = "evil"
   433  }
   434  `,
   435  		"/rules/test.rego": `
   436  package defsec.abcdefg
   437  
   438  __rego_metadata__ := {
   439  	"id": "TEST123",
   440  	"avd_id": "AVD-TEST-0123",
   441  	"title": "Buckets should not be evil",
   442  	"short_code": "no-evil-buckets",
   443  	"severity": "CRITICAL",
   444  	"type": "DefSec Security Check",
   445  	"description": "You should not allow buckets to be evil",
   446  	"recommended_actions": "Use a good bucket instead",
   447  	"url": "https://google.com/search?q=is+my+bucket+evil",
   448  }
   449  
   450  __rego_input__ := {
   451  	"combine": false,
   452  	"selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}],
   453  }
   454  
   455  deny[cause] {
   456  	bucket := input.aws.s3.buckets[_]
   457  	bucket.name.value == "evil"
   458  	cause := bucket.name
   459  }
   460  `,
   461  	})
   462  
   463  	debugLog := bytes.NewBuffer([]byte{})
   464  	scanner := New(
   465  		options.ScannerWithDebug(debugLog),
   466  		options.ScannerWithPolicyDirs("rules"),
   467  		options.ScannerWithRegoOnly(true),
   468  	)
   469  
   470  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   471  	require.NoError(t, err)
   472  
   473  	require.Len(t, results.GetFailed(), 1)
   474  	assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID)
   475  
   476  	if t.Failed() {
   477  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   478  	}
   479  }
   480  
   481  func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) {
   482  
   483  	fs := testutil.CreateFS(t, map[string]string{
   484  		"/code/main.tf": `
   485  resource "aws_s3_bucket" "my-bucket" {
   486  	bucket = "evil"
   487  }
   488  `,
   489  		"/rules/test.rego": `
   490  package defsec.abcdefg
   491  
   492  __rego_metadata__ := {
   493  	"id": "TEST123",
   494  	"avd_id": "AVD-TEST-0123",
   495  	"title": "Buckets should not be evil",
   496  	"short_code": "no-evil-buckets",
   497  	"severity": "CRITICAL",
   498  	"type": "DefSec Security Check",
   499  	"description": "You should not allow buckets to be evil",
   500  	"recommended_actions": "Use a good bucket instead",
   501  	"url": "https://google.com/search?q=is+my+bucket+evil",
   502  }
   503  
   504  __rego_input__ := {
   505  	"combine": false,
   506  	"selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}],
   507  }
   508  
   509  deny[res] {
   510  	bucket := input.aws.s3.buckets[_]
   511  	bucket.name.value == "evil"
   512  	res := result.new("oh no", bucket.name)
   513  }
   514  `,
   515  	})
   516  
   517  	debugLog := bytes.NewBuffer([]byte{})
   518  	scanner := New(
   519  		options.ScannerWithDebug(debugLog),
   520  		options.ScannerWithPolicyDirs("rules"),
   521  		options.ScannerWithRegoOnly(true),
   522  		options.ScannerWithEmbeddedLibraries(true),
   523  	)
   524  
   525  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   526  	require.NoError(t, err)
   527  
   528  	require.Len(t, results.GetFailed(), 1)
   529  	assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID)
   530  	assert.NotNil(t, results[0].Metadata().Range().GetFS())
   531  
   532  	if t.Failed() {
   533  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   534  	}
   535  }
   536  
   537  func Test_IAMPolicyRego(t *testing.T) {
   538  	fs := testutil.CreateFS(t, map[string]string{
   539  		"/code/main.tf": `
   540  resource "aws_sqs_queue_policy" "bad_example" {
   541     queue_url = aws_sqs_queue.q.id
   542  
   543     policy = <<POLICY
   544   {
   545     "Statement": [
   546       {
   547         "Effect": "Allow",
   548         "Principal": "*",
   549         "Action": "*"
   550       }
   551     ]
   552   }
   553   POLICY
   554   }`,
   555  		"/rules/test.rego": `
   556  # METADATA
   557  # title: Buckets should not be evil
   558  # description: You should not allow buckets to be evil
   559  # scope: package
   560  # schemas:
   561  #  - input: schema.input
   562  # related_resources:
   563  # - https://google.com/search?q=is+my+bucket+evil
   564  # custom:
   565  #   id: TEST123
   566  #   avd_id: AVD-TEST-0123
   567  #   short_code: no-evil-buckets
   568  #   severity: CRITICAL
   569  #   recommended_action: Use a good bucket instead
   570  #   input:
   571  #     selector:
   572  #     - type: cloud
   573  #       subtypes: 
   574  #         - service: sqs
   575  #           provider: aws
   576  package defsec.abcdefg
   577  
   578  
   579  deny[res] {
   580  	queue := input.aws.sqs.queues[_]
   581  	policy := queue.policies[_]
   582  	doc := json.unmarshal(policy.document.value)
   583  	statement = doc.Statement[_]
   584  	action := statement.Action[_]
   585  	action == "*"
   586  	res := result.new("SQS Policy contains wildcard in action", policy.document)
   587  }
   588  `,
   589  	})
   590  
   591  	debugLog := bytes.NewBuffer([]byte{})
   592  	scanner := New(
   593  		options.ScannerWithDebug(debugLog),
   594  		options.ScannerWithTrace(debugLog),
   595  		options.ScannerWithPolicyDirs("rules"),
   596  		options.ScannerWithRegoOnly(true),
   597  		options.ScannerWithEmbeddedLibraries(true),
   598  	)
   599  
   600  	defer func() {
   601  		if t.Failed() {
   602  			fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   603  		}
   604  	}()
   605  
   606  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   607  	require.NoError(t, err)
   608  
   609  	require.Len(t, results.GetFailed(), 1)
   610  	assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID)
   611  	assert.NotNil(t, results[0].Metadata().Range().GetFS())
   612  
   613  }
   614  
   615  func Test_ContainerDefinitionRego(t *testing.T) {
   616  	fs := testutil.CreateFS(t, map[string]string{
   617  		"/code/main.tf": `
   618  resource "aws_ecs_task_definition" "test" {
   619    family                = "test"
   620    container_definitions = <<TASK_DEFINITION
   621  [
   622    {
   623  	"privileged": true,
   624      "cpu": 10,
   625      "command": ["sleep", "10"],
   626      "entryPoint": ["/"],
   627      "environment": [
   628        {"name": "VARNAME", "value": "VARVAL"}
   629      ],
   630      "essential": true,
   631      "image": "jenkins",
   632      "memory": 128,
   633      "name": "jenkins",
   634      "portMappings": [
   635        {
   636          "containerPort": 80,
   637          "hostPort": 8080
   638        }
   639      ],
   640          "resourceRequirements":[
   641              {
   642                  "type":"InferenceAccelerator",
   643                  "value":"device_1"
   644              }
   645          ]
   646    }
   647  ]
   648  TASK_DEFINITION
   649  
   650    inference_accelerator {
   651      device_name = "device_1"
   652      device_type = "eia1.medium"
   653    }
   654  }`,
   655  		"/rules/test.rego": `
   656  package defsec.abcdefg
   657  
   658  
   659  __rego_metadata__ := {
   660  	"id": "TEST123",
   661  	"avd_id": "AVD-TEST-0123",
   662  	"title": "Buckets should not be evil",
   663  	"short_code": "no-evil-buckets",
   664  	"severity": "CRITICAL",
   665  	"type": "DefSec Security Check",
   666  	"description": "You should not allow buckets to be evil",
   667  	"recommended_actions": "Use a good bucket instead",
   668  	"url": "https://google.com/search?q=is+my+bucket+evil",
   669  }
   670  
   671  __rego_input__ := {
   672  	"combine": false,
   673  	"selector": [{"type": "defsec", "subtypes": [{"service": "ecs", "provider": "aws"}]}],
   674  }
   675  
   676  deny[res] {
   677  	definition := input.aws.ecs.taskdefinitions[_].containerdefinitions[_]
   678  	definition.privileged.value == true
   679  	res := result.new("Privileged container detected", definition.privileged)
   680  }
   681  `,
   682  	})
   683  
   684  	debugLog := bytes.NewBuffer([]byte{})
   685  	scanner := New(
   686  		options.ScannerWithDebug(debugLog),
   687  		options.ScannerWithPolicyDirs("rules"),
   688  		options.ScannerWithRegoOnly(true),
   689  		options.ScannerWithEmbeddedLibraries(true),
   690  	)
   691  
   692  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   693  	require.NoError(t, err)
   694  
   695  	require.Len(t, results.GetFailed(), 1)
   696  	assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID)
   697  	assert.NotNil(t, results[0].Metadata().Range().GetFS())
   698  
   699  	if t.Failed() {
   700  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   701  	}
   702  }
   703  
   704  func Test_S3_Linking(t *testing.T) {
   705  
   706  	code := `
   707  ## tfsec:ignore:aws-s3-enable-bucket-encryption
   708  ## tfsec:ignore:aws-s3-enable-bucket-logging
   709  ## tfsec:ignore:aws-s3-enable-versioning
   710  resource "aws_s3_bucket" "blubb" {
   711    bucket = "test"
   712  }
   713  
   714  resource "aws_s3_bucket_public_access_block" "audit_logs_athena" {
   715    bucket = aws_s3_bucket.blubb.id
   716  
   717    block_public_acls       = true
   718    block_public_policy     = true
   719    ignore_public_acls      = true
   720    restrict_public_buckets = true
   721  }
   722  
   723  # tfsec:ignore:aws-s3-enable-bucket-encryption
   724  # tfsec:ignore:aws-s3-enable-bucket-logging
   725  # tfsec:ignore:aws-s3-enable-versioning
   726  resource "aws_s3_bucket" "foo" {
   727    bucket        = "prefix-" # remove this variable and it works; does not report
   728    force_destroy = true
   729  }
   730  
   731  resource "aws_s3_bucket_public_access_block" "foo" {
   732    bucket = aws_s3_bucket.foo.id
   733  
   734    block_public_acls       = true
   735    block_public_policy     = true
   736    ignore_public_acls      = true
   737    restrict_public_buckets = true
   738  }
   739  
   740  `
   741  
   742  	fs := testutil.CreateFS(t, map[string]string{
   743  		"code/main.tf": code,
   744  	})
   745  
   746  	debugLog := bytes.NewBuffer([]byte{})
   747  	scanner := New(
   748  		options.ScannerWithDebug(debugLog),
   749  	)
   750  
   751  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   752  	require.NoError(t, err)
   753  
   754  	failed := results.GetFailed()
   755  	for _, result := range failed {
   756  		// public access block
   757  		assert.NotEqual(t, "AVD-AWS-0094", result.Rule().AVDID, "AVD-AWS-0094 should not be reported - was found at "+result.Metadata().Range().String())
   758  		// encryption
   759  		assert.NotEqual(t, "AVD-AWS-0088", result.Rule().AVDID)
   760  		// logging
   761  		assert.NotEqual(t, "AVD-AWS-0089", result.Rule().AVDID)
   762  		// versioning
   763  		assert.NotEqual(t, "AVD-AWS-0090", result.Rule().AVDID)
   764  	}
   765  
   766  	if t.Failed() {
   767  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   768  	}
   769  }
   770  
   771  func Test_S3_Linking_PublicAccess(t *testing.T) {
   772  
   773  	code := `
   774  resource "aws_s3_bucket" "testA" {
   775    bucket = "com.test.testA"
   776  }
   777  
   778  resource "aws_s3_bucket_acl" "testA" {
   779    bucket = aws_s3_bucket.testA.id
   780    acl    = "private"
   781  }
   782  
   783  resource "aws_s3_bucket_public_access_block" "testA" {
   784    bucket = aws_s3_bucket.testA.id
   785  
   786    block_public_acls       = true
   787    block_public_policy     = true
   788    ignore_public_acls      = true
   789    restrict_public_buckets = true
   790  }
   791  
   792  resource "aws_s3_bucket" "testB" {
   793    bucket = "com.test.testB"
   794  }
   795  
   796  resource "aws_s3_bucket_acl" "testB" {
   797    bucket = aws_s3_bucket.testB.id
   798    acl    = "private"
   799  }
   800  
   801  resource "aws_s3_bucket_public_access_block" "testB" {
   802    bucket = aws_s3_bucket.testB.id
   803  
   804    block_public_acls       = true
   805    block_public_policy     = true
   806    ignore_public_acls      = true
   807    restrict_public_buckets = true
   808  }
   809  
   810  `
   811  
   812  	fs := testutil.CreateFS(t, map[string]string{
   813  		"code/main.tf": code,
   814  	})
   815  
   816  	debugLog := bytes.NewBuffer([]byte{})
   817  	scanner := New(
   818  		options.ScannerWithDebug(debugLog),
   819  	)
   820  
   821  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   822  	require.NoError(t, err)
   823  
   824  	for _, result := range results.GetFailed() {
   825  		// public access block
   826  		assert.NotEqual(t, "AVD-AWS-0094", result.Rule().AVDID)
   827  	}
   828  
   829  	if t.Failed() {
   830  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   831  	}
   832  }
   833  
   834  func Test_RegoInput(t *testing.T) {
   835  
   836  	var regoInput interface{}
   837  
   838  	opts := []options.ScannerOption{
   839  		ScannerWithStateFunc(func(s *state.State) {
   840  			regoInput = s.ToRego()
   841  		}),
   842  	}
   843  	_ = scanWithOptions(t, `
   844  resource "aws_security_group" "example_security_group" {
   845    name = "example_security_group"
   846  
   847    description = "Example SG"
   848  
   849    ingress {
   850      description = "Allow SSH"
   851      from_port   = 22
   852      to_port     = 22
   853      protocol    = "tcp"
   854      cidr_blocks = ["1.2.3.4", "5.6.7.8"]
   855    }
   856  
   857  }
   858  `, opts...)
   859  
   860  	outer, ok := regoInput.(map[string]interface{})
   861  	require.True(t, ok)
   862  	aws, ok := outer["aws"].(map[string]interface{})
   863  	require.True(t, ok)
   864  	ec2, ok := aws["ec2"].(map[string]interface{})
   865  	require.True(t, ok)
   866  	sgs, ok := ec2["securitygroups"].([]interface{})
   867  	require.True(t, ok)
   868  	require.Len(t, sgs, 1)
   869  	sg0, ok := sgs[0].(map[string]interface{})
   870  	require.True(t, ok)
   871  	ingress, ok := sg0["ingressrules"].([]interface{})
   872  	require.True(t, ok)
   873  	require.Len(t, ingress, 1)
   874  	ingress0, ok := ingress[0].(map[string]interface{})
   875  	require.True(t, ok)
   876  	cidrs, ok := ingress0["cidrs"].([]interface{})
   877  	require.True(t, ok)
   878  	require.Len(t, cidrs, 2)
   879  
   880  	cidr0, ok := cidrs[0].(map[string]interface{})
   881  	require.True(t, ok)
   882  
   883  	cidr1, ok := cidrs[1].(map[string]interface{})
   884  	require.True(t, ok)
   885  
   886  	assert.Equal(t, "1.2.3.4", cidr0["value"])
   887  	assert.Equal(t, "5.6.7.8", cidr1["value"])
   888  }
   889  
   890  // PoC for replacing Go with Rego: AVD-AWS-0001
   891  func Test_RegoRules(t *testing.T) {
   892  
   893  	fs := testutil.CreateFS(t, map[string]string{
   894  		"/code/main.tf": `
   895  resource "aws_apigatewayv2_stage" "bad_example" {
   896    api_id = aws_apigatewayv2_api.example.id
   897    name   = "example-stage"
   898  }
   899  `,
   900  		"/rules/test.rego": `# METADATA
   901  # schemas:
   902  # - input: schema.input
   903  # custom:
   904  #   avd_id: AVD-AWS-0001
   905  #   input:
   906  #     selector:
   907  #     - type: cloud
   908  #       subtypes:
   909  #         - service: apigateway
   910  #           provider: aws
   911  package builtin.cloud.AWS0001
   912  
   913  deny[res] {
   914  	api := input.aws.apigateway.v1.apis[_]
   915  	stage := api.stages[_]
   916  	isManaged(stage)
   917  	stage.accesslogging.cloudwatchloggrouparn.value == ""
   918  	res := result.new("Access logging is not configured.", stage.accesslogging.cloudwatchloggrouparn)
   919  }
   920  
   921  deny[res] {
   922  	api := input.aws.apigateway.v2.apis[_]
   923  	stage := api.stages[_]
   924  	isManaged(stage)
   925  	stage.accesslogging.cloudwatchloggrouparn.value == ""
   926  	res := result.new("Access logging is not configured.", stage.accesslogging.cloudwatchloggrouparn)
   927  }
   928  `,
   929  	})
   930  
   931  	debugLog := bytes.NewBuffer([]byte{})
   932  	scanner := New(
   933  		options.ScannerWithDebug(debugLog),
   934  		options.ScannerWithPolicyFilesystem(fs),
   935  		options.ScannerWithPolicyDirs("rules"),
   936  		options.ScannerWithRegoOnly(true),
   937  	)
   938  
   939  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
   940  	require.NoError(t, err)
   941  
   942  	require.Len(t, results.GetFailed(), 1)
   943  
   944  	failure := results.GetFailed()[0]
   945  
   946  	assert.Equal(t, "AVD-AWS-0001", failure.Rule().AVDID)
   947  
   948  	actualCode, err := failure.GetCode()
   949  	require.NoError(t, err)
   950  	for i := range actualCode.Lines {
   951  		actualCode.Lines[i].Highlighted = ""
   952  	}
   953  	assert.Equal(t, []scan.Line{
   954  		{
   955  			Number:     2,
   956  			Content:    "resource \"aws_apigatewayv2_stage\" \"bad_example\" {",
   957  			IsCause:    true,
   958  			FirstCause: true,
   959  			LastCause:  false,
   960  			Annotation: "",
   961  		},
   962  		{
   963  			Number:     3,
   964  			Content:    "  api_id = aws_apigatewayv2_api.example.id",
   965  			IsCause:    true,
   966  			FirstCause: false,
   967  			LastCause:  false,
   968  			Annotation: "",
   969  		},
   970  		{
   971  			Number:     4,
   972  			Content:    "  name   = \"example-stage\"",
   973  			IsCause:    true,
   974  			FirstCause: false,
   975  			LastCause:  false,
   976  			Annotation: "",
   977  		},
   978  		{
   979  			Number:     5,
   980  			Content:    "}",
   981  			IsCause:    true,
   982  			FirstCause: false,
   983  			LastCause:  true,
   984  			Annotation: "",
   985  		},
   986  	}, actualCode.Lines)
   987  
   988  	if t.Failed() {
   989  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
   990  	}
   991  
   992  }
   993  
   994  func Test_OptionWithConfigsFileSystem(t *testing.T) {
   995  	fs := testutil.CreateFS(t, map[string]string{
   996  		"code/main.tf": `
   997  variable "bucket_name" {
   998    type = string
   999  }
  1000  resource "aws_s3_bucket" "main" {
  1001    bucket = var.bucket_name
  1002  }
  1003  `,
  1004  		"rules/bucket_name.rego": emptyBucketRule,
  1005  	})
  1006  
  1007  	configsFS := testutil.CreateFS(t, map[string]string{
  1008  		"main.tfvars": `
  1009  bucket_name = "test"
  1010  `,
  1011  	})
  1012  
  1013  	debugLog := bytes.NewBuffer([]byte{})
  1014  	scanner := New(
  1015  		options.ScannerWithDebug(debugLog),
  1016  		options.ScannerWithPolicyDirs("rules"),
  1017  		options.ScannerWithPolicyFilesystem(fs),
  1018  		options.ScannerWithRegoOnly(true),
  1019  		options.ScannerWithEmbeddedLibraries(false),
  1020  		options.ScannerWithEmbeddedPolicies(false),
  1021  		ScannerWithAllDirectories(true),
  1022  		ScannerWithTFVarsPaths("main.tfvars"),
  1023  		ScannerWithConfigsFileSystem(configsFS),
  1024  	)
  1025  
  1026  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
  1027  	require.NoError(t, err)
  1028  
  1029  	assert.Len(t, results, 1)
  1030  	assert.Len(t, results.GetPassed(), 1)
  1031  
  1032  	if t.Failed() {
  1033  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
  1034  	}
  1035  }
  1036  
  1037  func Test_OptionWithConfigsFileSystem_ConfigInCode(t *testing.T) {
  1038  	fs := testutil.CreateFS(t, map[string]string{
  1039  		"code/main.tf": `
  1040  variable "bucket_name" {
  1041    type = string
  1042  }
  1043  resource "aws_s3_bucket" "main" {
  1044    bucket = var.bucket_name
  1045  }
  1046  `,
  1047  		"rules/bucket_name.rego": emptyBucketRule,
  1048  		"main.tfvars": `
  1049  bucket_name = "test"
  1050  `,
  1051  	})
  1052  
  1053  	debugLog := bytes.NewBuffer([]byte{})
  1054  	scanner := New(
  1055  		options.ScannerWithDebug(debugLog),
  1056  		options.ScannerWithPolicyDirs("rules"),
  1057  		options.ScannerWithPolicyFilesystem(fs),
  1058  		options.ScannerWithRegoOnly(true),
  1059  		options.ScannerWithEmbeddedLibraries(false),
  1060  		options.ScannerWithEmbeddedPolicies(false),
  1061  		ScannerWithAllDirectories(true),
  1062  		ScannerWithTFVarsPaths("main.tfvars"),
  1063  		ScannerWithConfigsFileSystem(fs),
  1064  	)
  1065  
  1066  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
  1067  	require.NoError(t, err)
  1068  
  1069  	assert.Len(t, results, 1)
  1070  	assert.Len(t, results.GetPassed(), 1)
  1071  
  1072  	if t.Failed() {
  1073  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
  1074  	}
  1075  }
  1076  
  1077  func Test_DoNotScanNonRootModules(t *testing.T) {
  1078  	fs := testutil.CreateFS(t, map[string]string{
  1079  		"/code/app1/main.tf": `
  1080  module "s3" {
  1081    source      = "./modules/s3"
  1082    bucket_name = "test"
  1083  }
  1084  `,
  1085  		"/code/app1/modules/s3/main.tf": `
  1086  variable "bucket_name" {
  1087    type = string
  1088  }
  1089  
  1090  resource "aws_s3_bucket" "main" {
  1091    bucket = var.bucket_name
  1092  }
  1093  `,
  1094  		"/code/app1/app2/main.tf": `
  1095  module "s3" {
  1096    source      = "../modules/s3"
  1097    bucket_name = "test"
  1098  }
  1099  
  1100  module "ec2" {
  1101    source = "./modules/ec2"
  1102  }
  1103  `,
  1104  		"/code/app1/app2/modules/ec2/main.tf": `
  1105  variable "security_group_description" {
  1106  	type = string
  1107  }
  1108  resource "aws_security_group" "main" {
  1109  	description = var.security_group_description
  1110  }
  1111  `,
  1112  		"/rules/bucket_name.rego": `
  1113  # METADATA
  1114  # schemas:
  1115  # - input: schema.input
  1116  # custom:
  1117  #   avd_id: AVD-AWS-0001
  1118  #   input:
  1119  #     selector:
  1120  #     - type: cloud
  1121  #       subtypes:
  1122  #         - service: s3
  1123  #           provider: aws
  1124  package defsec.test.aws1
  1125  deny[res] {
  1126    bucket := input.aws.s3.buckets[_]
  1127    bucket.name.value == ""
  1128    res := result.new("The name of the bucket must not be empty", bucket)
  1129  }
  1130  `,
  1131  		"/rules/sec_group_description.rego": `
  1132  # METADATA
  1133  # schemas:
  1134  # - input: schema.input
  1135  # custom:
  1136  #   avd_id: AVD-AWS-0002
  1137  #   input:
  1138  #     selector:
  1139  #     - type: cloud
  1140  #       subtypes:
  1141  #         - service: ec2
  1142  #           provider: aws
  1143  package defsec.test.aws2
  1144  deny[res] {
  1145    group := input.aws.ec2.securitygroups[_]
  1146    group.description.value == ""
  1147    res := result.new("The description of the security group must not be empty", group)
  1148  }
  1149  `,
  1150  	})
  1151  
  1152  	debugLog := bytes.NewBuffer([]byte{})
  1153  	scanner := New(
  1154  		options.ScannerWithDebug(debugLog),
  1155  		options.ScannerWithPolicyFilesystem(fs),
  1156  		options.ScannerWithPolicyDirs("rules"),
  1157  		options.ScannerWithEmbeddedPolicies(false),
  1158  		options.ScannerWithEmbeddedLibraries(false),
  1159  		options.ScannerWithRegoOnly(true),
  1160  		ScannerWithAllDirectories(true),
  1161  	)
  1162  
  1163  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
  1164  	require.NoError(t, err)
  1165  
  1166  	assert.Len(t, results.GetPassed(), 2)
  1167  	require.Len(t, results.GetFailed(), 1)
  1168  	assert.Equal(t, "AVD-AWS-0002", results.GetFailed()[0].Rule().AVDID)
  1169  
  1170  	if t.Failed() {
  1171  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
  1172  	}
  1173  }
  1174  
  1175  func Test_RoleRefToOutput(t *testing.T) {
  1176  	fs := testutil.CreateFS(t, map[string]string{
  1177  		"code/main.tf": `
  1178  module "this" {
  1179    source = "./modules/iam"
  1180  }
  1181  
  1182  resource "aws_iam_role_policy" "bad-policy" {
  1183    name     = "bad-policy"
  1184    role     = module.this.role_name
  1185    policy = jsonencode({
  1186      Version = "2012-10-17",
  1187      Statement = [
  1188        {
  1189          Effect   = "Allow"
  1190          Action   = "*"
  1191          Resource = "*"
  1192        },
  1193      ]
  1194    })
  1195  }
  1196  		`,
  1197  		"code/modules/iam/main.tf": `
  1198  resource "aws_iam_role" "example" {
  1199    name               = "example"
  1200    assume_role_policy = jsonencode({})
  1201  }
  1202  
  1203  output "role_name" {
  1204    value = aws_iam_role.example.id
  1205  }
  1206  		`,
  1207  		"rules/test.rego": `
  1208  # METADATA
  1209  # schemas:
  1210  # - input: schema.input
  1211  # custom:
  1212  #   avd_id: AVD-AWS-0001
  1213  #   input:
  1214  #     selector:
  1215  #     - type: cloud
  1216  #       subtypes:
  1217  #         - service: iam
  1218  #           provider: aws
  1219  package defsec.test.aws1
  1220  deny[res] {
  1221    policy := input.aws.iam.roles[_].policies[_]
  1222    policy.name.value == "bad-policy"
  1223    res := result.new("Deny!", policy)
  1224  }
  1225  `,
  1226  	})
  1227  
  1228  	debugLog := bytes.NewBuffer([]byte{})
  1229  	scanner := New(
  1230  		options.ScannerWithDebug(debugLog),
  1231  		options.ScannerWithPolicyDirs("rules"),
  1232  		options.ScannerWithPolicyFilesystem(fs),
  1233  		options.ScannerWithRegoOnly(true),
  1234  		options.ScannerWithEmbeddedLibraries(false),
  1235  		options.ScannerWithEmbeddedPolicies(false),
  1236  		ScannerWithAllDirectories(true),
  1237  	)
  1238  
  1239  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
  1240  	require.NoError(t, err)
  1241  
  1242  	assert.Len(t, results, 1)
  1243  	assert.Len(t, results.GetFailed(), 1)
  1244  }
  1245  
  1246  func Test_RegoRefToAwsProviderAttributes(t *testing.T) {
  1247  	fs := testutil.CreateFS(t, map[string]string{
  1248  		"code/providers.tf": `
  1249  provider "aws" {
  1250    region  = "us-east-2"
  1251    default_tags {
  1252      tags = {
  1253        Environment = "Local"
  1254        Name        = "LocalStack"
  1255      }
  1256    }
  1257  }
  1258  `,
  1259  		"rules/region.rego": `
  1260  # METADATA
  1261  # schemas:
  1262  # - input: schema.input
  1263  # custom:
  1264  #   avd_id: AVD-AWS-0001
  1265  #   input:
  1266  #     selector:
  1267  #     - type: cloud
  1268  #       subtypes:
  1269  #         - service: meta
  1270  #           provider: aws
  1271  package defsec.test.aws1
  1272  deny[res] {
  1273    region := input.aws.meta.tfproviders[_].region
  1274    region.value != "us-east-1"
  1275    res := result.new("Only the 'us-east-1' region is allowed!", region)
  1276  }
  1277  `,
  1278  		"rules/tags.rego": `
  1279  # METADATA
  1280  # schemas:
  1281  # - input: schema.input
  1282  # custom:
  1283  #   avd_id: AVD-AWS-0002
  1284  #   input:
  1285  #     selector:
  1286  #     - type: cloud
  1287  #       subtypes:
  1288  #         - service: meta
  1289  #           provider: aws
  1290  package defsec.test.aws2
  1291  deny[res] {
  1292    provider := input.aws.meta.tfproviders[_]
  1293    tags = provider.defaulttags.tags.value
  1294    not tags.Environment
  1295    res := result.new("provider should have the following default tags: 'Environment'", tags)
  1296  }`,
  1297  	})
  1298  
  1299  	debugLog := bytes.NewBuffer([]byte{})
  1300  	scanner := New(
  1301  		options.ScannerWithDebug(debugLog),
  1302  		options.ScannerWithPolicyDirs("rules"),
  1303  		options.ScannerWithPolicyFilesystem(fs),
  1304  		options.ScannerWithRegoOnly(true),
  1305  		options.ScannerWithEmbeddedLibraries(false),
  1306  		options.ScannerWithEmbeddedPolicies(false),
  1307  		ScannerWithAllDirectories(true),
  1308  	)
  1309  
  1310  	results, err := scanner.ScanFS(context.TODO(), fs, "code")
  1311  	require.NoError(t, err)
  1312  
  1313  	require.Len(t, results, 2)
  1314  
  1315  	require.Len(t, results.GetFailed(), 1)
  1316  	assert.Equal(t, "AVD-AWS-0001", results.GetFailed()[0].Rule().AVDID)
  1317  
  1318  	require.Len(t, results.GetPassed(), 1)
  1319  	assert.Equal(t, "AVD-AWS-0002", results.GetPassed()[0].Rule().AVDID)
  1320  
  1321  	if t.Failed() {
  1322  		fmt.Printf("Debug logs:\n%s\n", debugLog.String())
  1323  	}
  1324  }