Private/Get-AACDriftRule.ps1
|
function Get-AACDriftRule { <# .SYNOPSIS The desired-state rules Get-AACConfigurationDrift checks: the built-in security baseline (-UseDefaultRules), and/or the rules in a file (-Path, .psd1 or .json). .DESCRIPTION A rule: @{ Name; ResourceType ('microsoft.storage/storageaccounts', wildcards work); Property ('properties.minimumTlsVersion', as a snapshot path); Operator (Equals, NotEquals, In, Match, Exists); Expected (a value, or values for In); Severity (High, Medium, Low); Remediation }. A .psd1 file holds @{ Rules = @( @{ ... } ) }; a .json file an array of them (or { "Rules": [...] }). #> [CmdletBinding()] [OutputType([hashtable])] param( [string] $Path, [switch] $Default ) $rules = [System.Collections.Generic.List[hashtable]]::new() $rule = { param([string] $Type, [string] $Property, [string] $Operator, $Expected, [string] $Severity, [string] $Fix) @{ Name = "$($Type -replace '^microsoft\.', '') $($Property -replace '^properties\.', '')"; ResourceType = $Type; Property = $Property; Operator = $Operator; Expected = $Expected; Severity = $Severity; Remediation = $Fix } } if ($Default) { foreach ($r in @( (& $rule 'microsoft.storage/storageaccounts' 'properties.minimumTlsVersion' 'In' @('TLS1_2', 'TLS1_3') 'High' 'Set the minimum TLS version to 1.2.') (& $rule 'microsoft.storage/storageaccounts' 'properties.supportsHttpsTrafficOnly' 'Equals' 'true' 'High' 'Allow HTTPS traffic only.') (& $rule 'microsoft.storage/storageaccounts' 'properties.allowBlobPublicAccess' 'Equals' 'false' 'High' 'Disallow anonymous blob access.') (& $rule 'microsoft.storage/storageaccounts' 'properties.allowSharedKeyAccess' 'Equals' 'false' 'Medium' 'Disable shared key access: use Entra ID.') (& $rule 'microsoft.keyvault/vaults' 'properties.enableSoftDelete' 'Equals' 'true' 'High' 'Enable soft delete.') (& $rule 'microsoft.keyvault/vaults' 'properties.enablePurgeProtection' 'Equals' 'true' 'Medium' 'Enable purge protection.') (& $rule 'microsoft.keyvault/vaults' 'properties.enableRbacAuthorization' 'Equals' 'true' 'Low' 'Use Azure RBAC for data access rather than access policies.') (& $rule 'microsoft.web/sites' 'properties.httpsOnly' 'Equals' 'true' 'High' 'Turn on HTTPS only.') (& $rule 'microsoft.sql/servers' 'properties.minimalTlsVersion' 'In' @('1.2', '1.3') 'High' 'Set the minimal TLS version to 1.2.') (& $rule 'microsoft.sql/servers' 'properties.publicNetworkAccess' 'Equals' 'Disabled' 'Medium' 'Disable public network access: use private endpoints.') (& $rule 'microsoft.containerservice/managedclusters' 'properties.enableRBAC' 'Equals' 'true' 'High' 'Enable Kubernetes RBAC.') (& $rule 'microsoft.containerservice/managedclusters' 'properties.disableLocalAccounts' 'Equals' 'true' 'Medium' 'Disable local accounts: sign in with Entra ID.') (& $rule 'microsoft.documentdb/databaseaccounts' 'properties.disableLocalAuth' 'Equals' 'true' 'Medium' 'Disable key-based authentication: use Entra ID.') (& $rule 'microsoft.cache/redis' 'properties.enableNonSslPort' 'Equals' 'false' 'High' 'Disable the non-TLS port.') (& $rule 'microsoft.cache/redis' 'properties.minimumTlsVersion' 'In' @('1.2') 'High' 'Set the minimum TLS version to 1.2.') (& $rule 'microsoft.containerregistry/registries' 'properties.adminUserEnabled' 'Equals' 'false' 'Medium' 'Disable the admin user: use Entra ID or tokens.') )) { $rules.Add($r) } } if ($Path) { if (-not (Test-Path -LiteralPath $Path)) { throw "The desired-state file $Path doesn't exist." } $loaded = if ($Path -like '*.psd1') { Import-PowerShellDataFile -LiteralPath $Path } else { Get-Content -LiteralPath $Path -Raw | ConvertFrom-Json -AsHashtable } $list = if ($loaded -is [System.Collections.IDictionary] -and $loaded.Contains('Rules')) { @($loaded['Rules']) } else { @($loaded) } $n = 0 foreach ($item in $list) { $n++ if ($item -isnot [System.Collections.IDictionary] -or -not $item.Contains('ResourceType') -or -not $item.Contains('Property')) { throw "Rule $n in $Path needs ResourceType and Property." } $operator = if ($item.Contains('Operator')) { [string]$item['Operator'] } else { 'Equals' } if ($operator -notin 'Equals', 'NotEquals', 'In', 'Match', 'Exists') { throw "Rule $n in $Path has an unknown Operator '$operator': Equals, NotEquals, In, Match or Exists." } $rules.Add(@{ Name = $(if ($item.Contains('Name')) { [string]$item['Name'] } else { "$($item['ResourceType'] -replace '^microsoft\.', '') $($item['Property'] -replace '^properties\.', '')" }) ResourceType = ([string]$item['ResourceType']).ToLowerInvariant(); Property = [string]$item['Property']; Operator = $operator Expected = $(if ($item.Contains('Expected')) { $item['Expected'] } else { $null }); Severity = $(if ($item.Contains('Severity')) { [string]$item['Severity'] } else { 'Medium' }) Remediation = $(if ($item.Contains('Remediation')) { [string]$item['Remediation'] } else { "Set $($item['Property']) as the rule expects." }) }) } } $rules.ToArray() } |