Skip to content

Commit 65d85c8

Browse files
BenjaminEngesetCopilotBernieWhite
authored
Add Azure.Grafana.AvailabilityZone (#3584)
* Initial plan * Add Azure.Grafana.AvailabilityZone rule for zone redundancy Co-authored-by: BenjaminEngeset <99641908+BenjaminEngeset@users.noreply.github.com> * Address PR feedback: Update rule ref to AZR-000501, use 'workspaces' terminology, update API version to 2024-10-01, add issue reference, revert index.md changes Co-authored-by: BenjaminEngeset <99641908+BenjaminEngeset@users.noreply.github.com> * Reorder test cases (fail, fail, pass, pass, pass) and add test for missing zoneRedundancy property Co-authored-by: BenjaminEngeset <99641908+BenjaminEngeset@users.noreply.github.com> * Update comment to use 'virtual machine scale set' instead of 'Compute' Co-authored-by: BenjaminEngeset <99641908+BenjaminEngeset@users.noreply.github.com> * api version * version * version 10 * Updates --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: Bernie White <bewhite@microsoft.com>
1 parent 94c6d54 commit 65d85c8

File tree

5 files changed

+300
-7
lines changed

5 files changed

+300
-7
lines changed

docs/changelog.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers
4040
- Data Explorer:
4141
- Check that public network access is disabled by @BenjaminEngeset.
4242
[#3114](https://github.com/Azure/PSRule.Rules.Azure/issues/3114)
43+
- Managed Grafana:
44+
- Check that zone redundancy is enabled for Grafana workspaces in supported regions by @BenjaminEngeset.
45+
[#3294](https://github.com/Azure/PSRule.Rules.Azure/issues/3294)
4346
- Updated rules:
4447
- Application Gateway Policy:
4548
- Updated `Azure.AppGwWAF.RuleGroups` to use Microsoft Default Rule Set instead of legacy OWASP rule set by @BenjaminEngeset.
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
---
2+
reviewed: 2025-11-06
3+
severity: Important
4+
pillar: Reliability
5+
category: RE:05 Regions and availability zones
6+
resource: Managed Grafana
7+
resourceType: Microsoft.Dashboard/grafana
8+
online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.Grafana.AvailabilityZone/
9+
---
10+
11+
# Use zone redundant Managed Grafana workspaces
12+
13+
## SYNOPSIS
14+
15+
Use zone redundant Grafana workspaces in supported regions to improve reliability.
16+
17+
## DESCRIPTION
18+
19+
Azure Managed Grafana supports zone redundancy to provide enhanced resiliency and high availability.
20+
When zone redundancy is enabled, Azure Managed Grafana automatically distributes the
21+
service across multiple availability zones within a region.
22+
23+
Managed Grafana implements this redundancy using active-passive replicas.
24+
One active replica serves traffic while passive replicas remain on standby in other
25+
zones and take over if the active replica becomes unavailable.
26+
27+
This configuration ensures that the service remains available even if an entire
28+
availability zone experiences an outage.
29+
30+
Zone redundancy can only be configured during the initial workspace creation and cannot be modified afterwards.
31+
Additionally, zone redundancy is only available in regions that support availability zones.
32+
33+
## RECOMMENDATION
34+
35+
Consider enabling zone redundancy for Azure Managed Grafana workspaces deployed in regions that support
36+
availability zones to improve service reliability and resilience.
37+
38+
## EXAMPLES
39+
40+
### Configure with Azure template
41+
42+
To deploy Managed Grafana workspaces that pass this rule:
43+
44+
- Set the `properties.zoneRedundancy` property to `Enabled`.
45+
46+
For example:
47+
48+
```json
49+
{
50+
"type": "Microsoft.Dashboard/grafana",
51+
"apiVersion": "2024-10-01",
52+
"name": "[parameters('name')]",
53+
"location": "[parameters('location')]",
54+
"sku": {
55+
"name": "Standard"
56+
},
57+
"identity": {
58+
"type": "SystemAssigned"
59+
},
60+
"properties": {
61+
"zoneRedundancy": "Enabled"
62+
}
63+
}
64+
```
65+
66+
### Configure with Bicep
67+
68+
To deploy Managed Grafana workspaces that pass this rule:
69+
70+
- Set the `properties.zoneRedundancy` property to `Enabled`.
71+
72+
For example:
73+
74+
```bicep
75+
resource grafana 'Microsoft.Dashboard/grafana@2024-10-01' = {
76+
name: name
77+
location: location
78+
sku: {
79+
name: 'Standard'
80+
}
81+
identity: {
82+
type: 'SystemAssigned'
83+
}
84+
properties: {
85+
zoneRedundancy: 'Enabled'
86+
}
87+
}
88+
```
89+
90+
## NOTES
91+
92+
Zone redundancy must be configured during the initial deployment.
93+
It is not possible to modify an existing Managed Grafana workspace to enable zone redundancy after it has been deployed.
94+
95+
## LINKS
96+
97+
- [RE:05 Redundancy](https://learn.microsoft.com/azure/well-architected/reliability/redundancy)
98+
- [Architecture strategies for using availability zones and regions](https://learn.microsoft.com/azure/well-architected/reliability/regions-availability-zones)
99+
- [Enable zone redundancy in Azure Managed Grafana](https://learn.microsoft.com/azure/managed-grafana/how-to-enable-zone-redundancy)
100+
- [Azure regions with availability zone support](https://learn.microsoft.com/azure/reliability/availability-zones-service-support)
101+
- [Azure deployment reference](https://learn.microsoft.com/azure/templates/microsoft.dashboard/grafana)
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Copyright (c) Microsoft Corporation.
2+
# Licensed under the MIT License.
3+
4+
#
5+
# Validation rules for Azure Managed Grafana
6+
#
7+
8+
#region Rules
9+
10+
# Synopsis: Use zone redundant Grafana workspaces in supported regions to improve reliability.
11+
Rule 'Azure.Grafana.AvailabilityZone' -Ref 'AZR-000501' -Type 'Microsoft.Dashboard/grafana' -Tag @{ release = 'GA'; ruleSet = '2025_12'; 'Azure.WAF/pillar' = 'Reliability'; } -Labels @{ 'Azure.WAF/maturity' = 'L1'; } {
12+
# Check for availability zones based on virtual machine scale set, because it is not exposed through the provider for Grafana.
13+
$provider = [PSRule.Rules.Azure.Runtime.Helper]::GetResourceType('Microsoft.Compute', 'virtualMachineScaleSets');
14+
$availabilityZones = GetAvailabilityZone -Location $TargetObject.Location -Zone $provider.ZoneMappings;
15+
16+
# Don't flag if the region does not support AZ.
17+
if (-not $availabilityZones) {
18+
return $Assert.Pass();
19+
}
20+
21+
$Assert.HasFieldValue($TargetObject, 'properties.zoneRedundancy', 'Enabled');
22+
}
23+
24+
#endregion Rules

tests/PSRule.Rules.Azure.Tests/Azure.Grafana.Tests.ps1

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,25 @@ Describe 'Azure.Grafana' -Tag 'Grafana' {
4848

4949
# Pass
5050
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
51-
$ruleResult.TargetName | Should -Be 'grafana-b';
52-
$ruleResult.Length | Should -Be 1;
51+
$ruleResult | Should -Not -BeNullOrEmpty;
52+
$ruleResult.Length | Should -Be 4;
53+
$ruleResult.TargetName | Should -BeIn @('grafana-b', 'grafana-c', 'grafana-d', 'grafana-e');
54+
}
55+
56+
It 'Azure.Grafana.AvailabilityZone' {
57+
$filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.Grafana.AvailabilityZone' };
58+
59+
# Fail
60+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' });
61+
$ruleResult | Should -Not -BeNullOrEmpty;
62+
$ruleResult.Length | Should -Be 2;
63+
$ruleResult.TargetName | Should -BeIn @('grafana-a', 'grafana-b');
64+
65+
# Pass
66+
$ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' });
67+
$ruleResult | Should -Not -BeNullOrEmpty;
68+
$ruleResult.Length | Should -Be 3;
69+
$ruleResult.TargetName | Should -BeIn @('grafana-c', 'grafana-d', 'grafana-e');
5370
}
5471
}
5572
}

tests/PSRule.Rules.Azure.Tests/Resources.Grafana.json

Lines changed: 153 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
[
22
{
33
"type": "Microsoft.Dashboard/grafana",
4-
"apiVersion": "2023-09-01",
4+
"apiVersion": "2024-10-01",
55
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.Dashboard/grafana/grafana-a",
66
"name": "grafana-a",
7-
"location": "westus",
7+
"location": "westus2",
88
"tags": {
99
"application": "grafana"
1010
},
@@ -46,12 +46,12 @@
4646
"{customized property}": {}
4747
},
4848
"publicNetworkAccess": "Enabled",
49-
"zoneRedundancy": null
49+
"zoneRedundancy": "Disabled"
5050
}
5151
},
5252
{
5353
"type": "Microsoft.Dashboard/grafana",
54-
"apiVersion": "2023-09-01",
54+
"apiVersion": "2024-10-01",
5555
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.Dashboard/grafana/grafana-b",
5656
"name": "grafana-b",
5757
"location": "westeurope",
@@ -64,6 +64,105 @@
6464
"identity": {
6565
"type": "SystemAssigned"
6666
},
67+
"properties": {
68+
"apiKey": "string",
69+
"autoGeneratedDomainNameLabelScope": "TenantReuse",
70+
"deterministicOutboundIP": "string",
71+
"enterpriseConfigurations": {
72+
"marketplaceAutoRenew": "string",
73+
"marketplacePlanId": "string"
74+
},
75+
"grafanaConfigurations": {
76+
"smtp": {
77+
"enabled": "bool",
78+
"fromAddress": "string",
79+
"fromName": "string",
80+
"host": "string",
81+
"password": "string",
82+
"skipVerify": "bool",
83+
"startTLSPolicy": "string",
84+
"user": "string"
85+
}
86+
},
87+
"grafanaIntegrations": {
88+
"azureMonitorWorkspaceIntegrations": [
89+
{
90+
"azureMonitorWorkspaceResourceId": "00000000-0000-0000-0000-000000000000"
91+
}
92+
]
93+
},
94+
"grafanaMajorVersion": "11",
95+
"grafanaPlugins": {
96+
"{customized property}": {}
97+
},
98+
"publicNetworkAccess": "Enabled"
99+
}
100+
},
101+
{
102+
"type": "Microsoft.Dashboard/grafana",
103+
"apiVersion": "2024-10-01",
104+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.Dashboard/grafana/grafana-c",
105+
"name": "grafana-c",
106+
"location": "westeurope",
107+
"tags": {
108+
"application": "grafana"
109+
},
110+
"sku": {
111+
"name": "Standard"
112+
},
113+
"identity": {
114+
"type": "SystemAssigned"
115+
},
116+
"properties": {
117+
"apiKey": "string",
118+
"autoGeneratedDomainNameLabelScope": "TenantReuse",
119+
"deterministicOutboundIP": "string",
120+
"enterpriseConfigurations": {
121+
"marketplaceAutoRenew": "string",
122+
"marketplacePlanId": "string"
123+
},
124+
"grafanaConfigurations": {
125+
"smtp": {
126+
"enabled": "bool",
127+
"fromAddress": "string",
128+
"fromName": "string",
129+
"host": "string",
130+
"password": "string",
131+
"skipVerify": "bool",
132+
"startTLSPolicy": "string",
133+
"user": "string"
134+
}
135+
},
136+
"grafanaIntegrations": {
137+
"azureMonitorWorkspaceIntegrations": [
138+
{
139+
"azureMonitorWorkspaceResourceId": "00000000-0000-0000-0000-000000000000"
140+
}
141+
]
142+
},
143+
"grafanaMajorVersion": "11",
144+
"grafanaPlugins": {
145+
"{customized property}": {}
146+
},
147+
"publicNetworkAccess": "Enabled",
148+
"zoneRedundancy": "Enabled"
149+
}
150+
},
151+
{
152+
"type": "Microsoft.Dashboard/grafana",
153+
"apiVersion": "2024-10-01",
154+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.Dashboard/grafana/grafana-d",
155+
"name": "grafana-d",
156+
"location": "australiacentral",
157+
"tags": {
158+
"application": "grafana"
159+
},
160+
"sku": {
161+
"name": "Standard"
162+
},
163+
"identity": {
164+
"type": "SystemAssigned"
165+
},
67166
"properties": {
68167
"apiKey": "string",
69168
"autoGeneratedDomainNameLabelScope": "TenantReuse",
@@ -96,7 +195,56 @@
96195
"{customized property}": {}
97196
},
98197
"publicNetworkAccess": "Enabled",
99-
"zoneRedundancy": null
198+
"zoneRedundancy": "Disabled"
199+
}
200+
},
201+
{
202+
"type": "Microsoft.Dashboard/grafana",
203+
"apiVersion": "2024-10-01",
204+
"ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourcegroups/test-rg/providers/Microsoft.Dashboard/grafana/grafana-e",
205+
"name": "grafana-e",
206+
"location": "australiacentral",
207+
"tags": {
208+
"application": "grafana"
209+
},
210+
"sku": {
211+
"name": "Standard"
212+
},
213+
"identity": {
214+
"type": "SystemAssigned"
215+
},
216+
"properties": {
217+
"apiKey": "string",
218+
"autoGeneratedDomainNameLabelScope": "TenantReuse",
219+
"deterministicOutboundIP": "string",
220+
"enterpriseConfigurations": {
221+
"marketplaceAutoRenew": "string",
222+
"marketplacePlanId": "string"
223+
},
224+
"grafanaConfigurations": {
225+
"smtp": {
226+
"enabled": "bool",
227+
"fromAddress": "string",
228+
"fromName": "string",
229+
"host": "string",
230+
"password": "string",
231+
"skipVerify": "bool",
232+
"startTLSPolicy": "string",
233+
"user": "string"
234+
}
235+
},
236+
"grafanaIntegrations": {
237+
"azureMonitorWorkspaceIntegrations": [
238+
{
239+
"azureMonitorWorkspaceResourceId": "00000000-0000-0000-0000-000000000000"
240+
}
241+
]
242+
},
243+
"grafanaMajorVersion": "11",
244+
"grafanaPlugins": {
245+
"{customized property}": {}
246+
},
247+
"publicNetworkAccess": "Enabled"
100248
}
101249
}
102250
]

0 commit comments

Comments
 (0)