From 71b205da24bbf1ab352b8d51293d2f9bdd8ca4d5 Mon Sep 17 00:00:00 2001 From: Bernie White Date: Mon, 25 Mar 2024 01:24:55 +1000 Subject: [PATCH] Fixes Azure.AppService.PHPVersion when phpVersion is null #2768 (#2770) --- data/policy-ignore.json | 8 + docs/CHANGELOG-v1.md | 4 + docs/en/rules/Azure.AppService.PHPVersion.md | 96 ++-- docs/examples-appservice.bicep | 24 + docs/examples-appservice.json | 29 +- .../rules/Azure.AppService.Rule.ps1 | 35 +- .../rules/Azure.AppService.Rule.yaml | 14 +- .../Azure.AppService.Tests.ps1 | 55 ++- .../Azure.Baseline.Tests.ps1 | 26 +- .../PolicyAssignmentVisitorTests.cs | 2 +- .../Resources.AppService.json | 454 +++++++++++++++++- 11 files changed, 648 insertions(+), 99 deletions(-) diff --git a/data/policy-ignore.json b/data/policy-ignore.json index 047fef2a8d7..a22d495a35f 100644 --- a/data/policy-ignore.json +++ b/data/policy-ignore.json @@ -228,5 +228,13 @@ ], "reason": "Duplicate", "value": "Azure.AppService.WebSecureFtp" + }, + { + "policyDefinitionIds": [ + "/providers/Microsoft.Authorization/policyDefinitions/7261b898-8a84-4db8-9e04-18527132abb3", + "/providers/Microsoft.Authorization/policyDefinitions/f466b2a6-823d-470d-8ea5-b031e72d79ae" + ], + "reason": "Duplicate", + "value": "Azure.AppService.PHPVersion" } ] diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 72d174a8bc8..e56db4e7422 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -38,6 +38,10 @@ What's changed since pre-release v1.35.0-B0030: - Updated `Azure.AppService.NETVersion` to detect out of date .NET versions including .NET 5/6/7 by @BernieWhite. [#2766](https://github.com/Azure/PSRule.Rules.Azure/issues/2766) - Bumped rule set to `2024_03`. + - Updated `Azure.AppService.PHPVersion` to detect out of date PHP versions before 8.2 by @BernieWhite. + [#2768](https://github.com/Azure/PSRule.Rules.Azure/issues/2768) + - Fixed `Azure.AppService.PHPVersion` check fails when phpVersion is null. + - Bumped rule set to `2024_03`. - General improvements: - Quality updates to rule documentation by @BernieWhite. [#2570](https://github.com/Azure/PSRule.Rules.Azure/issues/2570) diff --git a/docs/en/rules/Azure.AppService.PHPVersion.md b/docs/en/rules/Azure.AppService.PHPVersion.md index 090630383ec..54eeb1cfe83 100644 --- a/docs/en/rules/Azure.AppService.PHPVersion.md +++ b/docs/en/rules/Azure.AppService.PHPVersion.md @@ -1,8 +1,8 @@ --- -reviewed: 2022-05-14 +reviewed: 2024-03-24 severity: Important pillar: Security -category: Deployment +category: SE:02 Secured development lifecycle resource: App Service online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AppService.PHPVersion/ --- @@ -16,11 +16,15 @@ Configure applications to use newer PHP runtime versions. ## DESCRIPTION Within a App Service app, the version of PHP runtime used to run application/ site code is configurable. -Older versions of PHP may not use the latest security features. + +Overtime, a specific version of PHP may become outdated and no longer supported by Microsoft in Azure App Service. +This can lead to security vulnerabilities or are simply not able to use the latest security features. + +PHP 8.0 and 8.1 are approaching end of support. ## RECOMMENDATION -Consider updating the site to use a newer PHP runtime version such as `7.4`. +Consider updating the site to use a newer PHP runtime version such as `8.2`. ## EXAMPLES @@ -28,37 +32,36 @@ Consider updating the site to use a newer PHP runtime version such as `7.4`. To deploy App Services that pass this rule: -- Set `properties.siteConfig.phpVersion` to a minimum of `7.0`. +- Set `properties.siteConfig.linuxFxVersion` to a minimum of `PHP|8.2`. For example: ```json { - "type": "Microsoft.Web/sites", - "apiVersion": "2021-03-01", - "name": "[parameters('name')]", - "location": "[parameters('location')]", - "identity": { - "type": "SystemAssigned" - }, - "kind": "web", - "properties": { - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]", - "httpsOnly": true, - "siteConfig": { - "alwaysOn": true, - "minTlsVersion": "1.2", - "ftpsState": "FtpsOnly", - "remoteDebuggingEnabled": false, - "http20Enabled": true, - "netFrameworkVersion": "OFF", - "phpVersion": "7.4" - } - }, - "tags": "[parameters('tags')]", - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]" - ] + "type": "Microsoft.Web/sites", + "apiVersion": "2023-01-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "kind": "web", + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]", + "httpsOnly": true, + "clientAffinityEnabled": false, + "siteConfig": { + "alwaysOn": true, + "minTlsVersion": "1.2", + "ftpsState": "Disabled", + "http20Enabled": true, + "healthCheckPath": "/healthz", + "linuxFxVersion": "PHP|8.2" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]" + ] } ``` @@ -66,12 +69,12 @@ For example: To deploy App Services that pass this rule: -- Set `properties.siteConfig.phpVersion` to a minimum of `7.0`. +- Set `properties.siteConfig.linuxFxVersion` to a minimum of `PHP|8.2`. For example: ```bicep -resource webAppPHP 'Microsoft.Web/sites@2021-03-01' = { +resource php 'Microsoft.Web/sites@2023-01-01' = { name: name location: location identity: { @@ -81,22 +84,35 @@ resource webAppPHP 'Microsoft.Web/sites@2021-03-01' = { properties: { serverFarmId: plan.id httpsOnly: true + clientAffinityEnabled: false siteConfig: { alwaysOn: true minTlsVersion: '1.2' - ftpsState: 'FtpsOnly' - remoteDebuggingEnabled: false + ftpsState: 'Disabled' http20Enabled: true - netFrameworkVersion: 'OFF' - phpVersion: '7.4' + healthCheckPath: '/healthz' + linuxFxVersion: 'PHP|8.2' } } - tags: tags } ``` +### Configure with Azure Policy + +To address this issue at runtime use the following policies: + +- [App Service apps that use PHP should use a specified 'PHP version'](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/App%20Service/Webapp_Audit_PHP_Latest.json) + `/providers/Microsoft.Authorization/policyDefinitions/7261b898-8a84-4db8-9e04-18527132abb3` +- [App Service app slots that use PHP should use a specified 'PHP version'](https://github.com/Azure/azure-policy/blob/master/built-in-policies/policyDefinitions/App%20Service/Webapp_Slot_Audit_PHP_Latest.json) + `/providers/Microsoft.Authorization/policyDefinitions/f466b2a6-823d-470d-8ea5-b031e72d79ae` + +## NOTES + +From November 2022 - PHP is only supported on Linux-based plans. + ## LINKS -- [Security design principles](https://learn.microsoft.com/azure/architecture/framework/security/security-principles#protect-against-code-level-vulnerabilities) -- [Set PHP Version](https://docs.microsoft.com/azure/app-service/configure-language-php#set-php-version) -- [Azure deployment reference](https://docs.microsoft.com/azure/templates/microsoft.web/sites#siteconfig) +- [SE:02 Secured development lifecycle](https://learn.microsoft.com/azure/well-architected/security/secure-development-lifecycle) +- [Set PHP Version](https://learn.microsoft.com/azure/app-service/configure-language-php?pivots=platform-linux#set-php-version) +- [PHP on App Service](https://github.com/Azure/app-service-linux-docs/blob/master/Runtime_Support/php_support.md) +- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.web/sites) diff --git a/docs/examples-appservice.bicep b/docs/examples-appservice.bicep index af46445e7d6..1579a9aa989 100644 --- a/docs/examples-appservice.bicep +++ b/docs/examples-appservice.bicep @@ -36,6 +36,7 @@ resource web 'Microsoft.Web/sites@2023-01-01' = { properties: { serverFarmId: plan.id httpsOnly: true + clientAffinityEnabled: false siteConfig: { alwaysOn: true minTlsVersion: '1.2' @@ -54,6 +55,29 @@ resource web 'Microsoft.Web/sites@2023-01-01' = { } } +// An example PHP Web App running on a Linux App Services Plan. +resource php 'Microsoft.Web/sites@2023-01-01' = { + name: name + location: location + identity: { + type: 'SystemAssigned' + } + kind: 'web' + properties: { + serverFarmId: plan.id + httpsOnly: true + clientAffinityEnabled: false + siteConfig: { + alwaysOn: true + minTlsVersion: '1.2' + ftpsState: 'Disabled' + http20Enabled: true + healthCheckPath: '/healthz' + linuxFxVersion: 'PHP|8.2' + } + } +} + // Disable basic publishing credentials for FTP. resource ftp 'Microsoft.Web/sites/basicPublishingCredentialsPolicies@2023-01-01' = { parent: web diff --git a/docs/examples-appservice.json b/docs/examples-appservice.json index 83bcd81f7ac..811e0f7bc57 100644 --- a/docs/examples-appservice.json +++ b/docs/examples-appservice.json @@ -5,7 +5,7 @@ "_generator": { "name": "bicep", "version": "0.26.54.24096", - "templateHash": "210994321631769997" + "templateHash": "10435733640424545318" } }, "parameters": { @@ -55,6 +55,7 @@ "properties": { "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]", "httpsOnly": true, + "clientAffinityEnabled": false, "siteConfig": { "alwaysOn": true, "minTlsVersion": "1.2", @@ -75,6 +76,32 @@ "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]" ] }, + { + "type": "Microsoft.Web/sites", + "apiVersion": "2023-01-01", + "name": "[parameters('name')]", + "location": "[parameters('location')]", + "identity": { + "type": "SystemAssigned" + }, + "kind": "web", + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]", + "httpsOnly": true, + "clientAffinityEnabled": false, + "siteConfig": { + "alwaysOn": true, + "minTlsVersion": "1.2", + "ftpsState": "Disabled", + "http20Enabled": true, + "healthCheckPath": "/healthz", + "linuxFxVersion": "PHP|8.2" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('planName'))]" + ] + }, { "type": "Microsoft.Web/sites/basicPublishingCredentialsPolicies", "apiVersion": "2023-01-01", diff --git a/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.ps1 b/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.ps1 index 6e767b79e33..96e1c40f65f 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.ps1 +++ b/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.ps1 @@ -71,20 +71,33 @@ Rule 'Azure.AppService.NETVersion' -Ref 'AZR-000075' -Type 'Microsoft.Web/sites' } # Synopsis: Configure applications to use newer PHP runtime versions. -Rule 'Azure.AppService.PHPVersion' -Ref 'AZR-000076' -Type 'Microsoft.Web/sites', 'Microsoft.Web/sites/slots' -Tag @{ release = 'GA'; ruleSet = '2020_12'; 'Azure.WAF/pillar' = 'Security'; } { - $siteConfigs = @(GetWebSiteConfig | Where-Object { - ![String]::IsNullOrEmpty($_.Properties.phpVersion) - }) +Rule 'Azure.AppService.PHPVersion' -Ref 'AZR-000076' -Type 'Microsoft.Web/sites', 'Microsoft.Web/sites/slots' -Tag @{ release = 'GA'; ruleSet = '2024_03'; 'Azure.WAF/pillar' = 'Security'; } { + $siteConfigs = @(GetWebSiteConfig) if ($siteConfigs.Length -eq 0) { - return AnyOf { - $Assert.HasDefaultValue($TargetObject, 'Properties.siteConfig.phpVersion', 'OFF') - $Assert.Version($TargetObject, 'Properties.siteConfig.phpVersion', '>=7.0') + if ($Assert.HasFieldValue($TargetObject, 'properties.siteConfig.linuxFxVersion').Result -and $TargetObject.properties.siteConfig.linuxFxVersion -like 'PHP|*') { + $linuxVersion = $TargetObject.properties.siteConfig.linuxFxVersion.Split('|')[1]; + return $Assert.Version($linuxVersion, '.', '>=8.2').PathPrefix('properties.siteConfig.linuxFxVersion'); + } + elseif (!$Assert.HasDefaultValue($TargetObject, 'properties.siteConfig.phpVersion', 'OFF').Result -and + ![String]::IsNullOrEmpty($TargetObject.properties.siteConfig.phpVersion)) { + return $Assert.Version($TargetObject, 'properties.siteConfig.phpVersion', '>=8.2'); + } + else { + return $Assert.Pass(); } } foreach ($siteConfig in $siteConfigs) { - AnyOf { - $Assert.HasFieldValue($siteConfig, 'Properties.phpVersion', 'OFF') - $Assert.Version($siteConfig, 'Properties.phpVersion', '>=7.0') + $path = $siteConfig._PSRule.path; + if ($Assert.HasFieldValue($siteConfig, 'properties.linuxFxVersion').Result -and $siteConfig.properties.linuxFxVersion -like 'PHP|*') { + $linuxVersion = $siteConfig.properties.linuxFxVersion.Split('|')[1]; + $Assert.Version($linuxVersion, '.', '>=8.2').PathPrefix("$path.properties.linuxFxVersion"); + } + elseif (!$Assert.HasDefaultValue($siteConfig, 'properties.phpVersion', 'OFF').Result -and + ![String]::IsNullOrEmpty($siteConfig.properties.phpVersion)) { + $Assert.Version($siteConfig, 'properties.phpVersion', '>=8.2').PathPrefix($path); + } + else { + $Assert.Pass(); } } } @@ -96,7 +109,7 @@ Rule 'Azure.AppService.AlwaysOn' -Ref 'AZR-000077' -Type 'Microsoft.Web/sites', return $Assert.HasFieldValue($TargetObject, 'Properties.siteConfig.alwaysOn', $True); } foreach ($siteConfig in $siteConfigs) { - $Assert.HasFieldValue($siteConfig, 'Properties.alwaysOn', $True); + $Assert.HasFieldValue($siteConfig, 'properties.alwaysOn', $True); } } diff --git a/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.yaml b/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.yaml index 37518107558..bb783491991 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.yaml +++ b/src/PSRule.Rules.Azure/rules/Azure.AppService.Rule.yaml @@ -124,13 +124,15 @@ spec: allOf: - type: '.' in: - - 'Microsoft.Web/sites' - - 'Microsoft.Web/sites/slots' + - Microsoft.Web/sites + - Microsoft.Web/sites/slots - anyOf: - field: kind exists: false - field: kind - equals: 'app' + in: + - app + - app,linux --- # Synopsis: App Service sites that are API apps. @@ -145,10 +147,10 @@ spec: allOf: - type: '.' in: - - 'Microsoft.Web/sites' - - 'Microsoft.Web/sites/slots' + - Microsoft.Web/sites + - Microsoft.Web/sites/slots - field: kind - equals: 'api' + equals: api --- # Synopsis: App Service sites that are Function apps. diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.AppService.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.AppService.Tests.ps1 index 8d5923d9f27..01d6e8dbf68 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.AppService.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.AppService.Tests.ps1 @@ -83,8 +83,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'site-A', 'site-A/staging', 'fn-app'; + $ruleResult.Length | Should -Be 5; + $ruleResult.TargetName | Should -BeIn 'site-A', 'site-A/staging', 'fn-app', 'site-c', 'site-d'; } It 'Azure.AppService.UseHTTPS' { @@ -100,8 +100,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'site-A', 'site-A/staging', 'fn-app'; + $ruleResult.Length | Should -Be 5; + $ruleResult.TargetName | Should -BeIn 'site-A', 'site-A/staging', 'fn-app', 'site-c', 'site-d'; } It 'Azure.AppService.MinTLS' { @@ -110,8 +110,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging', 'site-c', 'site-d'; $ruleResult.Detail.Reason.Path | Should -BeIn 'properties.siteConfig.minTlsVersion', 'resources[0].properties.minTlsVersion'; $ruleResult[0].Reason | Should -Not -BeNullOrEmpty; @@ -132,8 +132,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging', 'site-c', 'site-d'; # TODO: $ruleResult.Detail.Reason.Path | Should -BeIn 'properties.siteConfig.remoteDebuggingEnabled'; # Pass @@ -158,8 +158,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'site-A', 'site-A/staging', 'fn-app'; + $ruleResult.Length | Should -Be 5; + $ruleResult.TargetName | Should -BeIn 'site-A', 'site-A/staging', 'fn-app', 'site-c', 'site-d'; } It 'Azure.AppService.PHPVersion' { @@ -168,14 +168,17 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'site-B', 'site-B/staging', 'fn-app'; + # $ruleResult.Length | Should -Be 2; + $ruleResult.TargetName | Should -BeIn 'site-B', 'site-B/staging', 'site-d'; + $ruleResult[0].Reason | Should -BeExactly "Path resources[0].properties.phpVersion: The version '5.6.-1' does not match the constraint '>=8.2'."; + $ruleResult[1].Reason | Should -BeExactly "Path resources[0].properties.phpVersion: The version '5.6.-1' does not match the constraint '>=8.2'."; + $ruleResult[2].Reason | Should -BeExactly "Path properties.siteConfig.linuxFxVersion..: The version '8.1.-1' does not match the constraint '>=8.2'."; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -Be 'site-A', 'site-A/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -Be 'site-A', 'site-A/staging', 'fn-app', 'site-c'; } It 'Azure.AppService.AlwaysOn' { @@ -184,8 +187,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging', 'site-c', 'site-d'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); @@ -206,8 +209,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging', 'site-c', 'site-d'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); @@ -222,8 +225,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -Be 'site-B', 'site-B/staging', 'site-c', 'site-d'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); @@ -446,8 +449,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 2; - $ruleResult.TargetName | Should -BeIn 'site-B', 'site-B/staging'; + $ruleResult.Length | Should -Be 4; + $ruleResult.TargetName | Should -BeIn 'site-B', 'site-B/staging', 'site-c', 'site-d'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); @@ -462,8 +465,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'site-B', 'site-B/staging', 'site-A/staging'; + $ruleResult.Length | Should -Be 5; + $ruleResult.TargetName | Should -BeIn 'site-B', 'site-B/staging', 'site-A/staging', 'site-c', 'site-d'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); @@ -478,8 +481,8 @@ Describe 'Azure.AppService' -Tag 'AppService' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 1; - $ruleResult.TargetName | Should -BeIn 'site-B'; + $ruleResult.Length | Should -Be 3; + $ruleResult.TargetName | Should -BeIn 'site-B', 'site-c', 'site-d'; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 index 49a47af8012..74aee94ee4c 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 @@ -73,28 +73,28 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2020_12' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 175; + $filteredResult.Length | Should -Be 174; } It 'With Azure.GA_2021_03' { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2021_03' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 190; + $filteredResult.Length | Should -Be 189; } It 'With Azure.GA_2021_06' { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2021_06' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 204; + $filteredResult.Length | Should -Be 203; } It 'With Azure.GA_2021_09' { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2021_09' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 223; + $filteredResult.Length | Should -Be 222; } It 'With Azure.Preview_2021_09' { @@ -108,7 +108,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2021_12' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 249; + $filteredResult.Length | Should -Be 248; } It 'With Azure.Preview_2021_12' { @@ -122,7 +122,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2022_03' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 265; + $filteredResult.Length | Should -Be 264; } It 'With Azure.Preview_2022_03' { @@ -136,7 +136,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2022_06' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 269; + $filteredResult.Length | Should -Be 268; } It 'With Azure.Preview_2022_06' { @@ -150,7 +150,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2022_09' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 300; + $filteredResult.Length | Should -Be 299; } It 'With Azure.Preview_2022_09' { @@ -164,7 +164,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2022_12' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 338; + $filteredResult.Length | Should -Be 337; } It 'With Azure.Preview_2022_12' { @@ -178,7 +178,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2023_03' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 358; + $filteredResult.Length | Should -Be 357; } It 'With Azure.Preview_2023_03' { @@ -192,7 +192,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2023_06' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 373; + $filteredResult.Length | Should -Be 372; } It 'With Azure.Preview_2023_06' { @@ -206,7 +206,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2023_09' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 384; + $filteredResult.Length | Should -Be 383; } It 'With Azure.Preview_2023_09' { @@ -220,7 +220,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2023_12' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 393; + $filteredResult.Length | Should -Be 392; } It 'With Azure.Preview_2023_12' { diff --git a/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs b/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs index 3eb08f93e9a..65bdfa3afbd 100644 --- a/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs +++ b/tests/PSRule.Rules.Azure.Tests/PolicyAssignmentVisitorTests.cs @@ -95,7 +95,7 @@ public void GetPolicyDefinitionWithIgnore() var definitions = context.GetDefinitions(); Assert.NotNull(definitions); - Assert.Equal(115, definitions.Length); + Assert.Equal(114, definitions.Length); // Check category and version var actual = definitions.FirstOrDefault(definition => definition.DefinitionId == "/providers/Microsoft.Authorization/policyDefinitions/34c877ad-507e-4c82-993e-3452a6e0ad3c"); diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.AppService.json b/tests/PSRule.Rules.Azure.Tests/Resources.AppService.json index 4cb83e6a4cd..5e187ec682f 100644 --- a/tests/PSRule.Rules.Azure.Tests/Resources.AppService.json +++ b/tests/PSRule.Rules.Azure.Tests/Resources.AppService.json @@ -1343,7 +1343,7 @@ "index.php" ], "netFrameworkVersion": "v4.0", - "phpVersion": "5.6", + "phpVersion": "", "pythonVersion": "", "nodeVersion": "", "powerShellVersion": "~7", @@ -1502,5 +1502,457 @@ "SubscriptionId": "00000000-0000-0000-0000-000000000000" } ] + }, + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.Web/sites/site-c", + "name": "site-c", + "type": "Microsoft.Web/sites", + "kind": "app,linux", + "location": "East US", + "properties": { + "name": "site-c", + "state": "Running", + "hostNames": [ + "site-c.azurewebsites.net" + ], + "repositorySiteName": "site-c", + "owner": null, + "usageState": "Normal", + "enabled": true, + "adminEnabled": true, + "afdEnabled": false, + "enabledHostNames": [ + "site-c.azurewebsites.net", + "site-c.scm.azurewebsites.net" + ], + "siteProperties": { + "metadata": null, + "properties": [ + { + "name": "LinuxFxVersion", + "value": "PHP|8.2" + }, + { + "name": "WindowsFxVersion", + "value": null + } + ], + "appSettings": null + }, + "availabilityState": "Normal", + "sslCertificates": null, + "csrs": [], + "cers": null, + "siteMode": null, + "hostNameSslStates": [ + { + "name": "site-c.azurewebsites.net", + "sslState": "Disabled", + "ipBasedSslResult": null, + "virtualIP": null, + "virtualIPv6": null, + "thumbprint": null, + "certificateResourceId": null, + "toUpdate": null, + "toUpdateIpBasedSsl": null, + "ipBasedSslState": "NotConfigured", + "hostType": "Standard" + }, + { + "name": "site-c.scm.azurewebsites.net", + "sslState": "Disabled", + "ipBasedSslResult": null, + "virtualIP": null, + "virtualIPv6": null, + "thumbprint": null, + "certificateResourceId": null, + "toUpdate": null, + "toUpdateIpBasedSsl": null, + "ipBasedSslState": "NotConfigured", + "hostType": "Repository" + } + ], + "computeMode": null, + "serverFarm": null, + "serverFarmId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.Web/serverfarms/nnn", + "reserved": true, + "isXenon": false, + "hyperV": false, + "lastModifiedTimeUtc": "2024-03-24T13:48:53.3233333", + "storageRecoveryDefaultState": "Running", + "contentAvailabilityState": "Normal", + "runtimeAvailabilityState": "Normal", + "dnsConfiguration": {}, + "vnetRouteAllEnabled": false, + "containerAllocationSubnet": null, + "useContainerLocalhostBindings": null, + "vnetImagePullEnabled": false, + "vnetContentShareEnabled": false, + "siteConfig": { + "numberOfWorkers": 1, + "defaultDocuments": null, + "netFrameworkVersion": null, + "phpVersion": null, + "pythonVersion": null, + "nodeVersion": null, + "powerShellVersion": null, + "linuxFxVersion": "PHP|8.2", + "windowsFxVersion": null, + "windowsConfiguredStacks": null, + "requestTracingEnabled": null, + "remoteDebuggingEnabled": null, + "remoteDebuggingVersion": null, + "httpLoggingEnabled": null, + "azureMonitorLogCategories": null, + "acrUseManagedIdentityCreds": false, + "acrUserManagedIdentityID": null, + "logsDirectorySizeLimit": null, + "detailedErrorLoggingEnabled": null, + "publishingUsername": null, + "publishingPassword": null, + "appSettings": null, + "metadata": null, + "connectionStrings": null, + "machineKey": null, + "handlerMappings": null, + "documentRoot": null, + "scmType": null, + "use32BitWorkerProcess": null, + "webSocketsEnabled": null, + "alwaysOn": false, + "javaVersion": null, + "javaContainer": null, + "javaContainerVersion": null, + "appCommandLine": null, + "managedPipelineMode": null, + "virtualApplications": null, + "winAuthAdminState": null, + "winAuthTenantState": null, + "customAppPoolIdentityAdminState": null, + "customAppPoolIdentityTenantState": null, + "runtimeADUser": null, + "runtimeADUserPassword": null, + "loadBalancing": null, + "routingRules": null, + "experiments": null, + "limits": null, + "autoHealEnabled": null, + "autoHealRules": null, + "tracingOptions": null, + "vnetName": null, + "vnetRouteAllEnabled": null, + "vnetPrivatePortsCount": null, + "publicNetworkAccess": null, + "cors": null, + "push": null, + "apiDefinition": null, + "apiManagementConfig": null, + "autoSwapSlotName": null, + "localMySqlEnabled": null, + "managedServiceIdentityId": null, + "xManagedServiceIdentityId": null, + "keyVaultReferenceIdentity": null, + "ipSecurityRestrictions": null, + "ipSecurityRestrictionsDefaultAction": null, + "scmIpSecurityRestrictions": null, + "scmIpSecurityRestrictionsDefaultAction": null, + "scmIpSecurityRestrictionsUseMain": null, + "http20Enabled": false, + "minTlsVersion": null, + "minTlsCipherSuite": null, + "supportedTlsCipherSuites": null, + "scmMinTlsVersion": null, + "ftpsState": null, + "preWarmedInstanceCount": null, + "functionAppScaleLimit": 0, + "elasticWebAppScaleLimit": null, + "healthCheckPath": null, + "fileChangeAuditEnabled": null, + "functionsRuntimeScaleMonitoringEnabled": null, + "websiteTimeZone": null, + "minimumElasticInstanceCount": 0, + "azureStorageAccounts": null, + "http20ProxyFlag": null, + "sitePort": null, + "antivirusScanEnabled": null, + "storageType": null, + "sitePrivateLinkHostEnabled": null + }, + "functionAppConfig": null, + "daprConfig": null, + "deploymentId": "site-c", + "slotName": null, + "trafficManagerHostNames": null, + "sku": "Basic", + "scmSiteAlsoStopped": false, + "targetSwapSlot": null, + "hostingEnvironment": null, + "hostingEnvironmentProfile": null, + "clientAffinityEnabled": false, + "clientCertEnabled": false, + "clientCertMode": "Required", + "clientCertExclusionPaths": null, + "hostNamesDisabled": false, + "ipMode": "IPv4", + "vnetBackupRestoreEnabled": false, + "kind": "app,linux", + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "suspendedTill": null, + "siteDisabledReason": 0, + "cloningInfo": null, + "hostingEnvironmentId": null, + "resourceGroup": "rg-test", + "defaultHostName": "site-c.azurewebsites.net", + "slotSwapStatus": null, + "httpsOnly": true, + "endToEndEncryptionEnabled": false, + "functionsRuntimeAdminIsolationEnabled": false, + "redundancyMode": "None", + "inProgressOperationId": null, + "geoDistributions": null, + "privateEndpointConnections": [], + "publicNetworkAccess": "Enabled", + "buildVersion": null, + "targetBuildVersion": null, + "migrationState": null, + "eligibleLogCategories": "AppServiceAppLogs,AppServiceAuditLogs,AppServiceConsoleLogs,AppServiceHTTPLogs,AppServiceIPSecAuditLogs,AppServicePlatformLogs,ScanLogs", + "inFlightFeatures": [ + "SiteContainers" + ], + "storageAccountRequired": false, + "virtualNetworkSubnetId": null, + "keyVaultReferenceIdentity": "SystemAssigned", + "autoGeneratedDomainNameLabelScope": null, + "defaultHostNameScope": "Global", + "privateLinkIdentifiers": null, + "sshEnabled": null + } + }, + { + "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.Web/sites/site-d", + "name": "site-d", + "type": "Microsoft.Web/sites", + "kind": "app,linux", + "location": "East US", + "properties": { + "name": "site-d", + "state": "Running", + "hostNames": [ + "site-d.azurewebsites.net" + ], + "repositorySiteName": "site-d", + "owner": null, + "usageState": "Normal", + "enabled": true, + "adminEnabled": true, + "afdEnabled": false, + "enabledHostNames": [ + "site-d.azurewebsites.net", + "site-d.scm.azurewebsites.net" + ], + "siteProperties": { + "metadata": null, + "properties": [ + { + "name": "LinuxFxVersion", + "value": "PHP|8.2" + }, + { + "name": "WindowsFxVersion", + "value": null + } + ], + "appSettings": null + }, + "availabilityState": "Normal", + "sslCertificates": null, + "csrs": [], + "cers": null, + "siteMode": null, + "hostNameSslStates": [ + { + "name": "site-d.azurewebsites.net", + "sslState": "Disabled", + "ipBasedSslResult": null, + "virtualIP": null, + "virtualIPv6": null, + "thumbprint": null, + "certificateResourceId": null, + "toUpdate": null, + "toUpdateIpBasedSsl": null, + "ipBasedSslState": "NotConfigured", + "hostType": "Standard" + }, + { + "name": "site-d.scm.azurewebsites.net", + "sslState": "Disabled", + "ipBasedSslResult": null, + "virtualIP": null, + "virtualIPv6": null, + "thumbprint": null, + "certificateResourceId": null, + "toUpdate": null, + "toUpdateIpBasedSsl": null, + "ipBasedSslState": "NotConfigured", + "hostType": "Repository" + } + ], + "computeMode": null, + "serverFarm": null, + "serverFarmId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.Web/serverfarms/nnn", + "reserved": true, + "isXenon": false, + "hyperV": false, + "lastModifiedTimeUtc": "2024-03-24T13:48:53.3233333", + "storageRecoveryDefaultState": "Running", + "contentAvailabilityState": "Normal", + "runtimeAvailabilityState": "Normal", + "dnsConfiguration": {}, + "vnetRouteAllEnabled": false, + "containerAllocationSubnet": null, + "useContainerLocalhostBindings": null, + "vnetImagePullEnabled": false, + "vnetContentShareEnabled": false, + "siteConfig": { + "numberOfWorkers": 1, + "defaultDocuments": null, + "netFrameworkVersion": null, + "phpVersion": null, + "pythonVersion": null, + "nodeVersion": null, + "powerShellVersion": null, + "linuxFxVersion": "PHP|8.1", + "windowsFxVersion": null, + "windowsConfiguredStacks": null, + "requestTracingEnabled": null, + "remoteDebuggingEnabled": null, + "remoteDebuggingVersion": null, + "httpLoggingEnabled": null, + "azureMonitorLogCategories": null, + "acrUseManagedIdentityCreds": false, + "acrUserManagedIdentityID": null, + "logsDirectorySizeLimit": null, + "detailedErrorLoggingEnabled": null, + "publishingUsername": null, + "publishingPassword": null, + "appSettings": null, + "metadata": null, + "connectionStrings": null, + "machineKey": null, + "handlerMappings": null, + "documentRoot": null, + "scmType": null, + "use32BitWorkerProcess": null, + "webSocketsEnabled": null, + "alwaysOn": false, + "javaVersion": null, + "javaContainer": null, + "javaContainerVersion": null, + "appCommandLine": null, + "managedPipelineMode": null, + "virtualApplications": null, + "winAuthAdminState": null, + "winAuthTenantState": null, + "customAppPoolIdentityAdminState": null, + "customAppPoolIdentityTenantState": null, + "runtimeADUser": null, + "runtimeADUserPassword": null, + "loadBalancing": null, + "routingRules": null, + "experiments": null, + "limits": null, + "autoHealEnabled": null, + "autoHealRules": null, + "tracingOptions": null, + "vnetName": null, + "vnetRouteAllEnabled": null, + "vnetPrivatePortsCount": null, + "publicNetworkAccess": null, + "cors": null, + "push": null, + "apiDefinition": null, + "apiManagementConfig": null, + "autoSwapSlotName": null, + "localMySqlEnabled": null, + "managedServiceIdentityId": null, + "xManagedServiceIdentityId": null, + "keyVaultReferenceIdentity": null, + "ipSecurityRestrictions": null, + "ipSecurityRestrictionsDefaultAction": null, + "scmIpSecurityRestrictions": null, + "scmIpSecurityRestrictionsDefaultAction": null, + "scmIpSecurityRestrictionsUseMain": null, + "http20Enabled": false, + "minTlsVersion": null, + "minTlsCipherSuite": null, + "supportedTlsCipherSuites": null, + "scmMinTlsVersion": null, + "ftpsState": null, + "preWarmedInstanceCount": null, + "functionAppScaleLimit": 0, + "elasticWebAppScaleLimit": null, + "healthCheckPath": null, + "fileChangeAuditEnabled": null, + "functionsRuntimeScaleMonitoringEnabled": null, + "websiteTimeZone": null, + "minimumElasticInstanceCount": 0, + "azureStorageAccounts": null, + "http20ProxyFlag": null, + "sitePort": null, + "antivirusScanEnabled": null, + "storageType": null, + "sitePrivateLinkHostEnabled": null + }, + "functionAppConfig": null, + "daprConfig": null, + "deploymentId": "site-d", + "slotName": null, + "trafficManagerHostNames": null, + "sku": "Basic", + "scmSiteAlsoStopped": false, + "targetSwapSlot": null, + "hostingEnvironment": null, + "hostingEnvironmentProfile": null, + "clientAffinityEnabled": false, + "clientCertEnabled": false, + "clientCertMode": "Required", + "clientCertExclusionPaths": null, + "hostNamesDisabled": false, + "ipMode": "IPv4", + "vnetBackupRestoreEnabled": false, + "kind": "app,linux", + "containerSize": 0, + "dailyMemoryTimeQuota": 0, + "suspendedTill": null, + "siteDisabledReason": 0, + "cloningInfo": null, + "hostingEnvironmentId": null, + "resourceGroup": "rg-test", + "defaultHostName": "site-d.azurewebsites.net", + "slotSwapStatus": null, + "httpsOnly": true, + "endToEndEncryptionEnabled": false, + "functionsRuntimeAdminIsolationEnabled": false, + "redundancyMode": "None", + "inProgressOperationId": null, + "geoDistributions": null, + "privateEndpointConnections": [], + "publicNetworkAccess": "Enabled", + "buildVersion": null, + "targetBuildVersion": null, + "migrationState": null, + "eligibleLogCategories": "AppServiceAppLogs,AppServiceAuditLogs,AppServiceConsoleLogs,AppServiceHTTPLogs,AppServiceIPSecAuditLogs,AppServicePlatformLogs,ScanLogs", + "inFlightFeatures": [ + "SiteContainers" + ], + "storageAccountRequired": false, + "virtualNetworkSubnetId": null, + "keyVaultReferenceIdentity": "SystemAssigned", + "autoGeneratedDomainNameLabelScope": null, + "defaultHostNameScope": "Global", + "privateLinkIdentifiers": null, + "sshEnabled": null + } } ]