Repository: aquasecurity/tfsec
Pull request #1236 (2022-01-01 18:16:47)

Azure app service - convert to use defsec
Closes: https://github.com/aquasecurity/tfsec/issues/1235


Patch:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 105dbf262..a47c4668a 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -42,7 +42,7 @@ Find your new check and the associated test in one of the subfolders of `interna
 
 Here's an example:
 
-You need to tell the scanner about your check; this is done by calling an init() function with the following code:
+You need to tell the scanner about your check; this is done by calling an `init()` function with the following code:
 
 ```go
 func init() {
@@ -68,27 +68,11 @@ hackable = false
 		RequiredTypes:  []string{"resource"},
 		// the type of resource(s) you want to target
 		RequiredLabels: []string{"aws_gibson"},
-		// the actual logic for your check
-		CheckTerraform: func(block block.Block, module block.Module) (results rules.Results){
-			// TODO: add check logic here
-			return results
-		},
 	})
 }
 ```
 
-Now all that's left is writing the logic itself. You'll likely find it useful here to learn from preexisting check code, but the logic is usually fairly minimal. Here's a basic example:
-
-```go
-...
-
-		CheckTerraform: func(block block.Block, module block.Module) (results rules.Results){
-			if attr := block.GetAttribute("hackable"); attr.IsTrue() {
-				results.Add("The Gibson '%s' is configured to be hackable.", attr)
-			}
-		},
-...
-```
+Now all that's left is writing the logic itself. This has been moved to [defsec](https://github.com/aquasecurity/defsec). You need to make sure that there's an adapter for the resource. You can see [aws/ec2/adapter.go](https://github.com/aquasecurity/tfsec/blob/master/internal/app/tfsec/adapter/aws/ec2/adapt.go) for an example.
 
 You can see a good example of a real check file [here](https://github.com/aquasecurity/tfsec/blob/master/internal/app/tfsec/rules/aws/vpc/no_public_egress_sg_rule.go).
 This check also provides [tests](https://github.com/aquasecurity/tfsec/blob/master/internal/app/tfsec/rules/aws/vpc/no_public_egress_sg_rule_test.go) and uses provided data checks like `cidr.IsAttributeOpen()` provided [here](https://github.com/aquasecurity/tfsec/blob/master/internal/app/tfsec/cidr/cidr.go).
@@ -99,10 +83,8 @@ There is no longer a need to create dedicated tests for new checks - the `BadExa
 
 The first example that you add for Good and Bad will be used in the documentation, additional blocks you want to be tested to to verify the check should be added afterwards.
 
-
 And that's it! If you have any difficulties, please feel free to raise a draft PR and note any questions/problems in the description and we'll do our best to help you out.
 
-
 ### Submitting the PR
 
 When you are ready to submit the PR for review, please run 
diff --git a/internal/app/tfsec/adapter/azure/appservice/adapt.go b/internal/app/tfsec/adapter/azure/appservice/adapt.go
index 5ea9e6139..f317304fb 100644
--- a/internal/app/tfsec/adapter/azure/appservice/adapt.go
+++ b/internal/app/tfsec/adapter/azure/appservice/adapt.go
@@ -2,9 +2,91 @@ package appservice
 
 import (
 	"github.com/aquasecurity/defsec/provider/azure/appservice"
+	"github.com/aquasecurity/defsec/types"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 )
 
 func Adapt(modules []block.Module) appservice.AppService {
-	return appservice.AppService{}
+	return appservice.AppService{
+		Services:     adaptServices(modules),
+		FunctionApps: adaptFunctionApps(modules),
+	}
+}
+
+func adaptServices(modules []block.Module) []appservice.Service {
+	var services []appservice.Service
+
+	for _, module := range modules {
+		for _, resource := range module.GetResourcesByType("azurerm_app_service") {
+			services = append(services, adaptService(resource))
+		}
+	}
+	return services
+}
+
+func adaptFunctionApps(modules []block.Module) []appservice.FunctionApp {
+	var functionApps []appservice.FunctionApp
+
+	for _, module := range modules {
+		for _, resource := range module.GetResourcesByType("azurerm_function_app") {
+			functionApps = append(functionApps, adaptFunctionApp(resource))
+		}
+	}
+	return functionApps
+}
+
+func adaptService(resource block.Block) appservice.Service {
+	enableClientCertAttr := resource.GetAttribute("client_cert_enabled")
+	enableClientCertVal := enableClientCertAttr.AsBoolValueOrDefault(false, resource)
+
+	identityBlock := resource.GetBlock("identity")
+	typeVal := types.String("", *resource.GetMetadata())
+	if identityBlock.IsNotNil() {
+		typeAttr := identityBlock.GetAttribute("type")
+		typeVal = typeAttr.AsStringValueOrDefault("", identityBlock)
+	}
+
+	authBlock := resource.GetBlock("auth_settings")
+	enabledVal := types.Bool(false, *resource.GetMetadata())
+	if authBlock.IsNotNil() {
+		enabledAttr := authBlock.GetAttribute("enabled")
+		enabledVal = enabledAttr.AsBoolValueOrDefault(false, authBlock)
+	}
+
+	siteBlock := resource.GetBlock("site_config")
+	enableHTTP2Val := types.Bool(false, *resource.GetMetadata())
+	minTLSVersionVal := types.String("1.2", *resource.GetMetadata())
+	if siteBlock.IsNotNil() {
+		enableHTTP2Attr := siteBlock.GetAttribute("http2_enabled")
+		enableHTTP2Val = enableHTTP2Attr.AsBoolValueOrDefault(false, siteBlock)
+
+		minTLSVersionAttr := siteBlock.GetAttribute("min_tls_version")
+		minTLSVersionVal = minTLSVersionAttr.AsStringValueOrDefault("1.2", siteBlock)
+	}
+
+	return appservice.Service{
+		EnableClientCert: enableClientCertVal,
+		Identity: struct{ Type types.StringValue }{
+			Type: typeVal,
+		},
+		Authentication: struct{ Enabled types.BoolValue }{
+			Enabled: enabledVal,
+		},
+		Site: struct {
+			EnableHTTP2       types.BoolValue
+			MinimumTLSVersion types.StringValue
+		}{
+			EnableHTTP2:       enableHTTP2Val,
+			MinimumTLSVersion: minTLSVersionVal,
+		},
+	}
+}
+
+func adaptFunctionApp(resource block.Block) appservice.FunctionApp {
+	HTTPSOnlyAttr := resource.GetAttribute("https_only")
+	HTTPSOnlyVal := HTTPSOnlyAttr.AsBoolValueOrDefault(false, resource)
+
+	return appservice.FunctionApp{
+		HTTPSOnly: HTTPSOnlyVal,
+	}
 }
diff --git a/internal/app/tfsec/block/type.go b/internal/app/tfsec/block/type.go
index 05ee86f26..6c0fb7987 100644
--- a/internal/app/tfsec/block/type.go
+++ b/internal/app/tfsec/block/type.go
@@ -38,6 +38,10 @@ var TypeLocal = Type{
 	refName: "local",
 }
 
+var TypeMoved = Type{
+	name: "moved",
+}
+
 var TypeProvider = Type{
 	name: "provider",
 }
@@ -58,6 +62,7 @@ var ValidTypes = []Type{
 	TypeData,
 	TypeLocal,
 	TypeModule,
+	TypeMoved,
 	TypeOutput,
 	TypeProvider,
 	TypeResource,
diff --git a/internal/app/tfsec/parser/evaluator.go b/internal/app/tfsec/parser/evaluator.go
index 89d918fd3..a12bffedb 100644
--- a/internal/app/tfsec/parser/evaluator.go
+++ b/internal/app/tfsec/parser/evaluator.go
@@ -399,7 +399,7 @@ func (e *Evaluator) getValuesByBlockType(blockType string) cty.Value {
 				continue
 			}
 			values[b.Label()] = val
-		case "locals":
+		case "locals", "moved":
 			for key, val := range b.Values().AsValueMap() {
 				values[key] = val
 			}
diff --git a/internal/app/tfsec/rules/azure/appservice/account_identity_registered_rule.go b/internal/app/tfsec/rules/azure/appservice/account_identity_registered_rule.go
index 0fff9cd47..dbe920411 100644
--- a/internal/app/tfsec/rules/azure/appservice/account_identity_registered_rule.go
+++ b/internal/app/tfsec/rules/azure/appservice/account_identity_registered_rule.go
@@ -1,9 +1,7 @@
 package appservice
 
 import (
-	"github.com/aquasecurity/defsec/rules"
 	"github.com/aquasecurity/defsec/rules/azure/appservice"
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
@@ -41,11 +39,5 @@ func init() {
 			"azurerm_app_service",
 		},
 		Base: appservice.CheckAccountIdentityRegistered,
-		CheckTerraform: func(resourceBlock block.Block, _ block.Module) (results rules.Results) {
-			if identityAttr := resourceBlock.GetBlock("identity"); identityAttr.IsNil() { // alert on use of default value
-				results.Add("Resource does not set identity", resourceBlock)
-			}
-			return results
-		},
 	})
 }
diff --git a/internal/app/tfsec/rules/azure/appservice/authentication_enabled_rule.go b/internal/app/tfsec/rules/azure/appservice/authentication_enabled_rule.go
index ec7ac3544..ed1eb430d 100644
--- a/internal/app/tfsec/rules/azure/appservice/authentication_enabled_rule.go
+++ b/internal/app/tfsec/rules/azure/appservice/authentication_enabled_rule.go
@@ -1,9 +1,7 @@
 package appservice
 
 import (
-	"github.com/aquasecurity/defsec/rules"
 	"github.com/aquasecurity/defsec/rules/azure/appservice"
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
@@ -40,15 +38,5 @@ func init() {
 			"azurerm_app_service",
 		},
 		Base: appservice.CheckAuthenticationEnabled,
-		CheckTerraform: func(resourceBlock block.Block, _ block.Module) (results rules.Results) {
-			if authBlock := resourceBlock.GetBlock("auth_settings"); authBlock.IsNil() {
-				results.Add("Resource uses default value for auth_settings.enabled", resourceBlock)
-			} else if enabledAttr := authBlock.GetAttribute("enabled"); enabledAttr.IsNil() {
-				results.Add("Resource uses default value for auth_settings.enabled", authBlock)
-			} else if enabledAttr.IsFalse() {
-				results.Add("Resource has attribute auth_settings.enabled that is false", enabledAttr)
-			}
-			return results
-		},
 	})
 }
diff --git a/internal/app/tfsec/rules/azure/appservice/enable_http2_rule.go b/internal/app/tfsec/rules/azure/appservice/enable_http2_rule.go
index 804472301..df66fcd34 100644
--- a/internal/app/tfsec/rules/azure/appservice/enable_http2_rule.go
+++ b/internal/app/tfsec/rules/azure/appservice/enable_http2_rule.go
@@ -1,9 +1,7 @@
 package appservice
 
 import (
-	"github.com/aquasecurity/defsec/rules"
 	"github.com/aquasecurity/defsec/rules/azure/appservice"
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
@@ -51,15 +49,5 @@ func init() {
 			"azurerm_app_service",
 		},
 		Base: appservice.CheckEnableHttp2,
-		CheckTerraform: func(resourceBlock block.Block, _ block.Module) (results rules.Results) {
-			if configBlock := resourceBlock.GetBlock("site_config"); configBlock.IsNil() {
-				results.Add("Resource uses default value for site_config.http2_enabled", resourceBlock)
-			} else if http2EnabledAttr := configBlock.GetAttribute("http2_enabled"); http2EnabledAttr.IsNil() { // alert on use of default value
-				results.Add("Resource uses default value for site_config.http2_enabled", configBlock)
-			} else if http2EnabledAttr.IsFalse() {
-				results.Add("Resource has attribute site_config.http2_enabled that is false", http2EnabledAttr)
-			}
-			return results
-		},
 	})
 }
diff --git a/internal/app/tfsec/rules/azure/appservice/enforce_https_rule.go b/internal/app/tfsec/rules/azure/appservice/enforce_https_rule.go
index 2aeef5f60..ca072aa57 100644
--- a/internal/app/tfsec/rules/azure/appservice/enforce_https_rule.go
+++ b/internal/app/tfsec/rules/azure/appservice/enforce_https_rule.go
@@ -1,9 +1,7 @@
 package appservice
 
 import (
-	"github.com/aquasecurity/defsec/rules"
 	"github.com/aquasecurity/defsec/rules/azure/appservice"
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
@@ -40,15 +38,5 @@ func init() {
 		RequiredTypes:  []string{"resource"},
 		RequiredLabels: []string{"azurerm_function_app"},
 		Base:           appservice.CheckEnforceHttps,
-		CheckTerraform: func(resourceBlock block.Block, _ block.Module) (results rules.Results) {
-			if resourceBlock.MissingChild("https_only") {
-				results.Add("Resource should have https_only set to true, the default is false.", resourceBlock)
-				return
-			}
-			if httpsOnlyAttr := resourceBlock.GetAttribute("https_only"); httpsOnlyAttr.IsFalse() {
-				results.Add("Resource should be HTTPS only.", httpsOnlyAttr)
-			}
-			return results
-		},
 	})
 }
diff --git a/internal/app/tfsec/rules/azure/appservice/require_client_cert_rule.go b/internal/app/tfsec/rules/azure/appservice/require_client_cert_rule.go
index cba20964b..dc45bcd8f 100644
--- a/internal/app/tfsec/rules/azure/appservice/require_client_cert_rule.go
+++ b/internal/app/tfsec/rules/azure/appservice/require_client_cert_rule.go
@@ -1,9 +1,7 @@
 package appservice
 
 import (
-	"github.com/aquasecurity/defsec/rules"
 	"github.com/aquasecurity/defsec/rules/azure/appservice"
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
@@ -47,13 +45,5 @@ func init() {
 			"azurerm_app_service",
 		},
 		Base: appservice.CheckRequireClientCert,
-		CheckTerraform: func(resourceBlock block.Block, _ block.Module) (results rules.Results) {
-			if clientCertEnabledAttr := resourceBlock.GetAttribute("client_cert_enabled"); clientCertEnabledAttr.IsNil() { // alert on use of default value
-				results.Add("Resource uses default value for client_cert_enabled", resourceBlock)
-			} else if clientCertEnabledAttr.IsFalse() {
-				results.Add("Resource has attribute client_cert_enabled that is false", clientCertEnabledAttr)
-			}
-			return results
-		},
 	})
 }
diff --git a/internal/app/tfsec/rules/azure/appservice/use_secure_tls_policy_rule.go b/internal/app/tfsec/rules/azure/appservice/use_secure_tls_policy_rule.go
index 5ed498887..7f037caf1 100644
--- a/internal/app/tfsec/rules/azure/appservice/use_secure_tls_policy_rule.go
+++ b/internal/app/tfsec/rules/azure/appservice/use_secure_tls_policy_rule.go
@@ -1,9 +1,7 @@
 package appservice
 
 import (
-	"github.com/aquasecurity/defsec/rules"
 	"github.com/aquasecurity/defsec/rules/azure/appservice"
-	"github.com/aquasecurity/tfsec/internal/app/tfsec/block"
 	"github.com/aquasecurity/tfsec/internal/app/tfsec/scanner"
 	"github.com/aquasecurity/tfsec/pkg/rule"
 )
@@ -40,15 +38,5 @@ func init() {
 			"azurerm_app_service",
 		},
 		Base: appservice.CheckUseSecureTlsPolicy,
-		CheckTerraform: func(resourceBlock block.Block, _ block.Module) (results rules.Results) {
-			if resourceBlock.MissingChild("site_config") {
-				return
-			}
-
-			if minTlsVersionAttr := resourceBlock.GetBlock("site_config").GetAttribute("min_tls_version"); minTlsVersionAttr.IsNotNil() && minTlsVersionAttr.NotEqual("1.2") {
-				results.Add("Resource does not have site_config.min_tls_version set to 1.2", minTlsVersionAttr)
-			}
-			return results
-		},
 	})
 }
diff --git a/internal/app/tfsec/schema/schema.go b/internal/app/tfsec/schema/schema.go
index 2f8d3023a..9920a7a15 100644
--- a/internal/app/tfsec/schema/schema.go
+++ b/internal/app/tfsec/schema/schema.go
@@ -19,6 +19,9 @@ var TerraformSchema_0_12 = &hcl.BodySchema{
 		{
 			Type: "locals",
 		},
+		{
+			Type: "moved",
+		},
 		{
 			Type:       "output",
 			LabelNames: []string{"name"},


Test patch:
diff --git a/internal/app/tfsec/parser/parser_test.go b/internal/app/tfsec/parser/parser_test.go
index b7cb16484..316d7c503 100644
--- a/internal/app/tfsec/parser/parser_test.go
+++ b/internal/app/tfsec/parser/parser_test.go
@@ -32,6 +32,10 @@ provider "cats" {
 
 }
 
+moved {
+
+}
+
 resource "cats_cat" "mittens" {
 	name = "mittens"
 	special = true