From 5b1af7420ea344a5ee5ad1f989d62937ac6da490 Mon Sep 17 00:00:00 2001 From: Benjamin Engeset Date: Mon, 20 May 2024 09:17:32 +0200 Subject: [PATCH 1/2] feat: Updated Azure.APIM.AvailabilityZone --- docs/CHANGELOG-v1.md | 8 + docs/en/rules/Azure.APIM.AvailabilityZone.md | 23 ++- .../rules/Azure.APIM.Rule.ps1 | 10 +- .../Azure.APIM.Tests.ps1 | 148 +++++++++--------- 4 files changed, 102 insertions(+), 87 deletions(-) diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 7b1beeb22f1..97b1ace6612 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -39,6 +39,14 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers [#2846](https://github.com/Azure/PSRule.Rules.Azure/issues/2846) - Check that database accounts have public network access disabled by @BenjaminEngeset. [#2702](https://github.com/Azure/PSRule.Rules.Azure/issues/2702) +- Updated rules: + - API Management: + - Removed the `If` Premium SKU check. + - Added check for Premium SKU. + - Updated tests. + - Updated rule doc. + [#2788](https://github.com/Azure/PSRule.Rules.Azure/issues/2788) + ## v1.37.0-B0009 (pre-release) diff --git a/docs/en/rules/Azure.APIM.AvailabilityZone.md b/docs/en/rules/Azure.APIM.AvailabilityZone.md index 4b653de5c4f..fe9e57bdbfa 100644 --- a/docs/en/rules/Azure.APIM.AvailabilityZone.md +++ b/docs/en/rules/Azure.APIM.AvailabilityZone.md @@ -6,26 +6,29 @@ resource: API Management online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.APIM.AvailabilityZone/ --- -# API management services should use Availability zones in supported regions +# API management instances should use availability zones in supported regions ## SYNOPSIS -API management services deployed with Premium SKU should use availability zones in supported regions for high availability. + API Management instances should use availability zones in supported regions for high availability. ## DESCRIPTION -API management services using availability zones improve reliability and ensure availability during failure scenarios affecting a data center within a region. -With zone redundancy, the gateway and the control plane of your API Management instance (Management API, developer portal, Git configuration) are replicated across data centers in physically separated zones, making it resilient to a zone failure. +API Management instances using availability zones improve reliability and ensure availability during failure scenarios affecting a data center within a region. + +Enabling zone redundancy for an API Management instance in a primary region provides redundancy for all service components: gateway, management plane, and developer portal are replicated across data centers in physically separated zones, making it resilient to a zone failure. + +Enabling zone redundancy for an API Management instance in a additional region provides redundancy for *only* the gateway service component. ## RECOMMENDATION -Consider using availability zones for API management services deployed with Premium SKU. +Consider using availability zones for API Management instances. ## NOTES This rule applies when analyzing resources deployed to Azure using *pre-flight* and *in-flight* data. -This rule fails when `"zones"` is `null`, `[]` or less than two zones when API management service is deployed with Premium SKU and there are supported availability zones for the given region. +This rule fails when `"zones"` is `null`, `[]` or less than two zones when API Management instance is deployed with Premium SKU and there are supported availability zones for the given region. Configure `AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST` to set additional availability zones that need to be supported which are not in the existing [providers](https://github.com/Azure/PSRule.Rules.Azure/blob/main/data/providers/) for namespace `Microsoft.ApiManagement` and resource type `services`. @@ -39,7 +42,7 @@ configuration: ### Configure with Azure template -To set availability zones for a API management service +To set availability zones for a API Management instance: - Set `zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `sku.capacity`. - Set `properties.additionalLocations[*].zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `properties.additionalLocations[*].sku.capacity`. @@ -109,7 +112,7 @@ For example: ### Configure with Bicep -To set availability zones for a API management service +To set availability zones for a API Management instance: - Set `zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `sku.capacity`. - Set `properties.additionalLocations[*].zones` to a minimum of two zones from `["1", "2", "3"]`, ensuring the number of zones match `properties.additionalLocations[*].sku.capacity`. @@ -173,6 +176,10 @@ resource service_api_mgmt_test2_name_resource 'Microsoft.ApiManagement/service@2 } ``` +## NOTES + +For developer environments, supressing the rule might make sense as enabling zone redundancy for an API Management instance requries the `Premium` SKU currently. + ## LINKS - [RE:05 Regions and availability zones](https://learn.microsoft.com/azure/well-architected/reliability/regions-availability-zones) diff --git a/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 b/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 index aeef3bfca41..08bccda9ac2 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 +++ b/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 @@ -193,8 +193,14 @@ Rule 'Azure.APIM.CertificateExpiry' -Ref 'AZR-000051' -Type 'Microsoft.ApiManage } } -Configure @{ Azure_MinimumCertificateLifetime = 30 } -# Synopsis: API management services deployed with Premium SKU should use availability zones in supported regions for high availability. -Rule 'Azure.APIM.AvailabilityZone' -Ref 'AZR-000052' -Type 'Microsoft.ApiManagement/service' -If { IsPremiumAPIM } -Tag @{ release = 'GA'; ruleSet = '2021_12'; 'Azure.WAF/pillar' = 'Reliability'; } { +# Synopsis: API Management instances should use availability zones in supported regions for high availability. +Rule 'Azure.APIM.AvailabilityZone' -Ref 'AZR-000052' -Type 'Microsoft.ApiManagement/service' -Tag @{ release = 'GA'; ruleSet = '2021_12'; 'Azure.WAF/pillar' = 'Reliability'; } { + if ($TargetObject.sku.name -ne 'Premium') { + return $Assert.HasFieldValue($TargetObject, 'sku.name', 'Premium') # Availability zones are only supported for the Premium SKU. + } + + $Assert.HasFieldValue($TargetObject, 'sku.name', 'Premium') + $apiManagementServiceProvider = [PSRule.Rules.Azure.Runtime.Helper]::GetResourceType('Microsoft.ApiManagement', 'service'); $configurationZoneMappings = $Configuration.AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST; diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.APIM.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.APIM.Tests.ps1 index 6318849df25..5badc83fcba 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.APIM.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.APIM.Tests.ps1 @@ -309,43 +309,41 @@ Describe 'Azure.APIM' -Tag 'APIM' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 8; - $ruleResult.TargetName | Should -BeIn 'apim-D', 'apim-F', 'apim-G', 'apim-I', 'apim-J', 'apim-K', 'apim-L', 'apim-P'; + $ruleResult.Length | Should -Be 11; + $ruleResult.TargetName | Should -BeIn 'apim-A', 'apim-B', 'apim-C', 'apim-D', 'apim-F', 'apim-G', 'apim-I', 'apim-J', 'apim-K', 'apim-L', 'apim-P'; - $ruleResult[0].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[0].Reason | Should -BeExactly "The API management service (apim-D) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[1].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[1].Reason | Should -Be @( + $ruleResult[0].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + $ruleResult[1].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + $ruleResult[2].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + + $ruleResult[3].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[3].Reason | Should -BeExactly "The API management service (apim-D) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[4].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[4].Reason | Should -Be @( "The API management service (apim-F) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-F) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[2].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[2].Reason | Should -BeExactly "The API management service (apim-G) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[3].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[3].Reason | Should -BeExactly "The API management service (apim-I) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[4].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[4].Reason | Should -BeExactly "The API management service (apim-J) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; $ruleResult[5].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[5].Reason | Should -Be @( + $ruleResult[5].Reason | Should -BeExactly "The API management service (apim-G) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[6].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[6].Reason | Should -BeExactly "The API management service (apim-I) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[7].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[7].Reason | Should -BeExactly "The API management service (apim-J) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[8].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[8].Reason | Should -Be @( "The API management service (apim-K) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-K) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[6].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[6].Reason | Should -BeExactly "The API management service (apim-L) deployed to region (Australia East) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[7].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[7].Reason | Should -BeExactly "The API management service (apim-P) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[9].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[9].Reason | Should -BeExactly "The API management service (apim-L) deployed to region (Australia East) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[10].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[10].Reason | Should -BeExactly "The API management service (apim-P) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]."; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; $ruleResult.Length | Should -Be 5; $ruleResult.TargetName | Should -BeIn 'apim-E', 'apim-H', 'apim-M', 'apim-N', 'apim-O'; - - # None - $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' }); - $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'apim-A', 'apim-B', 'apim-C'; } It 'Azure.APIM.MinAPIVersion' { @@ -581,53 +579,51 @@ Describe 'Azure.APIM' -Tag 'APIM' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 10; - $ruleResult.TargetName | Should -BeIn 'apim-D', 'apim-E', 'apim-F', 'apim-G', 'apim-H', 'apim-I', 'apim-J', 'apim-K', 'apim-L', 'apim-P'; + $ruleResult.Length | Should -Be 13; + $ruleResult.TargetName | Should -BeIn 'apim-A', 'apim-B', 'apim-C', 'apim-D', 'apim-E', 'apim-F', 'apim-G', 'apim-H', 'apim-I', 'apim-J', 'apim-K', 'apim-L', 'apim-P'; - $ruleResult[0].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[0].Reason | Should -BeExactly "The API management service (apim-D) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[1].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[1].Reason | Should -BeExactly "The API management service (apim-E) deployed to region (antarcticanorth) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[2].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[2].Reason | Should -Be @( + $ruleResult[0].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + $ruleResult[1].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + $ruleResult[2].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + + $ruleResult[3].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[3].Reason | Should -BeExactly "The API management service (apim-D) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[4].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[4].Reason | Should -BeExactly "The API management service (apim-E) deployed to region (antarcticanorth) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[5].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[5].Reason | Should -Be @( "The API management service (apim-F) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-F) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[3].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[3].Reason | Should -Be @( + $ruleResult[6].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[6].Reason | Should -Be @( "The API management service (apim-G) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-G) deployed to region (Antarctica South) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[4].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[4].Reason | Should -BeExactly "The API management service (apim-H) deployed to region (antarcticasouth) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[5].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[5].Reason | Should -BeExactly "The API management service (apim-I) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[6].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[6].Reason | Should -BeExactly "The API management service (apim-J) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; $ruleResult[7].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[7].Reason | Should -Be @( + $ruleResult[7].Reason | Should -BeExactly "The API management service (apim-H) deployed to region (antarcticasouth) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[8].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[8].Reason | Should -BeExactly "The API management service (apim-I) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[9].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[9].Reason | Should -BeExactly "The API management service (apim-J) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[10].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[10].Reason | Should -Be @( "The API management service (apim-K) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-K) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[8].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[8].Reason | Should -Be @( + $ruleResult[11].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[11].Reason | Should -Be @( "The API management service (apim-L) deployed to region (antarcticanorth) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-L) deployed to region (Australia East) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[9].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[9].Reason | Should -BeExactly "The API management service (apim-P) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[12].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[12].Reason | Should -BeExactly "The API management service (apim-P) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]."; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; $ruleResult.Length | Should -Be 3; $ruleResult.TargetName | Should -BeIn 'apim-M', 'apim-N', 'apim-O'; - - # None - $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' }); - $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'apim-A', 'apim-B', 'apim-C'; } It 'Azure.APIM.AvailabilityZone - YAML file option' { @@ -637,53 +633,51 @@ Describe 'Azure.APIM' -Tag 'APIM' { # Fail $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 10; - $ruleResult.TargetName | Should -BeIn 'apim-D', 'apim-E', 'apim-F', 'apim-G', 'apim-H', 'apim-I', 'apim-J', 'apim-K', 'apim-L', 'apim-P'; + $ruleResult.Length | Should -Be 13; + $ruleResult.TargetName | Should -BeIn 'apim-A', 'apim-B', 'apim-C', 'apim-D', 'apim-E', 'apim-F', 'apim-G', 'apim-H', 'apim-I', 'apim-J', 'apim-K', 'apim-L', 'apim-P'; - $ruleResult[0].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[0].Reason | Should -BeExactly "The API management service (apim-D) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[1].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[1].Reason | Should -BeExactly "The API management service (apim-E) deployed to region (antarcticanorth) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[2].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[2].Reason | Should -Be @( + $ruleResult[0].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + $ruleResult[1].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + $ruleResult[2].Reason | Should -BeExactly "Path sku.name: Is set to 'Developer'."; + + $ruleResult[3].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[3].Reason | Should -BeExactly "The API management service (apim-D) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[4].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[4].Reason | Should -BeExactly "The API management service (apim-E) deployed to region (antarcticanorth) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[5].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[5].Reason | Should -Be @( "The API management service (apim-F) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-F) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[3].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[3].Reason | Should -Be @( + $ruleResult[6].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[6].Reason | Should -Be @( "The API management service (apim-G) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-G) deployed to region (Antarctica South) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[4].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[4].Reason | Should -BeExactly "The API management service (apim-H) deployed to region (antarcticasouth) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[5].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[5].Reason | Should -BeExactly "The API management service (apim-I) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; - $ruleResult[6].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[6].Reason | Should -BeExactly "The API management service (apim-J) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; $ruleResult[7].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[7].Reason | Should -Be @( + $ruleResult[7].Reason | Should -BeExactly "The API management service (apim-H) deployed to region (antarcticasouth) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[8].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[8].Reason | Should -BeExactly "The API management service (apim-I) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[9].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[9].Reason | Should -BeExactly "The API management service (apim-J) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[10].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[10].Reason | Should -Be @( "The API management service (apim-K) deployed to region (australiaeast) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-K) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[8].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[8].Reason | Should -Be @( + $ruleResult[11].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[11].Reason | Should -Be @( "The API management service (apim-L) deployed to region (antarcticanorth) should use a minimum of two availability zones from the following [1, 2, 3]." "The API management service (apim-L) deployed to region (Australia East) should use a minimum of two availability zones from the following [1, 2, 3]." ) - $ruleResult[9].Reason | Should -Not -BeNullOrEmpty; - $ruleResult[9].Reason | Should -BeExactly "The API management service (apim-P) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]."; + $ruleResult[12].Reason | Should -Not -BeNullOrEmpty; + $ruleResult[12].Reason | Should -BeExactly "The API management service (apim-P) deployed to region (East US) should use a minimum of two availability zones from the following [1, 2, 3]."; # Pass $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); $ruleResult | Should -Not -BeNullOrEmpty; $ruleResult.Length | Should -Be 3; $ruleResult.TargetName | Should -BeIn 'apim-M', 'apim-N', 'apim-O'; - - # None - $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'None' }); - $ruleResult | Should -Not -BeNullOrEmpty; - $ruleResult.Length | Should -Be 3; - $ruleResult.TargetName | Should -BeIn 'apim-A', 'apim-B', 'apim-C'; } } } From 0f41eaee30645051caf51d1c5b888f52da0ac221 Mon Sep 17 00:00:00 2001 From: Benjamin Engeset Date: Mon, 20 May 2024 20:22:07 +0200 Subject: [PATCH 2/2] feat: Updated with feedback from review --- docs/CHANGELOG-v1.md | 11 ++--- docs/en/rules/Azure.APIM.AvailabilityZone.md | 42 +++++++++++-------- .../rules/Azure.APIM.Rule.ps1 | 2 +- .../Azure.Baseline.Tests.ps1 | 20 ++++----- 4 files changed, 41 insertions(+), 34 deletions(-) diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 97b1ace6612..7d295909bf4 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -41,12 +41,13 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers [#2702](https://github.com/Azure/PSRule.Rules.Azure/issues/2702) - Updated rules: - API Management: - - Removed the `If` Premium SKU check. - - Added check for Premium SKU. - - Updated tests. - - Updated rule doc. + - **Important change**: Updated `Azure.APIM.AvailabilityZone` to improve accuracy with non-premium SKUs by @BenjaminEngeset. [#2788](https://github.com/Azure/PSRule.Rules.Azure/issues/2788) - + - Removed the `If` Premium SKU. + - Added check for Premium SKU. + - Updated tests. + - Updated rule doc. + - Bumped rule set to `2024_06`. ## v1.37.0-B0009 (pre-release) diff --git a/docs/en/rules/Azure.APIM.AvailabilityZone.md b/docs/en/rules/Azure.APIM.AvailabilityZone.md index fe9e57bdbfa..ecde0ce7160 100644 --- a/docs/en/rules/Azure.APIM.AvailabilityZone.md +++ b/docs/en/rules/Azure.APIM.AvailabilityZone.md @@ -14,29 +14,23 @@ online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.APIM.A ## DESCRIPTION -API Management instances using availability zones improve reliability and ensure availability during failure scenarios affecting a data center within a region. +API Management supports using availability zones to provide zone redundency for key components. +Zone redundancy improves resiliency and high availability of the service by deploying scale units across data centers in physically separated zones. -Enabling zone redundancy for an API Management instance in a primary region provides redundancy for all service components: gateway, management plane, and developer portal are replicated across data centers in physically separated zones, making it resilient to a zone failure. +The following are required to deploy a zone redundant configuration: -Enabling zone redundancy for an API Management instance in a additional region provides redundancy for *only* the gateway service component. +- API Management is deployed with the Premium SKU. +- At least two scale units must be deployed in separate availbility zones. -## RECOMMENDATION - -Consider using availability zones for API Management instances. - -## NOTES +Zone redundancy has a different configuration for the primary and additional regions: -This rule applies when analyzing resources deployed to Azure using *pre-flight* and *in-flight* data. - -This rule fails when `"zones"` is `null`, `[]` or less than two zones when API Management instance is deployed with Premium SKU and there are supported availability zones for the given region. +- In the primary region, all service components are replicated across zones (gateway, management plane, and developer portal). +- In additional regions, *only* the gateway is replicated across zones. + As the management plane/developer portal service components are only deployed to the primary region. -Configure `AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST` to set additional availability zones that need to be supported which are not in the existing [providers](https://github.com/Azure/PSRule.Rules.Azure/blob/main/data/providers/) for namespace `Microsoft.ApiManagement` and resource type `services`. +## RECOMMENDATION -```yaml -# YAML: The default AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST configuration option -configuration: - AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST: [] -``` +Consider using at least two (2) scale units and availability zones to improve resiliency of API Management instances to zone failures. ## EXAMPLES @@ -178,7 +172,19 @@ resource service_api_mgmt_test2_name_resource 'Microsoft.ApiManagement/service@2 ## NOTES -For developer environments, supressing the rule might make sense as enabling zone redundancy for an API Management instance requries the `Premium` SKU currently. +This rule applies when analyzing resources deployed to Azure using *pre-flight* and *in-flight* data. + +This rule fails when `"zones"` is `null`, `[]` or less than two zones when API Management instance is deployed with Premium SKU and there are supported availability zones for the given region. + +Configure `AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST` to set additional availability zones that need to be supported which are not in the existing [providers](https://github.com/Azure/PSRule.Rules.Azure/blob/main/data/providers/) for namespace `Microsoft.ApiManagement` and resource type `services`. + +```yaml +# YAML: The default AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST configuration option +configuration: + AZURE_APIM_ADDITIONAL_REGION_AVAILABILITY_ZONE_LIST: [] +``` + +For developer environments, suppressing the rule might make sense as enabling zone redundancy for an API Management instance requries the `Premium` SKU currently. ## LINKS diff --git a/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 b/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 index 08bccda9ac2..025d4e8d5f9 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 +++ b/src/PSRule.Rules.Azure/rules/Azure.APIM.Rule.ps1 @@ -194,7 +194,7 @@ Rule 'Azure.APIM.CertificateExpiry' -Ref 'AZR-000051' -Type 'Microsoft.ApiManage } -Configure @{ Azure_MinimumCertificateLifetime = 30 } # Synopsis: API Management instances should use availability zones in supported regions for high availability. -Rule 'Azure.APIM.AvailabilityZone' -Ref 'AZR-000052' -Type 'Microsoft.ApiManagement/service' -Tag @{ release = 'GA'; ruleSet = '2021_12'; 'Azure.WAF/pillar' = 'Reliability'; } { +Rule 'Azure.APIM.AvailabilityZone' -Ref 'AZR-000052' -Type 'Microsoft.ApiManagement/service' -Tag @{ release = 'GA'; ruleSet = '2024_06'; 'Azure.WAF/pillar' = 'Reliability'; } { if ($TargetObject.sku.name -ne 'Premium') { return $Assert.HasFieldValue($TargetObject, 'sku.name', 'Premium') # Availability zones are only supported for the Premium SKU. } diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 index 88cda4a38aa..6750229de84 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.Baseline.Tests.ps1 @@ -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 244; + $filteredResult.Length | Should -Be 243; } 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 260; + $filteredResult.Length | Should -Be 259; } 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 264; + $filteredResult.Length | Should -Be 263; } 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 295; + $filteredResult.Length | Should -Be 294; } 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 333; + $filteredResult.Length | Should -Be 332; } 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 353; + $filteredResult.Length | Should -Be 352; } 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 368; + $filteredResult.Length | Should -Be 367; } 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 379; + $filteredResult.Length | Should -Be 378; } 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 388; + $filteredResult.Length | Should -Be 387; } It 'With Azure.Preview_2023_12' { @@ -234,7 +234,7 @@ Describe 'Baselines' -Tag Baseline { $result = @(Get-PSRule -Module PSRule.Rules.Azure -Baseline 'Azure.GA_2024_03' -WarningAction Ignore); $filteredResult = @($result | Where-Object { $_.Tag.release -in 'GA'}); $filteredResult | Should -Not -BeNullOrEmpty; - $filteredResult.Length | Should -Be 398; + $filteredResult.Length | Should -Be 397; } It 'With Azure.Preview_2024_03' {