github.com/khulnasoft-lab/defsec@v1.0.5-0.20230827010352-5e9f46893d95/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/khulnasoft-lab/defsec/internal/rules" 11 "github.com/khulnasoft-lab/defsec/pkg/providers" 12 "github.com/khulnasoft-lab/defsec/pkg/scan" 13 "github.com/khulnasoft-lab/defsec/pkg/scanners/options" 14 "github.com/khulnasoft-lab/defsec/pkg/severity" 15 "github.com/khulnasoft-lab/defsec/pkg/state" 16 "github.com/khulnasoft-lab/defsec/pkg/terraform" 17 "github.com/khulnasoft-lab/defsec/test/testutil" 18 "github.com/stretchr/testify/assert" 19 "github.com/stretchr/testify/require" 20 ) 21 22 var alwaysFailRule = scan.Rule{ 23 Provider: providers.AWSProvider, 24 Service: "service", 25 ShortCode: "abc", 26 Severity: severity.High, 27 CustomChecks: scan.CustomChecks{ 28 Terraform: &scan.TerraformCustomCheck{ 29 RequiredTypes: []string{}, 30 RequiredLabels: []string{}, 31 Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { 32 results.Add("oh no", resourceBlock) 33 return 34 }, 35 }, 36 }, 37 } 38 39 func scanWithOptions(t *testing.T, code string, opt ...options.ScannerOption) scan.Results { 40 41 fs := testutil.CreateFS(t, map[string]string{ 42 "project/main.tf": code, 43 }) 44 45 scanner := New(opt...) 46 results, _, err := scanner.ScanFSWithMetrics(context.TODO(), fs, "project") 47 require.NoError(t, err) 48 return results 49 } 50 51 func Test_OptionWithAlternativeIDProvider(t *testing.T) { 52 reg := rules.Register(alwaysFailRule, nil) 53 defer rules.Deregister(reg) 54 55 options := []options.ScannerOption{ 56 ScannerWithAlternativeIDProvider(func(s string) []string { 57 return []string{"something", "altid", "blah"} 58 }), 59 } 60 results := scanWithOptions(t, ` 61 //terrasec:ignore:altid 62 resource "something" "else" {} 63 `, options...) 64 require.Len(t, results.GetFailed(), 0) 65 require.Len(t, results.GetIgnored(), 1) 66 67 } 68 69 func Test_VulOptionWithAlternativeIDProvider(t *testing.T) { 70 reg := rules.Register(alwaysFailRule, nil) 71 defer rules.Deregister(reg) 72 73 options := []options.ScannerOption{ 74 ScannerWithAlternativeIDProvider(func(s string) []string { 75 return []string{"something", "altid", "blah"} 76 }), 77 } 78 results := scanWithOptions(t, ` 79 //vul:ignore:altid 80 resource "something" "else" {} 81 `, options...) 82 require.Len(t, results.GetFailed(), 0) 83 require.Len(t, results.GetIgnored(), 1) 84 85 } 86 87 func Test_OptionWithSeverityOverrides(t *testing.T) { 88 reg := rules.Register(alwaysFailRule, nil) 89 defer rules.Deregister(reg) 90 91 options := []options.ScannerOption{ 92 ScannerWithSeverityOverrides(map[string]string{"aws-service-abc": "LOW"}), 93 } 94 results := scanWithOptions(t, ` 95 resource "something" "else" {} 96 `, options...) 97 require.Len(t, results.GetFailed(), 1) 98 assert.Equal(t, severity.Low, results.GetFailed()[0].Severity()) 99 } 100 101 func Test_OptionWithDebugWriter(t *testing.T) { 102 reg := rules.Register(alwaysFailRule, nil) 103 defer rules.Deregister(reg) 104 105 buffer := bytes.NewBuffer([]byte{}) 106 107 scannerOpts := []options.ScannerOption{ 108 options.ScannerWithDebug(buffer), 109 } 110 _ = scanWithOptions(t, ` 111 resource "something" "else" {} 112 `, scannerOpts...) 113 require.Greater(t, buffer.Len(), 0) 114 } 115 116 func Test_OptionNoIgnores(t *testing.T) { 117 reg := rules.Register(alwaysFailRule, nil) 118 defer rules.Deregister(reg) 119 120 scannerOpts := []options.ScannerOption{ 121 ScannerWithNoIgnores(), 122 } 123 results := scanWithOptions(t, ` 124 //terrasec:ignore:aws-service-abc 125 resource "something" "else" {} 126 `, scannerOpts...) 127 require.Len(t, results.GetFailed(), 1) 128 require.Len(t, results.GetIgnored(), 0) 129 130 } 131 132 func Test_OptionExcludeRules(t *testing.T) { 133 reg := rules.Register(alwaysFailRule, nil) 134 defer rules.Deregister(reg) 135 136 options := []options.ScannerOption{ 137 ScannerWithExcludedRules([]string{"aws-service-abc"}), 138 } 139 results := scanWithOptions(t, ` 140 resource "something" "else" {} 141 `, options...) 142 require.Len(t, results.GetFailed(), 0) 143 require.Len(t, results.GetIgnored(), 1) 144 145 } 146 147 func Test_OptionIncludeRules(t *testing.T) { 148 reg := rules.Register(alwaysFailRule, nil) 149 defer rules.Deregister(reg) 150 151 scannerOpts := []options.ScannerOption{ 152 ScannerWithIncludedRules([]string{"this-only"}), 153 } 154 results := scanWithOptions(t, ` 155 resource "something" "else" {} 156 `, scannerOpts...) 157 require.Len(t, results.GetFailed(), 0) 158 require.Len(t, results.GetIgnored(), 1) 159 160 } 161 162 func Test_OptionWithMinimumSeverity(t *testing.T) { 163 reg := rules.Register(alwaysFailRule, nil) 164 defer rules.Deregister(reg) 165 166 scannerOpts := []options.ScannerOption{ 167 ScannerWithMinimumSeverity(severity.Critical), 168 } 169 results := scanWithOptions(t, ` 170 resource "something" "else" {} 171 `, scannerOpts...) 172 require.Len(t, results.GetFailed(), 0) 173 require.Len(t, results.GetIgnored(), 1) 174 175 } 176 177 func Test_OptionWithPolicyDirs(t *testing.T) { 178 179 fs := testutil.CreateFS(t, map[string]string{ 180 "/code/main.tf": ` 181 resource "aws_s3_bucket" "my-bucket" { 182 bucket = "evil" 183 } 184 `, 185 "/rules/test.rego": ` 186 package defsec.abcdefg 187 188 __rego_metadata__ := { 189 "id": "TEST123", 190 "avd_id": "AVD-TEST-0123", 191 "title": "Buckets should not be evil", 192 "short_code": "no-evil-buckets", 193 "severity": "CRITICAL", 194 "type": "DefSec Security Check", 195 "description": "You should not allow buckets to be evil", 196 "recommended_actions": "Use a good bucket instead", 197 "url": "https://google.com/search?q=is+my+bucket+evil", 198 } 199 200 __rego_input__ := { 201 "combine": false, 202 "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], 203 } 204 205 deny[cause] { 206 bucket := input.aws.s3.buckets[_] 207 bucket.name.value == "evil" 208 cause := bucket.name 209 } 210 `, 211 }) 212 213 debugLog := bytes.NewBuffer([]byte{}) 214 scanner := New( 215 options.ScannerWithDebug(debugLog), 216 options.ScannerWithPolicyFilesystem(fs), 217 options.ScannerWithPolicyDirs("rules"), 218 options.ScannerWithRegoOnly(true), 219 ) 220 221 results, err := scanner.ScanFS(context.TODO(), fs, "code") 222 require.NoError(t, err) 223 224 require.Len(t, results.GetFailed(), 1) 225 226 failure := results.GetFailed()[0] 227 228 assert.Equal(t, "AVD-TEST-0123", failure.Rule().AVDID) 229 230 actualCode, err := failure.GetCode() 231 require.NoError(t, err) 232 for i := range actualCode.Lines { 233 actualCode.Lines[i].Highlighted = "" 234 } 235 assert.Equal(t, []scan.Line{ 236 { 237 Number: 2, 238 Content: "resource \"aws_s3_bucket\" \"my-bucket\" {", 239 IsCause: false, 240 FirstCause: false, 241 LastCause: false, 242 Annotation: "", 243 }, 244 { 245 Number: 3, 246 Content: "\tbucket = \"evil\"", 247 IsCause: true, 248 FirstCause: true, 249 LastCause: true, 250 Annotation: "", 251 }, 252 { 253 Number: 4, 254 Content: "}", 255 IsCause: false, 256 FirstCause: false, 257 LastCause: false, 258 Annotation: "", 259 }, 260 }, actualCode.Lines) 261 262 if t.Failed() { 263 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 264 } 265 266 } 267 268 func Test_OptionWithPolicyNamespaces(t *testing.T) { 269 270 tests := []struct { 271 includedNamespaces []string 272 policyNamespace string 273 wantFailure bool 274 }{ 275 { 276 includedNamespaces: nil, 277 policyNamespace: "blah", 278 wantFailure: false, 279 }, 280 { 281 includedNamespaces: nil, 282 policyNamespace: "appshield.something", 283 wantFailure: true, 284 }, 285 { 286 includedNamespaces: nil, 287 policyNamespace: "defsec.blah", 288 wantFailure: true, 289 }, 290 { 291 includedNamespaces: []string{"user"}, 292 policyNamespace: "users", 293 wantFailure: false, 294 }, 295 { 296 includedNamespaces: []string{"users"}, 297 policyNamespace: "something.users", 298 wantFailure: false, 299 }, 300 { 301 includedNamespaces: []string{"users"}, 302 policyNamespace: "users", 303 wantFailure: true, 304 }, 305 { 306 includedNamespaces: []string{"users"}, 307 policyNamespace: "users.my_rule", 308 wantFailure: true, 309 }, 310 { 311 includedNamespaces: []string{"a", "users", "b"}, 312 policyNamespace: "users", 313 wantFailure: true, 314 }, 315 { 316 includedNamespaces: []string{"user"}, 317 policyNamespace: "defsec", 318 wantFailure: true, 319 }, 320 } 321 322 for i, test := range tests { 323 324 t.Run(strconv.Itoa(i), func(t *testing.T) { 325 326 fs := testutil.CreateFS(t, map[string]string{ 327 "/code/main.tf": ` 328 resource "aws_s3_bucket" "my-bucket" { 329 bucket = "evil" 330 } 331 `, 332 "/rules/test.rego": fmt.Sprintf(` 333 # METADATA 334 # custom: 335 # input: 336 # selector: 337 # - type: cloud 338 # subtypes: 339 # - service: s3 340 # provider: aws 341 package %s 342 343 deny[cause] { 344 bucket := input.aws.s3.buckets[_] 345 bucket.name.value == "evil" 346 cause := bucket.name 347 } 348 349 `, test.policyNamespace), 350 }) 351 352 scanner := New( 353 options.ScannerWithPolicyDirs("rules"), 354 options.ScannerWithPolicyNamespaces(test.includedNamespaces...), 355 ) 356 357 results, _, err := scanner.ScanFSWithMetrics(context.TODO(), fs, "code") 358 require.NoError(t, err) 359 360 var found bool 361 for _, result := range results.GetFailed() { 362 if result.RegoNamespace() == test.policyNamespace && result.RegoRule() == "deny" { 363 found = true 364 break 365 } 366 } 367 assert.Equal(t, test.wantFailure, found) 368 369 }) 370 } 371 372 } 373 374 func Test_OptionWithStateFunc(t *testing.T) { 375 376 fs := testutil.CreateFS(t, map[string]string{ 377 "code/main.tf": ` 378 resource "aws_s3_bucket" "my-bucket" { 379 bucket = "evil" 380 } 381 `, 382 }) 383 384 var actual state.State 385 386 debugLog := bytes.NewBuffer([]byte{}) 387 scanner := New( 388 options.ScannerWithDebug(debugLog), 389 ScannerWithStateFunc(func(s *state.State) { 390 require.NotNil(t, s) 391 actual = *s 392 }), 393 ) 394 395 _, _, err := scanner.ScanFSWithMetrics(context.TODO(), fs, "code") 396 require.NoError(t, err) 397 398 assert.Equal(t, 1, len(actual.AWS.S3.Buckets)) 399 400 if t.Failed() { 401 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 402 } 403 404 } 405 406 func Test_OptionWithRegoOnly(t *testing.T) { 407 408 fs := testutil.CreateFS(t, map[string]string{ 409 "/code/main.tf": ` 410 resource "aws_s3_bucket" "my-bucket" { 411 bucket = "evil" 412 } 413 `, 414 "/rules/test.rego": ` 415 package defsec.abcdefg 416 417 __rego_metadata__ := { 418 "id": "TEST123", 419 "avd_id": "AVD-TEST-0123", 420 "title": "Buckets should not be evil", 421 "short_code": "no-evil-buckets", 422 "severity": "CRITICAL", 423 "type": "DefSec Security Check", 424 "description": "You should not allow buckets to be evil", 425 "recommended_actions": "Use a good bucket instead", 426 "url": "https://google.com/search?q=is+my+bucket+evil", 427 } 428 429 __rego_input__ := { 430 "combine": false, 431 "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], 432 } 433 434 deny[cause] { 435 bucket := input.aws.s3.buckets[_] 436 bucket.name.value == "evil" 437 cause := bucket.name 438 } 439 `, 440 }) 441 442 debugLog := bytes.NewBuffer([]byte{}) 443 scanner := New( 444 options.ScannerWithDebug(debugLog), 445 options.ScannerWithPolicyDirs("rules"), 446 options.ScannerWithRegoOnly(true), 447 ) 448 449 results, err := scanner.ScanFS(context.TODO(), fs, "code") 450 require.NoError(t, err) 451 452 require.Len(t, results.GetFailed(), 1) 453 assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) 454 455 if t.Failed() { 456 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 457 } 458 } 459 460 func Test_OptionWithRegoOnly_CodeHighlighting(t *testing.T) { 461 462 fs := testutil.CreateFS(t, map[string]string{ 463 "/code/main.tf": ` 464 resource "aws_s3_bucket" "my-bucket" { 465 bucket = "evil" 466 } 467 `, 468 "/rules/test.rego": ` 469 package defsec.abcdefg 470 471 __rego_metadata__ := { 472 "id": "TEST123", 473 "avd_id": "AVD-TEST-0123", 474 "title": "Buckets should not be evil", 475 "short_code": "no-evil-buckets", 476 "severity": "CRITICAL", 477 "type": "DefSec Security Check", 478 "description": "You should not allow buckets to be evil", 479 "recommended_actions": "Use a good bucket instead", 480 "url": "https://google.com/search?q=is+my+bucket+evil", 481 } 482 483 __rego_input__ := { 484 "combine": false, 485 "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], 486 } 487 488 deny[res] { 489 bucket := input.aws.s3.buckets[_] 490 bucket.name.value == "evil" 491 res := result.new("oh no", bucket.name) 492 } 493 `, 494 }) 495 496 debugLog := bytes.NewBuffer([]byte{}) 497 scanner := New( 498 options.ScannerWithDebug(debugLog), 499 options.ScannerWithPolicyDirs("rules"), 500 options.ScannerWithRegoOnly(true), 501 ScannerWithEmbeddedLibraries(true), 502 ) 503 504 results, err := scanner.ScanFS(context.TODO(), fs, "code") 505 require.NoError(t, err) 506 507 require.Len(t, results.GetFailed(), 1) 508 assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) 509 assert.NotNil(t, results[0].Metadata().Range().GetFS()) 510 511 if t.Failed() { 512 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 513 } 514 } 515 516 func Test_OptionWithSkipDownloaded(t *testing.T) { 517 fs := testutil.CreateFS(t, map[string]string{ 518 "test/main.tf": ` 519 module "s3-bucket" { 520 source = "terraform-aws-modules/s3-bucket/aws" 521 version = "3.14.0" 522 bucket = mybucket 523 } 524 `, 525 // creating our own rule for the reliability of the test 526 "/rules/test.rego": ` 527 package defsec.abcdefg 528 529 __rego_input__ := { 530 "combine": false, 531 "selector": [{"type": "defsec", "subtypes": [{"service": "s3", "provider": "aws"}]}], 532 } 533 534 deny[cause] { 535 bucket := input.aws.s3.buckets[_] 536 bucket.name.value == "mybucket" 537 cause := bucket.name 538 }`, 539 }) 540 541 scanner := New() 542 results, err := scanner.ScanFS(context.TODO(), fs, "test") 543 assert.NoError(t, err) 544 assert.Greater(t, len(results.GetFailed()), 0) 545 546 scanner = New(ScannerWithSkipDownloaded(true)) 547 results, err = scanner.ScanFS(context.TODO(), fs, "test") 548 assert.NoError(t, err) 549 assert.Len(t, results.GetFailed(), 0) 550 551 } 552 553 func Test_IAMPolicyRego(t *testing.T) { 554 fs := testutil.CreateFS(t, map[string]string{ 555 "/code/main.tf": ` 556 resource "aws_sqs_queue_policy" "bad_example" { 557 queue_url = aws_sqs_queue.q.id 558 559 policy = <<POLICY 560 { 561 "Statement": [ 562 { 563 "Effect": "Allow", 564 "Principal": "*", 565 "Action": "*" 566 } 567 ] 568 } 569 POLICY 570 }`, 571 "/rules/test.rego": ` 572 # METADATA 573 # title: Buckets should not be evil 574 # description: You should not allow buckets to be evil 575 # scope: package 576 # schemas: 577 # - input: schema.input 578 # related_resources: 579 # - https://google.com/search?q=is+my+bucket+evil 580 # custom: 581 # id: TEST123 582 # avd_id: AVD-TEST-0123 583 # short_code: no-evil-buckets 584 # severity: CRITICAL 585 # recommended_action: Use a good bucket instead 586 # input: 587 # selector: 588 # - type: cloud 589 # subtypes: 590 # - service: sqs 591 # provider: aws 592 package defsec.abcdefg 593 594 595 deny[res] { 596 queue := input.aws.sqs.queues[_] 597 policy := queue.policies[_] 598 doc := json.unmarshal(policy.document.value) 599 statement = doc.Statement[_] 600 action := statement.Action[_] 601 action == "*" 602 res := result.new("SQS Policy contains wildcard in action", policy.document) 603 } 604 `, 605 }) 606 607 debugLog := bytes.NewBuffer([]byte{}) 608 scanner := New( 609 options.ScannerWithDebug(debugLog), 610 options.ScannerWithTrace(debugLog), 611 options.ScannerWithPolicyDirs("rules"), 612 options.ScannerWithRegoOnly(true), 613 ScannerWithEmbeddedLibraries(true), 614 ) 615 616 defer func() { 617 if t.Failed() { 618 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 619 } 620 }() 621 622 results, err := scanner.ScanFS(context.TODO(), fs, "code") 623 require.NoError(t, err) 624 625 require.Len(t, results.GetFailed(), 1) 626 assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) 627 assert.NotNil(t, results[0].Metadata().Range().GetFS()) 628 629 } 630 631 func Test_ContainerDefinitionRego(t *testing.T) { 632 fs := testutil.CreateFS(t, map[string]string{ 633 "/code/main.tf": ` 634 resource "aws_ecs_task_definition" "test" { 635 family = "test" 636 container_definitions = <<TASK_DEFINITION 637 [ 638 { 639 "privileged": true, 640 "cpu": 10, 641 "command": ["sleep", "10"], 642 "entryPoint": ["/"], 643 "environment": [ 644 {"name": "VARNAME", "value": "VARVAL"} 645 ], 646 "essential": true, 647 "image": "jenkins", 648 "memory": 128, 649 "name": "jenkins", 650 "portMappings": [ 651 { 652 "containerPort": 80, 653 "hostPort": 8080 654 } 655 ], 656 "resourceRequirements":[ 657 { 658 "type":"InferenceAccelerator", 659 "value":"device_1" 660 } 661 ] 662 } 663 ] 664 TASK_DEFINITION 665 666 inference_accelerator { 667 device_name = "device_1" 668 device_type = "eia1.medium" 669 } 670 }`, 671 "/rules/test.rego": ` 672 package defsec.abcdefg 673 674 675 __rego_metadata__ := { 676 "id": "TEST123", 677 "avd_id": "AVD-TEST-0123", 678 "title": "Buckets should not be evil", 679 "short_code": "no-evil-buckets", 680 "severity": "CRITICAL", 681 "type": "DefSec Security Check", 682 "description": "You should not allow buckets to be evil", 683 "recommended_actions": "Use a good bucket instead", 684 "url": "https://google.com/search?q=is+my+bucket+evil", 685 } 686 687 __rego_input__ := { 688 "combine": false, 689 "selector": [{"type": "defsec", "subtypes": [{"service": "ecs", "provider": "aws"}]}], 690 } 691 692 deny[res] { 693 definition := input.aws.ecs.taskdefinitions[_].containerdefinitions[_] 694 definition.privileged.value == true 695 res := result.new("Privileged container detected", definition.privileged) 696 } 697 `, 698 }) 699 700 debugLog := bytes.NewBuffer([]byte{}) 701 scanner := New( 702 options.ScannerWithDebug(debugLog), 703 options.ScannerWithPolicyDirs("rules"), 704 options.ScannerWithRegoOnly(true), 705 ScannerWithEmbeddedLibraries(true), 706 ) 707 708 results, err := scanner.ScanFS(context.TODO(), fs, "code") 709 require.NoError(t, err) 710 711 require.Len(t, results.GetFailed(), 1) 712 assert.Equal(t, "AVD-TEST-0123", results[0].Rule().AVDID) 713 assert.NotNil(t, results[0].Metadata().Range().GetFS()) 714 715 if t.Failed() { 716 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 717 } 718 } 719 720 func Test_S3_Linking(t *testing.T) { 721 722 code := ` 723 ## terrasec:ignore:aws-s3-enable-bucket-encryption 724 ## terrasec:ignore:aws-s3-enable-bucket-logging 725 ## terrasec:ignore:aws-s3-enable-versioning 726 resource "aws_s3_bucket" "blubb" { 727 bucket = "test" 728 } 729 730 resource "aws_s3_bucket_public_access_block" "audit_logs_athena" { 731 bucket = aws_s3_bucket.blubb.id 732 733 block_public_acls = true 734 block_public_policy = true 735 ignore_public_acls = true 736 restrict_public_buckets = true 737 } 738 739 # terrasec:ignore:aws-s3-enable-bucket-encryption 740 # terrasec:ignore:aws-s3-enable-bucket-logging 741 # terrasec:ignore:aws-s3-enable-versioning 742 resource "aws_s3_bucket" "foo" { 743 bucket = "prefix-" # remove this variable and it works; does not report 744 force_destroy = true 745 } 746 747 resource "aws_s3_bucket_public_access_block" "foo" { 748 bucket = aws_s3_bucket.foo.id 749 750 block_public_acls = true 751 block_public_policy = true 752 ignore_public_acls = true 753 restrict_public_buckets = true 754 } 755 756 ` 757 758 fs := testutil.CreateFS(t, map[string]string{ 759 "code/main.tf": code, 760 }) 761 762 debugLog := bytes.NewBuffer([]byte{}) 763 scanner := New( 764 options.ScannerWithDebug(debugLog), 765 ) 766 767 results, err := scanner.ScanFS(context.TODO(), fs, "code") 768 require.NoError(t, err) 769 770 failed := results.GetFailed() 771 for _, result := range failed { 772 // public access block 773 assert.NotEqual(t, "AVD-AWS-0094", result.Rule().AVDID, "AVD-AWS-0094 should not be reported - was found at "+result.Metadata().Range().String()) 774 // encryption 775 assert.NotEqual(t, "AVD-AWS-0088", result.Rule().AVDID) 776 // logging 777 assert.NotEqual(t, "AVD-AWS-0089", result.Rule().AVDID) 778 // versioning 779 assert.NotEqual(t, "AVD-AWS-0090", result.Rule().AVDID) 780 } 781 782 if t.Failed() { 783 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 784 } 785 } 786 787 func Test_S3_Linking_PublicAccess(t *testing.T) { 788 789 code := ` 790 resource "aws_s3_bucket" "testA" { 791 bucket = "com.test.testA" 792 } 793 794 resource "aws_s3_bucket_acl" "testA" { 795 bucket = aws_s3_bucket.testA.id 796 acl = "private" 797 } 798 799 resource "aws_s3_bucket_public_access_block" "testA" { 800 bucket = aws_s3_bucket.testA.id 801 802 block_public_acls = true 803 block_public_policy = true 804 ignore_public_acls = true 805 restrict_public_buckets = true 806 } 807 808 resource "aws_s3_bucket" "testB" { 809 bucket = "com.test.testB" 810 } 811 812 resource "aws_s3_bucket_acl" "testB" { 813 bucket = aws_s3_bucket.testB.id 814 acl = "private" 815 } 816 817 resource "aws_s3_bucket_public_access_block" "testB" { 818 bucket = aws_s3_bucket.testB.id 819 820 block_public_acls = true 821 block_public_policy = true 822 ignore_public_acls = true 823 restrict_public_buckets = true 824 } 825 826 ` 827 828 fs := testutil.CreateFS(t, map[string]string{ 829 "code/main.tf": code, 830 }) 831 832 debugLog := bytes.NewBuffer([]byte{}) 833 scanner := New( 834 options.ScannerWithDebug(debugLog), 835 ) 836 837 results, err := scanner.ScanFS(context.TODO(), fs, "code") 838 require.NoError(t, err) 839 840 for _, result := range results.GetFailed() { 841 // public access block 842 assert.NotEqual(t, "AVD-AWS-0094", result.Rule().AVDID) 843 } 844 845 if t.Failed() { 846 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 847 } 848 } 849 850 func Test_RegoInput(t *testing.T) { 851 852 var regoInput interface{} 853 854 opts := []options.ScannerOption{ 855 ScannerWithStateFunc(func(s *state.State) { 856 regoInput = s.ToRego() 857 }), 858 } 859 _ = scanWithOptions(t, ` 860 resource "aws_security_group" "example_security_group" { 861 name = "example_security_group" 862 863 description = "Example SG" 864 865 ingress { 866 description = "Allow SSH" 867 from_port = 22 868 to_port = 22 869 protocol = "tcp" 870 cidr_blocks = ["1.2.3.4", "5.6.7.8"] 871 } 872 873 } 874 `, opts...) 875 876 outer, ok := regoInput.(map[string]interface{}) 877 require.True(t, ok) 878 aws, ok := outer["aws"].(map[string]interface{}) 879 require.True(t, ok) 880 ec2, ok := aws["ec2"].(map[string]interface{}) 881 require.True(t, ok) 882 sgs, ok := ec2["securitygroups"].([]interface{}) 883 require.True(t, ok) 884 require.Len(t, sgs, 1) 885 sg0, ok := sgs[0].(map[string]interface{}) 886 require.True(t, ok) 887 ingress, ok := sg0["ingressrules"].([]interface{}) 888 require.True(t, ok) 889 require.Len(t, ingress, 1) 890 ingress0, ok := ingress[0].(map[string]interface{}) 891 require.True(t, ok) 892 cidrs, ok := ingress0["cidrs"].([]interface{}) 893 require.True(t, ok) 894 require.Len(t, cidrs, 2) 895 896 cidr0, ok := cidrs[0].(map[string]interface{}) 897 require.True(t, ok) 898 899 cidr1, ok := cidrs[1].(map[string]interface{}) 900 require.True(t, ok) 901 902 assert.Equal(t, "1.2.3.4", cidr0["value"]) 903 assert.Equal(t, "5.6.7.8", cidr1["value"]) 904 } 905 906 // PoC for replacing Go with Rego: AVD-AWS-0001 907 func Test_RegoRules(t *testing.T) { 908 909 fs := testutil.CreateFS(t, map[string]string{ 910 "/code/main.tf": ` 911 resource "aws_apigatewayv2_stage" "bad_example" { 912 api_id = aws_apigatewayv2_api.example.id 913 name = "example-stage" 914 } 915 `, 916 "/rules/test.rego": `# METADATA 917 # schemas: 918 # - input: schema.input 919 # custom: 920 # avd_id: AVD-AWS-0001 921 # input: 922 # selector: 923 # - type: cloud 924 # subtypes: 925 # - service: apigateway 926 # provider: aws 927 package builtin.cloud.AWS0001 928 929 deny[res] { 930 api := input.aws.apigateway.v1.apis[_] 931 stage := api.stages[_] 932 isManaged(stage) 933 stage.accesslogging.cloudwatchloggrouparn.value == "" 934 res := result.new("Access logging is not configured.", stage.accesslogging.cloudwatchloggrouparn) 935 } 936 937 deny[res] { 938 api := input.aws.apigateway.v2.apis[_] 939 stage := api.stages[_] 940 isManaged(stage) 941 stage.accesslogging.cloudwatchloggrouparn.value == "" 942 res := result.new("Access logging is not configured.", stage.accesslogging.cloudwatchloggrouparn) 943 } 944 `, 945 }) 946 947 debugLog := bytes.NewBuffer([]byte{}) 948 scanner := New( 949 options.ScannerWithDebug(debugLog), 950 options.ScannerWithPolicyFilesystem(fs), 951 options.ScannerWithPolicyDirs("rules"), 952 options.ScannerWithRegoOnly(true), 953 ) 954 955 results, err := scanner.ScanFS(context.TODO(), fs, "code") 956 require.NoError(t, err) 957 958 require.Len(t, results.GetFailed(), 1) 959 960 failure := results.GetFailed()[0] 961 962 assert.Equal(t, "AVD-AWS-0001", failure.Rule().AVDID) 963 964 actualCode, err := failure.GetCode() 965 require.NoError(t, err) 966 for i := range actualCode.Lines { 967 actualCode.Lines[i].Highlighted = "" 968 } 969 assert.Equal(t, []scan.Line{ 970 { 971 Number: 2, 972 Content: "resource \"aws_apigatewayv2_stage\" \"bad_example\" {", 973 IsCause: true, 974 FirstCause: true, 975 LastCause: false, 976 Annotation: "", 977 }, 978 { 979 Number: 3, 980 Content: " api_id = aws_apigatewayv2_api.example.id", 981 IsCause: true, 982 FirstCause: false, 983 LastCause: false, 984 Annotation: "", 985 }, 986 { 987 Number: 4, 988 Content: " name = \"example-stage\"", 989 IsCause: true, 990 FirstCause: false, 991 LastCause: false, 992 Annotation: "", 993 }, 994 { 995 Number: 5, 996 Content: "}", 997 IsCause: true, 998 FirstCause: false, 999 LastCause: true, 1000 Annotation: "", 1001 }, 1002 }, actualCode.Lines) 1003 1004 if t.Failed() { 1005 fmt.Printf("Debug logs:\n%s\n", debugLog.String()) 1006 } 1007 1008 }