Repository: aquasecurity/tfsec
Pull request #937 (2021-07-22 16:30:51)

Add rule for openstack firewall rules


Patch:
diff --git a/internal/app/tfsec/rules/init.go b/internal/app/tfsec/rules/init.go
index 06748b187..47dc6d497 100644
--- a/internal/app/tfsec/rules/init.go
+++ b/internal/app/tfsec/rules/init.go
@@ -56,5 +56,6 @@ import (
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/google/iam"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/google/storage"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/openstack/compute"
+	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/openstack/fw"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/oracle/compute"
 )
diff --git a/internal/app/tfsec/rules/openstack/fw/no_public_access_rule.go b/internal/app/tfsec/rules/openstack/fw/no_public_access_rule.go
new file mode 100644
index 000000000..4fe5750e7
--- /dev/null
+++ b/internal/app/tfsec/rules/openstack/fw/no_public_access_rule.go
@@ -0,0 +1,95 @@
+package fw
+
+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:   "fw",
+		ShortCode: "no-public-access",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "A firewall rule allows traffic from/to the public internet",
+			Explanation: `Opening up ports to the public internet is generally to be avoided. You should restrict access to IP addresses or ranges that explicitly require it where possible.`,
+			Impact:      "Exposure of infrastructure to the public internet",
+			Resolution:  "Employ more restrictive firewall rules",
+			BadExample: `
+resource "openstack_fw_rule_v1" "rule_1" {
+	name             = "my_rule"
+	description      = "let anyone in"
+	action           = "allow"
+	protocol         = "tcp"
+	destination_port = "22"
+	enabled          = "true"
+}
+			`,
+			GoodExample: `
+resource "openstack_fw_rule_v1" "rule_1" {
+	name                   = "my_rule"
+	description            = "don't let just anyone in"
+	action                 = "allow"
+	protocol               = "tcp"
+	destination_ip_address = "10.10.10.1"
+	source_ip_address      = "10.10.10.2"
+	destination_port       = "22"
+	enabled                = "true"
+}
+			`,
+			Links: []string{
+				"https://registry.terraform.io/providers/terraform-provider-openstack/openstack/latest/docs/resources/fw_rule_v1",
+			},
+		},
+		Provider:        provider.OpenStackProvider,
+		RequiredTypes:   []string{"resource"},
+		RequiredLabels:  []string{"openstack_fw_rule_v1"},
+		DefaultSeverity: severity.Medium,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, _ *hclcontext.Context) {
+
+			if enabledAttr := resourceBlock.GetAttribute("enabled"); enabledAttr != nil && enabledAttr.IsFalse() {
+				return
+			}
+
+			if actionAttr := resourceBlock.GetAttribute("action"); actionAttr != nil && actionAttr.Equals("deny") {
+				return
+			}
+
+			if destinationIP := resourceBlock.GetAttribute("destination_ip_address"); destinationIP == nil || destinationIP.Equals("") {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' defines a firewall rule with no restriction on destination IP", resourceBlock)),
+				)
+			} else if cidr.IsOpen(destinationIP) {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' defines a firewall rule with a public destination CIDR", resourceBlock)),
+				)
+			}
+
+			if sourceIP := resourceBlock.GetAttribute("source_ip_address"); sourceIP == nil || sourceIP.Equals("") {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' defines a firewall rule with no restriction on source IP", resourceBlock)),
+				)
+			} else if cidr.IsOpen(sourceIP) {
+				set.Add(
+					result.New(resourceBlock).
+						WithDescription(fmt.Sprintf("Resource '%s' defines a firewall rule with a public source CIDR", resourceBlock)),
+				)
+			}
+		},
+	})
+}


Test patch:
diff --git a/internal/app/tfsec/rules/openstack/fw/no_public_access_rule_test.go b/internal/app/tfsec/rules/openstack/fw/no_public_access_rule_test.go
new file mode 100644
index 000000000..82fa5269e
--- /dev/null
+++ b/internal/app/tfsec/rules/openstack/fw/no_public_access_rule_test.go
@@ -0,0 +1,57 @@
+package fw
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_OpenStackNoPublicAccess(t *testing.T) {
+	expectedCode := "openstack-fw-no-public-access"
+
+	var tests = []struct {
+		name                  string
+		source                string
+		mustIncludeResultCode string
+		mustExcludeResultCode string
+	}{
+		{
+			name: "rule matches for open firewall rule",
+			source: `
+resource "openstack_fw_rule_v1" "rule_1" {
+	name             = "my_rule"
+	description      = "let anyone in"
+	action           = "allow"
+	protocol         = "tcp"
+	destination_port = "22"
+	enabled          = "true"
+}
+`,
+			mustIncludeResultCode: expectedCode,
+		},
+		{
+			name: "rule does not match for more specific firewall rule",
+			source: `
+resource "openstack_fw_rule_v1" "rule_1" {
+	name                   = "my_rule"
+	description            = "don't let just anyone in"
+	action                 = "allow"
+	protocol               = "tcp"
+	destination_ip_address = "10.10.10.1"
+	source_ip_address      = "10.10.10.2"
+	destination_port       = "22"
+	enabled                = "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)
+		})
+	}
+}