Repository: tfsec/tfsec
Pull request #492 (2020-10-31 16:06:27)

RDS Encryption checks
- check encryption on 
  - `aws_rds_cluster` and 
  - `aws_db_instance`



Patch:
diff --git a/docs-website/_docs/pr_commenter.md b/docs-website/_docs/pr_commenter.md
index eb7367a78..60bd5b003 100644
--- a/docs-website/_docs/pr_commenter.md
+++ b/docs-website/_docs/pr_commenter.md
@@ -40,7 +40,6 @@ On each pull request and subsequent commit, tfsec will run and add comments to t
 
 The comment will only be added once per transgression.
 
-
 ## Example PR Comment
 
 The screenshot below demonstrates the comments that can be expected when using the action
diff --git a/internal/app/tfsec/checks/aws051.go b/internal/app/tfsec/checks/aws051.go
new file mode 100644
index 000000000..cff849db5
--- /dev/null
+++ b/internal/app/tfsec/checks/aws051.go
@@ -0,0 +1,80 @@
+package checks
+
+import (
+	"fmt"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+const AWSRDSAuroraClusterEncryptionDisabled scanner.RuleCode = "AWS051"
+const AWSRDSAuroraClusterEncryptionDisabledDescription scanner.RuleSummary = "There is no encryption specified or encryption is disabled on the RDS Cluster."
+const AWSRDSAuroraClusterEncryptionDisabledExplanation = `
+Encryption should be enabled for an RDS Aurora cluster. 
+
+When enabling encryption by setting the kms_key_id, the storage_encrypted must also be set to true. 
+`
+const AWSRDSAuroraClusterEncryptionDisabledBadExample = `
+resource "aws_rds_cluster" "foo" {
+  name       = "bar"
+  kms_key_id = ""
+}`
+const AWSRDSAuroraClusterEncryptionDisabledGoodExample = `
+resource "aws_rds_cluster" "foo" {
+  name              = "bar"
+  kms_key_id  = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
+  storage_encrypted = true
+}`
+
+func init() {
+	scanner.RegisterCheck(scanner.Check{
+		Code: AWSRDSAuroraClusterEncryptionDisabled,
+		Documentation: scanner.CheckDocumentation{
+			Summary:     AWSRDSAuroraClusterEncryptionDisabledDescription,
+			Explanation: AWSRDSAuroraClusterEncryptionDisabledExplanation,
+			BadExample:  AWSRDSAuroraClusterEncryptionDisabledBadExample,
+			GoodExample: AWSRDSAuroraClusterEncryptionDisabledGoodExample,
+			Links: []string{
+				"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html",
+				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/rds_cluster",
+			},
+		},
+		Provider:       scanner.AWSProvider,
+		RequiredTypes:  []string{"resource"},
+		RequiredLabels: []string{"aws_rds_cluster"},
+		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+
+			kmsKeyIdAttr := block.GetAttribute("kms_key_id")
+			storageEncryptedattr := block.GetAttribute("storage_encrypted")
+
+			if kmsKeyIdAttr == nil {
+				return []scanner.Result{
+					check.NewResult(
+						fmt.Sprintf("Resource '%s' defines a disabled RDS Cluster encryption.", block.FullName()),
+						block.Range(),
+						scanner.SeverityError,
+					),
+				}
+			} else if kmsKeyIdAttr.Equals("") {
+				return []scanner.Result{
+					check.NewResultWithValueAnnotation(
+						fmt.Sprintf("Resource '%s' defines a disabled RDS Cluster encryption.", block.FullName()),
+						kmsKeyIdAttr.Range(),
+						kmsKeyIdAttr,
+						scanner.SeverityError,
+					),
+				}
+			} else if storageEncryptedattr == nil || storageEncryptedattr.IsFalse() {
+				return []scanner.Result{
+					check.NewResultWithValueAnnotation(
+						fmt.Sprintf("Resource '%s' defines a enabled RDS Cluster encryption but not the required encrypted_storage.", block.FullName()),
+						kmsKeyIdAttr.Range(),
+						kmsKeyIdAttr,
+						scanner.SeverityError,
+					),
+				}
+			}
+			return nil
+		},
+	})
+}
diff --git a/internal/app/tfsec/checks/aws052.go b/internal/app/tfsec/checks/aws052.go
new file mode 100644
index 000000000..c9e31b5c8
--- /dev/null
+++ b/internal/app/tfsec/checks/aws052.go
@@ -0,0 +1,69 @@
+package checks
+
+import (
+	"fmt"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/parser"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+const AWSRDSEncryptionNotEnabled scanner.RuleCode = "AWS052"
+const AWSRDSEncryptionNotEnabledDescription scanner.RuleSummary = "RDS encryption has not been enabled at a DB Instance level."
+const AWSRDSEncryptionNotEnabledExplanation = `
+Encryption should be enabled for an RDS Database instances. 
+
+When enabling encryption by setting the kms_key_id. 
+`
+const AWSRDSEncryptionNotEnabledBadExample = `
+resource "aws_db_instance" "my-db-instance" {
+	
+}
+`
+const AWSRDSEncryptionNotEnabledGoodExample = `
+resource "aws_db_instance" "my-db-instance" {
+	kms_key_id  = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
+}
+`
+
+func init() {
+	scanner.RegisterCheck(scanner.Check{
+		Code: AWSRDSEncryptionNotEnabled,
+		Documentation: scanner.CheckDocumentation{
+			Summary:     AWSRDSEncryptionNotEnabledDescription,
+			Explanation: AWSRDSEncryptionNotEnabledExplanation,
+			BadExample:  AWSRDSEncryptionNotEnabledBadExample,
+			GoodExample: AWSRDSEncryptionNotEnabledGoodExample,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/db_instance",
+				"https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/Overview.Encryption.html",
+			},
+		},
+		Provider:       scanner.AWSProvider,
+		RequiredTypes:  []string{"resource"},
+		RequiredLabels: []string{"aws_db_instance"},
+		CheckFunc: func(check *scanner.Check, block *parser.Block, _ *scanner.Context) []scanner.Result {
+
+			kmsKeyIdAttr := block.GetAttribute("kms_key_id")
+
+			if kmsKeyIdAttr == nil {
+				return []scanner.Result{
+					check.NewResult(
+						fmt.Sprintf("Resource '%s' defines a disabled RDS Instance Encryption.", block.FullName()),
+						block.Range(),
+						scanner.SeverityError,
+					),
+				}
+			} else if kmsKeyIdAttr.Equals("") {
+				return []scanner.Result{
+					check.NewResultWithValueAnnotation(
+						fmt.Sprintf("Resource '%s' defines a disabled RDS Instance Encryption.", block.FullName()),
+						kmsKeyIdAttr.Range(),
+						kmsKeyIdAttr,
+						scanner.SeverityError,
+					),
+				}
+			}
+			return nil
+		},
+	})
+}
diff --git a/internal/app/tfsec/parser/attribute.go b/internal/app/tfsec/parser/attribute.go
index fb779295e..f73cac36c 100644
--- a/internal/app/tfsec/parser/attribute.go
+++ b/internal/app/tfsec/parser/attribute.go
@@ -173,3 +173,11 @@ func (attr *Attribute) IsNone(options ...interface{}) bool {
 
 	return true
 }
+
+func (attr *Attribute) IsTrue() bool {
+	return attr.Value().Type() == cty.Bool && attr.Value().True()
+}
+
+func (attr *Attribute) IsFalse() bool {
+	return attr.Value().Type() == cty.Bool && attr.Value().False()
+}


Test patch:
diff --git a/internal/app/tfsec/test/aws051_test.go b/internal/app/tfsec/test/aws051_test.go
new file mode 100644
index 000000000..3bc43df94
--- /dev/null
+++ b/internal/app/tfsec/test/aws051_test.go
@@ -0,0 +1,66 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/checks"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+func Test_AWSRDSAuroraClusterEncryptionDisabled(t *testing.T) {
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode scanner.RuleCode
+		mustExcludeResultCode scanner.RuleCode
+	}{
+		{
+			name: "check rds is not specified",
+			source: `
+resource "aws_rds_cluster" "my-instance" {
+	name = "cluster-1"
+}
+`,
+			mustIncludeResultCode: checks.AWSRDSAuroraClusterEncryptionDisabled,
+		},
+		{
+			name: "check rds kms is specified but false",
+			source: `
+resource "aws_rds_cluster" "my-instance" {
+	name       = "cluster-1"
+	kms_key_id  = ""
+}
+`,
+			mustIncludeResultCode: checks.AWSRDSAuroraClusterEncryptionDisabled,
+		},
+		{
+			name: "check rds kms is specified but not storage_encrypted",
+			source: `
+resource "aws_rds_cluster" "my-instance" {
+	name       = "cluster-1"
+	kms_key_id  = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
+}
+`,
+			mustIncludeResultCode: checks.AWSRDSAuroraClusterEncryptionDisabled,
+		},
+		{
+			name: "check rds encryption is enabled correctly",
+			source: `
+resource "aws_rds_cluster" "my-instance" {
+	name              = "cluster-1"
+	kms_key_id  = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
+	storage_encrypted = true
+}
+`,
+			mustExcludeResultCode: checks.AWSRDSAuroraClusterEncryptionDisabled,
+		},
+	}
+
+	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/aws052_test.go b/internal/app/tfsec/test/aws052_test.go
new file mode 100644
index 000000000..e3ab1ba98
--- /dev/null
+++ b/internal/app/tfsec/test/aws052_test.go
@@ -0,0 +1,54 @@
+package test
+
+import (
+	"testing"
+
+	"github.com/tfsec/tfsec/internal/app/tfsec/checks"
+	"github.com/tfsec/tfsec/internal/app/tfsec/scanner"
+)
+
+func Test_AWSRDSEncryptionNotEnabled(t *testing.T) {
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode scanner.RuleCode
+		mustExcludeResultCode scanner.RuleCode
+	}{
+		{
+			name: "Encryption not enabled on db instance",
+			source: `
+resource "aws_db_instance" "my-db-instance" {
+	
+}
+`,
+			mustIncludeResultCode: checks.AWSRDSEncryptionNotEnabled,
+		},
+		{
+			name: "Encryption not enabled on db instance",
+			source: `
+resource "aws_db_instance" "my-db-instance" {
+	kms_key_id = ""
+}
+`,
+			mustIncludeResultCode: checks.AWSRDSEncryptionNotEnabled,
+		},
+		{
+			name: "Encryption enabled on db instance",
+			source: `
+resource "aws_db_instance" "my-db-instance" {
+	kms_key_id  = "arn:aws:kms:us-west-2:111122223333:key/1234abcd-12ab-34cd-56ef-1234567890ab"
+}
+`,
+			mustExcludeResultCode: checks.AWSRDSEncryptionNotEnabled,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+			results := scanSource(test.source)
+			assertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+
+}