Public/Import-AzSentinelAlertRule.ps1
#requires -module @{ModuleName = 'Az.Accounts'; ModuleVersion = '1.5.2'} #requires -version 6.2 function Import-AzSentinelAlertRule { <# .SYNOPSIS Import Azure Sentinal Alert rule .DESCRIPTION This function imports Azure Sentinal Alert rules from JSON and YAML config files. This way you can manage your Alert rules dynamic from JSON or multiple YAML files .PARAMETER SubscriptionId Enter the subscription ID, if no subscription ID is provided then current AZContext subscription will be used .PARAMETER WorkspaceName Enter the Workspace name .PARAMETER SettingsFile Path to the JSON or YAML file for the AlertRules .EXAMPLE Import-AzSentinelAlertRule -WorkspaceName "" -SettingsFile ".\examples\AlertRules.json" In this example all the rules configured in the JSON file will be created or updated Performing the operation "Import-AzSentinelAlertRule" on target "Do you want to update profile: AlertRule01". [Y] Yes [A] Yes to All [N] No [L] No to All [S] Suspend [?] Help (default is "Yes"): Successfully created Action for Rule: with Playbook pkmsentinel Status: Created Created Successfully updated rule: AlertRule01 with status: OK Name : b6103d42-xxx-4f35-xxx-c76a7f31ee4e DisplayName : AlertRule01 Description : Severity : Medium Enabled : True Query : SecurityEvent | where EventID == "4688" | where CommandLine contains "-noni -ep bypass $" QueryFrequency : PT5H QueryPeriod : PT6H TriggerOperator : GreaterThan TriggerThreshold : 5 SuppressionDuration : PT6H SuppressionEnabled : False Tactics : {Persistence, LateralMovement, Collection} PlaybookName : Playbook01 .EXAMPLE Import-AzSentinelAlertRule -WorkspaceName "" -SettingsFile ".\examples\SuspectApplicationConsent.yaml" In this example all the rules configured in the YAML file will be created or updated .EXAMPLE Get-Item .\examples\*.json | Import-AzSentinelAlertRule -WorkspaceName "" In this example you can select multiple JSON files and Pipeline it to the SettingsFile parameter #> [CmdletBinding(SupportsShouldProcess, ConfirmImpact = 'High')] param ( [Parameter(Mandatory = $false, ParameterSetName = "Sub")] [ValidateNotNullOrEmpty()] [string] $SubscriptionId, [Parameter(Mandatory)] [ValidateNotNullOrEmpty()] [string] $WorkspaceName, [Parameter(Mandatory, ValueFromPipeline)] [ValidateScript( { (Test-Path -Path $_) -and ($_.Extension -in '.json', '.yaml', '.yml') })] [System.IO.FileInfo] $SettingsFile ) begin { precheck } process { switch ($PsCmdlet.ParameterSetName) { Sub { $arguments = @{ WorkspaceName = $WorkspaceName SubscriptionId = $SubscriptionId } } default { $arguments = @{ WorkspaceName = $WorkspaceName } } } if ($SettingsFile.Extension -eq '.json') { try { $rulesRaw = Get-Content $SettingsFile -Raw $rules = $rulesRaw | ConvertFrom-Json -Depth 99 Write-Verbose -Message "Found $($rules.count) rules" } catch { Write-Verbose $_ Write-Error -Message 'Unable to import JSON file' -ErrorAction Stop } } elseif ($SettingsFile.Extension -in '.yaml', '.yml') { try { $rules = [pscustomobject](Get-Content $SettingsFile -Raw | ConvertFrom-Yaml -ErrorAction Stop) $rules | Add-Member -MemberType NoteProperty -Name DisplayName -Value $rules.name Write-Verbose -Message 'Found compatibel yaml file' } catch { Write-Verbose $_ Write-Error -Message 'Unable to convert yaml file' -ErrorAction Stop } } else { Write-Error -Message 'Unsupported extension for SettingsFile' -ErrorAction Stop } $return = @() <# Test All rules first #> if($rules.analytics -or $rules.Scheduled -or $rules.fusion -or $rules.MLBehaviorAnalytics -or $rules.MicrosoftSecurityIncidentCreation) { $allRules = $rules.analytics + $rules.Scheduled + $rules.fusion + $rules.MLBehaviorAnalytics + $rules.MicrosoftSecurityIncidentCreation | Select-Object displayName try { Write-Verbose -Message "Found $($allRules.displayName.Count) rules in the settings file." $allRulesContent = Get-AzSentinelAlertRule @arguments -RuleName $($allRules.displayName) -ErrorAction Stop } catch { Write-Error $_.Exception.Message break } } <# Analytics rule Take the raw rule configuration if it is not nested in "analytics", "Scheduled", "fusion", "MLBehaviorAnalytics" or "MicrosoftIncidentCreation" #> if (-not $rules.analytics -and -not $rules.Scheduled -and -not $rules.fusion -and -not $rules.MLBehaviorAnalytics -and -not $rules.MicrosoftSecurityIncidentCreation){ Write-Verbose -Message "Settings file is not nested in root schema, using raw configuration." $scheduled = $rules } elseif ($rules.analytics) { $scheduled = $rules.analytics } else{ $scheduled = $rules.Scheduled } foreach ($item in $scheduled) { Write-Verbose -Message "Started with rule: $($item.displayName)" $guid = (New-Guid).Guid if($allRulesContent) { $content = $allRulesContent | Where-Object {$_.kind -eq 'Scheduled' -and $_.displayName -eq $item.displayName} } else{ $content = Get-AzSentinelAlertRule @arguments -RuleName $($item.displayName) -ErrorAction Stop } Write-Verbose -Message "Get rule $($item.description)" if ($content) { Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview" } else { Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview" } # The official API schema indicates that the grouping configuration is part of the incident configuration try { # Added if/else statement for backwards compatibility if($item.incidentConfiguration){ $groupingConfiguration = [GroupingConfiguration]::new( $item.incidentConfiguration.groupingConfiguration.enabled, $item.incidentConfiguration.groupingConfiguration.reopenClosedIncident, $item.incidentConfiguration.groupingConfiguration.lookbackDuration, $item.incidentConfiguration.groupingConfiguration.entitiesMatchingMethod, $item.incidentConfiguration.groupingConfiguration.groupByEntities ) $incidentConfiguration = [IncidentConfiguration]::new( $item.incidentConfiguration.createIncident, $groupingConfiguration ) } else{ $groupingConfiguration = [GroupingConfiguration]::new( $item.groupingConfiguration.enabled, $item.groupingConfiguration.reopenClosedIncident, $item.groupingConfiguration.lookbackDuration, $item.groupingConfiguration.entitiesMatchingMethod, $item.groupingConfiguration.groupByEntities ) $incidentConfiguration = [IncidentConfiguration]::new( $item.createIncident, $groupingConfiguration ) Write-Warning -Message "`"$($item.displayName)`" configuration is not following the official API schema, consider updating the incident and grouping configuration." } if (($item.AlertRuleTemplateName -and ! $content) -or $content.AlertRuleTemplateName){ if ($content.AlertRuleTemplateName){ <# If alertRule is already created with a TemplateName then Always use template name from existing rule. You can't attach existing scheduled rule to another templatename or remove the link to the template #> $item | Add-Member -NotePropertyName AlertRuleTemplateName -NotePropertyValue $content.AlertRuleTemplateName -Force } $bodyAlertProp = [ScheduledAlertProp]::new( $item.name, $item.displayName, $item.description, $item.severity, $item.enabled, $item.query, $item.queryFrequency, $item.queryPeriod, $item.triggerOperator, $item.triggerThreshold, $item.suppressionDuration, $item.suppressionEnabled, $item.Tactics, $item.playbookName, $incidentConfiguration, $item.aggregationKind, $item.AlertRuleTemplateName ) } else { $bodyAlertProp = [ScheduledAlertProp]::new( $item.name, $item.displayName, $item.description, $item.severity, $item.enabled, $item.query, $item.queryFrequency, $item.queryPeriod, $item.triggerOperator, $item.triggerThreshold, $item.suppressionDuration, $item.suppressionEnabled, $item.Tactics, $item.playbookName, $incidentConfiguration, $item.aggregationKind ) } $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Scheduled') } catch { Write-Error "Unable to initiate class with error: $($_.Exception.Message)" -ErrorAction Stop } if ($content) { if ($item.playbookName -or $content.playbookName) { $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, incidentConfiguration, queryResultsAggregationSettings) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, incidentConfiguration, queryResultsAggregationSettings) } else { $compareResult = Compare-Policy -ReferenceTemplate ($content | Select-Object * -ExcludeProperty lastModifiedUtc, alertRuleTemplateName, name, etag, id, PlaybookName, incidentConfiguration, queryResultsAggregationSettings) -DifferenceTemplate ($body.Properties | Select-Object * -ExcludeProperty name, PlaybookName, incidentConfiguration, queryResultsAggregationSettings) } try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | Select-Object * -ExcludeProperty Properties.PlaybookName | ConvertTo-Json -Depth 10 -EnumsAsStrings) if (($compareResult | Where-Object PropertyName -eq "playbookName").DiffValue) { $PlaybookResult = New-AzSentinelAlertRuleAction @arguments -PlayBookName $($item.playbookName) -RuleId $($body.Name) $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force } elseif (($compareResult | Where-Object PropertyName -eq "playbookName").RefValue) { $PlaybookResult = Remove-AzSentinelAlertRuleAction @arguments -RuleId $body.Name -Confirm:$false $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force } else { #nothing } $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force $return += $body.Properties } catch { $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force $return += $body.Properties Write-Verbose $_ Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue } } else { Write-Verbose "Creating new rule: $($item.displayName)" try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | Select-Object * -ExcludeProperty Properties.PlaybookName | ConvertTo-Json -Depth 10 -EnumsAsStrings) if ($body.Properties.playbookName) { $PlaybookResult = New-AzSentinelAlertRuleAction @arguments -PlayBookName $($item.playbookName) -RuleId $($body.Name) -confirm:$false $body.Properties | Add-Member -NotePropertyName PlaybookStatus -NotePropertyValue $PlaybookResult -Force } $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force $return += $body.Properties } catch { $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Scheduled" -Force $return += $body.Properties Write-Verbose $_ Write-Error "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue } } } <# Fusion rule #> foreach ($item in $rules.fusion) { Write-Verbose "Rule type is Fusion" $guid = (New-Guid).Guid $content = $allRulesContent | Where-Object {$_.kind -eq 'Fusion' -and $_.displayName -eq $item.displayName} Write-Verbose -Message "Get rule $($item.description)" if ($content) { Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview" } else { Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview" } $bodyAlertProp = [Fusion]::new( $item.enabled, $item.alertRuleTemplateName ) $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'Fusion') try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings) $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force $return += $body.Properties } catch { $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "Fusion" -Force $return += $body.Properties Write-Verbose $_ Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue } } <# MLBehaviorAnalytics #> foreach ($item in $rules.MLBehaviorAnalytics) { Write-Verbose "Rule type is ML Behavior Analytics" $guid = (New-Guid).Guid $content = $allRulesContent | Where-Object {$_.kind -eq 'MLBehaviorAnalytics' -and $_.displayName -eq $item.displayName} Write-Verbose -Message "Get rule $($item.description)" if ($content) { Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview" } else { Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview" } $bodyAlertProp = [MLBehaviorAnalytics]::new( $item.enabled, $item.alertRuleTemplateName ) $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MLBehaviorAnalytics') try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings) $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force $return += $body.Properties } catch { $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MLBehaviorAnalytics" -Force $return += $body.Properties Write-Verbose $_ Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue } } <# MicrosoftSecurityIncidentCreation #> foreach ($item in $rules.MicrosoftSecurityIncidentCreation) { Write-Verbose "Rule type is Microsoft Security" $guid = (New-Guid).Guid $content = $allRulesContent | Where-Object {$_.kind -eq 'MicrosoftSecurityIncidentCreation' -and $_.displayName -eq $item.displayName} Write-Verbose -Message "Get rule $($item.description)" $content = Get-AzSentinelAlertRule @arguments -RuleName $($item.displayName) -ErrorAction SilentlyContinue if ($content) { Write-Verbose "Rule $($item.displayName) exists in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $content.name -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $content.etag -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue $content.id -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($content.name)?api-version=2019-01-01-preview" } else { Write-Verbose -Message "Rule $($item.displayName) doesn't exist in Azure Sentinel" $item | Add-Member -NotePropertyName name -NotePropertyValue $guid -Force $item | Add-Member -NotePropertyName etag -NotePropertyValue $null -Force $item | Add-Member -NotePropertyName Id -NotePropertyValue "$script:Workspace/providers/Microsoft.SecurityInsights/alertRules/$guid" -Force $uri = "$script:baseUri/providers/Microsoft.SecurityInsights/alertRules/$($guid)?api-version=2019-01-01-preview" } $bodyAlertProp = [MicrosoftSecurityIncidentCreation]::new( $item.displayName, $item.description, $item.enabled, $item.productFilter, $item.severitiesFilter, $item.displayNamesFilter ) $body = [AlertRule]::new( $item.name, $item.etag, $bodyAlertProp, $item.Id, 'MicrosoftSecurityIncidentCreation') try { $result = Invoke-webrequest -Uri $uri -Method Put -Headers $script:authHeader -Body ($body | ConvertTo-Json -Depth 10 -EnumsAsStrings) $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue $($result.StatusDescription) -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force $return += $body.Properties } catch { $body.Properties | Add-Member -NotePropertyName status -NotePropertyValue "failed" -Force $body.Properties | Add-Member -NotePropertyName Kind -NotePropertyValue "MicrosoftSecurityIncidentCreation" -Force $return += $body.Properties Write-Verbose $_ Write-Verbose "Unable to invoke webrequest for rule $($item.displayName) with error message: $($_.Exception.Message)" -ErrorAction Continue } } return $return } } |