diff --git a/cmd/tfsec-docs/webpage.go b/cmd/tfsec-docs/webpage.go index 99c21838ee..254390396f 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 0000000000..b17e7901f9 --- /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/enable_ssl_enforcement_rule_test.go b/internal/app/tfsec/rules/azure/database/enable_ssl_enforcement_rule_test.go new file mode 100644 index 0000000000..e88bf8a9b3 --- /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.go b/internal/app/tfsec/rules/azure/database/no_public_access_rule.go new file mode 100644 index 0000000000..98228f7ef1 --- /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/no_public_access_rule_test.go b/internal/app/tfsec/rules/azure/database/no_public_access_rule_test.go new file mode 100644 index 0000000000..5bfa1d6f38 --- /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.go b/internal/app/tfsec/rules/azure/database/secure_tls_policy_rule.go new file mode 100644 index 0000000000..5f3bdf9aac --- /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), + ) + } + }, + }) +} 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 0000000000..e78eb206e7 --- /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) + }) + } +}