github.com/aquasecurity/trivy-iac@v0.8.1-0.20240127024015-3d8e412cf0ab/test/module_test.go (about)

     1  package test
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"fmt"
     7  	"os"
     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/terraform"
    16  	"github.com/stretchr/testify/require"
    17  
    18  	"github.com/aquasecurity/trivy-iac/pkg/scanners/terraform/executor"
    19  	"github.com/aquasecurity/trivy-iac/pkg/scanners/terraform/parser"
    20  	"github.com/aquasecurity/trivy-iac/test/testutil"
    21  	"github.com/aquasecurity/trivy-policies/checks/cloud/aws/iam"
    22  )
    23  
    24  var badRule = scan.Rule{
    25  	Provider:    providers.AWSProvider,
    26  	Service:     "service",
    27  	ShortCode:   "abc",
    28  	Summary:     "A stupid example check for a test.",
    29  	Impact:      "You will look stupid",
    30  	Resolution:  "Don't do stupid stuff",
    31  	Explanation: "Bad should not be set.",
    32  	Severity:    severity.High,
    33  	CustomChecks: scan.CustomChecks{
    34  		Terraform: &scan.TerraformCustomCheck{
    35  			RequiredTypes:  []string{"resource"},
    36  			RequiredLabels: []string{"problem"},
    37  			Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
    38  				if attr := resourceBlock.GetAttribute("bad"); attr.IsTrue() {
    39  					results.Add("bad", attr)
    40  				}
    41  				return
    42  			},
    43  		},
    44  	},
    45  }
    46  
    47  // IMPORTANT: if this test is failing, you probably need to set the version of go-cty in go.mod to the same version that hcl uses.
    48  func Test_GoCtyCompatibilityIssue(t *testing.T) {
    49  	registered := rules.Register(badRule)
    50  	defer rules.Deregister(registered)
    51  
    52  	fs := testutil.CreateFS(t, map[string]string{
    53  		"/project/main.tf": `
    54  data "aws_vpc" "default" {
    55    default = true
    56  }
    57  
    58  module "test" {
    59    source     = "../modules/problem/"
    60    cidr_block = data.aws_vpc.default.cidr_block
    61  }
    62  `,
    63  		"/modules/problem/main.tf": `
    64  variable "cidr_block" {}
    65  
    66  variable "open" {                
    67    default = false
    68  }                
    69  
    70  resource "aws_security_group" "this" {
    71    name = "Test"                       
    72  
    73    ingress {    
    74      description = "HTTPs"
    75      from_port   = 443    
    76      to_port     = 443
    77      protocol    = "tcp"
    78      self        = ! var.open
    79      cidr_blocks = var.open ? [var.cidr_block] : null
    80    }                                                 
    81  }  
    82  
    83  resource "problem" "uhoh" {
    84  	bad = true
    85  }
    86  `,
    87  	})
    88  
    89  	debug := bytes.NewBuffer([]byte{})
    90  
    91  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(debug))
    92  	err := p.ParseFS(context.TODO(), "project")
    93  	require.NoError(t, err)
    94  	modules, _, err := p.EvaluateAll(context.TODO())
    95  	require.NoError(t, err)
    96  	results, _, err := executor.New().Execute(modules)
    97  	require.NoError(t, err)
    98  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
    99  	if t.Failed() {
   100  		fmt.Println(debug.String())
   101  	}
   102  }
   103  
   104  func Test_ProblemInModuleInSiblingDir(t *testing.T) {
   105  
   106  	registered := rules.Register(badRule)
   107  	defer rules.Deregister(registered)
   108  
   109  	fs := testutil.CreateFS(t, map[string]string{
   110  		"/project/main.tf": `
   111  module "something" {
   112  	source = "../modules/problem"
   113  }
   114  `,
   115  		"modules/problem/main.tf": `
   116  resource "problem" "uhoh" {
   117  	bad = true
   118  }
   119  `},
   120  	)
   121  
   122  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   123  	err := p.ParseFS(context.TODO(), "project")
   124  	require.NoError(t, err)
   125  	modules, _, err := p.EvaluateAll(context.TODO())
   126  	require.NoError(t, err)
   127  	results, _, _ := executor.New().Execute(modules)
   128  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   129  
   130  }
   131  
   132  func Test_ProblemInModuleIgnored(t *testing.T) {
   133  
   134  	registered := rules.Register(badRule)
   135  	defer rules.Deregister(registered)
   136  
   137  	fs := testutil.CreateFS(t, map[string]string{
   138  		"/project/main.tf": `
   139  #tfsec:ignore:aws-service-abc
   140  module "something" {
   141  	source = "../modules/problem"
   142  }
   143  `,
   144  		"modules/problem/main.tf": `
   145  resource "problem" "uhoh" {
   146  	bad = true
   147  }
   148  `},
   149  	)
   150  
   151  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   152  	err := p.ParseFS(context.TODO(), "project")
   153  	require.NoError(t, err)
   154  	modules, _, err := p.EvaluateAll(context.TODO())
   155  	require.NoError(t, err)
   156  	results, _, _ := executor.New().Execute(modules)
   157  	testutil.AssertRuleNotFound(t, badRule.LongID(), results, "")
   158  
   159  }
   160  
   161  func Test_ProblemInModuleInSubdirectory(t *testing.T) {
   162  
   163  	registered := rules.Register(badRule)
   164  	defer rules.Deregister(registered)
   165  
   166  	fs := testutil.CreateFS(t, map[string]string{
   167  		"project/main.tf": `
   168  module "something" {
   169  	source = "./modules/problem"
   170  }
   171  `,
   172  		"project/modules/problem/main.tf": `
   173  resource "problem" "uhoh" {
   174  	bad = true
   175  }
   176  `})
   177  
   178  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   179  	err := p.ParseFS(context.TODO(), "project")
   180  	require.NoError(t, err)
   181  	modules, _, err := p.EvaluateAll(context.TODO())
   182  	require.NoError(t, err)
   183  	results, _, _ := executor.New().Execute(modules)
   184  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   185  
   186  }
   187  
   188  func Test_ProblemInModuleInParentDir(t *testing.T) {
   189  
   190  	registered := rules.Register(badRule)
   191  	defer rules.Deregister(registered)
   192  
   193  	fs := testutil.CreateFS(t, map[string]string{
   194  		"project/main.tf": `
   195  module "something" {
   196  	source = "../problem"
   197  }
   198  `,
   199  		"problem/main.tf": `
   200  resource "problem" "uhoh" {
   201  	bad = true
   202  }
   203  `})
   204  
   205  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   206  	err := p.ParseFS(context.TODO(), "project")
   207  	require.NoError(t, err)
   208  	modules, _, err := p.EvaluateAll(context.TODO())
   209  	require.NoError(t, err)
   210  	results, _, _ := executor.New().Execute(modules)
   211  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   212  
   213  }
   214  
   215  func Test_ProblemInModuleReuse(t *testing.T) {
   216  
   217  	registered := rules.Register(badRule)
   218  	defer rules.Deregister(registered)
   219  
   220  	fs := testutil.CreateFS(t, map[string]string{
   221  		"project/main.tf": `
   222  module "something_good" {
   223  	source = "../modules/problem"
   224  	bad = false
   225  }
   226  
   227  module "something_bad" {
   228  	source = "../modules/problem"
   229  	bad = true
   230  }
   231  `,
   232  		"modules/problem/main.tf": `
   233  variable "bad" {
   234  	default = false
   235  }
   236  resource "problem" "uhoh" {
   237  	bad = var.bad
   238  }
   239  `})
   240  
   241  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   242  	err := p.ParseFS(context.TODO(), "project")
   243  	require.NoError(t, err)
   244  	modules, _, err := p.EvaluateAll(context.TODO())
   245  	require.NoError(t, err)
   246  	results, _, _ := executor.New().Execute(modules)
   247  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   248  
   249  }
   250  
   251  func Test_ProblemInNestedModule(t *testing.T) {
   252  
   253  	registered := rules.Register(badRule)
   254  	defer rules.Deregister(registered)
   255  
   256  	fs := testutil.CreateFS(t, map[string]string{
   257  		"project/main.tf": `
   258  module "something" {
   259  	source = "../modules/a"
   260  }
   261  `,
   262  		"modules/a/main.tf": `
   263  	module "something" {
   264  	source = "../../modules/b"
   265  }
   266  `,
   267  		"modules/b/main.tf": `
   268  module "something" {
   269  	source = "../c"
   270  }
   271  `,
   272  		"modules/c/main.tf": `
   273  resource "problem" "uhoh" {
   274  	bad = true
   275  }
   276  `,
   277  	})
   278  
   279  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr))
   280  	err := p.ParseFS(context.TODO(), "project")
   281  	require.NoError(t, err)
   282  	modules, _, err := p.EvaluateAll(context.TODO())
   283  	require.NoError(t, err)
   284  	results, _, _ := executor.New().Execute(modules)
   285  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   286  
   287  }
   288  
   289  func Test_ProblemInReusedNestedModule(t *testing.T) {
   290  
   291  	registered := rules.Register(badRule)
   292  	defer rules.Deregister(registered)
   293  
   294  	fs := testutil.CreateFS(t, map[string]string{
   295  		"project/main.tf": `
   296  module "something" {
   297    source = "../modules/a"
   298    bad = false
   299  }
   300  
   301  module "something-bad" {
   302  	source = "../modules/a"
   303  	bad = true
   304  }
   305  `,
   306  		"modules/a/main.tf": `
   307  variable "bad" {
   308  	default = false
   309  }
   310  module "something" {
   311  	source = "../../modules/b"
   312  	bad = var.bad
   313  }
   314  `,
   315  		"modules/b/main.tf": `
   316  variable "bad" {
   317  	default = false
   318  }
   319  module "something" {
   320  	source = "../c"
   321  	bad = var.bad
   322  }
   323  `,
   324  		"modules/c/main.tf": `
   325  variable "bad" {
   326  	default = false
   327  }
   328  resource "problem" "uhoh" {
   329  	bad = var.bad
   330  }
   331  `,
   332  	})
   333  
   334  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   335  	err := p.ParseFS(context.TODO(), "project")
   336  	require.NoError(t, err)
   337  	modules, _, err := p.EvaluateAll(context.TODO())
   338  	require.NoError(t, err)
   339  	results, _, _ := executor.New().Execute(modules)
   340  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   341  }
   342  
   343  func Test_ProblemInInitialisedModule(t *testing.T) {
   344  
   345  	registered := rules.Register(badRule)
   346  	defer rules.Deregister(registered)
   347  
   348  	fs := testutil.CreateFS(t, map[string]string{
   349  		"project/main.tf": `
   350  module "something" {
   351    	source = "../modules/somewhere"
   352  	bad = false
   353  }
   354  `,
   355  		"modules/somewhere/main.tf": `
   356  module "something_nested" {
   357  	count = 1
   358    	source = "github.com/some/module.git"
   359  	bad = true
   360  }
   361  
   362  variable "bad" {
   363  	default = false
   364  }
   365  
   366  `,
   367  		"project/.terraform/modules/something.something_nested/main.tf": `
   368  variable "bad" {
   369  	default = false
   370  }
   371  resource "problem" "uhoh" {
   372  	bad = var.bad
   373  }
   374  `,
   375  		"project/.terraform/modules/modules.json": `
   376  	{"Modules":[
   377          {"Key":"something","Source":"../modules/somewhere","Version":"2.35.0","Dir":"../modules/somewhere"},
   378          {"Key":"something.something_nested","Source":"git::https://github.com/some/module.git","Version":"2.35.0","Dir":".terraform/modules/something.something_nested"}
   379      ]}
   380  `,
   381  	})
   382  
   383  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   384  	err := p.ParseFS(context.TODO(), "project")
   385  	require.NoError(t, err)
   386  	modules, _, err := p.EvaluateAll(context.TODO())
   387  	require.NoError(t, err)
   388  	results, _, _ := executor.New().Execute(modules)
   389  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   390  }
   391  
   392  func Test_ProblemInReusedInitialisedModule(t *testing.T) {
   393  
   394  	registered := rules.Register(badRule)
   395  	defer rules.Deregister(registered)
   396  
   397  	fs := testutil.CreateFS(t, map[string]string{
   398  		"project/main.tf": `
   399  module "something" {
   400    	source = "/nowhere"
   401  	bad = false
   402  } 
   403  module "something2" {
   404  	source = "/nowhere"
   405    	bad = true
   406  }
   407  `,
   408  		"project/.terraform/modules/a/main.tf": `
   409  variable "bad" {
   410  	default = false
   411  }
   412  resource "problem" "uhoh" {
   413  	bad = var.bad
   414  }
   415  `,
   416  		"project/.terraform/modules/modules.json": `
   417  	{"Modules":[{"Key":"something","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"},{"Key":"something2","Source":"/nowhere","Version":"2.35.0","Dir":".terraform/modules/a"}]}
   418  `,
   419  	})
   420  
   421  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   422  	err := p.ParseFS(context.TODO(), "project")
   423  	require.NoError(t, err)
   424  	modules, _, err := p.EvaluateAll(context.TODO())
   425  	require.NoError(t, err)
   426  	results, _, _ := executor.New().Execute(modules)
   427  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   428  
   429  }
   430  
   431  func Test_ProblemInDuplicateModuleNameAndPath(t *testing.T) {
   432  	registered := rules.Register(badRule)
   433  	defer rules.Deregister(registered)
   434  
   435  	fs := testutil.CreateFS(t, map[string]string{
   436  		"project/main.tf": `
   437  module "something" {
   438    source = "../modules/a"
   439    bad = 0
   440  }
   441  
   442  module "something-bad" {
   443  	source = "../modules/a"
   444  	bad = 1
   445  }
   446  `,
   447  		"modules/a/main.tf": `
   448  variable "bad" {
   449  	default = 0
   450  }
   451  module "something" {
   452  	source = "../b"
   453  	bad = var.bad
   454  }
   455  `,
   456  		"modules/b/main.tf": `
   457  variable "bad" {
   458  	default = 0
   459  }
   460  module "something" {
   461  	source = "../c"
   462  	bad = var.bad
   463  }
   464  `,
   465  		"modules/c/main.tf": `
   466  variable "bad" {
   467  	default = 0
   468  }
   469  resource "problem" "uhoh" {
   470  	count = var.bad
   471  	bad = true
   472  }
   473  `,
   474  	})
   475  
   476  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   477  	err := p.ParseFS(context.TODO(), "project")
   478  	require.NoError(t, err)
   479  	modules, _, err := p.EvaluateAll(context.TODO())
   480  	require.NoError(t, err)
   481  	results, _, _ := executor.New().Execute(modules)
   482  	testutil.AssertRuleFound(t, badRule.LongID(), results, "")
   483  
   484  }
   485  
   486  func Test_Dynamic_Variables(t *testing.T) {
   487  	fs := testutil.CreateFS(t, map[string]string{
   488  		"project/main.tf": `
   489  resource "something" "this" {
   490  
   491  	dynamic "blah" {
   492  		for_each = ["a"]
   493  
   494  		content {
   495  			ok = true
   496  		}
   497  	}
   498  }
   499  	
   500  resource "bad" "thing" {
   501  	secure = something.this.blah[0].ok
   502  }
   503  `})
   504  
   505  	r1 := scan.Rule{
   506  		Provider:  providers.AWSProvider,
   507  		Service:   "service",
   508  		ShortCode: "abc123",
   509  		Severity:  severity.High,
   510  		CustomChecks: scan.CustomChecks{
   511  			Terraform: &scan.TerraformCustomCheck{
   512  				RequiredLabels: []string{"bad"},
   513  				Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
   514  					if resourceBlock.GetAttribute("secure").IsTrue() {
   515  						return
   516  					}
   517  					results.Add("example problem", resourceBlock)
   518  					return
   519  				},
   520  			},
   521  		},
   522  	}
   523  	reg := rules.Register(r1)
   524  	defer rules.Deregister(reg)
   525  
   526  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   527  	err := p.ParseFS(context.TODO(), "project")
   528  	require.NoError(t, err)
   529  	modules, _, err := p.EvaluateAll(context.TODO())
   530  	require.NoError(t, err)
   531  	results, _, _ := executor.New().Execute(modules)
   532  	testutil.AssertRuleFound(t, r1.LongID(), results, "")
   533  }
   534  
   535  func Test_Dynamic_Variables_FalsePositive(t *testing.T) {
   536  	fs := testutil.CreateFS(t, map[string]string{
   537  		"project/main.tf": `
   538  resource "something" "else" {
   539  	x = 1
   540  	dynamic "blah" {
   541  		for_each = toset(["true"])
   542  
   543  		content {
   544  			ok = each.value
   545  		}
   546  	}
   547  }
   548  	
   549  resource "bad" "thing" {
   550  	secure = something.else.blah.ok
   551  }
   552  `})
   553  
   554  	r1 := scan.Rule{
   555  		Provider:  providers.AWSProvider,
   556  		Service:   "service",
   557  		ShortCode: "abc123",
   558  		Severity:  severity.High,
   559  		CustomChecks: scan.CustomChecks{
   560  			Terraform: &scan.TerraformCustomCheck{
   561  				RequiredLabels: []string{"bad"},
   562  				Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) {
   563  					if resourceBlock.GetAttribute("secure").IsTrue() {
   564  						return
   565  					}
   566  					results.Add("example problem", resourceBlock)
   567  					return
   568  				},
   569  			},
   570  		},
   571  	}
   572  	reg := rules.Register(r1)
   573  	defer rules.Deregister(reg)
   574  
   575  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   576  	err := p.ParseFS(context.TODO(), "project")
   577  	require.NoError(t, err)
   578  	modules, _, err := p.EvaluateAll(context.TODO())
   579  	require.NoError(t, err)
   580  	results, _, _ := executor.New().Execute(modules)
   581  	testutil.AssertRuleNotFound(t, r1.LongID(), results, "")
   582  }
   583  
   584  func Test_ReferencesPassedToNestedModule(t *testing.T) {
   585  
   586  	fs := testutil.CreateFS(t, map[string]string{
   587  		"project/main.tf": `
   588  
   589  resource "aws_iam_group" "developers" {
   590      name = "developers"
   591  }
   592  
   593  module "something" {
   594  	source = "../modules/a"
   595      group = aws_iam_group.developers.name
   596  }
   597  `,
   598  		"modules/a/main.tf": `
   599  variable "group" {
   600      type = string
   601  }
   602  
   603  resource aws_iam_group_policy mfa {
   604    group = var.group
   605    policy = data.aws_iam_policy_document.policy.json
   606  }
   607  
   608  data "aws_iam_policy_document" "policy" {
   609    statement {
   610      sid    = "main"
   611      effect = "Allow"
   612  
   613      actions   = ["s3:*"]
   614      resources = ["*"]
   615      condition {
   616          test = "Bool"
   617          variable = "aws:MultiFactorAuthPresent"
   618          values = ["true"]
   619      }
   620    }
   621  }
   622  `})
   623  
   624  	p := parser.New(fs, "", parser.OptionStopOnHCLError(true))
   625  	err := p.ParseFS(context.TODO(), "project")
   626  	require.NoError(t, err)
   627  	modules, _, err := p.EvaluateAll(context.TODO())
   628  	require.NoError(t, err)
   629  	results, _, _ := executor.New().Execute(modules)
   630  	testutil.AssertRuleNotFound(t, iam.CheckEnforceGroupMFA.LongID(), results, "")
   631  
   632  }