From 0e8ad12693c80e5fb19724d6e175935e1b0cee41 Mon Sep 17 00:00:00 2001 From: Benjamin Engeset <99641908+BenjaminEngeset@users.noreply.github.com> Date: Thu, 30 May 2024 03:00:09 +0200 Subject: [PATCH] feat(new): Added Azure.LogAnalytics.Replication (#2894) * feat(new): Added Azure.LogAnalytics.Replication * feat: Feedback from code review --- docs/CHANGELOG-v1.md | 5 + .../rules/Azure.LogAnalytics.Replication.md | 106 ++++++++++++++ .../rules/Azure.LogAnalytics.Rule.yaml | 34 +++++ .../Azure.LogAnalytics.Tests.ps1 | 57 ++++++++ .../Resources.LogAnalytics.json | 137 ++++++++++++++++++ 5 files changed, 339 insertions(+) create mode 100644 docs/en/rules/Azure.LogAnalytics.Replication.md create mode 100644 src/PSRule.Rules.Azure/rules/Azure.LogAnalytics.Rule.yaml create mode 100644 tests/PSRule.Rules.Azure.Tests/Azure.LogAnalytics.Tests.ps1 create mode 100644 tests/PSRule.Rules.Azure.Tests/Resources.LogAnalytics.json diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 65febff2e2..284c5c98a8 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -29,6 +29,11 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers ## Unreleased +- New rules: + - Log Analytics: + - Check that workspaces have workspace replication enabled by @BenjaminEngeset. + [#2893](https://github.com/Azure/PSRule.Rules.Azure/issues/2893) + ## v1.37.0-B0034 (pre-release) What's changed since pre-release v1.37.0-B0009: diff --git a/docs/en/rules/Azure.LogAnalytics.Replication.md b/docs/en/rules/Azure.LogAnalytics.Replication.md new file mode 100644 index 0000000000..a0f9443174 --- /dev/null +++ b/docs/en/rules/Azure.LogAnalytics.Replication.md @@ -0,0 +1,106 @@ +--- +severity: Important +pillar: Reliability +category: RE:05 Regions and availability zones +resource: Log Analytics +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.LogAnalytics.Replication/ +--- + +# Replicate workspaces across regions + +## SYNOPSIS + +Log Analytics workspaces should have workspace replication enabled to improve service availability. + +## DESCRIPTION + +In the event of a service disruption, access to monitoring data and collection of new data in a workspace may be temporarily impacted. + +Log Analytics workspaces support replication of monitoring data to a workspace to a secondary region. +When relication is enabled, new monitoring data is replicated to the secondary region in addition to the primary region. +Failover to the workspace in the secondary region can be triggered manually. + +Some limitations apply: + +- Failover occurs by updating DNS records to point to the secondary region. + As a result, failover won't occur immediately and clients with open connections won't update until a new connection is established. +- Failover is a customer initiated action. + Failover to the secondard region does not occur automatically. +- Data collection rules need to be updated to point to the system data collection endpoint for ingested data to be replicated. +- See documentation references below for additional limitations and important information. + +## RECOMMENDATION + +Consider replicating Log Analytics workspaces across regions to improve access to monitoring data in the event of a regional service disruption. + +## EXAMPLES + +### Configure with Azure template + +To deploy Log Analytics workspaces that pass this rule: + +- Set the `properties.replication.enabled` property to `true`. +- Set the `properties.replication.location` property to a supported region in the same region group as the workspace primary region. + +For example: + +```json +{ + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-01-01-preview", + "name": "[parameters('name')]", + "location": "westeurope", + "properties": { + "replication": { + "enabled": true, + "location": "northeurope" + }, + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "retentionInDays": 30, + "sku": { + "name": "PERGB2018" + } + } +} +``` + +### Configure with Bicep + +To deploy Log Analytics workspaces that pass this rule: + +- Set the `properties.replication.enabled` property to `true`. +- Set the `properties.replication.location` property to a supported region in the same region group as the workspace primary region. + +For example: + +```bicep +resource workspace 'Microsoft.OperationalInsights/workspaces@2023-01-01-preview' = { + name: name + location: 'westeurope' + properties: { + replication: { + enabled: true + location: 'northeurope' + } + publicNetworkAccessForIngestion: 'Enabled' + publicNetworkAccessForQuery: 'Enabled' + retentionInDays: 30 + sku: { + name: 'PERGB2018' + } + } +} +``` + +## NOTES + +This feature for Log Analytics workspaces is currently in preview. + +Replication of Log Analytics workspaces linked to a dedicated cluster is currently not supported. + +## LINKS + +- [RE:05 Regions and availability zones](https://learn.microsoft.com/azure/well-architected/reliability/regions-availability-zones) +- [Replicate workspace across regions](https://learn.microsoft.com/azure/azure-monitor/logs/workspace-replication) +- [Azure resource deployment](https://learn.microsoft.com/azure/templates/microsoft.operationalinsights/workspaces) diff --git a/src/PSRule.Rules.Azure/rules/Azure.LogAnalytics.Rule.yaml b/src/PSRule.Rules.Azure/rules/Azure.LogAnalytics.Rule.yaml new file mode 100644 index 0000000000..b1c9f789a4 --- /dev/null +++ b/src/PSRule.Rules.Azure/rules/Azure.LogAnalytics.Rule.yaml @@ -0,0 +1,34 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# +# Rules for Log Analytics +# + +#region Rules + +--- +# Synopsis: Log Analytics workspaces should have workspace replication enabled to improve service availability. +apiVersion: github.com/microsoft/PSRule/v1 +kind: Rule +metadata: + name: Azure.LogAnalytics.Replication + ref: AZR-000425 + tags: + release: preview + ruleSet: 2024_06 + Azure.WAF/pillar: Reliability +spec: + type: + - Microsoft.OperationalInsights/workspaces + where: + field: properties.features.clusterResourceId + hasValue: false + condition: + allOf: + - field: properties.replication.enabled + equals: true + - field: properties.replication.location + hasValue: true + +#endregion Rules diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.LogAnalytics.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.LogAnalytics.Tests.ps1 new file mode 100644 index 0000000000..ad56c979aa --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Azure.LogAnalytics.Tests.ps1 @@ -0,0 +1,57 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +# +# Unit tests for Log Analytics rules +# + +[CmdletBinding()] +param () + +BeforeAll { + # Setup error handling + $ErrorActionPreference = 'Stop'; + Set-StrictMode -Version latest; + + if ($Env:SYSTEM_DEBUG -eq 'true') { + $VerbosePreference = 'Continue'; + } + + # Setup tests paths + $rootPath = $PWD; + Import-Module (Join-Path -Path $rootPath -ChildPath out/modules/PSRule.Rules.Azure) -Force; + $here = (Resolve-Path $PSScriptRoot).Path; +} + +Describe 'Azure.LogAnalytics' -Tag 'LogAnalytics' { + Context 'Conditions' { + BeforeAll { + $invokeParams = @{ + Baseline = 'Azure.All' + Module = 'PSRule.Rules.Azure' + WarningAction = 'Ignore' + ErrorAction = 'Stop' + } + $dataPath = Join-Path -Path $here -ChildPath 'Resources.LogAnalytics.json'; + $result = Invoke-PSRule @invokeParams -InputPath $dataPath; + } + + It 'Azure.LogAnalytics.Replication' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.LogAnalytics.Replication' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult.Length | Should -Be 3; + $ruleResult.TargetName | Should -Be 'workspace-a', 'workspace-b', 'workspace-c'; + + $ruleResult[0].Reason | Should -Be "Path properties.replication.enabled: The field 'properties.replication.enabled' does not exist."; + $ruleResult[1].Reason | Should -Be "Path properties.replication.enabled: Is set to 'False'."; + $ruleResult[2].Reason | Should -Be "Path properties.replication.location: The field 'properties.replication.location' does not exist."; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult.Length | Should -Be 1; + $ruleResult.TargetName | Should -Be 'workspace-d'; + } + } +} diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.LogAnalytics.json b/tests/PSRule.Rules.Azure.Tests/Resources.LogAnalytics.json new file mode 100644 index 0000000000..23c46da780 --- /dev/null +++ b/tests/PSRule.Rules.Azure.Tests/Resources.LogAnalytics.json @@ -0,0 +1,137 @@ +[ + { + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-a", + "Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-a", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-01-01-preview", + "name": "workspace-a", + "location": "westus", + "tags": { + "Resource": "Log Analytics Workspace" + }, + "identity": { + "type": "string", + "userAssignedIdentities": {} + }, + "properties": { + "features": { + "disableLocalAuth": false, + "enableLogAccessUsingOnlyResourcePermissions": false, + "immediatePurgeDataOn30Days": true + }, + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "retentionInDays": 30, + "sku": { + "name": "PERGB2018" + }, + "workspaceCapping": { + "dailyQuotaGb": -1 + } + } + }, + { + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-b", + "Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-b", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-01-01-preview", + "name": "workspace-b", + "location": "westeurope", + "tags": { + "Resource": "Log Analytics Workspace" + }, + "identity": { + "type": "string", + "userAssignedIdentities": {} + }, + "properties": { + "features": { + "disableLocalAuth": false, + "enableLogAccessUsingOnlyResourcePermissions": false, + "immediatePurgeDataOn30Days": true + }, + "replication": { + "enabled": false, + "location": "northeurope" + }, + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "retentionInDays": 30, + "sku": { + "name": "PERGB2018" + }, + "workspaceCapping": { + "dailyQuotaGb": -1 + } + } + }, + { + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-c", + "Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-c", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-01-01-preview", + "name": "workspace-c", + "location": "westeurope", + "tags": { + "Resource": "Log Analytics Workspace" + }, + "identity": { + "type": "string", + "userAssignedIdentities": {} + }, + "properties": { + "features": { + "disableLocalAuth": false, + "enableLogAccessUsingOnlyResourcePermissions": false, + "immediatePurgeDataOn30Days": true + }, + "replication": { + "enabled": true + }, + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "retentionInDays": 30, + "sku": { + "name": "PERGB2018" + }, + "workspaceCapping": { + "dailyQuotaGb": -1 + } + } + }, + { + "ResourceId": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-d", + "Id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/test-rg/providers/Microsoft.OperationalInsights/workspace/workspace-d", + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2023-01-01-preview", + "name": "workspace-d", + "location": "westeurope", + "tags": { + "Resource": "Log Analytics Workspace" + }, + "identity": { + "type": "string", + "userAssignedIdentities": {} + }, + "properties": { + "features": { + "disableLocalAuth": false, + "enableLogAccessUsingOnlyResourcePermissions": false, + "immediatePurgeDataOn30Days": true + }, + "replication": { + "enabled": true, + "location": "northeurope" + }, + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "retentionInDays": 30, + "sku": { + "name": "PERGB2018" + }, + "workspaceCapping": { + "dailyQuotaGb": -1 + } + } + } +]