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 }