diff --git a/docs/README.md b/docs/README.md index bf5e51dd..a122dbbe 100644 --- a/docs/README.md +++ b/docs/README.md @@ -6,6 +6,7 @@ This documentation describes a list of rules available by enabling this ruleset. |Rule|Enabled by default| | --- | --- | +|[azurerm_app_service_missing_auto_heal_setting](rules/azurerm_app_service_missing_auto_heal_setting.md)|✔| |[azurerm_linux_virtual_machine_invalid_name](rules/azurerm_linux_virtual_machine_invalid_name.md)|✔| |[azurerm_linux_virtual_machine_invalid_size](rules/azurerm_linux_virtual_machine_invalid_size.md)|✔| |[azurerm_linux_virtual_machine_scale_set_invalid_sku](rules/azurerm_linux_virtual_machine_scale_set_invalid_sku.md)|✔| diff --git a/docs/rules/azurerm_app_service_missing_auto_heal_setting.md b/docs/rules/azurerm_app_service_missing_auto_heal_setting.md new file mode 100644 index 00000000..6986381e --- /dev/null +++ b/docs/rules/azurerm_app_service_missing_auto_heal_setting.md @@ -0,0 +1,126 @@ +# azurerm_app_service_missing_auto_heal_setting + +Disallow missing auto_heal_setting configuration in site_config block for Azure App Service resources. + +This rule applies to the following Azure App Service resource types: +- `azurerm_linux_web_app` +- `azurerm_linux_web_app_slot` +- `azurerm_windows_web_app` +- `azurerm_windows_web_app_slot` + +## Configuration + +```hcl +rule "azurerm_app_service_missing_auto_heal_setting" { + enabled = true +} +``` + +## Example + +### Terraform Configuration + +```hcl +# Non-compliant: Linux Web App with site_config but no auto_heal_setting +resource "azurerm_linux_web_app" "example" { + name = "example-app" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + service_plan_id = azurerm_service_plan.example.id + + site_config { + always_on = true + } +} + +# Compliant: Linux Web App with auto_heal_setting configured +resource "azurerm_linux_web_app" "example" { + name = "example-app" + resource_group_name = azurerm_resource_group.example.name + location = azurerm_resource_group.example.location + service_plan_id = azurerm_service_plan.example.id + + site_config { + always_on = true + + auto_heal_setting { + action { + action_type = "Recycle" + } + trigger { + status_code { + count = 5 + interval = "00:01:00" + status_code_range = "500-599" + } + } + } + } +} + +# Compliant: Windows Web App Slot with comprehensive auto_heal_setting +resource "azurerm_windows_web_app_slot" "example" { + name = "example-slot" + app_service_id = azurerm_windows_web_app.example.id + + site_config { + auto_heal_setting { + action { + action_type = "Recycle" + minimum_process_execution_time = "00:01:00" + } + trigger { + status_code { + count = 5 + interval = "00:01:00" + status_code_range = "500-599" + } + requests { + count = 100 + interval = "00:01:00" + } + slow_request { + count = 10 + interval = "00:02:00" + time_taken = "00:00:45" + } + } + } + } +} +``` + +## Why + +Configuring `auto_heal_setting` in the `site_config` block is a best practice for production Azure App Service resources. Auto-healing helps improve application resilience by automatically recycling or restarting the app when specific conditions are met, such as: + +- High number of HTTP errors (status codes in the 400-599 range) +- Excessive request volume +- Slow response times +- Memory threshold breaches + +By proactively detecting and responding to unhealthy states, auto-healing can prevent prolonged outages and improve overall application availability. This rule ensures that App Service resources have auto-healing configured to maintain production resilience. + +For more information about building robust apps for the cloud with auto-heal, see the [Azure App Service documentation on Auto Heal](https://azure.github.io/AppService/2020/05/15/Robust-Apps-for-the-cloud.html#auto-heal). + +## How to Fix + +Add an `auto_heal_setting` block within the `site_config` block: + +```hcl +site_config { + auto_heal_setting { + action { + action_type = "Recycle" + } + trigger { + status_code { + count = 5 + interval = "00:01:00" + status_code_range = "500-599" + } + } + } +} +``` +You can combine multiple triggers to create comprehensive auto-healing policies. diff --git a/rules/azurerm_app_service_missing_auto_heal_setting.go b/rules/azurerm_app_service_missing_auto_heal_setting.go new file mode 100644 index 00000000..f8ce0429 --- /dev/null +++ b/rules/azurerm_app_service_missing_auto_heal_setting.go @@ -0,0 +1,122 @@ +package rules + +import ( + "github.com/terraform-linters/tflint-plugin-sdk/hclext" + "github.com/terraform-linters/tflint-plugin-sdk/logger" + "github.com/terraform-linters/tflint-plugin-sdk/tflint" + "github.com/terraform-linters/tflint-ruleset-azurerm/project" +) + +// AzurermAppServiceMissingAutoHealSettingRule checks whether auto_heal_setting is configured in site_config +type AzurermAppServiceMissingAutoHealSettingRule struct { + tflint.DefaultRule +} + +const ( + autoHealSettingBlockName = "auto_heal_setting" +) + +var autoHealResourceTypes = []string{ + "azurerm_linux_web_app", + "azurerm_linux_web_app_slot", + "azurerm_windows_web_app", + "azurerm_windows_web_app_slot", +} + +// NewAzurermAppServiceMissingAutoHealSettingRule returns new rule for checking auto_heal_setting configuration +func NewAzurermAppServiceMissingAutoHealSettingRule() *AzurermAppServiceMissingAutoHealSettingRule { + return &AzurermAppServiceMissingAutoHealSettingRule{} +} + +// Name returns the rule name +func (r *AzurermAppServiceMissingAutoHealSettingRule) Name() string { + return "azurerm_app_service_missing_auto_heal_setting" +} + +// Enabled returns whether the rule is enabled by default +func (r *AzurermAppServiceMissingAutoHealSettingRule) Enabled() bool { + return true +} + +// Severity returns the rule severity +func (r *AzurermAppServiceMissingAutoHealSettingRule) Severity() tflint.Severity { + return tflint.WARNING +} + +// Link returns the rule reference link +func (r *AzurermAppServiceMissingAutoHealSettingRule) Link() string { + return project.ReferenceLink(r.Name()) +} + +// checkResourceType checks a specific resource type for auto_heal_setting configuration +func (r *AzurermAppServiceMissingAutoHealSettingRule) checkResourceType(runner tflint.Runner, resourceType string) error { + resources, err := runner.GetResourceContent(resourceType, &hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: "site_config", + Body: &hclext.BodySchema{ + Blocks: []hclext.BlockSchema{ + { + Type: autoHealSettingBlockName, + }, + }, + }, + }, + }, + }, nil) + + if err != nil { + return err + } + + for _, resource := range resources.Blocks { + logger.Debug("checking", "resource type", resource.Labels[0], "resource name", resource.Labels[1]) + + // Check if site_config block exists + hasSiteConfig := false + hasAutoHealSetting := false + + for _, block := range resource.Body.Blocks { + if block.Type == "site_config" { + hasSiteConfig = true + + // Check for auto_heal_setting block + for _, siteConfigBlock := range block.Body.Blocks { + if siteConfigBlock.Type == "auto_heal_setting" { + hasAutoHealSetting = true + break + } + } + + break + } + } + + // If site_config doesn't exist, skip this resource + if !hasSiteConfig { + logger.Debug("no site_config block found", "resource type", resource.Labels[0], "resource name", resource.Labels[1]) + continue + } + + // Emit issue if auto_heal_setting is not configured in site_config + if !hasAutoHealSetting { + issue := "auto_heal_setting should be configured in site_config block for robust app services." + if err := runner.EmitIssue(r, issue, resource.DefRange); err != nil { + return err + } + } + } + + return nil +} + +// Check checks whether auto_heal_setting is configured in site_config +func (r *AzurermAppServiceMissingAutoHealSettingRule) Check(runner tflint.Runner) error { + for _, resourceType := range autoHealResourceTypes { + if err := r.checkResourceType(runner, resourceType); err != nil { + return err + } + } + + return nil +} diff --git a/rules/azurerm_app_service_missing_auto_heal_setting_test.go b/rules/azurerm_app_service_missing_auto_heal_setting_test.go new file mode 100644 index 00000000..9837f750 --- /dev/null +++ b/rules/azurerm_app_service_missing_auto_heal_setting_test.go @@ -0,0 +1,268 @@ +package rules + +import ( + "testing" + + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/helper" +) + +func Test_AzurermAppServiceMissingAutoHealSetting(t *testing.T) { + cases := []struct { + Name string + Content string + Expected helper.Issues + }{ + // Linux Web App tests + { + Name: "Linux Web App - no site_config block - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App - site_config without auto_heal_setting - should fail", + Content: ` +resource "azurerm_linux_web_app" "example" { + site_config { + always_on = true + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceMissingAutoHealSettingRule(), + Message: "auto_heal_setting should be configured in site_config block for robust app services.", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 43}, + }, + }, + }, + }, + { + Name: "Linux Web App - site_config with auto_heal_setting - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { + site_config { + auto_heal_setting { + action { + action_type = "Recycle" + } + trigger { + status_code { + count = 5 + interval = "00:01:00" + status_code_range = "500-599" + } + } + } + } +} +`, + Expected: helper.Issues{}, + }, + // Linux Web App Slot tests + { + Name: "Linux Web App Slot - no site_config block - should pass", + Content: ` +resource "azurerm_linux_web_app_slot" "example" { +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App Slot - site_config without auto_heal_setting - should fail", + Content: ` +resource "azurerm_linux_web_app_slot" "example" { + site_config { + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceMissingAutoHealSettingRule(), + Message: "auto_heal_setting should be configured in site_config block for robust app services.", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 48}, + }, + }, + }, + }, + { + Name: "Linux Web App Slot - site_config with auto_heal_setting - should pass", + Content: ` +resource "azurerm_linux_web_app_slot" "example" { + site_config { + auto_heal_setting { + action { + action_type = "Recycle" + } + trigger { + requests { + count = 100 + interval = "00:01:00" + } + } + } + } +} +`, + Expected: helper.Issues{}, + }, + // Windows Web App tests + { + Name: "Windows Web App - no site_config block - should pass", + Content: ` +resource "azurerm_windows_web_app" "example" { +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App - site_config without auto_heal_setting - should fail", + Content: ` +resource "azurerm_windows_web_app" "example" { + site_config { + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceMissingAutoHealSettingRule(), + Message: "auto_heal_setting should be configured in site_config block for robust app services.", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 45}, + }, + }, + }, + }, + { + Name: "Windows Web App - site_config with auto_heal_setting - should pass", + Content: ` +resource "azurerm_windows_web_app" "example" { + site_config { + auto_heal_setting { + action { + action_type = "Recycle" + } + trigger { + slow_request { + count = 10 + interval = "00:01:00" + time_taken = "00:00:30" + } + } + } + } +} +`, + Expected: helper.Issues{}, + }, + // Windows Web App Slot tests + { + Name: "Windows Web App Slot - no site_config block - should pass", + Content: ` +resource "azurerm_windows_web_app_slot" "example" { +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App Slot - site_config without auto_heal_setting - should fail", + Content: ` +resource "azurerm_windows_web_app_slot" "example" { + site_config { + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceMissingAutoHealSettingRule(), + Message: "auto_heal_setting should be configured in site_config block for robust app services.", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 50}, + }, + }, + }, + }, + { + Name: "Windows Web App Slot - site_config with auto_heal_setting - should pass", + Content: ` +resource "azurerm_windows_web_app_slot" "example" { + site_config { + auto_heal_setting { + action { + action_type = "Recycle" + } + trigger { + status_code { + count = 3 + interval = "00:05:00" + status_code_range = "400-499" + } + } + } + } +} +`, + Expected: helper.Issues{}, + }, + // Multiple triggers + { + Name: "Linux Web App - site_config with complex auto_heal_setting - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { + site_config { + auto_heal_setting { + action { + action_type = "Recycle" + minimum_process_execution_time = "00:01:00" + } + trigger { + status_code { + count = 5 + interval = "00:01:00" + status_code_range = "500-599" + } + requests { + count = 100 + interval = "00:01:00" + } + slow_request { + count = 10 + interval = "00:02:00" + time_taken = "00:00:45" + } + } + } + } +} +`, + Expected: helper.Issues{}, + }, + } + + rule := NewAzurermAppServiceMissingAutoHealSettingRule() + + for _, tc := range cases { + t.Run(tc.Name, func(t *testing.T) { + runner := helper.TestRunner(t, map[string]string{"resource.tf": tc.Content}) + + if err := rule.Check(runner); err != nil { + t.Fatalf("Unexpected error occurred: %s", err) + } + + helper.AssertIssues(t, tc.Expected, runner.Issues) + }) + } +} diff --git a/rules/provider.go b/rules/provider.go index 6b1baef7..1f358ac6 100644 --- a/rules/provider.go +++ b/rules/provider.go @@ -7,6 +7,7 @@ import ( // Rules is a list of all rules var Rules = append([]tflint.Rule{ + NewAzurermAppServiceMissingAutoHealSettingRule(), NewAzurermKubernetesClusterDefaultNodePoolInvalidVMSizeRule(), NewAzurermKubernetesClusterNodePoolInvalidVMSizeRule(), NewAzurermLinuxVirtualMachineInvalidSizeRule(), diff --git a/tools/apispec-rule-gen/doc_README.md.tmpl b/tools/apispec-rule-gen/doc_README.md.tmpl index 6070394e..6ae947eb 100644 --- a/tools/apispec-rule-gen/doc_README.md.tmpl +++ b/tools/apispec-rule-gen/doc_README.md.tmpl @@ -6,6 +6,7 @@ This documentation describes a list of rules available by enabling this ruleset. |Rule|Enabled by default| | --- | --- | +|[azurerm_app_service_missing_auto_heal_setting](rules/azurerm_app_service_missing_auto_heal_setting.md)|✔| |[azurerm_linux_virtual_machine_invalid_name](rules/azurerm_linux_virtual_machine_invalid_name.md)|✔| |[azurerm_linux_virtual_machine_invalid_size](rules/azurerm_linux_virtual_machine_invalid_size.md)|✔| |[azurerm_linux_virtual_machine_scale_set_invalid_sku](rules/azurerm_linux_virtual_machine_scale_set_invalid_sku.md)|✔|