Repository: aquasecurity/tfsec
Pull request #1058 (2021-08-30 18:51:24)

Added new test for Azure Function App Authentication Enabled


Patch:
diff --git a/internal/app/tfsec/rules/azure/functionapp/authentication_enabled_rule.go b/internal/app/tfsec/rules/azure/functionapp/authentication_enabled_rule.go
new file mode 100644
index 000000000..86202c8f2
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/functionapp/authentication_enabled_rule.go
@@ -0,0 +1,64 @@
+package functionapp
+
+import (
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/pkg/provider"
+	"github.com/aquasecurity/tfsec/pkg/result"
+	"github.com/aquasecurity/tfsec/pkg/rule"
+	"github.com/aquasecurity/tfsec/pkg/severity"
+)
+
+func init() {
+	scanner.RegisterCheckRule(rule.Rule{
+		Provider:  provider.AzureProvider,
+		Service:   "functionapp",
+		ShortCode: "authentication-enabled",
+		Documentation: rule.RuleDocumentation{
+			Summary:     "Function App authentication is activated",
+			Explanation: `Enabling authentication ensures that all communications in the application are authenticated. The auth_settings block needs to be filled out with the appropriate auth backend settings`,
+			Impact:      "Anonymous HTTP requests will be accepted",
+			Resolution:  "Enable authentication to prevent anonymous request being accepted",
+			BadExample: []string{`
+resource "azurerm_function_app" "bad_example" {
+  name                = "example-function-app"
+  location            = azurerm_resource_group.example.location
+  resource_group_name = azurerm_resource_group.example.name
+  app_service_plan_id = azurerm_function_app_plan.example.id
+}
+`},
+			GoodExample: []string{`
+resource "azurerm_function_app" "good_example" {
+  name                = "example-function-app"
+  location            = azurerm_resource_group.example.location
+  resource_group_name = azurerm_resource_group.example.name
+  app_service_plan_id = azurerm_function_app_plan.example.id
+
+  auth_settings {
+    enabled = true
+  }
+}
+`},
+			Links: []string{
+				"https://registry.terraform.io/providers/hashicorp/azurerm/latest/docs/resources/function_app#enabled",
+			},
+		},
+		RequiredTypes: []string{
+			"resource",
+		},
+		RequiredLabels: []string{
+			"azurerm_function_app",
+		},
+		DefaultSeverity: severity.Low,
+		CheckFunc: func(set result.Set, resourceBlock block.Block, module block.Module) {
+			if enabledAttr := resourceBlock.GetBlock("auth_settings").GetAttribute("enabled"); enabledAttr.IsNil() { // alert on use of default value
+				set.AddResult().
+					WithDescription("Resource '%s' uses default value for auth_settings.enabled", resourceBlock.FullName())
+			} else if enabledAttr.IsFalse() {
+				set.AddResult().
+					WithDescription("Resource '%s' has attribute auth_settings.enabled that is false", resourceBlock.FullName()).
+					WithAttribute(enabledAttr)
+			}
+		},
+	})
+}
diff --git a/internal/app/tfsec/rules/init.go b/internal/app/tfsec/rules/init.go
index 40d4c7942..587852718 100644
--- a/internal/app/tfsec/rules/init.go
+++ b/internal/app/tfsec/rules/init.go
@@ -54,6 +54,7 @@ import (
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/securitycenter"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/storage"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/synapse"
+	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/azure/functionapp"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/cloudstack/compute"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/compute"
 	_ "github.com/aquasecurity/tfsec/internal/app/tfsec/rules/digitalocean/droplet"
diff --git a/rules.md b/rules.md
index 48b19113b..4c6210c44 100644
--- a/rules.md
+++ b/rules.md
@@ -117,6 +117,7 @@
 | azure-keyvault-ensure-key-expiry | azure | keyvault | Ensure that the expiration date is set on all keys |
 | azure-synapse-virtual-network-enabled | azure | synapse | Synapse Workspace should have managed virtual network enabled, the default is disabled. |
 | azure-appservice-enforce-https | azure | appservice | Ensure the Function App can only be accessed via HTTPS. The default is false. |
+| azure-functionapp-authentication-enabled | functionapp | Ensure the Function App has authentication enabled. The default is false. |
 | digitalocean-compute-no-public-ingress | digitalocean | compute | The firewall has an inbound rule with open access |
 | digitalocean-compute-no-public-egress | digitalocean | compute | The firewall has an outbound rule with open access |
 | digitalocean-droplet-use-ssh-keys | digitalocean | droplet | SSH Keys are the preferred way to connect to your droplet, no keys are supplied |


Test patch:
diff --git a/internal/app/tfsec/rules/azure/functionapp/authentication_enabled_rule_test.go b/internal/app/tfsec/rules/azure/functionapp/authentication_enabled_rule_test.go
new file mode 100644
index 000000000..437c95c8c
--- /dev/null
+++ b/internal/app/tfsec/rules/azure/functionapp/authentication_enabled_rule_test.go
@@ -0,0 +1,53 @@
+package functionapp
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
+	"github.com/aquasecurity/tfsec/internal/app/tfsec/testutil"
+)
+
+func Test_AzureAuthenticationEnabled_FailureExamples(t *testing.T) {
+	expectedCode := "azure-functionapp-authentication-enabled"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	for i, badExample := range rule.Documentation.BadExample {
+		t.Logf("Running bad example for '%s' #%d", expectedCode, i+1)
+		if strings.TrimSpace(badExample) == "" {
+			t.Fatalf("bad example code not provided for %s", rule.ID())
+		}
+		defer func() {
+			if err := recover(); err != nil {
+				t.Fatalf("Scan (bad) failed: %s", err)
+			}
+		}()
+		results := testutil.ScanHCL(badExample, t)
+		testutil.AssertCheckCode(t, rule.ID(), "", results)
+	}
+}
+
+func Test_AzureAuthenticationEnabled_SuccessExamples(t *testing.T) {
+	expectedCode := "azure-functionapp-authentication-enabled"
+
+	rule, err := scanner.GetRuleById(expectedCode)
+	if err != nil {
+		t.Fatalf("Rule not found: %s", expectedCode)
+	}
+	for i, example := range rule.Documentation.GoodExample {
+		t.Logf("Running good example for '%s' #%d", expectedCode, i+1)
+		if strings.TrimSpace(example) == "" {
+			t.Fatalf("good example code not provided for %s", rule.ID())
+		}
+		defer func() {
+			if err := recover(); err != nil {
+				t.Fatalf("Scan (good) failed: %s", err)
+			}
+		}()
+		results := testutil.ScanHCL(example, t)
+		testutil.AssertCheckCode(t, "", rule.ID(), results)
+	}
+}