From 37b5da895bcac45a617fee00f9de2bececb3b26a Mon Sep 17 00:00:00 2001 From: Nikita Pivkin Date: Thu, 15 Jan 2026 01:43:04 +0600 Subject: [PATCH] feat(misconf): support for azurerm_*_web_app (#9944) Signed-off-by: nikpivkin --- pkg/iac/adapters/arm/appservice/adapt.go | 2 + pkg/iac/adapters/arm/appservice/adapt_test.go | 5 +- .../terraform/azure/appservice/adapt.go | 58 ++++++++- .../terraform/azure/appservice/adapt_test.go | 114 +++++++++++++++--- .../providers/azure/appservice/appservice.go | 1 + pkg/iac/rego/schemas/cloud.json | 4 + 6 files changed, 164 insertions(+), 20 deletions(-) diff --git a/pkg/iac/adapters/arm/appservice/adapt.go b/pkg/iac/adapters/arm/appservice/adapt.go index 9df3f8dff0..710be93200 100644 --- a/pkg/iac/adapters/arm/appservice/adapt.go +++ b/pkg/iac/adapters/arm/appservice/adapt.go @@ -3,6 +3,7 @@ package appservice import ( "github.com/aquasecurity/trivy/pkg/iac/providers/azure/appservice" "github.com/aquasecurity/trivy/pkg/iac/scanners/azure" + "github.com/aquasecurity/trivy/pkg/iac/types" ) func Adapt(deployment azure.Deployment) appservice.AppService { @@ -44,6 +45,7 @@ func adaptService(resource azure.Resource) appservice.Service { siteConfig := props.GetMapValue("siteConfig") return appservice.Service{ Metadata: resource.Metadata, + Resource: types.String("Microsoft.Web/sites", resource.Metadata), EnableClientCert: props.GetMapValue("clientCertEnabled").AsBoolValue(false, props.GetMetadata()), HTTPSOnly: props.GetMapValue("httpsOnly").AsBoolValue(false, props.GetMetadata()), Identity: appservice.Identity{ diff --git a/pkg/iac/adapters/arm/appservice/adapt_test.go b/pkg/iac/adapters/arm/appservice/adapt_test.go index 42e24fd52a..19fe144a33 100644 --- a/pkg/iac/adapters/arm/appservice/adapt_test.go +++ b/pkg/iac/adapters/arm/appservice/adapt_test.go @@ -25,7 +25,9 @@ func TestAdapt(t *testing.T) { ] }`, expected: appservice.AppService{ - Services: []appservice.Service{{}}, + Services: []appservice.Service{{ + Resource: types.StringTest("Microsoft.Web/sites"), + }}, FunctionApps: []appservice.FunctionApp{{}}, }, }, @@ -58,6 +60,7 @@ func TestAdapt(t *testing.T) { }`, expected: appservice.AppService{ Services: []appservice.Service{{ + Resource: types.StringTest("Microsoft.Web/sites"), EnableClientCert: types.BoolTest(true), HTTPSOnly: types.BoolTest(true), Identity: appservice.Identity{ diff --git a/pkg/iac/adapters/terraform/azure/appservice/adapt.go b/pkg/iac/adapters/terraform/azure/appservice/adapt.go index fcbd0d77ec..09330eb107 100644 --- a/pkg/iac/adapters/terraform/azure/appservice/adapt.go +++ b/pkg/iac/adapters/terraform/azure/appservice/adapt.go @@ -18,12 +18,17 @@ func adaptServices(modules terraform.Modules) []appservice.Service { for _, resource := range modules.GetResourcesByType("azurerm_app_service") { services = append(services, adaptService(resource)) } + for _, resource := range modules.GetResourcesByType("azurerm_linux_web_app", "azurerm_windows_web_app") { + services = append(services, adaptWebApp(resource)) + } return services } func adaptFunctionApps(modules terraform.Modules) []appservice.FunctionApp { var functionApps []appservice.FunctionApp - for _, resource := range modules.GetResourcesByType("azurerm_function_app") { + for _, resource := range modules.GetResourcesByType( + "azurerm_function_app", "azurerm_linux_function_app", "azurerm_windows_function_app", + ) { functionApps = append(functionApps, adaptFunctionApp(resource)) } return functionApps @@ -32,6 +37,7 @@ func adaptFunctionApps(modules terraform.Modules) []appservice.FunctionApp { func adaptService(resource *terraform.Block) appservice.Service { service := appservice.Service{ Metadata: resource.GetMetadata(), + Resource: types.String(resource.TypeLabel(), resource.GetMetadata()), EnableClientCert: resource.GetAttribute("client_cert_enabled").AsBoolValueOrDefault(false, resource), HTTPSOnly: resource.GetAttribute("https_only").AsBoolValueOrDefault(false, resource), Site: appservice.Site{ @@ -74,3 +80,53 @@ func adaptFunctionApp(resource *terraform.Block) appservice.FunctionApp { HTTPSOnly: resource.GetAttribute("https_only").AsBoolValueOrDefault(false, resource), } } + +func adaptWebApp(resource *terraform.Block) appservice.Service { + service := appservice.Service{ + Metadata: resource.GetMetadata(), + Resource: types.String(resource.TypeLabel(), resource.GetMetadata()), + EnableClientCert: resource.GetAttribute("client_certificate_enabled").AsBoolValueOrDefault(false, resource), + HTTPSOnly: resource.GetAttribute("https_only").AsBoolValueOrDefault(false, resource), + Site: appservice.Site{ + Metadata: resource.GetMetadata(), + FTPSState: types.StringDefault("Disabled", resource.GetMetadata()), + MinimumTLSVersion: types.StringDefault("1.2", resource.GetMetadata()), + }, + } + + if identityBlock := resource.GetBlock("identity"); identityBlock.IsNotNil() { + service.Identity = appservice.Identity{ + Metadata: identityBlock.GetMetadata(), + Type: identityBlock.GetAttribute("type").AsStringValueOrDefault("", identityBlock), + } + } + + if authBlock := resource.GetBlock("auth_settings"); authBlock.IsNotNil() { + service.Authentication = appservice.Authentication{ + Metadata: authBlock.GetMetadata(), + Enabled: authBlock.GetAttribute("enabled").AsBoolValueOrDefault(false, authBlock), + } + } + + if siteBlock := resource.GetBlock("site_config"); siteBlock.IsNotNil() { + service.Site = appservice.Site{ + Metadata: siteBlock.GetMetadata(), + EnableHTTP2: siteBlock.GetAttribute("http2_enabled").AsBoolValueOrDefault(false, siteBlock), + MinimumTLSVersion: siteBlock.GetAttribute("minimum_tls_version").AsStringValueOrDefault("1.2", siteBlock), + FTPSState: siteBlock.GetAttribute("ftps_state").AsStringValueOrDefault("Disabled", siteBlock), + } + + if appStack := siteBlock.GetBlock("application_stack"); appStack.IsNotNil() { + switch resource.TypeLabel() { + case "azurerm_linux_web_app": + service.Site.PHPVersion = appStack.GetAttribute("php_version").AsStringValueOrDefault("", appStack) + service.Site.PythonVersion = appStack.GetAttribute("python_version").AsStringValueOrDefault("", appStack) + case "azurerm_windows_web_app": + // azurerm_windows_web_app does not support configuring the python version + appStack := siteBlock.GetBlock("application_stack") + service.Site.PHPVersion = appStack.GetAttribute("php_version").AsStringValueOrDefault("", appStack) + } + } + } + return service +} diff --git a/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go b/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go index 1049f5c96e..0f3b57da10 100644 --- a/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go +++ b/pkg/iac/adapters/terraform/azure/appservice/adapt_test.go @@ -16,7 +16,7 @@ func Test_adaptService(t *testing.T) { tests := []struct { name string terraform string - expected appservice.Service + expected appservice.AppService }{ { name: "configured", @@ -39,18 +39,21 @@ func Test_adaptService(t *testing.T) { } } `, - expected: appservice.Service{ - EnableClientCert: iacTypes.BoolTest(true), - Identity: appservice.Identity{ - Type: iacTypes.StringTest("UserAssigned"), - }, - Authentication: appservice.Authentication{ - Enabled: iacTypes.BoolTest(true), - }, - Site: appservice.Site{ - EnableHTTP2: iacTypes.BoolTest(true), - MinimumTLSVersion: iacTypes.StringTest("1.0"), - }, + expected: appservice.AppService{ + Services: []appservice.Service{{ + Resource: iacTypes.StringTest("azurerm_app_service"), + EnableClientCert: iacTypes.BoolTest(true), + Identity: appservice.Identity{ + Type: iacTypes.StringTest("UserAssigned"), + }, + Authentication: appservice.Authentication{ + Enabled: iacTypes.BoolTest(true), + }, + Site: appservice.Site{ + EnableHTTP2: iacTypes.BoolTest(true), + MinimumTLSVersion: iacTypes.StringTest("1.0"), + }, + }}, }, }, { @@ -59,10 +62,73 @@ func Test_adaptService(t *testing.T) { resource "azurerm_app_service" "my_example" { } `, - expected: appservice.Service{ - Site: appservice.Site{ - MinimumTLSVersion: iacTypes.StringTest("1.2"), - }, + expected: appservice.AppService{ + Services: []appservice.Service{{ + Resource: iacTypes.StringTest("azurerm_app_service"), + Site: appservice.Site{ + MinimumTLSVersion: iacTypes.StringTest("1.2"), + }, + }}, + }, + }, + { + name: "empty azurerm_windows_web_app", + terraform: `resource "azurerm_windows_web_app" "example" { + name = "example" +}`, + expected: appservice.AppService{ + Services: []appservice.Service{{ + Resource: iacTypes.StringTest("azurerm_windows_web_app"), + Site: appservice.Site{ + MinimumTLSVersion: iacTypes.StringTest("1.2"), + FTPSState: iacTypes.StringTest("Disabled"), + }, + }}, + }, + }, + { + name: "complete azurerm_windows_web_app", + terraform: `resource "azurerm_windows_web_app" "example" { + https_only = true + client_certificate_enabled = true + + identity { + type = "SystemAssigned" + } + + auth_settings { + enabled = true + } + + site_config { + http2_enabled = true + minimum_tls_version = "1.3" + ftps_state = "FtpsOnly" + + application_stack { + php_version = "7.4" + } + } +} +`, + expected: appservice.AppService{ + Services: []appservice.Service{{ + Resource: iacTypes.StringTest("azurerm_windows_web_app"), + HTTPSOnly: iacTypes.BoolTest(true), + EnableClientCert: iacTypes.BoolTest(true), + Identity: appservice.Identity{ + Type: iacTypes.StringTest("SystemAssigned"), + }, + Authentication: appservice.Authentication{ + Enabled: iacTypes.BoolTest(true), + }, + Site: appservice.Site{ + EnableHTTP2: iacTypes.BoolTest(true), + MinimumTLSVersion: iacTypes.StringTest("1.3"), + FTPSState: iacTypes.StringTest("FtpsOnly"), + PHPVersion: iacTypes.StringTest("7.4"), + }, + }}, }, }, } @@ -70,7 +136,7 @@ func Test_adaptService(t *testing.T) { for _, test := range tests { t.Run(test.name, func(t *testing.T) { modules := tftestutil.CreateModulesFromSource(t, test.terraform, ".tf") - adapted := adaptService(modules.GetBlocks()[0]) + adapted := Adapt(modules) testutil.AssertDefsecEqual(t, test.expected, adapted) }) } @@ -104,6 +170,18 @@ func Test_adaptFunctionApp(t *testing.T) { HTTPSOnly: iacTypes.BoolTest(false), }, }, + { + name: "os-specific resource", + terraform: ` + resource "azurerm_windows_function_app" "my_example" { + name = "test-azure-functions" + https_only = true + } +`, + expected: appservice.FunctionApp{ + HTTPSOnly: iacTypes.BoolTest(true), + }, + }, } for _, test := range tests { diff --git a/pkg/iac/providers/azure/appservice/appservice.go b/pkg/iac/providers/azure/appservice/appservice.go index 9e9efbed3a..f597b126b7 100755 --- a/pkg/iac/providers/azure/appservice/appservice.go +++ b/pkg/iac/providers/azure/appservice/appservice.go @@ -21,6 +21,7 @@ type Authentication struct { type Service struct { Metadata iacTypes.Metadata + Resource iacTypes.StringValue EnableClientCert iacTypes.BoolValue HTTPSOnly iacTypes.BoolValue Identity Identity diff --git a/pkg/iac/rego/schemas/cloud.json b/pkg/iac/rego/schemas/cloud.json index 5369f4c653..6d9e363c31 100644 --- a/pkg/iac/rego/schemas/cloud.json +++ b/pkg/iac/rego/schemas/cloud.json @@ -4566,6 +4566,10 @@ "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Identity" }, + "platform": { + "type": "object", + "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.types.StringValue" + }, "site": { "type": "object", "$ref": "#/definitions/github.com.aquasecurity.trivy.pkg.iac.providers.azure.appservice.Site"