Repository: tfsec/tfsec
Pull request #732 (2021-05-12 21:04:24)

Add some more checks


Patch:
diff --git a/internal/app/tfsec/checks/aws078.go b/internal/app/tfsec/checks/aws078.go
index aa3b21e9b..3a031e62c 100644
--- a/internal/app/tfsec/checks/aws078.go
+++ b/internal/app/tfsec/checks/aws078.go
@@ -7,16 +7,16 @@ import (
 	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
 )
 
-const AWSEnsureEcrImagesHaveImmutableTags scanner.RuleCode = "AWS078"
-const AWSEnsureEcrImagesHaveImmutableTagsDescription scanner.RuleSummary = "ECR images tags shouldn't be mutable."
-const AWSEnsureEcrImagesHaveImmutableTagsImpact = "Image tags could be overwritten with compromised images"
-const AWSEnsureEcrImagesHaveImmutableTagsResolution = "Only use immutable images in ECR"
-const AWSEnsureEcrImagesHaveImmutableTagsExplanation = `
+const AWSEcrImagesHaveImmutableTags scanner.RuleCode = "AWS078"
+const AWSEcrImagesHaveImmutableTagsDescription scanner.RuleSummary = "ECR images tags shouldn't be mutable."
+const AWSEcrImagesHaveImmutableTagsImpact = "Image tags could be overwritten with compromised images"
+const AWSEcrImagesHaveImmutableTagsResolution = "Only use immutable images in ECR"
+const AWSEcrImagesHaveImmutableTagsExplanation = `
 ECR images should be set to IMMUTABLE to prevent code injection through image mutation.
 
 This can be done by setting <code>image_tab_mutability</code> to <code>IMMUTABLE</code>
 `
-const AWSEnsureEcrImagesHaveImmutableTagsBadExample = `
+const AWSEcrImagesHaveImmutableTagsBadExample = `
 resource "aws_ecr_repository" "bad_example" {
   name                 = "bar"
   image_tag_mutability = "MUTABLE"
@@ -26,7 +26,7 @@ resource "aws_ecr_repository" "bad_example" {
   }
 }
 `
-const AWSEnsureEcrImagesHaveImmutableTagsGoodExample = `
+const AWSEcrImagesHaveImmutableTagsGoodExample = `
 resource "aws_ecr_repository" "good_example" {
   name                 = "bar"
   image_tag_mutability = "IMMUTABLE"
@@ -39,14 +39,14 @@ resource "aws_ecr_repository" "good_example" {
 
 func init() {
 	scanner.RegisterCheck(scanner.Check{
-		Code: AWSEnsureEcrImagesHaveImmutableTags,
+		Code: AWSEcrImagesHaveImmutableTags,
 		Documentation: scanner.CheckDocumentation{
-			Summary:     AWSEnsureEcrImagesHaveImmutableTagsDescription,
-			Impact:      AWSEnsureEcrImagesHaveImmutableTagsImpact,
-			Resolution:  AWSEnsureEcrImagesHaveImmutableTagsResolution,
-			Explanation: AWSEnsureEcrImagesHaveImmutableTagsExplanation,
-			BadExample:  AWSEnsureEcrImagesHaveImmutableTagsBadExample,
-			GoodExample: AWSEnsureEcrImagesHaveImmutableTagsGoodExample,
+			Summary:     AWSEcrImagesHaveImmutableTagsDescription,
+			Impact:      AWSEcrImagesHaveImmutableTagsImpact,
+			Resolution:  AWSEcrImagesHaveImmutableTagsResolution,
+			Explanation: AWSEcrImagesHaveImmutableTagsExplanation,
+			BadExample:  AWSEcrImagesHaveImmutableTagsBadExample,
+			GoodExample: AWSEcrImagesHaveImmutableTagsGoodExample,
 			Links: []string{
 				"https://sysdig.com/blog/toctou-tag-mutability/",
 				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecr_repository",
diff --git a/internal/app/tfsec/checks/aws084.go b/internal/app/tfsec/checks/aws084.go
index ea7051133..50ee3c788 100644
--- a/internal/app/tfsec/checks/aws084.go
+++ b/internal/app/tfsec/checks/aws084.go
@@ -68,51 +68,47 @@ func init() {
 		RequiredLabels: []string{"aws_workspaces_workspace"},
 		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
 
+			var results []scanner.Result
+
 			if block.MissingChild("root_volume_encryption_enabled") {
-				return []scanner.Result{
+				results = append(results,
 					check.NewResult(
 						fmt.Sprintf("Resource '%s' should have root volume encryption enables", block.FullName()),
 						block.Range(),
 						scanner.SeverityError,
-					),
-				}
+					))
 			} else {
 				attr := block.GetAttribute("root_volume_encryption_enabled")
 				if attr.IsFalse() {
-					return []scanner.Result{
-						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' has the root volumnet encyption set to false", block.FullName()),
-							attr.Range(),
-							attr,
-							scanner.SeverityError,
-						),
-					}
+					results = append(results, check.NewResultWithValueAnnotation(
+						fmt.Sprintf("Resource '%s' has the root volume encyption set to false", block.FullName()),
+						attr.Range(),
+						attr,
+						scanner.SeverityError,
+					))
 				}
 			}
 
 			if block.MissingChild("user_volume_encryption_enabled") {
-				return []scanner.Result{
-					check.NewResult(
-						fmt.Sprintf("Resource '%s' should have user volume encryption enables", block.FullName()),
-						block.Range(),
-						scanner.SeverityError,
-					),
-				}
+				results = append(results, check.NewResult(
+					fmt.Sprintf("Resource '%s' should have user volume encryption enables", block.FullName()),
+					block.Range(),
+					scanner.SeverityError,
+				))
 			} else {
 				attr := block.GetAttribute("user_volume_encryption_enabled")
 				if attr.IsFalse() {
-					return []scanner.Result{
+					results = append(results,
 						check.NewResultWithValueAnnotation(
-							fmt.Sprintf("Resource '%s' has the user volumnet encyption set to false", block.FullName()),
+							fmt.Sprintf("Resource '%s' has the user volume encyption set to false", block.FullName()),
 							attr.Range(),
 							attr,
 							scanner.SeverityError,
-						),
-					}
+						))
 				}
 			}
 
-			return nil
+			return results
 		},
 	})
 }
diff --git a/internal/app/tfsec/checks/aws089.go b/internal/app/tfsec/checks/aws089.go
new file mode 100644
index 000000000..77546ff0b
--- /dev/null
+++ b/internal/app/tfsec/checks/aws089.go
@@ -0,0 +1,63 @@
+package checks
+
+import (
+	"fmt"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+const AWSCloudWatchLogGroupsCMKEncrypted scanner.RuleCode = "AWS089"
+const AWSCloudWatchLogGroupsCMKEncryptedDescription scanner.RuleSummary = "CloudWatch log groups should be encrypted"
+const AWSCloudWatchLogGroupsCMKEncryptedImpact = "Log data may be leaked if the logs are compromised"
+const AWSCloudWatchLogGroupsCMKEncryptedResolution = "Enable CMK encryption of CloudWatch Log Groups"
+const AWSCloudWatchLogGroupsCMKEncryptedExplanation = `
+CloudWatch log groups are encrypted by default, however, to get the full benefit of controlling key rotation and other KMS aspects a KMS CMK should be used.
+`
+const AWSCloudWatchLogGroupsCMKEncryptedBadExample = `
+resource "aws_cloudwatch_log_group" "bad_exampe" {
+	name = "bad_example"
+
+}
+`
+const AWSCloudWatchLogGroupsCMKEncryptedGoodExample = `
+resource "aws_cloudwatch_log_group" "good_example" {
+	name = "good_example"
+
+	kms_key_id = aws_kms_key.log_key.id
+}
+`
+
+func init() {
+	scanner.RegisterCheck(scanner.Check{
+		Code: AWSCloudWatchLogGroupsCMKEncrypted,
+		Documentation: scanner.CheckDocumentation{
+			Summary:     AWSCloudWatchLogGroupsCMKEncryptedDescription,
+			Explanation: AWSCloudWatchLogGroupsCMKEncryptedExplanation,
+			Impact:      AWSCloudWatchLogGroupsCMKEncryptedImpact,
+			Resolution:  AWSCloudWatchLogGroupsCMKEncryptedResolution,
+			BadExample:  AWSCloudWatchLogGroupsCMKEncryptedBadExample,
+			GoodExample: AWSCloudWatchLogGroupsCMKEncryptedGoodExample,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/cloudwatch_log_group#kms_key_id",
+				"https://docs.aws.amazon.com/AmazonCloudWatch/latest/logs/encrypt-log-data-kms.html",
+			},
+		},
+		Provider:       scanner.AWSProvider,
+		RequiredTypes:  []string{"resource"},
+		RequiredLabels: []string{"aws_cloudwatch_log_group"},
+		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+			if block.MissingChild("kms_key_id") {
+				return []scanner.Result{
+					check.NewResult(
+						fmt.Sprintf("Resource '%s' is only using default encryption", block.FullName()),
+						block.Range(),
+						scanner.SeverityWarning,
+					),
+				}
+			}
+
+			return nil
+		},
+	})
+}
diff --git a/internal/app/tfsec/checks/aws090.go b/internal/app/tfsec/checks/aws090.go
new file mode 100644
index 000000000..1d029ab9e
--- /dev/null
+++ b/internal/app/tfsec/checks/aws090.go
@@ -0,0 +1,79 @@
+package checks
+
+import (
+	"fmt"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+const AWSECSClusterContainerInsights scanner.RuleCode = "AWS090"
+const AWSECSClusterContainerInsightsDescription scanner.RuleSummary = "ECS clusters should have container insights enabled"
+const AWSECSClusterContainerInsightsImpact = "Not all metrics and logs may be gathered for containers when Container Insights isn't enabled"
+const AWSECSClusterContainerInsightsResolution = "Enable Container Insights"
+const AWSECSClusterContainerInsightsExplanation = `
+Cloudwatch Container Insights provide more metrics and logs for container based applications and micro services.
+`
+const AWSECSClusterContainerInsightsBadExample = `
+resource "aws_ecs_cluster" "bad_example" {
+  	name = "services-cluster"
+}
+`
+const AWSECSClusterContainerInsightsGoodExample = `
+resource "aws_ecs_cluster" "good_example" {
+	name = "services-cluster"
+  
+	setting {
+	  name  = "containerInsights"
+	  value = "enabled"
+	}
+}
+`
+
+func init() {
+	scanner.RegisterCheck(scanner.Check{
+		Code: AWSECSClusterContainerInsights,
+		Documentation: scanner.CheckDocumentation{
+			Summary:     AWSECSClusterContainerInsightsDescription,
+			Explanation: AWSECSClusterContainerInsightsExplanation,
+			Impact:      AWSECSClusterContainerInsightsImpact,
+			Resolution:  AWSECSClusterContainerInsightsResolution,
+			BadExample:  AWSECSClusterContainerInsightsBadExample,
+			GoodExample: AWSECSClusterContainerInsightsGoodExample,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/ecs_cluster#setting",
+				"https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/ContainerInsights.html",
+			},
+		},
+		Provider:       scanner.AWSProvider,
+		RequiredTypes:  []string{"resource"},
+		RequiredLabels: []string{"aws_ecs_cluster"},
+		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+
+			settings := block.GetBlocks("setting")
+			for _, setting := range settings {
+				if name := setting.GetAttribute("name"); name.Equals("containerinsights", parser.IgnoreCase) {
+					if setting.GetAttribute("value").Equals("enabled", parser.IgnoreCase) {
+						return nil
+					} else {
+						return []scanner.Result{
+							check.NewResultWithValueAnnotation(
+								fmt.Sprintf("Resource '%s' has containerInsights set to disabled", block.FullName()),
+								setting.Range(),
+								setting.GetAttribute("value"),
+								scanner.SeverityWarning,
+							),
+						}
+					}
+				}
+			}
+			return []scanner.Result{
+				check.NewResult(
+					fmt.Sprintf("Resoure '%s' does not have codeInsights enabled", block.FullName()),
+					block.Range(),
+					scanner.SeverityWarning,
+				),
+			}
+		},
+	})
+}
diff --git a/internal/app/tfsec/parser/attribute.go b/internal/app/tfsec/parser/attribute.go
index b3873863f..ab62b5d44 100644
--- a/internal/app/tfsec/parser/attribute.go
+++ b/internal/app/tfsec/parser/attribute.go
@@ -170,8 +170,9 @@ func (attr *Attribute) RegexMatches(pattern interface{}) bool {
 
 func (attr *Attribute) IsAny(options ...interface{}) bool {
 	if attr.Value().Type() == cty.String {
+		value := attr.Value().AsString()
 		for _, option := range options {
-			if option == attr.Value().AsString() {
+			if option == value {
 				return true
 			}
 		}


Test patch:
diff --git a/internal/app/tfsec/test/aws078_test.go b/internal/app/tfsec/test/aws078_test.go
index 62f09f68d..06524abc9 100644
--- a/internal/app/tfsec/test/aws078_test.go
+++ b/internal/app/tfsec/test/aws078_test.go
@@ -7,7 +7,7 @@ import (
 	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
 )
 
-func Test_AWSEnsureEcrImagesHaveImmutableTags(t *testing.T) {
+func Test_AWSEcrImagesHaveImmutableTags(t *testing.T) {
 
 	var tests = []struct {
 		name                  string
@@ -26,7 +26,7 @@ resource "aws_ecr_repository" "foo" {
   }
 }
 `,
-			mustIncludeResultCode: checks.AWSEnsureEcrImagesHaveImmutableTags,
+			mustIncludeResultCode: checks.AWSEcrImagesHaveImmutableTags,
 		},
 		{
 			name: "should fire when image_tab_mutability not set to IMMUTABLE",
@@ -40,7 +40,7 @@ resource "aws_ecr_repository" "foo" {
   }
 }
 `,
-			mustIncludeResultCode: checks.AWSEnsureEcrImagesHaveImmutableTags,
+			mustIncludeResultCode: checks.AWSEcrImagesHaveImmutableTags,
 		},
 		{
 			name: "should not fire when image_tab_mutability set to IMMUTABLE",
@@ -54,7 +54,7 @@ resource "aws_ecr_repository" "foo" {
   }
 }
 `,
-			mustExcludeResultCode: checks.AWSEnsureEcrImagesHaveImmutableTags,
+			mustExcludeResultCode: checks.AWSEcrImagesHaveImmutableTags,
 		},
 	}
 
diff --git a/internal/app/tfsec/test/aws089_test.go b/internal/app/tfsec/test/aws089_test.go
new file mode 100644
index 000000000..9bd11c23f
--- /dev/null
+++ b/internal/app/tfsec/test/aws089_test.go
@@ -0,0 +1,47 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/checks"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+func Test_AWSCloudWatchLogGroupsCMKEncrypted(t *testing.T) {
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode scanner.RuleCode
+		mustExcludeResultCode scanner.RuleCode
+	}{
+		{
+			name: "cloudwatch without cmk fails check",
+			source: `
+resource "aws_cloudwatch_log_group" "bad_exampe" {
+	name = "bad_example"
+}
+`,
+			mustIncludeResultCode: checks.AWSCloudWatchLogGroupsCMKEncrypted,
+		},
+		{
+			name: "cloudwatch with cmk passes check",
+			source: `
+resource "aws_cloudwatch_log_group" "good_example" {
+	name = "good_example"
+
+	kms_key_id = aws_kms_key.log_key.id
+}
+`,
+			mustExcludeResultCode: checks.AWSCloudWatchLogGroupsCMKEncrypted,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			results := scanSource(test.source)
+			assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+
+}
diff --git a/internal/app/tfsec/test/aws090_test.go b/internal/app/tfsec/test/aws090_test.go
new file mode 100644
index 000000000..b6b0bf08f
--- /dev/null
+++ b/internal/app/tfsec/test/aws090_test.go
@@ -0,0 +1,78 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/checks"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+func Test_AWSECSClusterContainerInsights(t *testing.T) {
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode scanner.RuleCode
+		mustExcludeResultCode scanner.RuleCode
+	}{
+		{
+			name: "ECS cluster without container insights fails check",
+			source: `
+resource "aws_ecs_cluster" "bad_example" {
+  	name = "services-cluster"
+}
+`,
+			mustIncludeResultCode: checks.AWSECSClusterContainerInsights,
+		},
+		{
+			name: "ECS cluster with container insights configured but disabled fails check",
+			source: `
+resource "aws_ecs_cluster" "bad_example" {
+  	name = "services-cluster"
+
+	setting {
+		name  = "containerInsights"
+		value = "disabled"
+	}
+}
+`,
+			mustIncludeResultCode: checks.AWSECSClusterContainerInsights,
+		},
+		{
+			name: "ECS cluster with settings but no container insights fails check",
+			source: `
+resource "aws_ecs_cluster" "bad_example" {
+  	name = "services-cluster"
+  
+	  setting {
+		name  = "NotContainerInsights"
+		value = "enabled"
+	  }
+}
+`,
+			mustIncludeResultCode: checks.AWSECSClusterContainerInsights,
+		},
+		{
+			name: "ECS cluster with container insights enabled passess check",
+			source: `
+resource "aws_ecs_cluster" "good_example" {
+	name = "services-cluster"
+  
+	setting {
+	  name  = "containerInsights"
+	  value = "enabled"
+	}
+}
+`,
+			mustExcludeResultCode: checks.AWSECSClusterContainerInsights,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			results := scanSource(test.source)
+			assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+
+}