Skip to content

Commit

Permalink
Merge pull request #323 from MicrosoftLearning/KenMAG-Slice-Labs
Browse files Browse the repository at this point in the history
Merge Ken mag slice labs branch
  • Loading branch information
KenMAG authored Dec 19, 2024
2 parents ca7741a + 8f322b9 commit 910c7fb
Show file tree
Hide file tree
Showing 53 changed files with 1,288 additions and 636 deletions.
226 changes: 226 additions & 0 deletions Allfiles/Bicep/Create-NewSolutionAndRulesFromList.ps1
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
param(
[Parameter(Mandatory = $true)][string]$ResourceGroup,
[Parameter(Mandatory = $true)][string]$Workspace,
[Parameter(Mandatory = $true)][string]$Region,
[Parameter(Mandatory = $true)][string[]]$Solutions,
[Parameter(Mandatory = $true)][string]$SubscriptionId,
[Parameter(Mandatory = $true)][string]$TenantId,
[Parameter(Mandatory = $true)][string]$Identity,
[Parameter(Mandatory = $false)][string]$Buffer
)

Write-Output "Resource Group: $ResourceGroup"
Write-Output "Workspace: $Workspace"
Write-Output "Region: $Region"
Write-Output "Solutions: $Solutions"
Write-Output "SubscriptionId: $SubscriptionId"
Write-Output "TenantId: $TenantId"
Write-Output "Identity: $Identity"
Write-Output "Buffer: " $Buffer

$VerbosePreference = "Continue"

Connect-AzAccount -Identity -AccountId $Identity

$SeveritiesToInclude = @("informational", "low", "medium", "high")
$apiVersion = "?api-version=2024-03-01"
$instanceProfile = [Microsoft.Azure.Commands.Common.Authentication.Abstractions.AzureRmProfileProvider]::Instance.Profile
$profileClient = New-Object -TypeName Microsoft.Azure.Commands.ResourceManager.Common.RMProfileClient -ArgumentList ($instanceProfile)
$token = $profileClient.AcquireAccessToken($TenantId)
$authHeader = @{
'Content-Type' = 'application/json'
'Authorization' = 'Bearer ' + $token.AccessToken
}

$serverUrl = "https://management.azure.com"
$baseUri = $serverUrl + $SubscriptionId + "/resourceGroups/${ResourceGroup}/providers/Microsoft.OperationalInsights/workspaces/${Workspace}"
$alertUri = "$baseUri/providers/Microsoft.SecurityInsights/alertRules/"

Write-Output " Base Uri: $baseUri"

# Get a list of all the solutions
$url = $baseUri + "/providers/Microsoft.SecurityInsights/contentProductPackages" + $apiVersion

Write-Output " Content Product Packages Uri: $url"

$allSolutions = (Invoke-RestMethod -Method "Get" -Uri $url -Headers $authHeader ).value

Write-Output "Number of solutions: " ($allSolutions.count)

#Deploy each single solution
foreach ($deploySolution in $Solutions) {
Write-Output "Deploy Solution: $deploySolution"
if ($deploySolution.StartsWith("["))
{
$deploySolution = $deploySolution.Substring(1)
}
Write-Output "Deploy Solution: $deploySolution"
$singleSolution = $allSolutions | Where-Object { $_.properties.displayName -Contains $deploySolution }
if ($null -eq $singleSolution) {
Write-Error "Unable to get find solution with name $deploySolution"
}
else {
$solutionURL = $baseUri + "/providers/Microsoft.SecurityInsights/contentProductPackages/$($singleSolution.name)" + $apiVersion
$solution = (Invoke-RestMethod -Method "Get" -Uri $solutionURL -Headers $authHeader )
Write-Output "Solution name: " $solution.name
$packagedContent = $solution.properties.packagedContent
#Some of the post deployment instruction contains invalid characters and since this is not displayed anywhere
#get rid of them.
foreach ($resource in $packagedContent.resources) {
if ($null -ne $resource.properties.mainTemplate.metadata.postDeployment ) {
$resource.properties.mainTemplate.metadata.postDeployment = $null
}
}
$installBody = @{"properties" = @{
"parameters" = @{
"workspace" = @{"value" = $Workspace }
"workspace-location" = @{"value" = $Region }
}
"template" = $packagedContent
"mode" = "Incremental"
}
}
$deploymentName = ("allinone-" + $solution.name)
if ($deploymentName.Length -ge 64) {
$deploymentName = $deploymentName.Substring(0, 64)
}
$installURL = $serverUrl + $SubscriptionId + "/resourcegroups/$($ResourceGroup)/providers/Microsoft.Resources/deployments/" + $deploymentName + $apiVersion
Write-Output "Deploying solution: $deploySolution"
Write-Output "Deploy URL: $installURL"

try {
Invoke-RestMethod -Uri $installURL -Method Put -Headers $authHeader -Body ($installBody | ConvertTo-Json -EnumsAsStrings -Depth 50 -EscapeHandling EscapeNonAscii)
Write-Output "Deployed solution: $deploySolution"
}
catch {
$errorReturn = $_
Write-Error $errorReturn
}
}

}

#####
#create rules from any rule templates that came from solutions
#####

if (($SeveritiesToInclude -eq "None") -or ($null -eq $SeveritiesToInclude)) {
Exit
}

#Give the system time to update all the needed databases before trying to install the rules.
Start-Sleep -Seconds 60

#URL to get all the needed Analytic Rule templates
$solutionURL = $baseUri + "/providers/Microsoft.SecurityInsights/contentTemplates" + $apiVersion
#Add a filter only return analytic rule templates
$solutionURL += "&%24filter=(properties%2FcontentKind%20eq%20'AnalyticsRule')&%24expand=properties/mainTemplate"

Write-Output "Solution URL: $solutionURL"

$results = (Invoke-RestMethod -Uri $solutionURL -Method Get -Headers $authHeader).value

$BaseAlertUri = $baseUri + "/providers/Microsoft.SecurityInsights/alertRules/"
$BaseMetaURI = $baseURI + "/providers/Microsoft.SecurityInsights/metadata/analyticsrule-"

Write-Output "Results: " ($results.count)

Write-Output "Severities to include... $SeveritiesToInclude"

#Iterate through all the rule templates
foreach ($result in $results ) {
#Make sure that the template's severity is one we want to include
$severity = $result.properties.mainTemplate.resources.properties[0].severity
Write-Output "Rule Template's severity is... $severity "
if ($SeveritiesToInclude.Contains($severity.ToLower())) {
Write-Output "Enabling alert rule template... " $result.properties.template.resources.properties.displayName

$templateVersion = $result.properties.mainTemplate.resources.properties[1].version
$template = $result.properties.mainTemplate.resources.properties[0]
$kind = $result.properties.mainTemplate.resources.kind
$displayName = $template.displayName
$eventGroupingSettings = $template.eventGroupingSettings
if ($null -eq $eventGroupingSettings) {
$eventGroupingSettings = [ordered]@{aggregationKind = "SingleAlert" }
}
$body = ""
$properties = $result.properties.mainTemplate.resources[0].properties
$properties.enabled = $true
#Add the field to link this rule with the rule template so that the rule template will show up as used
#We had to use the "Add-Member" command since this field does not exist in the rule template that we are copying from.
$properties | Add-Member -NotePropertyName "alertRuleTemplateName" -NotePropertyValue $result.properties.mainTemplate.resources[0].name
$properties | Add-Member -NotePropertyName "templateVersion" -NotePropertyValue $result.properties.mainTemplate.resources[1].properties.version


#Depending on the type of alert we are creating, the body has different parameters
switch ($kind) {
"MicrosoftSecurityIncidentCreation" {
$body = @{
"kind" = "MicrosoftSecurityIncidentCreation"
"properties" = $properties
}
}
"NRT" {
$body = @{
"kind" = "NRT"
"properties" = $properties
}
}
"Scheduled" {
$body = @{
"kind" = "Scheduled"
"properties" = $properties
}

}
Default { }
}
#If we have created the body...
if ("" -ne $body) {
#Create the GUId for the alert and create it.
$guid = (New-Guid).Guid
#Create the URI we need to create the alert.
$alertUri = $BaseAlertUri + $guid + "?api-version=2022-12-01-preview"
try {
Write-Output "Attempting to create rule $($displayName)"
$verdict = Invoke-RestMethod -Uri $alertUri -Method Put -Headers $authHeader -Body ($body | ConvertTo-Json -EnumsAsStrings -Depth 50)
#Invoke-RestMethod -Uri $installURL -Method Put -Headers $authHeader -Body ($installBody | ConvertTo-Json -EnumsAsStrings -Depth 50)
Write-Output "Succeeded"
$solution = $allSolutions.properties | Where-Object -Property "contentId" -Contains $result.properties.packageId
$metabody = @{
"apiVersion" = "2022-01-01-preview"
"name" = "analyticsrule-" + $verdict.name
"type" = "Microsoft.OperationalInsights/workspaces/providers/metadata"
"id" = $null
"properties" = @{
"contentId" = $result.properties.mainTemplate.resources[0].name
"parentId" = $verdict.id
"kind" = "AnalyticsRule"
"version" = $templateVersion
"source" = $solution.source
"author" = $solution.author
"support" = $solution.support
}
}
Write-Output " Updating metadata...."
$metaURI = $BaseMetaURI + $verdict.name + "?api-version=2022-01-01-preview"
$metaVerdict = Invoke-RestMethod -Uri $metaURI -Method Put -Headers $authHeader -Body ($metabody | ConvertTo-Json -EnumsAsStrings -Depth 5)
Write-Output "Succeeded"
}
catch {
#The most likely error is that there is a missing dataset. There is a new
#addition to the REST API to check for the existance of a dataset but
#it only checks certain ones. Hope to modify this to do the check
#before trying to create the alert.
$errorReturn = $_
Write-Error $errorReturn
Write-Output $errorReturn
}
#This pauses for 5 second so that we don't overload the workspace.
Start-Sleep -Seconds 1
}
else {
Write-Outout "No body created"
}
}
}
21 changes: 21 additions & 0 deletions Allfiles/Bicep/Readme.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
## Folder for Bicep & PowerShell files

Use to pre-install Microsoft Sentinel and Content Hub Solutions from WIN1.

### Instructions

1. Create a *Resource Group* for the deployment.

```azurecli
az group create --location eastus --resource-group Defender-RG
```
1. Deploy the Bicep template.
```azurecli
az deployment group create --name testDeploy --template-file .\sentinel.bicep --parameters .\sentinelParams.bicepparam --resource-group Defender-RG
```
### Additional Information
See the following *Microsoft Tech Community* blog post for more information: [Deploy Microsoft Sentinel using Bicep](https://techcommunity.microsoft.com/blog/microsoftsentinelblog/deploy-microsoft-sentinel-using-bicep/4270970)
133 changes: 133 additions & 0 deletions Allfiles/Bicep/Sentinel.bicep
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
targetScope = 'resourceGroup'

@description('Specifies the name of the client who needs Sentinel.')
param workspaceName string

@description('Specifies the number of days to retain data.')
param retentionInDays int

@description('Which solutions to deploy automatically')
param contentSolutions string[]

var subscriptionId = subscription().id
var location = resourceGroup().location
//Sentinel Contributor role GUID
var roleDefinitionId = 'ab8e14d6-4a74-4a29-9ba8-549422addade'

// Create the Log Analytics Workspace
resource workspace 'Microsoft.OperationalInsights/workspaces@2022-10-01' = {
name: workspaceName
location: location
properties: {
retentionInDays: retentionInDays
}
}

// Create Microsoft Sentinel on the Log Analytics Workspace
resource sentinel 'Microsoft.OperationsManagement/solutions@2015-11-01-preview' = {
name: 'SecurityInsights(${workspaceName})'
location: location
properties: {
workspaceResourceId: workspace.id
}
plan: {
name: 'SecurityInsights(${workspaceName})'
product: 'OMSGallery/SecurityInsights'
promotionCode: ''
publisher: 'Microsoft'
}
}

// Onboard Sentinel after it has been created
resource onboardingStates 'Microsoft.SecurityInsights/onboardingStates@2022-12-01-preview' = {
scope: workspace
name: 'default'
}

/*
// Enable the Entity Behavior directory service
resource EntityAnalytics 'Microsoft.SecurityInsights/settings@2023-02-01-preview' = {
name: 'EntityAnalytics'
kind: 'EntityAnalytics'
scope: workspace
properties: {
entityProviders: ['AzureActiveDirectory']
}
dependsOn: [
onboardingStates
]
}
// Enable the additional UEBA data sources
resource uebaAnalytics 'Microsoft.SecurityInsights/settings@2023-02-01-preview' = {
name: 'Ueba'
kind: 'Ueba'
scope: workspace
properties: {
dataSources: ['AuditLogs', 'AzureActivity', 'SigninLogs', 'SecurityEvent']
}
dependsOn: [
EntityAnalytics
]
}
*/

//Create the user identity to interact with Azure
@description('The user identity for the deployment script.')
resource scriptIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' = {
name: 'script-identity'
location: location
}

//Pausing for 5 minutes to allow the new user identity to propagate
resource pauseScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'pauseScript'
location: resourceGroup().location
kind: 'AzurePowerShell'
properties: {
azPowerShellVersion: '12.2.0'
scriptContent: 'Start-Sleep -Seconds 300'
timeout: 'PT30M'
cleanupPreference: 'OnSuccess'
retentionInterval: 'PT1H'
}
dependsOn: [
scriptIdentity
]
}

//Assign the Sentinel Contributor rights on the Resource Group to the User Identity that was just created
resource roleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().name, roleDefinitionId)
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', roleDefinitionId)
principalId: scriptIdentity.properties.principalId
}
dependsOn: [
pauseScript
]
}

// Call the external PowerShell script to deploy the solutions and rules
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2023-08-01' = {
name: 'deploySolutionsScript'
location: resourceGroup().location
kind: 'AzurePowerShell'
identity: {
type: 'UserAssigned'
userAssignedIdentities: {
'${scriptIdentity.id}': {}
}
}
properties: {
azPowerShellVersion: '12.2.0'
arguments: '-ResourceGroup ${resourceGroup().name} -Workspace ${workspaceName} -Region ${resourceGroup().location} -Solutions ${contentSolutions} -SubscriptionId ${subscriptionId} -TenantId ${subscription().tenantId} -Identity ${scriptIdentity.properties.clientId} '
scriptContent: loadTextContent('./Create-NewSolutionAndRulesFromList.ps1')
timeout: 'PT30M'
cleanupPreference: 'OnSuccess'
retentionInterval: 'P1D'
}
dependsOn: [
roleAssignment
]
}
12 changes: 12 additions & 0 deletions Allfiles/Bicep/sentinelParams.bicepparam
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using './Sentinel.bicep'

param workspaceName = 'defenderWorkspace'
param retentionInDays = 90
param contentSolutions = [
'Microsoft Defender For Cloud'
'Sentinel SOAR Essentials'
'Azure Activity'
'Windows Security Events'
'Common Event Format'
'Syslog'
]
Loading

0 comments on commit 910c7fb

Please sign in to comment.