Skip to content

Commit 7aab5d8

Browse files
authored
Merge pull request #8884 from SecureHats/azurekid/asim/tools/ConvertFrom-ASim
Azurekid - ConvertFrom-Asim
2 parents 1cb4702 + 3dc8d03 commit 7aab5d8

File tree

6 files changed

+451
-1
lines changed

6 files changed

+451
-1
lines changed
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
[CmdletBinding()]
2+
param (
3+
[Parameter(Mandatory = $true)]
4+
[alias("templates", "t")]
5+
[string]$FilesPath,
6+
7+
[Parameter(Mandatory = $false)]
8+
[alias("dest", "d")]
9+
[string]$OutputFolder,
10+
11+
[Parameter(Mandatory = $false)]
12+
[alias("--object", "-o")]
13+
[switch]$ReturnObject
14+
)
15+
16+
#Region Install Modules
17+
$modulesToInstall = @(
18+
'powershell-yaml'
19+
)
20+
21+
$modulesToInstall | ForEach-Object {
22+
if (-not (Get-Module -ListAvailable -All $_)) {
23+
Write-Output "Module [$_] not found, INSTALLING..."
24+
Install-Module $_ -Force
25+
Import-Module $_ -Force
26+
}
27+
}
28+
#EndRegion Install Modules
29+
30+
#Region HelperFunctions
31+
function ConvertTo-ARM {
32+
param (
33+
[Parameter(Mandatory = $true)]
34+
[object]$Value,
35+
36+
[Parameter(Mandatory = $true)]
37+
[object]$Metadata,
38+
39+
[Parameter(Mandatory = $false)]
40+
[string]$OutputPath,
41+
42+
[Parameter(Mandatory = $false)]
43+
[bool]$ReturnObject
44+
45+
)
46+
47+
Write-Debug 'Creating ARM Template'
48+
$template = [PSCustomObject]@{
49+
'$schema' = "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#"
50+
contentVersion = "1.0.0.0"
51+
metadata = $metadata
52+
parameters = @{
53+
workspace = [PSCustomObject]@{
54+
type = "string"
55+
metadata = @{
56+
description = "The Microsoft Sentinel workspace into which the function will be deployed. Has to be in the selected Resource Group."
57+
}
58+
}
59+
}
60+
resources = @(
61+
[PSCustomObject]@{
62+
type = "Microsoft.OperationalInsights/workspaces"
63+
apiVersion = "2022-10-01"
64+
name = "[parameters('Workspace')]"
65+
location = "[resourcegroup().location]"
66+
resources = @([PSCustomObject]@{
67+
type = "savedSearches"
68+
apiVersion = "2020-08-01"
69+
name = $($value.properties.FunctionAlias)
70+
dependsOn = @("[resourceId('Microsoft.OperationalInsights/workspaces', parameters('Workspace'))]")
71+
properties = $value.properties
72+
}
73+
)
74+
}
75+
)
76+
}
77+
78+
if ($returnObject) {
79+
return $template
80+
}
81+
else {
82+
$template | ConvertTo-Json -Depth 20 | Out-File $OutputPath -ErrorAction Stop
83+
}
84+
}
85+
#EndRegion HelperFunctions
86+
87+
#Region Fetching Yaml Files
88+
try {
89+
$yamlFiles = Get-ChildItem -Path $FilesPath -Include "*.yaml", "*.yml" -Recurse
90+
Write-Debug "Found $($yamlFiles.Count) yaml files"
91+
}
92+
catch {
93+
Write-Error $_.Exception.Message
94+
break
95+
}
96+
#EndRegion Fetching Yaml Files
97+
98+
#Region Processing Yaml Files
99+
try {
100+
if ($null -ne $yamlFiles) {
101+
foreach ($item in $yamlFiles) {
102+
try {
103+
$yamlObject = Get-Content $item.FullName | ConvertFrom-Yaml
104+
Write-Debug "Processing $($item)"
105+
106+
# Creating parameters from the parameters in the yaml file
107+
$null = $yamlObject.ParserParams | ForEach-Object {
108+
($stringParams = "$($stringParams), $($_.Name):$($_.Type)=$($_.Default)")
109+
}
110+
111+
$properties = [pscustomobject]@{
112+
"properties" = @{
113+
etag = "*"
114+
displayName = $yamlObject.Parser.Title
115+
category = 'ASIM'
116+
FunctionAlias = $yamlObject.ParserName
117+
query = $yamlObject.ParserQuery
118+
version = 1.0
119+
functionParameters = $stringParams.replace("string=*", "string='*'").trim(', ')
120+
}
121+
}
122+
123+
#Clearing the variable for the next iteration
124+
$stringParams = ''
125+
126+
$metadata = [PSCustomObject]@{
127+
"title" = $yamlObject.Parser.Title
128+
"version" = [single]$yamlObject.Parser.Version
129+
"lastUpdated" = $yamlObject.Parser.lastUpdated
130+
"description" = $yamlObject.Description
131+
}
132+
}
133+
catch {
134+
Write-Error $_.Exception.Message
135+
break
136+
}
137+
138+
if ($OutputFolder) {
139+
$outputPath = $OutputFolder
140+
}
141+
else {
142+
$outputPath = $item.DirectoryName
143+
}
144+
145+
$arguments = @{
146+
Value = $properties
147+
Metadata = $metadata
148+
OutputPath = ('{0}/{1}.json' -f ($($outputPath), $($item.BaseName)))
149+
ReturnObject = $ReturnObject
150+
}
151+
152+
ConvertTo-ARM @arguments
153+
}
154+
}
155+
}
156+
catch {
157+
Write-Error $_.Exception.Message
158+
break
159+
}
160+
#EndRegion Processing Yaml Files
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
# ConvertFrom-ASim
2+
3+
## Description
4+
5+
This PowerShell script can be used to convert an ASIM function in YAML format in to a deployable ARM template.</br>
6+
7+
## Usage
8+
9+
### Example 1
10+
11+
This example will convert the `vimAuditEventAzureAdminActivity` to an ARM template and output it in the same folder
12+
13+
```powershell
14+
ConvertFrom-ASim.ps1 -FilesPath 'C:\Users\RogierDijkman\Parsers\vimAuditEventAzureAdminActivity.yaml'
15+
```
16+
17+
### Example 2 - using alias
18+
```powershell
19+
ConvertFrom-ASim.ps1 -templates 'C:\Users\RogierDijkman\Parsers\vimAuditEventAzureAdminActivity.yaml'
20+
```
21+
22+
### Example 3
23+
24+
This example will convert the `vimAuditEventAzureAdminActivity` to an ARM template and output it the folder C:\output
25+
26+
```powershell
27+
ConvertFrom-ASim.ps1 -FilesPath 'C:\Users\RogierDijkman\Parsers\vimAuditEventAzureAdminActivity.yaml' -OutputFolder 'C:\output'
28+
```
29+
### Example 4 - using alias
30+
31+
```powershell
32+
ConvertFrom-ASim.ps1 -templates 'C:\Users\RogierDijkman\Parsers\vimAuditEventAzureAdminActivity.yaml' -dest 'C:\output'
33+
```
34+
35+
### Example 5
36+
37+
This example will convert the `vimAuditEventAzureAdminActivity` to and return the template as an object.
38+
> Returning the ARM template as an object can be useful in situations where the YAML files are converted and deployed an automation pipeline
39+
40+
```powershell
41+
ConvertFrom-ASim.ps1 -FilesPath 'C:\Users\RogierDijkman\Parsers\vimAuditEventAzureAdminActivity.yaml' -ReturnObject
42+
```
43+
44+
![returnObject](image.png)
27.6 KB
Loading
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
{
2+
"$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#",
3+
"contentVersion": "1.0.0.0",
4+
"metadata": {
5+
"title": "Audit Event ASIM filtering parser for Microsoft Exchange 365 administrative activity",
6+
"version": "0.2",
7+
"lastUpdated": "Feb 19 2023",
8+
"description": "This ASIM parser supports filtering and normalizing Microsoft Exchange 365 administrative activity in the OfficeActivity table to the ASIM Audit Event schema.\n"
9+
},
10+
"parameters": {
11+
"workspace": {
12+
"type": "string",
13+
"metadata": {
14+
"description": "The Microsoft Sentinel workspace into which the function will be deployed. Has to be in the selected Resource Group."
15+
}
16+
}
17+
},
18+
"resources": [
19+
{
20+
"type": "Microsoft.OperationalInsights/workspaces",
21+
"apiVersion": "2022-10-01",
22+
"name": "[parameters('Workspace')]",
23+
"location": "[resourcegroup().location]",
24+
"resources": [
25+
{
26+
"type": "savedSearches",
27+
"apiVersion": "2020-08-01",
28+
"name": "vimAuditEventMicrosoftExchangeAdmin365",
29+
"dependsOn": [
30+
"[resourceId('Microsoft.OperationalInsights/workspaces', parameters('Workspace'))]"
31+
],
32+
"properties": {
33+
"version": "0.2",
34+
"category": "ASIM",
35+
"FunctionAlias": "vimAuditEventMicrosoftExchangeAdmin365",
36+
"query": "let usertypes=datatable (ActorOriginalUserType:string, ActorUserType:string)\n[\n // Regular, Regular\n \"Admin\", \"Admin\"\n , \"DcAdmin\", \"Admin\"\n , \"System\", \"System\"\n , \"Application\", \"Application\"\n , \"ServicePrincipal\", \"Service Principal\"\n , \"CustomPolicy\", \"Other\"\n , \"SystemPolicy\", \"Other\"\n , \"Reserved\", \"Other\"\n];\nlet eventtypes=datatable (op:string, EventType:string)\n[\n \"Remove\", \"Delete\",\n \"New\", \"Create\",\n \"Add\", \"Create\",\n \"Enable\", \"Enable\",\n \"Install\", \"Install\",\n \"Set\", \"Set\",\n \"Disable\", \"Disable\",\n \"disable\", \"Disable\"\n];\n let parser= (\n starttime:datetime=datetime(null), \n endtime:datetime=datetime(null),\n srcipaddr_has_any_prefix:dynamic=dynamic([]), \n eventresult:string='*',\n actorusername_has_any:dynamic=dynamic([]),\n eventtype_in:dynamic=dynamic([]),\n operation_has_any:dynamic=dynamic([]),\n object_has_any:dynamic=dynamic([]),\n newvalue_has_any:dynamic=dynamic([]),\n disabled:bool = false\n ){\n OfficeActivity\n | where not(disabled)\n | where\n (isnull(starttime) or TimeGenerated >= starttime) \n and (isnull(endtime) or TimeGenerated <= endtime)\n | where RecordType in ('ExchangeAdmin')\n | where \n (array_length(srcipaddr_has_any_prefix) == 0 or has_any_ipv4_prefix(ClientIP,srcipaddr_has_any_prefix))\n and (array_length(actorusername_has_any) == 0 or UserId has_any (actorusername_has_any))\n and (array_length(operation_has_any) == 0 or Operation has_any (operation_has_any))\n and (array_length(object_has_any) == 0 or OfficeObjectId has_any (object_has_any))\n and (array_length(newvalue_has_any) == 0 or Parameters has_any (newvalue_has_any))\n | project Operation, ResultStatus, Parameters, OrganizationName, OrganizationId, OfficeObjectId, ClientIP, UserId, UserKey, UserAgent, UserType, TimeGenerated, OriginatingServer, SourceRecordId, Type, _ResourceId\n // --\n // Calculate and filter result\n | where (eventresult == \"*\" or (eventresult == \"Success\" and ResultStatus == \"True\"))\n | extend EventResult = iff(ResultStatus == \"True\", \"Success\", \"Failure\")\n // --\n // -- Calculate and filter operation and event type\n | extend \n SplitOp = split (Operation,\"-\")\n | extend\n op=tostring(SplitOp[0])\n | lookup eventtypes on op\n | where array_length(eventtype_in) == 0 or EventType in (eventtype_in)\n | project-away op \n // --\n // Calculate and post-filter source IP address and port\n | extend \n SplitIpAddr = extract_all(@'^\\[?(.*?)\\]?:(\\d+)$', ClientIP)[0]\n | extend \n SrcIpAddr = iff (SplitIpAddr[1] == \"\", ClientIP, SplitIpAddr[0]),\n SrcPortNumber = toint(iff (SplitIpAddr[1] == \"\", \"\", SplitIpAddr[1]))\n | where (array_length(srcipaddr_has_any_prefix) == 0 or has_any_ipv4_prefix(SrcIpAddr,srcipaddr_has_any_prefix))\n // --\n /// Calculate and post filter actor and acting app\n | parse UserId with ActorUsername \" (\" ActingAppName \")\"\n | extend \n ActorUsernameType = iff (ActorUsername == \"\", \"UPN\", \"Windows\"),\n ActorUsername = iff (ActorUsername == \"\", UserId, ActorUsername),\n ActingAppType = iff (ActingAppName == \"\", \"\", \"Process\")\n | where (array_length(actorusername_has_any) == 0 or ActorUsername has_any (actorusername_has_any))\n // --\n // Calculate Object\n | extend\n SplitObject = extract_all(@'^(.*?)[\\\\/](.*)$', OfficeObjectId)[0]\n | extend \n Object = case (\n SplitObject[0] == OrganizationName, SplitObject[1], \n OfficeObjectId == \"\", SplitOp[1],\n OfficeObjectId\n )\n | project-away SplitOp, OfficeObjectId\n // --\n | project-rename\n SrcDescription = OriginatingServer,\n NewValue = Parameters \n | project-away SplitObject, UserKey, SplitIpAddr, ClientIP, UserId\n | project-rename\n HttpUserAgent = UserAgent, \n ActorOriginalUserType = UserType,\n ActorScopeId = OrganizationId,\n ActorScope = OrganizationName,\n EventOriginalUid = SourceRecordId\n | lookup usertypes on ActorOriginalUserType\n | extend\n EventCount = int(1),\n EventStartTime = TimeGenerated, \n EventEndTime= TimeGenerated,\n EventProduct = 'Exchange 365',\n EventVendor = 'Microsoft',\n EventSchemaVersion = '0.1.0',\n EventSchema = 'AuditEvent',\n TargetAppName = 'Exchange 365',\n TargetAppType = 'SaaS application'\n | project-away \n ResultStatus\n | extend\n EventSeverity = iff(EventResult == \"Failure\", \"Low\", \"Informational\")\n // -- Aliases\n | extend \n User=ActorUsername,\n IpAddr = SrcIpAddr,\n Value = NewValue,\n Application = TargetAppName,\n Dst = TargetAppName,\n Src = coalesce (SrcIpAddr, SrcDescription),\n Dvc = TargetAppName,\n // -- Entity identifier explicit aliases\n ActorUserUpn = iif (ActorUsernameType == \"UPN\", ActorUsername, \"\"),\n ActorWindowsUsername = iif (ActorUsernameType == \"Windows\", ActorUsername, \"\")\n };\n parser (\n starttime = starttime,\n endtime = endtime,\n srcipaddr_has_any_prefix = srcipaddr_has_any_prefix,\n actorusername_has_any = actorusername_has_any,\n eventtype_in = eventtype_in,\n eventresult = eventresult,\n operation_has_any = operation_has_any,\n object_has_any=object_has_any,\n newvalue_has_any=newvalue_has_any,\n disabled=disabled\n )\n",
37+
"displayName": "Audit Event ASIM filtering parser for Microsoft Exchange 365 administrative activity",
38+
"functionParameters": "starttime:datetime=datetime(null), endtime:datetime=datetime(null), srcipaddr_has_any_prefix:dynamic=dynamic([]), actorusername_has_any:dynamic=dynamic([]), operation_has_any:dynamic=dynamic([]), eventtype_in:dynamic=dynamic([]), eventresult:string=*, object_has_any:dynamic=dynamic([]), newvalue_has_any:dynamic=dynamic([]), disabled:bool=False",
39+
"etag": "*"
40+
}
41+
}
42+
]
43+
}
44+
]
45+
}

0 commit comments

Comments
 (0)