Repository: aquasecurity/tfsec
Pull request #946 (2021-07-23 18:26:58)

Add checks for azure databases
- ssl enforcement
- no public access
- minimum tls



Patch:
diff --git a/cmd/tfsec-docs/webpage.go b/cmd/tfsec-docs/webpage.go
index 99c21838e..254390396 100644
--- a/cmd/tfsec-docs/webpage.go
+++ b/cmd/tfsec-docs/webpage.go
@@ -93,6 +93,8 @@ func formatProviderName(providerName string) string {
 	switch providerName {
 	case "aws":
 		return strings.ToUpper(providerName)
+	case "openstack":
+		return "OpenStack"
 	default:
 		return strings.Title(strings.ToLower(providerName))
 	}
diff --git a/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule.go b/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule.go
new file mode 100644
index 000000000..b17e7901f
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule.go
@@ -0,0 +1,78 @@
+package database
+
+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:   "database",
+		ShortCode: "enable-ssl-enforcement",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "SSL should be enforced on database connections where applicable",
+			Explanation: `SSL connections should be enforced were available to ensure secure transfer and reduce the risk of compromising data in flight.`,
+			Impact:      "Insecure connections could lead to data loss and other vulnerabilities",
+			Resolution:  "Enable SSL enforcement",
+			BadExample: `
+resource "azurerm_postgresql_server" "bad_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = false
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			GoodExample: `
+resource "azurerm_postgresql_server" "good_example" {
+  name                = "good_example"
+
+  public_network_access_enabled    = false
+  ssl_enforcement_enabled          = true
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_server#ssl_enforcement_enabled",
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mysql_server#ssl_enforcement_enabled",
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mariadb_server#ssl_enforcement_enabled",
+			},
+		},
+		Provider:        provider.AzureProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"azurerm_mariadb_server", "azurerm_mysql_server", "azurerm_postgresql_server"},
+		DefaultSeverity: severity.Medium,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+
+			if resourceBlock.MissingChild("ssl_enforcement_enabled") {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' is missing the required ssl_enforcement_enabled attribute", resourceBlock.FullName())),
+				)
+				return
+			}
+
+			sslEnforceAttr := resourceBlock.GetAttribute("ssl_enforcement_enabled")
+			if sslEnforceAttr.IsFalse() {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' has ssl_enforcement_enabled disabled", resourceBlock.FullName())).
+						WithRange(sslEnforceAttr.Range()).
+						WithAttributeAnnotation(sslEnforceAttr),
+				)
+			}
+		},
+	})
+}
diff --git a/internal/app/tfsec/rules/azure/database/no_public_access_rule.go b/internal/app/tfsec/rules/azure/database/no_public_access_rule.go
new file mode 100644
index 000000000..98228f7ef
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/database/no_public_access_rule.go
@@ -0,0 +1,78 @@
+package database
+
+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:   "database",
+		ShortCode: "no-public-access",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "Ensure databases are not publicly accessible",
+			Explanation: `Database resources should not publicly available. You should limit all access to the minimum that is required for your application to function.`,
+			Impact:      "Publicly accessible database could lead to compromised data",
+			Resolution:  "Disable public access to database when not required",
+			BadExample: `
+resource "azurerm_postgresql_server" "bad_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = true
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			GoodExample: `
+resource "azurerm_postgresql_server" "good_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = false
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_server#public_network_access_enabled",
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mysql_server#public_network_access_enabled",
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mariadb_server#public_network_access_enabled",
+			},
+		},
+		Provider:        provider.AzureProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"azurerm_mariadb_server", "azurerm_mssql_server", "azurerm_mysql_server", "azurerm_postgresql_server"},
+		DefaultSeverity: severity.Medium,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+
+			if resourceBlock.MissingChild("public_network_access_enabled") {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' has default public network access of enabled", resourceBlock.FullName())),
+				)
+				return
+			}
+
+			publicAccessAttr := resourceBlock.GetAttribute("public_network_access_enabled")
+			if publicAccessAttr.IsTrue() {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' has public access explicitly enabled", resourceBlock.FullName())).
+						WithRange(publicAccessAttr.Range()).
+						WithAttributeAnnotation(publicAccessAttr),
+				)
+			}
+		},
+	})
+}
diff --git a/internal/app/tfsec/rules/azure/database/secure_tls_policy_rule.go b/internal/app/tfsec/rules/azure/database/secure_tls_policy_rule.go
new file mode 100644
index 000000000..5f3bdf9aa
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/database/secure_tls_policy_rule.go
@@ -0,0 +1,114 @@
+package database
+
+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:   "database",
+		ShortCode: "secure-tls-policy",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "Databases should have the minimum TLS set for connections",
+			Explanation: `You should not use outdated/insecure TLS versions for encryption. You should be using TLS v1.2+.`,
+			Impact:      "Outdated TLS policies increase exposure to known issues",
+			Resolution:  "Use the most modern TLS policies available",
+			BadExample: `
+resource "azurerm_mssql_server" "bad_example" {
+  name                         = "mssqlserver"
+  resource_group_name          = azurerm_resource_group.example.name
+  location                     = azurerm_resource_group.example.location
+  version                      = "12.0"
+  administrator_login          = "missadministrator"
+  administrator_login_password = "thisIsKat11"
+  minimum_tls_version          = "1.1"
+}
+
+resource "azurerm_postgresql_server" "bad_example" {
+	name                = "bad_example"
+  
+	public_network_access_enabled    = true
+	ssl_enforcement_enabled          = false
+	ssl_minimal_tls_version_enforced = "TLS1_1"
+  }
+`,
+			GoodExample: `
+resource "azurerm_mssql_server" "good_example" {
+  name                         = "mssqlserver"
+  resource_group_name          = azurerm_resource_group.example.name
+  location                     = azurerm_resource_group.example.location
+  version                      = "12.0"
+  administrator_login          = "missadministrator"
+  administrator_login_password = "thisIsKat11"
+  minimum_tls_version          = "1.2"
+}
+
+resource "azurerm_postgresql_server" "good_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = true
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mssql_server#minimum_tls_version",
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/mysql_server#ssl_minimal_tls_version_enforced",
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/postgresql_server#ssl_minimal_tls_version_enforced",
+			},
+		},
+		Provider:        provider.AzureProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"azurerm_mssql_server", "azurerm_mysql_server", "azurerm_postgresql_server"},
+		DefaultSeverity: severity.Medium,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+
+			var attribute string
+			var requiredValue string
+
+			switch resourceBlock.TypeLabel() {
+			case "azurerm_mssql_server":
+				attribute = "minimum_tls_version"
+				requiredValue = "1.2"
+			case "azurerm_postgresql_server", "azurerm_mysql_server":
+				attribute = "ssl_minimal_tls_version_enforced"
+				requiredValue = "TLS1_2"
+			}
+
+			if resourceBlock.MissingChild(attribute) {
+				if resourceBlock.TypeLabel() == "azurerm_mssql_server" {
+					return
+				}
+
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' does not have %s set", resourceBlock.FullName(), attribute)),
+				)
+				return
+			}
+
+			tlsMinimumAttr := resourceBlock.GetAttribute(attribute)
+			if !tlsMinimumAttr.Equals(requiredValue) {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' has a value %s that is not %s", resourceBlock.FullName(), attribute, requiredValue)).
+						WithRange(tlsMinimumAttr.Range()).
+						WithAttributeAnnotation(tlsMinimumAttr),
+				)
+			}
+		},
+	})
+}


Test patch:
diff --git a/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule_test.go b/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule_test.go
new file mode 100644
index 000000000..e88bf8a9b
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule_test.go
@@ -0,0 +1,63 @@
+package database
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_AzureEnableSslEnforcement(t *testing.T) {
+	expectedCode := "azure-database-enable-ssl-enforcement"
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode string
+		mustExcludeResultCode string
+	}{
+		{
+			name: "server with ssl enforcement disabled fails check",
+			source: `
+resource "azurerm_postgresql_server" "bad_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = false
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "server with ssl enforcement not set fails check",
+			source: `
+resource "azurerm_mariadb_server" "bad_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "server ssl enforcement enabled passes check",
+			source: `
+resource "azurerm_mysql_server" "goodl_example" {
+  name                = "goodl_example"
+
+  public_network_access_enabled    = true
+  ssl_enforcement_enabled          = true
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			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/azure/database/no_public_access_rule_test.go b/internal/app/tfsec/rules/azure/database/no_public_access_rule_test.go
new file mode 100644
index 000000000..5bfa1d6f3
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/database/no_public_access_rule_test.go
@@ -0,0 +1,63 @@
+package database
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_AzureNoPublicAccess(t *testing.T) {
+	expectedCode := "azure-database-no-public-access"
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode string
+		mustExcludeResultCode string
+	}{
+		{
+			name: "server with public access enabled fails check",
+			source: `
+resource "azurerm_postgresql_server" "bad_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = true
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "server with public access not set fails check",
+			source: `
+resource "azurerm_mariadb_server" "bad_example" {
+  name                = "bad_example"
+
+
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "server public access disabled passes check",
+			source: `
+resource "azurerm_mysql_server" "goodl_example" {
+  name                = "goodl_example"
+
+  public_network_access_enabled    = false
+  ssl_enforcement_enabled          = true
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			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/azure/database/secure_tls_policy_rule_test.go b/internal/app/tfsec/rules/azure/database/secure_tls_policy_rule_test.go
new file mode 100644
index 000000000..e78eb206e
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/database/secure_tls_policy_rule_test.go
@@ -0,0 +1,109 @@
+package database
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_AzureSecureTlsPolicy(t *testing.T) {
+	expectedCode := "azure-database-secure-tls-policy"
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode string
+		mustExcludeResultCode string
+	}{
+		{
+			name: "msql with incorrect tls fails check",
+			source: `
+resource "azurerm_mssql_server" "bad_example" {
+  name                         = "mssqlserver"
+  resource_group_name          = azurerm_resource_group.example.name
+  location                     = azurerm_resource_group.example.location
+  version                      = "12.0"
+  administrator_login          = "missadministrator"
+  administrator_login_password = "thisIsKat11"
+  minimum_tls_version          = "1.1"
+}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "postgresql with incorrect tls fails check",
+			source: `
+resource "azurerm_postgresql_server" "bad_example" {
+	name                = "bad_example"
+  
+	public_network_access_enabled    = true
+	ssl_enforcement_enabled          = false
+	ssl_minimal_tls_version_enforced = "TLS1_1"
+}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "mysql with incorrect tls fails check",
+			source: `
+resource "azurerm_mysql_server" "bad_example" {
+	name                = "bad_example"
+  
+	public_network_access_enabled    = true
+	ssl_enforcement_enabled          = false
+	ssl_minimal_tls_version_enforced = "TLS1_1"
+}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "mssql with correct tls passes check",
+			source: `
+resource "azurerm_mssql_server" "example" {
+  name                         = "mssqlserver"
+  resource_group_name          = azurerm_resource_group.example.name
+  location                     = azurerm_resource_group.example.location
+  version                      = "12.0"
+  administrator_login          = "missadministrator"
+  administrator_login_password = "thisIsKat11"
+  minimum_tls_version          = "1.2"
+}
+`,
+			mustExcludeResultCode: expectedCode,
+		},
+		{
+			name: "postgresql with correct tls passes check",
+			source: `
+resource "azurerm_postgresql_server" "good_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = true
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			mustExcludeResultCode: expectedCode,
+		},
+		{
+			name: "mysql with correct tls passes check",
+			source: `
+resource "azurerm_mysql_server" "good_example" {
+  name                = "bad_example"
+
+  public_network_access_enabled    = true
+  ssl_enforcement_enabled          = false
+  ssl_minimal_tls_version_enforced = "TLS1_2"
+}
+`,
+			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)
+		})
+	}
+}