├── Azure Activity ├── Azure RBAC Role Assignments with IdentityInfo and Roles.kql ├── Azure RBAC Role Assignments with IdentityInfo.kql ├── Azure RBAC Role Assignments.kql ├── azuredeploy-update-AzureRBACRolesWatchlist.json └── readme.md ├── KQL ├── GitHubAuditLogPolling.kql ├── Total Cost Estimates by Subscription with table breakdown.txt └── mfa changes by watchlist ├── agent-management ├── readme.md └── update-extension.ps1 ├── ama-management ├── readme.md └── update-ama.ps1 ├── analytics_rules ├── create-scheduledRuleFromTemplate.ps1 ├── export-analyticRuleTemplates.ps1 └── readme.md ├── data_collection_rules ├── logstash-syslog-dcr.json ├── readme.md ├── update-dcrDataCollectionEndpoint.ps1 ├── update-dcrStreamColumns.ps1 ├── update-dcrTimestampFormat.ps1 ├── update-dcrTransform.ps1 ├── update-dcrdatastream.ps1 └── update-dcrworkspace.ps1 ├── dataconnectors ├── GitHubAuditLogs │ ├── CCP │ │ ├── GitHubAuditLogs_CCP.json │ │ └── readme.md │ └── eventhub │ │ ├── eventhub.json │ │ └── readme.md ├── cross-tenant-logging │ └── azure-activity-logs │ │ ├── azuredeploy.json │ │ └── readme.md └── okta │ ├── AzureFunctionOktaSSO.zip │ ├── okta_custom_table.json │ └── oktaccp.json ├── enable-sentinel-mdfc-sub-con ├── azuredeploy.json ├── custom-role.json ├── customRoleDeploy.json ├── defenderForCloudConnectorCoverage.json ├── logic-app.svg ├── logicapp.puml ├── readme.md └── workbook.png ├── images ├── dcr-json.png ├── dcr-verify.png ├── dcr-view.png ├── dcr.gif └── github_pat.png ├── logstash-cef-adx ├── create-adx-table.txt ├── logstash.conf └── readme.md ├── logstash ├── logstash-syslog-dcr.json └── readme.md ├── playbooks ├── get-incident-chatgpt4 │ ├── azuredeploy.json │ └── readme.md └── get-incidentdetails │ ├── azuredeploy.json │ └── readme.md ├── readme.md ├── table_tools └── copy-logAnalyticsTable.ps1 ├── temp.json └── workbooks ├── Archiving, Basic Logs, and Retention.json └── Security Agent Workbook.json /Azure Activity/Azure RBAC Role Assignments with IdentityInfo and Roles.kql: -------------------------------------------------------------------------------- 1 | AzureActivity 2 | | where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' 3 | | where ActivityStatusValue =~ 'Success' and ActivitySubstatusValue =~ 'Created' 4 | | project TimeGenerated, Caller, CallerIpAddress, SubscriptionId, ResourceGroup, CorrelationId, ActivityStatusValue, ActivitySubstatusValue 5 | // Join AzureActivity start events to get role assignment details, these are not included in the Success event 6 | | join kind=leftouter ( 7 | AzureActivity 8 | | where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' 9 | | where ActivityStatusValue =~ 'Start' 10 | | project-away Caller, CallerIpAddress, SubscriptionId, ResourceGroup 11 | ) 12 | on CorrelationId 13 | | project-away CorrelationId1, ActivityStatusValue1 14 | | extend AssignedPrincipalId = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).PrincipalId) 15 | | extend AssignedPrincipalType = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).PrincipalType) 16 | | extend RoleDefinitionId = tostring(split(tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).RoleDefinitionId), '/')[-1]) 17 | | extend Scope = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).Scope) 18 | // Join the IdentityInfo to get the assigned identity UPN 19 | | join kind=inner ( 20 | IdentityInfo 21 | | where TimeGenerated >= ago(30d) 22 | | project AssignedPrincipalTenantId = TenantId, AssignedPrincipalUPN = AccountUPN, AssignedPrincipalId = AccountObjectId 23 | ) on AssignedPrincipalId 24 | | project-away AssignedPrincipalId1 25 | | join kind=inner ( 26 | _GetWatchlist('AzureRoles') 27 | | extend RoleDefinitionId = tostring(RoleDefinitionId) 28 | | project RoleDefinitionId, RoleName, RoleType, RoleDescription 29 | ) on RoleDefinitionId 30 | | project TimeGenerated, Actor = Caller, CallerIpAddress, CorrelationId, SubscriptionId, ResourceGroup, AssignedPrincipalUPN, AssignedPrincipalId, AssignedPrincipalType, AssignedPrincipalTenantId, RoleName, RoleType, RoleDefinitionId, RoleDescription, Scope, Src = SourceSystem, OperationNameValue, ActivityStatusValue, ActivitySubstatusValue 31 | -------------------------------------------------------------------------------- /Azure Activity/Azure RBAC Role Assignments with IdentityInfo.kql: -------------------------------------------------------------------------------- 1 | AzureActivity 2 | | where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' 3 | | where ActivityStatusValue =~ 'Success' and ActivitySubstatusValue =~ 'Created' 4 | | project TimeGenerated, Caller, CallerIpAddress, SubscriptionId, ResourceGroup, CorrelationId, ActivityStatusValue, ActivitySubstatusValue 5 | // Join AzureActivity start events to get role assignment details, these are not included in the Success event 6 | | join kind=leftouter ( 7 | AzureActivity 8 | | where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' 9 | | where ActivityStatusValue =~ 'Start' 10 | | project-away Caller, CallerIpAddress, SubscriptionId, ResourceGroup 11 | ) 12 | on CorrelationId 13 | | project-away CorrelationId1, ActivityStatusValue1 14 | | extend AssignedPrincipalId = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).PrincipalId) 15 | | extend AssignedPrincipalType = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).PrincipalType) 16 | | extend RoleDefinitionId = split(tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).RoleDefinitionId), '/')[-1] 17 | | extend Scope = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).Scope) 18 | // Join the IdentityInfo to get the assigned identity UPN 19 | | join kind=inner ( 20 | IdentityInfo 21 | | where TimeGenerated >= ago(30d) 22 | | project AssignedPrincipalTenantId = TenantId, AssignedPrincipalUPN = AccountUPN, AssignedPrincipalId = AccountObjectId 23 | ) on AssignedPrincipalId 24 | | project-away AssignedPrincipalId1 25 | | project TimeGenerated, Actor = Caller, CallerIpAddress, CorrelationId, SubscriptionId, ResourceGroup, AssignedPrincipalUPN, AssignedPrincipalId, AssignedPrincipalType, AssignedPrincipalTenantId, RoleDefinitionId, Scope, Src = SourceSystem, OperationNameValue, ActivityStatusValue, ActivitySubstatusValue 26 | -------------------------------------------------------------------------------- /Azure Activity/Azure RBAC Role Assignments.kql: -------------------------------------------------------------------------------- 1 | AzureActivity 2 | | where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' 3 | | where ActivityStatusValue =~ 'Success' and ActivitySubstatusValue =~ 'Created' 4 | | project TimeGenerated, Caller, CallerIpAddress, SubscriptionId, ResourceGroup, CorrelationId, ActivityStatusValue, ActivitySubstatusValue 5 | // Join AzureActivity start events to get role assignment details, these are not included in the Success event 6 | | join kind=leftouter ( 7 | AzureActivity 8 | | where OperationNameValue =~ 'MICROSOFT.AUTHORIZATION/ROLEASSIGNMENTS/WRITE' 9 | | where ActivityStatusValue =~ 'Start' 10 | | project-away Caller, CallerIpAddress, SubscriptionId, ResourceGroup 11 | ) 12 | on CorrelationId 13 | | project-away CorrelationId1, ActivityStatusValue1 14 | | extend AssignedPrincipalId = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).PrincipalId) 15 | | extend AssignedPrincipalType = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).PrincipalType) 16 | | extend RoleDefinitionId = split(tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).RoleDefinitionId), '/')[-1] 17 | | extend Scope = tostring(parse_json(tostring(parse_json(tostring(parse_json(Properties).requestbody)).Properties)).Scope) 18 | | project TimeGenerated, Actor = Caller, CallerIpAddress, CorrelationId, SubscriptionId, ResourceGroup, AssignedPrincipalId, AssignedPrincipalType, RoleDefinitionId, Scope, Src = SourceSystem, OperationNameValue, ActivityStatusValue, ActivitySubstatusValue 19 | -------------------------------------------------------------------------------- /Azure Activity/azuredeploy-update-AzureRBACRolesWatchlist.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "title": "", 6 | "description": "", 7 | "prerequisites": "", 8 | "postDeployment": [], 9 | "prerequisitesDeployTemplateFile": "", 10 | "lastUpdateTime": "", 11 | "entities": [], 12 | "tags": [], 13 | "support": { 14 | "tier": "community", 15 | "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" 16 | }, 17 | "author": { 18 | "name": "" 19 | } 20 | }, 21 | "parameters": { 22 | "PlaybookName": { 23 | "defaultValue": "update-AzureRBACRolesWatchlist", 24 | "type": "string" 25 | }, 26 | "Sentinel Resource Group": { 27 | "type": "String", 28 | "metadata": { 29 | "description": "Enter value for Sentinel Resource Group" 30 | } 31 | }, 32 | "Sentinel Subscription ID": { 33 | "type": "String", 34 | "metadata": { 35 | "description": "Enter value for Sentinel Subscription ID" 36 | } 37 | }, 38 | "Sentinel Workspace ID": { 39 | "type": "String", 40 | "metadata": { 41 | "description": "Enter value for Sentinel Workspace ID" 42 | } 43 | } 44 | }, 45 | "variables": { 46 | "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]" 47 | }, 48 | "resources": [ 49 | { 50 | "properties": { 51 | "provisioningState": "Succeeded", 52 | "state": "Enabled", 53 | "definition": { 54 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 55 | "contentVersion": "1.0.0.0", 56 | "parameters": { 57 | "$connections": { 58 | "defaultValue": {}, 59 | "type": "Object" 60 | }, 61 | "Sentinel Resource Group": { 62 | "type": "String", 63 | "defaultValue": "[parameters('Sentinel Resource Group')]" 64 | }, 65 | "Sentinel Subscription ID": { 66 | "type": "String", 67 | "defaultValue": "[parameters('Sentinel Subscription ID')]" 68 | }, 69 | "Sentinel Workspace ID": { 70 | "type": "String", 71 | "defaultValue": "[parameters('Sentinel Workspace ID')]" 72 | } 73 | }, 74 | "triggers": { 75 | "Recurrence": { 76 | "recurrence": { 77 | "frequency": "Day", 78 | "interval": 1 79 | }, 80 | "evaluatedRecurrence": { 81 | "frequency": "Day", 82 | "interval": 1 83 | }, 84 | "type": "Recurrence" 85 | } 86 | }, 87 | "actions": { 88 | "Create_CSV_table": { 89 | "runAfter": { 90 | "Parse_JSON": [ 91 | "Succeeded" 92 | ] 93 | }, 94 | "type": "Table", 95 | "inputs": { 96 | "columns": [ 97 | { 98 | "header": "RoleName", 99 | "value": "@item()?['properties']?['roleName']" 100 | }, 101 | { 102 | "header": "RoleDefinitionId", 103 | "value": "@item()?['name']" 104 | }, 105 | { 106 | "header": "RoleType", 107 | "value": "@item()?['properties']?['type']" 108 | }, 109 | { 110 | "header": "RoleDescription", 111 | "value": "@item()?['properties']?['description']" 112 | } 113 | ], 114 | "format": "CSV", 115 | "from": "@body('Parse_JSON')?['value']" 116 | } 117 | }, 118 | "Get_Azure_RBAC_Roles": { 119 | "runAfter": {}, 120 | "type": "Http", 121 | "inputs": { 122 | "authentication": { 123 | "type": "ManagedServiceIdentity" 124 | }, 125 | "method": "GET", 126 | "queries": { 127 | "api-version": "2022-04-01", 128 | "select": "roleName,name,type,description" 129 | }, 130 | "uri": "https://management.azure.com/providers/Microsoft.Authorization/roleDefinitions" 131 | }, 132 | "runtimeConfiguration": { 133 | "contentTransfer": { 134 | "transferMode": "Chunked" 135 | } 136 | } 137 | }, 138 | "Parse_JSON": { 139 | "runAfter": { 140 | "Get_Azure_RBAC_Roles": [ 141 | "Succeeded" 142 | ] 143 | }, 144 | "type": "ParseJson", 145 | "inputs": { 146 | "content": "@body('Get_Azure_RBAC_Roles')", 147 | "schema": { 148 | "properties": { 149 | "value": { 150 | "items": { 151 | "properties": { 152 | "id": { 153 | "type": "string" 154 | }, 155 | "name": { 156 | "type": "string" 157 | }, 158 | "properties": { 159 | "properties": { 160 | "assignableScopes": { 161 | "items": { 162 | "type": "string" 163 | }, 164 | "type": "array" 165 | }, 166 | "createdBy": {}, 167 | "createdOn": { 168 | "type": "string" 169 | }, 170 | "description": { 171 | "type": "string" 172 | }, 173 | "permissions": { 174 | "items": { 175 | "properties": { 176 | "actions": { 177 | "items": { 178 | "type": "string" 179 | }, 180 | "type": "array" 181 | }, 182 | "dataActions": { 183 | "type": "array" 184 | }, 185 | "notActions": { 186 | "type": "array" 187 | }, 188 | "notDataActions": { 189 | "type": "array" 190 | } 191 | }, 192 | "required": [ 193 | "actions", 194 | "dataActions", 195 | "notActions", 196 | "notDataActions" 197 | ], 198 | "type": "object" 199 | }, 200 | "type": "array" 201 | }, 202 | "roleName": { 203 | "type": "string" 204 | }, 205 | "type": { 206 | "type": "string" 207 | }, 208 | "updatedBy": {}, 209 | "updatedOn": { 210 | "type": "string" 211 | } 212 | }, 213 | "type": "object" 214 | }, 215 | "type": { 216 | "type": "string" 217 | } 218 | }, 219 | "required": [ 220 | "id", 221 | "name", 222 | "properties", 223 | "type" 224 | ], 225 | "type": "object" 226 | }, 227 | "type": "array" 228 | } 229 | }, 230 | "type": "object" 231 | } 232 | } 233 | }, 234 | "Watchlists_-_Create_a_new_Watchlist": { 235 | "runAfter": { 236 | "Create_CSV_table": [ 237 | "Succeeded" 238 | ] 239 | }, 240 | "type": "ApiConnection", 241 | "inputs": { 242 | "body": { 243 | "description": "Azure RBAC Definitions", 244 | "displayName": "Azure Role Definitions", 245 | "itemsSearchKey": "RoleDefinitionId", 246 | "rawContent": "@{body('Create_CSV_table')}" 247 | }, 248 | "host": { 249 | "connection": { 250 | "name": "@parameters('$connections')['azuresentinel']['connectionId']" 251 | } 252 | }, 253 | "method": "put", 254 | "path": "/Watchlists/subscriptions/@{encodeURIComponent(parameters('Sentinel Subscription ID'))}/resourceGroups/@{encodeURIComponent(parameters('Sentinel Resource Group'))}/workspaces/@{encodeURIComponent(parameters('Sentinel Workspace ID'))}/watchlists/@{encodeURIComponent('AzureRoles')}" 255 | } 256 | } 257 | }, 258 | "outputs": {} 259 | }, 260 | "parameters": { 261 | "$connections": { 262 | "value": { 263 | "azuresentinel": { 264 | "connectionId": "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", 265 | "connectionName": "[variables('MicrosoftSentinelConnectionName')]", 266 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]", 267 | "connectionProperties": { 268 | "authentication": { 269 | "type": "ManagedServiceIdentity" 270 | } 271 | } 272 | } 273 | } 274 | } 275 | } 276 | }, 277 | "name": "[parameters('PlaybookName')]", 278 | "type": "Microsoft.Logic/workflows", 279 | "location": "[resourceGroup().location]", 280 | "tags": { 281 | "hidden-SentinelTemplateName": "update-AzureRBACRolesWatchlist", 282 | "hidden-SentinelTemplateVersion": "1.0" 283 | }, 284 | "identity": { 285 | "type": "SystemAssigned" 286 | }, 287 | "apiVersion": "2017-07-01", 288 | "dependsOn": [ 289 | "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]" 290 | ] 291 | }, 292 | { 293 | "type": "Microsoft.Web/connections", 294 | "apiVersion": "2016-06-01", 295 | "name": "[variables('MicrosoftSentinelConnectionName')]", 296 | "location": "[resourceGroup().location]", 297 | "kind": "V1", 298 | "properties": { 299 | "displayName": "[variables('MicrosoftSentinelConnectionName')]", 300 | "customParameterValues": {}, 301 | "parameterValueType": "Alternative", 302 | "api": { 303 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]" 304 | } 305 | } 306 | } 307 | ] 308 | } 309 | -------------------------------------------------------------------------------- /Azure Activity/readme.md: -------------------------------------------------------------------------------- 1 | # Azure Activity RBAC Hunting and Enrichment 2 | This page contains a collection KQL queries and enrichment automation to hunt for Azure RBAC changes. 3 | 4 | ## Current Challenges 5 | The Azure Activity logs currently only contain GUIDs of Azure RBAC roles and identities. Furthermore, RBAC changes are represented in two separate events in the logs. This solution provides data enrichment using: 6 | - Logic App to pull in Azure Role Information 7 | - KQL Queries to pull in Identity information from the UEBA tables 8 | 9 | ## Data Enrichment 10 | 11 | ### update-AzureRBACRolesWatchlist 12 | The update-AzureRBACRolesWatchlist logic apps gets current role defintions in your tenant and creates/updates a watchlist with role definitions. 13 | > The logic app will run once a day by default 14 | 15 | 1. [](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2FAzure%2520Activity%2Fazuredeploy-update-AzureRBACRolesWatchlist.json) 16 | 17 | 2. Assign the **Microsoft Sentinel Contributor** role to the logic app system assigned managed identity 18 | 19 | ## KQL Queries 20 | 21 | | Query | Description | 22 | |---|---| 23 | | [Azure RBAC Role Assignments](./Azure%20RBAC%20Role%20Assignments.kql)| Gets Azure RBAC role changes | 24 | | [Azure RBAC Role Assignments with IdentityInfo](./Azure%20RBAC%20Role%20Assignments%20with%20IdentityInfo.kql)| Gets Azure RBAC role changes and assigned identity information from the IdentityInfo table. Requires UEBA | 25 | | [Azure RBAC Role Assignments with IdentityInfo and Roles](./Azure%20RBAC%20Role%20Assignments%20with%20IdentityInfo%20and%20Roles.kql)| Gets Azure RBAC role changes, assigned identity information from the IdentityInfo table and role information from the AzureRoles watchlist. Requires UEBA and the logic app | 26 | -------------------------------------------------------------------------------- /KQL/GitHubAuditLogPolling.kql: -------------------------------------------------------------------------------- 1 | // GitHubAuditLogPolling_CL Parser 2 | GitHubAuditLogPolling_CL 3 | | extend 4 | TimeGenerated = unixtime_milliseconds_todatetime(tolong(parse_json(RawData).created_at)), 5 | timestamp = parse_json(RawData).['@timestamp'], 6 | document_id = tostring(parse_json(RawData)._document_id), 7 | action = tostring(parse_json(RawData).action), 8 | actor = tostring(parse_json(RawData).actor), 9 | actor_id = tostring(parse_json(RawData).actor_id), 10 | actor_ip = tostring(parse_json(RawData).actor_ip), 11 | actor_location = parse_json(RawData).actor_location, 12 | application_name = tostring(parse_json(RawData).application_name), 13 | business = tostring(parse_json(RawData).business), 14 | business_id = tostring(parse_json(RawData).business_id), 15 | hashed_token = tostring(parse_json(RawData).hashed_token), 16 | integration = tostring(parse_json(RawData).integration), 17 | oauth_application_id = tostring(parse_json(RawData).oauth_application_id), 18 | operation_type = tostring(parse_json(RawData).operation_type), 19 | org = tostring(parse_json(RawData).org), 20 | org_id = tostring(parse_json(RawData).org_id), 21 | programmatic_access_type = tostring(parse_json(RawData).programmatic_access_type), 22 | public_repo = tostring(parse_json(RawData).public_repo), 23 | query_string = tostring(parse_json(RawData).query_string), 24 | rate_limit_remaining = tostring(parse_json(RawData).rate_limit_remaining), 25 | repo = tostring(parse_json(RawData).repo), 26 | repo_id = tostring(parse_json(RawData).repo_id), 27 | request_body = tostring(parse_json(RawData).request_body), 28 | request_method = tostring(parse_json(RawData).request_method), 29 | route = tostring(parse_json(RawData).route), 30 | status_code = tostring(parse_json(RawData).status_code), 31 | token_id = tostring(parse_json(RawData).token_id), 32 | token_scopes = tostring(parse_json(RawData).token_scopes), 33 | url_path = tostring(parse_json(RawData).url_path), 34 | user = tostring(parse_json(RawData).user), 35 | user_agent = tostring(parse_json(RawData).user_agent), 36 | user_id = tostring(parse_json(RawData).user_id) 37 | -------------------------------------------------------------------------------- /KQL/Total Cost Estimates by Subscription with table breakdown.txt: -------------------------------------------------------------------------------- 1 | let Price = 3; 2 | let billedSizeBySub = materialize (union withsource = TableName1 * 3 | | where _IsBillable == True 4 | | where _SubscriptionId contains "ada06e68-375e-4210-be3a-c6cacebf41c5" 5 | | summarize totalTableEntries = count(), tableSize = sum(_BilledSize), lastLogReceived = datetime_diff("second",now(), max(TimeGenerated)), 6 | estimate = sumif(_BilledSize, _IsBillable==true) 7 | by _SubscriptionId, ResourceGroup, TableName1, _IsBillable 8 | | extend SubscriptionId = iif(isempty(_SubscriptionId),'Non Subscription Data',_SubscriptionId) 9 | | project SubscriptionId, ResourceGroup = tolower(ResourceGroup), TableName1, _IsBillable, tableSize, ['Estimated Price'] = (estimate/(1024*1024*1024)) * Price,totalTableEntries, lastLogReceived); 10 | billedSizeBySub 11 | | union 12 | (billedSizeBySub 13 | | summarize resourceGroupCount = tostring(dcount(ResourceGroup)), tableCount = tostring(dcount(TableName1)), tableSize = sum(tableSize), ['Estimated Price'] = sum(['Estimated Price']), totalTableEntries = sum(totalTableEntries) 14 | ) 15 | | extend ResourceGroup = iif(isempty(ResourceGroup),resourceGroupCount,ResourceGroup) 16 | | extend TableName1 = iif(isempty(TableName1),tableCount,TableName1) 17 | | extend FinalTotals = iif(isempty(SubscriptionId),'Final Totals:','') 18 | | project FinalTotals, SubscriptionId, ResourceGroup, ['tableName'] = TableName1, _IsBillable, tableSize, ['Estimated Price'], totalTableEntries, lastLogReceived 19 | | order by ['Estimated Price'], totalTableEntries, FinalTotals asc 20 | -------------------------------------------------------------------------------- /KQL/mfa changes by watchlist: -------------------------------------------------------------------------------- 1 | AuditLogs 2 | | where OperationName in ('User changed default security info','User deleted security info','User registered all required security info', 'User registered security info') 3 | | where Result == "success" 4 | | extend userPrincipalName = parse_json(tostring(InitiatedBy.user)).userPrincipalName 5 | | extend IPAddress = tostring(parse_json(tostring(InitiatedBy.user)).ipAddress) 6 | | where userPrincipalName in ( 7 | (_GetWatchlist('watchlist') 8 | | project userPrincipalName) 9 | ) 10 | | project TimeGenerated, userPrincipalName, IPAddress, OperationName, ResultReason, LoggedByService, AdditionalDetails 11 | -------------------------------------------------------------------------------- /agent-management/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Update Extensions 3 | 4 | - [Overview](#overview) 5 | - [Usage](#usage) 6 | - [Examples](#examples) 7 | * [Azure Virtual Machines](#azure-virtual-machines) 8 | * [Update Azure Virtual Machines to a specific version of the Azure Monitor Agent](#update-azure-virtual-machines-to-a-specific-version-of-the-azure-monitor-agent) 9 | 10 | # Overview 11 | 12 | [**update-extension.ps1**](https://github.com/seanstark/sentinel-tools/blob/main/agent-management/update-extension.ps1) is a powershell script you can use to update extensions on Azure Virtual Machines and Azure Arc Machines. The script will handle both linux and windows servers with the below features. 13 | 14 | - Update the extension to a specific version 15 | - Update the extension to the latest version 16 | - Report on current versions without updating 17 | 18 | # Usage 19 | 20 | > Azure Arc does not return a detailed status of the update request 21 | > 22 | > The rest API only allows upgrading to Major+Minor versions. Therefore you can't upgrade from 1.2 to 1.2.2 as an example, only to 1.3 23 | 24 | - The script takes input from an object of machines from either [**Get-AzVM**](https://learn.microsoft.com/powershell/module/az.compute/get-azvm?view) or [**Get-AzConnectedMachine**](https://learn.microsoft.com/powershell/module/az.connectedmachine/get-azconnectedmachine). This will give you the flexibility to scope updates to specific machines. 25 | 26 | - You can specify the versions you want to update to using the **linuxTargetVersion** and **windowsTargetVersion** parameters. 27 | 28 | - If you specify the **latestVersion** parameter the script will automatically use the latest version available in the region where the machine resides. 29 | 30 | - If you specify the **report** parameter the script will only report on versions installed and will not update 31 | 32 | # Examples 33 | 34 | ## Azure Virtual Machines 35 | 36 | ### Update Azure Virtual Machines to a specific version of the Azure Monitor Agent 37 | ``` 38 | .\update-extension.ps1 -machines $(Get-AzVM) -linuxTargetVersion 1.22.2 -windowsTargetVersion 1.10.0.0 -extPublisherName 'Microsoft.Azure.Monitor' -windowsExtType 'AzureMonitorWindowsAgent' -linuxExtType 'AzureMonitorLinuxAgent' 39 | ``` 40 | -------------------------------------------------------------------------------- /agent-management/update-extension.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update Azure Virtual Machines and Azure Arc Machines to the desired or latest version of an extension. 4 | 5 | .PARAMETER machines 6 | Specify an object of machines from Get-AzVM or Get-AzConnectedMachine 7 | 8 | .PARAMETER linuxTargetVersion 9 | Specify the version of the extension to ugprade to for Linux. See https://learn.microsoft.com/azure/azure-monitor/agents/azure-monitor-agent-extension-versions for AMA 10 | 11 | .PARAMETER windowsTargetVersion 12 | Specify the version of the extension to ugprade to for Windows. See https://learn.microsoft.com/azure/azure-monitor/agents/azure-monitor-agent-extension-versions for AMA 13 | 14 | .PARAMETER latestVersion 15 | Specify the latestVersion switch to use the latest version available for Linux and Windows. This is unique to each region. 16 | 17 | .PARAMETER report 18 | Specify the report switch to only report on machines with current versions 19 | 20 | .PARAMETER extPublisherName 21 | Specify the Extension Publisher name. Defaults to Microsoft.Azure.Monitor. See https://learn.microsoft.com/azure/azure-arc/servers/manage-vm-extensions 22 | 23 | .PARAMETER windowsExtType 24 | Specify the Windows Extension Type name. Defaults to AzureMonitorWindowsAgent. See https://learn.microsoft.com/azure/azure-arc/servers/manage-vm-extensions 25 | 26 | .PARAMETER linuxExtType 27 | Specify the Linux Extension Type name. Defaults to AzureMonitorLinuxAgent. See https://learn.microsoft.com/azure/azure-arc/servers/manage-vm-extensions 28 | 29 | .EXAMPLE 30 | Update Azure Virtual Machines to a specific version of the Azure Monitor Agent 31 | .\update-ama.ps1 -machines $(Get-AzVM) -linuxTargetVersion 1.22.2 -windowsTargetVersion 1.10.0.0 -extPublisherName 'Microsoft.Azure.Monitor' -windowsExtType 'AzureMonitorWindowsAgent' -linuxExtType 'AzureMonitorLinuxAgent' 32 | #> 33 | 34 | param( 35 | [Parameter(Mandatory=$true)] 36 | [object]$machines, 37 | 38 | [Parameter(Mandatory=$true, ParameterSetName = 'TargetVersion')] 39 | [string]$linuxTargetVersion, 40 | 41 | [Parameter(Mandatory=$true, ParameterSetName = 'TargetVersion')] 42 | [string]$windowsTargetVersion, 43 | 44 | [Parameter(Mandatory=$true, ParameterSetName = 'LatestVersion')] 45 | [switch]$latestVersion, 46 | 47 | [Parameter(Mandatory=$false)] 48 | [switch]$report, 49 | 50 | [Parameter(Mandatory=$false)] 51 | [string]$extPublisherName = 'Microsoft.Azure.Monitor', 52 | 53 | [Parameter(Mandatory=$false)] 54 | [string]$windowsExtType = 'AzureMonitorWindowsAgent', 55 | 56 | [Parameter(Mandatory=$false)] 57 | [string]$linuxExtType = 'AzureMonitorLinuxAgent' 58 | ) 59 | 60 | $requiredModules = 'Az.Accounts', 'Az.Compute', 'Az.ConnectedMachine' 61 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 62 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 63 | ForEach ($module in $modulesToInstall){ 64 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 65 | Install-Module $module -force 66 | } 67 | 68 | # This isn't used for anything other than to query for arc extension versions 69 | $subscriptionId = (Get-AzContext | Select -ExpandProperty Subscription).id 70 | 71 | function Get-latestVersion{ 72 | Param($versions) 73 | $latest = $versions | % {[version]$_} | Sort-Object -Descending | Select -First 1 74 | $latest.ToString() 75 | } 76 | 77 | #Get Latest Versions for each machine region 78 | If($latestVersion){ 79 | 80 | $regionLatestVersions = @() 81 | $regions = $machines.location | Sort-Object | Get-Unique 82 | ForEach ($region in $regions){ 83 | Write-Verbose ('Getting Region Latest Extension Versions for {0}' -f $region ) 84 | # Azure Native Virtual Machines 85 | $windowsVersions = Get-AzVMExtensionImage -PublisherName $extPublisherName -Type $windowsExtType -Location $region 86 | $linuxVersions = Get-AzVMExtensionImage -PublisherName $extPublisherName -Type $linuxExtType -Location $region 87 | 88 | # Azure Arc Machines 89 | If($machines[0].Type -like 'Microsoft.HybridCompute/machines'){ 90 | Write-Verbose ('Getting Azure Arc Region Latest Extension Versions for {0}' -f $region ) 91 | $uri = ('https://management.azure.com/subscriptions/{0}/providers/Microsoft.HybridCompute/locations/{1}/publishers/{2}/extensionTypes/{3}/versions?api-version=2022-12-27-preview' -f $subscriptionId, $region, $extPublisherName, $windowsExtType) 92 | $windowsVersions = (Invoke-AzRestMethod -Uri $uri -Method GET).Content | ConvertFrom-Json | Select -ExpandProperty properties 93 | 94 | $uri = ('https://management.azure.com/subscriptions/{0}/providers/Microsoft.HybridCompute/locations/{1}/publishers/{2}/extensionTypes/{3}/versions?api-version=2022-12-27-preview' -f $subscriptionId, $region, $extPublisherName, $linuxExtType) 95 | $linuxVersions = (Invoke-AzRestMethod -Uri $uri -Method GET).Content | ConvertFrom-Json | Select -ExpandProperty properties 96 | } 97 | 98 | #Update Object 99 | $regionLatestVersions += [PSCustomObject]@{ 100 | location = $region 101 | linuxLatestVersion = Get-latestVersion $linuxVersions.Version 102 | windowsLatestVersion = Get-latestVersion $windowsVersions.Version 103 | } 104 | } 105 | } 106 | 107 | $agentsToUpgrade = @() 108 | 109 | Write-Host ('Evaluating {0} machines' -f $machines.Count) 110 | 111 | ForEach ($machine in $machines){ 112 | # Cannot trust the OsType is properly detected, check for AzureMonitorWindowsAgent and AzureMonitorLinuxAgent 113 | $agent = $null 114 | 115 | Write-Verbose ('Evaluating {0}' -f $machine.Name) 116 | 117 | If($machine.Type -like 'Microsoft.Compute/virtualMachines'){ 118 | $state = (($machine | Get-AzVM -Status).statuses | Where Code -like 'PowerState*').DisplayStatus 119 | $windowsAgent = Get-AzVMExtension -VMName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name $windowsExtType -ErrorAction SilentlyContinue 120 | $linuxAgent = Get-AzVMExtension -VMName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name $linuxExtType -ErrorAction SilentlyContinue 121 | } 122 | If($machine.Type -like 'Microsoft.HybridCompute/machines'){ 123 | $state = $machine.Status 124 | $windowsAgent = Get-AzConnectedMachineExtension -MachineName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name $windowsExtType -ErrorAction SilentlyContinue 125 | $linuxAgent = Get-AzConnectedMachineExtension -MachineName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name $linuxExtType -ErrorAction SilentlyContinue 126 | } 127 | 128 | # If latestVersion is flagged, get the latest published version for the region where the machine resides 129 | If($latestVersion){ 130 | Write-Verbose ('Latest Version Parameter Specified. Getting Latest Version for Region: {0}' -f $machine.Location) 131 | $linuxTargetVersion = $regionLatestVersions | Where-Object {$_.location -like $machine.Location} | Select -ExpandProperty linuxLatestVersion 132 | $windowsTargetVersion = $regionLatestVersions | Where-Object {$_.location -like $machine.Location} | Select -ExpandProperty windowsLatestVersion 133 | } 134 | 135 | # Build Agent Objects 136 | If ($windowsAgent){ 137 | $agent = $windowsAgent 138 | $agent | Add-Member -MemberType NoteProperty -Name TargetVersion -Value $windowsTargetVersion -Force 139 | $agent | Add-Member -MemberType NoteProperty -Name extensionTarget -Value $windowsAgent.extensionTarget -Force 140 | } 141 | If ($linuxAgent){ 142 | $agent = $linuxAgent 143 | $agent | Add-Member -MemberType NoteProperty -Name TargetVersion -Value $linuxTargetVersion -Force 144 | $agent | Add-Member -MemberType NoteProperty -Name extensionTarget -Value $linuxAgent.extensionTarget -Force 145 | } 146 | 147 | # Add Additional Attributes 148 | If ($agent){ 149 | #Fix Target Version, can only be major and minor version 150 | $agent | Add-Member -MemberType NoteProperty -Name TargetMajorMinorVersion -Value ('{0}.{1}'-f ([Version]($agent.TargetVersion)).Major, ([Version]($agent.TargetVersion)).Minor) -Force 151 | $agent | Add-Member -MemberType NoteProperty -Name CurrentVersion -Value $agent.TypeHandlerVersion -Force 152 | $agent | Add-Member -MemberType NoteProperty -Name MachineType -Value $machine.Type -Force 153 | $agent | Add-Member -MemberType NoteProperty -Name MachineState -Value $state -Force 154 | $agent | Add-Member -MemberType NoteProperty -Name MachineName -Value $machine.Name -Force 155 | $agent | Add-Member -MemberType NoteProperty -Name SubscriptionId -Value $machine.id.split('/')[2] -Force 156 | $agentsToUpgrade += $agent 157 | 158 | Write-Verbose ($agent | Select MachineName, SubscriptionId, ResourceGroupName, MachineState, MachineType, Name, CurrentVersion, TargetVersion, extensionTarget, EnableAutomaticUpgrade, ProvisioningState) 159 | } 160 | } 161 | 162 | If ($report){ 163 | Write-Host 'Report only specified' 164 | $agentsToUpgrade | Select MachineName, SubscriptionId, ResourceGroupName, MachineState, MachineType, Name, CurrentVersion, TargetVersion, extensionTarget, EnableAutomaticUpgrade, ProvisioningState | ft 165 | }else { 166 | #Get only running or connected machines 167 | $agentsToUpgrade = $agentsToUpgrade | Where-Object {$_.MachineState -like 'VM running' -or $_.MachineState -like 'Connected'} 168 | #Get only machines that do not match the target version 169 | $agentsToUpgrade = $agentsToUpgrade | Where-Object {[Version]$_.CurrentVersion -lt [Version]$_.TargetVersion} 170 | 171 | Write-Host ('{0} out of {1} machines to upgrade' -f $agentsToUpgrade.count, $machines.Count) 172 | 173 | ForEach ($agent in $agentsToUpgrade){ 174 | If($agent.MachineType -like 'Microsoft.Compute/virtualMachines'){ 175 | $uri = ('https://management.azure.com{0}?api-version=2022-11-01' -f $agent.Id) 176 | $method = 'PATCH' 177 | $body = @{ 178 | properties = @{ 179 | publisher = $agent.Publisher 180 | type = $agent.ExtensionType 181 | typeHandlerVersion = $agent.TargetMajorMinorVersion 182 | } 183 | } 184 | } 185 | If($agent.MachineType -like 'Microsoft.HybridCompute/machines'){ 186 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.HybridCompute/machines/{2}/upgradeExtensions?api-version=2022-12-27-preview' -f $agent.SubscriptionId, $agent.ResourceGroupName, $agent.MachineName) 187 | $method = 'POST' 188 | $targetType = ('{0}.{1}' -f $agent.Publisher, $agent.Name) 189 | $body = @{ 190 | extensionTargets = @{ 191 | "$targetType"= @{ 192 | targetVersion = $agent.TargetMajorMinorVersion 193 | } 194 | } 195 | } 196 | } 197 | Write-Host ('Updating {0} from version {1} to latest version: {2} ' -f $agent.MachineName, $agent.CurrentVersion, $agent.TargetVersion) 198 | Write-Verbose $($body | ConvertTo-Json) 199 | Write-Verbose $uri 200 | $request = Invoke-AzRestMethod -Uri $uri -Method $method -Payload $($body | ConvertTo-Json) 201 | $reqContent = $request.Content | ConvertFrom-Json 202 | $request | Add-Member -MemberType NoteProperty -Name provisioningState -Value $reqContent.properties.provisioningState -Force 203 | $request | Add-Member -MemberType NoteProperty -Name publisher -Value $reqContent.properties.publisher -Force 204 | $request | Add-Member -MemberType NoteProperty -Name type -Value $reqContent.properties.type -Force 205 | $request | Add-Member -MemberType NoteProperty -Name typeHandlerVersion -Value $reqContent.properties.typeHandlerVersion -Force 206 | $request | Add-Member -MemberType NoteProperty -Name machineName -Value $agent.MachineName -Force 207 | $request | Select machineName, StatusCode, Method, provisioningState, publisher, type, typeHandlerVersion 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /ama-management/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Update Azure Monitor Agent 3 | 4 | - [Overview](#overview) 5 | - [Usage](#usage) 6 | - [Examples](#examples) 7 | * [Azure Virtual Machines](#azure-virtual-machines) 8 | * [Update Azure Virtual Machines to a specific version](#update-azure-virtual-machines-to-a-specific-version) 9 | * [Update Azure Virtual Machines to the latest version of Windows and Linux](#update-azure-virtual-machines-to-the-latest-version-of-windows-and-linux) 10 | * [Generate a report of Azure Virtual Machines with current versions](#generate-a-report-of-azure-virtual-machines-with-current-versions) 11 | * [Azure Arc Machines](#azure-arc-machines) 12 | * [Update Azure Arc Machines to a specific version](#update-azure-arc-machines-to-a-specific-version) 13 | * [Update Azure Arc Machines to the latest version of Windows and Linux](#update-azure-arc-machines-to-the-latest-version-of-windows-and-linux) 14 | * [Generate a report of Azure Arc Machines with current versions](#generate-a-report-of-azure-arc-machines-with-current-versions) 15 | 16 | # Overview 17 | 18 | [**update-ama.ps1**](https://github.com/seanstark/sentinel-tools/blob/main/ama-management/update-ama.ps1) is a powershell script you can use to update the Azure Monitor Agent on Azure Virtual Machines and Azure Arc Machines. The script will handle both linux and windows servers with the below features. 19 | 20 | - Update the Azure Monitor Agent to a specific version 21 | - Update the Azure Monitor Agent to the latest version 22 | - Report on current versions without updating 23 | 24 | # Usage 25 | 26 | > Azure Arc does not return a detailed status of the update request 27 | > 28 | > The rest API only allows upgrading to Major+Minor versions. Therefore you can't upgrade from 1.2 to 1.2.2 as an example, only to 1.3 29 | 30 | - The script takes input from an object of machines from either [**Get-AzVM**](https://learn.microsoft.com/powershell/module/az.compute/get-azvm?view) or [**Get-AzConnectedMachine**](https://learn.microsoft.com/powershell/module/az.connectedmachine/get-azconnectedmachine). This will give you the flexibility to scope updates to specific machines. 31 | 32 | - You can specify the versions you want to update to using the **linuxTargetVersion** and **windowsTargetVersion** parameters. 33 | - > To get a list of versions see [Azure Monitor agent extension versions](https://learn.microsoft.com/en-us/azure/azure-monitor/agents/azure-monitor-agent-extension-versions) 34 | 35 | - If you specify the **latestVersion** parameter the script will automatically use the latest version available in the region where the machine resides. 36 | 37 | - If you specify the **report** parameter the script will only report on versions installed and will not update 38 | 39 | # Examples 40 | 41 | ## Azure Virtual Machines 42 | 43 | ### Update Azure Virtual Machines to a specific version 44 | ``` 45 | .\update-ama.ps1 -machines $(Get-AzVM) -linuxTargetVersion 1.22.2 -windowsTargetVersion 1.10.0.0 46 | ``` 47 | 48 | ### Update Azure Virtual Machines to the latest version of Windows and Linux 49 | ``` 50 | .\update-ama.ps1 -machines $(Get-AzVM) -latestVersion 51 | ``` 52 | 53 | ### Generate a report of Azure Virtual Machines with current versions 54 | ``` 55 | .\update-ama.ps1 -machines $(Get-AzVM) -latestVersion -report 56 | ``` 57 | 58 | ## Azure Arc Machines 59 | 60 | ### Update Azure Arc Machines to a specific version 61 | ``` 62 | .\update-ama.ps1 -machines $(Get-AzConnectedMachine) -linuxTargetVersion 1.22.2 -windowsTargetVersion 1.10.0.0 63 | ``` 64 | 65 | ### Update Azure Arc Machines to the latest version of Windows and Linux 66 | ``` 67 | .\update-ama.ps1 -machines $(Get-AzConnectedMachine) -latestVersion 68 | ``` 69 | 70 | ### Generate a report of Azure Arc Machines with current versions 71 | ``` 72 | .\update-ama.ps1 -machines $(Get-AzConnectedMachine) -latestVersion -report 73 | ``` 74 | -------------------------------------------------------------------------------- /ama-management/update-ama.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update Azure Virtual Machines and Azure Arc Machines to the desired or latest version of the Azure Monitor Agent. 4 | 5 | .PARAMETER machines 6 | Specify an object of machines from Get-AzVM or Get-AzConnectedMachine 7 | 8 | .PARAMETER linuxTargetVersion 9 | Specify the version of the Azure Monitor Agent to ugprade to for Linux. See https://learn.microsoft.com/azure/azure-monitor/agents/azure-monitor-agent-extension-versions 10 | 11 | .PARAMETER windowsTargetVersion 12 | Specify the version of the Azure Monitor Agent to ugprade to for Windows. See https://learn.microsoft.com/azure/azure-monitor/agents/azure-monitor-agent-extension-versions 13 | 14 | .PARAMETER latestVersion 15 | Specify the latestVersion switch to use the latest version available for Linux and Windows. This is unique to each region. 16 | 17 | .PARAMETER report 18 | Specify the report switch to only report on machines with current versions 19 | 20 | .EXAMPLE 21 | Update Azure Virtual Machines to a specific version 22 | .\update-ama.ps1 -machines $(Get-AzVM) -linuxTargetVersion 1.22.2 -windowsTargetVersion 1.10.0.0 23 | 24 | .EXAMPLE 25 | Update Azure Virtual Machines to the latest version of Windows and Linux 26 | .\update-ama.ps1 -machines $(Get-AzVM) -latestVersion 27 | 28 | .EXAMPLE 29 | Generate a report of Azure Virtual Machines with current versions 30 | .\update-ama.ps1 -machines $(Get-AzVM) -latestVersion -report 31 | 32 | .EXAMPLE 33 | Update Azure Arc Machines to a specific version 34 | .\update-ama.ps1 -machines $(Get-AzConnectedMachine) -linuxTargetVersion 1.22.2 -windowsTargetVersion 1.10.0.0 35 | 36 | .EXAMPLE 37 | Update Azure Arc Machines to the latest version of Windows and Linux 38 | .\update-ama.ps1 -machines $(Get-AzConnectedMachine) -latestVersion 39 | 40 | .EXAMPLE 41 | Generate a report of Azure Arc Machines with current versions 42 | .\update-ama.ps1 -machines $(Get-AzConnectedMachine) -latestVersion -report 43 | #> 44 | 45 | param( 46 | [Parameter(Mandatory=$true)] 47 | [object]$machines, 48 | 49 | [Parameter(Mandatory=$true, ParameterSetName = 'TargetVersion')] 50 | [string]$linuxTargetVersion, 51 | 52 | [Parameter(Mandatory=$true, ParameterSetName = 'TargetVersion')] 53 | [string]$windowsTargetVersion, 54 | 55 | [Parameter(Mandatory=$true, ParameterSetName = 'LatestVersion')] 56 | [switch]$latestVersion, 57 | 58 | [Parameter(Mandatory=$false)] 59 | [switch]$report, 60 | 61 | [Parameter(Mandatory=$false)] 62 | [string]$extPublisherName = 'Microsoft.Azure.Monitor', 63 | 64 | [Parameter(Mandatory=$false)] 65 | [string]$windowsExtType = 'AzureMonitorWindowsAgent', 66 | 67 | [Parameter(Mandatory=$false)] 68 | [string]$linuxExtType = 'AzureMonitorLinuxAgent' 69 | ) 70 | 71 | $requiredModules = 'Az.Accounts', 'Az.Compute', 'Az.ConnectedMachine' 72 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 73 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 74 | ForEach ($module in $modulesToInstall){ 75 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 76 | Install-Module $module -force 77 | } 78 | 79 | # This isn't used for anything other than to query for arc extension versions 80 | $subscriptionId = (Get-AzContext | Select -ExpandProperty Subscription).id 81 | 82 | function Get-latestVersion{ 83 | Param($versions) 84 | $latest = $versions | % {[version]$_} | Sort-Object -Descending | Select -First 1 85 | $latest.ToString() 86 | } 87 | 88 | #Get Latest Versions for each machine region 89 | If($latestVersion){ 90 | 91 | $regionLatestVersions = @() 92 | $regions = $machines.location | Sort-Object | Get-Unique 93 | ForEach ($region in $regions){ 94 | Write-Verbose ('Getting Region Latest Extension Versions for {0}' -f $region ) 95 | # Azure Native Virtual Machines 96 | $windowsVersions = Get-AzVMExtensionImage -PublisherName $extPublisherName -Type $windowsExtType -Location $region 97 | $linuxVersions = Get-AzVMExtensionImage -PublisherName $extPublisherName -Type $linuxExtType -Location $region 98 | 99 | # Azure Arc Machines 100 | If($machines[0].Type -like 'Microsoft.HybridCompute/machines'){ 101 | Write-Verbose ('Getting Azure Arc Region Latest Extension Versions for {0}' -f $region ) 102 | $uri = ('https://management.azure.com/subscriptions/{0}/providers/Microsoft.HybridCompute/locations/{1}/publishers/{2}/extensionTypes/{3}/versions?api-version=2022-12-27-preview' -f $subscriptionId, $region, $extPublisherName, $windowsExtType) 103 | $windowsVersions = (Invoke-AzRestMethod -Uri $uri -Method GET).Content | ConvertFrom-Json | Select -ExpandProperty properties 104 | 105 | $uri = ('https://management.azure.com/subscriptions/{0}/providers/Microsoft.HybridCompute/locations/{1}/publishers/{2}/extensionTypes/{3}/versions?api-version=2022-12-27-preview' -f $subscriptionId, $region, $extPublisherName, $linuxExtType) 106 | $linuxVersions = (Invoke-AzRestMethod -Uri $uri -Method GET).Content | ConvertFrom-Json | Select -ExpandProperty properties 107 | } 108 | 109 | #Update Object 110 | $regionLatestVersions += [PSCustomObject]@{ 111 | location = $region 112 | linuxLatestVersion = Get-latestVersion $linuxVersions.Version 113 | windowsLatestVersion = Get-latestVersion $windowsVersions.Version 114 | } 115 | } 116 | } 117 | 118 | $agentsToUpgrade = @() 119 | 120 | Write-Host ('Evaluating {0} machines' -f $machines.Count) 121 | 122 | ForEach ($machine in $machines){ 123 | # Cannot trust the OsType is properly detected, check for AzureMonitorWindowsAgent and AzureMonitorLinuxAgent 124 | $agent = $null; $windowsAgent = $null; $linuxAgent = $null; $state= $null 125 | 126 | Write-Verbose ('Evaluating {0}' -f $machine.Name) 127 | # Write-Verbose $machine | out-string #Enable for debug logging 128 | 129 | If($machine.Type -like 'Microsoft.Compute/virtualMachines'){ 130 | $state = (($machine | Get-AzVM -Status).statuses | Where Code -like 'PowerState*').DisplayStatus 131 | #Write-Verbose $state 132 | if ($state -like 'VM running'){ 133 | Write-Verbose ('Machine State: {0}' -f $state) 134 | $windowsAgent = Get-AzVMExtension -VMName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name 'AzureMonitorWindowsAgent' -ErrorAction SilentlyContinue 135 | $linuxAgent = Get-AzVMExtension -VMName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name 'AzureMonitorLinuxAgent' -ErrorAction SilentlyContinue 136 | } 137 | } 138 | If($machine.Type -like 'Microsoft.HybridCompute/machines'){ 139 | $state = $machine.Status 140 | #Write-Verbose $state 141 | if ($state -like 'Connected'){ 142 | Write-Verbose ('Machine State: {0}' -f $state) 143 | $windowsAgent = Get-AzConnectedMachineExtension -MachineName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name 'AzureMonitorWindowsAgent' -ErrorAction SilentlyContinue 144 | $linuxAgent = Get-AzConnectedMachineExtension -MachineName $machine.Name -ResourceGroupName $machine.ResourceGroupName -Name 'AzureMonitorLinuxAgent' -ErrorAction SilentlyContinue 145 | } 146 | } 147 | 148 | # If latestVersion is flagged, get the latest published version for the region where the machine resides 149 | If($latestVersion -and ($state -like 'Connected' -or $state -like 'VM running')){ 150 | Write-Verbose ('Latest Version Parameter Specified. Getting Latest Version for Region: {0}' -f $machine.Location) 151 | $linuxTargetVersion = $regionLatestVersions | Where-Object {$_.location -like $machine.Location} | Select -ExpandProperty linuxLatestVersion 152 | $windowsTargetVersion = $regionLatestVersions | Where-Object {$_.location -like $machine.Location} | Select -ExpandProperty windowsLatestVersion 153 | Write-Verbose ('Latest Windows Version: {0}, Latest Linux Version: {1}' -f $windowsTargetVersion, $linuxTargetVersion) 154 | } 155 | 156 | # Build Agent Objects 157 | If ($windowsAgent){ 158 | $agent = $windowsAgent 159 | $agent | Add-Member -MemberType NoteProperty -Name TargetVersion -Value $windowsTargetVersion -Force 160 | $agent | Add-Member -MemberType NoteProperty -Name extensionTarget -Value $windowsAgent.extensionTarget -Force 161 | } 162 | If ($linuxAgent){ 163 | $agent = $linuxAgent 164 | $agent | Add-Member -MemberType NoteProperty -Name TargetVersion -Value $linuxTargetVersion -Force 165 | $agent | Add-Member -MemberType NoteProperty -Name extensionTarget -Value $linuxAgent.extensionTarget -Force 166 | } 167 | 168 | # Add Additional Attributes 169 | If ($agent){ 170 | #Fix Target Version, can only be major and minor version 171 | $agent | Add-Member -MemberType NoteProperty -Name TargetMajorMinorVersion -Value ('{0}.{1}'-f ([Version]($agent.TargetVersion)).Major, ([Version]($agent.TargetVersion)).Minor) -Force 172 | $agent | Add-Member -MemberType NoteProperty -Name CurrentVersion -Value $agent.TypeHandlerVersion -Force 173 | $agent | Add-Member -MemberType NoteProperty -Name MachineType -Value $machine.Type -Force 174 | $agent | Add-Member -MemberType NoteProperty -Name MachineState -Value $state -Force 175 | $agent | Add-Member -MemberType NoteProperty -Name MachineName -Value $machine.Name -Force 176 | $agent | Add-Member -MemberType NoteProperty -Name SubscriptionId -Value $machine.id.split('/')[2] -Force 177 | $agentsToUpgrade += $agent 178 | 179 | Write-Verbose ($agent | Select MachineName, SubscriptionId, ResourceGroupName, MachineState, MachineType, Name, CurrentVersion, TargetVersion, TargetMajorMinorVersion, extensionTarget, EnableAutomaticUpgrade, ProvisioningState) 180 | } 181 | } 182 | 183 | If ($report){ 184 | Write-Host 'Report only specified' 185 | $agentsToUpgrade | Select MachineName, SubscriptionId, ResourceGroupName, MachineState, MachineType, Name, CurrentVersion, TargetVersion, TargetMajorMinorVersion, extensionTarget, EnableAutomaticUpgrade, ProvisioningState | ft 186 | }else { 187 | #Get only running or connected machines 188 | $agentsToUpgrade = $agentsToUpgrade | Where-Object {$_.MachineState -like 'VM running' -or $_.MachineState -like 'Connected'} 189 | #Get only machines that do not match the target version 190 | $agentsToUpgrade = $agentsToUpgrade | Where-Object {[Version]$_.CurrentVersion -lt [Version]$_.TargetVersion} 191 | 192 | Write-Host ('{0} out of {1} machines to upgrade' -f $agentsToUpgrade.count, $machines.Count) 193 | 194 | ForEach ($agent in $agentsToUpgrade){ 195 | If($agent.MachineType -like 'Microsoft.Compute/virtualMachines'){ 196 | $uri = ('https://management.azure.com{0}?api-version=2022-11-01' -f $agent.Id) 197 | $method = 'PATCH' 198 | $body = @{ 199 | properties = @{ 200 | publisher = $agent.Publisher 201 | type = $agent.ExtensionType 202 | typeHandlerVersion = $agent.TargetMajorMinorVersion 203 | } 204 | } 205 | } 206 | If($agent.MachineType -like 'Microsoft.HybridCompute/machines'){ 207 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.HybridCompute/machines/{2}/upgradeExtensions?api-version=2022-12-27-preview' -f $agent.SubscriptionId, $agent.ResourceGroupName, $agent.MachineName) 208 | $method = 'POST' 209 | $targetType = ('{0}.{1}' -f $agent.Publisher, $agent.Name) 210 | $body = @{ 211 | extensionTargets = @{ 212 | "$targetType"= @{ 213 | targetVersion = $agent.TargetMajorMinorVersion 214 | } 215 | } 216 | } 217 | } 218 | Write-Host ('Updating {0} from version {1} to latest version: {2}({3}) ' -f $agent.MachineName, $agent.CurrentVersion, $agent.TargetVersion, $agent.TargetMajorMinorVersion) 219 | Write-Verbose $($body | ConvertTo-Json) 220 | Write-Verbose $uri 221 | $request = Invoke-AzRestMethod -Uri $uri -Method $method -Payload $($body | ConvertTo-Json) 222 | $reqContent = $request.Content | ConvertFrom-Json 223 | $request | Add-Member -MemberType NoteProperty -Name provisioningState -Value $reqContent.properties.provisioningState -Force 224 | $request | Add-Member -MemberType NoteProperty -Name publisher -Value $reqContent.properties.publisher -Force 225 | $request | Add-Member -MemberType NoteProperty -Name type -Value $reqContent.properties.type -Force 226 | $request | Add-Member -MemberType NoteProperty -Name typeHandlerVersion -Value $reqContent.properties.typeHandlerVersion -Force 227 | $request | Add-Member -MemberType NoteProperty -Name machineName -Value $agent.MachineName -Force 228 | $request | Select machineName, StatusCode, Method, provisioningState, publisher, type, typeHandlerVersion 229 | } 230 | } -------------------------------------------------------------------------------- /analytics_rules/create-scheduledRuleFromTemplate.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | You can leverage this script to create multiple scheduled analytics rules from the analytics rules templates on github https://github.com/Azure/Azure-Sentinel/tree/master/Detections. 4 | 5 | Known Limitations 6 | 1. Associated tables in the rule query need to exist first for the rule to be created. Tables are generally created when you start ingesting data. 7 | If the table does not exist the rule creation will fail during the script run 8 | 2. YAML files in the github repo may have incorrect query column to entity mappings defined. The rule creation will fail during the script run. 9 | If you run across either sumbit an issue via github on the YAML file or fork the github repo and submit a pull request - https://github.com/Azure/Azure-Sentinel#contributing 10 | 3. A fair number of rule templates do not have values for required data connectors. Be aware when using the dataconnector filter parameter you may not get a complete list of rules that leverage associated tables 11 | 12 | .PARAMETER subscriptionId 13 | Specify the subscriptionID GUID where your Sentinel Workspace Resides 14 | .PARAMETER resourceGroupName 15 | Specify the Resource Group Name where your Sentinel Workspace Resides 16 | .PARAMETER workspaceName 17 | Specify the Sentinel Workspace Name 18 | .PARAMETER githubToken 19 | Specify the GitHub Access Personal Access Token you created. Refer to the steps in [] to configure this token correctly. 20 | .PARAMETER apiVersion 21 | Optionally you can specify the API version of the Microsoft.SecurityInsights/alertRules endpoint 22 | .PARAMETER name 23 | Optionally you can enter the name of an Analytic Rule to Filter on. This parameter supports the -like operator for filtering 24 | .PARAMETER severity 25 | Optionally you can enter one or more rule severities separated by commas to filter rule templates on 26 | .PARAMETER detectionFolderName 27 | Optionally you can enter one or more child folders under https://github.com/Azure/Azure-Sentinel/tree/master/Detections separated by commas to filter rule templates on 28 | .PARAMETER techniques 29 | Optionally you can enter one or more techniques separated by commas to filter rule templates on 30 | .PARAMETER tactics 31 | Optionally you can enter one or more tactics separated by commas to filter rule templates on 32 | .PARAMETER dataConnector 33 | Optionally you can enter one or more dataConnector names separated by commas to filter rule templates on 34 | .PARAMETER dataType 35 | Optionally you can enter one or more data type names separated by commas to filter rule templates on. Data Type names refer to the table names in Sentinel 36 | .PARAMETER queryFilter 37 | Optionally you can enter one or more strings separated by commas to filter the query in the rule templates on. A common example is to filter based on tables names 38 | .PARAMETER tag 39 | Optionally you can enter one or more tag names separated by commas to filter rule templates on 40 | .PARAMETER enable 41 | Optionally you set the enable parameter to false to create rules but not enable them by default 42 | .PARAMETER reportOnly 43 | Optionally you can specify the reportOnly parameter to only report on what templates will be created 44 | .PARAMETER selectUI 45 | Optionally you can specify the selectUI parameter to show a UI to further select specific rules to import 46 | 47 | .EXAMPLE 48 | Create rules from all templates 49 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' 50 | .EXAMPLE 51 | Create rules from all templates in a disabled state 52 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' -enabled $false 53 | .EXAMPLE 54 | Create rules from all templates with "TI map" in the name 55 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' -name '*TI map* 56 | .EXAMPLE 57 | The below example will open an out-grid UI where you can further select specific rules to import 58 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -selectUI 59 | .EXAMPLE 60 | Filter by detection or solution child folder Name. 61 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2EOUmy4P0Rb3yd' -detectionFolderName 'ASimAuthentication','ASimProcess' 62 | .EXAMPLE 63 | Filter by severity of alert rule templates 64 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -severity 'High','Medium' 65 | .EXAMPLE 66 | Filter by severity and tactic of alert rule templates 67 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -severity 'High','Medium' -tactic 'CredentialAccess' 68 | .EXAMPLE 69 | Run in report only mode 70 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -detectionFolderName 'ASimAuthentication','ASimProcess' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -reportOnly 71 | 72 | $rules | Select name, severity, tactics, techniques, requiredDataConnectors, templateURL 73 | .NOTES 74 | Author: seanstark-ms 75 | Website: https://starkonsec.medium.com/ 76 | Link to GitHub Source: https://github.com/seanstark/sentinel-tools/tree/main/analytics_rules 77 | Requires PowerShell Version 7.0 and above 78 | Requires PowerShell Modules: 'PowerShellForGitHub', 'Az.Accounts', 'Az.SecurityInsights', 'powershell-yaml' 79 | #> 80 | 81 | param( 82 | [Parameter(Mandatory=$true)] 83 | [string]$subscriptionId, 84 | 85 | [Parameter(Mandatory=$true)] 86 | [string]$resourceGroupName, 87 | 88 | [Parameter(Mandatory=$true)] 89 | [string]$workspaceName, 90 | 91 | [Parameter(Mandatory=$true, 92 | HelpMessage='Specifiy your GitHub personal access token that has public_repo to the Microsoft Azure Organization')] 93 | [string]$githubToken, 94 | 95 | [Parameter(Mandatory=$false, 96 | HelpMessage='Specifiy the API version of the Microsoft.SecurityInsights/alertRules endpoint')] 97 | [string]$apiVersion = '2021-10-01-preview', 98 | 99 | [Parameter(Mandatory=$false, 100 | HelpMessage='Enter a name to filter on')] 101 | [string]$name, 102 | 103 | [Parameter(Mandatory=$false, 104 | HelpMessage='Enter one or more rule severities separated by commas to filter on')] 105 | [string[]]$severity, 106 | 107 | [Parameter(Mandatory=$false, 108 | HelpMessage='Enter one or more detection child folder names separated by commas to filter on')] 109 | [string[]]$detectionFolderName, 110 | 111 | [Parameter(Mandatory=$false, 112 | HelpMessage='Enter one or more techniques separated by commas to filter on')] 113 | [string[]]$techniques, 114 | 115 | [Parameter(Mandatory=$false, 116 | HelpMessage='Enter one or more tactics separated by commas to filter on')] 117 | [string[]]$tactics, 118 | 119 | [Parameter(Mandatory=$false, 120 | HelpMessage='Enter one or more dataconnector names separated by commas to filter on')] 121 | [string[]]$dataConnector, 122 | 123 | [Parameter(Mandatory=$false, 124 | HelpMessage='Enter one or more dataconnector names separated by commas to filter on')] 125 | [string[]]$dataType, 126 | 127 | [Parameter(Mandatory=$false, 128 | HelpMessage='Enter one or more dataconnector names separated by commas to filter on')] 129 | [string[]]$queryFilter, 130 | 131 | [Parameter(Mandatory=$false, 132 | HelpMessage='Enter one or more tag names separated by commas to filter on')] 133 | [string[]]$tag, 134 | 135 | [Parameter(Mandatory=$false, 136 | HelpMessage='Specify if the rule will be created in an enabled state. By default this is set to true')] 137 | [boolean]$enable = $true, 138 | 139 | [Parameter(Mandatory=$false, 140 | HelpMessage='Specify if you only want to report on alert rule templates that will be enabled')] 141 | [switch]$reportOnly, 142 | 143 | [Parameter(Mandatory=$false, 144 | HelpMessage='Open a UI to further select which Analytics Rules you want to import')] 145 | [switch]$selectUI 146 | ) 147 | 148 | #Requires -Version 7.0 149 | 150 | # Check for required modules 151 | $requiredModules = 'PowerShellForGitHub', 'Az.Accounts', 'Az.SecurityInsights', 'powershell-yaml' 152 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 153 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 154 | ForEach ($module in $modulesToInstall){ 155 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 156 | Install-Module $module -force 157 | } 158 | 159 | # Sentinel GitHub Repo URI and Analytic Rule Root Path 160 | $sentinelGitHuburi = 'https://github.com/Azure/Azure-Sentinel' 161 | $sentinelGitHubPaths = 'Detections','Solutions' 162 | 163 | #Setup GitHubAuthentication 164 | [pscredential]$gitHubCred = New-Object System.Management.Automation.PSCredential ('dummy', $(ConvertTo-SecureString $githubToken -AsPlainText -Force)) 165 | Set-GitHubConfiguration -DisableLogging -DisableTelemetry 166 | Set-GitHubAuthentication -Credential $gitHubCred 167 | 168 | # Auth to Azure 169 | If(!(Get-AzContext)){ 170 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 171 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 172 | } 173 | 174 | #Set context to the subscriptionid 175 | Set-AzContext -Subscription $subscriptionId -WarningAction SilentlyContinue -ErrorAction SilentlyContinue | Out-Null 176 | 177 | #Get the current Bearer Token 178 | $azToken = (Get-AzAccessToken).Token 179 | 180 | # Get all anlaytics rules from github 181 | $detectionFolders = @() 182 | ForEach ($sentinelGitHubPath in $sentinelGitHubPaths){ 183 | If ($sentinelGitHubPath -like 'Solutions'){ 184 | $solutions = Get-GitHubContent -Uri $sentinelGitHuburi -Path 'Solutions' -MediaType Object | Select -ExpandProperty entries | Where type -like 'dir' | Select name 185 | ForEach ($solution in $solutions.name){ 186 | $detectionFolders += Get-GitHubContent -Uri $sentinelGitHuburi -Path "Solutions/$solution" -MediaType Object | Select -ExpandProperty entries | Where-Object {$_.type -like 'dir' -and $_.name -like 'Analytic Rules'} | Select @{Name = 'name'; Expression = {$solution}}, path 187 | } 188 | }else{ 189 | $detectionFolders += Get-GitHubContent -Uri $sentinelGitHuburi -Path $sentinelGitHubPath -MediaType Object | Select -ExpandProperty entries | Where type -like 'dir' | Select name, path 190 | } 191 | } 192 | 193 | # Filter on specific detection folders if detectionFolderName parameter is defined 194 | If($detectionFolderName){ 195 | $detectionFolders = $detectionFolders | where name -in $detectionFolderName 196 | } 197 | 198 | # Get all created by template analytic rules in Sentinel 199 | $existingRules = Get-AzSentinelAlertRule -resourceGroupName $resourceGroupName -workspaceName $workspaceName | Where-Object kind -like 'Scheduled' 200 | 201 | # Iterate through each detection folder in github and build an array of psobjects of the yaml files 202 | Write-Host 'Iterating through each detection folder to build an index of each analytic rule template. This will take a few minutes..' -ForegroundColor Yellow 203 | $alertRuleTemplates = @() 204 | ForEach ($detectionFolder in $($detectionFolders | Select -ExpandProperty path)){ 205 | 206 | $yamlFiles = Get-GitHubContent -Uri $sentinelGitHuburi -Path $detectionFolder -MediaType Object | Select -ExpandProperty entries | Where name -Like '*.yaml' 207 | 208 | ForEach ($yamlFile in $yamlFiles){ 209 | 210 | $alertRuleTemplate = ConvertFrom-Yaml (Invoke-RestMethod -uri $yamlFile.download_url) 211 | 212 | If ($alertRuleTemplate.kind -like 'Scheduled'){ 213 | 214 | Write-Verbose ('Found Scheduled Rule Template: {0}, adding to index' -f $alertRuleTemplate.name) 215 | 216 | $alertRuleTemplates += ([PSCustomObject]@{ 217 | id = $alertRuleTemplate.id 218 | name = $alertRuleTemplate.name 219 | kind = 'Scheduled' 220 | templateURL = $yamlFile.download_url 221 | templateFolder = $detectionFolder 222 | severity = $alertRuleTemplate.severity 223 | requiredDataConnectors = $alertRuleTemplate.requiredDataConnectors.connectorId -join ',' 224 | techniques = $alertRuleTemplate.relevantTechniques -join ',' 225 | tactics = $alertRuleTemplate.tactics -join ',' 226 | properties = ($alertRuleTemplate | Select-object -ExcludeProperty name, id, kind) 227 | }) 228 | } 229 | } 230 | } 231 | 232 | # This function is used to dynamically create an inclusive filter set when multiple filter parameters are defined 233 | function check-filterScript { 234 | param( 235 | [string]$filterToAdd 236 | ) 237 | if ($filterScript){ 238 | $newFilter = "$filterScript -and $filterToAdd" 239 | }else{ 240 | $newFilter = $filterToAdd 241 | } 242 | [scriptblock]::Create($newFilter) 243 | } 244 | 245 | # This function is used to compare filter parameters with rule template properties that are an object "List" or Object[] type and contain multiple objects. 246 | # These are present in relevantTechniques, tactics, dataTypes, and requiredDataConnectors properties 247 | function match-Lists { 248 | param( 249 | [string[]]$filterParameter, 250 | $list 251 | ) 252 | $matched = $false 253 | 254 | if($list){ 255 | $list | ForEach-Object{ 256 | if ($_ -in $filterParameter){ 257 | $matched = $true 258 | } 259 | } 260 | } 261 | 262 | $matched 263 | } 264 | 265 | # Build dynamic filters on rule severities, relevantTechniques, tactics, and data connectors if parameters are defined 266 | $filterScript = $null 267 | If($severity){ 268 | $filterScript = check-filterScript -filterToAdd ('$_.properties.severity -in {0}' -f $severity) 269 | } 270 | If($name){ 271 | $filterScript = check-filterScript -filterToAdd ('$_.name -like "{0}"' -f $name) 272 | } 273 | If($techniques){ 274 | $filterScript = check-filterScript -filterToAdd ('$(match-Lists -filterParameter $techniques -list $_.properties.relevantTechniques) -eq $true') 275 | } 276 | If($tactics){ 277 | $filterScript = check-filterScript -filterToAdd ('$(match-Lists -filterParameter $tactics -list $_.properties.tactics) -eq $true') 278 | } 279 | If($dataConnector){ 280 | $filterScript = check-filterScript -filterToAdd ('$(match-Lists -filterParameter $dataConnector -list $_.properties.requiredDataConnectors.connectorId) -eq $true') 281 | } 282 | If($dataType){ 283 | $filterScript = check-filterScript -filterToAdd ('$(match-Lists -filterParameter $dataType -list $_.properties.requiredDataConnectors.dataTypes) -eq $true') 284 | } 285 | If($queryFilter){ 286 | $filterScript = check-filterScript -filterToAdd ('$($_.properties.query | Select-String $queryFilter) -ne $null') 287 | } 288 | If($tag){ 289 | $filterScript = check-filterScript -filterToAdd ('$(match-Lists -filterParameter $tag -list $_.properties.tags) -eq $true') 290 | } 291 | 292 | #Filter templates based on defined filter parameters 293 | If ($filterScript){ 294 | Write-Verbose ('Filters that will be applied: {0}' -f $filterScript) 295 | $alertRuleTemplates = $alertRuleTemplates | where -FilterScript $filterScript 296 | } 297 | 298 | Write-Host ('Found a total of {0} Rule Templates from GitHub' -f $alertRuleTemplates.count) -ForegroundColor Cyan 299 | 300 | # Find which rules need to be created that don't already exist 301 | $rulesToCreate = $alertRuleTemplates | Where-object {$_.id -notin $existingRules.AlertRuleTemplateName -and $_.name -notin $existingRules.DisplayName} 302 | 303 | #Open the selection UI to further select specific rules to import 304 | if ($selectUI){ 305 | Write-Host ('Opening UI for Rule Selection' -f $rulesToCreate.count) -ForegroundColor Cyan 306 | $rulesToCreate = $rulesToCreate | Out-GridView -PassThru -Title 'Select Rules to be Imported (For Multi-Select use CTRL)' | Select * 307 | } 308 | 309 | Write-Host ('Found a total of {0} Rule Templates to Enable' -f $rulesToCreate.count) -ForegroundColor Cyan 310 | 311 | If ($reportOnly){ 312 | Write-Host 'Report Only Mode: Outputing rules that will be created' -ForegroundColor Yellow 313 | $rulesToCreate 314 | } 315 | 316 | #Updated Yaml Trigger Operators Mapping 317 | $triggerOperators = @{ 318 | gt = 'GreaterThan' 319 | eq = 'Equal' 320 | lt = 'LessThan' 321 | ne = 'NotEqual' 322 | } 323 | 324 | #ISO 8601 Time Converstion Function 325 | Function ConvertFrom-YAMLTimeFormat { 326 | Param ( 327 | [Parameter(Mandatory = $true)] 328 | [string]$timespan 329 | ) 330 | 331 | if($timespan.contains("d")) 332 | { 333 | $result = "P$timespan".ToUpper() 334 | } 335 | if($timespan.contains("h")) 336 | { 337 | $result = "PT$timespan".ToUpper() 338 | } 339 | if($timespan.contains("m")) 340 | { 341 | $result = "PT$timespan".ToUpper() 342 | } 343 | $result 344 | } 345 | 346 | If ($reportOnly -eq $false){ 347 | 348 | # Define Authorization headers for the Microsoft.SecurityInsights/alertRules API 349 | $headers = @{ 350 | Authorization="Bearer $azToken" 351 | } 352 | 353 | # Create each rule from the template 354 | ForEach ($rule in $rulesToCreate){ 355 | 356 | # Build an updated object for each yaml file to interact properly with the Microsoft.SecurityInsights/alertRules API endpoint. 357 | $newRuleGuid = (New-Guid).Guid 358 | $rule | Add-Member -NotePropertyName 'type' -NotePropertyValue 'Microsoft.SecurityInsights/alertRules' -Force 359 | $rule.properties | Add-Member -NotePropertyName 'templateVersion' -NotePropertyValue $rule.properties.version -Force 360 | $rule.properties| Add-Member -NotePropertyName 'alertRuleTemplateName' -NotePropertyValue $rule.id -Force 361 | $rule.properties| Add-Member -NotePropertyName 'suppressionDuration' -NotePropertyValue 'PT5H' -Force 362 | $rule.properties| Add-Member -NotePropertyName 'suppressionEnabled' -NotePropertyValue $false -Force 363 | $rule.properties| Add-Member -NotePropertyName 'displayName' -NotePropertyValue $rule.name -Force 364 | $rule.properties| Add-Member -NotePropertyName 'enabled' -NotePropertyValue $enable -Force 365 | $rule.properties.triggerOperator = $triggerOperators[$rule.properties.triggerOperator] 366 | $rule.properties.queryFrequency = ConvertFrom-YAMLTimeFormat $rule.properties.queryFrequency 367 | $rule.properties.queryPeriod = ConvertFrom-YAMLTimeFormat $rule.properties.queryPeriod 368 | $rule.name = $newRuleGuid 369 | $rule.id = "/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)/providers/Microsoft.OperationalInsights/workspaces/$($workspaceName)/providers/Microsoft.SecurityInsights/alertRules/$($newRuleGuid)" 370 | 371 | Write-Host "Attempting to Create Rule: $($rule.properties.displayName)" -ForegroundColor Yellow 372 | 373 | # Create an Analytic Rule from the template using the rest API 374 | try{ 375 | $uri = "https://management.azure.com/subscriptions/$($subscriptionId)/resourceGroups/$($resourceGroupName)/providers/Microsoft.OperationalInsights/workspaces/$($workspaceName)/providers/Microsoft.SecurityInsights/alertRules/$($newRuleGuid)?api-version=$($apiVersion)" 376 | $newRule = Invoke-RestMethod -Uri $uri -Headers $headers -Method Put -Body $($rule | ConvertTo-Json -Depth 5) -ContentType 'application/json' 377 | }catch{ 378 | $outputErrorMessage = ($PSItem.ErrorDetails | ConvertFrom-Json).error.message 379 | $outputErrorCode = ($PSItem.ErrorDetails | ConvertFrom-Json).error.code 380 | Write-Host "Error Creating Rule: $outputErrorMessage" -ForegroundColor Red 381 | }finally{ 382 | $outputObject = New-Object PSObject -Property @{ 383 | ruleName = $rule.properties.displayName 384 | ruleid = $newRule.name 385 | ruletype = $newRule.kind 386 | created = If($outputErrorCode -or $outputErrorMessage){$false}else{$true} 387 | errorCode = $outputErrorCode 388 | errorMessage = $outputErrorMessage 389 | properties = ($newRule | Select-object -ExcludeProperty name, id, kind) 390 | } 391 | } 392 | $outputObject 393 | #Cleanup rule and error variables 394 | Clear-Variable outputErrorMessage, outputErrorCode, outputObject -ErrorAction SilentlyContinue 395 | $error.Clear() 396 | } 397 | } -------------------------------------------------------------------------------- /analytics_rules/export-analyticRuleTemplates.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | You can leverage this script to create export scheduled analytics rules from the analytics rules templates on github https://github.com/Azure/Azure-Sentinel 4 | 5 | .PARAMETER githubToken 6 | Specify the GitHub Access Personal Access Token you created. public_repo access required 7 | 8 | .EXAMPLE 9 | Create rules from all templates 10 | $rules = .\export-analyticRuleTemplates.ps1 -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' 11 | 12 | .NOTES 13 | Author: seanstark-ms 14 | Link to GitHub Source: https://github.com/seanstark/sentinel-tools/tree/main/analytics_rules 15 | Requires PowerShell Version 7.0 and above 16 | Requires PowerShell Modules: 'PowerShellForGitHub', 'powershell-yaml' 17 | #> 18 | 19 | param( 20 | [Parameter(Mandatory=$true, 21 | HelpMessage='Specifiy your GitHub personal access token that has public_repo to the Microsoft Azure Organization')] 22 | [string]$githubToken 23 | ) 24 | 25 | #Requires -Version 7.0 26 | 27 | # Check for required modules 28 | $requiredModules = 'PowerShellForGitHub', 'powershell-yaml' 29 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 30 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 31 | ForEach ($module in $modulesToInstall){ 32 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 33 | Install-Module $module -force 34 | } 35 | 36 | # Sentinel GitHub Repo URI and Analytic Rule Root Path 37 | $sentinelGitHuburi = 'https://github.com/Azure/Azure-Sentinel' 38 | $sentinelGitHubPaths = 'Detections','Solutions' 39 | 40 | #Setup GitHubAuthentication 41 | [pscredential]$gitHubCred = New-Object System.Management.Automation.PSCredential ('dummy', $(ConvertTo-SecureString $githubToken -AsPlainText -Force)) 42 | Set-GitHubConfiguration -DisableLogging -DisableTelemetry 43 | Set-GitHubAuthentication -Credential $gitHubCred 44 | 45 | # Get all anlaytics rules from github 46 | Write-Host 'Iterating through each detection folders to build directory structure. This will take a few minutes..' -ForegroundColor Yellow 47 | $detectionFolders = @() 48 | ForEach ($sentinelGitHubPath in $sentinelGitHubPaths){ 49 | If ($sentinelGitHubPath -like 'Solutions'){ 50 | $solutions = Get-GitHubContent -Uri $sentinelGitHuburi -Path 'Solutions' -MediaType Object | Select -ExpandProperty entries | Where type -like 'dir' | Select name 51 | ForEach ($solution in $solutions.name){ 52 | $detectionFolders += Get-GitHubContent -Uri $sentinelGitHuburi -Path "Solutions/$solution" -MediaType Object | Select -ExpandProperty entries | Where-Object {$_.type -like 'dir' -and $_.name -like 'Analytic Rules'} | Select @{Name = 'name'; Expression = {$solution}}, path 53 | } 54 | }else{ 55 | $detectionFolders += Get-GitHubContent -Uri $sentinelGitHuburi -Path $sentinelGitHubPath -MediaType Object | Select -ExpandProperty entries | Where type -like 'dir' | Select name, path 56 | } 57 | } 58 | 59 | # Filter on specific detection folders if detectionFolderName parameter is defined 60 | If($detectionFolderName){ 61 | $detectionFolders = $detectionFolders | where name -in $detectionFolderName 62 | } 63 | 64 | # Iterate through each detection folder in github and build an array of psobjects of the yaml files 65 | Write-Host 'Iterating through each detection folder to build an index of each analytic rule template. This will take a few minutes..' -ForegroundColor Yellow 66 | $alertRuleTemplates = @() 67 | ForEach ($detectionFolder in $($detectionFolders | Select -ExpandProperty path)){ 68 | 69 | $yamlFiles = Get-GitHubContent -Uri $sentinelGitHuburi -Path $detectionFolder -MediaType Object | Select -ExpandProperty entries | Where name -Like '*.yaml' 70 | 71 | ForEach ($yamlFile in $yamlFiles){ 72 | 73 | $alertRuleTemplate = ConvertFrom-Yaml (Invoke-RestMethod -uri $yamlFile.download_url) 74 | 75 | If ($alertRuleTemplate.kind -like 'Scheduled'){ 76 | 77 | Write-Verbose ('Found Scheduled Rule Template: {0}, adding to index' -f $alertRuleTemplate.name) 78 | 79 | $alertRuleTemplates += ([PSCustomObject]@{ 80 | id = $alertRuleTemplate.id 81 | name = $alertRuleTemplate.name 82 | description = $alertRuleTemplate.description 83 | kind = 'Scheduled' 84 | templateURL = $yamlFile.download_url 85 | templateFolder = $detectionFolder 86 | severity = $alertRuleTemplate.severity 87 | requiredDataConnectors = $alertRuleTemplate.requiredDataConnectors.connectorId -join ',' | out-string 88 | techniques = $alertRuleTemplate.relevantTechniques -join ',' | out-string 89 | tactics = $alertRuleTemplate.tactics -join ',' | out-string 90 | query = $alertRuleTemplate.query 91 | entityMappings = $alertRuleTemplate.entityMappings | ConvertTo-Json -Depth 4 | out-string 92 | }) 93 | } 94 | } 95 | } 96 | Write-Host "Found $($alertRuleTemplates.count) analytic rules" -ForegroundColor Yellow 97 | $alertRuleTemplates 98 | -------------------------------------------------------------------------------- /analytics_rules/readme.md: -------------------------------------------------------------------------------- 1 | # Exporting Scheduled Analytics Rules From Templates 2 | You can export all rules from from the [Sentinel Github rule template repository](https://github.com/Azure/Azure-Sentinel) using the [export-analyticRuleTemplates.ps1](/analytics_rules/export-analyticRuleTemplates.ps1) script 3 | 4 | - [Configuration Requirements](#configuration-requirements) 5 | * [Github Personal Access Token](#github-personal-access-token) 6 | - [Example](#example) 7 | 8 | ## Example 9 | ```powershell 10 | $rules = .\export-analyticRuleTemplates.ps1 -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' 11 | 12 | $rules | Export-Csv C:\analyticrules.csv 13 | 14 | ``` 15 | 16 | # Creating Scheduled Analytics Rules From Templates 17 | 18 | - [Overview](#overview) 19 | - [Features](#features) 20 | - [Known Limitations](#known-limitations) 21 | - [Configuration Requirements](#configuration-requirements) 22 | * [Github Personal Access Token](#github-personal-access-token) 23 | * [Required PowerShell Modules](#required-powershell-modules) 24 | * [Required Sentinel Roles](#required-sentinel-roles) 25 | - [Running the Script](#running-the-script) 26 | + [Create rules from all templates](#create-rules-from-all-templates) 27 | + [Create rules from all templates in a disabled state](#create-rules-from-all-templates-in-a-disabled-state) 28 | + [Create rules from all templates with "TI map" in the name](#create-rules-from-all-templates-with-"ti-map"-in-the-name) 29 | + [Open a UI where you can further select specific rules to import](#open-a-ui-where-you-can-further-select-specific-rules-to-import) 30 | + [Run in report only mode](#run-in-report-only-mode) 31 | + [Filter by detection child folder name](#filter-by-detection-child-folder-name) 32 | + [Filter by severity of alert rule templates](#filter-by-severity-of-alert-rule-templates) 33 | + [Filter by severity and tactics of alert rule templates](#filter-by-severity-and-tactics-of-alert-rule-templates) 34 | + [Filter by tags](#filter-by-tags) 35 | 36 | ## Overview 37 | [**create-scheduledRuleFromTemplate.ps1**](/analytics_rules/create-scheduledRuleFromTemplate.ps1) is a PowerShell script you can leverage to import (create) multiple scheduled analytics rules from the [Sentinel Github rule template repository](https://github.com/Azure/Azure-Sentinel/tree/master/Detections) and from Analytics rules in the [Solutions folder](https://github.com/Azure/Azure-Sentinel/tree/master/Solutions) 38 | 39 | This script was written to account for current limitations when leveraging the **AzSentinel** or **Az.SecurityInsights** PowerShell modules. Most of which are related to an incomplete set of properties being returned such as tactics and techniques from the API endpoints. 40 | 41 | ## Features 42 | 43 | - Create multiple scheduled analytics rules from rule templates 44 | - Filter rule templates on name, severity, tactics, techniques, tags, datatypes, queries, and data connectors 45 | - Run in report only mode to output templates based on the filters you defined 46 | - Create rules from templates in an enabled or disabled state 47 | - Open an out-gridview UI to further select specific rules to import 48 | 49 | ## Known Limitations 50 | 51 | - Associated tables in the rule query need to exist first for the rule to be created. Tables are generally created when you start ingesting data. If the table does not exist the rule creation will fail during the script run 52 | - YAML files in the github repo may have incorrect query column to entity mappings defined. The rule creation will fail during the script run. If you run across either submit an issue via github on the YAML file or fork the github repo and submit a pull request - https://github.com/Azure/Azure-Sentinel#contributing 53 | - A fair number of rule templates do not have values for required data connectors. Be aware when using the dataconnector filter parameter you may not get a complete list of rules that leverage associated tables 54 | - YAML file definitions continue to evolve, new attributes such as tags do not persist across all rule templates. Be aware when using some of these filters you may not get an accurate result 55 | 56 | ## Configuration Requirements 57 | 58 | ### Github Personal Access Token 59 | You will need to setup a GitHub **personal access token** in order for the PowerShell script to gather the rule template details. This is required to avoid GitHub API limits. 60 | 61 | 1. Navigate to https://github.com/settings/tokens/new 62 | 2. Generate a new token with the public_repo scope 63 | 3. I would also recommend setting the expiration to 7 days 64 | 4. Copy the generated token value for use the **-githubToken** parameter 65 | 66 | > ![GitHub PAT](/images/github_pat.png) 67 | 68 | 69 | Depending on your organizaiton affiliation you may need to authorize the token for use with SSO to the Azure organization 70 | 71 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/9d6dacac-fc11-421c-9b90-28fc80863ddb) 72 | 73 | ### Required PowerShell Modules 74 | The script will check and install any missing modules. For reference the below is required 75 | - PowerShellForGitHub 76 | - Az.Accounts 77 | - Az.SecurityInsights 78 | - powershell-yaml 79 | 80 | ### Required Sentinel Roles 81 | - Microsoft Sentinel Contributor 82 | 83 | ## Running the Script 84 | Below are some examples on running the script. In the examples below the script output is assigned to a variable $rules. 85 | I would recommend assigning the script output to a variable to easily review the results as some rule creations may fail. 86 | 87 | ```powershell 88 | $rules | Where created -eq $false | Select ruleName, created, errorCode, errorMessage 89 | 90 | $rules | Where created -eq $true 91 | 92 | ``` 93 | 94 | > Rules will be created in an **enabled** state by default 95 | 96 | > Note: `-githubToken` example is not a valid token 97 | 98 | ### Create rules from all templates 99 | ```powershell 100 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' 101 | ``` 102 | ### Create rules from all templates in a disabled state 103 | ```powershell 104 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' -enabled $false 105 | ``` 106 | ### Create rules from all templates with "TI map" in the name 107 | ```powershell 108 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFB2pTrEEOUmy4P0Rb3yd' -name '*TI map* 109 | ``` 110 | ### Open a UI where you can further select specific rules to import 111 | The below example will open an out-grid UI where you can further select specific rules to import 112 | ```powershell 113 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -selectUI 114 | ``` 115 | ### Run in report only mode 116 | ```powershell 117 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -reportOnly 118 | 119 | $rules | Select name, severity, tactics, techniques, requiredDataConnectors, templateURL 120 | ``` 121 | ### Filter by detection or solution child folder Name 122 | ```powershell 123 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2EOUmy4P0Rb3yd' -detectionFolderName 'ASimAuthentication','ASimProcess' 124 | ``` 125 | ### Filter by severity of alert rule templates 126 | ```powershell 127 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2EOUmy4P0Rb3yd' -detectionFolderName 'ASimAuthentication','ASimProcess' 128 | ``` 129 | ### Filter by severity and tactics of alert rule templates 130 | ```powershell 131 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -severity 'High','Medium' 132 | ``` 133 | ### Filter by tags 134 | The below example returns all templates tagged with Log4j 135 | ```powershell 136 | $rules = .\create-scheduledRuleFromTemplate.ps1 -subscriptionId 'ada06e68-375e-4564-be3a-c6cacebf41c5' -resourceGroupName 'sentinel-prd' -workspaceName 'sentinel-prd' -githubToken 'ghp_ECgzFoyPsbSKrFoK5B2pOUmy4P0Rb3yd' -tag 'Log4j' 137 | ``` 138 | -------------------------------------------------------------------------------- /data_collection_rules/logstash-syslog-dcr.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dataCollectionRuleName": { 6 | "type": "String", 7 | "defaultValue": "logstash-sentinel", 8 | "metadata": { 9 | "description": "Specify the name of the Data Collection Rule to create." 10 | } 11 | }, 12 | "location": { 13 | "defaultValue": "", 14 | "type": "String", 15 | "metadata": { 16 | "description": "Specify the location in which to create the Data Collection Rule." 17 | } 18 | }, 19 | "workspaceResourceId": { 20 | "type": "String", 21 | "metadata": { 22 | "description": "Specify the Azure resource ID of the Log Analytics workspace to use." 23 | } 24 | }, 25 | "dataCollectionEndpointResourceId": { 26 | "type": "String", 27 | "metadata": { 28 | "description": "Specify the Azure resource ID of the Data Collection Endpoint to use." 29 | } 30 | }, 31 | "transformKql": { 32 | "defaultValue": "source | project TimeGenerated = ls_timestamp, HostName = hostname, Facility = facility, SeverityLevel = severity, SyslogMessage = message, ProcessID = pid, ProcessName = process, Computer = hostname, HostIP = ip, EventTime = timestamp, SourceSystem = service", 33 | "type": "String", 34 | "metadata": { 35 | "description": "The KQL statement to transform the data on ingest. The TimeGenerated datetime field is required" 36 | } 37 | } 38 | }, 39 | "resources": [ 40 | { 41 | "type": "Microsoft.Insights/dataCollectionRules", 42 | "apiVersion": "2021-09-01-preview", 43 | "name": "[parameters('dataCollectionRuleName')]", 44 | "location": "[parameters('location')]", 45 | "properties": { 46 | "dataCollectionEndpointId": "[parameters('dataCollectionEndpointResourceId')]", 47 | "streamDeclarations": { 48 | "Custom-Microsoft-Syslog": { 49 | "columns": [ 50 | { 51 | "name": "ls_timestamp", 52 | "type": "datetime" 53 | }, 54 | { 55 | "name": "hostname", 56 | "type": "string" 57 | }, 58 | { 59 | "name": "facility", 60 | "type": "string" 61 | }, 62 | { 63 | "name": "severity", 64 | "type": "string" 65 | }, 66 | { 67 | "name": "message", 68 | "type": "string" 69 | }, 70 | { 71 | "name": "pid", 72 | "type": "int" 73 | }, 74 | { 75 | "name": "process", 76 | "type": "string" 77 | }, 78 | { 79 | "name": "ip", 80 | "type": "string" 81 | }, 82 | { 83 | "name": "timestamp", 84 | "type": "datetime" 85 | }, 86 | { 87 | "name": "service", 88 | "type": "string" 89 | } 90 | ] 91 | } 92 | }, 93 | "destinations": { 94 | "logAnalytics": [ 95 | { 96 | "workspaceResourceId": "[parameters('workspaceResourceId')]", 97 | "name": "Custom-Microsoft-Syslog-Workspace" 98 | } 99 | ] 100 | }, 101 | "dataFlows": [ 102 | { 103 | "streams": [ 104 | "Custom-Microsoft-Syslog" 105 | ], 106 | "destinations": [ 107 | "Custom-Microsoft-Syslog-Workspace" 108 | ], 109 | "transformKql": "[parameters('transformKql')]", 110 | "outputStream": "Microsoft-Syslog" 111 | } 112 | ] 113 | } 114 | } 115 | ], 116 | "outputs": { 117 | "dataCollectionRuleId": { 118 | "type": "String", 119 | "value": "[resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleName'))]" 120 | } 121 | } 122 | } -------------------------------------------------------------------------------- /data_collection_rules/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Overview 3 | A breif intro to the various tools that reside here. 4 | 5 | > Are you looking for simplier Azure Policies for assigning data collection rules without all the image, region, and publisher policy rules? 6 | Check out [my policy repo](https://github.com/seanstark/Azure-Policy/tree/main/policyDefinitions/monitoring) 7 | 8 | - [update-dcrdatastream](#update-dcrdatastream) 9 | 10 | ## update-dcrdatastream 11 | This script will update a data collection rule to send events to the SecurityEvents Table. 12 | This was created since the Azure Monitor UI in the Azure Portal currently does not support this for windows based event collection. 13 | > Technically this can be used for any use case in updating the data flows (destination tables) with a DCR. 14 | You just need to specify the -currentDataStream and -newDataStream parameters. (These are not table names, but refer to data streams names) 15 | 16 | ### Usage 17 | 1. Create a data collection rule via Azure Monitor for Windows Events 18 | * https://learn.microsoft.com/en-us/azure/azure-monitor/agents/data-collection-rule-azure-monitor-agent?tabs=portal 19 | 20 | 2. Run the script against the the applicable rule like below 21 | 22 | ``` 23 | .\update-dcrdatastream.ps1 -subscriptionId ada06e68-375e-4210-be3a-c6cacebf41c5 ` 24 | -resourceGroup sentinel-dcrs -ruleName windows-security-events 25 | ``` 26 | ![dcr-view](/images/dcr.gif) 27 | 28 | 3. You can verify the DCR was modified by checking the output of the script. You should see a StatusCode of 200 and the streams updated like below. 29 | 30 | ![dcr-view](/images/dcr-verify.png) 31 | 32 | 4. You can also verify the DCR was updated by checking the data collection rule stream via the Azure Portal 33 | 1. Navigate to [Data Collection Rules](https://portal.azure.com/#view/Microsoft_Azure_Monitoring/AzureMonitoringBrowseBlade/~/dataCollectionRules) in the Azure Portal under Azure Monitor 34 | 2. Select the Data Collection Rule you just updated 35 | 36 | ![dcr-view](/images/dcr-view.png) 37 | 38 | 3. Select JSON View and verify the stream(s) have been updated 39 | 40 | ![dcr-json](/images/dcr-json.png) 41 | -------------------------------------------------------------------------------- /data_collection_rules/update-dcrDataCollectionEndpoint.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update a data collection rule with an associated data collection endpoint 4 | 5 | .PARAMETER subscriptionId 6 | Specify the subscriptionID GUID where your data collection rule resides 7 | 8 | .PARAMETER resourceGroup 9 | Specify the Resource Group Name where your data collection rule resides 10 | 11 | .PARAMETER ruleName 12 | Specify the data collection rule name 13 | 14 | .PARAMETER apiVersion 15 | Optionally you can specify the api version to use for Microsoft.Insights/dataCollectionRules 16 | 17 | .PARAMETER dataCollectionEndpointId 18 | The full Resource ID of the data collection endpoint 19 | 20 | .EXAMPLE 21 | .\update-dcrDataCollectionEndpoint.ps1 -subscriptionId 'ada078449-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' -dataCollectionEndpointId '/subscriptions/ada111e68-375e-4330-be3a-c6caddbf41c5/resourceGroups/data-collection-end/providers/Microsoft.Insights/dataCollectionEndpoints/dce-customlog-westus2' 22 | #> 23 | 24 | param( 25 | [Parameter(Mandatory=$true)] 26 | [string]$subscriptionId, 27 | 28 | [Parameter(Mandatory=$true)] 29 | [string]$resourceGroup, 30 | 31 | [Parameter(Mandatory=$true)] 32 | [string]$ruleName, 33 | 34 | [Parameter(Mandatory=$false)] 35 | [string]$apiVersion = '2022-06-01', 36 | 37 | [Parameter(Mandatory=$true)] 38 | [string]$dataCollectionEndpointId 39 | ) 40 | 41 | $requiredModules = 'Az.Accounts' 42 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 43 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 44 | ForEach ($module in $modulesToInstall){ 45 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 46 | Install-Module $module -force 47 | } 48 | 49 | If(!(Get-AzContext)){ 50 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 51 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 52 | } 53 | 54 | #Get Data Collection Rule 55 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/dataCollectionRules/{2}?api-version={3}' -f $subscriptionId, $resourceGroup, $ruleName, $apiVersion) 56 | $dcr = (Invoke-AzRestMethod -Uri $uri).content | ConvertFrom-Json -Depth 20 57 | 58 | #Update the data collection endpoint 59 | If ($dcr.properties.dataCollectionEndpointId){ 60 | $dcr.properties.dataCollectionEndpointId = $dataCollectionEndpointId 61 | }else{ 62 | $dcr.properties | Add-Member -MemberType NoteProperty -Name 'dataCollectionEndpointId' -Value $dataCollectionEndpointId -Force 63 | } 64 | 65 | $newDCR = $dcr | ConvertTo-Json -Depth 20 66 | 67 | # Update the DCR 68 | Invoke-AzRestMethod -Uri $uri -Method PUT -Payload $newDCR 69 | -------------------------------------------------------------------------------- /data_collection_rules/update-dcrStreamColumns.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update a data collection rule stream declaration columns 4 | 5 | .PARAMETER subscriptionId 6 | Specify the subscriptionID GUID where your data collection rule resides 7 | 8 | .PARAMETER resourceGroup 9 | Specify the Resource Group Name where your data collection rule resides 10 | 11 | .PARAMETER ruleName 12 | Specify the data collection rule name 13 | 14 | .PARAMETER apiVersion 15 | Optionally you can specify the api version to use for Microsoft.Insights/dataCollectionRules 16 | 17 | .PARAMETER streamName 18 | The stream declaration name to update. 19 | 20 | .PARAMETER columnsToAdd 21 | Specify an array of columns to add to the stream. Column names cannot contain spaces. This needs to be in a json formated list, example: '{"name": "Test1", "type": "string"}', '{"name": "Test2", "type": "string"}' 22 | 23 | .PARAMETER columnsToRemove 24 | Specify an array of columns to remove from the stream. This needs to be a list of names, example: 'Message', 'Host' 25 | 26 | .EXAMPLE 27 | .\update-dcrStreamColumns.ps1 -subscriptionId 'ada078449-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' -columnsToAdd '{"name": "Test1", "type": "string"}', '{"name": "Test2", "type": "string"}' 28 | 29 | .EXAMPLE 30 | .\update-dcrStreamColumns.ps1 -subscriptionId 'ada078449-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' -columnsToRemove 'Message', 'Host' 31 | #> 32 | 33 | param( 34 | [Parameter(Mandatory=$true)] 35 | [string]$subscriptionId, 36 | 37 | [Parameter(Mandatory=$true)] 38 | [string]$resourceGroup, 39 | 40 | [Parameter(Mandatory=$true)] 41 | [string]$ruleName, 42 | 43 | [Parameter(Mandatory=$true)] 44 | [string]$streamName, 45 | 46 | [Parameter(Mandatory=$false)] 47 | [string[]]$columnsToAdd, 48 | 49 | [Parameter(Mandatory=$false)] 50 | [string[]]$columnsToRemove, 51 | 52 | [Parameter(Mandatory=$false)] 53 | [string]$apiVersion = '2022-06-01' 54 | ) 55 | 56 | $requiredModules = 'Az.Accounts' 57 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 58 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 59 | ForEach ($module in $modulesToInstall){ 60 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 61 | Install-Module $module -force 62 | } 63 | 64 | If(!(Get-AzContext)){ 65 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 66 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 67 | } 68 | 69 | #Get Data Collection Rule 70 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/dataCollectionRules/{2}?api-version={3}' -f $subscriptionId, $resourceGroup, $ruleName, $apiVersion) 71 | $dcr = (Invoke-AzRestMethod -Uri $uri).content | ConvertFrom-Json -Depth 20 72 | 73 | #Update the data collection endpoint 74 | If (!($dcr.properties.streamDeclarations."$streamName")){ 75 | $json = '{"streamDeclarations": {"' + $streamName + '": {"columns": []}}}' 76 | $dcr.properties = @($dcr.properties) + $($json | ConvertFrom-Json) 77 | } 78 | 79 | If ($dcr.properties.streamDeclarations."$streamName"){ 80 | #Columns to Add 81 | If ($columnsToAdd){ 82 | ForEach ($columnToAdd in $columnsToAdd){ 83 | $dcr.properties.streamDeclarations."$streamName".columns = @($dcr.properties.streamDeclarations."$streamName".columns) + $($columnToAdd | ConvertFrom-Json) 84 | } 85 | } 86 | #Columns to Remove 87 | If ($columnsToRemove) { 88 | Write-Host 'Removing' 89 | $dcr.properties.streamDeclarations."$streamName".columns = @($dcr.properties.streamDeclarations."$streamName".columns | where name -NotIn $columnsToRemove) 90 | } 91 | } 92 | 93 | $newDCR = $dcr | ConvertTo-Json -Depth 20 94 | 95 | # Update the DCR 96 | Invoke-AzRestMethod -Uri $uri -Method PUT -Payload $dcr 97 | 98 | 99 | $dcr = ' 100 | { 101 | "properties": { 102 | "immutableId": "dcr-b995a3a48f34461b9255238f4b1f628a", 103 | "dataCollectionEndpointId": "/subscriptions/ada06e68-375e-4210-be3a-c6cacebf41c5/resourceGroups/data-collection-end/providers/Microsoft.Insights/dataCollectionEndpoints/dce-customlog-westus3", 104 | "streamDeclarations": { 105 | "Custom-Microsoft-Syslog": { 106 | "columns": [ 107 | { 108 | "name": "ls_timestamp", 109 | "type": "datetime" 110 | }, 111 | { 112 | "name": "hostname", 113 | "type": "string" 114 | }, 115 | { 116 | "name": "facility", 117 | "type": "string" 118 | }, 119 | { 120 | "name": "severity", 121 | "type": "string" 122 | }, 123 | { 124 | "name": "message", 125 | "type": "string" 126 | }, 127 | { 128 | "name": "pid", 129 | "type": "int" 130 | }, 131 | { 132 | "name": "process_name", 133 | "type": "string" 134 | }, 135 | { 136 | "name": "ip", 137 | "type": "string" 138 | }, 139 | { 140 | "name": "timestamp", 141 | "type": "datetime" 142 | }, 143 | { 144 | "name": "service", 145 | "type": "string" 146 | } 147 | ] 148 | } 149 | }, 150 | "destinations": { 151 | "logAnalytics": [ 152 | { 153 | "workspaceResourceId": "/subscriptions/ada06e68-375e-4210-be3a-c6cacebf41c5/resourcegroups/sentinel-prd/providers/microsoft.operationalinsights/workspaces/test-new", 154 | "workspaceId": "488aedbf-638c-4844-aeaa-888b16f278db", 155 | "name": "Custom-Microsoft-Syslog-Workspace" 156 | } 157 | ] 158 | }, 159 | "dataFlows": [ 160 | { 161 | "streams": [ 162 | "Custom-Microsoft-Syslog" 163 | ], 164 | "destinations": [ 165 | "Custom-Microsoft-Syslog-Workspace" 166 | ], 167 | "transformKql": "source | project TimeGenerated = ls_timestamp, HostName = hostname, Facility = facility, SeverityLevel = severity, SyslogMessage = message, ProcessID = pid, ProcessName = process_name, Computer = hostname, HostIP = ip, EventTime = timestamp, SourceSystem = service", 168 | "outputStream": "Microsoft-Syslog" 169 | } 170 | ], 171 | "provisioningState": "Succeeded" 172 | }, 173 | "location": "westus3", 174 | "id": "/subscriptions/ada06e68-375e-4210-be3a-c6cacebf41c5/resourceGroups/sentinel-dcrs/providers/Microsoft.Insights/dataCollectionRules/logstash-sentinel-redux", 175 | "name": "logstash-sentinel-redux", 176 | "type": "Microsoft.Insights/dataCollectionRules", 177 | "etag": "\"01009632-0000-4d00-0000-64931dfd0000\"", 178 | "systemData": { 179 | "createdBy": "sean.stark@msdx250797.onmicrosoft.com", 180 | "createdByType": "User", 181 | "createdAt": "2023-06-15T18:56:27.7371654Z", 182 | "lastModifiedBy": "sean.stark@msdx250797.onmicrosoft.com", 183 | "lastModifiedByType": "User", 184 | "lastModifiedAt": "2023-06-21T15:57:48.0839327Z" 185 | } 186 | }' -------------------------------------------------------------------------------- /data_collection_rules/update-dcrTimestampFormat.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update data collection rule timestamps for custom log files 4 | .PARAMETER subscriptionId 5 | Specify the subscriptionID GUID where your data collection rule resides 6 | .PARAMETER resourceGroup 7 | Specify the Resource Group Name where your data collection rule resides 8 | .PARAMETER ruleName 9 | Specify the data collection rule name 10 | .PARAMETER apiVersion 11 | Optionally you can specify the api version to use for Microsoft.Insights/dataCollectionRules 12 | .PARAMETER currentTimeFormat 13 | Optionally you can specify the current rule recordStartTimestampFormat. This configured to ISO 8601 by default 14 | .PARAMETER newTimeFormat 15 | Optionally you can specify the recordStartTimestampFormat to update to. This configured to yyyy-MM-ddTHH:mm:ssK by default 16 | .EXAMPLE 17 | .\update-dcrTimestampFormat.ps1 -subscriptionId 'ada06e68-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' 18 | #> 19 | 20 | param( 21 | [Parameter(Mandatory=$true)] 22 | [string]$subscriptionId, 23 | 24 | [Parameter(Mandatory=$true)] 25 | [string]$resourceGroup, 26 | 27 | [Parameter(Mandatory=$true)] 28 | [string]$ruleName, 29 | 30 | [Parameter(Mandatory=$false)] 31 | [string]$apiVersion = '2021-09-01-preview', 32 | 33 | [Parameter(Mandatory=$false)] 34 | [string]$currentTimeFormat = 'ISO 8601', 35 | 36 | [Parameter(Mandatory=$false)] 37 | [string]$newTimeFormat = 'yyyy-MM-ddTHH:mm:ssK' 38 | ) 39 | 40 | $requiredModules = 'Az.Accounts' 41 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 42 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 43 | ForEach ($module in $modulesToInstall){ 44 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 45 | Install-Module $module -force 46 | } 47 | 48 | If(!(Get-AzContext)){ 49 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 50 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 51 | } 52 | 53 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/dataCollectionRules/{2}?api-version={3}' -f $subscriptionId, $resourceGroup, $ruleName, $apiVersion) 54 | 55 | #Get Data Collection Rule 56 | $dcr = (Invoke-AzRestMethod -Uri $uri).content 57 | 58 | # Update Data Collection Rule Data Flow Streams from Microsoft-Event to Microsoft-SecurityEvent 59 | $newDCR = $dcr.replace(('"recordStartTimestampFormat":"{0}"' -f $currentTimeFormat), ('"recordStartTimestampFormat":"{0}"' -f $newTimeFormat)) 60 | 61 | # Update the DCR 62 | Invoke-AzRestMethod -Uri $uri -Method PUT -Payload $newDCR 63 | -------------------------------------------------------------------------------- /data_collection_rules/update-dcrTransform.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update a data collection rule with an associated kql transform statement 4 | 5 | .PARAMETER subscriptionId 6 | Specify the subscriptionID GUID where your data collection rule resides 7 | 8 | .PARAMETER resourceGroup 9 | Specify the Resource Group Name where your data collection rule resides 10 | 11 | .PARAMETER ruleName 12 | Specify the data collection rule name 13 | 14 | .PARAMETER streamName 15 | The stream declaration name to update. 16 | 17 | .PARAMETER outputStream 18 | The outputStream name, the table name 19 | 20 | .PARAMETER transformKql 21 | Specify the KQL transform statement in a single line 22 | 23 | .PARAMETER apiVersion 24 | Optionally you can specify the api version to use for Microsoft.Insights/dataCollectionRules 25 | 26 | .EXAMPLE 27 | .\update-dcrTransform.ps1 -subscriptionId 'ada078449-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' -transformKql 'source | extend TimeGenerated = todatetime(parse_json(RawData).timestamp) | extend SyslogMessage = RawData"' 28 | #> 29 | 30 | param( 31 | [Parameter(Mandatory=$true)] 32 | [string]$subscriptionId, 33 | 34 | [Parameter(Mandatory=$true)] 35 | [string]$resourceGroup, 36 | 37 | [Parameter(Mandatory=$true)] 38 | [string]$ruleName, 39 | 40 | [Parameter(Mandatory=$true)] 41 | [string]$streamName, 42 | 43 | [Parameter(Mandatory=$true)] 44 | [string]$outputStream, 45 | 46 | [Parameter(Mandatory=$true)] 47 | [string]$transformKql, 48 | 49 | [Parameter(Mandatory=$false)] 50 | [string]$apiVersion = '2022-06-01' 51 | ) 52 | 53 | $requiredModules = 'Az.Accounts' 54 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 55 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 56 | ForEach ($module in $modulesToInstall){ 57 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 58 | Install-Module $module -force 59 | } 60 | 61 | If(!(Get-AzContext)){ 62 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 63 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 64 | } 65 | 66 | #Get Data Collection Rule 67 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/dataCollectionRules/{2}?api-version={3}' -f $subscriptionId, $resourceGroup, $ruleName, $apiVersion) 68 | $dcr = (Invoke-AzRestMethod -Uri $uri).content | ConvertFrom-Json -Depth 20 69 | 70 | #Update the data collection rule transformation 71 | If ($dcr.properties.dataFlows | where streams -eq $streamName){ 72 | If (($dcr.properties.dataFlows | where streams -eq $streamName).transformKql){ 73 | ($dcr.properties.dataFlows | where streams -eq $streamName).transformKql = $transformKql 74 | }else{ 75 | ($dcr.properties.dataFlows | where streams -eq $streamName) | Add-Member -MemberType NoteProperty -Name 'transformKql' -Value $transformKql -Force 76 | ($dcr.properties.dataFlows | where streams -eq $streamName) | Add-Member -MemberType NoteProperty -Name 'outputStream' -Value $outputStream -Force 77 | } 78 | } 79 | 80 | $newDCR = $dcr | ConvertTo-Json -Depth 20 81 | 82 | # Update the DCR 83 | Invoke-AzRestMethod -Uri $uri -Method PUT -Payload $newDCR -------------------------------------------------------------------------------- /data_collection_rules/update-dcrdatastream.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update data collection rule data streams destinations 4 | 5 | .PARAMETER subscriptionId 6 | Specify the subscriptionID GUID where your data collection rule resides 7 | 8 | .PARAMETER resourceGroup 9 | Specify the Resource Group Name where your data collection rule resides 10 | 11 | .PARAMETER ruleName 12 | Specify the data collection rule name 13 | 14 | .PARAMETER apiVersion 15 | Optionally you can specify the api version to use for Microsoft.Insights/dataCollectionRules 16 | 17 | .PARAMETER currentDataStream 18 | Optionally you can specify the current rule data stream name. This configured to Microsoft-Event by default 19 | 20 | .PARAMETER newDataStream 21 | Optionally you can specify the data stream name to update to. This configured to Microsoft-SecurityEvent by default 22 | 23 | .EXAMPLE 24 | .\update-dcrdatastream.ps1 -subscriptionId 'ada06e68-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' 25 | #> 26 | 27 | param( 28 | [Parameter(Mandatory=$true)] 29 | [string]$subscriptionId, 30 | 31 | [Parameter(Mandatory=$true)] 32 | [string]$resourceGroup, 33 | 34 | [Parameter(Mandatory=$true)] 35 | [string]$ruleName, 36 | 37 | [Parameter(Mandatory=$false)] 38 | [string]$apiVersion = '2021-09-01-preview', 39 | 40 | [Parameter(Mandatory=$false)] 41 | [string]$currentDataStream = 'Microsoft-Event', 42 | 43 | [Parameter(Mandatory=$false)] 44 | [string]$newDataStream = 'Microsoft-SecurityEvent' 45 | ) 46 | 47 | $requiredModules = 'Az.Accounts' 48 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 49 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 50 | ForEach ($module in $modulesToInstall){ 51 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 52 | Install-Module $module -force 53 | } 54 | 55 | If(!(Get-AzContext)){ 56 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 57 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 58 | } 59 | 60 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/dataCollectionRules/{2}?api-version={3}' -f $subscriptionId, $resourceGroup, $ruleName, $apiVersion) 61 | 62 | #Get Data Collection Rule 63 | $dcr = (Invoke-AzRestMethod -Uri $uri).content 64 | 65 | # Update Data Collection Rule Data Flow Streams from Microsoft-Event to Microsoft-SecurityEvent 66 | $newDCR = $dcr.replace(('"streams":["{0}"]' -f $currentDataStream), ('"streams":["{0}"]' -f $newDataStream)) 67 | 68 | # Update the DCR 69 | Invoke-AzRestMethod -Uri $uri -Method PUT -Payload $newDCR 70 | -------------------------------------------------------------------------------- /data_collection_rules/update-dcrworkspace.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will update a data collection rule destination log analytics workspace 4 | 5 | .PARAMETER subscriptionId 6 | Specify the subscriptionID GUID where your data collection rule resides 7 | 8 | .PARAMETER resourceGroup 9 | Specify the Resource Group Name where your data collection rule resides 10 | 11 | .PARAMETER ruleName 12 | Specify the data collection rule name 13 | 14 | .PARAMETER apiVersion 15 | Optionally you can specify the api version to use for Microsoft.Insights/dataCollectionRules 16 | 17 | .PARAMETER currentWorkspaceId 18 | The currently configured log analytics workspace Id 19 | 20 | .PARAMETER newWorkspaceResourceId 21 | The full Resource Id of the new log analytics workspace change to 22 | 23 | .EXAMPLE 24 | .\update-dcrworkspace.ps1 -subscriptionId 'ada078449-375e-4210-be3a-c6cacebf41c5' -resourceGroup 'sentinel-dcrs' -ruleName 'windows-events' -currentWorkspaceId 'b6222115-73bc-4c99-b795-4560c061aced' -newWorkspaceResourceId '/subscriptions/166c8347-0480-4aa7-b984-75f0fda42c69/resourceGroups/sentinel/providers/Microsoft.OperationalInsights/workspaces/sentinel' 25 | #> 26 | 27 | param( 28 | [Parameter(Mandatory=$true)] 29 | [string]$subscriptionId, 30 | 31 | [Parameter(Mandatory=$true)] 32 | [string]$resourceGroup, 33 | 34 | [Parameter(Mandatory=$true)] 35 | [string]$ruleName, 36 | 37 | [Parameter(Mandatory=$false)] 38 | [string]$apiVersion = '2021-04-01', 39 | 40 | [Parameter(Mandatory=$true)] 41 | [string]$currentWorkspaceId, 42 | 43 | [Parameter(Mandatory=$true)] 44 | [string]$newWorkspaceResourceId 45 | ) 46 | 47 | $requiredModules = 'Az.Accounts' 48 | $availableModules = Get-Module -ListAvailable -Name $requiredModules 49 | $modulesToInstall = $requiredModules | where-object {$_ -notin $availableModules.Name} 50 | ForEach ($module in $modulesToInstall){ 51 | Write-Host "Installing Missing PowerShell Module: $module" -ForegroundColor Yellow 52 | Install-Module $module -force 53 | } 54 | 55 | If(!(Get-AzContext)){ 56 | Write-Host ('Connecting to Azure Subscription: {0}' -f $subscriptionId) -ForegroundColor Yellow 57 | Connect-AzAccount -Subscription $subscriptionId | Out-Null 58 | } 59 | 60 | #Get the new workspace Id 61 | $uri = ('https://management.azure.com{0}?api-version=2021-12-01-preview' -f $newWorkspaceResourceId) 62 | 63 | $newWorkspaceId = ((Invoke-AzRestMethod -Uri $uri).content | ConvertFrom-Json).properties.customerId 64 | 65 | $uri = ('https://management.azure.com/subscriptions/{0}/resourceGroups/{1}/providers/Microsoft.Insights/dataCollectionRules/{2}?api-version={3}' -f $subscriptionId, $resourceGroup, $ruleName, $apiVersion) 66 | 67 | #Get Data Collection Rule 68 | $dcr = (Invoke-AzRestMethod -Uri $uri).content | ConvertFrom-Json -Depth 20 69 | 70 | #Update to the new workspace 71 | $destName = ($dcr.properties.destinations.logAnalytics | where workspaceId -Like $currentWorkspaceId).name 72 | $newDestName = 'la--{0}' -f $(get-random) 73 | ($dcr.properties.destinations.logAnalytics | where workspaceId -Like $currentWorkspaceId).workspaceResourceId = $newWorkspaceResourceId 74 | ($dcr.properties.destinations.logAnalytics | where workspaceId -Like $currentWorkspaceId).name = $newDestName 75 | ($dcr.properties.destinations.logAnalytics | where workspaceId -Like $currentWorkspaceId).workspaceId = $newWorkspaceId 76 | $newDCR = $dcr | ConvertTo-Json -Depth 20 77 | $newDCR = $newDCR.Replace($destName, $newDestName) 78 | 79 | # Update the DCR 80 | Invoke-AzRestMethod -Uri $uri -Method PUT -Payload $newDCR 81 | 82 | -------------------------------------------------------------------------------- /dataconnectors/GitHubAuditLogs/CCP/GitHubAuditLogs_CCP.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "workspace": { 6 | "type": "string", 7 | "defaultValue": "" 8 | } 9 | }, 10 | "resources": [ 11 | { 12 | "id": "[concat('/subscriptions/',subscription().subscriptionId,'/resourceGroups/',resourceGroup().name,'/providers/Microsoft.OperationalInsights/workspaces/',parameters('workspace'),'/providers/Microsoft.SecurityInsights/dataConnectors/',guid(resourceGroup().id, deployment().name))]", 13 | "name": "[concat(parameters('workspace'),'/Microsoft.SecurityInsights/',guid(resourceGroup().id, deployment().name))]", 14 | "apiVersion": "2021-03-01-preview", 15 | "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors", 16 | "kind": "APIPolling", 17 | "properties": { 18 | "connectorUiConfig": { 19 | "id": "GitHubEntAuditLogPolling", 20 | "title": "GitHub Enterprise Audit Log", 21 | "publisher": "Microsoft-Custom", 22 | "descriptionMarkdown": "The GitHub audit log connector provides the capability to ingest GitHub audit log at Enterprise level into Microsoft Sentinel. By connecting GitHub audit logs into Microsoft Sentinel, you can view this data in workbooks, use it to create custom alerts, and improve your investigation process. \n\n ", 23 | "graphQueriesTableName": "GitHubEntAuditLogPolling_CL", 24 | "graphQueries": [ 25 | { 26 | "metricName": "Total events received", 27 | "legend": "GitHub audit log events", 28 | "baseQuery": "{{graphQueriesTableName}}" 29 | } 30 | ], 31 | "sampleQueries": [ 32 | { 33 | "description": "All logs", 34 | "query": "{{graphQueriesTableName}}\n | take 10" 35 | } 36 | ], 37 | "dataTypes": [ 38 | { 39 | "name": "{{graphQueriesTableName}}", 40 | "lastDataReceivedQuery": "{{graphQueriesTableName}}\n | summarize Time = max(TimeGenerated)\n | where isnotempty(Time)" 41 | } 42 | ], 43 | "connectivityCriterias": [ 44 | { 45 | "type": "SentinelKindsV2", 46 | "value": [] 47 | } 48 | ], 49 | "availability": { 50 | "status": 1, 51 | "isPreview": true 52 | }, 53 | "permissions": { 54 | "resourceProvider": [ 55 | { 56 | "provider": "Microsoft.OperationalInsights/workspaces", 57 | "permissionsDisplayText": "read and write permissions are required.", 58 | "providerDisplayName": "Workspace", 59 | "scope": "Workspace", 60 | "requiredPermissions": { 61 | "write": true, 62 | "read": true, 63 | "delete": true 64 | } 65 | } 66 | ], 67 | "customs": [ 68 | { 69 | "name": "GitHub API personal access token", 70 | "description": "You need a GitHub personal access token to enable polling for the Enterprise audit log. You need to use a classic token; you must be an enterprise admin and you must use an access token with the read:audit_log scope." 71 | }, 72 | { 73 | "name": "GitHub Enterprise type", 74 | "description": "This connector will only function with GitHub Enterprise Cloud; it will not support GitHub Enterprise Server. " 75 | } 76 | ] 77 | }, 78 | "instructionSteps": [ 79 | { 80 | "title": "Connect the GitHub Enterprise (All Orgs) Audit Log to Microsoft Sentinel", 81 | "description": "Enable GitHub audit logs. \n Follow [this guide](https://docs.github.com/en/github/authenticating-to-github/keeping-your-account-and-data-secure/creating-a-personal-access-token) to create or find your personal access token.", 82 | "instructions": [ 83 | { 84 | "parameters": { 85 | "enable": "true", 86 | "userRequestPlaceHoldersInput": [ 87 | { 88 | "displayText": "Enterprise Name", 89 | "requestObjectKey": "apiEndpoint", 90 | "placeHolderName": "{{placeHolder1}}", 91 | "placeHolderValue": "" 92 | } 93 | ] 94 | }, 95 | "type": "APIKey" 96 | } 97 | ] 98 | } 99 | ] 100 | }, 101 | "pollingConfig": { 102 | "owner": "ASI", 103 | "version": "2.0", 104 | "source": "PaaS", 105 | "templateFilePath": "", 106 | "templateFileName": "", 107 | "auth": { 108 | "authType": "APIKey", 109 | "APIKeyName": "Authorization", 110 | "APIKeyIdentifier": "token" 111 | }, 112 | "request": { 113 | "apiEndpoint": "https://api.github.com/enterprises/{{placeHolder1}}/audit-log", 114 | "rateLimitQPS": 50, 115 | "queryWindowInMin": 10, 116 | "httpMethod": "Get", 117 | "queryTimeFormat": "yyyy-MM-ddTHH:mm:ssZ", 118 | "retryCount": 3, 119 | "timeoutInSeconds": 60, 120 | "headers": { 121 | "Accept": "application/json", 122 | "X-GitHub-Api-Version": "2022-11-28", 123 | "user-agent": "scuba" 124 | }, 125 | "queryParameters": { 126 | "phrase": "created:{_QueryWindowStartTime}..{_QueryWindowEndTime}", 127 | "include": "all" 128 | } 129 | }, 130 | "paging": { 131 | "pagingType": "LinkHeader", 132 | "pageSizeParaName": "per_page" 133 | }, 134 | "response": { 135 | "eventsJsonPaths": [ 136 | "$" 137 | ] 138 | } 139 | } 140 | } 141 | } 142 | ] 143 | } 144 | -------------------------------------------------------------------------------- /dataconnectors/GitHubAuditLogs/CCP/readme.md: -------------------------------------------------------------------------------- 1 | # GitHub Enterprise Audit Logs Sentinel CCP 2 | 3 | - [Solution Overview](#solution-overview) 4 | * [Step 1 - Deploy the Data Connector](#step-1---deploy-the-data-connector) 5 | * [Step 2 - Configure GitHub Enterprise](#step-2---configure-github-enterprise) 6 | * [Step 3 - Create a GitHub Personal Access Token](#step-3---create-a-github-personal-access-token) 7 | * [Step 4 - Configure the Sentinel Data Connector](#step-4---configure-the-sentinel-data-connector) 8 | 9 | # Solution Overview 10 | This solution will ingest audit logs from GitHub Enterprise for all organizations to Microsoft Sentinel using the Codeless Connector Platform data connector. The data will end up in the **GitHubEntAuditLogPolling_CL** table. 11 | > ⚠️ If you are using IP Restrictions in GitHub you will need to whitelist the CIDR ranges for CCP under the **SCUBA** tag here - [Home Page - Azure IP Ranges](https://azureipranges.azurewebsites.net/) 12 | 13 | ## Step 1 - Deploy the Data Connector 14 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fdataconnectors%2FGitHubAuditLogs%2FCCP%2FGitHubAuditLogs_CCP.json) 15 | 16 | ## Step 2 - Configure GitHub Enterprise 17 | > This step is optional but recommend 18 | 19 | 1. From the [GitHub Enterprise](https://github.com/enterprises) navigate to **Settings** > **Audit Log** 20 | 2. Navigate to the **Settings** tab, turn on **Enable source IP disclosure** and **Enable API Request Events** 21 | 22 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/a5c4d65a-67a6-4c69-9f61-1ae04b2f3a1b) 23 | 24 | ## Step 3 - Create a GitHub Personal Access Token 25 | You will need a GitHub personal access token to enable polling for the Enterprise audit log. You need to use a **classic token**; you must be an enterprise admin and you must use an access token with the **read:audit_log** scope 26 | 27 | 1. In the upper-right corner of any page, click your profile photo, then click **Settings** 28 | 2. In the left sidebar, click **Developer settings** 29 | 3. In the left sidebar, click **Personal access tokens** > **Tokens (classic)** 30 | 4. Click **Generate new token (classic)** 31 | 5. Give the token a name and add the **read:audit_log** scope 32 | 33 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/d47009fa-a54e-420e-b84b-36fee737d702) 34 | 35 | 7. Copy the access token to a safe location 36 | 37 | ## Step 4 - Configure the Sentinel Data Connector 38 | 1. From Microsoft Sentinel navigate to **Data Connectors** 39 | 2. You should see a **GitHub Enterprise Audit Log (Preview)** data connector, click ***Open connector page** 40 | 3. Enter your **enterprise name** and **GitHub personal access token** in the **API Key** field. Click **Connect** 41 | 4. Generally the intial data will show up in the **GitHubEntAuditLogPolling_CL** table around 20 minutes after the data connector is configured 42 | -------------------------------------------------------------------------------- /dataconnectors/GitHubAuditLogs/eventhub/eventhub.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "eventHubName": { 6 | "defaultValue": "githubauditlogs", 7 | "type": "String" 8 | }, 9 | "eventHubNamespace": { 10 | "defaultValue": "githubauditlogs", 11 | "type": "String" 12 | }, 13 | "eventHubLocation": { 14 | "defaultValue": "westus3", 15 | "metadata": { 16 | "description": "Location for the Event Hub. Must be a supported region https://learn.microsoft.com/en-us/azure/azure-monitor/logs/ingest-logs-event-hub#supported-regions " 17 | }, 18 | "type": "string" 19 | } 20 | }, 21 | "resources": [ 22 | { 23 | "apiVersion": "2023-01-01-preview", 24 | "location": "[parameters('eventHubLocation')]", 25 | "name": "[variables('_eventHubNamespace')]", 26 | "properties": { 27 | "disableLocalAuth": false, 28 | "isAutoInflateEnabled": false, 29 | "kafkaEnabled": false, 30 | "maximumThroughputUnits": 0, 31 | "minimumTlsVersion": "1.2", 32 | "publicNetworkAccess": "Enabled", 33 | "zoneRedundant": true 34 | }, 35 | "sku": { 36 | "capacity": 1, 37 | "name": "Standard", 38 | "tier": "Standard" 39 | }, 40 | "type": "Microsoft.EventHub/namespaces" 41 | }, 42 | { 43 | "apiVersion": "2023-01-01-preview", 44 | "dependsOn": [ 45 | "[resourceId('Microsoft.EventHub/namespaces', variables('_eventHubNamespace'))]" 46 | ], 47 | "location": "[parameters('eventHubLocation')]", 48 | "name": "[concat(variables('_eventHubNamespace'), '/RootManageSharedAccessKey')]", 49 | "properties": { 50 | "rights": [ 51 | "Listen", 52 | "Manage", 53 | "Send" 54 | ] 55 | }, 56 | "type": "Microsoft.EventHub/namespaces/authorizationrules" 57 | }, 58 | { 59 | "apiVersion": "2023-01-01-preview", 60 | "dependsOn": [ 61 | "[resourceId('Microsoft.EventHub/namespaces', variables('_eventHubNamespace'))]" 62 | ], 63 | "location": "[parameters('eventHubLocation')]", 64 | "name": "[concat(variables('_eventHubNamespace'), '/', parameters('eventHubName'))]", 65 | "properties": { 66 | "messageRetentionInDays": 1, 67 | "partitionCount": 2, 68 | "retentionDescription": { 69 | "cleanupPolicy": "Delete", 70 | "retentionTimeInHours": 1 71 | }, 72 | "status": "Active" 73 | }, 74 | "type": "Microsoft.EventHub/namespaces/eventhubs" 75 | }, 76 | { 77 | "apiVersion": "2023-01-01-preview", 78 | "dependsOn": [ 79 | "[resourceId('Microsoft.EventHub/namespaces', variables('_eventHubNamespace'))]" 80 | ], 81 | "location": "[parameters('eventHubLocation')]", 82 | "name": "[concat(variables('_eventHubNamespace'), '/default')]", 83 | "properties": { 84 | "defaultAction": "Allow", 85 | "ipRules": [ 86 | ], 87 | "publicNetworkAccess": "Enabled", 88 | "trustedServiceAccessEnabled": false, 89 | "virtualNetworkRules": [ 90 | ] 91 | }, 92 | "type": "Microsoft.EventHub/namespaces/networkrulesets" 93 | }, 94 | { 95 | "apiVersion": "2023-01-01-preview", 96 | "dependsOn": [ 97 | "[resourceId('Microsoft.EventHub/namespaces/eventhubs', variables('_eventHubNamespace'), parameters('eventHubName'))]", 98 | "[resourceId('Microsoft.EventHub/namespaces', variables('_eventHubNamespace'))]" 99 | ], 100 | "location": "[parameters('eventHubLocation')]", 101 | "name": "[concat(variables('_eventHubNamespace'), '/', parameters('eventHubName'), '/', parameters('eventHubName'), 'Send')]", 102 | "properties": { 103 | "rights": [ 104 | "Send" 105 | ] 106 | }, 107 | "type": "Microsoft.EventHub/namespaces/eventhubs/authorizationrules" 108 | }, 109 | { 110 | "apiVersion": "2023-01-01-preview", 111 | "dependsOn": [ 112 | "[resourceId('Microsoft.EventHub/namespaces/eventhubs', variables('_eventHubNamespace'), parameters('eventHubName'))]", 113 | "[resourceId('Microsoft.EventHub/namespaces', variables('_eventHubNamespace'))]" 114 | ], 115 | "location": "[parameters('eventHubLocation')]", 116 | "name": "[concat(variables('_eventHubNamespace'), '/', parameters('eventHubName'), '/$Default')]", 117 | "properties": { 118 | }, 119 | "type": "Microsoft.EventHub/namespaces/eventhubs/consumergroups" 120 | } 121 | ], 122 | "variables": { 123 | "_eventHubNamespace": "[concat(parameters('eventHubNamespace'), '-', uniqueString(parameters('eventHubNamespace')))]" 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /dataconnectors/GitHubAuditLogs/eventhub/readme.md: -------------------------------------------------------------------------------- 1 | ## ⚠️ Work In Progress, not ready yet 2 | 3 | ## Solution Overview 4 | This solution will stream [audit logs](https://docs.github.com/en/enterprise-cloud@latest/admin/monitoring-activity-in-your-enterprise/reviewing-audit-logs-for-your-enterprise/about-the-audit-log-for-your-enterprise) from GitHub Enterprise for all organizations to an Azure Event Hub. Events that are sent to the Event Hub will then be ingested to the Microsoft Sentinel workspace via the method documented here - [Ingest events from Azure Event Hubs into Azure Monitor Logs](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/ingest-logs-event-hub) 5 | 6 | > This solution also avoids any of the rate limits imposed with directly pulling audit logs from the rest api and exposing any PAT tokens from GitHub 7 | 8 | ## Requirements 9 | To send events from Azure Event Hubs to Sentinel you wil require the below 10 | 11 | - Log Analytics workspace where you have at least contributor rights. 12 | - Your Log Analytics workspace needs to be linked to a **dedicated cluster** or to have a **commitment tier**. 13 | - Event Hubs namespace that permits public network access. Private Link and Network Security Perimeters (NSP) are currently not supported. 14 | - ❗The Event Hub must be in a **supported region** documented here [Supported Regions](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/ingest-logs-event-hub#supported-regions). 15 | 16 | ## Step 1 - Deploy the Event Hub 17 | > ❗The Event Hub must be in a supported region documented here [Supported Regions](https://learn.microsoft.com/en-us/azure/azure-monitor/logs/ingest-logs-event-hub#supported-regions). West US 2 is not supported. 18 | 19 | 1. [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fdataconnectors%2FGitHubAuditLogs%2Feventhub.json) 20 | 21 | 2. Collect the following information from the Event Hub 22 | 3. Navigate to the Event Hub Namespace **Event Hubs** > Select the **Event Hub** (githubauditlogs) 23 | 4. Copy down the Event Hub instance name, which should be githubauditlogs by default 24 | 25 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/316fe9bd-605c-4c87-a62c-9021996587b6) 26 | 27 | 6. Select **Shared access policies** and select the **githubauditlogsSend** policy 28 | 29 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/b283f50f-c2e2-44d9-b26c-58c606791579) 30 | 31 | 8. Copy down the Connection string–primary key in a safe location 32 | 33 | ## Step 2 - Create the Data Collection Endpoint and Data Collection Rule 34 | 35 | 36 | ## Step 3 - Configure GitHub Enterprise 37 | > Steps 2 is optional but recommend 38 | 39 | 1. From the [GitHub Enterprise](https://github.com/enterprises) navigate to **Settings** > **Audit Log** 40 | 2. Navigate to the **Settings** tab, turn on **Enable source IP disclosure** and **Enable API Request Events** 41 | 42 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/a5c4d65a-67a6-4c69-9f61-1ae04b2f3a1b) 43 | 44 | 4. Navigate to the **Log Streaming** tab 45 | 5. Enter your **Azure Event Hubs Instance** name and **Connection String** collected from **Step 1** 46 | 47 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/6d2a63d3-bfa1-4824-826d-7053648ff9bc) 48 | 49 | ## Step 4 - Verify Logs 50 | 1. After Step 1 - 3 are completed you should see events hitting your Event Hub Namespace and logs in your log analytics workspace 51 | -------------------------------------------------------------------------------- /dataconnectors/cross-tenant-logging/azure-activity-logs/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "diagnosticsSettingsName": { 6 | "defaultValue": "send-to-sentinel", 7 | "type": "String", 8 | "metadata": { 9 | "description": "Enter value for diagnosticsSettingsName" 10 | } 11 | }, 12 | "logCategoryAdministrative": { 13 | "defaultValue": true, 14 | "type": "Bool", 15 | "metadata": { 16 | "description": "Enter value for logCategoryAdministrative" 17 | } 18 | }, 19 | "logCategoryAlert": { 20 | "defaultValue": true, 21 | "type": "Bool", 22 | "metadata": { 23 | "description": "Enter value for logCategoryAlert" 24 | } 25 | }, 26 | "logCategoryAutoscale": { 27 | "defaultValue": true, 28 | "type": "Bool", 29 | "metadata": { 30 | "description": "Enter value for logCategoryAutoscale" 31 | } 32 | }, 33 | "logCategoryPolicy": { 34 | "defaultValue": true, 35 | "type": "Bool", 36 | "metadata": { 37 | "description": "Enter value for logCategoryPolicy" 38 | } 39 | }, 40 | "logCategoryRecommendation": { 41 | "defaultValue": true, 42 | "type": "Bool", 43 | "metadata": { 44 | "description": "Enter value for logCategoryRecommendation" 45 | } 46 | }, 47 | "logCategoryResourceHealth": { 48 | "defaultValue": true, 49 | "type": "Bool", 50 | "metadata": { 51 | "description": "Enter value for logCategoryResourceHealth" 52 | } 53 | }, 54 | "logCategorySecurity": { 55 | "defaultValue": true, 56 | "type": "Bool", 57 | "metadata": { 58 | "description": "Enter value for logCategorySecurity" 59 | } 60 | }, 61 | "logCategoryServiceHealth": { 62 | "defaultValue": true, 63 | "type": "Bool", 64 | "metadata": { 65 | "description": "Enter value for logCategoryServiceHealth" 66 | } 67 | }, 68 | "PlaybookName": { 69 | "defaultValue": "configure-azure-activity-logging", 70 | "type": "String" 71 | }, 72 | "workspaceResourceId": { 73 | "type": "String", 74 | "metadata": { 75 | "description": "The full resourceID of the log analytics workspace" 76 | } 77 | } 78 | }, 79 | "variables": { 80 | "ArmConnectionName": "[concat('Arm-', parameters('PlaybookName'))]" 81 | }, 82 | "resources": [ 83 | { 84 | "type": "Microsoft.Logic/workflows", 85 | "apiVersion": "2017-07-01", 86 | "name": "[parameters('PlaybookName')]", 87 | "location": "[resourceGroup().location]", 88 | "dependsOn": [ 89 | "[resourceId('Microsoft.Web/connections', variables('ArmConnectionName'))]" 90 | ], 91 | "tags": { 92 | "hidden-SentinelTemplateName": "configure-cross-tenant-logging", 93 | "hidden-SentinelTemplateVersion": "1.0" 94 | }, 95 | "identity": { 96 | "type": "SystemAssigned" 97 | }, 98 | "properties": { 99 | "definition": { 100 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 101 | "actions": { 102 | "For_each_Subscription_-_Configure_Azure_Activity_Logs": { 103 | "actions": { 104 | "HTTP_-_Configure_Diagnostic_Settings": { 105 | "inputs": { 106 | "authentication": { 107 | "type": "ManagedServiceIdentity" 108 | }, 109 | "body": { 110 | "properties": { 111 | "logs": [ 112 | { 113 | "category": "Administrative", 114 | "categoryGroup": null, 115 | "enabled": "@parameters('logCategoryAdministrative')" 116 | }, 117 | { 118 | "category": "Security", 119 | "categoryGroup": null, 120 | "enabled": "@parameters('logCategorySecurity')" 121 | }, 122 | { 123 | "category": "ServiceHealth", 124 | "categoryGroup": null, 125 | "enabled": "@parameters('logCategoryServiceHealth')" 126 | }, 127 | { 128 | "category": "Alert", 129 | "categoryGroup": null, 130 | "enabled": "@parameters('logCategoryAlert')" 131 | }, 132 | { 133 | "category": "Recommendation", 134 | "categoryGroup": null, 135 | "enabled": "@parameters('logCategoryRecommendation')" 136 | }, 137 | { 138 | "category": "Policy", 139 | "categoryGroup": null, 140 | "enabled": "@parameters('logCategoryPolicy')" 141 | }, 142 | { 143 | "category": "Autoscale", 144 | "categoryGroup": null, 145 | "enabled": "@parameters('logCategoryAutoscale')" 146 | }, 147 | { 148 | "category": "ResourceHealth", 149 | "categoryGroup": null, 150 | "enabled": "@parameters('logCategoryResourceHealth')" 151 | } 152 | ], 153 | "workspaceId": "@{parameters('workspaceResourceId')}" 154 | } 155 | }, 156 | "method": "PUT", 157 | "queries": { 158 | "api-version": "2021-05-01-preview" 159 | }, 160 | "uri": "https://management.azure.com/subscriptions/@{items('For_each_Subscription_-_Configure_Azure_Activity_Logs')?['subscriptionId']}/providers/microsoft.insights/diagnosticSettings/@{parameters('diagnosticsSettingsName')}" 161 | }, 162 | "runAfter": {}, 163 | "type": "Http" 164 | } 165 | }, 166 | "foreach": "@body('List_subscriptions_-_All_Tenants')?['value']", 167 | "runAfter": { 168 | "List_subscriptions_-_All_Tenants": [ 169 | "Succeeded" 170 | ] 171 | }, 172 | "type": "Foreach" 173 | }, 174 | "List_subscriptions_-_All_Tenants": { 175 | "inputs": { 176 | "host": { 177 | "connection": { 178 | "name": "@parameters('$connections')['arm']['connectionId']" 179 | } 180 | }, 181 | "method": "get", 182 | "path": "/subscriptions", 183 | "queries": { 184 | "x-ms-api-version": "2016-06-01" 185 | } 186 | }, 187 | "runAfter": {}, 188 | "type": "ApiConnection" 189 | } 190 | }, 191 | "contentVersion": "1.0.0.0", 192 | "outputs": {}, 193 | "parameters": { 194 | "$connections": { 195 | "defaultValue": {}, 196 | "type": "Object" 197 | }, 198 | "diagnosticsSettingsName": { 199 | "defaultValue": "[parameters('diagnosticsSettingsName')]", 200 | "type": "string" 201 | }, 202 | "logCategoryAdministrative": { 203 | "defaultValue": "[parameters('logCategoryAdministrative')]", 204 | "type": "bool" 205 | }, 206 | "logCategoryAlert": { 207 | "defaultValue": "[parameters('logCategoryAlert')]", 208 | "type": "bool" 209 | }, 210 | "logCategoryAutoscale": { 211 | "defaultValue": "[parameters('logCategoryAutoscale')]", 212 | "type": "bool" 213 | }, 214 | "logCategoryPolicy": { 215 | "defaultValue": "[parameters('logCategoryPolicy')]", 216 | "type": "bool" 217 | }, 218 | "logCategoryRecommendation": { 219 | "defaultValue": "[parameters('logCategoryRecommendation')]", 220 | "type": "bool" 221 | }, 222 | "logCategoryResourceHealth": { 223 | "defaultValue": "[parameters('logCategoryResourceHealth')]", 224 | "type": "bool" 225 | }, 226 | "logCategorySecurity": { 227 | "defaultValue": "[parameters('logCategorySecurity')]", 228 | "type": "bool" 229 | }, 230 | "logCategoryServiceHealth": { 231 | "defaultValue": "[parameters('logCategoryServiceHealth')]", 232 | "type": "bool" 233 | }, 234 | "workspaceResourceId": { 235 | "defaultValue": "[parameters('workspaceResourceId')]", 236 | "type": "string" 237 | } 238 | }, 239 | "triggers": { 240 | "Recurrence": { 241 | "evaluatedRecurrence": { 242 | "frequency": "Day", 243 | "interval": 1 244 | }, 245 | "recurrence": { 246 | "frequency": "Day", 247 | "interval": 1 248 | }, 249 | "type": "Recurrence" 250 | } 251 | } 252 | }, 253 | "parameters": { 254 | "$connections": { 255 | "value": { 256 | "arm": { 257 | "connectionId": "[resourceId('Microsoft.Web/connections', variables('ArmConnectionName'))]", 258 | "connectionName": "[variables('ArmConnectionName')]", 259 | "connectionProperties": { 260 | "authentication": { 261 | "type": "ManagedServiceIdentity" 262 | } 263 | }, 264 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Arm')]" 265 | } 266 | } 267 | } 268 | }, 269 | "provisioningState": "Succeeded", 270 | "state": "Enabled" 271 | } 272 | }, 273 | { 274 | "type": "Microsoft.Web/connections", 275 | "apiVersion": "2016-06-01", 276 | "name": "[variables('ArmConnectionName')]", 277 | "location": "[resourceGroup().location]", 278 | "kind": "V1", 279 | "properties": { 280 | "api": { 281 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Arm')]" 282 | }, 283 | "customParameterValues": {}, 284 | "displayName": "[variables('ArmConnectionName')]", 285 | "parameterValueType": "Alternative" 286 | } 287 | } 288 | ] 289 | } 290 | -------------------------------------------------------------------------------- /dataconnectors/cross-tenant-logging/azure-activity-logs/readme.md: -------------------------------------------------------------------------------- 1 | # Azure Activity Logs Cross Tenant Logging 2 | 3 | - [Solution Overview](#solution-overview) 4 | - [Step 1 - Deploy the Logic App](#step-1-deploy-the-logic-app) 5 | - [Step 2 - Configure the Logic App](#step-2-configure-the-logic-app) 6 | - [Step 3 - Configure Azure Lighthouse](#step-3-configure-azure-lighthouse) 7 | - [Step 4 - Test The Logic App](#step-4-test-the-logic-app) 8 | 9 | # Solution Overview 10 | This solution will configure cross-tenant logging of Azure Activity Logs to a centralized log analytics workspace in a primary tenant's workspace using Azure Lighthouse delegated roles. The overall steps are to deploy the logic app in the tenant hosting the log analytics workspace and then deploy an Azure Lighthouse delegation to all other tenants to allow the logic app to create the logging profile. 11 | 12 | - The logic app runs once a day by default 13 | - The logic app will configure Azure Activity logs on any subscription where the system assigned managed identity has been delegated rights to 14 | 15 | # Step 1 - Deploy the Logic App 16 | 17 | 1. Deploy this in the tenant hosting the log analytics workspace 18 | 19 | > [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fdataconnectors%2Fcross-tenant-logging%2Fazure-activity-logs%2Fazuredeploy.json) 20 | 21 | > ⚠️ Make you specify the full log analytics workspace resource id. Example: /subscriptions/\/resourcegroups/\/providers/microsoft.operationalinsights/workspaces/\ 22 | 23 | # Step 2 - Configure the Logic App 24 | 1. Navigate to **Logic App** > **Identity** Section 25 | 2. Confirm a system assigned managed identity was created and **copy** the **Object (principal) ID** 26 | 3. Click **Azure role assignments** 27 | 4. Click **Add role assignment** 28 | 5. For the Scope select **Resource Group** 29 | 6. Select the **Subscription** and **Resource Group** where your **Sentinel Workspace** Resides 30 | 7. Select the **Log Analytics Contributor Role** 31 | 32 | > ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/cbdd69b1-518d-46f6-a80b-6b4cfb68b2c8) 33 | 34 | 8. Select **Save** 35 | > ℹ️ If you would like the logic app to also configure logging in the current tenant add the **Monitoring Contributor** role to applicable subscriptions or managment groups in the current tenant 36 | 37 | # Step 3 - Configure Azure Lighthouse 38 | 1. From the Azure Portal navigate to **Azure Lighthouse** 39 | 2. Select **Manage your customers** 40 | 3. Select **Create ARM Template** 41 | 4. Enter a **name** for the Azure Lighthouse offer (Sentinel Cross Tenant Logging) 42 | 5. Enter a **description** (Configures Cross Tenant Logging of Azure resource provider logs to Sentinel) 43 | 6. For the **Delegate scope** select **Subscription** 44 | 7. Click Add Authorization 45 | 8. Select **Service principal** and select the **system assigned managed identity of the logic app** 46 | 47 | >![image](https://github.com/seanstark/sentinel-tools/assets/84108246/4e47777a-13d7-4d06-a7ff-97775ff13fa6) 48 | 49 | 9. Select the **Monitoring Contributor** role 50 | 10. Select **Permanent** for the Access Type 51 | 52 | >![image](https://github.com/seanstark/sentinel-tools/assets/84108246/ea5b5988-6272-4bdf-9192-dab9cf64a067) 53 | 54 | 11. Click **Add** 55 | 12. Click **View Template** 56 | 13. Next deploy the ARM template to your other Tenant Subscriptions 57 | 58 | # Step 4 - Test The Logic App 59 | 1. Navigate to **Logic App** > Select Run 60 | 2. Wait for the Logic App run to complete and verify the diagnostics settings were configured 61 | -------------------------------------------------------------------------------- /dataconnectors/okta/AzureFunctionOktaSSO.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/dataconnectors/okta/AzureFunctionOktaSSO.zip -------------------------------------------------------------------------------- /dataconnectors/okta/okta_custom_table.json: -------------------------------------------------------------------------------- 1 | $tableParams = @' 2 | { 3 | "properties": { 4 | "schema": { 5 | "name": "Okta_App_Team_CL" 6 | "columns": [ 7 | { 8 | "isDefaultDisplay": false, 9 | "isHidden": false, 10 | "name": "ActingAppName", 11 | "type": "string" 12 | }, 13 | { 14 | "isDefaultDisplay": false, 15 | "isHidden": false, 16 | "name": "ActingAppType", 17 | "type": "string" 18 | }, 19 | { 20 | "isDefaultDisplay": false, 21 | "isHidden": false, 22 | "name": "ActorDetailEntry", 23 | "type": "dynamic" 24 | }, 25 | { 26 | "isDefaultDisplay": false, 27 | "isHidden": false, 28 | "name": "ActorDisplayName", 29 | "type": "string" 30 | }, 31 | { 32 | "isDefaultDisplay": false, 33 | "isHidden": false, 34 | "name": "ActorSessionId", 35 | "type": "string" 36 | }, 37 | { 38 | "isDefaultDisplay": false, 39 | "isHidden": false, 40 | "name": "ActorUserId", 41 | "type": "string" 42 | }, 43 | { 44 | "isDefaultDisplay": false, 45 | "isHidden": false, 46 | "name": "ActorUserIdType", 47 | "type": "string" 48 | }, 49 | { 50 | "isDefaultDisplay": false, 51 | "isHidden": false, 52 | "name": "ActorUsername", 53 | "type": "string" 54 | }, 55 | { 56 | "isDefaultDisplay": false, 57 | "isHidden": false, 58 | "name": "ActorUsernameType", 59 | "type": "string" 60 | }, 61 | { 62 | "isDefaultDisplay": false, 63 | "isHidden": false, 64 | "name": "ActorUserType", 65 | "type": "string" 66 | }, 67 | { 68 | "isDefaultDisplay": false, 69 | "isHidden": false, 70 | "name": "AuthenticationContextAuthenticationProvider", 71 | "type": "string" 72 | }, 73 | { 74 | "isDefaultDisplay": false, 75 | "isHidden": false, 76 | "name": "AuthenticationContextAuthenticationStep", 77 | "type": "int" 78 | }, 79 | { 80 | "isDefaultDisplay": false, 81 | "isHidden": false, 82 | "name": "AuthenticationContextCredentialProvider", 83 | "type": "string" 84 | }, 85 | { 86 | "isDefaultDisplay": false, 87 | "isHidden": false, 88 | "name": "AuthenticationContextInterface", 89 | "type": "string" 90 | }, 91 | { 92 | "isDefaultDisplay": false, 93 | "isHidden": false, 94 | "name": "AuthenticationContextIssuerId", 95 | "type": "string" 96 | }, 97 | { 98 | "isDefaultDisplay": false, 99 | "isHidden": false, 100 | "name": "AuthenticationContextIssuerType", 101 | "type": "string" 102 | }, 103 | { 104 | "isDefaultDisplay": false, 105 | "isHidden": false, 106 | "name": "DebugData", 107 | "type": "dynamic" 108 | }, 109 | { 110 | "isDefaultDisplay": false, 111 | "isHidden": false, 112 | "name": "DvcAction", 113 | "type": "string" 114 | }, 115 | { 116 | "isDefaultDisplay": false, 117 | "isHidden": false, 118 | "name": "EventMessage", 119 | "type": "string" 120 | }, 121 | { 122 | "isDefaultDisplay": false, 123 | "isHidden": false, 124 | "name": "EventOriginalResultDetails", 125 | "type": "string" 126 | }, 127 | { 128 | "isDefaultDisplay": false, 129 | "isHidden": false, 130 | "name": "EventOriginalType", 131 | "type": "string" 132 | }, 133 | { 134 | "isDefaultDisplay": false, 135 | "isHidden": false, 136 | "name": "EventOriginalUid", 137 | "type": "string" 138 | }, 139 | { 140 | "isDefaultDisplay": false, 141 | "isHidden": false, 142 | "name": "EventResult", 143 | "type": "string" 144 | }, 145 | { 146 | "isDefaultDisplay": false, 147 | "isHidden": false, 148 | "name": "EventSeverity", 149 | "type": "string" 150 | }, 151 | { 152 | "isDefaultDisplay": false, 153 | "isHidden": false, 154 | "name": "HttpUserAgent", 155 | "type": "string" 156 | }, 157 | { 158 | "isDefaultDisplay": false, 159 | "isHidden": false, 160 | "name": "LegacyEventType", 161 | "type": "string" 162 | }, 163 | { 164 | "isDefaultDisplay": false, 165 | "isHidden": false, 166 | "name": "LogonMethod", 167 | "type": "string" 168 | }, 169 | { 170 | "isDefaultDisplay": false, 171 | "isHidden": false, 172 | "name": "OriginalActorAlternateId", 173 | "type": "string" 174 | }, 175 | { 176 | "isDefaultDisplay": false, 177 | "isHidden": false, 178 | "name": "OriginalClientDevice", 179 | "type": "string" 180 | }, 181 | { 182 | "isDefaultDisplay": false, 183 | "isHidden": false, 184 | "name": "OriginalOutcomeResult", 185 | "type": "string" 186 | }, 187 | { 188 | "isDefaultDisplay": false, 189 | "isHidden": false, 190 | "name": "OriginalSeverity", 191 | "type": "string" 192 | }, 193 | { 194 | "isDefaultDisplay": false, 195 | "isHidden": false, 196 | "name": "OriginalTarget", 197 | "type": "dynamic" 198 | }, 199 | { 200 | "isDefaultDisplay": false, 201 | "isHidden": false, 202 | "name": "OriginalUserId", 203 | "type": "string" 204 | }, 205 | { 206 | "isDefaultDisplay": false, 207 | "isHidden": false, 208 | "name": "OriginalUserType", 209 | "type": "string" 210 | }, 211 | { 212 | "isDefaultDisplay": false, 213 | "isHidden": false, 214 | "name": "Request", 215 | "type": "dynamic" 216 | }, 217 | { 218 | "isDefaultDisplay": false, 219 | "isHidden": false, 220 | "name": "SecurityContextAsNumber", 221 | "type": "int" 222 | }, 223 | { 224 | "isDefaultDisplay": false, 225 | "isHidden": false, 226 | "name": "SecurityContextAsOrg", 227 | "type": "string" 228 | }, 229 | { 230 | "isDefaultDisplay": false, 231 | "isHidden": false, 232 | "name": "SecurityContextDomain", 233 | "type": "string" 234 | }, 235 | { 236 | "isDefaultDisplay": false, 237 | "isHidden": false, 238 | "name": "SecurityContextIsProxy", 239 | "type": "boolean" 240 | }, 241 | { 242 | "isDefaultDisplay": false, 243 | "isHidden": false, 244 | "name": "SrcDeviceType", 245 | "type": "string" 246 | }, 247 | { 248 | "isDefaultDisplay": false, 249 | "isHidden": false, 250 | "name": "SrcDvcId", 251 | "type": "string" 252 | }, 253 | { 254 | "isDefaultDisplay": false, 255 | "isHidden": false, 256 | "name": "SrcDvcOs", 257 | "type": "string" 258 | }, 259 | { 260 | "isDefaultDisplay": false, 261 | "isHidden": false, 262 | "name": "SrcGeoCity", 263 | "type": "string" 264 | }, 265 | { 266 | "isDefaultDisplay": false, 267 | "isHidden": false, 268 | "name": "SrcGeoCountry", 269 | "type": "string" 270 | }, 271 | { 272 | "isDefaultDisplay": false, 273 | "isHidden": false, 274 | "name": "SrcGeoLatitude", 275 | "type": "real" 276 | }, 277 | { 278 | "isDefaultDisplay": false, 279 | "isHidden": false, 280 | "name": "SrcGeoLongtitude", 281 | "type": "real" 282 | }, 283 | { 284 | "isDefaultDisplay": false, 285 | "isHidden": false, 286 | "name": "SrcGeoPostalCode", 287 | "type": "string" 288 | }, 289 | { 290 | "isDefaultDisplay": false, 291 | "isHidden": false, 292 | "name": "SrcGeoRegion", 293 | "type": "string" 294 | }, 295 | { 296 | "isDefaultDisplay": false, 297 | "isHidden": false, 298 | "name": "SrcIpAddr", 299 | "type": "string" 300 | }, 301 | { 302 | "isDefaultDisplay": false, 303 | "isHidden": false, 304 | "name": "SrcIsp", 305 | "type": "string" 306 | }, 307 | { 308 | "isDefaultDisplay": false, 309 | "isHidden": false, 310 | "name": "SrcZone", 311 | "type": "string" 312 | }, 313 | { 314 | "isDefaultDisplay": false, 315 | "isHidden": false, 316 | "name": "TimeGenerated", 317 | "type": "datetime" 318 | }, 319 | { 320 | "isDefaultDisplay": false, 321 | "isHidden": false, 322 | "name": "TransactionDetail", 323 | "type": "dynamic" 324 | }, 325 | { 326 | "isDefaultDisplay": false, 327 | "isHidden": false, 328 | "name": "TransactionId", 329 | "type": "string" 330 | }, 331 | { 332 | "isDefaultDisplay": false, 333 | "isHidden": false, 334 | "name": "TransactionType", 335 | "type": "string" 336 | }, 337 | { 338 | "isDefaultDisplay": false, 339 | "isHidden": false, 340 | "name": "Version", 341 | "type": "string" 342 | }, 343 | { 344 | "isDefaultDisplay": false, 345 | "isHidden": false, 346 | "name": "SrcDvcIdType", 347 | "type": "string" 348 | } 349 | ] 350 | } 351 | } 352 | } 353 | '@ 354 | -------------------------------------------------------------------------------- /dataconnectors/okta/oktaccp.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0", 4 | "parameters": { 5 | "apikey": { 6 | "defaultValue": "-NA-", 7 | "minLength": 1, 8 | "type": "SecureString" 9 | }, 10 | "domainname": { 11 | "defaultValue": "Enter Okta domainname value", 12 | "minLength": 1, 13 | "type": "String" 14 | }, 15 | "workspace": { 16 | "defaultValue": "Sentinel Workspace Name", 17 | "type": "String" 18 | }, 19 | "dataCollectionEndpoint": { 20 | "defaultValue": "Data Collection Endpoint name (found via the DCR Overview Page)", 21 | "type": "String" 22 | }, 23 | "dataCollectionRuleImmutableId": { 24 | "defaultValue": "DCR immutableId (found via the JSON view on the DCR)", 25 | "type": "String" 26 | } 27 | }, 28 | "variables": { 29 | "_dataConnectorContentIdConnections2": "OktaSSOv2Connections", 30 | "_oktaDomainHostName": "[first(split(parameters('domainname'), '.'))]" 31 | }, 32 | "resources": [ 33 | { 34 | "type": "Microsoft.OperationalInsights/workspaces/providers/dataConnectors", 35 | "apiVersion": "2023-02-01-preview", 36 | "name": "[concat(parameters('workspace'), '/Microsoft.SecurityInsights/OktaDCV1_2_', variables('_oktaDomainHostName'))]", 37 | "location": "westus2", 38 | "kind": "RestApiPoller", 39 | "properties": { 40 | "connectorDefinitionName": "OktaSSOv2", 41 | "dcrConfig": { 42 | "dataCollectionEndpoint": "[parameters('dataCollectionEndpoint')]", 43 | "dataCollectionRuleImmutableId": "[parameters('dataCollectionRuleImmutableId')]", 44 | "streamName": "Custom-OktaSSO_CL" 45 | }, 46 | "addOnAttributes": { 47 | "Domain": "[parameters('domainname')]" 48 | }, 49 | "dataType": "Okta System Log API", 50 | "response": { 51 | "eventsJsonPaths": [ 52 | "$" 53 | ], 54 | "format": "json" 55 | }, 56 | "paging": { 57 | "pagingType": "LinkHeader" 58 | }, 59 | "auth": { 60 | "apiKeyName": "Authorization", 61 | "ApiKey": "[parameters('apikey')]", 62 | "apiKeyIdentifier": "SSWS", 63 | "type": "APIKey" 64 | }, 65 | "request": { 66 | "apiEndpoint": "[concat('https://', parameters('domainname'), '/api/v1/logs')]", 67 | "rateLimitQPS": 10, 68 | "queryWindowInMin": 5, 69 | "httpMethod": "GET", 70 | "retryCount": 3, 71 | "timeoutInSeconds": 60, 72 | "headers": { 73 | "Accept": "application/json", 74 | "User-Agent": "Scuba" 75 | }, 76 | "startTimeAttributeName": "since", 77 | "endTimeAttributeName": "until" 78 | }, 79 | "isActive": true 80 | } 81 | } 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /enable-sentinel-mdfc-sub-con/custom-role.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "/providers/Microsoft.Authorization/roleDefinitions/46e686a4-16b0-401b-945d-e83196987077", 3 | "properties": { 4 | "roleName": "Microsoft Sentinel Defender for Cloud Connector Contributor", 5 | "description": "Microsoft Sentinel Defender for Cloud Connector Contributor", 6 | "assignableScopes": [ 7 | "/providers/Microsoft.Management/managementGroups/869d26a0-a8cb-4068-aba6-c3284b615873" 8 | ], 9 | "permissions": [ 10 | { 11 | "actions": [ 12 | "Microsoft.Security/register/action", 13 | "Microsoft.Security/*/read", 14 | "Microsoft.Resources/subscriptions/read", 15 | "Microsoft.SecurityInsights/dataConnectors/read", 16 | "Microsoft.SecurityInsights/dataConnectors/write", 17 | "Microsoft.Security/settings/write" 18 | ], 19 | "notActions": [], 20 | "dataActions": [], 21 | "notDataActions": [] 22 | } 23 | ] 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /enable-sentinel-mdfc-sub-con/customRoleDeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-08-01/managementGroupDeploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "roleName": { 6 | "defaultValue": "Microsoft Sentinel Defender for Cloud Connector Contributor", 7 | "type": "String", 8 | "metadata": { 9 | "description": "Friendly name of the role definition" 10 | } 11 | }, 12 | "roleDescription": { 13 | "defaultValue": "Microsoft Sentinel Defender for Cloud Connector Contributor", 14 | "type": "String", 15 | "metadata": { 16 | "description": "Detailed description of the role definition" 17 | } 18 | } 19 | }, 20 | "variables": { 21 | "roleDefName": "[guid(managementGroup().id, string(parameters('roleName')))]" 22 | }, 23 | "resources": [ 24 | { 25 | "type": "Microsoft.Authorization/roleDefinitions", 26 | "apiVersion": "2022-04-01", 27 | "name": "[variables('roleDefName')]", 28 | "properties": { 29 | "roleName": "[parameters('roleName')]", 30 | "description": "[parameters('roleDescription')]", 31 | "type": "customRole", 32 | "permissions": [ 33 | { 34 | "actions": [ 35 | "Microsoft.Security/register/action", 36 | "Microsoft.Security/*/read", 37 | "Microsoft.Resources/subscriptions/read", 38 | "Microsoft.SecurityInsights/dataConnectors/read", 39 | "Microsoft.SecurityInsights/dataConnectors/write", 40 | "Microsoft.Security/settings/write" 41 | ], 42 | "notActions": [], 43 | "dataActions": [], 44 | "notDataActions": [] 45 | } 46 | ], 47 | "assignableScopes": [ 48 | "[managementGroup().id]" 49 | ] 50 | } 51 | } 52 | ] 53 | } 54 | -------------------------------------------------------------------------------- /enable-sentinel-mdfc-sub-con/logicapp.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | !$defaultBg = '#d9dadb' 3 | !$borderColor = '#999a9b' 4 | !$darkColor = '#494a4b' 5 | skinparam ActivityDiamondBackgroundColor $defaultBg 6 | skinparam ActivityDiamondBorderColor $borderColor 7 | skinparam ActivityBorderColor $borderColor 8 | skinparam ArrowColor $darkColor 9 | skinparam ActivityBarColor $darkColor 10 | skinparam ActivityStartColor $darkColor 11 | skinparam ActivityEndColor $darkColor 12 | start 13 | fork 14 | :Recurrence; 15 | endfork 16 | #eadef8:InitializeVariable 17 | Initialize Variable - Run Time; 18 | #eadef8:InitializeVariable 19 | Initialize Variable - sentinel-subscriptionid; 20 | #eadef8:InitializeVariable 21 | Initialize Variable - sentinel-workspacename; 22 | #eadef8:InitializeVariable 23 | Initialize Variable - sentinel-resourcegroupname; 24 | #eadef8:InitializeVariable 25 | Initialize variable - taskOutput; 26 | #eadef8:InitializeVariable 27 | Initialize Variable - new-connected-subscriptions; 28 | #eadef8:InitializeVariable 29 | Initialize variable - subscriptionsToCheck; 30 | #eadef8:InitializeVariable 31 | Initialize variable - alertSyncEnabled; 32 | #c4e2ff:ApiConnection 33 | List subscriptions; 34 | :Select 35 | Parse Subscription List; 36 | if (Check for Excluded Subscriptions) then (yes) 37 | :Query 38 | Filter out excluded subscriptions; 39 | #eadef8:SetVariable 40 | Set variable - subscriptionsToCheck (excluded filtered); 41 | 42 | else (no) 43 | #eadef8:SetVariable 44 | Set variable - subscriptionsToCheck; 45 | 46 | endif 47 | fork 48 | if (Check for valid list of Subscriptions - Enable Bi-Directional Alert Sync) then (yes) 49 | while (Enable Bi-Directional Alert Sync) 50 | #d9eced:Http 51 | Check for Alert Sync Settings; 52 | #eadef8:SetVariable 53 | Set variable - alertSyncEnabled; 54 | if (Check Alert Sync Settings) then (yes) 55 | 56 | else (no) 57 | #d9eced:Http 58 | Enable Alert Sync; 59 | :AppendToArrayVariable 60 | Append to array variable; 61 | 62 | endif 63 | endwhile 64 | 65 | else (no) 66 | 67 | endif 68 | if (Check for Output) then (yes) 69 | if (Condition - Log Results) then (yes) 70 | while (Send to Log Analytics) 71 | #c4e2ff:ApiConnection 72 | Send Data; 73 | endwhile 74 | 75 | else (no) 76 | 77 | endif 78 | if (Condition - Send Email) then (yes) 79 | :Table 80 | Create HTML table; 81 | #c4e2ff:ApiConnection 82 | Send an email (V2); 83 | 84 | else (no) 85 | 86 | endif 87 | 88 | else (no) 89 | 90 | endif 91 | forkagain 92 | if (Check for valid list of Subscriptions - Enable Data Connector Group) then (yes) 93 | #c4e2ff:ApiConnection 94 | Get All Data Connectors; 95 | :ParseJson 96 | Parse Data Connector JSON; 97 | :Query 98 | Return Defender for Cloud Data Connectors; 99 | :Select 100 | Parse Enabled Subscription Ids; 101 | :Query 102 | Filter Subscriptions Not Connected; 103 | if (Check for Subscriptions to Enable) then (yes) 104 | while (Enable disconnected Subscriptions) 105 | #d9eced:Http 106 | Enable the Data Connector; 107 | :AppendToArrayVariable 108 | Append to array variable 2; 109 | endwhile 110 | 111 | else (no) 112 | 113 | endif 114 | 115 | else (no) 116 | 117 | endif 118 | if (Check for Output) then (yes) 119 | if (Condition - Log Results) then (yes) 120 | while (Send to Log Analytics) 121 | #c4e2ff:ApiConnection 122 | Send Data; 123 | endwhile 124 | 125 | else (no) 126 | 127 | endif 128 | if (Condition - Send Email) then (yes) 129 | :Table 130 | Create HTML table; 131 | #c4e2ff:ApiConnection 132 | Send an email (V2); 133 | 134 | else (no) 135 | 136 | endif 137 | 138 | else (no) 139 | 140 | endif 141 | forkagain 142 | if (Check for valid list of Subscriptions - Register Subscriptions with Defender) then (yes) 143 | while (Register Subscriptions with Defender for Cloud) 144 | #d9eced:Http 145 | Check for Microsoft.Security Resource Provider Registration; 146 | if (Check for Registered Resource Provider) then (yes) 147 | #c4e2ff:ApiConnection 148 | Register resource provider - Microsoft.Security; 149 | :AppendToArrayVariable 150 | Append to array variable 3; 151 | 152 | else (no) 153 | 154 | endif 155 | endwhile 156 | 157 | else (no) 158 | 159 | endif 160 | if (Check for Output) then (yes) 161 | if (Condition - Log Results) then (yes) 162 | while (Send to Log Analytics) 163 | #c4e2ff:ApiConnection 164 | Send Data; 165 | endwhile 166 | 167 | else (no) 168 | 169 | endif 170 | if (Condition - Send Email) then (yes) 171 | :Table 172 | Create HTML table; 173 | #c4e2ff:ApiConnection 174 | Send an email (V2); 175 | 176 | else (no) 177 | 178 | endif 179 | 180 | else (no) 181 | 182 | endif 183 | endfork 184 | stop 185 | @enduml 186 | -------------------------------------------------------------------------------- /enable-sentinel-mdfc-sub-con/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Microsoft Sentinel Defender for Cloud Data Connector At Scale 3 | 4 | > ℹ️ This solution is no longer needed. Defender for Cloud is now integrated into the Defender XDR at the tenant level. You can integrate Defender XDR with Sentinel to sync all alerts and incidents. 5 | > See [Microsoft Defender for Cloud in Microsoft Defender XDR](https://learn.microsoft.com/en-us/microsoft-365/security/defender/microsoft-365-security-center-defender-cloud?view=o365-worldwide) 6 | 7 | This workflow will enable the Microsoft Defender for Cloud data connector in Microsoft Sentinel automatically for all subscriptions you have the logic app scoped to. The solution also provides the ability to: 8 | 9 | - Exclude Subscriptions 10 | - Log results to your Sentinel Workspace 11 | - Leverage a workbook to track and audit connector changes 12 | - Send Email Notifications 13 | 14 | > TLDR Version 15 | 16 | > [Step 1 - Deploy Custom Role](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fenable-sentinel-mdfc-sub-con%2FcustomRoleDeploy.json) ---> [Step 2 - Deploy Logic App](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fenable-sentinel-mdfc-sub-con%2FcustomRoleDeploy.json) ---> [Step 3 - Deploy Workbook](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fenable-sentinel-mdfc-sub-con%2FdefenderForCloudConnectorCoverage.json) 17 | 18 | > ---- 19 | 20 | - [Requirements](#requirements) 21 | - [Setup and Configuration](#setup-and-configuration) 22 | - [Create a Custom Role](#create-a-custom-role) 23 | - [Deploy the Logic App](#deploy-the-logic-app) 24 | - [Authorize Permissions](#authorize-permissions) 25 | - [Assign the Role to the Logic App System Managed Identity](#assign-the-role-to-the-logic-app-system-managed-identity) 26 | - [Enable the Logic App](#enable-the-logic-app) 27 | - [Working with Parameters](#working-with-parameters) 28 | - [Workbook](#workbook) 29 | - [Logic App Overview](#logic-app-overview) 30 | - [Credentials Used](#credentials-used) 31 | - [Workflow](#workflow) 32 | - [Troubleshooting](#troubleshooting) 33 | 34 | ## Requirements 35 | 36 | - The custom role described in [Create a Custom Role](##create-a-custom-role) 37 | - Rights to assign the role and scope to either subscriptions or management groups 38 | - Rights to complete the deployment, Log Analytics Contributor, Microsoft Sentinel Contributor, and Contributor. 39 | - Rights to create a custom role at the desired scope, such as Owner or User Access Administrator 40 | 41 | ## Setup and Configuration 42 | 43 | ### Create a Custom Role 44 | I highly reccomend creating the custom role to follow the principle of least privilege. 45 | There is no need to assign the built-in roles that provide more permissions than required. 46 | 47 | > You will need permissions to create custom roles at the management group level, such as Owner or User Access Administrator 48 | 49 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fenable-sentinel-mdfc-sub-con%2FcustomRoleDeploy.json) 50 | 51 | **Manual Steps** 52 | 53 | 1. Follow the steps outlined in [Create or update Azure custom roles using the Azure portal](https://learn.microsoft.com/en-us/azure/role-based-access-control/custom-roles-portal#start-from-scratch) 54 | 55 | 3. For the name I reccomend using **Microsoft Sentinel Defender for Cloud Connector Contributor** 56 | 57 | 4. For the role definition use the [custom role defintion](https://github.com/seanstark/sentinel-tools/blob/main/enable-sentinel-mdfc-sub-con/custom-role.json) json 58 | 59 | ### Deploy the Logic App 60 | 61 | > The Logic App will be deployed in a disabled state by default 62 | 63 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fenable-sentinel-mdfc-sub-con%2Fazuredeploy.json) 64 | 65 | ### Authorize Permissions 66 | Most of the logic leverages the System Managed Identity to perform tasks. However if you plan on sending email notifications you will need to authorize the Office 365 Outlook connector. 67 | 68 | 1. Navigate to the Logic App in the Azure Portal 69 | 2. Select API connections on the left hand side 70 | 3. Select the Office365-'logic app name' api connection 71 | 4. Select Edit API connection 72 | 5. Select Authorize and complete the authorization process 73 | 6. Make sure to click Save 74 | 75 | ### Assign the Role to the Logic App System Managed Identity 76 | > - If you would like to test the logic app first on a subset of subscriptions only assign the role to that scope 77 | > - If you can't use the custom role you will need to assign the System Managed Identity the Security Admin and Microsoft Sentinel Contributor. 78 | > - In the example below the role is assigned at the root managment group level. 79 | > - I would reccomend waiting 15 minutes after assigning the role before executing your first run of the logic. 80 | 81 | 1. Navigate to [Azure Management Groups](https://azmg.cmd.ms/) 82 | 2. Select your **Tenant Root Group** 83 | 3. Select **Access Control (IAM)** 84 | 4. Select **Add** > **Add Role Assignment** 85 | 5. Select the custom role you created earlier (Microsoft Sentinel Defender for Cloud Connector Contributor) 86 | 6. Under the **Members** tab select **Managed Identity** 87 | 7. Click Select **Members** and select the Logic App Managed Identity 88 | 8. Click **Next** > **Review + Assign** 89 | 90 | ### Enable the Logic App 91 | 1. From the Logic App **Overview** pane, select **Enable** in the top menu bar 92 | 2. If you would like to run the logic immediately you can select **Run Trigger** 93 | 94 | ### Working with Parameters 95 | 96 | There are several parameters you can update in the logic app 97 | 98 | 1. Navigate to the Logic App in the Azure Portal and select **Logic App Designer** 99 | 2. Select **Parameters** 100 | 3. Update the parameter according to the table below 101 | 4. Make sure to the **Save** the Logic App 102 | 103 | > true or false values are case sensitive 104 | 105 | | Parameter | Use Case | Type | Value Example | 106 | |----------------|-------------------------------|------| -----------------------------| 107 | | emailRecipients | Email Addresses to send notifications to. Semi-colon seperated values | string | ``` user1@domain.com;dl@domain.com ``` | 108 | | excludedSubscriptions | Exclude subscriptions from being enabled | array | ``` ["ada06e68-375e-4210-b43a-c6fgdcebf41c5","ada0dht8-375e-4210-be3a-c6cacebf41c5"] ``` | 109 | | logResults | Log results to your Sentinel Workspace | string | ``` true or false ``` | 110 | | sendEmail | Send email notifications on subscriptions that were enabled | string | ``` true or false ``` | 111 | | sentinel-resourcegroupname | The reource group name where Sentinel Resides | string | ``` sentinel-prd ``` | 112 | | sentinel-subscriptionid | The subscription ID where Sentinel resides | string | ``` ada06e68-375e-4210-b43a-c6fgdcebf41c5 ``` | 113 | | sentinel-workspacename | The workspace name of Sentinel | string | ``` sentinel-wrk-prd ``` | 114 | 115 | ## Workbook 116 | To fully leverage this workbook you will need to enable logging within the Logic App by setting the **logResults** parameter to **true**. You will also need to ensure Azure Activity logs are being sent to your Sentinel workspace. 117 | 118 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fenable-sentinel-mdfc-sub-con%2FdefenderForCloudConnectorCoverage.json) 119 | 120 | ![](workbook.png) 121 | 122 | ## Logic App Overview 123 | 124 | ### Credentials Used 125 | 126 | | Credential | Type | Use Case | 127 | |----------------|--------------|-------------| 128 | | Logic App System Managed Identity | System Managed Identity | Get Azure Subscriptions, Defender for Cloud Settings, Sentinel Data Connectors. Put Defender for Cloud Settings, Enable Bi-Directional Incident Sync, Create the Sentinel Data Connector, Register the Microsoft.Security Resource Provider 129 | | Log Analytics API Key | Workspace Shared Key | Log Results to a custom table in your Sentinel Workspace | 130 | | Azure AD Account | User | Sending Email Notifications after execution, graph api permissions to an Office 365 Mailbox | 131 | 132 | ### Workflow 133 | 134 | The Logic app runs on a re-occuring schedule every 12 hours by default. The overall sequence of events are as follows. 135 | 136 | ![](logic-app.svg) 137 | 138 | ## Troubleshooting 139 | 140 | - The Logic App won't present any errors when the system managed identity doesn't have permissions to list subscriptions in the tenant. 141 | - Ensure the system managed identity is assign the custom role and applied to either management group or subscription scopes. 142 | - Ensure the Logic App API Connections are properly authorized 143 | - Ensure the loganalyticsdatacollector-'logic app name' API connection has the correct workspaceid and workspace shared key for logging results 144 | 145 | 146 | -------------------------------------------------------------------------------- /enable-sentinel-mdfc-sub-con/workbook.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/enable-sentinel-mdfc-sub-con/workbook.png -------------------------------------------------------------------------------- /images/dcr-json.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/images/dcr-json.png -------------------------------------------------------------------------------- /images/dcr-verify.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/images/dcr-verify.png -------------------------------------------------------------------------------- /images/dcr-view.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/images/dcr-view.png -------------------------------------------------------------------------------- /images/dcr.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/images/dcr.gif -------------------------------------------------------------------------------- /images/github_pat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/seanstark/sentinel-tools/1832eec427e35a748d236ac2506b30da25f7dfbc/images/github_pat.png -------------------------------------------------------------------------------- /logstash-cef-adx/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | tcp { 3 | port => 5514 4 | type => syslog 5 | tags => [ "PAN-OS_syslog" ] 6 | } 7 | udp { 8 | port => 5514 9 | type => syslog 10 | tags => [ "PAN-OS_syslog" ] 11 | } 12 | } 13 | 14 | filter { 15 | if "PAN-OS_syslog" in [tags] { 16 | 17 | # Log types are "TRAFFIC", "THREAT", "CONFIG", "SYSTEM" and "HIP-MATCH". 18 | 19 | # Traffic log fields: https://docs.paloaltonetworks.com/pan-os/9-0/pan-os-admin/monitoring/use-syslog-for-monitoring/syslog-field-descriptions/traffic-log-fields.html 20 | if ([message] =~ /TRAFFIC/) { 21 | csv { 22 | source => "message" 23 | columns => [ 24 | "FUTURE_USE", "ReceiveTime", "SerialNumber", "Type", "Threat_ContentType", "FUTURE_USE", 25 | "GeneratedTime", "SourceIP", "DestinationIP", "NATSourceIP", "NATDestinationIP", "RuleName", 26 | "SourceUser", "DestinationUser", "Application", "VirtualSystem", "SourceZone", "DestinationZone", 27 | "InboundInterface", "OutboundInterface", "LogAction", "FUTURE_USE", "SessionID", 28 | "RepeatCount", "SourcePort", "DestinationPort", "NATSourcePort", "NATDestinationPort", "Flags", 29 | "Protocol", "Action", "Bytes", "BytesSent", "BytesReceived", "Packets", "StartTime", "ElapsedTime", 30 | "Category", "FUTURE_USE", "SequenceNumber", "ActionFlags", "SourceLocation", 31 | "DestinationLocation", "FUTURE_USE", "PacketsSent", "PacketsReceived", "SessionEndReason", 32 | "DeviceGroupHierarchyLevel1", "DeviceGroupHierarchyLevel2", "DeviceGroupHierarchyLevel3", 33 | "DeviceGroupHierarchyLevel4", "VirtualSystemName", "DeviceName", "ActionSource", "SourceVMUUID", 34 | "DestinationVMUUID", "TunnelID_IMSI", "MonitorTag_IMEI", "ParentSessionID", "ParentStartTime", 35 | "TunnelType", "SCTPAssociationID", "SCTPChunks", "SCTPChunksSent", "SCTPChunksReceived" 36 | ] 37 | } 38 | 39 | mutate { 40 | convert => [ "Bytes", "integer" ] 41 | convert => [ "BytesReceived", "integer" ] 42 | convert => [ "BytesSent", "integer" ] 43 | convert => [ "ElapsedTime", "integer" ] 44 | convert => [ "GeoIP.dma_code", "integer" ] 45 | convert => [ "GeoIP.latitude", "float" ] 46 | convert => [ "GeoIP.longitude", "float" ] 47 | convert => [ "NATDestinationPort", "integer" ] 48 | convert => [ "NATSourcePort", "integer" ] 49 | convert => [ "Packets", "integer" ] 50 | convert => [ "PacketsReceived", "integer" ] 51 | convert => [ "PacketsSent", "integer" ] 52 | convert => [ "SequenceNumber", "integer" ] 53 | 54 | add_tag => [ "PAN-OS_traffic"] 55 | } 56 | } 57 | 58 | # Threat log fields: https://docs.paloaltonetworks.com/pan-os/9-0/pan-os-admin/monitoring/use-syslog-for-monitoring/syslog-field-descriptions/threat-log-fields.html 59 | else if ([message] =~ /THREAT/) { 60 | csv { 61 | source => "message" 62 | columns => [ 63 | "FUTURE_USE", "ReceiveTime", "SerialNumber", "Type", "Threat_ContentType", "FUTURE_USE", 64 | "GeneratedTime", "SourceIP", "DestinationIP", "NATSourceIP", "NATDestinationIP", "RuleName", 65 | "SourceUser", "DestinationUser", "Application", "VirtualSystem", "SourceZone", "DestinationZone", 66 | "InboundInterface", "OutboundInterface", "LogAction", "FUTURE_USE", "SessionID", 67 | "RepeatCount", "SourcePort", "DestinationPort", "NATSourcePort", "NATDestinationPort", "Flags", 68 | "Protocol", "Action", "URL_Filename", "ThreatID", "Category", "Severity", "Direction", 69 | "SequenceNumber", "ActionFlags", "SourceLocation", "DestinationLocation", "FUTURE_USE", 70 | "ContentType", "PCAP_ID", "FileDigest", "Cloud", "URLIndex", "UserAgent", "FileType", 71 | "X-Forwarded-For", "Referer", "Sender", "Subject", "Recipient", "ReportID", 72 | "DeviceGroupHierarchyLevel1", "DeviceGroupHierarchyLevel2", "DeviceGroupHierarchyLevel3", 73 | "DeviceGroupHierarchyLevel4", "VirtualSystemName", "DeviceName", "FUTURE_USE", "SourceVMUUID", 74 | "DestinationVMUUID", "HTTPMethod", "TunnelID_IMSI", "MonitorTag_IMEI", "ParentSessionID", 75 | "ParentStartTime", "TunnelType", "ThreatCategory", "ContentVersion", "FUTURE_USE" , 76 | "SCTPAssociationID", "PayloadProtocolID", "HTTPHeaders" 77 | ] 78 | } 79 | 80 | mutate { 81 | convert => [ "GeoIP.dma_code", "integer" ] 82 | convert => [ "GeoIP.latitude", "float" ] 83 | convert => [ "GeoIP.longitude", "float" ] 84 | convert => [ "NATDestinationPort", "integer" ] 85 | convert => [ "NATSourcePort", "integer" ] 86 | convert => [ "SequenceNumber", "integer" ] 87 | 88 | add_tag => ["PAN-OS_threat"] 89 | } 90 | } 91 | 92 | mutate { 93 | # Original message has been fully parsed, so remove it. 94 | remove_field => [ "message" ] 95 | } 96 | 97 | # Geolocate logs that have SourceIP if that SourceIP is a non-RFC1918 address 98 | if [SourceIP] and [SourceIP] !~ "(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)|(^169\.254\.)" { 99 | geoip { 100 | source => "SourceIP" 101 | target => "SourceIPGeo" 102 | } 103 | 104 | # Delete 0,0 in SourceIPGeo.location if equal to 0,0 105 | if ([SourceIPGeo.location] and [SourceIPGeo.location] =~ "0,0") { 106 | mutate { 107 | replace => [ "SourceIPGeo.location", "" ] 108 | } 109 | } 110 | } 111 | 112 | # Geolocate logs that have DestinationIP and if that DestinationIP is a non-RFC1918 address 113 | if [DestinationIP] and [DestinationIP] !~ "(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)|(^169\.254\.)" { 114 | geoip { 115 | source => "DestinationIP" 116 | target => "DestinationIPGeo" 117 | } 118 | 119 | # Delete 0,0 in DestinationIPGeo.location if equal to 0,0 120 | if ([DestinationIPGeo.location] and [DestinationIPGeo.location] =~ "0,0") { 121 | mutate { 122 | replace => [ "DestinationIPGeo.location", "" ] 123 | } 124 | } 125 | } 126 | 127 | # Takes the 5-tuple of source address, source port, destination address, destination port, and protocol and does a SHA1 hash to fingerprint the flow. This is a useful 128 | # way to be able to do top N terms queries on flows, not just on one field. 129 | if [SourceIP] and [DestinationIP] { 130 | fingerprint { 131 | concatenate_sources => true 132 | method => "SHA1" 133 | key => "logstash" 134 | source => [ "SourceIP", "SourcePort", "DestinationIP", "DestinationPort", "Protocol" ] 135 | } 136 | } 137 | 138 | } 139 | } 140 | 141 | output { 142 | kusto { 143 | path => "/tmp/kusto/%{+YYYY-MM-dd-HH-mm-ss}.txt" 144 | ingest_url => "" 145 | app_id => "" 146 | app_key => "" 147 | app_tenant => "" 148 | database => "" 149 | table => "CommonSecurityLog" 150 | json_mapping => "CEF" 151 | } 152 | } 153 | -------------------------------------------------------------------------------- /logstash-cef-adx/readme.md: -------------------------------------------------------------------------------- 1 | placeholder 2 | -------------------------------------------------------------------------------- /logstash/logstash-syslog-dcr.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "dataCollectionRuleName": { 6 | "type": "String", 7 | "defaultValue": "logstash-sentinel", 8 | "metadata": { 9 | "description": "Specify the name of the Data Collection Rule to create." 10 | } 11 | }, 12 | "location": { 13 | "defaultValue": "", 14 | "type": "String", 15 | "metadata": { 16 | "description": "Specify the location in which to create the Data Collection Rule." 17 | } 18 | }, 19 | "workspaceResourceId": { 20 | "type": "String", 21 | "metadata": { 22 | "description": "Specify the Azure resource ID of the Log Analytics workspace to use." 23 | } 24 | }, 25 | "dataCollectionEndpointResourceId": { 26 | "type": "String", 27 | "metadata": { 28 | "description": "Specify the Azure resource ID of the Data Collection Endpoint to use." 29 | } 30 | }, 31 | "transformKql": { 32 | "defaultValue": "source | project TimeGenerated = ls_timestamp, HostName = hostname, Facility = facility, SeverityLevel = severity, SyslogMessage = message, ProcessID = pid, ProcessName = process_name, Computer = hostname, HostIP = ip, EventTime = timestamp, SourceSystem = service", 33 | "type": "String", 34 | "metadata": { 35 | "description": "The KQL statement to transform the data on ingest. The TimeGenerated datetime field is required" 36 | } 37 | } 38 | }, 39 | "resources": [ 40 | { 41 | "type": "Microsoft.Insights/dataCollectionRules", 42 | "apiVersion": "2021-09-01-preview", 43 | "name": "[parameters('dataCollectionRuleName')]", 44 | "location": "[parameters('location')]", 45 | "properties": { 46 | "dataCollectionEndpointId": "[parameters('dataCollectionEndpointResourceId')]", 47 | "streamDeclarations": { 48 | "Custom-Microsoft-Syslog": { 49 | "columns": [ 50 | { 51 | "name": "ls_timestamp", 52 | "type": "datetime" 53 | }, 54 | { 55 | "name": "hostname", 56 | "type": "string" 57 | }, 58 | { 59 | "name": "facility", 60 | "type": "string" 61 | }, 62 | { 63 | "name": "severity", 64 | "type": "string" 65 | }, 66 | { 67 | "name": "message", 68 | "type": "string" 69 | }, 70 | { 71 | "name": "pid", 72 | "type": "int" 73 | }, 74 | { 75 | "name": "process_name", 76 | "type": "string" 77 | }, 78 | { 79 | "name": "ip", 80 | "type": "string" 81 | }, 82 | { 83 | "name": "timestamp", 84 | "type": "datetime" 85 | }, 86 | { 87 | "name": "service", 88 | "type": "string" 89 | } 90 | ] 91 | } 92 | }, 93 | "destinations": { 94 | "logAnalytics": [ 95 | { 96 | "workspaceResourceId": "[parameters('workspaceResourceId')]", 97 | "name": "Custom-Microsoft-Syslog-Workspace" 98 | } 99 | ] 100 | }, 101 | "dataFlows": [ 102 | { 103 | "streams": [ 104 | "Custom-Microsoft-Syslog" 105 | ], 106 | "destinations": [ 107 | "Custom-Microsoft-Syslog-Workspace" 108 | ], 109 | "transformKql": "[parameters('transformKql')]", 110 | "outputStream": "Microsoft-Syslog" 111 | } 112 | ] 113 | } 114 | } 115 | ], 116 | "outputs": { 117 | "dataCollectionRuleId": { 118 | "type": "String", 119 | "value": "[resourceId('Microsoft.Insights/dataCollectionRules', parameters('dataCollectionRuleName'))]" 120 | } 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /logstash/readme.md: -------------------------------------------------------------------------------- 1 | ## Sending logs to Sentinel using Logstash 2 | 3 | ## Step 1 - Deploy the Data Collection Rule 4 | Deploy the custom data collection rule in Azure 5 | 6 | | Syslog | 7 | | ------ | 8 | | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Flogstash%2Flogstash-syslog-dcr.json) | 9 | 10 | ## Step 2 - Create a logstash configuration file 11 | Default directory is /etc/logstash/conf.d/ 12 | 13 | ### Logstash Configuration Example 14 | 15 | #### Syslog 16 | 17 | ``` ruby 18 | input { 19 | syslog { 20 | port => 11536 21 | type => syslog 22 | } 23 | } 24 | filter { 25 | grok { 26 | match => { 27 | "message" => "%{SYSLOGTIMESTAMP:ls_timestamp} %{SYSLOGHOST:hostname} %{DATA:proc}(?:\[%{POSINT:pid}\])?:%{SPACE}%{TIMESTAMP_ISO8601:timestamp} %{LOGLEVEL:severity} \[%{DATA:proctag}\]%{SPACE}%{GREEDYDATA:message}" 28 | } 29 | } 30 | mutate { 31 | replace => { 32 | "service" => "logstash" 33 | "ip" => "%{[host][ip]}" 34 | "hostname" => "%{[host][hostname]}" 35 | "severity" => "%{[log][syslog][severity][name]}" 36 | "facility" => "%{[log][syslog][facility][name]}" 37 | "pid" => "%{[process][pid]}" 38 | "process_name" => "%{[process][name]}" 39 | } 40 | convert => { 41 | "[pid]" => "integer" 42 | } 43 | } 44 | if [process_name] == "%{[process][name]}" { 45 | mutate { 46 | remove_field => ["process_name", "pid"] 47 | } 48 | } 49 | } 50 | output { 51 | microsoft-sentinel-logstash-output-plugin { 52 | client_app_Id => "" 53 | client_app_secret => "" 54 | tenant_id => "" 55 | data_collection_endpoint => "" 56 | dcr_immutable_id => "" 57 | dcr_stream_name => "Custom-Microsoft-Syslog" 58 | } 59 | } 60 | ``` 61 | 62 | ## Step 3 - Configure rsyslog to forward logs to logstash 63 | 64 | Update your rsyslog.conf or other rsyslog config files to sent to the syslog input port you defined above in the logstash config file. Example: 65 | 66 | ``` 67 | # Send all syslog to logstash 68 | *.* @0.0.0.0:11536 69 | ``` 70 | 71 | ## Step 4 - Test the configuration 72 | 73 | Run logstash interactively to view console output during testing to ensure logs are being sent to the log analytics workspace 74 | 75 | 1. Stop the logstash service first 76 | ``` 77 | service logstash stop 78 | ``` 79 | 80 | 2. Start logstash in interactive mode. 81 | 82 | ( Specify the full path to the configuration file after -f ) 83 | ``` 84 | /usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/sentinel.conf 85 | ``` 86 | 87 | 4. Verify in the console output logstash is listening on the defined local port and logs are getting sent to log analytics. 88 | 89 | ![image](https://github.com/seanstark/sentinel-tools/assets/84108246/4878d36c-094a-4405-8607-776ccf3fa4e3) 90 | 91 | 5. You can stop the interactive process by entering ctrl + C 92 | 93 | ## Step 5 - Run logstash as service 94 | 95 | 1. Start logstash as service 96 | 97 | Logstash will use any configuration files you have in the logstash config directory, /etc/logstash/conf.d/ by default. 98 | ``` 99 | serivce logstash start 100 | ``` 101 | 102 | 3. Verify the service is running 103 | ``` 104 | serivce logstash status 105 | ``` 106 | 107 | ## Troubleshooting 108 | 109 | ### Debug Logging 110 | You can turn on debug logging with logstash via different methods. The methods below will work when running logstash in interactive mode. 111 | 112 | 1. Stop the logstash service first 113 | ``` 114 | service logstash stop 115 | ``` 116 | 117 | 2. Add the below to your logstash configuration output section 118 | ``` ruby 119 | stdout { 120 | codec => rubydebug{} 121 | } 122 | ``` 123 | 124 | 3. Start logstash in interactive moode. 125 | 126 | ( Specify the full path to the configuration file after -f ) 127 | ``` 128 | /usr/share/logstash/bin/logstash -f /etc/logstash/conf.d/sentinel.conf 129 | ``` 130 | 131 | 5. To enable further debug output, open another ssh sessions and run the below 132 | ``` 133 | curl -XPUT 'localhost:9600/_node/logging?pretty' -H 'Content-Type: application/json' -d' 134 | { 135 | "logger.logstash.inputs.syslog" : "DEBUG", 136 | "logger.logstash.filters.grok" : "DEBUG", 137 | "logger.logstash.outputs.microsoftsentineloutput" : "DEBUG" 138 | } 139 | ' 140 | ``` 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /playbooks/get-incident-chatgpt4/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "title": "", 6 | "description": "", 7 | "prerequisites": "", 8 | "postDeployment": [], 9 | "prerequisitesDeployTemplateFile": "", 10 | "lastUpdateTime": "", 11 | "entities": [], 12 | "tags": [], 13 | "support": { 14 | "tier": "community", 15 | "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" 16 | }, 17 | "author": { 18 | "name": "" 19 | } 20 | }, 21 | "parameters": { 22 | "PlaybookName": { 23 | "defaultValue": "get-incident-chatgpt4", 24 | "type": "string" 25 | } 26 | }, 27 | "variables": { 28 | "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]", 29 | "Openaigpt4ipConnectionName": "[concat('Openaigpt4ip-', parameters('PlaybookName'))]" 30 | }, 31 | "resources": [ 32 | { 33 | "properties": { 34 | "provisioningState": "Succeeded", 35 | "state": "Enabled", 36 | "definition": { 37 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 38 | "contentVersion": "1.0.0.0", 39 | "parameters": { 40 | "$connections": { 41 | "defaultValue": {}, 42 | "type": "Object" 43 | } 44 | }, 45 | "triggers": { 46 | "Microsoft_Sentinel_incident": { 47 | "type": "ApiConnectionWebhook", 48 | "inputs": { 49 | "body": { 50 | "callback_url": "@{listCallbackUrl()}" 51 | }, 52 | "host": { 53 | "connection": { 54 | "name": "@parameters('$connections')['azuresentinel']['connectionId']" 55 | } 56 | }, 57 | "path": "/incident-creation" 58 | } 59 | } 60 | }, 61 | "actions": { 62 | "For_each": { 63 | "foreach": "@triggerBody()?['object']?['properties']?['Alerts']", 64 | "actions": { 65 | "Create_completion_-_OpenAI": { 66 | "runAfter": { 67 | "Defender_365_-_Get_Incident": [ 68 | "Succeeded" 69 | ] 70 | }, 71 | "type": "ApiConnection", 72 | "inputs": { 73 | "body": { 74 | "model": "text-davinci-003" 75 | }, 76 | "host": { 77 | "connection": { 78 | "name": "@parameters('$connections')['openaigpt4ip']['connectionId']" 79 | } 80 | }, 81 | "method": "post", 82 | "path": "/v1/completions" 83 | } 84 | }, 85 | "Defender_365_-_Get_Incident": { 86 | "runAfter": {}, 87 | "type": "Http", 88 | "inputs": { 89 | "authentication": { 90 | "audience": "https://graph.microsoft.com", 91 | "type": "ManagedServiceIdentity" 92 | }, 93 | "method": "GET", 94 | "uri": "https://graph.microsoft.com/v1.0/security/alerts_v2/@{last(split(first(split(items('For_each')?['properties']?['alertLink'],'?tid')),'/'))}" 95 | } 96 | } 97 | }, 98 | "runAfter": {}, 99 | "type": "Foreach" 100 | } 101 | }, 102 | "outputs": {} 103 | }, 104 | "parameters": { 105 | "$connections": { 106 | "value": { 107 | "azuresentinel": { 108 | "connectionId": "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", 109 | "connectionName": "[variables('MicrosoftSentinelConnectionName')]", 110 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]", 111 | "connectionProperties": { 112 | "authentication": { 113 | "type": "ManagedServiceIdentity" 114 | } 115 | } 116 | }, 117 | "openaigpt4ip": { 118 | "connectionId": "[resourceId('Microsoft.Web/connections', variables('Openaigpt4ipConnectionName'))]", 119 | "connectionName": "[variables('Openaigpt4ipConnectionName')]", 120 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Openaigpt4ip')]" 121 | } 122 | } 123 | } 124 | } 125 | }, 126 | "name": "[parameters('PlaybookName')]", 127 | "type": "Microsoft.Logic/workflows", 128 | "location": "[resourceGroup().location]", 129 | "tags": { 130 | "hidden-SentinelTemplateName": "get-incident-chatgpt4", 131 | "hidden-SentinelTemplateVersion": "1.0" 132 | }, 133 | "identity": { 134 | "type": "SystemAssigned" 135 | }, 136 | "apiVersion": "2017-07-01", 137 | "dependsOn": [ 138 | "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", 139 | "[resourceId('Microsoft.Web/connections', variables('Openaigpt4ipConnectionName'))]" 140 | ] 141 | }, 142 | { 143 | "type": "Microsoft.Web/connections", 144 | "apiVersion": "2016-06-01", 145 | "name": "[variables('MicrosoftSentinelConnectionName')]", 146 | "location": "[resourceGroup().location]", 147 | "kind": "V1", 148 | "properties": { 149 | "displayName": "[variables('MicrosoftSentinelConnectionName')]", 150 | "customParameterValues": {}, 151 | "parameterValueType": "Alternative", 152 | "api": { 153 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]" 154 | } 155 | } 156 | }, 157 | { 158 | "type": "Microsoft.Web/connections", 159 | "apiVersion": "2016-06-01", 160 | "name": "[variables('Openaigpt4ipConnectionName')]", 161 | "location": "[resourceGroup().location]", 162 | "kind": "V1", 163 | "properties": { 164 | "displayName": "[variables('Openaigpt4ipConnectionName')]", 165 | "customParameterValues": {}, 166 | "api": { 167 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Openaigpt4ip')]" 168 | } 169 | } 170 | } 171 | ] 172 | } 173 | -------------------------------------------------------------------------------- /playbooks/get-incident-chatgpt4/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Deploy the Logic App 3 | 4 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fplaybooks%2Fget-incidentdetails%2Fazuredeploy.json) 5 | 6 | # Grant the System Managed Identity SecurityAlert.Read.All rights 7 | 8 | ``` Powershell 9 | Install-Module -Name AzureAD 10 | 11 | $msgraph = Get-AzureADServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" 12 | $permission = $msgraph.AppRoles | where Value -Like 'SecurityAlert.Read.All' | Select-Object -First 1 13 | 14 | $msi = Get-AzureADServicePrincipal -ObjectId 15 | New-AzureADServiceAppRoleAssignment -Id $permission.Id -ObjectId $msi.ObjectId -PrincipalId $msi.ObjectId -ResourceId $msgraph.ObjectId 16 | ``` 17 | -------------------------------------------------------------------------------- /playbooks/get-incidentdetails/azuredeploy.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", 3 | "contentVersion": "1.0.0.0", 4 | "metadata": { 5 | "title": "", 6 | "description": "", 7 | "prerequisites": "", 8 | "postDeployment": [], 9 | "prerequisitesDeployTemplateFile": "", 10 | "lastUpdateTime": "", 11 | "entities": [], 12 | "tags": [], 13 | "support": { 14 | "tier": "community", 15 | "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" 16 | }, 17 | "author": { 18 | "name": "" 19 | } 20 | }, 21 | "parameters": { 22 | "PlaybookName": { 23 | "defaultValue": "get-incidentdetails", 24 | "type": "string" 25 | } 26 | }, 27 | "variables": { 28 | "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]" 29 | }, 30 | "resources": [ 31 | { 32 | "properties": { 33 | "provisioningState": "Succeeded", 34 | "state": "Enabled", 35 | "definition": { 36 | "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", 37 | "contentVersion": "1.0.0.0", 38 | "parameters": { 39 | "$connections": { 40 | "defaultValue": {}, 41 | "type": "Object" 42 | } 43 | }, 44 | "triggers": { 45 | "Microsoft_Sentinel_incident": { 46 | "type": "ApiConnectionWebhook", 47 | "inputs": { 48 | "body": { 49 | "callback_url": "@{listCallbackUrl()}" 50 | }, 51 | "host": { 52 | "connection": { 53 | "name": "@parameters('$connections')['azuresentinel']['connectionId']" 54 | } 55 | }, 56 | "path": "/incident-creation" 57 | } 58 | } 59 | }, 60 | "actions": { 61 | "For_each": { 62 | "foreach": "@triggerBody()?['object']?['properties']?['Alerts']", 63 | "actions": { 64 | "Defender_365_-_Get_Incident": { 65 | "runAfter": {}, 66 | "type": "Http", 67 | "inputs": { 68 | "authentication": { 69 | "audience": "https://graph.microsoft.com", 70 | "type": "ManagedServiceIdentity" 71 | }, 72 | "method": "GET", 73 | "uri": "https://graph.microsoft.com/v1.0/security/alerts_v2/@{items('For_each')?['properties']?['systemAlertId']}" 74 | } 75 | } 76 | }, 77 | "runAfter": {}, 78 | "type": "Foreach" 79 | } 80 | }, 81 | "outputs": {} 82 | }, 83 | "parameters": { 84 | "$connections": { 85 | "value": { 86 | "azuresentinel": { 87 | "connectionId": "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", 88 | "connectionName": "[variables('MicrosoftSentinelConnectionName')]", 89 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]", 90 | "connectionProperties": { 91 | "authentication": { 92 | "type": "ManagedServiceIdentity" 93 | } 94 | } 95 | } 96 | } 97 | } 98 | } 99 | }, 100 | "name": "[parameters('PlaybookName')]", 101 | "type": "Microsoft.Logic/workflows", 102 | "location": "[resourceGroup().location]", 103 | "tags": { 104 | "hidden-SentinelTemplateName": "get-incidentdetails", 105 | "hidden-SentinelTemplateVersion": "1.0" 106 | }, 107 | "identity": { 108 | "type": "SystemAssigned" 109 | }, 110 | "apiVersion": "2017-07-01", 111 | "dependsOn": [ 112 | "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]" 113 | ] 114 | }, 115 | { 116 | "type": "Microsoft.Web/connections", 117 | "apiVersion": "2016-06-01", 118 | "name": "[variables('MicrosoftSentinelConnectionName')]", 119 | "location": "[resourceGroup().location]", 120 | "kind": "V1", 121 | "properties": { 122 | "displayName": "[variables('MicrosoftSentinelConnectionName')]", 123 | "customParameterValues": {}, 124 | "parameterValueType": "Alternative", 125 | "api": { 126 | "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Azuresentinel')]" 127 | } 128 | } 129 | } 130 | ] 131 | } 132 | -------------------------------------------------------------------------------- /playbooks/get-incidentdetails/readme.md: -------------------------------------------------------------------------------- 1 | 2 | # Deploy the Logic App 3 | 4 | [![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fseanstark%2Fsentinel-tools%2Fmain%2Fplaybooks%2Fget-incidentdetails%2Fazuredeploy.json) 5 | 6 | # Grant the System Managed Identity SecurityAlert.Read.All rights 7 | 8 | ``` Powershell 9 | Install-Module -Name AzureAD 10 | 11 | $msgraph = Get-AzureADServicePrincipal -Filter "AppId eq '00000003-0000-0000-c000-000000000000'" 12 | $permission = $msgraph.AppRoles | where Value -Like 'SecurityAlert.Read.All' | Select-Object -First 1 13 | 14 | $msi = Get-AzureADServicePrincipal -ObjectId 15 | New-AzureADServiceAppRoleAssignment -Id $permission.Id -ObjectId $msi.ObjectId -PrincipalId $msi.ObjectId -ResourceId $msgraph.ObjectId 16 | ``` 17 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | Various Sentinel Tools -------------------------------------------------------------------------------- /table_tools/copy-logAnalyticsTable.ps1: -------------------------------------------------------------------------------- 1 | <# 2 | .DESCRIPTION 3 | This script will create a new table from existing table with the same properties and schema. 4 | 5 | .PARAMETER sourceTableName 6 | Specify the exisiting table name 7 | 8 | .PARAMETER newTableName 9 | Specify the name of the new table 10 | 11 | .PARAMETER subscriptionID 12 | Specify the subscriptionID of your log analytics workspace 13 | 14 | .PARAMETER resourceGroupName 15 | Specify the Resource Group Name of your log analytics workspace 16 | 17 | .PARAMETER workspaceName 18 | Specify the log analytics workspace name where the source table resides 19 | 20 | .PARAMETER apiVersion 21 | Specify the apiVersion to use, not required 22 | 23 | .EXAMPLE 24 | Create a new log analytics table from an existing one 25 | .\copy-logAnalyticsTable.ps1 -sourceTableName 'Okta_CL' -newTableName 'Okta_AppTeam_CL' -subscriptionID 'ada06dd8-375e-4210-be3a-c6cdde3341c5' -resourceGroupName 'sentinel' -workspaceName 'sentinel-prd' 26 | #> 27 | 28 | param( 29 | [Parameter(Mandatory=$true)] 30 | [string]$sourceTableName, 31 | 32 | [Parameter(Mandatory=$true)] 33 | [string]$newTableName, 34 | 35 | [Parameter(Mandatory=$true)] 36 | [string]$subscriptionID, 37 | 38 | [Parameter(Mandatory=$true)] 39 | [string]$resourceGroupName, 40 | 41 | [Parameter(Mandatory=$true)] 42 | [string]$workspaceName, 43 | 44 | [Parameter(Mandatory=$false)] 45 | [string]$apiVersion = '2021-12-01-preview' 46 | 47 | ) 48 | 49 | #Get source table 50 | $sourceTable = Invoke-AzRestMethod -Path ('/subscriptions/{0}/resourcegroups/{1}/providers/microsoft.operationalinsights/workspaces/{2}/tables/{3}?api-version=2021-12-01-preview' -f $subscriptionID, $resourceGroupName, $workspaceName, $sourceTableName, $apiVersion) -Method GET 51 | 52 | # Convert From Json 53 | $sourceTable = $sourceTable.Content | ConvertFrom-Json 54 | 55 | # Check that new table name ends in CL 56 | If (!($newTableName.EndsWith('_CL'))){ 57 | $newTableName = $newTableName + "_CL" 58 | } 59 | 60 | # Udpate Schema with new table name and look for default workspace retention properties 61 | $sourceTable.properties.schema.Name = $newTableName 62 | If ($sourceTable.properties.retentionInDaysAsDefault){ 63 | $sourceTable.properties.totalRetentionInDays = $null 64 | $sourceTable.properties.retentionInDays = -1 65 | } 66 | 67 | #Create the new table 68 | Invoke-AzRestMethod -Path ('/subscriptions/{0}/resourcegroups/{1}/providers/microsoft.operationalinsights/workspaces/{2}/tables/{3}?api-version=2021-12-01-preview' -f $subscriptionID, $resourceGroupName, $workspaceName, $newTableName, $apiVersion) -Method PUT -Payload $($sourceTable | ConvertTo-Json -Depth 6) 69 | --------------------------------------------------------------------------------