Added Azure.AppConfig.GeoReplication (#1735)

* Added Azure.AppConfig.GeoReplication rule and supporting rule files

* Fixed typos in rule documentation

* Changed up in examples bicep file

* Fixed Azure.AppConfig.SKU test cases

* Fixed Azure.AppConfig.SKU test cases

* Added refactored Azure.AppConfig.GeoReplica

* Updated rule documentation

* Update src/PSRule.Rules.Azure/rules/Azure.AppConfig.Rule.ps1

Co-authored-by: Bernie White <bewhite@microsoft.com>

Co-authored-by: Bernie White <bewhite@microsoft.com>
This commit is contained in:
Benjamin Engeset 2022-10-02 12:28:48 +02:00 коммит произвёл GitHub
Родитель 203a36181f
Коммит 0ea871e065
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
9 изменённых файлов: 465 добавлений и 13 удалений

Просмотреть файл

@ -24,6 +24,11 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
## Unreleased
- New rules:
- App Configuration:
- Check app configuration store has one or more replicas by @bengeset96.
[#1688](https://github.com/Azure/PSRule.Rules.Azure/issues/1688)
What's changed since pre-release v1.20.0-B0148:
- Engineering:

Просмотреть файл

@ -0,0 +1,153 @@
---
severity: Important
pillar: Reliability
category: Data management
resource: App Configuration
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AppConfig.GeoReplica/
---
# Geo-replicate app configuration store
## SYNOPSIS
Consider replication for app configuration store to ensure resiliency to region outages.
## DESCRIPTION
A app configuration store is stored and maintained by default in a single region.
The app configuration geo-replication feature allows you to replicate your configuration store at-will to the regions of your choice. Each new `replica` will be in a different region and creates a new endpoint for your applications to send requests to. The original endpoint of your configuration store is called the `Origin`. The origin can't be removed, but otherwise behaves like any replica.
Replicating your configuration store adds the following benefits:
- Added resiliency for Azure outages.
- Redistribution of request limits.
- Regional compartmentalization.
Geo-replication is currently a **preview** feature.
During the preview geo-replication has additional limitations including support and regional availability.
## RECOMMENDATION
Consider replication for app configuration store to ensure resiliency to region outages.
## EXAMPLES
### Configure with Azure template
To deploy App Configuration Stores that pass this rule:
- Set `sku.name` to `Standard` (required for geo-replication).
- Deploy a replica sub-resource (child resource).
- Set `location` on replica sub-resource to a different location than the app configuration store.
For example:
```json
{
"parameters": {
"appConfigName": {
"type": "string",
"defaultValue": "configstore01",
"metadata": {
"description": "The name of the app configuration store."
}
},
"replicaName": {
"type": "string",
"defaultValue": "replica01",
"metadata": {
"description": "The name of the replica."
}
},
"appConfigLocation": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "The location resources will be deployed."
}
},
"replicaLocation": {
"type": "string",
"defaultValue": "northeurope",
"metadata": {
"description": "The location where the replica will be deployed."
}
}
},
"resources": [
{
"type": "Microsoft.AppConfiguration/configurationStores",
"apiVersion": "2022-05-01",
"name": "[parameters('appConfigName')]",
"location": "[parameters('appConfigLocation')]",
"sku": {
"name": "standard"
},
"properties": {
"disableLocalAuth": true,
"enablePurgeProtection": true
}
},
{
"type": "Microsoft.AppConfiguration/configurationStores/replicas",
"apiVersion": "2022-03-01-preview",
"name": "[format('{0}/{1}', parameters('appConfigName'), parameters('replicaName'))]",
"location": "[parameters('replicaLocation')]",
"dependsOn": [
"[resourceId('Microsoft.AppConfiguration/configurationStores', parameters('appConfigName'))]"
]
}
]
}
```
### Configure with Bicep
To deploy App Configuration Stores that pass this rule:
- Set `sku.name` to `Standard` (required for geo-replication).
- Deploy a replica sub-resource (child resource).
- Set `location` on replica sub-resource to a different location than the app configuration store.
For example:
```bicep
@description('The name of the app configuration store.')
param appConfigName string = 'configstore01'
@description('The name of the replica.')
param replicaName string = 'replica01'
@description('The location resources will be deployed.')
param appConfigLocation string = resourceGroup().location
@description('The location where the replica will be deployed.')
param replicaLocation string = 'northeurope'
resource store 'Microsoft.AppConfiguration/configurationStores@2022-05-01' = {
name: appConfigName
location: appConfigLocation
sku: {
name: 'standard'
}
properties: {
disableLocalAuth: true
enablePurgeProtection: true
}
}
resource replica 'Microsoft.AppConfiguration/configurationStores/replicas@2022-03-01-preview' = {
name: replicaName
location: replicaLocation
parent: store
}
```
## LINKS
- [Resiliency and dependencies](https://learn.microsoft.com/azure/architecture/framework/resiliency/design-resiliency)
- [Resiliency and diaster recovery](https://learn.microsoft.com/azure/azure-app-configuration/concept-disaster-recovery)
- [Geo-replication overview](https://learn.microsoft.com/azure/azure-app-configuration/concept-geo-replication)
- [Enable geo-replication](https://learn.microsoft.com/azure/azure-app-configuration/howto-geo-replication)
- [Azure template reference](https://learn.microsoft.com/azure/templates/microsoft.appconfiguration/configurationstores/replicas)

Просмотреть файл

@ -0,0 +1,35 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT License.
// Bicep documentation examples
@description('The name of the app configuration store.')
param appConfigName string = 'configstore01'
@description('The name of the replica.')
param replicaName string = 'replica01'
@description('The location resources will be deployed.')
param appConfigLocation string = resourceGroup().location
@description('The location where the replica will be deployed.')
param replicaLocation string = 'northeurope'
// An example App Configuration Store
resource store 'Microsoft.AppConfiguration/configurationStores@2022-05-01' = {
name: appConfigName
location: appConfigLocation
sku: {
name: 'standard'
}
properties: {
disableLocalAuth: true
enablePurgeProtection: true
}
}
resource replica 'Microsoft.AppConfiguration/configurationStores/replicas@2022-03-01-preview' = {
name: replicaName
location: replicaLocation
parent: store
}

Просмотреть файл

@ -0,0 +1,65 @@
{
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
"contentVersion": "1.0.0.0",
"metadata": {
"_generator": {
"name": "bicep",
"version": "0.10.61.36676",
"templateHash": "16621422579239964777"
}
},
"parameters": {
"appConfigName": {
"type": "string",
"defaultValue": "configstore01",
"metadata": {
"description": "The name of the app configuration store."
}
},
"replicaName": {
"type": "string",
"defaultValue": "replica01",
"metadata": {
"description": "The name of the replica."
}
},
"appConfigLocation": {
"type": "string",
"defaultValue": "[resourceGroup().location]",
"metadata": {
"description": "The location resources will be deployed."
}
},
"replicaLocation": {
"type": "string",
"defaultValue": "northeurope",
"metadata": {
"description": "The location where the replica will be deployed."
}
}
},
"resources": [
{
"type": "Microsoft.AppConfiguration/configurationStores",
"apiVersion": "2022-05-01",
"name": "[parameters('appConfigName')]",
"location": "[parameters('appConfigLocation')]",
"sku": {
"name": "standard"
},
"properties": {
"disableLocalAuth": true,
"enablePurgeProtection": true
}
},
{
"type": "Microsoft.AppConfiguration/configurationStores/replicas",
"apiVersion": "2022-03-01-preview",
"name": "[format('{0}/{1}', parameters('appConfigName'), parameters('replicaName'))]",
"location": "[parameters('replicaLocation')]",
"dependsOn": [
"[resourceId('Microsoft.AppConfiguration/configurationStores', parameters('appConfigName'))]"
]
}
]
}

Просмотреть файл

@ -63,9 +63,11 @@
AKSMinimumVersionReplace = "The configuration option 'Azure_AKSMinimumVersion' has been replaced with 'AZURE_AKS_CLUSTER_MINIMUM_VERSION'. The option 'Azure_AKSMinimumVersion' is deprecated and will no longer work in the next major version. Please update your configuration to the new name. See https://aka.ms/ps-rule-azure/upgrade."
# DeprecatedSupportsTags = "The 'SupportsTags' PowerShell function has been replaced with the selector 'Azure.Resource.SupportsTags'. The 'SupportsTags' function is deprecated and will no longer work in the next major version. Please update your PowerShell rules to the selector instead. See https://aka.ms/ps-rule-azure/upgrade."
KeyVaultAutoRotationPolicy = "The key ({0}) should enable a auto-rotation policy."
ReplicaNotFound = "A replica in a secondary region was not found."
ReplicaNotFound = "A replica was not found."
ReplicaInSecondaryNotFound = "A replica in a secondary region was not found."
VMSSPublicKey = "The virtual machine scale set '{0}' should have password authentication disabled."
ACRSoftDeletePolicy = "The container registry '{0}' should have soft delete policy enabled."
ACRSoftDeletePolicyRetention = "The container registry '{0}' should have retention period value between one to 90 days for the soft delete policy."
AppConfigStoresDiagnosticSetting = "Minimum one diagnostic setting should have ({0}) configured or category group ({1}) configured."
}

Просмотреть файл

@ -45,7 +45,7 @@ Rule 'Azure.ACR.GeoReplica' -Ref 'AZR-000004' -Type 'Microsoft.ContainerRegistry
return $Assert.Pass();
}
}
return $Assert.Fail($LocalizedData.ReplicaNotFound);
return $Assert.Fail($LocalizedData.ReplicaInSecondaryNotFound);
}
# Synopsis: Azure Container Registries should have soft delete policy enabled.

Просмотреть файл

@ -26,4 +26,27 @@ Rule 'Azure.AppConfig.AuditLogs' -Ref 'AZR-000311' -Type 'Microsoft.AppConfigura
).PathPrefix('resources')
}
# Synopsis: Consider replication for app configuration store to ensure resiliency to region outages.
Rule 'Azure.AppConfig.GeoReplica' -Ref 'AZR-000312' -Type 'Microsoft.AppConfiguration/configurationStores' -If { IsAppConfigStandardSKU } -Tag @{ release = 'Preview'; ruleSet = '2022_09' } {
$appConfigLocation = GetNormalLocation -Location $TargetObject.Location
$replicas = @(GetSubResources -ResourceType 'Microsoft.AppConfiguration/configurationStores/replicas' |
ForEach-Object { GetNormalLocation -Location $_.Location } |
Where-Object { $_ -ne $appConfigLocation })
$Assert.Greater($replicas, '.', 0).Reason($LocalizedData.ReplicaInSecondaryNotFound).PathPrefix('resources')
}
#endregion Rules
#region Helper functions
function global:IsAppConfigStandardSKU {
[CmdletBinding()]
[OutputType([System.Boolean])]
param ()
process {
$Assert.HasFieldValue($TargetObject, 'sku.name', 'Standard').Result
}
}
#endregion Helper functions

Просмотреть файл

@ -42,15 +42,15 @@ Describe 'Azure.AppConfig' -Tag 'AppConfig' {
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 8;
$ruleResult.TargetName | Should -Be 'app-config-B', 'app-config-C', 'app-config-D', 'app-config-E', 'app-config-F', 'app-config-G', 'app-config-H', 'app-config-I';
$ruleResult.Length | Should -Be 5;
$ruleResult.TargetName | Should -Be 'app-config-B', 'app-config-C', 'app-config-D', 'app-config-E', 'app-config-G';
$ruleResult.Detail.Reason.Path | Should -BeIn 'Sku.name';
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -Be 'app-config-A';
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -Be 'app-config-A', 'app-config-F', 'app-config-H', 'app-config-I';
}
It 'Azure.AppConfig.DisableLocalAuth' {
@ -93,6 +93,28 @@ Describe 'Azure.AppConfig' -Tag 'AppConfig' {
$ruleResult.Length | Should -Be 4;
$ruleResult.TargetName | Should -BeIn 'app-config-B', 'app-config-D', 'app-config-F', 'app-config-I';
}
It 'Azure.AppConfig.GeoReplica' {
$dataPath = Join-Path -Path $here -ChildPath 'Resources.AppConfig.json';
$result = Invoke-PSRule @invokeParams -InputPath $dataPath;
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.AppConfig.GeoReplica' };
# Fail
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 3;
$ruleResult.TargetName | Should -BeIn 'app-config-A', 'app-config-F', 'app-config-H';
$ruleResult[0].Reason | Should -BeExactly "A replica in a secondary region was not found.";
$ruleResult[1].Reason | Should -BeExactly "A replica in a secondary region was not found.";
$ruleResult[2].Reason | Should -BeExactly "A replica in a secondary region was not found.";
# Pass
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
$ruleResult | Should -Not -BeNullOrEmpty;
$ruleResult.Length | Should -Be 1;
$ruleResult.TargetName | Should -BeIn 'app-config-I';
}
}
Context 'Resource name' {

Просмотреть файл

@ -157,6 +157,35 @@
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-B/replicas/region2",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-B/replicas/region2",
"Identity": null,
"Kind": null,
"Location": "northeurope",
"ManagedBy": null,
"ResourceName": "region2",
"Name": "region2",
"ExtensionResourceName": null,
"ParentResource": null,
"Plan": null,
"Properties": {
"provisioningState": "Succeeded",
"status": {
"displayStatus": "Ready"
}
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.AppConfiguration/configurationStores/replicas",
"ResourceType": "Microsoft.AppConfiguration/configurationStores/replicas",
"ExtensionResourceType": null,
"Sku": null,
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
}
]
},
@ -237,6 +266,35 @@
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-C/replicas/region",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-C/replicas/region",
"Identity": null,
"Kind": null,
"Location": "westeurope",
"ManagedBy": null,
"ResourceName": "region",
"Name": "region",
"ExtensionResourceName": null,
"ParentResource": null,
"Plan": null,
"Properties": {
"provisioningState": "Succeeded",
"status": {
"displayStatus": "Ready"
}
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.AppConfiguration/configurationStores/replicas",
"ResourceType": "Microsoft.AppConfiguration/configurationStores/replicas",
"ExtensionResourceType": null,
"Sku": null,
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
}
]
},
@ -397,6 +455,35 @@
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-E/replicas/region",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-E/replicas/region",
"Identity": null,
"Kind": null,
"Location": "westeurope",
"ManagedBy": null,
"ResourceName": "region",
"Name": "region",
"ExtensionResourceName": null,
"ParentResource": null,
"Plan": null,
"Properties": {
"provisioningState": "Succeeded",
"status": {
"displayStatus": "Ready"
}
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.AppConfiguration/configurationStores/replicas",
"ResourceType": "Microsoft.AppConfiguration/configurationStores/replicas",
"ExtensionResourceType": null,
"Sku": null,
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
}
]
},
@ -425,7 +512,7 @@
"ResourceType": "Microsoft.AppConfiguration/configurationStores",
"ExtensionResourceType": null,
"Sku": {
"Name": "free",
"Name": "Standard",
"Tier": null,
"Size": null,
"Family": null,
@ -565,7 +652,7 @@
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.AppConfiguration/configurationStores/app-config-H",
"Identity": null,
"Kind": null,
"Location": "region",
"Location": "westeurope",
"ManagedBy": null,
"ResourceName": "app-config-H",
"Name": "app-config-H",
@ -585,7 +672,7 @@
"ResourceType": "Microsoft.AppConfiguration/configurationStores",
"ExtensionResourceType": null,
"Sku": {
"Name": "free",
"Name": "Standard",
"Tier": null,
"Size": null,
"Family": null,
@ -593,14 +680,45 @@
"Capacity": null
},
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000"
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
"resources": [
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-H/replicas/region",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-H/replicas/region",
"Identity": null,
"Kind": null,
"Location": "westeurope",
"ManagedBy": null,
"ResourceName": "region",
"Name": "region",
"ExtensionResourceName": null,
"ParentResource": null,
"Plan": null,
"Properties": {
"provisioningState": "Succeeded",
"status": {
"displayStatus": "Ready"
}
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.AppConfiguration/configurationStores/replicas",
"ResourceType": "Microsoft.AppConfiguration/configurationStores/replicas",
"ExtensionResourceType": null,
"Sku": null,
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
}
]
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.AppConfiguration/configurationStores/app-config-I",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.AppConfiguration/configurationStores/app-config-I",
"Identity": null,
"Kind": null,
"Location": "region",
"Location": "westeurope",
"ManagedBy": null,
"ResourceName": "app-config-I",
"Name": "app-config-I",
@ -620,7 +738,7 @@
"ResourceType": "Microsoft.AppConfiguration/configurationStores",
"ExtensionResourceType": null,
"Sku": {
"Name": "free",
"Name": "Standard",
"Tier": null,
"Size": null,
"Family": null,
@ -678,7 +796,7 @@
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/rg-test/providers/Microsoft.AppConfiguration/configurationStores/app-config-I/providers/microsoft.insights/diagnosticSettings/metrics",
"Identity": null,
"Kind": null,
"Location": "australiaeast",
"Location": "northeurope",
"ManagedBy": null,
"ResourceName": "app-config-I-diagnostic2",
"Name": "app-config-I-diagnostic2",
@ -715,6 +833,35 @@
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
},
{
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-I/replicas/region2",
"Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.AppConfiguration/configurationStores/app-config-I/replicas/region2",
"Identity": null,
"Kind": null,
"Location": "northeurope",
"ManagedBy": null,
"ResourceName": "region2",
"Name": "region2",
"ExtensionResourceName": null,
"ParentResource": null,
"Plan": null,
"Properties": {
"provisioningState": "Succeeded",
"status": {
"displayStatus": "Ready"
}
},
"ResourceGroupName": "test-rg",
"Type": "Microsoft.AppConfiguration/configurationStores/replicas",
"ResourceType": "Microsoft.AppConfiguration/configurationStores/replicas",
"ExtensionResourceType": null,
"Sku": null,
"Tags": null,
"SubscriptionId": "00000000-0000-0000-0000-000000000000",
"CreatedTime": null,
"ChangedTime": null,
"ETag": null
}
]
}