From 478c5270bc31ad6aef55dbae673d9efca348f06e Mon Sep 17 00:00:00 2001 From: Benjamin Engeset <99641908+BenjaminEngeset@users.noreply.github.com> Date: Tue, 9 Jul 2024 18:10:12 +0200 Subject: [PATCH] feat(new): Added Azure.SQLMI.MaintenanceWindow (#2980) * feat(new): Added Azure.SQLMI.MaintenanceWindow * Fix link --------- Co-authored-by: Bernie White --- docs/CHANGELOG-v1.md | 3 + .../en/rules/Azure.SQLMI.MaintenanceWindow.md | 99 +++++++++++++++++++ src/PSRule.Rules.Azure/en/PSRule-rules.psd1 | 1 + .../rules/Azure.SQLMI.Rule.ps1 | 9 ++ .../Azure.SQLMI.Tests.ps1 | 19 +++- .../Resources.SQLMI.json | 9 +- 6 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 docs/en/rules/Azure.SQLMI.MaintenanceWindow.md diff --git a/docs/CHANGELOG-v1.md b/docs/CHANGELOG-v1.md index 329238166d..f318f20bc4 100644 --- a/docs/CHANGELOG-v1.md +++ b/docs/CHANGELOG-v1.md @@ -33,6 +33,9 @@ See [upgrade notes][1] for helpful information when upgrading from previous vers - Azure SQL Database: - Verify that Azure SQL databases have a customer-controlled maintenance window configured by @BenjaminEngeset. [#2956](https://github.com/Azure/PSRule.Rules.Azure/issues/2956) + - Azure SQL Managed Instance: + - Verify that Azure SQL Managed Instances have a customer-controlled maintenance window configured by @BenjaminEngeset. + [#2979](https://github.com/Azure/PSRule.Rules.Azure/issues/2979) ## v1.38.0 diff --git a/docs/en/rules/Azure.SQLMI.MaintenanceWindow.md b/docs/en/rules/Azure.SQLMI.MaintenanceWindow.md new file mode 100644 index 0000000000..ba0500279c --- /dev/null +++ b/docs/en/rules/Azure.SQLMI.MaintenanceWindow.md @@ -0,0 +1,99 @@ +--- +severity: Important +pillar: Reliability +category: RE:04 Target metrics +resource: SQL Managed Instance +online version: https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.SQLMI.MaintenanceWindow/ +--- + +# Customer-controlled maintenance window configuration + +## SYNOPSIS + +Configure a customer-controlled maintenance window for Azure SQL Managed Instances. + +## DESCRIPTION + +Azure SQL Managed Instances undergo periodic maintenance to ensure your managed databases remains secure, stable, and up-to-date. +This maintenance includes applying security updates, system upgrades, and software patches. + +Maintenance windows can be scheduled in two ways for each instance: + +- System-Managed Schedule: The system automatically selects a 9-hour maintenance window between 8:00 AM to 5:00 PM local time, Monday - Sunday. + - Urgent updates may occur outside of it. To ensure all updates occur only during the maintenance window, select a non-default option. +- Custom Schedule: You can specify a preferred 8-hour maintenance window by choosing between two non-default maintenance windows: + - Weekday window: 10:00 PM to 6:00 AM local time, Monday - Thursday. + - Weekend window: 10:00 PM to 6:00 AM local time, Friday - Sunday. + +By configuring a customer-controlled maintenance window, you can schedule updates to occur during a preferred time, ideally outside business hours, minimizing disruptions. + +There are limitations to the non-default maintenance windows. You can find more details about this in the documentation. + +## RECOMMENDATION + +Consider using a customer-controlled maintenance window to efficiently schedule updates and minimize disruptions. + +## EXAMPLES + +### Configure with Azure template + +To configure managed instances that pass this rule: + +- Set the `properties.maintenanceConfigurationId` property to `/subscriptions//providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL__MI_1` or `SQL__MI_2`. + +For example: + +```json +{ + "type": "Microsoft.Sql/managedInstances", + "apiVersion": "2023-05-01-preview", + "name": "[parameters('sqlManagedInstanceName'))]", + "location": "westeurope", + "sku": { + "name": "GP_Gen5", + }, + "properties": { + "maintenanceConfigurationId": "[subscriptionResourceId('Microsoft.Maintenance/publicMaintenanceConfigurations', 'SQL_WestEurope_MI_1')]" + } +} +``` + +### Configure with Bicep + +To configure managed instances that pass this rule: + +- Set the `properties.maintenanceConfigurationId` property to `/subscriptions//providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL__MI_1` or `SQL__MI_2`. + +For example: + +```bicep +resource maintenanceWindow 'Microsoft.Maintenance/publicMaintenanceConfigurations@2023-04-01' existing = { + scope: subscription() + name: 'SQL_WestEurope_MI_1' +} + +resource sqlManagedInstance 'Microsoft.Sql/managedInstances@2023-05-01-preview' = { + name: sqlManagedInstanceName + location: 'westeurope' + sku: { + name: 'GP_Gen5' + } + properties: { + maintenanceConfigurationId: maintenanceWindow.id + } +} +``` + +## NOTES + +For managed instances within an instance pool, the maintenance configuration set at the instance pool level is inherited by all instances within that pool. +However, instance pools do not support customer-controlled maintenance windows directly. +To specify maintenance windows, you must configure them at the individual instance level, thereby overriding the inherited instance pool configuration. + +## LINKS + +- [RE:04 Target metrics](https://learn.microsoft.com/azure/well-architected/reliability/metrics) +- [Maintenance window in Azure SQL Managed Instance](https://learn.microsoft.com/azure/azure-sql/managed-instance/maintenance-window) +- [Configure maintenance window](https://learn.microsoft.com/azure/azure-sql/managed-instance/maintenance-window-configure) +- [Azure deployment reference - Maintenance Configuration](https://learn.microsoft.com/azure/templates/microsoft.maintenance/publicmaintenanceconfigurations) +- [Azure deployment reference - Azure SQL Managed Instance](https://learn.microsoft.com/azure/templates/microsoft.sql/managedinstances) diff --git a/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 b/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 index 2fd33f0666..8d51a59ba9 100644 --- a/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 +++ b/src/PSRule.Rules.Azure/en/PSRule-rules.psd1 @@ -110,5 +110,6 @@ ResStorageSensitiveDataThreatDetection = "The storage account '{0}' should have sensitive data threat detection in Microsoft Defender for Storage configured." ResAPIDefender = "The API '{0}' should be onboarded to Microsoft Defender for APIs." InsecureParameterType = "The parameter '{0}' with type '{1}' is not secure." + AzureSQLMIMaintenanceWindow = "The managed instance ({0}) should have a customer-controlled maintenance window configured." AzureSQLDatabaseMaintenanceWindow = "The {0} ({1}) should have a customer-controlled maintenance window configured." } diff --git a/src/PSRule.Rules.Azure/rules/Azure.SQLMI.Rule.ps1 b/src/PSRule.Rules.Azure/rules/Azure.SQLMI.Rule.ps1 index feafa8217f..b462b5af49 100644 --- a/src/PSRule.Rules.Azure/rules/Azure.SQLMI.Rule.ps1 +++ b/src/PSRule.Rules.Azure/rules/Azure.SQLMI.Rule.ps1 @@ -53,4 +53,13 @@ Rule 'Azure.SQLMI.AAD' -Ref 'AZR-000368' -Type 'Microsoft.Sql/managedInstances', } } +# Synopsis: Configure a customer-controlled maintenance window for Azure SQL managed instances. +Rule 'Azure.SQLMI.MaintenanceWindow' -Ref 'AZR-000441' -Type 'Microsoft.Sql/managedInstances' -Tag @{ release = 'GA'; ruleSet = '2024_09'; 'Azure.WAF/pillar' = 'Reliability'; } { + $Assert.Match($TargetObject, 'properties.maintenanceConfigurationId', '\/publicMaintenanceConfigurations\/SQL_[A-Za-z]+[A-Za-z0-9]*_MI_[12]$', $False). + Reason( + $LocalizedData.AzureSQLMIMaintenanceWindow, + $TargetObject.Name + ) +} + #endregion SQL Managed Instance diff --git a/tests/PSRule.Rules.Azure.Tests/Azure.SQLMI.Tests.ps1 b/tests/PSRule.Rules.Azure.Tests/Azure.SQLMI.Tests.ps1 index 7107574de1..4fae748c71 100644 --- a/tests/PSRule.Rules.Azure.Tests/Azure.SQLMI.Tests.ps1 +++ b/tests/PSRule.Rules.Azure.Tests/Azure.SQLMI.Tests.ps1 @@ -90,6 +90,23 @@ Describe 'Azure.SQLMI' -Tag 'SQLMI' { $ruleResult.Length | Should -Be 3; $ruleResult.TargetName | Should -BeIn 'server-B', 'server-D', 'ActiveDirectoryAdmin-B'; } + + It 'Azure.SQLMI.MaintenanceWindow' { + $filteredResult = $result | Where-Object { $_.RuleName -eq 'Azure.SQLMI.MaintenanceWindow' }; + + # Fail + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Fail' }); + $ruleResult.Length | Should -Be 2; + $ruleResult.TargetName | Should -BeIn 'server-A', 'server-B'; + + $ruleResult[0].Reason | Should -BeExactly "The managed instance (server-A) should have a customer-controlled maintenance window configured."; + $ruleResult[1].Reason | Should -BeExactly "The managed instance (server-B) should have a customer-controlled maintenance window configured."; + + # Pass + $ruleResult = @($filteredResult | Where-Object { $_.Outcome -eq 'Pass' }); + $ruleResult.Length | Should -Be 2; + $ruleResult.TargetName | Should -BeIn 'server-C', 'server-D'; + } } Context 'Resource name - Azure.SQLMI.Name' { @@ -139,4 +156,4 @@ Describe 'Azure.SQLMI' -Tag 'SQLMI' { $ruleResult.Outcome | Should -Be 'Fail'; } } -} \ No newline at end of file +} diff --git a/tests/PSRule.Rules.Azure.Tests/Resources.SQLMI.json b/tests/PSRule.Rules.Azure.Tests/Resources.SQLMI.json index 1ca5de3a91..f7e969a886 100644 --- a/tests/PSRule.Rules.Azure.Tests/Resources.SQLMI.json +++ b/tests/PSRule.Rules.Azure.Tests/Resources.SQLMI.json @@ -59,7 +59,8 @@ "primaryUserAssignedIdentityId": null, "publicNetworkAccess": null, "restrictOutboundNetworkAccess": null, - "version": null + "version": null, + "maintenanceConfigurationId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_Default" } }, { @@ -85,7 +86,8 @@ "primaryUserAssignedIdentityId": null, "publicNetworkAccess": null, "restrictOutboundNetworkAccess": null, - "version": null + "version": null, + "maintenanceConfigurationId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_WestEurope_MI_1" }, "resources": [ { @@ -138,7 +140,8 @@ "primaryUserAssignedIdentityId": null, "publicNetworkAccess": null, "restrictOutboundNetworkAccess": null, - "version": null + "version": null, + "maintenanceConfigurationId": "/subscriptions/00000000-0000-0000-0000-000000000000/providers/Microsoft.Maintenance/publicMaintenanceConfigurations/SQL_EastUS_MI_2" }, "resources": [ {