github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/pkg/scanners/terraform/parser/parser_test.go (about) 1 package parser 2 3 import ( 4 "context" 5 "os" 6 "sort" 7 "testing" 8 9 "github.com/khulnasoft-lab/defsec/pkg/scanners/options" 10 11 "github.com/khulnasoft-lab/defsec/test/testutil" 12 13 "github.com/zclconf/go-cty/cty" 14 15 "github.com/stretchr/testify/assert" 16 17 "github.com/stretchr/testify/require" 18 ) 19 20 func Test_BasicParsing(t *testing.T) { 21 22 fs := testutil.CreateFS(t, map[string]string{ 23 "test.tf": ` 24 25 locals { 26 proxy = var.cats_mother 27 } 28 29 variable "cats_mother" { 30 default = "boots" 31 } 32 33 provider "cats" { 34 35 } 36 37 moved { 38 39 } 40 41 import { 42 to = cats_cat.mittens 43 id = "mittens" 44 } 45 46 resource "cats_cat" "mittens" { 47 name = "mittens" 48 special = true 49 } 50 51 resource "cats_kitten" "the-great-destroyer" { 52 name = "the great destroyer" 53 parent = cats_cat.mittens.name 54 } 55 56 data "cats_cat" "the-cats-mother" { 57 name = local.proxy 58 } 59 60 check "cats_mittens_is_special" { 61 data "cats_cat" "mittens" { 62 name = "mittens" 63 } 64 65 assert { 66 condition = data.cats_cat.mittens.special == true 67 error_message = "${data.cats_cat.mittens.name} must be special" 68 } 69 } 70 71 `, 72 }) 73 74 parser := New(fs, "", OptionStopOnHCLError(true)) 75 if err := parser.ParseFS(context.TODO(), "."); err != nil { 76 t.Fatal(err) 77 } 78 modules, _, err := parser.EvaluateAll(context.TODO()) 79 if err != nil { 80 t.Fatal(err) 81 } 82 blocks := modules[0].GetBlocks() 83 84 // variable 85 variables := blocks.OfType("variable") 86 require.Len(t, variables, 1) 87 assert.Equal(t, "variable", variables[0].Type()) 88 require.Len(t, variables[0].Labels(), 1) 89 assert.Equal(t, "cats_mother", variables[0].TypeLabel()) 90 defaultVal := variables[0].GetAttribute("default") 91 require.NotNil(t, defaultVal) 92 assert.Equal(t, cty.String, defaultVal.Value().Type()) 93 assert.Equal(t, "boots", defaultVal.Value().AsString()) 94 95 // provider 96 providerBlocks := blocks.OfType("provider") 97 require.Len(t, providerBlocks, 1) 98 assert.Equal(t, "provider", providerBlocks[0].Type()) 99 require.Len(t, providerBlocks[0].Labels(), 1) 100 assert.Equal(t, "cats", providerBlocks[0].TypeLabel()) 101 102 // resources 103 resourceBlocks := blocks.OfType("resource") 104 105 sort.Slice(resourceBlocks, func(i, j int) bool { 106 return resourceBlocks[i].TypeLabel() < resourceBlocks[j].TypeLabel() 107 }) 108 109 require.Len(t, resourceBlocks, 2) 110 require.Len(t, resourceBlocks[0].Labels(), 2) 111 112 assert.Equal(t, "resource", resourceBlocks[0].Type()) 113 assert.Equal(t, "cats_cat", resourceBlocks[0].TypeLabel()) 114 assert.Equal(t, "mittens", resourceBlocks[0].NameLabel()) 115 116 assert.Equal(t, "mittens", resourceBlocks[0].GetAttribute("name").Value().AsString()) 117 assert.True(t, resourceBlocks[0].GetAttribute("special").Value().True()) 118 119 assert.Equal(t, "resource", resourceBlocks[1].Type()) 120 assert.Equal(t, "cats_kitten", resourceBlocks[1].TypeLabel()) 121 assert.Equal(t, "the great destroyer", resourceBlocks[1].GetAttribute("name").Value().AsString()) 122 assert.Equal(t, "mittens", resourceBlocks[1].GetAttribute("parent").Value().AsString()) 123 124 // import 125 importBlocks := blocks.OfType("import") 126 127 assert.Equal(t, "import", importBlocks[0].Type()) 128 require.NotNil(t, importBlocks[0].GetAttribute("to")) 129 assert.Equal(t, "mittens", importBlocks[0].GetAttribute("id").Value().AsString()) 130 131 // data 132 dataBlocks := blocks.OfType("data") 133 require.Len(t, dataBlocks, 1) 134 require.Len(t, dataBlocks[0].Labels(), 2) 135 136 assert.Equal(t, "data", dataBlocks[0].Type()) 137 assert.Equal(t, "cats_cat", dataBlocks[0].TypeLabel()) 138 assert.Equal(t, "the-cats-mother", dataBlocks[0].NameLabel()) 139 140 assert.Equal(t, "boots", dataBlocks[0].GetAttribute("name").Value().AsString()) 141 142 // check 143 checkBlocks := blocks.OfType("check") 144 require.Len(t, checkBlocks, 1) 145 require.Len(t, checkBlocks[0].Labels(), 1) 146 147 assert.Equal(t, "check", checkBlocks[0].Type()) 148 assert.Equal(t, "cats_mittens_is_special", checkBlocks[0].TypeLabel()) 149 150 require.NotNil(t, checkBlocks[0].GetBlock("data")) 151 require.NotNil(t, checkBlocks[0].GetBlock("assert")) 152 } 153 154 func Test_Modules(t *testing.T) { 155 156 fs := testutil.CreateFS(t, map[string]string{ 157 "code/test.tf": ` 158 module "my-mod" { 159 source = "../module" 160 input = "ok" 161 } 162 163 output "result" { 164 value = module.my-mod.mod_result 165 } 166 `, 167 "module/module.tf": ` 168 variable "input" { 169 default = "?" 170 } 171 172 output "mod_result" { 173 value = var.input 174 } 175 `, 176 }) 177 178 parser := New(fs, "", OptionStopOnHCLError(true), options.ParserWithDebug(os.Stderr)) 179 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 180 t.Fatal(err) 181 } 182 modules, _, err := parser.EvaluateAll(context.TODO()) 183 if err != nil { 184 t.Fatal(err) 185 } 186 require.Len(t, modules, 2) 187 rootModule := modules[0] 188 childModule := modules[1] 189 190 moduleBlocks := rootModule.GetBlocks().OfType("module") 191 require.Len(t, moduleBlocks, 1) 192 193 assert.Equal(t, "module", moduleBlocks[0].Type()) 194 assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName()) 195 inputAttr := moduleBlocks[0].GetAttribute("input") 196 require.NotNil(t, inputAttr) 197 require.Equal(t, cty.String, inputAttr.Value().Type()) 198 assert.Equal(t, "ok", inputAttr.Value().AsString()) 199 200 rootOutputs := rootModule.GetBlocks().OfType("output") 201 require.Len(t, rootOutputs, 1) 202 assert.Equal(t, "output.result", rootOutputs[0].FullName()) 203 valAttr := rootOutputs[0].GetAttribute("value") 204 require.NotNil(t, valAttr) 205 require.Equal(t, cty.String, valAttr.Type()) 206 assert.Equal(t, "ok", valAttr.Value().AsString()) 207 208 childOutputs := childModule.GetBlocks().OfType("output") 209 require.Len(t, childOutputs, 1) 210 assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName()) 211 childValAttr := childOutputs[0].GetAttribute("value") 212 require.NotNil(t, childValAttr) 213 require.Equal(t, cty.String, childValAttr.Type()) 214 assert.Equal(t, "ok", childValAttr.Value().AsString()) 215 216 } 217 218 func Test_NestedParentModule(t *testing.T) { 219 220 fs := testutil.CreateFS(t, map[string]string{ 221 "code/test.tf": ` 222 module "my-mod" { 223 source = "../." 224 input = "ok" 225 } 226 227 output "result" { 228 value = module.my-mod.mod_result 229 } 230 `, 231 "root.tf": ` 232 variable "input" { 233 default = "?" 234 } 235 236 output "mod_result" { 237 value = var.input 238 } 239 `, 240 }) 241 242 parser := New(fs, "", OptionStopOnHCLError(true)) 243 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 244 t.Fatal(err) 245 } 246 modules, _, err := parser.EvaluateAll(context.TODO()) 247 if err != nil { 248 t.Fatal(err) 249 } 250 require.Len(t, modules, 2) 251 rootModule := modules[0] 252 childModule := modules[1] 253 254 moduleBlocks := rootModule.GetBlocks().OfType("module") 255 require.Len(t, moduleBlocks, 1) 256 257 assert.Equal(t, "module", moduleBlocks[0].Type()) 258 assert.Equal(t, "module.my-mod", moduleBlocks[0].FullName()) 259 inputAttr := moduleBlocks[0].GetAttribute("input") 260 require.NotNil(t, inputAttr) 261 require.Equal(t, cty.String, inputAttr.Value().Type()) 262 assert.Equal(t, "ok", inputAttr.Value().AsString()) 263 264 rootOutputs := rootModule.GetBlocks().OfType("output") 265 require.Len(t, rootOutputs, 1) 266 assert.Equal(t, "output.result", rootOutputs[0].FullName()) 267 valAttr := rootOutputs[0].GetAttribute("value") 268 require.NotNil(t, valAttr) 269 require.Equal(t, cty.String, valAttr.Type()) 270 assert.Equal(t, "ok", valAttr.Value().AsString()) 271 272 childOutputs := childModule.GetBlocks().OfType("output") 273 require.Len(t, childOutputs, 1) 274 assert.Equal(t, "module.my-mod.output.mod_result", childOutputs[0].FullName()) 275 childValAttr := childOutputs[0].GetAttribute("value") 276 require.NotNil(t, childValAttr) 277 require.Equal(t, cty.String, childValAttr.Type()) 278 assert.Equal(t, "ok", childValAttr.Value().AsString()) 279 } 280 281 func Test_UndefinedModuleOutputReference(t *testing.T) { 282 283 fs := testutil.CreateFS(t, map[string]string{ 284 "code/test.tf": ` 285 resource "something" "blah" { 286 value = module.x.y 287 } 288 `, 289 }) 290 291 parser := New(fs, "", OptionStopOnHCLError(true)) 292 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 293 t.Fatal(err) 294 } 295 modules, _, err := parser.EvaluateAll(context.TODO()) 296 if err != nil { 297 t.Fatal(err) 298 } 299 require.Len(t, modules, 1) 300 rootModule := modules[0] 301 302 blocks := rootModule.GetResourcesByType("something") 303 require.Len(t, blocks, 1) 304 block := blocks[0] 305 306 attr := block.GetAttribute("value") 307 require.NotNil(t, attr) 308 309 assert.Equal(t, false, attr.IsResolvable()) 310 } 311 312 func Test_UndefinedModuleOutputReferenceInSlice(t *testing.T) { 313 314 fs := testutil.CreateFS(t, map[string]string{ 315 "code/test.tf": ` 316 resource "something" "blah" { 317 value = ["first", module.x.y, "last"] 318 } 319 `, 320 }) 321 322 parser := New(fs, "", OptionStopOnHCLError(true)) 323 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 324 t.Fatal(err) 325 } 326 modules, _, err := parser.EvaluateAll(context.TODO()) 327 if err != nil { 328 t.Fatal(err) 329 } 330 require.Len(t, modules, 1) 331 rootModule := modules[0] 332 333 blocks := rootModule.GetResourcesByType("something") 334 require.Len(t, blocks, 1) 335 block := blocks[0] 336 337 attr := block.GetAttribute("value") 338 require.NotNil(t, attr) 339 340 assert.Equal(t, true, attr.IsResolvable()) 341 342 values := attr.AsStringValueSliceOrEmpty(block) 343 require.Len(t, values, 3) 344 345 assert.Equal(t, "first", values[0].Value()) 346 assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) 347 348 assert.Equal(t, false, values[1].GetMetadata().IsResolvable()) 349 350 assert.Equal(t, "last", values[2].Value()) 351 assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) 352 } 353 354 func Test_TemplatedSliceValue(t *testing.T) { 355 356 fs := testutil.CreateFS(t, map[string]string{ 357 "code/test.tf": ` 358 359 variable "x" { 360 default = "hello" 361 } 362 363 resource "something" "blah" { 364 value = ["first", "${var.x}-${var.x}", "last"] 365 } 366 `, 367 }) 368 369 parser := New(fs, "", OptionStopOnHCLError(true)) 370 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 371 t.Fatal(err) 372 } 373 modules, _, err := parser.EvaluateAll(context.TODO()) 374 if err != nil { 375 t.Fatal(err) 376 } 377 require.Len(t, modules, 1) 378 rootModule := modules[0] 379 380 blocks := rootModule.GetResourcesByType("something") 381 require.Len(t, blocks, 1) 382 block := blocks[0] 383 384 attr := block.GetAttribute("value") 385 require.NotNil(t, attr) 386 387 assert.Equal(t, true, attr.IsResolvable()) 388 389 values := attr.AsStringValueSliceOrEmpty(block) 390 require.Len(t, values, 3) 391 392 assert.Equal(t, "first", values[0].Value()) 393 assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) 394 395 assert.Equal(t, "hello-hello", values[1].Value()) 396 assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) 397 398 assert.Equal(t, "last", values[2].Value()) 399 assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) 400 } 401 402 func Test_SliceOfVars(t *testing.T) { 403 404 fs := testutil.CreateFS(t, map[string]string{ 405 "code/test.tf": ` 406 407 variable "x" { 408 default = "1" 409 } 410 411 variable "y" { 412 default = "2" 413 } 414 415 resource "something" "blah" { 416 value = [var.x, var.y] 417 } 418 `, 419 }) 420 421 parser := New(fs, "", OptionStopOnHCLError(true)) 422 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 423 t.Fatal(err) 424 } 425 modules, _, err := parser.EvaluateAll(context.TODO()) 426 if err != nil { 427 t.Fatal(err) 428 } 429 require.Len(t, modules, 1) 430 rootModule := modules[0] 431 432 blocks := rootModule.GetResourcesByType("something") 433 require.Len(t, blocks, 1) 434 block := blocks[0] 435 436 attr := block.GetAttribute("value") 437 require.NotNil(t, attr) 438 439 assert.Equal(t, true, attr.IsResolvable()) 440 441 values := attr.AsStringValueSliceOrEmpty(block) 442 require.Len(t, values, 2) 443 444 assert.Equal(t, "1", values[0].Value()) 445 assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) 446 447 assert.Equal(t, "2", values[1].Value()) 448 assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) 449 } 450 451 func Test_VarSlice(t *testing.T) { 452 453 fs := testutil.CreateFS(t, map[string]string{ 454 "code/test.tf": ` 455 456 variable "x" { 457 default = ["a", "b", "c"] 458 } 459 460 resource "something" "blah" { 461 value = var.x 462 } 463 `, 464 }) 465 466 parser := New(fs, "", OptionStopOnHCLError(true)) 467 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 468 t.Fatal(err) 469 } 470 modules, _, err := parser.EvaluateAll(context.TODO()) 471 if err != nil { 472 t.Fatal(err) 473 } 474 require.Len(t, modules, 1) 475 rootModule := modules[0] 476 477 blocks := rootModule.GetResourcesByType("something") 478 require.Len(t, blocks, 1) 479 block := blocks[0] 480 481 attr := block.GetAttribute("value") 482 require.NotNil(t, attr) 483 484 assert.Equal(t, true, attr.IsResolvable()) 485 486 values := attr.AsStringValueSliceOrEmpty(block) 487 require.Len(t, values, 3) 488 489 assert.Equal(t, "a", values[0].Value()) 490 assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) 491 492 assert.Equal(t, "b", values[1].Value()) 493 assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) 494 495 assert.Equal(t, "c", values[2].Value()) 496 assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) 497 } 498 499 func Test_LocalSliceNested(t *testing.T) { 500 501 fs := testutil.CreateFS(t, map[string]string{ 502 "code/test.tf": ` 503 504 variable "x" { 505 default = "a" 506 } 507 508 locals { 509 y = [var.x, "b", "c"] 510 } 511 512 resource "something" "blah" { 513 value = local.y 514 } 515 `, 516 }) 517 518 parser := New(fs, "", OptionStopOnHCLError(true)) 519 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 520 t.Fatal(err) 521 } 522 modules, _, err := parser.EvaluateAll(context.TODO()) 523 if err != nil { 524 t.Fatal(err) 525 } 526 require.Len(t, modules, 1) 527 rootModule := modules[0] 528 529 blocks := rootModule.GetResourcesByType("something") 530 require.Len(t, blocks, 1) 531 block := blocks[0] 532 533 attr := block.GetAttribute("value") 534 require.NotNil(t, attr) 535 536 assert.Equal(t, true, attr.IsResolvable()) 537 538 values := attr.AsStringValueSliceOrEmpty(block) 539 require.Len(t, values, 3) 540 541 assert.Equal(t, "a", values[0].Value()) 542 assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) 543 544 assert.Equal(t, "b", values[1].Value()) 545 assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) 546 547 assert.Equal(t, "c", values[2].Value()) 548 assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) 549 } 550 551 func Test_FunctionCall(t *testing.T) { 552 553 fs := testutil.CreateFS(t, map[string]string{ 554 "code/test.tf": ` 555 556 variable "x" { 557 default = ["a", "b"] 558 } 559 560 resource "something" "blah" { 561 value = concat(var.x, ["c"]) 562 } 563 `, 564 }) 565 566 parser := New(fs, "", OptionStopOnHCLError(true)) 567 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 568 t.Fatal(err) 569 } 570 modules, _, err := parser.EvaluateAll(context.TODO()) 571 if err != nil { 572 t.Fatal(err) 573 } 574 require.Len(t, modules, 1) 575 rootModule := modules[0] 576 577 blocks := rootModule.GetResourcesByType("something") 578 require.Len(t, blocks, 1) 579 block := blocks[0] 580 581 attr := block.GetAttribute("value") 582 require.NotNil(t, attr) 583 584 assert.Equal(t, true, attr.IsResolvable()) 585 586 values := attr.AsStringValueSliceOrEmpty(block) 587 require.Len(t, values, 3) 588 589 assert.Equal(t, "a", values[0].Value()) 590 assert.Equal(t, true, values[0].GetMetadata().IsResolvable()) 591 592 assert.Equal(t, "b", values[1].Value()) 593 assert.Equal(t, true, values[1].GetMetadata().IsResolvable()) 594 595 assert.Equal(t, "c", values[2].Value()) 596 assert.Equal(t, true, values[2].GetMetadata().IsResolvable()) 597 } 598 599 func Test_DefaultRegistry(t *testing.T) { 600 601 fs := testutil.CreateFS(t, map[string]string{ 602 "code/test.tf": ` 603 module "registry" { 604 source = "terraform-aws-modules/vpc/aws" 605 } 606 `, 607 }) 608 609 parser := New(fs, "", OptionStopOnHCLError(true)) 610 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 611 t.Fatal(err) 612 } 613 modules, _, err := parser.EvaluateAll(context.TODO()) 614 require.NoError(t, err) 615 require.Len(t, modules, 2) 616 } 617 618 func Test_SpecificRegistry(t *testing.T) { 619 620 fs := testutil.CreateFS(t, map[string]string{ 621 "code/test.tf": ` 622 module "registry" { 623 source = "registry.terraform.io/terraform-aws-modules/vpc/aws" 624 } 625 `, 626 }) 627 628 parser := New(fs, "", OptionStopOnHCLError(true)) 629 if err := parser.ParseFS(context.TODO(), "code"); err != nil { 630 t.Fatal(err) 631 } 632 modules, _, err := parser.EvaluateAll(context.TODO()) 633 require.NoError(t, err) 634 require.Len(t, modules, 2) 635 } 636 637 func Test_NullDefaultValueForVar(t *testing.T) { 638 fs := testutil.CreateFS(t, map[string]string{ 639 "test.tf": ` 640 variable "bucket_name" { 641 type = string 642 default = null 643 } 644 645 resource "aws_s3_bucket" "default" { 646 bucket = var.bucket_name != null ? var.bucket_name : "default" 647 } 648 `, 649 }) 650 651 parser := New(fs, "", OptionStopOnHCLError(true)) 652 if err := parser.ParseFS(context.TODO(), "."); err != nil { 653 t.Fatal(err) 654 } 655 modules, _, err := parser.EvaluateAll(context.TODO()) 656 require.NoError(t, err) 657 require.Len(t, modules, 1) 658 659 rootModule := modules[0] 660 661 blocks := rootModule.GetResourcesByType("aws_s3_bucket") 662 require.Len(t, blocks, 1) 663 block := blocks[0] 664 665 attr := block.GetAttribute("bucket") 666 require.NotNil(t, attr) 667 assert.Equal(t, "default", attr.Value().AsString()) 668 } 669 670 func Test_MultipleInstancesOfSameResource(t *testing.T) { 671 fs := testutil.CreateFS(t, map[string]string{ 672 "test.tf": ` 673 674 resource "aws_kms_key" "key1" { 675 description = "Key #1" 676 enable_key_rotation = true 677 } 678 679 resource "aws_kms_key" "key2" { 680 description = "Key #2" 681 enable_key_rotation = true 682 } 683 684 resource "aws_s3_bucket" "this" { 685 bucket = "test" 686 } 687 688 689 resource "aws_s3_bucket_server_side_encryption_configuration" "this1" { 690 bucket = aws_s3_bucket.this.id 691 692 rule { 693 apply_server_side_encryption_by_default { 694 kms_master_key_id = aws_kms_key.key1.arn 695 sse_algorithm = "aws:kms" 696 } 697 } 698 } 699 700 resource "aws_s3_bucket_server_side_encryption_configuration" "this2" { 701 bucket = aws_s3_bucket.this.id 702 703 rule { 704 apply_server_side_encryption_by_default { 705 kms_master_key_id = aws_kms_key.key2.arn 706 sse_algorithm = "aws:kms" 707 } 708 } 709 } 710 `, 711 }) 712 713 parser := New(fs, "", OptionStopOnHCLError(true)) 714 if err := parser.ParseFS(context.TODO(), "."); err != nil { 715 t.Fatal(err) 716 } 717 modules, _, err := parser.EvaluateAll(context.TODO()) 718 assert.NoError(t, err) 719 assert.Len(t, modules, 1) 720 721 rootModule := modules[0] 722 723 blocks := rootModule.GetResourcesByType("aws_s3_bucket_server_side_encryption_configuration") 724 assert.Len(t, blocks, 2) 725 726 for _, block := range blocks { 727 attr := block.GetNestedAttribute("rule.apply_server_side_encryption_by_default.kms_master_key_id") 728 assert.NotNil(t, attr) 729 assert.NotEmpty(t, attr.Value().AsString()) 730 } 731 }