diff --git a/docs/docs/scanner/misconfiguration/index.md b/docs/docs/scanner/misconfiguration/index.md index b1107a530718..6cf4473d6652 100644 --- a/docs/docs/scanner/misconfiguration/index.md +++ b/docs/docs/scanner/misconfiguration/index.md @@ -494,8 +494,21 @@ resource "aws_security_group_rule" "example" { } ``` -!!! note - Currently nested attributes are not supported. For example you will not be able to reference the `each.key` attribute. +Checks can also be ignored by nested attributes, but certain restrictions apply: + +- You cannot access an individual block using indexes, for example when working with dynamic blocks. +- Special variables like [each](https://developer.hashicorp.com/terraform/language/meta-arguments/for_each#the-each-object) and [count](https://developer.hashicorp.com/terraform/language/meta-arguments/count#the-count-object) cannot be accessed. + +```tf +#trivy:ignore:*[logging_config.prefix=myprefix] +resource "aws_cloudfront_distribution" "example" { + logging_config { + include_cookies = false + bucket = "mylogs.s3.amazonaws.com" + prefix = "myprefix" + } +} +``` #### Ignoring module issues diff --git a/pkg/iac/scanners/terraform/ignore_test.go b/pkg/iac/scanners/terraform/ignore_test.go index 6e561d256653..0e8c0c8bfdd5 100644 --- a/pkg/iac/scanners/terraform/ignore_test.go +++ b/pkg/iac/scanners/terraform/ignore_test.go @@ -24,12 +24,18 @@ var exampleRule = scan.Rule{ Terraform: &scan.TerraformCustomCheck{ RequiredLabels: []string{"bad"}, Check: func(resourceBlock *terraform.Block, _ *terraform.Module) (results scan.Results) { - attr := resourceBlock.GetAttribute("secure") - if attr.IsNil() { - results.Add("example problem", resourceBlock) - } - if attr.IsFalse() { - results.Add("example problem", attr) + if attr, _ := resourceBlock.GetNestedAttribute("secure_settings.enabled"); attr.IsNotNil() { + if attr.IsFalse() { + results.Add("example problem", attr) + } + } else { + attr := resourceBlock.GetAttribute("secure") + if attr.IsNil() { + results.Add("example problem", resourceBlock) + } + if attr.IsFalse() { + results.Add("example problem", attr) + } } return }, @@ -44,58 +50,92 @@ func Test_IgnoreAll(t *testing.T) { inputOptions string assertLength int }{ - {name: "IgnoreAll", inputOptions: ` + { + name: "IgnoreAll", + inputOptions: ` resource "bad" "my-rule" { secure = false // tfsec:ignore:* } -`, assertLength: 0}, - {name: "IgnoreLineAboveTheBlock", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlock", + inputOptions: ` // tfsec:ignore:* resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "IgnoreLineAboveTheBlockMatchingParamBool", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockMatchingParamBool", + inputOptions: ` // tfsec:ignore:*[secure=false] resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "IgnoreLineAboveTheBlockNotMatchingParamBool", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockNotMatchingParamBool", + inputOptions: ` // tfsec:ignore:*[secure=true] resource "bad" "my-rule" { secure = false } -`, assertLength: 1}, - {name: "IgnoreLineAboveTheBlockMatchingParamString", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "IgnoreLineAboveTheBlockMatchingParamString", + inputOptions: ` // tfsec:ignore:*[name=myrule] resource "bad" "my-rule" { name = "myrule" secure = false } -`, assertLength: 0}, - {name: "IgnoreLineAboveTheBlockNotMatchingParamString", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockNotMatchingParamString", + inputOptions: ` // tfsec:ignore:*[name=myrule2] resource "bad" "my-rule" { name = "myrule" secure = false } -`, assertLength: 1}, - {name: "IgnoreLineAboveTheBlockMatchingParamInt", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "IgnoreLineAboveTheBlockMatchingParamInt", + inputOptions: ` // tfsec:ignore:*[port=123] resource "bad" "my-rule" { secure = false port = 123 } -`, assertLength: 0}, - {name: "IgnoreLineAboveTheBlockNotMatchingParamInt", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheBlockNotMatchingParamInt", + inputOptions: ` // tfsec:ignore:*[port=456] resource "bad" "my-rule" { secure = false port = 123 } -`, assertLength: 1}, - {name: "IgnoreLineStackedAboveTheBlock", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "IgnoreLineStackedAboveTheBlock", + inputOptions: ` // tfsec:ignore:* // tfsec:ignore:a // tfsec:ignore:b @@ -104,8 +144,12 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "IgnoreLineStackedAboveTheBlockWithoutMatch", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineStackedAboveTheBlockWithoutMatch", + inputOptions: ` #tfsec:ignore:* #tfsec:ignore:x @@ -116,8 +160,12 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 1}, - {name: "IgnoreLineStackedAboveTheBlockWithHashesWithoutSpaces", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "IgnoreLineStackedAboveTheBlockWithHashesWithoutSpaces", + inputOptions: ` #tfsec:ignore:* #tfsec:ignore:a #tfsec:ignore:b @@ -126,8 +174,12 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "IgnoreLineStackedAboveTheBlockWithoutSpaces", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineStackedAboveTheBlockWithoutSpaces", + inputOptions: ` //tfsec:ignore:* //tfsec:ignore:a //tfsec:ignore:b @@ -136,135 +188,261 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "IgnoreLineAboveTheLine", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreLineAboveTheLine", + inputOptions: ` resource "bad" "my-rule" { # tfsec:ignore:aws-service-abc123 secure = false } -`, assertLength: 0}, - {name: "IgnoreWithExpDateIfDateBreachedThenDontIgnore", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreWithExpDateIfDateBreachedThenDontIgnore", + inputOptions: ` resource "bad" "my-rule" { secure = false # tfsec:ignore:aws-service-abc123:exp:2000-01-02 } -`, assertLength: 1}, - {name: "IgnoreWithExpDateIfDateNotBreachedThenIgnoreIgnore", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "IgnoreWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` resource "bad" "my-rule" { secure = false # tfsec:ignore:aws-service-abc123:exp:2221-01-02 } -`, assertLength: 0}, - {name: "IgnoreWithExpDateIfDateInvalidThenDropTheIgnore", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreWithExpDateIfDateInvalidThenDropTheIgnore", + inputOptions: ` resource "bad" "my-rule" { secure = false # tfsec:ignore:aws-service-abc123:exp:2221-13-02 } -`, assertLength: 1}, - {name: "IgnoreAboveResourceBlockWithExpDateIfDateNotBreachedThenIgnoreIgnore", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "IgnoreAboveResourceBlockWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` #tfsec:ignore:aws-service-abc123:exp:2221-01-02 resource "bad" "my-rule" { } -`, assertLength: 0}, - {name: "IgnoreAboveResourceBlockWithExpDateAndMultipleIgnoresIfDateNotBreachedThenIgnoreIgnore", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreAboveResourceBlockWithExpDateAndMultipleIgnoresIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` # tfsec:ignore:aws-service-abc123:exp:2221-01-02 resource "bad" "my-rule" { } -`, assertLength: 0}, - {name: "IgnoreForImpliedIAMResource", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "IgnoreForImpliedIAMResource", + inputOptions: ` terraform { -required_version = "~> 1.1.6" + required_version = "~> 1.1.6" -required_providers { -aws = { -source = "hashicorp/aws" -version = "~> 3.48" -} -} + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.48" + } + } } # Retrieve an IAM group defined outside of this Terraform config. # tfsec:ignore:aws-iam-enforce-mfa data "aws_iam_group" "externally_defined_group" { -group_name = "group-name" # tfsec:ignore:aws-iam-enforce-mfa + group_name = "group-name" # tfsec:ignore:aws-iam-enforce-mfa } # Create an IAM policy and attach it to the group. # tfsec:ignore:aws-iam-enforce-mfa resource "aws_iam_policy" "test_policy" { -name = "test-policy" # tfsec:ignore:aws-iam-enforce-mfa -policy = data.aws_iam_policy_document.test_policy.json # tfsec:ignore:aws-iam-enforce-mfa + name = "test-policy" # tfsec:ignore:aws-iam-enforce-mfa + policy = data.aws_iam_policy_document.test_policy.json # tfsec:ignore:aws-iam-enforce-mfa } # tfsec:ignore:aws-iam-enforce-mfa resource "aws_iam_group_policy_attachment" "test_policy_attachment" { -group = data.aws_iam_group.externally_defined_group.group_name # tfsec:ignore:aws-iam-enforce-mfa -policy_arn = aws_iam_policy.test_policy.arn # tfsec:ignore:aws-iam-enforce-mfa + group = data.aws_iam_group.externally_defined_group.group_name # tfsec:ignore:aws-iam-enforce-mfa + policy_arn = aws_iam_policy.test_policy.arn # tfsec:ignore:aws-iam-enforce-mfa } # tfsec:ignore:aws-iam-enforce-mfa data "aws_iam_policy_document" "test_policy" { -statement { -sid = "PublishToCloudWatch" # tfsec:ignore:aws-iam-enforce-mfa -actions = [ -"cloudwatch:PutMetricData", # tfsec:ignore:aws-iam-enforce-mfa -] -resources = ["*"] # tfsec:ignore:aws-iam-enforce-mfa -} -} -`, assertLength: 0}, - {name: "TrivyIgnoreAll", inputOptions: ` + statement { + sid = "PublishToCloudWatch" # tfsec:ignore:aws-iam-enforce-mfa + actions = [ + "cloudwatch:PutMetricData", # tfsec:ignore:aws-iam-enforce-mfa + ] + resources = ["*"] # tfsec:ignore:aws-iam-enforce-mfa + } +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreAll", + inputOptions: ` resource "bad" "my-rule" { secure = false // trivy:ignore:* } -`, assertLength: 0}, - {name: "TrivyIgnoreLineAboveTheBlock", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlock", + inputOptions: ` // trivy:ignore:* resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreLineAboveTheBlockMatchingParamBool", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockMatchingParamBool", + inputOptions: ` // trivy:ignore:*[secure=false] resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamBool", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamBool", + inputOptions: ` // trivy:ignore:*[secure=true] resource "bad" "my-rule" { secure = false } -`, assertLength: 1}, - {name: "TrivyIgnoreLineAboveTheBlockMatchingParamString", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreLineAboveTheBlockMatchingParamString", + inputOptions: ` // trivy:ignore:*[name=myrule] resource "bad" "my-rule" { name = "myrule" secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamString", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamString", + inputOptions: ` // trivy:ignore:*[name=myrule2] resource "bad" "my-rule" { name = "myrule" secure = false } -`, assertLength: 1}, - {name: "TrivyIgnoreLineAboveTheBlockMatchingParamInt", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreLineAboveTheBlockMatchingParamInt", + inputOptions: ` // trivy:ignore:*[port=123] resource "bad" "my-rule" { secure = false port = 123 } -`, assertLength: 0}, - {name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamInt", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheBlockNotMatchingParamInt", + inputOptions: ` // trivy:ignore:*[port=456] resource "bad" "my-rule" { secure = false port = 123 } -`, assertLength: 1}, - {name: "TrivyIgnoreLineStackedAboveTheBlock", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "ignore by nested attribute", + inputOptions: ` +// trivy:ignore:*[secure_settings.enabled=false] +resource "bad" "my-rule" { + secure_settings { + enabled = false + } +} +`, + assertLength: 0, + }, + { + name: "ignore by nested attribute of another type", + inputOptions: ` +// trivy:ignore:*[secure_settings.enabled=1] +resource "bad" "my-rule" { + secure_settings { + enabled = false + } +} +`, + assertLength: 1, + }, + { + name: "ignore by non-existent nested attribute", + inputOptions: ` +// trivy:ignore:*[secure_settings.rule=myrule] +resource "bad" "my-rule" { + secure_settings { + enabled = false + } +} +`, + assertLength: 1, + }, + { + name: "ignore resource with `for_each` meta-argument", + inputOptions: ` +// trivy:ignore:*[secure=false] +resource "bad" "my-rule" { + for_each = toset(["false", "true", "false"]) + secure = each.key +} +`, + assertLength: 0, + }, + { + name: "ignore by dynamic block value", + inputOptions: ` +// trivy:ignore:*[secure_settings.enabled=false] +resource "bad" "my-rule" { + dynamic "secure_settings" { + for_each = ["false", "true"] + content { + enabled = secure_settings.value + } + } +} +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlock", + inputOptions: ` // trivy:ignore:* // trivy:ignore:a // trivy:ignore:b @@ -273,8 +451,12 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreLineStackedAboveTheBlockWithoutMatch", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlockWithoutMatch", + inputOptions: ` #trivy:ignore:* #trivy:ignore:x @@ -285,8 +467,12 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 1}, - {name: "TrivyIgnoreLineStackedAboveTheBlockWithHashesWithoutSpaces", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlockWithHashesWithoutSpaces", + inputOptions: ` #trivy:ignore:* #trivy:ignore:a #trivy:ignore:b @@ -295,8 +481,12 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreLineStackedAboveTheBlockWithoutSpaces", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineStackedAboveTheBlockWithoutSpaces", + inputOptions: ` //trivy:ignore:* //trivy:ignore:a //trivy:ignore:b @@ -305,81 +495,109 @@ resource "bad" "my-rule" { resource "bad" "my-rule" { secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreLineAboveTheLine", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreLineAboveTheLine", + inputOptions: ` resource "bad" "my-rule" { # trivy:ignore:aws-service-abc123 secure = false } -`, assertLength: 0}, - {name: "TrivyIgnoreWithExpDateIfDateBreachedThenDontIgnore", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreWithExpDateIfDateBreachedThenDontIgnore", + inputOptions: ` resource "bad" "my-rule" { secure = false # trivy:ignore:aws-service-abc123:exp:2000-01-02 } -`, assertLength: 1}, - {name: "TrivyIgnoreWithExpDateIfDateNotBreachedThenIgnoreIgnore", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` resource "bad" "my-rule" { secure = false # trivy:ignore:aws-service-abc123:exp:2221-01-02 } -`, assertLength: 0}, - {name: "TrivyIgnoreWithExpDateIfDateInvalidThenDropTheIgnore", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreWithExpDateIfDateInvalidThenDropTheIgnore", + inputOptions: ` resource "bad" "my-rule" { secure = false # trivy:ignore:aws-service-abc123:exp:2221-13-02 } -`, assertLength: 1}, - {name: "TrivyIgnoreAboveResourceBlockWithExpDateIfDateNotBreachedThenIgnoreIgnore", inputOptions: ` +`, + assertLength: 1, + }, + { + name: "TrivyIgnoreAboveResourceBlockWithExpDateIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` #trivy:ignore:aws-service-abc123:exp:2221-01-02 resource "bad" "my-rule" { } -`, assertLength: 0}, - {name: "TrivyIgnoreAboveResourceBlockWithExpDateAndMultipleIgnoresIfDateNotBreachedThenIgnoreIgnore", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreAboveResourceBlockWithExpDateAndMultipleIgnoresIfDateNotBreachedThenIgnoreIgnore", + inputOptions: ` # trivy:ignore:aws-service-abc123:exp:2221-01-02 resource "bad" "my-rule" { } -`, assertLength: 0}, - {name: "TrivyIgnoreForImpliedIAMResource", inputOptions: ` +`, + assertLength: 0, + }, + { + name: "TrivyIgnoreForImpliedIAMResource", + inputOptions: ` terraform { -required_version = "~> 1.1.6" + required_version = "~> 1.1.6" -required_providers { -aws = { -source = "hashicorp/aws" -version = "~> 3.48" -} -} + required_providers { + aws = { + source = "hashicorp/aws" + version = "~> 3.48" + } + } } # Retrieve an IAM group defined outside of this Terraform config. # trivy:ignore:aws-iam-enforce-mfa data "aws_iam_group" "externally_defined_group" { -group_name = "group-name" # trivy:ignore:aws-iam-enforce-mfa + group_name = "group-name" # trivy:ignore:aws-iam-enforce-mfa } # Create an IAM policy and attach it to the group. # trivy:ignore:aws-iam-enforce-mfa resource "aws_iam_policy" "test_policy" { -name = "test-policy" # trivy:ignore:aws-iam-enforce-mfa -policy = data.aws_iam_policy_document.test_policy.json # trivy:ignore:aws-iam-enforce-mfa + name = "test-policy" # trivy:ignore:aws-iam-enforce-mfa + policy = data.aws_iam_policy_document.test_policy.json # trivy:ignore:aws-iam-enforce-mfa } # trivy:ignore:aws-iam-enforce-mfa resource "aws_iam_group_policy_attachment" "test_policy_attachment" { -group = data.aws_iam_group.externally_defined_group.group_name # trivy:ignore:aws-iam-enforce-mfa -policy_arn = aws_iam_policy.test_policy.arn # trivy:ignore:aws-iam-enforce-mfa + group = data.aws_iam_group.externally_defined_group.group_name # trivy:ignore:aws-iam-enforce-mfa + policy_arn = aws_iam_policy.test_policy.arn # trivy:ignore:aws-iam-enforce-mfa } # trivy:ignore:aws-iam-enforce-mfa data "aws_iam_policy_document" "test_policy" { -statement { -sid = "PublishToCloudWatch" # trivy:ignore:aws-iam-enforce-mfa -actions = [ -"cloudwatch:PutMetricData", # trivy:ignore:aws-iam-enforce-mfa -] -resources = ["*"] # trivy:ignore:aws-iam-enforce-mfa -} + statement { + sid = "PublishToCloudWatch" # trivy:ignore:aws-iam-enforce-mfa + actions = [ + "cloudwatch:PutMetricData", # trivy:ignore:aws-iam-enforce-mfa + ] + resources = ["*"] # trivy:ignore:aws-iam-enforce-mfa + } } `, assertLength: 0}} diff --git a/pkg/iac/terraform/ignore.go b/pkg/iac/terraform/ignore.go index e52fbf202be5..69e0341ed4be 100644 --- a/pkg/iac/terraform/ignore.go +++ b/pkg/iac/terraform/ignore.go @@ -71,7 +71,7 @@ func (ignore Ignore) MatchParams(modules Modules, blockMetadata *iacTypes.Metada return true } for key, val := range ignore.Params { - attr := block.GetAttribute(key) + attr, _ := block.GetNestedAttribute(key) if attr.IsNil() || !attr.Value().IsKnown() { return false }