diff --git a/.github/workflows/azure-sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.ps1 b/.github/workflows/azure-sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.ps1 new file mode 100644 index 00000000000..7aa9adf09bc --- /dev/null +++ b/.github/workflows/azure-sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.ps1 @@ -0,0 +1,602 @@ +## Globals ## +$CloudEnv = $Env:cloudEnv +$ResourceGroupName = $Env:resourceGroupName +$WorkspaceName = $Env:workspaceName +$WorkspaceId = $Env:workspaceId +$Directory = $Env:directory +$Creds = $Env:creds +$contentTypes = $Env:contentTypes +$contentTypeMapping = @{ + "AnalyticsRule"=@("Microsoft.OperationalInsights/workspaces/providers/alertRules", "Microsoft.OperationalInsights/workspaces/providers/alertRules/actions"); + "AutomationRule"=@("Microsoft.OperationalInsights/workspaces/providers/automationRules"); + "HuntingQuery"=@("Microsoft.OperationalInsights/workspaces/savedSearches"); + "Parser"=@("Microsoft.OperationalInsights/workspaces/savedSearches"); + "Playbook"=@("Microsoft.Web/connections", "Microsoft.Logic/workflows", "Microsoft.Web/customApis"); + "Workbook"=@("Microsoft.Insights/workbooks"); +} +$sourceControlId = $Env:sourceControlId +$rootDirectory = $Env:rootDirectory +$githubAuthToken = $Env:githubAuthToken +$githubRepository = $Env:GITHUB_REPOSITORY +$branchName = $Env:branch +$smartDeployment = $Env:smartDeployment +$newResourceBranch = $branchName + "-sentinel-deployment" +$csvPath = "$rootDirectory\.sentinel\tracking_table_$sourceControlId.csv" +$configPath = "$rootDirectory\sentinel-deployment.config" +$global:localCsvTablefinal = @{} +$global:updatedCsvTable = @{} +$global:parameterFileMapping = @{} +$global:prioritizedContentFiles = @() +$global:excludeContentFiles = @() + +$guidPattern = '(\b[0-9a-f]{8}\b-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-\b[0-9a-f]{12}\b)' +$namePattern = '([-\w\._\(\)]+)' +$sentinelResourcePatterns = @{ + "AnalyticsRule" = "/subscriptions/$guidPattern/resourceGroups/$namePattern/providers/Microsoft.OperationalInsights/workspaces/$namePattern/providers/Microsoft.SecurityInsights/alertRules/$namePattern" + "AutomationRule" = "/subscriptions/$guidPattern/resourceGroups/$namePattern/providers/Microsoft.OperationalInsights/workspaces/$namePattern/providers/Microsoft.SecurityInsights/automationRules/$namePattern" + "HuntingQuery" = "/subscriptions/$guidPattern/resourceGroups/$namePattern/providers/Microsoft.OperationalInsights/workspaces/$namePattern/savedSearches/$namePattern" + "Parser" = "/subscriptions/$guidPattern/resourceGroups/$namePattern/providers/Microsoft.OperationalInsights/workspaces/$namePattern/savedSearches/$namePattern" + "Playbook" = "/subscriptions/$guidPattern/resourceGroups/$namePattern/providers/Microsoft.Logic/workflows/$namePattern" + "Workbook" = "/subscriptions/$guidPattern/resourceGroups/$namePattern/providers/Microsoft.Insights/workbooks/$namePattern" +} + +if ([string]::IsNullOrEmpty($contentTypes)) { + $contentTypes = "AnalyticsRule" +} + +$metadataFilePath = "metadata.json" +@" +{ + "`$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "parentResourceId": { + "type": "string" + }, + "kind": { + "type": "string" + }, + "sourceControlId": { + "type": "string" + }, + "workspace": { + "type": "string" + }, + "contentId": { + "type": "string" + } + }, + "variables": { + "metadataName": "[concat(toLower(parameters('kind')), '-', parameters('contentId'))]" + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',variables('metadataName'))]", + "properties": { + "parentId": "[parameters('parentResourceId')]", + "kind": "[parameters('kind')]", + "source": { + "kind": "SourceRepository", + "name": "Repositories", + "sourceId": "[parameters('sourceControlId')]" + } + } + } + ] +} +"@ | Out-File -FilePath $metadataFilePath + +$resourceTypes = $contentTypes.Split(",") | ForEach-Object { $contentTypeMapping[$_] } | ForEach-Object { $_.ToLower() } +$MaxRetries = 3 +$secondsBetweenAttempts = 5 + +#Converts hashtable to string that can be set as content when pushing csv file +function ConvertTableToString { + $output = "FileName, CommitSha`n" + $global:updatedCsvTable.GetEnumerator() | ForEach-Object { + $key = RelativePathWithBackslash $_.Key + $output += "{0},{1}`n" -f $key, $_.Value + } + return $output +} + +$header = @{ + "authorization" = "Bearer $githubAuthToken" +} + +#Gets all files and commit shas using Get Trees API +function GetGithubTree { + $branchResponse = AttemptInvokeRestMethod "Get" "https://api.github.com/repos/$githubRepository/branches/$branchName" $null $null 3 + $treeUrl = "https://api.github.com/repos/$githubRepository/git/trees/" + $branchResponse.commit.sha + "?recursive=true" + $getTreeResponse = AttemptInvokeRestMethod "Get" $treeUrl $null $null 3 + return $getTreeResponse +} + +#Creates a table using the reponse from the tree api, creates a table +function GetCommitShaTable($getTreeResponse) { + $shaTable = @{} + $getTreeResponse.tree | ForEach-Object { + $truePath = AbsolutePathWithSlash $_.path + if (([System.IO.Path]::GetExtension($_.path) -eq ".json") -or ($truePath -eq $configPath)) + { + $shaTable.Add($truePath, $_.sha) + } + } + return $shaTable +} + +function PushCsvToRepo() { + $content = ConvertTableToString + $relativeCsvPath = RelativePathWithBackslash $csvPath + $resourceBranchExists = git ls-remote --heads "https://github.com/$githubRepository" $newResourceBranch | wc -l + + if ($resourceBranchExists -eq 0) { + git switch --orphan $newResourceBranch + git commit --allow-empty -m "Initial commit on orphan branch" + git push -u origin $newResourceBranch + New-Item -ItemType "directory" -Path ".sentinel" + } else { + git fetch > $null + git checkout $newResourceBranch + } + + Write-Output $content > $relativeCsvPath + git add $relativeCsvPath + git commit -m "Modified tracking table" + git push -u origin $newResourceBranch + git checkout $branchName +} + +function ReadCsvToTable { + $csvTable = Import-Csv -Path $csvPath + $HashTable=@{} + foreach($r in $csvTable) + { + $key = AbsolutePathWithSlash $r.FileName + $HashTable[$key]=$r.CommitSha + } + return $HashTable +} + +function AttemptInvokeRestMethod($method, $url, $body, $contentTypes, $maxRetries) { + $Stoploop = $false + $retryCount = 0 + do { + try { + $result = Invoke-RestMethod -Uri $url -Method $method -Headers $header -Body $body -ContentType $contentTypes + $Stoploop = $true + } + catch { + if ($retryCount -gt $maxRetries) { + Write-Host "[Error] API call failed after $retryCount retries: $_" + $Stoploop = $true + } + else { + Write-Host "[Warning] API call failed: $_.`n Conducting retry #$retryCount." + Start-Sleep -Seconds 5 + $retryCount = $retryCount + 1 + } + } + } + While ($Stoploop -eq $false) + return $result +} + +function AttemptAzLogin($psCredential, $tenantId, $cloudEnv) { + $maxLoginRetries = 3 + $delayInSeconds = 30 + $retryCount = 1 + $stopTrying = $false + do { + try { + Connect-AzAccount -ServicePrincipal -Tenant $tenantId -Credential $psCredential -Environment $cloudEnv | out-null; + Write-Host "Login Successful" + $stopTrying = $true + } + catch { + if ($retryCount -ge $maxLoginRetries) { + Write-Host "Login failed after $maxLoginRetries attempts." + $stopTrying = $true + } + else { + Write-Host "Login attempt failed, retrying in $delayInSeconds seconds." + Start-Sleep -Seconds $delayInSeconds + $retryCount++ + } + } + } + while (-not $stopTrying) +} + +function ConnectAzCloud { + $RawCreds = $Creds | ConvertFrom-Json + + Clear-AzContext -Scope Process; + Clear-AzContext -Scope CurrentUser -Force -ErrorAction SilentlyContinue; + + Add-AzEnvironment ` + -Name $CloudEnv ` + -ActiveDirectoryEndpoint $RawCreds.activeDirectoryEndpointUrl ` + -ResourceManagerEndpoint $RawCreds.resourceManagerEndpointUrl ` + -ActiveDirectoryServiceEndpointResourceId $RawCreds.activeDirectoryServiceEndpointResourceId ` + -GraphEndpoint $RawCreds.graphEndpointUrl | out-null; + + $servicePrincipalKey = ConvertTo-SecureString $RawCreds.clientSecret.replace("'", "''") -AsPlainText -Force + $psCredential = New-Object System.Management.Automation.PSCredential($RawCreds.clientId, $servicePrincipalKey) + + AttemptAzLogin $psCredential $RawCreds.tenantId $CloudEnv + Set-AzContext -Tenant $RawCreds.tenantId | out-null; +} + +function AttemptDeployMetadata($deploymentName, $resourceGroupName, $templateObject) { + $deploymentInfo = $null + try { + $deploymentInfo = Get-AzResourceGroupDeploymentOperation -DeploymentName $deploymentName -ResourceGroupName $ResourceGroupName -ErrorAction Ignore + } + catch { + Write-Host "[Warning] Unable to fetch deployment info for $deploymentName, no metadata was created for the resources in the file. Error: $_" + return + } + $deploymentInfo | Where-Object { $_.TargetResource -ne "" } | ForEach-Object { + $resource = $_.TargetResource + $sentinelContentKinds = GetContentKinds $resource + if ($sentinelContentKinds.Count -gt 0) { + $contentKind = ToContentKind $sentinelContentKinds $resource $templateObject + $contentId = $resource.Split("/")[-1] + try { + New-AzResourceGroupDeployment -Name "md-$deploymentName" -ResourceGroupName $ResourceGroupName -TemplateFile $metadataFilePath ` + -parentResourceId $resource ` + -kind $contentKind ` + -contentId $contentId ` + -sourceControlId $sourceControlId ` + -workspace $workspaceName ` + -ErrorAction Stop | Out-Host + Write-Host "[Info] Created metadata metadata for $contentKind with parent resource id $resource" + } + catch { + Write-Host "[Warning] Failed to deploy metadata for $contentKind with parent resource id $resource with error $_" + } + } + } +} + +function GetContentKinds($resource) { + return $sentinelResourcePatterns.Keys | Where-Object { $resource -match $sentinelResourcePatterns[$_] } +} + +function ToContentKind($contentKinds, $resource, $templateObject) { + if ($contentKinds.Count -eq 1) { + return $contentKinds + } + if ($null -ne $resource -and $resource.Contains('savedSearches')) { + if ($templateObject.resources.properties.Category -eq "Hunting Queries") { + return "HuntingQuery" + } + return "Parser" + } + return $null +} + +function IsValidTemplate($path, $templateObject) { + Try { + if (DoesContainWorkspaceParam $templateObject) { + Test-AzResourceGroupDeployment -ResourceGroupName $ResourceGroupName -TemplateFile $path -workspace $WorkspaceName + } + else { + Test-AzResourceGroupDeployment -ResourceGroupName $ResourceGroupName -TemplateFile $path + } + + return $true + } + Catch { + Write-Host "[Warning] The file $path is not valid: $_" + return $false + } +} + +function IsRetryable($deploymentName) { + $retryableStatusCodes = "Conflict","TooManyRequests","InternalServerError","DeploymentActive" + Try { + $deploymentResult = Get-AzResourceGroupDeploymentOperation -DeploymentName $deploymentName -ResourceGroupName $ResourceGroupName -ErrorAction Stop + return $retryableStatusCodes -contains $deploymentResult.StatusCode + } + Catch { + return $false + } +} + +function IsValidResourceType($template) { + try { + $isAllowedResources = $true + $template.resources | ForEach-Object { + $isAllowedResources = $resourceTypes.contains($_.type.ToLower()) -and $isAllowedResources + } + } + catch { + Write-Host "[Error] Failed to check valid resource type." + $isAllowedResources = $false + } + return $isAllowedResources +} + +function DoesContainWorkspaceParam($templateObject) { + $templateObject.parameters.PSobject.Properties.Name -contains "workspace" +} + +function AttemptDeployment($path, $parameterFile, $deploymentName, $templateObject) { + Write-Host "[Info] Deploying $path with deployment name $deploymentName" + + $isValid = IsValidTemplate $path $templateObject + if (-not $isValid) { + return $false + } + $isSuccess = $false + $currentAttempt = 0 + While (($currentAttempt -lt $MaxRetries) -and (-not $isSuccess)) + { + $currentAttempt ++ + Try + { + Write-Host "[Info] Deploy $path with parameter file: [$parameterFile]" + if (DoesContainWorkspaceParam $templateObject) + { + if ($parameterFile) { + New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $ResourceGroupName -TemplateFile $path -workspace $workspaceName -TemplateParameterFile $parameterFile -ErrorAction Stop | Out-Host + } + else + { + New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $ResourceGroupName -TemplateFile $path -workspace $workspaceName -ErrorAction Stop | Out-Host + } + } + else + { + if ($parameterFile) { + New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $ResourceGroupName -TemplateFile $path -TemplateParameterFile $parameterFile -ErrorAction Stop | Out-Host + } + else + { + New-AzResourceGroupDeployment -Name $deploymentName -ResourceGroupName $ResourceGroupName -TemplateFile $path -ErrorAction Stop | Out-Host + } + } + AttemptDeployMetadata $deploymentName $ResourceGroupName $templateObject + + $isSuccess = $true + } + Catch [Exception] + { + $err = $_ + if (-not (IsRetryable $deploymentName)) + { + Write-Host "[Warning] Failed to deploy $path with error: $err" + break + } + else + { + if ($currentAttempt -le $MaxRetries) + { + Write-Host "[Warning] Failed to deploy $path with error: $err. Retrying in $secondsBetweenAttempts seconds..." + Start-Sleep -Seconds $secondsBetweenAttempts + } + else + { + Write-Host "[Warning] Failed to deploy $path after $currentAttempt attempts with error: $err" + } + } + } + } + return $isSuccess +} + +function GenerateDeploymentName() { + $randomId = [guid]::NewGuid() + return "Sentinel_Deployment_$randomId" +} + +#Load deployment configuration +function LoadDeploymentConfig() { + Write-Host "[Info] load the deployment configuration from [$configPath]" + $global:parameterFileMapping = @{} + $global:prioritizedContentFiles = @() + $global:excludeContentFiles = @() + try { + if (Test-Path $configPath) { + $deployment_config = Get-Content $configPath | Out-String | ConvertFrom-Json + $parameterFileMappings = @{} + if ($deployment_config.parameterfilemappings) { + $deployment_config.parameterfilemappings.psobject.properties | ForEach { $parameterFileMappings[$_.Name] = $_.Value } + } + $key = ($parameterFileMappings.Keys | ? { $_ -eq $workspaceId }) + if ($null -ne $key) { + $parameterFileMappings[$key].psobject.properties | ForEach { $global:parameterFileMapping[$_.Name] = $_.Value } + } + if ($deployment_config.prioritizedcontentfiles) { + $global:prioritizedContentFiles = $deployment_config.prioritizedcontentfiles + } + $excludeList = $global:parameterFileMapping.Values + $global:prioritizedcontentfiles + if ($deployment_config.excludecontentfiles) { + $excludeList = $excludeList + $deployment_config.excludecontentfiles + } + $global:excludeContentFiles = $excludeList | Where-Object { Test-Path (AbsolutePathWithSlash $_) } + } + } + catch { + Write-Host "[Warning] An error occurred while trying to load deployment configuration." + Write-Host "Exception details: $_" + Write-Host $_.ScriptStackTrace + } +} + +function filterContentFile($fullPath) { + $temp = RelativePathWithBackslash $fullPath + return $global:excludeContentFiles | ? {$temp.StartsWith($_, 'CurrentCultureIgnoreCase')} +} + +function RelativePathWithBackslash($absolutePath) { + return $absolutePath.Replace($rootDirectory + "\", "").Replace("\", "/") +} + +function AbsolutePathWithSlash($relativePath) { + return Join-Path -Path $rootDirectory -ChildPath $relativePath +} + +#resolve parameter file name, return $null if there is none. +function GetParameterFile($path) { + $index = RelativePathWithBackslash $path + $key = ($global:parameterFileMapping.Keys | ? { $_ -eq $index }) + if ($key) { + $mappedParameterFile = AbsolutePathWithSlash $global:parameterFileMapping[$key] + if (Test-Path $mappedParameterFile) { + return $mappedParameterFile + } + } + + $parameterFilePrefix = $path.TrimEnd(".json") + + $workspaceParameterFile = $parameterFilePrefix + ".parameters-$WorkspaceId.json" + if (Test-Path $workspaceParameterFile) { + return $workspaceParameterFile + } + + $defaultParameterFile = $parameterFilePrefix + ".parameters.json" + if (Test-Path $defaultParameterFile) { + return $defaultParameterFile + } + + return $null +} + +function Deployment($fullDeploymentFlag, $remoteShaTable, $tree) { + Write-Host "Starting Deployment for Files in path: $Directory" + if (Test-Path -Path $Directory) + { + $totalFiles = 0; + $totalFailed = 0; + $iterationList = @() + $global:prioritizedContentFiles | ForEach-Object { $iterationList += (AbsolutePathWithSlash $_) } + Get-ChildItem -Path $Directory -Recurse -Filter *.json -exclude *metadata.json, *.parameters*.json | + Where-Object { $null -eq ( filterContentFile $_.FullName ) } | + Select-Object -Property FullName | + ForEach-Object { $iterationList += $_.FullName } + $iterationList | ForEach-Object { + $path = $_ + Write-Host "[Info] Try to deploy $path" + if (-not (Test-Path $path)) { + Write-Host "[Warning] Skipping deployment for $path. The file doesn't exist." + return + } + $templateObject = Get-Content $path | Out-String | ConvertFrom-Json + if (-not (IsValidResourceType $templateObject)) + { + Write-Host "[Warning] Skipping deployment for $path. The file contains resources for content that was not selected for deployment. Please add content type to connection if you want this file to be deployed." + return + } + $parameterFile = GetParameterFile $path + $result = SmartDeployment $fullDeploymentFlag $remoteShaTable $path $parameterFile $templateObject + if ($result.isSuccess -eq $false) { + $totalFailed++ + } + if (-not $result.skip) { + $totalFiles++ + } + if ($result.isSuccess -or $result.skip) { + $global:updatedCsvTable[$path] = $remoteShaTable[$path] + if ($parameterFile) { + $global:updatedCsvTable[$parameterFile] = $remoteShaTable[$parameterFile] + } + } + } + PushCsvToRepo + if ($totalFiles -gt 0 -and $totalFailed -gt 0) + { + $err = "$totalFailed of $totalFiles deployments failed." + Throw $err + } + } + else + { + Write-Output "[Warning] $Directory not found. nothing to deploy" + } +} + +function SmartDeployment($fullDeploymentFlag, $remoteShaTable, $path, $parameterFile, $templateObject) { + try { + $skip = $false + $isSuccess = $null + if (!$fullDeploymentFlag) { + $existingSha = $global:localCsvTablefinal[$path] + $remoteSha = $remoteShaTable[$path] + $skip = (($existingSha) -and ($existingSha -eq $remoteSha)) + if ($skip -and $parameterFile) { + $existingShaForParameterFile = $global:localCsvTablefinal[$parameterFile] + $remoteShaForParameterFile = $remoteShaTable[$parameterFile] + $skip = (($existingShaForParameterFile) -and ($existingShaForParameterFile -eq $remoteShaForParameterFile)) + } + } + if (!$skip) { + $deploymentName = GenerateDeploymentName + $isSuccess = AttemptDeployment $path $parameterFile $deploymentName $templateObject + } + return @{ + skip = $skip + isSuccess = $isSuccess + } + } + catch { + Write-Host "[Error] An error occurred while trying to deploy file $path. Exception details: $_" + Write-Host $_.ScriptStackTrace + } +} + +function TryGetCsvFile { + if (Test-Path $csvPath) { + $global:localCsvTablefinal = ReadCsvToTable + Remove-Item -Path $csvPath + git add $csvPath + git commit -m "Removed tracking file and moved to new sentinel created branch" + git push origin $branchName + } + + $relativeCsvPath = RelativePathWithBackslash $csvPath + $resourceBranchExists = git ls-remote --heads "https://github.com/$githubRepository" $newResourceBranch | wc -l + + if ($resourceBranchExists -eq 1) { + git fetch > $null + git checkout $newResourceBranch + + if (Test-Path $relativeCsvPath) { + $global:localCsvTablefinal = ReadCsvToTable + } + git checkout $branchName + } +} + +function main() { + git config --global user.email "donotreply@microsoft.com" + git config --global user.name "Sentinel" + + if ($CloudEnv -ne 'AzureCloud') + { + Write-Output "Attempting Sign In to Azure Cloud" + ConnectAzCloud + } + + TryGetCsvFile + LoadDeploymentConfig + $tree = GetGithubTree + $remoteShaTable = GetCommitShaTable $tree + + $existingConfigSha = $global:localCsvTablefinal[$configPath] + $remoteConfigSha = $remoteShaTable[$configPath] + $modifiedConfig = ($existingConfigSha -xor $remoteConfigSha) -or ($existingConfigSha -and $remoteConfigSha -and ($existingConfigSha -ne $remoteConfigSha)) + + if ($remoteConfigSha) { + $global:updatedCsvTable[$configPath] = $remoteConfigSha + } + + $fullDeploymentFlag = $modifiedConfig -or ($smartDeployment -eq "false") + Deployment $fullDeploymentFlag $remoteShaTable $tree +} + +main \ No newline at end of file diff --git a/.github/workflows/sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.yml b/.github/workflows/sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.yml new file mode 100644 index 00000000000..0e8afb6c7b8 --- /dev/null +++ b/.github/workflows/sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.yml @@ -0,0 +1,81 @@ +name: Deploy Content to sentinel1 [60019580-bd93-4e54-ad5e-d462db768ce0] +# Note: This workflow will deploy everything in the root directory. +# To deploy content only from a specific path (for example SentinelContent): +# 1. Add the target path to the "paths" property like such +# paths: +# - 'SentinelContent/**' +# - '!.github/workflows/**' +# - '.github/workflows/sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.yml' +# 2. Append the path to the directory environment variable below +# directory: '${{ github.workspace }}/SentinelContent' + +on: + push: + branches: [ veritas_analytics_rules ] + paths: + - '**' + - '!.github/workflows/**' # this filter prevents other workflow changes from triggering this workflow + - '.github/workflows/sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.yml' + +jobs: + deploy-content: + runs-on: windows-latest + env: + resourceGroupName: 'quantile' + workspaceName: 'sentinel1' + workspaceId: 'b1b1e270-be02-40ca-a1f7-3aadc46662b1' + directory: '${{ github.workspace }}' + cloudEnv: 'AzureCloud' + creds: ${{ secrets.AZURE_SENTINEL_CREDENTIALS_60019580bd934e54ad5ed462db768ce0 }} + contentTypes: 'AnalyticsRule' + branch: 'veritas_analytics_rules' + sourceControlId: '60019580-bd93-4e54-ad5e-d462db768ce0' + rootDirectory: '${{ github.workspace }}' + githubAuthToken: ${{ secrets.GITHUB_TOKEN }} + smartDeployment: 'true' + + steps: + - name: Login to Azure (Attempt 1) + continue-on-error: true + id: login1 + uses: azure/login@v1 + if: ${{ env.cloudEnv == 'AzureCloud' }} + with: + creds: ${{ secrets.AZURE_SENTINEL_CREDENTIALS_60019580bd934e54ad5ed462db768ce0 }} + enable-AzPSSession: true + + - name: Wait 30 seconds if login attempt 1 failed + if: ${{ env.cloudEnv == 'AzureCloud' && steps.login1.outcome=='failure' }} + run: powershell Start-Sleep -s 30 + + - name: Login to Azure (Attempt 2) + continue-on-error: true + id: login2 + uses: azure/login@v1 + if: ${{ env.cloudEnv == 'AzureCloud' && steps.login1.outcome=='failure' }} + with: + creds: ${{ secrets.AZURE_SENTINEL_CREDENTIALS_60019580bd934e54ad5ed462db768ce0 }} + enable-AzPSSession: true + + - name: Wait 30 seconds if login attempt 2 failed + if: ${{ env.cloudEnv == 'AzureCloud' && steps.login2.outcome=='failure' }} + run: powershell Start-Sleep -s 30 + + - name: Login to Azure (Attempt 3) + continue-on-error: false + id: login3 + uses: azure/login@v1 + if: ${{ env.cloudEnv == 'AzureCloud' && steps.login2.outcome=='failure' }} + with: + creds: ${{ secrets.AZURE_SENTINEL_CREDENTIALS_60019580bd934e54ad5ed462db768ce0 }} + enable-AzPSSession: true + + - name: Checkout + uses: actions/checkout@v3 + + - name: Deploy Content to Azure Sentinel + uses: azure/powershell@v1 + with: + azPSVersion: 'latest' + inlineScript: | + ${{ github.workspace }}//.github/workflows/azure-sentinel-deploy-60019580-bd93-4e54-ad5e-d462db768ce0.ps1 \ No newline at end of file diff --git a/Solutions/Veritas NetBackup/Analytic Rules/NetBackup_many_Anomalies.yaml b/Solutions/Veritas NetBackup/Analytic Rules/NetBackup_many_Anomalies.yaml new file mode 100644 index 00000000000..22bf72245f9 --- /dev/null +++ b/Solutions/Veritas NetBackup/Analytic Rules/NetBackup_many_Anomalies.yaml @@ -0,0 +1,29 @@ +id: 2e0efcd4-56d2-41df-9098-d6898a58c62b +name: NetBackup too many anomalies generated +version: 1.0.0 +kind: Scheduled +description: Trigger Incident if we get too many anomalies in last 5 mins +severity: Medium +queryFrequency: 5m +queryPeriod: 5m +triggerOperator: gt +triggerThreshold: 0 +query: |- + NetBackupAlerts_CL + | where Category contains "ANOMALY" | summarize Total=count() + | where Total > 20 +suppressionEnabled: false +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: false + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: AllEntities + groupByEntities: [] + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +eventGroupingSettings: + aggregationKind: SingleAlert + diff --git a/Solutions/Veritas NetBackup/Analytic Rules/NetBackup_many_login_fail.yaml b/Solutions/Veritas NetBackup/Analytic Rules/NetBackup_many_login_fail.yaml new file mode 100644 index 00000000000..caeab1ab011 --- /dev/null +++ b/Solutions/Veritas NetBackup/Analytic Rules/NetBackup_many_login_fail.yaml @@ -0,0 +1,44 @@ +id: d39f0c47-2e85-49b9-a686-388c2eb7062c +name: NetBackup Too many failed login attempt +version: 1.0.0 +kind: Scheduled +description: This rule will generate an incident if for a given host there are more than 5 failed login attemts in a last 1 hour timespan +severity: Medium +queryFrequency: 5m +queryPeriod: 1h +triggerOperator: gt +triggerThreshold: 0 +tactics: +- CredentialAccess +- Discovery +relevantTechniques: +- T1110 +- T1212 +query: |- + let time_span = ago(60m); + NetBackupAlerts_CL + | where operation_s contains "LOGIN" and Message contains "authentication failed" + | extend userName = split(userName_s, "@")[0] | extend host = + split(userName_s, "@")[1] + | where TimeGenerated >= time_span + | summarize count() by tostring(host) +entityMappings: +- entityType: Host + fieldMappings: + - identifier: HostName + columnName: host +suppressionEnabled: false +incidentConfiguration: + createIncident: true + groupingConfiguration: + enabled: false + reopenClosedIncident: false + lookbackDuration: 5h + matchingMethod: AllEntities + groupByEntities: [] + groupByAlertDetails: [] + groupByCustomDetails: [] +suppressionDuration: 5h +eventGroupingSettings: + aggregationKind: SingleAlert + diff --git a/Solutions/Veritas NetBackup/Data/Solution_NetBackup.json b/Solutions/Veritas NetBackup/Data/Solution_NetBackup.json new file mode 100644 index 00000000000..bfbd6ae920a --- /dev/null +++ b/Solutions/Veritas NetBackup/Data/Solution_NetBackup.json @@ -0,0 +1,15 @@ +{ + "Name": "Veritas NetBackup", + "Author": "Microsoft - support@microsoft.com", + "Logo": "", + "Description": "The [Veritas](https://www.veritas.com/) solution for Microsoft Sentinel allows you to analyze NetBackup audit events. It includes analytics rules to automatically generate Incidents when an abnormal activity is detected.", + "Analytic Rules": [ + "Analytic Rules/NetBackup_many_Anomalies.yaml", + "Analytic Rules/NetBackup_many_login_fail.yaml" + ], + "Metadata": "SolutionMetadata.json", + "BasePath": "C:\\GitHub\\Azure-Sentinel\\Solutions\\Veritas NetBackup", + "Version": "3.0.0", + "TemplateSpec": true, + "Is1PConnector": false +} \ No newline at end of file diff --git a/Solutions/Veritas NetBackup/Package/3.0.0.zip b/Solutions/Veritas NetBackup/Package/3.0.0.zip new file mode 100644 index 00000000000..1ed0db98c18 Binary files /dev/null and b/Solutions/Veritas NetBackup/Package/3.0.0.zip differ diff --git a/Solutions/Veritas NetBackup/Package/createUiDefinition.json b/Solutions/Veritas NetBackup/Package/createUiDefinition.json new file mode 100644 index 00000000000..877e9955378 --- /dev/null +++ b/Solutions/Veritas NetBackup/Package/createUiDefinition.json @@ -0,0 +1,117 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/0.1.2-preview/CreateUIDefinition.MultiVm.json#", + "handler": "Microsoft.Azure.CreateUIDef", + "version": "0.1.2-preview", + "parameters": { + "config": { + "isWizard": false, + "basics": { + "description": "\n\n**Note:** _There may be [known issues](https://aka.ms/sentinelsolutionsknownissues) pertaining to this Solution, please refer to them before installing._\n\nThe [Veritas](https://www.veritas.com/) solution for Microsoft Sentinel allows you to analyze NetBackup audit events. It includes analytics rules to automatically generate Incidents when an abnormal activity is detected.\n\n**Analytic Rules:** 2\n\n[Learn more about Microsoft Sentinel](https://aka.ms/azuresentinel) | [Learn more about Solutions](https://aka.ms/azuresentinelsolutionsdoc)", + "subscription": { + "resourceProviders": [ + "Microsoft.OperationsManagement/solutions", + "Microsoft.OperationalInsights/workspaces/providers/alertRules", + "Microsoft.Insights/workbooks", + "Microsoft.Logic/workflows" + ] + }, + "location": { + "metadata": { + "hidden": "Hiding location, we get it from the log analytics workspace" + }, + "visible": false + }, + "resourceGroup": { + "allowExisting": true + } + } + }, + "basics": [ + { + "name": "getLAWorkspace", + "type": "Microsoft.Solutions.ArmApiControl", + "toolTip": "This filters by workspaces that exist in the Resource Group selected", + "condition": "[greater(length(resourceGroup().name),0)]", + "request": { + "method": "GET", + "path": "[concat(subscription().id,'/providers/Microsoft.OperationalInsights/workspaces?api-version=2020-08-01')]" + } + }, + { + "name": "workspace", + "type": "Microsoft.Common.DropDown", + "label": "Workspace", + "placeholder": "Select a workspace", + "toolTip": "This dropdown will list only workspace that exists in the Resource Group selected", + "constraints": { + "allowedValues": "[map(filter(basics('getLAWorkspace').value, (filter) => contains(toLower(filter.id), toLower(resourceGroup().name))), (item) => parse(concat('{\"label\":\"', item.name, '\",\"value\":\"', item.name, '\"}')))]", + "required": true + }, + "visible": true + } + ], + "steps": [ + { + "name": "analytics", + "label": "Analytics", + "subLabel": { + "preValidation": "Configure the analytics", + "postValidation": "Done" + }, + "bladeTitle": "Analytics", + "elements": [ + { + "name": "analytics-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This solution installs the following analytic rule templates. After installing the solution, create and enable analytic rules in Manage solution view." + } + }, + { + "name": "analytics-link", + "type": "Microsoft.Common.TextBlock", + "options": { + "link": { + "label": "Learn more", + "uri": "https://docs.microsoft.com/azure/sentinel/tutorial-detect-threats-custom?WT.mc_id=Portal-Microsoft_Azure_CreateUIDef" + } + } + }, + { + "name": "analytic1", + "type": "Microsoft.Common.Section", + "label": "NetBackup too many anomalies generated", + "elements": [ + { + "name": "analytic1-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "Trigger Incident if we get too many anomalies in last 5 mins" + } + } + ] + }, + { + "name": "analytic2", + "type": "Microsoft.Common.Section", + "label": "NetBackup Too many failed login attempt", + "elements": [ + { + "name": "analytic2-text", + "type": "Microsoft.Common.TextBlock", + "options": { + "text": "This rule will generate an incident if for a given host there are more than 5 failed login attemts in a last 1 hour timespan" + } + } + ] + } + ] + } + ], + "outputs": { + "workspace-location": "[first(map(filter(basics('getLAWorkspace').value, (filter) => and(contains(toLower(filter.id), toLower(resourceGroup().name)),equals(filter.name,basics('workspace')))), (item) => item.location))]", + "location": "[location()]", + "workspace": "[basics('workspace')]" + } + } +} diff --git a/Solutions/Veritas NetBackup/Package/mainTemplate.json b/Solutions/Veritas NetBackup/Package/mainTemplate.json new file mode 100644 index 00000000000..41b96ebfef6 --- /dev/null +++ b/Solutions/Veritas NetBackup/Package/mainTemplate.json @@ -0,0 +1,325 @@ +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "metadata": { + "author": "Microsoft - support@microsoft.com", + "comments": "Solution template for Veritas NetBackup" + }, + "parameters": { + "location": { + "type": "string", + "minLength": 1, + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Not used, but needed to pass arm-ttk test `Location-Should-Not-Be-Hardcoded`. We instead use the `workspace-location` which is derived from the LA workspace" + } + }, + "workspace-location": { + "type": "string", + "defaultValue": "", + "metadata": { + "description": "[concat('Region to deploy solution resources -- separate from location selection',parameters('location'))]" + } + }, + "workspace": { + "defaultValue": "", + "type": "string", + "metadata": { + "description": "Workspace name for Log Analytics where Microsoft Sentinel is setup" + } + } + }, + "variables": { + "email": "support@microsoft.com", + "_email": "[variables('email')]", + "_solutionName": "Veritas NetBackup", + "_solutionVersion": "3.0.0", + "solutionId": "azuresentinel.netbackup_sentinel", + "_solutionId": "[variables('solutionId')]", + "analyticRuleVersion1": "1.0.0", + "analyticRulecontentId1": "2e0efcd4-56d2-41df-9098-d6898a58c62b", + "_analyticRulecontentId1": "[variables('analyticRulecontentId1')]", + "TemplateEmptyArray": "[json('[]')]", + "analyticRuleId1": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', variables('analyticRulecontentId1'))]", + "analyticRuleTemplateSpecName1": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring(variables('_analyticRulecontentId1'))))]", + "_analyticRulecontentProductId1": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-',variables('_analyticRulecontentId1'),'-', variables('analyticRuleVersion1'))))]", + "analyticRuleVersion2": "1.0.0", + "analyticRulecontentId2": "d39f0c47-2e85-49b9-a686-388c2eb7062c", + "_analyticRulecontentId2": "[variables('analyticRulecontentId2')]", + "analyticRuleId2": "[resourceId('Microsoft.SecurityInsights/AlertRuleTemplates', variables('analyticRulecontentId2'))]", + "analyticRuleTemplateSpecName2": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat(parameters('workspace'),'-ar-',uniquestring(variables('_analyticRulecontentId2'))))]", + "_analyticRulecontentProductId2": "[concat(take(variables('_solutionId'),50),'-','ar','-', uniqueString(concat(variables('_solutionId'),'-','AnalyticsRule','-',variables('_analyticRulecontentId2'),'-', variables('analyticRuleVersion2'))))]", + "_solutioncontentProductId": "[concat(take(variables('_solutionId'),50),'-','sl','-', uniqueString(concat(variables('_solutionId'),'-','Solution','-',variables('_solutionId'),'-', variables('_solutionVersion'))))]" + }, + "resources": [ + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleTemplateSpecName1')]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "NetBackup_many_Anomalies_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleVersion1')]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRulecontentId1')]", + "apiVersion": "2022-04-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "Trigger Incident if we get too many anomalies in last 5 mins", + "displayName": "NetBackup too many anomalies generated", + "enabled": false, + "query": "NetBackupAlerts_CL\n| where Category contains \"ANOMALY\" | summarize Total=count() \n| where Total > 20", + "queryFrequency": "PT5M", + "queryPeriod": "PT5M", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": "[variables('TemplateEmptyArray')]", + "eventGroupingSettings": { + "aggregationKind": "SingleAlert" + }, + "incidentConfiguration": { + "groupingConfiguration": { + "matchingMethod": "AllEntities", + "enabled": false, + "reopenClosedIncident": false, + "lookbackDuration": "5h", + "groupByCustomDetails": [], + "groupByAlertDetails": [], + "groupByEntities": [] + }, + "createIncident": true + } + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleId1'),'/'))))]", + "properties": { + "description": "Veritas NetBackup Analytics Rule 1", + "parentId": "[variables('analyticRuleId1')]", + "contentId": "[variables('_analyticRulecontentId1')]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleVersion1')]", + "source": { + "kind": "Solution", + "name": "Veritas NetBackup", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "name": "Microsoft Corporation", + "email": "support@microsoft.com", + "tier": "Microsoft", + "link": "https://support.microsoft.com" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('_analyticRulecontentId1')]", + "contentKind": "AnalyticsRule", + "displayName": "NetBackup too many anomalies generated", + "contentProductId": "[variables('_analyticRulecontentProductId1')]", + "id": "[variables('_analyticRulecontentProductId1')]", + "version": "[variables('analyticRuleVersion1')]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentTemplates", + "apiVersion": "2023-04-01-preview", + "name": "[variables('analyticRuleTemplateSpecName2')]", + "location": "[parameters('workspace-location')]", + "dependsOn": [ + "[extensionResourceId(resourceId('Microsoft.OperationalInsights/workspaces', parameters('workspace')), 'Microsoft.SecurityInsights/contentPackages', variables('_solutionId'))]" + ], + "properties": { + "description": "NetBackup_many_login_fail_AnalyticalRules Analytics Rule with template version 3.0.0", + "mainTemplate": { + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "[variables('analyticRuleVersion2')]", + "parameters": {}, + "variables": {}, + "resources": [ + { + "type": "Microsoft.SecurityInsights/AlertRuleTemplates", + "name": "[variables('analyticRulecontentId2')]", + "apiVersion": "2022-04-01-preview", + "kind": "Scheduled", + "location": "[parameters('workspace-location')]", + "properties": { + "description": "This rule will generate an incident if for a given host there are more than 5 failed login attemts in a last 1 hour timespan", + "displayName": "NetBackup Too many failed login attempt", + "enabled": false, + "query": "let time_span = ago(60m);\nNetBackupAlerts_CL\n| where operation_s contains \"LOGIN\" and Message contains \"authentication failed\" \n| extend userName = split(userName_s, \"@\")[0] | extend host = \nsplit(userName_s, \"@\")[1] \n| where TimeGenerated >= time_span\n| summarize count() by tostring(host)", + "queryFrequency": "PT5M", + "queryPeriod": "PT1H", + "severity": "Medium", + "suppressionDuration": "PT1H", + "suppressionEnabled": false, + "triggerOperator": "GreaterThan", + "triggerThreshold": 0, + "status": "Available", + "requiredDataConnectors": "[variables('TemplateEmptyArray')]", + "tactics": [ + "CredentialAccess", + "Discovery" + ], + "techniques": [ + "T1110", + "T1212" + ], + "entityMappings": [ + { + "fieldMappings": [ + { + "identifier": "HostName", + "columnName": "host" + } + ], + "entityType": "Host" + } + ], + "eventGroupingSettings": { + "aggregationKind": "SingleAlert" + }, + "incidentConfiguration": { + "groupingConfiguration": { + "matchingMethod": "AllEntities", + "enabled": false, + "reopenClosedIncident": false, + "lookbackDuration": "5h", + "groupByCustomDetails": [], + "groupByAlertDetails": [], + "groupByEntities": [] + }, + "createIncident": true + } + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/metadata", + "apiVersion": "2022-01-01-preview", + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',concat('AnalyticsRule-', last(split(variables('analyticRuleId2'),'/'))))]", + "properties": { + "description": "Veritas NetBackup Analytics Rule 2", + "parentId": "[variables('analyticRuleId2')]", + "contentId": "[variables('_analyticRulecontentId2')]", + "kind": "AnalyticsRule", + "version": "[variables('analyticRuleVersion2')]", + "source": { + "kind": "Solution", + "name": "Veritas NetBackup", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "name": "Microsoft Corporation", + "email": "support@microsoft.com", + "tier": "Microsoft", + "link": "https://support.microsoft.com" + } + } + } + ] + }, + "packageKind": "Solution", + "packageVersion": "[variables('_solutionVersion')]", + "packageName": "[variables('_solutionName')]", + "packageId": "[variables('_solutionId')]", + "contentSchemaVersion": "3.0.0", + "contentId": "[variables('_analyticRulecontentId2')]", + "contentKind": "AnalyticsRule", + "displayName": "NetBackup Too many failed login attempt", + "contentProductId": "[variables('_analyticRulecontentProductId2')]", + "id": "[variables('_analyticRulecontentProductId2')]", + "version": "[variables('analyticRuleVersion2')]" + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces/providers/contentPackages", + "apiVersion": "2023-04-01-preview", + "location": "[parameters('workspace-location')]", + "properties": { + "version": "3.0.0", + "kind": "Solution", + "contentSchemaVersion": "3.0.0", + "displayName": "Veritas NetBackup", + "publisherDisplayName": "Microsoft Sentinel, Microsoft Corporation", + "descriptionHtml": "
Note: There may be known issues pertaining to this Solution, please refer to them before installing.
\nThe Veritas solution for Microsoft Sentinel allows you to analyze NetBackup audit events. It includes analytics rules to automatically generate Incidents when an abnormal activity is detected.
\nAnalytic Rules: 2
\nLearn more about Microsoft Sentinel | Learn more about Solutions
\n", + "contentKind": "Solution", + "contentProductId": "[variables('_solutioncontentProductId')]", + "id": "[variables('_solutioncontentProductId')]", + "icon": "", + "contentId": "[variables('_solutionId')]", + "parentId": "[variables('_solutionId')]", + "source": { + "kind": "Solution", + "name": "Veritas NetBackup", + "sourceId": "[variables('_solutionId')]" + }, + "author": { + "name": "Microsoft", + "email": "[variables('_email')]" + }, + "support": { + "name": "Microsoft Corporation", + "email": "support@microsoft.com", + "tier": "Microsoft", + "link": "https://support.microsoft.com" + }, + "dependencies": { + "operator": "AND", + "criteria": [ + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRulecontentId1')]", + "version": "[variables('analyticRuleVersion1')]" + }, + { + "kind": "AnalyticsRule", + "contentId": "[variables('analyticRulecontentId2')]", + "version": "[variables('analyticRuleVersion2')]" + } + ] + }, + "firstPublishDate": "2023-09-25", + "providers": [ + "Microsoft" + ], + "categories": { + "domains": [ + "Security - Others" + ] + } + }, + "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/', variables('_solutionId'))]" + } + ], + "outputs": {} +} diff --git a/Solutions/Veritas NetBackup/README.md b/Solutions/Veritas NetBackup/README.md new file mode 100644 index 00000000000..d06e2c663ba --- /dev/null +++ b/Solutions/Veritas NetBackup/README.md @@ -0,0 +1,121 @@ +**Veritas NetBackup and Microsoft Sentinel Integration Guide** + +The integration between Veritas NetBackup and Microsoft Sentinel empowers security operations teams by providing valuable insights from Veritas NetBackup Anomaly Detection and Malware Scanning engines directly into Microsoft Sentinel. +These insights provide the following advantages to Security and IT ops: + + +- Identification of early indicators of compromise such as malware or data anomaly detection including spikes in new uncompressible data or change in deduplication ratio, files modifications during backups, etc. + +- Enhances capabilities for security operators to prioritize and expedite the investigation of potential security incidents with the help of insights from threats published by Veritas NetBackup. + +- Enables NetBackup users to ingest alerts and other data into their Sentinel instance. With Analytic Rules, Sentinel can automatically create Sentinel incidents from incoming events. + + +Veritas NetBackup has developed a first-class, in-product integration with Microsoft Sentinel. Security insights will be pushed via NetBackup APIs directly into the Microsoft Sentinel workspace, eliminating any dependency on playbooks or the need to develop data connectors separately. The threat hunting queries with enrich anomaly events from NetBackup helps during Ransomware analysis and helps incident prioritization when security administrators deal with several thousand security events. + + +**Prerequisites** + +Veritas NetBackup should be configured to send appropriate events to Microsoft Sentinel and must be running version 10.2 or higher. + +Microsoft Sentinel and NetBackup should be configured to connect to API end points using an account with the relevant privileges necessary to perform the desired operations. + +A workspace key and ID are required for NetBackup to connect to Sentinel. These are generated in Sentinel via its SIEM WebUI/API interface and stored and used by the NetBackup primary server. + +**How NetBackup Sends Events to Microsoft Sentinel** + +Veritas NetBackup sends events to SIEM platforms using Microsoft Sentinel as an example. A workspace key and ID are required for NetBackup to connect to Sentinel. These are generated in Microsoft Sentinel via its SIEM WebUI/API interface and stored and used by the NetBackup primary server. Once NetBackup connect to Microsoft Sentinel, NetBackup audits its own logs for the type(s) of alerts you ve configured for forwarding to Microsoft Sentinel. The selected alerts are then sent to Microsoft Sentinel as audit alert broadcast messages. Ref Figure 1 + + +**Connecting Veritas NetBackup to Microsoft Sentinel** + +The Microsoft Sentinel workspace to receive audit alerts from NetBackup must already exist before it can be selected as an audit alert target. The example workspace in the content pack figures (Ref:Log_Analysis_Agent_Config) is sentinel1. Search the Microsoft Sentinel documentation for accessing the Log Analytics Agents Instructions feature to display workspace IDs and keys as shown. Existing IDs and keys can be copied, or new ones generated as needed. + + +Once you have a copy of the workspace ID and key, you can configure the workspace in NetBackup as an audit event target. Login to NetBackup WebUI. SIEM targets can only be configured in the WebUI. Go to *Security -> Security events -> Audit event settings* + + +Click the Send audit events to log forwarding endpoints checkbox, then click on Select Endpoints tab + +Click the Microsoft Sentinel checkbox, then the Add a new credential button. Enter the workspace ID and key and save the changes. The new credential for Microsoft Sentinel. Click on the dot menu and select Edit. + +Enter a tag (optional), description, workspace ID, and workspace primary key, as shown below. Click Next. The credentials are updated. Click Save. + +NetBackup can now send audit events to Microsoft Sentinel as shown below. + + +Now that you have a target workspace to receive audit alerts, click Edit to select the audit event categories you want to forward to selected endpoints. By default, all categories are selected. Click the checkboxes of the audit categories to select/unselect them shown and click save. + + + +NetBackup is now configured to send the audit events you selected to the Microsoft Sentinel workspace you specified. Only new audit alerts will be forwarded when they are generated. Click Close. + +**For example - Anomaly Audit Event -** Contains the sentinel function which receives anomaly event data via Veritas NetBackup Web APIs and ingests into Microsoft Sentinel. + + +**How to use NetBackup alerts in Microsoft Sentinel** + +**Query Data** + +- Users can query NetBackup data from sentinel using KQL queries in Sentinel. +- Below simple KQL will return all NetBackup alerts. + +**Analytics Rule Example 1:** + +**Anomaly Detection and malware detection Alerts** + +This query provides insights into anomaly detection and malware scanning alerts ingested by NetBackup into sentinel. + +union NetBackupAlerts\_CL | where Category contains "ANOMOLY\_EXTENSION" + +union NetBackupAlerts\_CL | where Category contains "MALWARE\_IMPACTED" + + + +**Analytics Rule Example 2:** + +**User login Alerts** + +This query creates an automated rule to check for 10 or more login failures, indicating a potential security breach or unauthorized access to the systems. + +*Query:* + +*NetBackupAlerts\_CL +| where operations contains "LOGIN" and Message contains "authentication failed" +| summarize total=count() | where total > 10* + + +Once the data is available in Sentinel, Analytics rules can be set to run different use cases. + +Using KQL language, we can write the scripts which fetches the desired data as per the use case. For example, there is need to create automated rule to check if there are 10 or more login failures in NetBackup, then this can be done using Analytics rules. + +In Microsoft Sentinel, under Analytics Tab- click on Create Scheduled rule. + +Setup basic rule details as below + + +Query: + +let time\_span = ago(60m); +NetBackupAlerts\_CL +| where operation\_s contains "LOGIN" and Message contains "authentication failed" +| extend userName = split(userName\_s, "@")[0] | extend host = +split(userName\_s, "@")[1] +| where TimeGenerated >= time\_span +| summarize count() by tostring(host) + + + +**Create Playbook for actions** + +From Microsoft Sentinel | Automation, click on Create | Playbook with incident trigger. + +Once playbook is created, it will open logic app designer. Click on New Step and select HTTP. + + +- Provide API details and Save. + + + + + diff --git a/Solutions/Veritas NetBackup/SolutionMetadata.json b/Solutions/Veritas NetBackup/SolutionMetadata.json new file mode 100644 index 00000000000..5160386bb5e --- /dev/null +++ b/Solutions/Veritas NetBackup/SolutionMetadata.json @@ -0,0 +1,16 @@ +{ + "publisherId": "azuresentinel", + "offerId": "netbackup_sentinel", + "firstPublishDate": "2023-09-25", + "providers": ["Microsoft"], + "categories": { + "domains" : ["Security - Others"], + "verticals": [] + }, + "support": { + "name": "Microsoft Corporation", + "email": "support@microsoft.com", + "tier": "Microsoft", + "link": "https://support.microsoft.com" + } +} \ No newline at end of file