Repository: aquasecurity/tfsec
Pull request #916 (2021-07-21 11:36:39)

Add multiple Google Cloud SQL DB Instance rules


Patch:
diff --git a/internal/app/tfsec/rules/google/sql/encrypt_in_transit_data_rule.go b/internal/app/tfsec/rules/google/sql/encrypt_in_transit_data_rule.go
new file mode 100644
index 000000000..8f95ac996
--- /dev/null
+++ b/internal/app/tfsec/rules/google/sql/encrypt_in_transit_data_rule.go
@@ -0,0 +1,106 @@
+package sql
+
+import (
+	"fmt"
+
+	"github.com/aquasecurity/tfsec/pkg/result"
+	"github.com/aquasecurity/tfsec/pkg/severity"
+
+	"github.com/aquasecurity/tfsec/pkg/provider"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/hclcontext"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+
+	"github.com/aquasecurity/tfsec/pkg/rule"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+)
+
+func init() {
+	scanner.RegisterCheckRule(rule.Rule{
+		Service:   "sql",
+		ShortCode: "encrypt-in-transit-data",
+		Documentation: rule.RuleDocumentation{
+			Summary: "SSL connections to a SQL database instance should be enforced.",
+			Explanation: `	`,
+			Impact:     "Intercepted data can be read in transit",
+			Resolution: "Enforce SSL for all connections",
+			BadExample: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+			require_ssl = false
+		}
+	}
+}
+			`,
+			GoodExample: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+			require_ssl = true
+		}
+	}
+}
+			`,
+			Links: []string{"https://cloud.google.com/sql/docs/mysql/configure-ssl-instance"},
+		},
+		Provider:        provider.GoogleProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"google_sql_database_instance"},
+		DefaultSeverity: severity.High,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+
+			settingsBlock := resourceBlock.GetBlock("settings")
+			if settingsBlock == nil {
+				return
+			}
+
+			ipConfigBlock := settingsBlock.GetBlock("ip_configuration")
+			if ipConfigBlock == nil {
+				set.Add(
+					result.New(resourceBlock).
+						WithRange(settingsBlock.Range()).
+						WithDescription(fmt.Sprintf("Resource '%s' does not require SSL for all connections", resourceBlock.FullName())),
+				)
+				return
+			}
+
+			if requireSSLAttr := ipConfigBlock.GetAttribute("require_ssl"); requireSSLAttr == nil {
+				set.Add(
+					result.New(resourceBlock).
+						WithRange(ipConfigBlock.Range()).
+						WithDescription(fmt.Sprintf("Resource '%s' does not require SSL for all connections", resourceBlock.FullName())),
+				)
+			} else if requireSSLAttr.IsFalse() {
+				set.Add(
+					result.New(resourceBlock).
+						WithAttributeAnnotation(requireSSLAttr).
+						WithDescription(fmt.Sprintf("Resource '%s' explicitly does not require SSL for all connections", resourceBlock.FullName())),
+				)
+			}
+
+		},
+	})
+}
diff --git a/internal/app/tfsec/rules/google/sql/no_public_access_rule.go b/internal/app/tfsec/rules/google/sql/no_public_access_rule.go
new file mode 100644
index 000000000..fd51ccd40
--- /dev/null
+++ b/internal/app/tfsec/rules/google/sql/no_public_access_rule.go
@@ -0,0 +1,122 @@
+package sql
+
+import (
+	"fmt"
+
+	"github.com/aquasecurity/tfsec/pkg/result"
+	"github.com/aquasecurity/tfsec/pkg/severity"
+
+	"github.com/aquasecurity/tfsec/pkg/provider"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/cidr"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/hclcontext"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+
+	"github.com/aquasecurity/tfsec/pkg/rule"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+)
+
+func init() {
+	scanner.RegisterCheckRule(rule.Rule{
+		Service:   "sql",
+		ShortCode: "no-public-access",
+		Documentation: rule.RuleDocumentation{
+			Summary: "Ensure that Cloud SQL Database Instances are not publicly exposed",
+			Explanation: `	`,
+			Impact:     "Public exposure of sensitive data",
+			Resolution: "Remove public access from database instances",
+			BadExample: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+	
+			authorized_networks {
+				value           = "0.0.0.0/0"
+				name            = "internet"
+			}
+		}
+	}
+}
+			`,
+			GoodExample: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+		}
+	}
+}
+			`,
+			Links: []string{"https://www.cloudconformity.com/knowledge-base/gcp/CloudSQL/publicly-accessible-cloud-sql-instances.html#"},
+		},
+		Provider:        provider.GoogleProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"google_sql_database_instance"},
+		DefaultSeverity: severity.High,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+
+			// function contents here
+			settingsBlock := resourceBlock.GetBlock("settings")
+			if settingsBlock == nil {
+				return
+			}
+
+			ipConfigBlock := settingsBlock.GetBlock("ip_configuration")
+			if ipConfigBlock == nil {
+				set.Add(
+					result.New(resourceBlock).
+						WithRange(settingsBlock.Range()).
+						WithDescription(fmt.Sprintf("Resource '%s' has a public ipv4 address assigned by default", resourceBlock.FullName())),
+				)
+				return
+			}
+
+			if ipv4Attr := ipConfigBlock.GetAttribute("ipv4_enabled"); ipv4Attr == nil {
+				set.Add(
+					result.New(resourceBlock).
+						WithRange(ipConfigBlock.Range()).
+						WithDescription(fmt.Sprintf("Resource '%s' has a public ipv4 address assigned by default", resourceBlock.FullName())),
+				)
+			} else if ipv4Attr.IsTrue() {
+				set.Add(
+					result.New(ipConfigBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' has a public ipv4 address explicitly assigned", resourceBlock.FullName())).
+						WithAttributeAnnotation(ipv4Attr),
+				)
+				return
+			}
+
+			for _, authorizedNetworkBlock := range ipConfigBlock.GetBlocks("authorized_networks") {
+				if cidrAttr := authorizedNetworkBlock.GetAttribute("value"); cidrAttr != nil && cidrAttr.IsString() && cidr.IsOpen(cidrAttr) {
+					set.Add(
+						result.New(resourceBlock).
+							WithDescription(fmt.Sprintf("Resource '%s' authorizes access from the public internet", resourceBlock.FullName())).
+							WithAttributeAnnotation(cidrAttr),
+					)
+				}
+			}
+
+		},
+	})
+}
diff --git a/pkg/result/result.go b/pkg/result/result.go
index ce10f2463..8b699c33f 100644
--- a/pkg/result/result.go
+++ b/pkg/result/result.go
@@ -125,6 +125,8 @@ func (r *Result) WithAttributeAnnotation(attr block.Attribute) *Result {
 
 	typ := attr.Type()
 
+	r.WithRange(attr.Range())
+
 	switch typ {
 	case cty.String:
 		raw = fmt.Sprintf("%q", attr.Value().AsString())


Test patch:
diff --git a/internal/app/tfsec/rules/google/sql/encrypt_in_transit_data_rule_test.go b/internal/app/tfsec/rules/google/sql/encrypt_in_transit_data_rule_test.go
new file mode 100644
index 000000000..0ed2287af
--- /dev/null
+++ b/internal/app/tfsec/rules/google/sql/encrypt_in_transit_data_rule_test.go
@@ -0,0 +1,73 @@
+package sql
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_GoogleEncryptInTransitData(t *testing.T) {
+	expectedCode := "google-sql-encrypt-in-transit-data"
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode string
+		mustExcludeResultCode string
+	}{
+		{
+			name: "Rule matches when ssl is not required",
+			source: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+			require_ssl = false
+		}
+	}
+}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "Rule does not match when ssl is required",
+			source: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+			require_ssl = true
+		}
+	}
+}
+`,
+			mustExcludeResultCode: expectedCode,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+
+			results := testutil.ScanHCL(test.source, t)
+			testutil.AssertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+}
diff --git a/internal/app/tfsec/rules/google/sql/no_public_access_rule_test.go b/internal/app/tfsec/rules/google/sql/no_public_access_rule_test.go
new file mode 100644
index 000000000..138df4077
--- /dev/null
+++ b/internal/app/tfsec/rules/google/sql/no_public_access_rule_test.go
@@ -0,0 +1,111 @@
+package sql
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_GoogleNoPublicAccess(t *testing.T) {
+	expectedCode := "google-sql-no-public-access"
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode string
+		mustExcludeResultCode string
+	}{
+		{
+			name: "check detects issue with public ipv4 address being assigned",
+			source: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = true
+		}
+	}
+	}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "check detects issue with public ipv4 address being assigned by default",
+			source: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+		}
+	}
+	}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "check detects issue with authorized network being internet",
+			source: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+	
+			authorized_networks {
+				value           = "0.0.0.0/0"
+				name            = "internet"
+			}
+		}
+	}
+}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "check detects no issue with authorized network being internal",
+			source: `
+resource "google_sql_database_instance" "postgres" {
+	name             = "postgres-instance-a"
+	database_version = "POSTGRES_11"
+	
+	settings {
+		tier = "db-f1-micro"
+	
+		ip_configuration {
+			ipv4_enabled = false
+			authorized_networks {
+				value           = "108.12.12.0/24"
+				name            = "internal"
+			}
+		}
+	}
+}
+`,
+			mustExcludeResultCode: expectedCode,
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.name, func(t *testing.T) {
+
+			results := testutil.ScanHCL(test.source, t)
+			testutil.AssertCheckCode(t, test.mustIncludeResultCode, test.mustExcludeResultCode, results)
+		})
+	}
+}