diff --git a/docs/README.md b/docs/README.md index bf5e51dd..400577cf 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_app_insights_hidden_link](rules/azurerm_app_service_app_insights_hidden_link.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_app_insights_hidden_link.md b/docs/rules/azurerm_app_service_app_insights_hidden_link.md new file mode 100644 index 00000000..76885db2 --- /dev/null +++ b/docs/rules/azurerm_app_service_app_insights_hidden_link.md @@ -0,0 +1,128 @@ +# azurerm_app_service_app_insights_hidden_link + +Disallow missing lifecycle ignore_changes for Application Insights hidden-link tags. + +This rule applies to all Azure App Service resource types (Web Apps and Function Apps): +- `azurerm_function_app_flex_consumption` +- `azurerm_linux_web_app` +- `azurerm_linux_web_app_slot` +- `azurerm_windows_web_app` +- `azurerm_windows_web_app_slot` +- `azurerm_linux_function_app` +- `azurerm_linux_function_app_slot` +- `azurerm_windows_function_app` +- `azurerm_windows_function_app_slot` + +## Configuration + +```hcl +rule "azurerm_app_service_app_insights_hidden_link" { + enabled = true +} +``` + +## Example + +### Terraform Configuration + +```hcl +# Non-compliant: Linux Web App with Application Insights configured without ignore_changes +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} + +# Non-compliant: Windows Web App Slot with Application Insights configured without ignore_changes +resource "azurerm_windows_web_app_slot" "example" { + name = "example-slot" + app_service_id = azurerm_windows_web_app.example.id + + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example-key" + } +} + +# Non-compliant: Windows Function App with Application Insights configured without ignore_changes +resource "azurerm_windows_function_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example-key" + } +} + +# Non-compliant: Linux Function App with Application Insights in site_config without ignore_changes +resource "azurerm_linux_function_app" "example" { + site_config { + application_insights_connection_string = "example-connection" + } +} + +# Compliant: Linux Function App with proper ignore_changes +resource "azurerm_linux_function_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} + +# Compliant: Windows Web App Slot with proper ignore_changes +resource "azurerm_windows_web_app_slot" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} + +# Compliant: Windows Function App with Application Insights in site_config and proper ignore_changes +resource "azurerm_windows_function_app" "example" { + site_config { + application_insights_key = "example-key" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +``` + +## Why + +When Application Insights is configured for Azure App Service resources (Web Apps, Web App Slots, Function Apps, or Function App Slots for both Linux and Windows), Azure automatically adds hidden-link tags to the resource. These tags can change during deployments and may cause unnecessary Terraform diffs. Using `lifecycle { ignore_changes }` for these specific tags prevents Terraform from attempting to manage these Azure-managed tags. + +Application Insights can be configured in two ways: +1. **app_settings**: Using `APPLICATIONINSIGHTS_CONNECTION_STRING` or `APPINSIGHTS_INSTRUMENTATIONKEY` keys +2. **site_config**: Using `application_insights_connection_string` or `application_insights_key` attributes (only applicable to Function Apps) + +This rule checks for the presence of Application Insights configuration in both locations and ensures that the corresponding hidden-link tags are properly ignored. + +## How to Fix + +Add a `lifecycle` block with `ignore_changes` containing the Application Insights hidden-link tags: + +```hcl +lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] +} +``` \ No newline at end of file diff --git a/rules/azurerm_app_service_app_insights_hidden_link.go b/rules/azurerm_app_service_app_insights_hidden_link.go new file mode 100644 index 00000000..63c7bfba --- /dev/null +++ b/rules/azurerm_app_service_app_insights_hidden_link.go @@ -0,0 +1,236 @@ +package rules + +import ( + "strings" + + "github.com/hashicorp/hcl/v2" + "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" + "github.com/zclconf/go-cty/cty" +) + +// AzurermAppServiceAppInsightsHiddenLinkRule checks whether lifecycle ignore_changes includes hidden-link tags when Application Insights is configured +type AzurermAppServiceAppInsightsHiddenLinkRule struct { + tflint.DefaultRule +} + +const ( + appServiceIgnoreChangesAttrName = "ignore_changes" + appServiceAppSettingsAttrName = "app_settings" + appServiceSiteConfigAttrName = "site_config" + appServiceAppInsightsConnectionKey = "APPLICATIONINSIGHTS_CONNECTION_STRING" + appServiceAppInsightsInstrumentKey = "APPINSIGHTS_INSTRUMENTATIONKEY" + appServiceSiteConfigAppInsightsConnectionKey = "application_insights_connection_string" + appServiceSiteConfigAppInsightsKey = "application_insights_key" +) + +var appServiceRequiredHiddenLinkTags = []string{ + "hidden-link: /app-insights-conn-string", + "hidden-link: /app-insights-instrumentation-key", + "hidden-link: /app-insights-resource-id", +} + +var appServiceResourceTypes = []string{ + "azurerm_function_app_flex_consumption", + "azurerm_linux_web_app", + "azurerm_linux_web_app_slot", + "azurerm_windows_web_app", + "azurerm_windows_web_app_slot", + "azurerm_linux_function_app", + "azurerm_linux_function_app_slot", + "azurerm_windows_function_app", + "azurerm_windows_function_app_slot", +} + +// NewAzurermAppServiceAppInsightsHiddenLinkRule returns new rule for checking Application Insights hidden-link configuration +func NewAzurermAppServiceAppInsightsHiddenLinkRule() *AzurermAppServiceAppInsightsHiddenLinkRule { + return &AzurermAppServiceAppInsightsHiddenLinkRule{} +} + +// Name returns the rule name +func (r *AzurermAppServiceAppInsightsHiddenLinkRule) Name() string { + return "azurerm_app_service_app_insights_hidden_link" +} + +// Enabled returns whether the rule is enabled by default +func (r *AzurermAppServiceAppInsightsHiddenLinkRule) Enabled() bool { + return true +} + +// Severity returns the rule severity +func (r *AzurermAppServiceAppInsightsHiddenLinkRule) Severity() tflint.Severity { + return tflint.WARNING +} + +// Link returns the rule reference link +func (r *AzurermAppServiceAppInsightsHiddenLinkRule) Link() string { + return project.ReferenceLink(r.Name()) +} + +// checkResourceType checks a specific resource type for Application Insights hidden-link configuration +func (r *AzurermAppServiceAppInsightsHiddenLinkRule) checkResourceType(runner tflint.Runner, resourceType string) error { + resources, err := runner.GetResourceContent(resourceType, &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{ + {Name: appServiceAppSettingsAttrName}, + }, + Blocks: []hclext.BlockSchema{ + { + Type: "lifecycle", + Body: &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{ + {Name: appServiceIgnoreChangesAttrName}, + }, + }, + }, + { + Type: "site_config", + Body: &hclext.BodySchema{ + Attributes: []hclext.AttributeSchema{ + {Name: appServiceSiteConfigAppInsightsConnectionKey}, + {Name: appServiceSiteConfigAppInsightsKey}, + }, + }, + }, + }, + }, 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 Application Insights is configured + hasAppInsights := false + + // Check in app_settings + if appSettingsAttr, exists := resource.Body.Attributes[appServiceAppSettingsAttrName]; exists { + err := runner.EvaluateExpr(appSettingsAttr.Expr, func(val map[string]string) error { + for key := range val { + if strings.EqualFold(key, appServiceAppInsightsConnectionKey) || strings.EqualFold(key, appServiceAppInsightsInstrumentKey) { + hasAppInsights = true + break + } + } + return nil + }, nil) + + if err != nil { + return err + } + } + + // Check in site_config block + if !hasAppInsights { + for _, block := range resource.Body.Blocks { + if block.Type == "site_config" { + // Check for application_insights_connection_string + if attr, exists := block.Body.Attributes[appServiceSiteConfigAppInsightsConnectionKey]; exists { + err := runner.EvaluateExpr(attr.Expr, func(val string) error { + if val != "" { + hasAppInsights = true + } + return nil + }, nil) + if err != nil { + return err + } + } + + // Check for application_insights_key + if !hasAppInsights { + if attr, exists := block.Body.Attributes[appServiceSiteConfigAppInsightsKey]; exists { + err := runner.EvaluateExpr(attr.Expr, func(val string) error { + if val != "" { + hasAppInsights = true + } + return nil + }, nil) + if err != nil { + return err + } + } + } + break + } + } + } + + // If Application Insights is not configured, skip this resource + if !hasAppInsights { + logger.Debug("no Application Insights configuration found", "resource type", resource.Labels[0], "resource name", resource.Labels[1]) + continue + } + + // Check if lifecycle block exists and contains proper ignore_changes + hasProperIgnoreChanges := false + + for _, block := range resource.Body.Blocks { + if block.Type == "lifecycle" { + if ignoreChangesAttr, ok := block.Body.Attributes[appServiceIgnoreChangesAttrName]; ok { + // Check if ignore_changes = all (keyword) + if keyword := hcl.ExprAsKeyword(ignoreChangesAttr.Expr); keyword == "all" { + hasProperIgnoreChanges = true + break + } + + // Parse the ignore_changes expression to find ignored tag keys + foundTags := 0 + tagsIgnored := false + if exprs, diags := hcl.ExprList(ignoreChangesAttr.Expr); !diags.HasErrors() { + for _, expr := range exprs { + if traversal, diags := hcl.AbsTraversalForExpr(expr); diags == nil { + if len(traversal) == 1 { + if root, ok := traversal[0].(hcl.TraverseRoot); ok && root.Name == "tags" { + tagsIgnored = true + break + } + } else if len(traversal) == 2 { + if root, ok := traversal[0].(hcl.TraverseRoot); ok && root.Name == "tags" { + if index, ok := traversal[1].(hcl.TraverseIndex); ok && index.Key.Type() == cty.String { + tagKey := index.Key.AsString() + for _, requiredTag := range appServiceRequiredHiddenLinkTags { + if tagKey == requiredTag { + foundTags++ + break + } + } + } + } + } + } + } + } + if tagsIgnored || foundTags == len(appServiceRequiredHiddenLinkTags) { + hasProperIgnoreChanges = true + } + break + } + } + } + + // Emit issue if Application Insights is configured but hidden-link tags are not properly ignored + if !hasProperIgnoreChanges { + issue := "Application Insights hidden-link tags should be included in ignore_changes" + if err := runner.EmitIssue(r, issue, resource.DefRange); err != nil { + return err + } + } + } + + return nil +} + +// Check checks whether hidden-link tags are ignored when Application Insights is configured +func (r *AzurermAppServiceAppInsightsHiddenLinkRule) Check(runner tflint.Runner) error { + for _, resourceType := range appServiceResourceTypes { + if err := r.checkResourceType(runner, resourceType); err != nil { + return err + } + } + + return nil +} diff --git a/rules/azurerm_app_service_app_insights_hidden_link_test.go b/rules/azurerm_app_service_app_insights_hidden_link_test.go new file mode 100644 index 00000000..7d32d720 --- /dev/null +++ b/rules/azurerm_app_service_app_insights_hidden_link_test.go @@ -0,0 +1,678 @@ +package rules + +import ( + "testing" + + hcl "github.com/hashicorp/hcl/v2" + "github.com/terraform-linters/tflint-plugin-sdk/helper" +) + +func Test_AzurermAppServiceAppInsightsHiddenLink(t *testing.T) { + cases := []struct { + Name string + Content string + Expected helper.Issues + }{ + // Linux Web App tests + { + Name: "Linux Web App - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 43}, + }, + }, + }, + }, + { + Name: "Linux Web App - instrumentation key without ignore_changes - should fail", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example-key" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 43}, + }, + }, + }, + }, + // Linux Web App Slot tests + { + Name: "Linux Web App Slot - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_linux_web_app_slot" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App Slot - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_linux_web_app_slot" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App Slot - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_linux_web_app_slot" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 48}, + }, + }, + }, + }, + // Windows Web App tests + { + Name: "Windows Web App - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_windows_web_app" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_windows_web_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_windows_web_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 45}, + }, + }, + }, + }, + { + Name: "Windows Web App - instrumentation key without ignore_changes - should fail", + Content: ` +resource "azurerm_windows_web_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example-key" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 45}, + }, + }, + }, + }, + // Windows Web App Slot tests + { + Name: "Windows Web App Slot - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_windows_web_app_slot" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App Slot - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_windows_web_app_slot" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App Slot - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_windows_web_app_slot" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 50}, + }, + }, + }, + }, + // Linux Function App tests + { + Name: "Linux Function App - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_linux_function_app" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Function App - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_linux_function_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Function App - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_linux_function_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 48}, + }, + }, + }, + }, + // Linux Function App Slot tests + { + Name: "Linux Function App Slot - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_linux_function_app_slot" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Function App Slot - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_linux_function_app_slot" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Function App Slot - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_linux_function_app_slot" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 53}, + }, + }, + }, + }, + // Windows Function App tests + { + Name: "Windows Function App - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_windows_function_app" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Function App - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_windows_function_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Function App - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_windows_function_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 50}, + }, + }, + }, + }, + // Windows Function App Slot tests + { + Name: "Windows Function App Slot - no Application Insights configuration - should pass", + Content: ` +resource "azurerm_windows_function_app_slot" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Function App Slot - Application Insights with proper ignore_changes - should pass", + Content: ` +resource "azurerm_windows_function_app_slot" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + tags["hidden-link: /app-insights-instrumentation-key"], + tags["hidden-link: /app-insights-resource-id"], + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Function App Slot - Application Insights connection string without ignore_changes - should fail", + Content: ` +resource "azurerm_windows_function_app_slot" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 55}, + }, + }, + }, + }, + // Mixed scenarios + { + Name: "Application Insights with partial ignore_changes - should fail", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } + + lifecycle { + ignore_changes = [ + tags["hidden-link: /app-insights-conn-string"], + ] + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 43}, + }, + }, + }, + }, + // Site Config tests + { + Name: "Linux Function App - site_config with application_insights_connection_string without ignore_changes - should fail", + Content: ` +resource "azurerm_linux_function_app" "example" { + site_config { + application_insights_connection_string = "example-connection" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 48}, + }, + }, + }, + }, + { + Name: "Windows Function App - site_config with application_insights_key without ignore_changes - should fail", + Content: ` +resource "azurerm_windows_function_app" "example" { + site_config { + application_insights_key = "example-key" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 50}, + }, + }, + }, + }, + { + Name: "Function App Slot - site_config with empty application_insights_connection_string - should pass", + Content: ` +resource "azurerm_linux_function_app_slot" "example" { + site_config { + application_insights_connection_string = "" + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Mixed configuration - app_settings and site_config with Application Insights - should fail", + Content: ` +resource "azurerm_linux_function_app" "example" { + app_settings = { + "SOME_OTHER_SETTING" = "value" + } + + site_config { + application_insights_connection_string = "example-connection" + } +} +`, + Expected: helper.Issues{ + { + Rule: NewAzurermAppServiceAppInsightsHiddenLinkRule(), + Message: "Application Insights hidden-link tags should be included in ignore_changes", + Range: hcl.Range{ + Filename: "resource.tf", + Start: hcl.Pos{Line: 2, Column: 1}, + End: hcl.Pos{Line: 2, Column: 48}, + }, + }, + }, + }, + // Tests for ignoring entire tags map + { + Name: "Linux Web App - Application Insights with tags ignored - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPLICATIONINSIGHTS_CONNECTION_STRING" = "example" + } + + lifecycle { + ignore_changes = [ + tags, + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Function App - Application Insights with tags ignored - should pass", + Content: ` +resource "azurerm_windows_function_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = [ + tags, + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Function App Slot - site_config with Application Insights and tags ignored - should pass", + Content: ` +resource "azurerm_linux_function_app_slot" "example" { + site_config { + application_insights_connection_string = "example-connection" + } + + lifecycle { + ignore_changes = [ + tags, + ] + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Linux Web App - Application Insights with ignore_changes = all - should pass", + Content: ` +resource "azurerm_linux_web_app" "example" { + app_settings = { + "APPINSIGHTS_INSTRUMENTATIONKEY" = "example" + } + + lifecycle { + ignore_changes = all + } +} +`, + Expected: helper.Issues{}, + }, + { + Name: "Windows Web App - Application Insights with ignore_changes = all - should pass", + Content: ` +resource "azurerm_windows_web_app" "example" { + site_config { + application_insights_key = "example-key" + } + + lifecycle { + ignore_changes = all + } +} +`, + Expected: helper.Issues{}, + }, + } + + rule := NewAzurermAppServiceAppInsightsHiddenLinkRule() + + 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..c183c304 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{ + NewAzurermAppServiceAppInsightsHiddenLinkRule(), 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..38ce1665 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_app_insights_hidden_link](rules/azurerm_app_service_app_insights_hidden_link.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)|✔|