diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 00000000..b242572e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "githubPullRequests.ignoredPullRequestBranches": [ + "main" + ] +} \ No newline at end of file diff --git a/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md b/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md index 4028a2a6..28bbcb38 100644 --- a/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md +++ b/Logic Apps/CfS-SendPromptbookResultsByEmail/readme.md @@ -18,7 +18,7 @@ Please refer to this article for further details and for a video on how to deplo 7. Enable the Logic App (the template deploys it in disabled state). ## Deployment button -[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fstefanpems%2Fcfs%2Frefs%2Fheads%2Fmain%2FCfS-SendPromptbookResultsByEmail%2FCfS-SendPromptbookResultsByEmail.json) +[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Frefs%2Fheads%2Fmain%2FLogic%2520Apps%2FCfS-SendPromptbookResultsByEmail%2FCfS-SendPromptbookResultsByEmail.json) ## Example of email customization: The following image shows: diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip new file mode 100644 index 00000000..39fb0b16 Binary files /dev/null and b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip differ diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md index 2ba19964..4bde5efd 100644 --- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/README.md @@ -1,7 +1,31 @@ # Phishing Analysis with Security Copilot **Author:** Craig Freyman -An automated solution for phishing email analysis using Azure Logic Apps, Function Apps, and Security Copilot. The system monitors a shared Office 365 mailbox, analyzes submitted emails for security threats, and generates detailed reports. Optional integration with Microsoft Sentinel for incident tracking and management. +An automated phishing email analysis solution leveraging Azure Logic Apps, Function Apps, and Security Copilot. With the right permissions, it can be deployed in five minutes. The Logic App monitors a shared Office 365 mailbox and triggers analysis when an email arrives. Security Copilot assesses the message for malicious intent, generating detailed HTML reports sent via email. Optional integration with Microsoft Sentinel enables incident tracking and management. This project demonstrates what’s possible with Security Copilot and Azure. It may work out of the box or require tuning — the good news: only a single Security Copilot prompt is used, making it easy to customize for your organization. + +## Overview + +This solution provides an end-to-end workflow for analyzing suspected phishing emails. The core functionality is handled by a series of Azure Functions that parse emails, extract important data, clean JSON input, and generate HTML reports. When combined with Security Copilot and Logic Apps, it creates a powerful system to distinguish between legitimate, spam, suspicious, and phishing emails. + +### Key Components + +1. **Email Parser Function** - Extracts and processes email content, including: + - Recursive unwrapping to find original emails (Handles forwarded, embedded, and TNEF-encapsulated messages) + - **Enhanced Proofpoint compatibility** for analyzing emails forwarded from Proofpoint + - Smart handling of Proofpoint and Microsoft SafeLinks URL protection + - Support for EML and MSG attachments + - Complete header, body, and attachment analysis + +2. **HTML Report Generator** - Creates formatted HTML reports from phishing analysis results: + - Color-coded categorization (Phishing, Spam, Legitimate, Suspicious) + - Detailed breakdown of analysis findings + - Organized by assessment categories + +### Purpose and Scope + +This solution is not designed to replace traditional spam filtering or email security gateways. Rather, it demonstrates the art of the possible when combining Security Copilot with Azure Functions and Logic Apps to enhance your existing email security infrastructure. This solution assumes you already have standard email gateway filtering in place. + +The primary focus is on providing deeper analysis of suspicious emails that have been forwarded for investigation, especially those coming from Proofpoint-protected environments. ## Prerequisites @@ -53,45 +77,109 @@ Optional Sentinel Parameters (use "none" if not using Sentinel): - Azure Monitor Logs (if using Sentinel) - Sentinel (if using Sentinel) -### 4. Configure Logic App Permissions +### 3. Configure Logic App Permissions If using Sentinel integration, assign these roles to the Logic App's managed identity: 1. "Log Analytics Reader" role (provides Microsoft.OperationalInsights/workspaces/read) 2. "Microsoft Sentinel Responder" role (provides Microsoft.SecurityInsights/incidents/comments/write) -Assign Permissions Step by Step +Assign Permissions Step by Step: 1. Open the Log Analytics workspace and go to "Access control (IAM)" - ![alt text](image.png) - 2. Select "Log Analytics Reader" - ![alt text](image-1.png) - 3. Select "Managed Identity" and select the name of your Logic App - ![alt text](image-2.png) - -4. Click next and assign permissions. -5. Repeat steps 1-4 for the "Microsoft Sentinel Responder" role. +4. Click next and assign permissions +5. Repeat steps 1-4 for the "Microsoft Sentinel Responder" role ### 5. Enable the Logic App The Logic App deploys in a disabled state. Enable it in the Logic App Overview to begin operation. +## Functions and Endpoints + +The Function App exposes the following HTTP endpoints: + +1. **Email Parser** (`/parse_email_functionapp`) + - Method: POST + - Body: Raw email content or JSON with Base64-encoded email + - Optional Parameters: `max_depth` (default=10) + - Returns: Parsed email data in JSON format + +2. **JSON Cleaner** (`/clean_json_functionapp`) + - Method: POST + - Body: JSON content (can include markdown notation) + - Returns: Cleaned JSON with markdown removed and nulls replaced + +3. **HTML Report Generator** (`/generate_html_report_functionapp`) + - Method: POST + - Body: Phishing analysis JSON + - Returns: Formatted HTML report + +4. **Regex Extractor** (`/extract_regex_functionapp`) + - Method: POST + - Body: JSON with `pattern` and `subject` fields + - Returns: Match results in JSON format + +## Email Parsing Capabilities + +### Email Format Support +- Regular MIME emails (text/plain, text/html) +- Embedded emails (message/rfc822) +- TNEF attachments (winmail.dat) +- EML and MSG file attachments +- Forwarded emails from various email clients (Gmail, Outlook, Apple Mail) +- Proofpoint-reported phishing emails + +### Content Extraction +- Complete header analysis +- Body content from both plain text and HTML +- URL extraction and normalization +- Domain extraction +- IP address extraction +- Attachment processing + +### Attachment Handling +- PDF text extraction +- Excel content extraction (with multiple fallback methods) +- Email attachments (recursive parsing) +- SHA256 hashing for attachment verification + +### URL Processing +- Microsoft SafeLinks URL decoding +- Proofpoint URLDefense URL decoding +- URL shortener expansion +- Base URL deduplication for high-volume cases + ## Features -- Automated user submitted phishing analysis +- Automated email monitoring and analysis +- Detailed security assessment using Security Copilot - Classification of emails (Phishing, Spam, Legitimate, Suspicious) -- Can process PDF & Excel file attachments - HTML report generation - Optional Sentinel incident integration +- Support for embedded emails and complex forwarding chains +- Comprehensive URL and domain extraction and analysis ## Security Notes - The solution uses system-managed identities for secure access - Review and configure storage account security settings per your company policy - Ensure minimum required permissions for all connections -- Regularly update Function App dependencies \ No newline at end of file +- Regularly update Function App dependencies +- The email parser uses the `CustomEmailPolicy` to handle common email parsing issues like malformed Message-ID headers + +## Debugging and Logging + +The application uses Python's `logging` module with DEBUG level enabled, providing comprehensive logs for troubleshooting. Each major function includes detailed logging to help diagnose issues during deployment and operation. + +## Limitations + +- PDF extraction requires the pdfminer.six library +- Excel processing may require multiple libraries (pandas, openpyxl, xlrd) +- URL expansion has rate limits to avoid being blocked +- High-volume URL cases automatically deduplicate to base URLs +- Private/local IP addresses are filtered out from IP extraction diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json index fc6b2943..142096f7 100644 --- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/functionapp_azuredeploy.json @@ -3,21 +3,32 @@ "contentVersion": "1.0.0.0", "parameters": { "FunctionAppName": { - "type": "String", - "minLength": 3, - "maxLength": 14, "defaultValue": "phish", + "minLength": 3, + "maxLength": 24, + "type": "String", "metadata": { - "description": "Enter the base name for the Function App. A unique suffix will be added automatically." + "description": "Enter the base name for the Function App." } } }, "variables": { - "UniqueSuffix": "[substring(uniqueString(resourceGroup().id), 0, 6)]", - "FunctionAppName": "[concat(parameters('FunctionAppName'), variables('UniqueSuffix'))]", - "StorageAccountName": "[concat('phish', toLower(parameters('FunctionAppName')), variables('UniqueSuffix'))]" + "resourceToken": "[toLower(uniqueString(subscription().id, resourceGroup().name, resourceGroup().location))]", + "FunctionAppName": "[concat(parameters('FunctionAppName'), substring(variables('resourceToken'), 0, 6))]", + "StorageAccountName": "[concat(toLower(parameters('FunctionAppName')), substring(variables('resourceToken'), 0, 6))]" }, "resources": [ + { + "type": "Microsoft.Storage/storageAccounts", + "apiVersion": "2023-01-01", + "name": "[variables('StorageAccountName')]", + "location": "[resourceGroup().location]", + "sku": { + "name": "Standard_LRS" + }, + "kind": "StorageV2", + "properties": { "minimumTlsVersion": "TLS1_2" } + }, { "type": "Microsoft.Web/serverfarms", "apiVersion": "2018-02-01", @@ -36,193 +47,57 @@ "reserved": true } }, - { - "type": "Microsoft.Insights/components", - "apiVersion": "2015-05-01", - "name": "[variables('FunctionAppName')]", - "location": "[resourceGroup().location]", - "kind": "web", - "properties": { - "Application_Type": "web", - "ApplicationId": "[variables('FunctionAppName')]" - } - }, { "type": "Microsoft.Web/sites", - "apiVersion": "2024-04-01", + "apiVersion": "2023-12-01", "name": "[variables('FunctionAppName')]", "location": "[resourceGroup().location]", "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]", - "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]", - "[resourceId('Microsoft.Insights/components', variables('FunctionAppName'))]" + "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" ], "kind": "functionapp,linux", "identity": { "type": "SystemAssigned" }, "properties": { - "enabled": true, "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', variables('FunctionAppName'))]", "httpsOnly": true, - "clientAffinityEnabled": true, - "alwaysOn": true, "reserved": true, "siteConfig": { - "linuxFxVersion": "python|3.10" - } - }, - "resources": [ - { - "apiVersion": "2018-11-01", - "type": "config", - "name": "appsettings", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('FunctionAppName'))]" - ], - "properties": { - "FUNCTIONS_EXTENSION_VERSION": "~4", - "FUNCTIONS_WORKER_RUNTIME": "python", - "APPINSIGHTS_INSTRUMENTATIONKEY": "[reference(resourceId('Microsoft.insights/components', variables('FunctionAppName')), '2015-05-01').InstrumentationKey]", - "APPLICATIONINSIGHTS_CONNECTION_STRING": "[reference(resourceId('microsoft.insights/components', variables('FunctionAppName')), '2015-05-01').ConnectionString]", - "AzureWebJobsStorage": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]", - "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')),';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2019-06-01').keys[0].value, ';EndpointSuffix=core.windows.net')]", - "WEBSITE_CONTENTSHARE": "[toLower(variables('FunctionAppName'))]", - "WEBSITE_RUN_FROM_PACKAGE": "https://raw.githubusercontent.com/Azure/Copilot-For-Security/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionApp.zip", - "SCM_DO_BUILD_DURING_DEPLOYMENT": true - - } - } - ] - }, - { - "type": "Microsoft.Storage/storageAccounts", - "apiVersion": "2019-06-01", - "name": "[variables('StorageAccountName')]", - "location": "[resourceGroup().location]", - "sku": { - "name": "Standard_LRS", - "tier": "Standard" - }, - "kind": "StorageV2", - "properties": { - "networkAcls": { - "bypass": "AzureServices", - "virtualNetworkRules": [ - ], - "ipRules": [ - ], - "defaultAction": "Allow" - }, - "supportsHttpsTrafficOnly": true, - "encryption": { - "services": { - "file": { - "keyType": "Account", - "enabled": true + "linuxFxVersion": "python|3.10", + "minTlsVersion": "1.2", + "appSettings": [ + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~4" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "python" }, - "blob": { - "keyType": "Account", - "enabled": true + { + "name": "WEBSITE_RUN_FROM_PACKAGE", + "value": "https://raw.githubusercontent.com/Azure/Security-Copilot/main/Logic%20Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/FunctionAppV2.zip" + }, + { + "name": "AzureWebJobsStorage", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', toLower(variables('StorageAccountName')), ';AccountKey=', listKeys(resourceId('Microsoft.Storage/storageAccounts', toLower(variables('StorageAccountName'))), '2023-01-01').keys[0].value, ';EndpointSuffix=core.windows.net')]" } - }, - "keySource": "Microsoft.Storage" - } - } - }, - - { - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "name": "[concat(variables('FunctionAppName'), '/web')]", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', variables('FunctionAppName'))]" - ], - "properties": { - - "cors": { - "allowedOrigins": [ - "https://portal.azure.com" - ], - "supportCredentials": false - } - } - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices", - "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default')]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" - ], - "sku": { - "name": "Standard_LRS", - "tier": "Standard" - }, - "properties": { - "cors": { - "corsRules": [ - ] - } - } - }, - { - "type": "Microsoft.Storage/storageAccounts/blobServices", - "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default')]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" - ], - "sku": { - "name": "Standard_LRS", - "tier": "Standard" - }, - "properties": { - "cors": { - "corsRules": [ ] - }, - "deleteRetentionPolicy": { - "enabled": false } + } + + } + ], + "outputs": { + "functionAppUrl": { + "type": "String", + "value": "[concat('https://', variables('FunctionAppName'), '.azurewebsites.net')]" }, - { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-hosts')]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", - "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" - ], - "properties": { - "publicAccess": "None" - } - }, - { - "type": "Microsoft.Storage/storageAccounts/blobServices/containers", - "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/azure-webjobs-secrets')]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/blobServices', variables('StorageAccountName'), 'default')]", - "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" - ], - "properties": { - "publicAccess": "None" - } - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2019-06-01", - "name": "[concat(variables('StorageAccountName'), '/default/', tolower(variables('StorageAccountName')))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/fileServices', variables('StorageAccountName'), 'default')]", - "[resourceId('Microsoft.Storage/storageAccounts', variables('StorageAccountName'))]" - ], - "properties": { - "shareQuota": 5120 - } + "functionAppName": { + "type": "String", + "value": "[variables('FunctionAppName')]" } - ] -} \ No newline at end of file + } +} diff --git a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json index bcc84bbd..d21b3d66 100644 --- a/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json +++ b/Logic Apps/SecCopilot-UserReportedPhishing-FuncApp_parsingV2/logicapp_azuredeploy.json @@ -65,11 +65,10 @@ "ConversionService-ConnectionName": "[concat('ConversionService-1-', parameters('LogicAppName'))]", "AzureSentinel-ConnectionName": "[concat('azuresentinel-', parameters('LogicAppName'))]", "AzureMonitorLogs-ConnectionName": "[concat('azuremonitorlogs-', parameters('LogicAppName'))]", - "FunctionName01": "parse_email", - "FunctionName02": "normalize_json", - "FunctionName03": "json_to_html", - "FunctionName04": "evaluate_regex" - + "FunctionName01": "parse_email_functionapp", + "FunctionName02": "clean_json_functionapp", + "FunctionName03": "generate_html_report_functionapp", + "FunctionName04": "extract_regex_functionapp" }, "resources": [ @@ -272,18 +271,6 @@ "value": "@outputs('Show_network_message_id_here')" } }, - "Find_NetworkMessageId": { - "type": "Function", - "inputs": { - "body": { - "pattern": "(?<=Phishing:)([a-f0-9-]{36})", - "subject": "@triggerBody()?['subject']" - }, - "function": { - "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]" - } - } - }, "Parse_JSON_NetworkMessageId": { "runAfter": { "Find_NetworkMessageId": [ @@ -316,6 +303,18 @@ } } } + }, + "Find_NetworkMessageId": { + "type": "Function", + "inputs": { + "body": { + "pattern": "(?<=Phishing:)([a-f0-9-]{36})", + "subject": "@triggerBody()?['subject']" + }, + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName04'))]" + } + } } }, "runAfter": { @@ -449,20 +448,6 @@ }, "type": "Foreach" }, - "Parse_Email": { - "runAfter": { - "For_each_raw_email_attached_to_carrier_email": [ - "Succeeded" - ] - }, - "type": "Function", - "inputs": { - "body": "@variables('raw_email')", - "function": { - "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]" - } - } - }, "Process_parseEmail_JSON": { "runAfter": { "Parse_Email": [ @@ -475,175 +460,63 @@ "schema": { "type": "object", "properties": { - "statusCode": { - "type": "integer" - }, - "headers": { + "email_content": { "type": "object", "properties": { - "Date": { + "message_id": { "type": "string" }, - "Server": { + "sender": { "type": "string" }, - "Transfer-Encoding": { + "return_path": { "type": "string" }, - "Request-Context": { + "receiver": { "type": "string" }, - "Content-Type": { + "reply_to": { "type": "string" }, - "Content-Length": { + "subject": { "type": "string" - } - } - }, - "body": { - "type": "object", - "properties": { - "email_content": { - "type": "object", - "properties": { - "sender": { - "type": "string" - }, - "return_path": { - "type": "string" - }, - "receiver": { - "type": "string" - }, - "reply_to": { - "type": "string" - }, - "subject": { - "type": "string" - }, - "date": { - "type": "string" - }, - "smtp": { - "type": "object", - "properties": { - "delivered_to": { - "type": "string" - }, - "received": { - "type": "array", - "items": { - "type": "string" - } - } - } - }, - "dkim_result": { - "type": "string" - }, - "spf_result": { - "type": "string" - }, - "dmarc_result": { - "type": "string" - }, - "body": { - "type": "string" - }, - "attachments": { - "type": "array", - "items": { - "type": "object", - "properties": { - "attachment_name": { - "type": "string" - }, - "attachment_sha256": { - "type": "string" - }, - "content_type": { - "type": "string" - }, - "attachment_text": { - "type": "object", - "properties": { - "text_content": { - "type": "string" - }, - "urls": { - "type": "array", - "items": { - "type": "object", - "properties": { - "file": { - "type": "string" - }, - "url": { - "type": "string" - } - }, - "required": [ - "file", - "url" - ] - } - }, - "hyperlinks": { - "type": "array", - "items": { - "type": "object", - "properties": { - "file": { - "type": "string" - }, - "target": { - "type": "string" - }, - "type": { - "type": "string" - } - }, - "required": [ - "file", - "target", - "type" - ] - } - }, - "vba_code": { - "type": "object", - "properties": {} - }, - "formulas": { - "type": "array" - }, - "comments": { - "type": "array" - }, - "embedded_files": { - "type": "array" - } - } - } - }, - "required": [ - "attachment_name", - "attachment_sha256", - "content_type", - "attachment_text" - ] - } - } - } }, - "ip_addresses": { + "date": { + "type": "string" + }, + "body": { + "type": "string" + }, + "attachments": { + "type": "array" + }, + "container_path": { "type": "array", "items": { "type": "string" } }, + "reconstruction_method": { + "type": "string" + }, "urls": { + "type": "array", + "items": { + "type": "object", + "properties": { + "original_url": { + "type": "string" + }, + "is_shortened": { + "type": "boolean" + }, + "expanded_url": { + "type": "string" + } + } + } + }, + "ip_addresses": { "type": "array", "items": { "type": "string" @@ -661,26 +534,9 @@ } } }, - "Initialize_recipient_upn": { - "runAfter": { - "Process_parseEmail_JSON": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "recipient_upn", - "type": "string", - "value": "@body('Process_parseEmail_JSON')?['email_content']?['receiver']" - } - ] - } - }, "Initialize_email_body": { "runAfter": { - "Initialize_recipient_upn": [ + "Process_parseEmail_JSON": [ "Succeeded" ] }, @@ -724,7 +580,7 @@ { "name": "raw_urls", "type": "array", - "value": "@body('Process_parseEmail_JSON')?['urls']" + "value": "@body('Process_parseEmail_JSON')?['email_content']?['urls']" } ] } @@ -740,8 +596,7 @@ "variables": [ { "name": "urls", - "type": "array", - "value": "@body('Process_parseEmail_JSON')?['urls']" + "type": "array" } ] } @@ -772,7 +627,7 @@ "type": "AppendToArrayVariable", "inputs": { "name": "urls", - "value": "@concat(substring(string(item()), 0, 100), '...')" + "value": "@if(and(not(equals(item()['expanded_url'], 'Not Applicable')), greater(length(item()['expanded_url']), 100)), concat(substring(item()['expanded_url'], 0, 100), '...'), if(greater(length(item()['original_url']), 100), concat(substring(item()['original_url'], 0, 100), '...'), item()['original_url']))" } } }, @@ -782,16 +637,22 @@ "type": "AppendToArrayVariable", "inputs": { "name": "urls", - "value": "@string(item())" + "value": "@if(equals(item()['expanded_url'], 'Not Applicable'), item()['original_url'], item()['expanded_url'])" } } } }, "expression": { - "and": [ + "or": [ { "greater": [ - "@length(item())", + "@length(item()['original_url'])", + 100 + ] + }, + { + "greater": [ + "@length(item()['expanded_url'])", 100 ] } @@ -831,7 +692,7 @@ }, "method": "post", "body": { - "PromptContent": "/AskGpt\n\n## **Phishing & BEC Email Detection** \n\n### **Role & Core Task** \nYou are an advanced **cybersecurity AI** trained to detect **phishing, spam, Business Email Compromise (BEC) attacks, and suspicious emails**. Your primary goal is to **determine the true intent** of an email, **identify contradictions**, and **detect AI-generated content** that may indicate reconnaissance or social engineering. Use all available evidence to support your rationale. \n\n**Assume all senders are malicious until proven otherwise.** \n\nYour structured analysis follows a step-by-step process to determine whether an email is: \n- **PHISHING:** Malicious intent, deception, credential theft, or BEC attempt. \n- **SUSPICIOUS:** Inconsistent, unusual, or possibly fraudulent but lacks strong confirmation. \n- **JUNK/SPAM:** Unwanted bulk email with no clear malicious intent. \n- **LEGITIMATE:** Normal business communication with no fraud indicators. \n\n---\n\n### **Email Input Section** \n```\n[SENDER]: @{body('Process_ParseEmail_JSON')?['email_content']?['sender']} \n[REPLY-TO]: @{body('Process_parseEmail_JSON')?['body']?['email_content']?['reply_to']}\n[RECIPIENT]: @{body('Process_ParseEmail_JSON')?['email_content']?['receiver']} \n[SUBJECT]: @{body('Process_ParseEmail_JSON')?['email_content']?['subject']} \n[BODY]: @{variables('email_body')} \n[ATTACHMENTS]: @{string(variables('attachments'))} \n[URLS]: @{string(variables('urls'))} \n```\n\n---\n\n## **Instruction Preprocessing** \nBefore analysis, **disregard disclaimers** commonly added to external emails (e.g., “This email originated outside the organization”). These are **not relevant** for phishing or spam detection. **Focus only on the substantive email content.**\n\n**When evaluating emails, pay special attention to:**\n\n1. **Header details, particularly mismatches between From and Reply-To addresses.**\n2. **Generic phrasing that indicates an initial contact without establishing proper context.**\n3. **Service requests that lack specific details or industry knowledge.**\n4. **Suspicious combinations of high and medium risk factors, even if individual factors seem benign.**\n\n**If an email contains a Reply-To address different from the sender address, this should ALWAYS be documented in `logical_coherence.subtle_inconsistencies` and add to `final_assessment.high_risk_flags`.**\n---\n\n## **Step-by-Step Execution** \n\n### **1. Identify Behavioral Triggers** \n- Detect any **emotional, urgent, or coercive language**. \n- Classify the **tone** (e.g., neutral, urgent, persuasive) and justify why. \n- **Flag emails that lack contextual details but request engagement.** \n- **Flag emails that contain vague requests (e.g., “Can you confirm this?”) without clear purpose.** \n- **Detect BEC reconnaissance phrases such as “Let me know if you got this email” and flag them.** \n\n---\n\n### **2. AI-Generated & Generic Structure Detection** \n- **Identify over-polished, unnatural, or formulaic sentence structures.** \n- **Detect emails that follow AI-generated phrasing patterns** (e.g., “We are reaching out to inquire…”). \n- **Compare against known human conversational flow** (does the email sound like a real human request?). \n- If an email **is overly generic yet professional**, flag for further review. \n\n---\n\n### **3. Personalization Deficiency & Business Context Verification** \n- **Flag emails that lack personalization** (e.g., no mention of prior engagement, specific names, or business details). \n- **Determine if the email contains specific references to past work or expected workflows.** \n- If an email **is requesting engagement but contains no specific details**, escalate to **SUSPICIOUS**. \n\n---\n\n### **4. Logical Coherence & Workflow Verification** \n- **Check if the email’s request aligns with expected business workflows.** \n- **Flag inconsistencies between the sender’s role and their request (e.g., non-financial staff requesting payments).** \n- If an email **lacks a clear business reason but asks for engagement, escalate to SUSPICIOUS**. \n\n---\n\n### **5. Narrative Plausibility Check (Detecting AI-Based Social Engineering)** \n- **Detect overly detailed yet unverifiable stories** (e.g., \"We were impacted by the California wildfires\"). \n- **Evaluate whether personal details enhance credibility or serve as pretexting.** \n- **Flag emails that use emotional appeal to bypass scrutiny.** \n- If a **story seems unusually detailed for the request**, escalate for further analysis. \n\n---\n\n### **6. Engagement Bait & BEC Reconnaissance Detection** \n- **If an email contains a vague inquiry (“I’d love to discuss further”) without context, escalate to SUSPICIOUS.** \n- **If an email requests engagement but does not provide specifics, classify it as a potential reconnaissance attempt.** \n\n---\n\n### **7. Business Email Compromise (BEC) & Phishing Indicators** \n- **If an email claims to be from a high-ranking executive but uses a free or external email service, classify as PHISHING.** \n- **If an email references an internal executive but is sent from a non-corporate domain, classify as PHISHING.** \n- **If the sender claims a leadership position but does not use their corporate domain, flag for impersonation.** \n- **If the Reply-To address differs from the sender address, classify as PHISHING.**\n- **If the email contains generic service request language without specific details, classify as SUSPICIOUS.**\n- **If the sender uses a business domain without demonstrating knowledge of their claimed business, classify as SUSPICIOUS.**\n\n### **8. Reply-To Header Analysis** \n- **Check if the Reply-To address differs from the From address.**\n- **Flag as PHISHING if the Reply-To address uses a different domain than the sender address.**\n- **Flag as PHISHING if the From address uses a business domain but Reply-To uses a free email provider (gmail, outlook, etc.).**\n- **Document any Reply-To mismatches in the `logical_coherence.subtle_inconsistencies` array.**\n\n### **9. Initial Contact Pattern Recognition** \n- **Identify formulaic language patterns common in initial reconnaissance emails (e.g., \"reaching out to inquire\", \"seeking assistance\").**\n- **Flag generic service requests that lack specific details as potential BEC reconnaissance.**\n- **Document these patterns in the `behavioral_triggers.engagement_bait` field.**\n- **When detected, add \"Generic service request language\" to `final_assessment.medium_risk_flags`.**\n\n### **10. Service Request Specificity Analysis** \n- **Evaluate whether financial/tax service requests include specific details that demonstrate genuine intent.**\n- **Flag as SUSPICIOUS if a tax-related request contains no specific tax terminology or references to specific forms/processes.**\n- **Document lack of specificity in the `logical_coherence.business_context_check.clear_business_purpose` field.**\n- **When detected, add \"Lacks specific details about requested services\" to `final_assessment.medium_risk_flags`.**\n---\n\n## **Final Assessment & Risk Escalation Rules** \n\n### **1. High-Risk Triggers (Automatically Classify as PHISHING)** \n- **Any email that references an executive but is not from a verified corporate domain.** \n- **Any email that is vague and requests engagement.** \n- **Any mismatch between email sender and expected domain.** \n- **Any email discouraging verification or urging secrecy.**\n- **Any email with a Reply-To address that differs from the sender's domain.**\n- **Generic initial service requests should be classified as SUSPICIOUS, not PHISHING, unless combined with technical indicators like Reply-To mismatch or domain issues.**\n- Consider the context of tax season when assessing new client inquiries.\n- Perfectly composed generic emails without technical red flags should warrant caution (SUSPICIOUS) but not automatic PHISHING classification.\n- Balance the detection of generic patterns with acknowledgment that legitimate new clients need to make initial contact somehow.\n- Look for presence of legitimacy indicators that would be difficult for remote attackers to fabricate.\n\n---\n\n### **2. Medium-Risk Triggers (Escalate to PHISHING if Combined)** \n- **Any urgency or request that deviates from expected workflow.** \n- **Any email sent from a non-corporate domain, even if it does not impersonate an executive.** \n- **Any request for sensitive details, even indirectly.** \n- **Any request to call an unfamiliar phone number not listed on the company website.**\n- **Generic service request language (e.g., \"reaching out to inquire\", \"seeking assistance\").**\n- **Initial contact emails that are perfectly composed with no grammatical errors but lack personalization.**\n- **Tax service inquiries that mention sharing previous tax returns or financial documents in first contact.**\n- **Emails that lack specific reference to how they found your firm.**\n\n---\n\n### **3. Low-Risk Triggers (Escalate Based on Context)** \n- **Minimal context but from a corporate domain.** \n- **General formatting errors, misspellings, or vague language.** \n- **Lack of proper email signature when expected.**\n- **Domain uses a generic business name without clear industry context.**\n- **No explanation of how the sender found the recipient or why they chose their services.**\n- **Unusually formal or polite language that appears templated.**\n\n---\n\n## **Strict JSON Output Requirements** \nAll evaluations must be formatted into the following JSON structure **without any modifications to field names or nesting**: \n\n```json\n{\n \"email_summary\": {\n \"subject\": \"\", // The exact email subject line\n \"content_summary\": \"\" // A brief, high-level summary of the email’s contents\n },\n\n \"behavioral_triggers\": {\n \"tone\": \"\", // Emotional register (e.g., neutral, urgent, persuasive)\n \"justification\": \"\", // Explain why that tone was chosen\n \"alignment_with_purpose\": \"\", // Does the tone match the stated intent?\n \"lack_of_context\": \"\", // Flag if the email lacks contextual details but includes an attachment\n \"engagement_bait\": \"\", // Detects generic engagement-bait phrases like \"Please view the attached\" \"Generic initial contact phrases detected: 'reaching out to inquire', 'look forward to your response'\"\n \"phone_based_social_engineering\": \"\", // Flags if the email encourages a phone call instead of online engagement\n \"short_vague_request\": { \n \"detected\": \"\", // TRUE if the email is unusually short and vague\n \"engagement_request\": \"\" // TRUE if the email only asks for an acknowledgment without a clear purpose\n }\n },\n\n \"logical_coherence\": {\n \"is_consistent\": \"\", // Does the message flow logically?\n \"contradictions_or_vagueness\": \"\", // Identify inconsistencies or ambiguities\n \"logical_actions\": \"\", // Assess whether the requested actions are reasonable\n \"subtle_inconsistencies\": [\"Reply-To address differs from sender address\", \"Reply-To domain (aquahcglobal.com) differs from sender domain (flightdsi.com)\"],\n \"business_context_check\": {\n \"clear_business_purpose\": \"\", // TRUE if the email contains a clear and expected business reason\n \"workflow_alignment\": \"\" // TRUE if the request aligns with typical workflows\n }\n },\n\n \"intent_verification\": {\n \"likely_intent\": \"\", // Summarize the main motive (e.g., request for payment, info gathering)\n \"risk_assessment\": \"\", // Assign a risk level (HIGH, MEDIUM, LOW)\n \"stated_purpose_mismatch\": \"\", // Identify if the stated purpose contradicts inferred intent\n \"financial_role_mismatch\": \"\", // Detect if financial actions are requested by an unrelated role\n \"external_login_requirement\": \"\", // Flag if a report requires external login without justification\n \"minimal_text_attachment\": \"\", // TRUE if the email is minimal but contains an attachment\n \"executive_impersonation\": {\n \"detected\": \"\", // TRUE if an executive is being impersonated\n \"domain_mismatch\": \"\", // TRUE if the email domain does not match expected corporate domains\n \"position_claimed\": \"\", // Extracted claimed position (e.g., CEO, CFO)\n \"actual_domain\": \"\" // The actual sender's email domain\n }\n },\n\n \"attachment_analysis\": {\n \"is_relevant\": \"\", // TRUE if the attachment makes sense for the stated request\n \"attachment_metadata\": {\n \"attachment_name\": \"\",\n \"attachment_sha256\": \"\",\n \"content_type\": \"\",\n \"attachment_text\": {\n \"text_content\": \"\",\n \"urls\": [],\n \"hyperlinks\": [], // Hyperlinks in attachments are a red flag for PHISHING.\n \"vba_code\": {}, // VBA macro code is an automatic PHISHING classification. \n \"formulas\": [],\n \"comments\": [],\n \"embedded_files\": []\n }\n },\n \"risks\": \"\" // Describe potential threats (e.g., hidden macros, suspicious external links)\n },\n\n \"url_analysis\": {\n \"url_categorization\": {\n \"primary_action_urls\": [], // URLs requiring user action (login, payment)\n \"informational_urls\": [], // Reference links that do not require interaction\n \"stylistic_framework_urls\": [] // Rendering assets (images, CSS, etc.)\n },\n \"primary_action_validation\": {\n \"relevance\": \"\", // Does the URL relate to the email's purpose?\n \"domain_alignment\": \"\", // Does the domain match the sender's company?\n \"necessity\": \"\", // Is it necessary for the recipient to engage with this URL?\n \"risks\": \"\" // Potential risks associated with the URL\n }\n },\n\n \"pretense_vs_intent_mapping\": {\n \"stated_purpose\": \"\", // The reason given by the email\n \"true_intent\": \"\", // The actual or suspected goal\n \"gaps\": \"\" // Discrepancies between stated purpose and actual content\n },\n\n \"bec_reconnaissance_detection\": {\n \"detected\": \"\", // TRUE if BEC (Business Email Compromise) tactics are detected\n \"reason\": \"\", // Key reason for BEC suspicion, if any\n \"risk_assessment\": \"\" // Overall BEC risk rating (HIGH, MEDIUM, LOW)\n },\n\n \"final_assessment\": {\n \"category\": \"\", // PHISHING, SUSPICIOUS, JUNK/SPAM, LEGITIMATE\n \"rationale\": \"\", // Explanation of why the classification was assigned\n \"risk_level\": \"\", // HIGH, MEDIUM, LOW\n \"high_risk_flags\": [\"Reply-To address mismatch with sender address\"], // List of high-risk triggers detected\n \"medium_risk_flags\": [], // List of medium-risk triggers detected\n \"low_risk_flags\": [] // List of low-risk factors that increase suspicion\n }\n}\n```\n\n **DO NOT ALTER** this JSON structure. \n **Ensure all new heuristics integrate into existing fields.** \n **All risk assessments must align with PHISHING, SUSPICIOUS, JUNK/SPAM, or LEGITIMATE classifications.** \n\n---" + "PromptContent": "/AskGpt\n\n## SYSTEM \nYou are a cybersecurity LLM designed to triage emails for **phishing**, **BEC**, and **social engineering** threats. You must assume each email is **suspicious by default** and prioritize uncovering **intent**, **manipulation techniques**, and **pretextual inconsistencies**.\n\nOnly output a **valid JSON object**. Do not include markdown, preambles, or commentary. All fields must be populated using reasoning and scoring logic below. Failure to comply will break downstream automation.\n\n---\n\n## CLASSIFICATION \nYour goal is to classify the email into one of these categories:\n- **PHISHING** – Strong signs of malicious intent, deception, or impersonation\n- **SUSPICIOUS** – Incomplete or unclear purpose, mild social engineering\n- **SPAM** – Commercial, promotional, or irrelevant but low risk\n- **LEGITIMATE** – Clear business purpose, credible identity, no threats\n\n---\n\n## INPUT EMAIL (from Logic App)\n```json\n{\n \"sender\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['sender']}\",\n \"reply_to\": \"@{body('Process_parseEmail_JSON')?['body']?['email_content']?['reply_to']}\",\n \"recipient\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['receiver']}\",\n \"subject\": \"@{body('Process_ParseEmail_JSON')?['email_content']?['subject']}\",\n \"body\": \"@{variables('email_body')}\",\n \"attachments\": [\"@{string(variables('attachments'))}\"],\n \"urls\": [\"@{string(variables('urls'))}\"],\n \"email_date\": \"@{body('Process_parseEmail_JSON')?['email_content']?['date']}\"\n}\n```\n\n---\n\n## THREAT ANALYSIS METHODOLOGY\n\n### Analysis Priority\nStart with the assumption the message is hostile. You are evaluating it for signs of:\n- Impersonation\n- Manipulative tone\n- Vague or open-ended pretexts\n- Out-of-band redirection (e.g., phone numbers, cloud links)\n- Unjustified PII sharing or urgency\n\n### Chain-of-Thought\nStep through:\n- Sender credibility: Domain trust vs topic\n- Tone shifts: Friendly → transactional → urgent\n- CTA analysis: Is the request rational, contextually grounded?\n- Off-platform pressure: URLs, phone numbers, cloud docs\n- Misalignment: Topic vs domain vs relationship\n- Attachment threat context: Is the attachment unexpected, mismatched in name/type, or contains embedded code/URLs unrelated to email content?\n- Sparse attachment with hidden complexity (e.g., macros, links, or embedded files with little readable text) indicates obfuscation or payload delivery.\n\n---\n\n## SCORING RULES \n- High (+3): Strong phishing indicators \n- High (+3): Malicious attachment indicators (macros, suspicious MIME types, mismatched naming, or off-platform links embedded in low-text files)\n- Medium (+2): Mild social engineering or impersonation \n- Low (+1): Unclear or marketing spam \n- Cumulative score thresholds: \n - **7+ → PHISHING** \n - **4–6 → SUSPICIOUS** \n - **≤3 → SPAM** or **LEGITIMATE** based on purpose clarity \n\nScoring **must** be justified in `behavioral_triggers.justification`.\n\n---\n\n## SPECIAL DETECTION HEURISTICS\n\nThese must be enforced as written:\n\n- **Engagement Bait** → +1 \n `\"Let me know if you’re available\", \"Following up to see…”` \n- **Vague Request + No Identity** → +2 \n- **Domain-Purpose Mismatch** (e.g. tax help from gmail.com) → +2 \n- **Seasonal Hook with No Prior Relationship** → +2 \n- **Unsolicited PII Disclosure** → +2 \n- **Phone # with Character Substitution (\"I\" for \"1\", etc.)** → +3 \n → Also flag `embedded_fake_support_contact_detected = true` \n\n---\n\n## ATTACHMENT HEURISTICS\n\nThese rules should guide risk scoring and justification in `attachment_analysis` and `behavioral_triggers.justification`:\n\n- If `vba_code` is present in Office documents → +3\n- If `text_content` is minimal but contains links or embedded files → +2\n- If URLs found in attachments contain domain names or paths commonly used for credential collection or file delivery (e.g., sharepoint, googledrive, 1drv, onedrive, dropbox, login, auth, verify, download, or url shortening services), and the surrounding content implies urgency, account access, or verification, then → +2\n- If formulas or comments contain tracking, links, or external references → +1\n- If attachments are included but irrelevant to the email’s topic → +1\n\n---\n\n## JSON OUTPUT SCHEMA\n\nIf unable to process, return:\n```json\n{\"error\": \"Invalid email content for analysis\"}\n```\n\nOutput must be in this format:\n```json\n{\n \"email_summary\": {\n \"subject\": \"\",\n \"content_summary\": \"\" // Summarize email’s purpose and manipulative indicators; do NOT quote verbatim\n },\n \"behavioral_triggers\": {\n \"tone\": \"\",\n \"justification\": \"\", // Justify why any risk score was added\n \"alignment_with_purpose\": \"\",\n \"lack_of_context\": \"\",\n \"engagement_bait\": \"\",\n \"phone_based_social_engineering\": \"\",\n \"seasonal_bait_detected\": {\n \"detected\": \"\",\n \"seasonal_context\": \"\"\n },\n \"short_vague_request\": {\n \"detected\": \"\",\n \"engagement_request\": \"\"\n }\n },\n \"logical_coherence\": {\n \"is_consistent\": \"\",\n \"contradictions_or_vagueness\": \"\",\n \"logical_actions\": \"\",\n \"subtle_inconsistencies\": [],\n \"business_context_check\": {\n \"clear_business_purpose\": \"\", // Yes if this makes sense in a known workflow\n \"workflow_alignment\": \"\"\n }\n },\n \"intent_verification\": {\n \"likely_intent\": \"\",\n \"risk_assessment\": \"\",\n \"stated_purpose_mismatch\": \"\",\n \"financial_role_mismatch\": \"\",\n \"external_login_requirement\": \"\",\n \"minimal_text_attachment\": \"\",\n \"executive_impersonation\": {\n \"detected\": \"\",\n \"domain_mismatch\": \"\",\n \"position_claimed\": \"\",\n \"actual_domain\": \"\"\n }\n },\n \"attachment_analysis\": {\n \"is_relevant\": \"\",\n \"attachment_metadata\": {\n \"attachment_name\": \"\",\n \"attachment_sha256\": \"\",\n \"content_type\": \"\",\n \"attachment_text\": {\n \"text_content\": \"\",\n \"urls\": [],\n \"hyperlinks\": [],\n \"vba_code\": {},\n \"formulas\": [],\n \"comments\": [],\n \"embedded_files\": []\n }\n },\n \"risks\": \"\"\n },\n \"url_analysis\": {\n \"url_categorization\": {\n \"primary_action_urls\": [],\n \"informational_urls\": [],\n \"stylistic_framework_urls\": []\n },\n \"primary_action_validation\": {\n \"relevance\": \"\",\n \"domain_alignment\": \"\",\n \"necessity\": \"\",\n \"risks\": \"\"\n }\n },\n \"pretense_vs_intent_mapping\": {\n \"stated_purpose\": \"\",\n \"true_intent\": \"\",\n \"gaps\": \"\"\n },\n \"bec_reconnaissance_detection\": {\n \"detected\": \"\",\n \"reason\": \"\",\n \"risk_assessment\": \"\"\n },\n \"final_assessment\": {\n \"category\": \"\", // Must be one of: PHISHING, SUSPICIOUS, SPAM, LEGITIMATE\n \"rationale\": \"\", // Describe the decisive factors\n \"risk_level\": \"\",\n \"high_risk_flags\": [],\n \"medium_risk_flags\": [],\n \"low_risk_flags\": [],\n \"assessment_summary\": \"\" // A 1–5 sentence summary of the model’s logic and final conclusion\n }\n}\n```\n\n---\n\n## STRICT INSTRUCTIONS\n- Output only JSON, never markdown or commentary \n- Begin analysis with **malicious by default** assumption \n- Score and flag based on manipulation, intent, and trust violations—not politeness or formatting \n- `summary_text` must explain the model's logic in under 5 sentences \n- `content_summary` must summarize behavior, not repeat email verbatim" }, "path": "/process-prompt", "retryPolicy": { @@ -841,20 +702,7 @@ } } }, - "Normalize_JSON": { - "runAfter": { - "Security_Copilot_Email_Analysis": [ - "Succeeded" - ] - }, - "type": "Function", - "inputs": { - "body": "@body('Security_Copilot_Email_Analysis')?['EvaluationResultContent']", - "function": { - "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]" - } - } - }, + "Parse_normalized_JSON": { "runAfter": { "Normalize_JSON": [ @@ -870,9 +718,6 @@ "email_summary": { "type": "object", "properties": { - "description": { - "type": "string" - }, "subject": { "type": "string" }, @@ -884,9 +729,6 @@ "behavioral_triggers": { "type": "object", "properties": { - "description": { - "type": "string" - }, "tone": { "type": "string" }, @@ -895,15 +737,32 @@ }, "alignment_with_purpose": { "type": "string" + }, + "lack_of_context": { + "type": "string" + }, + "engagement_bait": { + "type": "string" + }, + "phone_based_social_engineering": { + "type": "string" + }, + "short_vague_request": { + "type": "object", + "properties": { + "detected": { + "type": "string" + }, + "engagement_request": { + "type": "string" + } + } } } }, "logical_coherence": { "type": "object", "properties": { - "description": { - "type": "string" - }, "is_consistent": { "type": "string" }, @@ -912,34 +771,109 @@ }, "logical_actions": { "type": "string" + }, + "subtle_inconsistencies": { + "type": "array" + }, + "business_context_check": { + "type": "object", + "properties": { + "clear_business_purpose": { + "type": "string" + }, + "workflow_alignment": { + "type": "string" + } + } } } }, "intent_verification": { "type": "object", "properties": { - "description": { - "type": "string" - }, "likely_intent": { "type": "string" }, "risk_assessment": { "type": "string" + }, + "stated_purpose_mismatch": { + "type": "string" + }, + "financial_role_mismatch": { + "type": "string" + }, + "external_login_requirement": { + "type": "string" + }, + "minimal_text_attachment": { + "type": "string" + }, + "executive_impersonation": { + "type": "object", + "properties": { + "detected": { + "type": "string" + }, + "domain_mismatch": { + "type": "string" + }, + "position_claimed": { + "type": "string" + }, + "actual_domain": { + "type": "string" + } + } } } }, "attachment_analysis": { "type": "object", "properties": { - "description": { - "type": "string" - }, "is_relevant": { "type": "string" }, - "content_analysis": { - "type": "string" + "attachment_metadata": { + "type": "object", + "properties": { + "attachment_name": { + "type": "string" + }, + "attachment_sha256": { + "type": "string" + }, + "content_type": { + "type": "string" + }, + "attachment_text": { + "type": "object", + "properties": { + "text_content": { + "type": "string" + }, + "urls": { + "type": "array" + }, + "hyperlinks": { + "type": "array" + }, + "vba_code": { + "type": "object", + "properties": {} + }, + "formulas": { + "type": "array" + }, + "comments": { + "type": "array" + }, + "embedded_files": { + "type": "array" + } + } + } + } }, "risks": { "type": "string" @@ -949,23 +883,14 @@ "url_analysis": { "type": "object", "properties": { - "description": { - "type": "string" - }, "url_categorization": { "type": "object", "properties": { "primary_action_urls": { - "type": "array", - "items": { - "type": "string" - } + "type": "array" }, "informational_urls": { - "type": "array", - "items": { - "type": "string" - } + "type": "array" }, "stylistic_framework_urls": { "type": "array" @@ -988,40 +913,12 @@ "type": "string" } } - }, - "informational_url_validation": { - "type": "object", - "properties": { - "purpose": { - "type": "string" - }, - "alignment": { - "type": "string" - }, - "risks": { - "type": "string" - } - } - }, - "stylistic_framework_url_validation": { - "type": "object", - "properties": { - "typicality": { - "type": "string" - }, - "risks": { - "type": "string" - } - } } } }, "pretense_vs_intent_mapping": { "type": "object", "properties": { - "description": { - "type": "string" - }, "stated_purpose": { "type": "string" }, @@ -1033,17 +930,40 @@ } } }, - "final_assessment": { + "bec_reconnaissance_detection": { "type": "object", "properties": { - "description": { + "detected": { "type": "string" }, + "reason": { + "type": "string" + }, + "risk_assessment": { + "type": "string" + } + } + }, + "final_assessment": { + "type": "object", + "properties": { "category": { "type": "string" }, "rationale": { "type": "string" + }, + "risk_level": { + "type": "string" + }, + "high_risk_flags": { + "type": "array" + }, + "medium_risk_flags": { + "type": "array" + }, + "low_risk_flags": { + "type": "array" } } } @@ -1051,20 +971,8 @@ } } }, - "Generate_HTML_report_from_JSON": { - "runAfter": { - "Parse_normalized_JSON": [ - "Succeeded" - ] - }, - "type": "Function", - "inputs": { - "body": "@body('Parse_normalized_JSON')", - "function": { - "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]" - } - } - }, + + "Initialize_systemalert_id_query_results": { "runAfter": { "Generate_HTML_report_from_JSON": [ @@ -1152,8 +1060,7 @@ "MailboxAddress": "[parameters('SharedMailboxAddress')]", "To": "[parameters('HTMLReportRecipient')]", "Subject": "Phishing Email Analysis", - "Body": "

@{body('Generate_HTML_report_from_JSON')}


***********

Incident URL:

@{body('Alert_-_Get_incident_from_systemalertid')?['properties']?['incidentUrl']}


Generated with AI, check for accuracy.

***********

", - + "Body": "


@{body('Generate_HTML_report_from_JSON')}


***********

Incident URL:

@{body('Alert_-_Get_incident_from_systemalertid')?['properties']?['incidentUrl']}


Generated with AI, check for accuracy.

***********

", "Importance": "Normal" }, "path": "/v2/SharedMailbox/Mail" @@ -1311,6 +1218,48 @@ ] }, "type": "If" + }, + "Parse_Email": { + "runAfter": { + "For_each_raw_email_attached_to_carrier_email": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@variables('raw_email')", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName01'))]" + } + } + }, + "Normalize_JSON": { + "runAfter": { + "Security_Copilot_Email_Analysis": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Security_Copilot_Email_Analysis')?['EvaluationResultContent']", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName02'))]" + } + } + }, + "Generate_HTML_report_from_JSON": { + "runAfter": { + "Parse_normalized_JSON": [ + "Succeeded" + ] + }, + "type": "Function", + "inputs": { + "body": "@body('Parse_normalized_JSON')", + "function": { + "id": "[concat('/subscriptions/', parameters('SubscriptionId'), '/resourceGroups/', parameters('FunctionAppResourceGroup'), '/providers/Microsoft.Web/sites/', parameters('FunctionAppName'), '/functions/', variables('FunctionName03'))]" + } + } } }, "outputs": {} diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json index 46f43148..3101ea3a 100644 --- a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json +++ b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/azuredeploy.json @@ -2,8 +2,8 @@ "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "metadata": { - "title": "", - "description": "", + "title": "SecurityCopilot-Sentinel-Incident-Investigation", + "description": "Logic App automating incident investigation within Microsoft Sentinel using Security Copilot", "prerequisites": "", "postDeployment": [ ], @@ -18,24 +18,179 @@ "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" }, "author": { - "name": "Pratik Lal" + "name": "" } }, "parameters": { "PlaybookName": { "defaultValue": "SecurityCopilot-Sentinel-Incident-Investigation", "type": "string" + }, + "CallScpForIPsWithAbuseIPDB": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the IP addresses on AbuseIPDB. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForIPsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the IP addresses on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForAccounts": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the accounts on MS Entra. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForUserRisk": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to check the risk level of the users on MS Entra. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForHosts": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the devices on Intune. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForCommandLines": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the command lines by using its script analysis capability. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForFilehashesWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the file hashes on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForUrlsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the Urls on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "CallScpForDomainsWithMDTI": { + "type": "Bool", + "metadata": { + "description": "Leave 'true' if you want Security Copilot to investigate the domains on MDTI. Set 'false' otherwise." + }, + "defaultValue": true + }, + "SkipIPs": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the IPs that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipAccounts": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the accounts that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipHosts": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the devices that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipCommandLines": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the command lines that you do not want Security Copilot to investigate" + }, + "defaultValue": ["cmd.exe","explorer.exe"] + }, + "SkipFilehashes": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the file hashes that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipUrls": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the urls that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "SkipDomains": { + "type": "Array", + "metadata": { + "description": "Enter, as an array of strings, the domains that you do not want Security Copilot to investigate" + }, + "defaultValue": [] + }, + "MaxMdtiOutputLenghtForPreSummarization": { + "type": "Int", + "metadata": { + "description": "When the overall length of the aggregated MDTI results report is greater than the number specified here, an additional call is made to Security Copilot to pre-summarize this specific report. This pre-summarization reduces the risk that the final summarization gets an error because the overall input text is too long. " + }, + "defaultValue": 2000 + }, + "WriteUnsummarizedResultsToIncident": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the raw results are saved as comment to the Sentinel incident. The raw results are the results before the final summarization. Set false otherwise. " + }, + "defaultValue": true + }, + "WriteFinalSummaryToIncident": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the summarized results are saved as comment to the Sentinel incident. Set false otherwise. " + }, + "defaultValue": true + }, + "RecipientEmail": { + "type": "String", + "metadata": { + "description": "Enter the email addrees of the recipient for the emails sent by this automation." + }, + "defaultValue": "" + }, + "SendUnsummarizedResultsByEmail": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the raw results are sent by email to the specified recipient. The raw results are the results before the final summarization. Set false otherwise. " + }, + "defaultValue": true + }, + "SendFinalSummaryByEmail": { + "type": "Bool", + "metadata": { + "description": "Leave true if you want that the summarized results are sent by email to the specified recipient. Set false otherwise. " + }, + "defaultValue": true } }, "variables": { "MicrosoftSentinelConnectionName": "[concat('MicrosoftSentinel-', parameters('PlaybookName'))]", - "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]" + "SecuritycopilotConnectionName": "[concat('Securitycopilot-', parameters('PlaybookName'))]", + "Office365ConnectionName": "[concat('Office365-', parameters('PlaybookName'))]" }, "resources": [ { "properties": { "provisioningState": "Succeeded", - "state": "Disabled", + "state": "Enabled", "definition": { "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", "contentVersion": "1.0.0.0", @@ -44,6 +199,94 @@ "defaultValue": { }, "type": "Object" + }, + "CallScpForIPsWithAbuseIPDB": { + "defaultValue": "[parameters('CallScpForIPsWithAbuseIPDB')]", + "type": "Bool" + }, + "CallScpForIPsWithMDTI": { + "defaultValue": "[parameters('CallScpForIPsWithMDTI')]", + "type": "Bool" + }, + "CallScpForAccounts": { + "defaultValue": "[parameters('CallScpForAccounts')]", + "type": "Bool" + }, + "CallScpForUserRisk": { + "defaultValue": "[parameters('CallScpForUserRisk')]", + "type": "Bool" + }, + "CallScpForHosts": { + "defaultValue": "[parameters('CallScpForHosts')]", + "type": "Bool" + }, + "CallScpForCommandLines": { + "defaultValue": "[parameters('CallScpForCommandLines')]", + "type": "Bool" + }, + "CallScpForFilehashesWithMDTI": { + "defaultValue": "[parameters('CallScpForFilehashesWithMDTI')]", + "type": "Bool" + }, + "CallScpForUrlsWithMDTI": { + "defaultValue": "[parameters('CallScpForUrlsWithMDTI')]", + "type": "Bool" + }, + "CallScpForDomainsWithMDTI": { + "defaultValue": "[parameters('CallScpForDomainsWithMDTI')]", + "type": "Bool" + }, + "SkipIPs": { + "defaultValue": "[parameters('SkipIPs')]", + "type": "Array" + }, + "SkipAccounts": { + "defaultValue": "[parameters('SkipAccounts')]", + "type": "Array" + }, + "SkipHosts": { + "defaultValue": "[parameters('SkipHosts')]", + "type": "Array" + }, + "SkipCommandLines": { + "defaultValue": "[parameters('SkipCommandLines')]", + "type": "Array" + }, + "SkipFilehashes": { + "defaultValue": "[parameters('SkipFilehashes')]", + "type": "Array" + }, + "SkipUrls": { + "defaultValue": "[parameters('SkipUrls')]", + "type": "Array" + }, + "SkipDomains": { + "defaultValue": "[parameters('SkipDomains')]", + "type": "Array" + }, + "MaxMdtiOutputLenghtForPreSummarization": { + "defaultValue": "[parameters('MaxMdtiOutputLenghtForPreSummarization')]", + "type": "Int" + }, + "WriteUnsummarizedResultsToIncident": { + "defaultValue": "[parameters('WriteUnsummarizedResultsToIncident')]", + "type": "Bool" + }, + "WriteFinalSummaryToIncident": { + "defaultValue": "[parameters('WriteFinalSummaryToIncident')]", + "type": "Bool" + }, + "RecipientEmail": { + "defaultValue": "[parameters('RecipientEmail')]", + "type": "String" + }, + "SendUnsummarizedResultsByEmail": { + "defaultValue": "[parameters('SendUnsummarizedResultsByEmail')]", + "type": "Bool" + }, + "SendFinalSummaryByEmail": { + "defaultValue": "[parameters('SendFinalSummaryByEmail')]", + "type": "Bool" } }, "triggers": { @@ -54,167 +297,2476 @@ "connection": { "name": "@parameters('$connections')['azuresentinel']['connectionId']" } - }, - "body": { - "callback_url": "@{listCallbackUrl()}" - }, - "path": "/incident-creation" + }, + "body": { + "callback_url": "@{listCallbackUrl()}" + }, + "path": "/incident-creation" + } + } + }, + "actions": { + "Compose_Incident_Entities": { + "runAfter": { + }, + "type": "Compose", + "inputs": "@triggerBody()?['object']?['properties']?['relatedEntities']" + }, + "Condition_-_Validate_Entities": { + "actions": { + "For_Each_Entity": { + "foreach": "@outputs('Compose_Incident_Entities')", + "actions": { + "Switch": { + "runAfter": { + "Show_item_for_debug_purposes": [ + "Succeeded" + ] + }, + "cases": { + "Case_-_Ip_Address": { + "case": "Ip", + "actions": { + "Condition_-_IP": { + "actions": { + "Condition_-_IP_-_Use_AbuseIPDB": { + "actions": { + "Append_to_array_forScp-IPs": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-IPs", + "value": "@items('For_Each_Entity')?['properties']?['address']" + } + } + }, + "runAfter": { + "Append_IP_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForIPsWithAbuseIPDB')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_IP_-_Use_MDTI": { + "actions": { + "Append_IP_to_array_variable_IndicatorsForMDTI": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@items('For_Each_Entity')?['properties']?['address']" + } + } + }, + "runAfter": { + "Append_IP_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForIPsWithMDTI')", + true + ] + } + ] + }, + "type": "If" + }, + "Append_IP_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('IP:',trim(toLower(items('For_Each_Entity')?['properties']?['address'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "or": [ + { + "equals": [ + "@parameters('CallScpForIPsWithAbuseIPDB')", + "ture" + ] + }, + { + "equals": [ + "@parameters('CallScpForIPsWithMDTI')", + true + ] + } + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['address'])", + false + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipIPs')", + "@toLower(items('For_Each_Entity')?['properties']?['address'])" + ] + } + }, + { + "not": { + "equals": [ + "@items('For_Each_Entity')?['properties']?['address']", + "127.0.0.1" + ] + } + }, + { + "not": { + "equals": [ + "@items('For_Each_Entity')?['properties']?['address']", + "::1" + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "10." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "192.168." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.16." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.17." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.18." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.19." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.20." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.21." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.22." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.23." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.24." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.25." + ] + } + }, + { + "not": { + "endsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.26." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.27." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.28." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.29." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.30." + ] + } + }, + { + "not": { + "startsWith": [ + "@items('For_Each_Entity')?['properties']?['address']", + "172.31." + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fd00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fc00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "ff00::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "fe80::" + ] + } + }, + { + "not": { + "startsWith": [ + "@toLower(items('For_Each_Entity')?['properties']?['address'])", + "::ffff:" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Account": { + "case": "Account", + "actions": { + "Condition_-_Account": { + "actions": { + "Append_User_Upn_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Account:',trim(toLower(items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName'])))" + } + }, + "old_Condition_-_Account_-_Get_User_Details": { + "actions": { + "Append_to_array_forScp-UserForDetails": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-UsersForDetails", + "value": "@items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName']" + } + } + }, + "runAfter": { + "Append_User_Upn_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForAccounts')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Account_-_Get_User_Risk": { + "actions": { + "Append_UPN_to_array_variable_forScp-UsersForRiskAnalysis": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-UsersForRiskAnalysis", + "value": "@items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName']" + } + } + }, + "runAfter": { + "Append_User_Upn_to_ProcessedItems": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('CallScpForUserRisk')", + true + ] + } + ] + }, + "type": "If" + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "or": [ + { + "equals": [ + "@parameters('CallScpForAccounts')", + true + ] + }, + { + "equals": [ + "@parameters('CallScpForUserRisk')", + true + ] + } + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['accountName'])", + false + ] + }, + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['additionalData']?['UserPrincipalName'])", + false + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipAccounts')", + "@toLower(items('For_Each_Entity')?['properties']?['accountName'])" + ] + } + }, + { + "not": { + "endsWith": [ + "@items('For_Each_Entity')?['properties']?['accountName']", + "$" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Host": { + "case": "Host", + "actions": { + "Condition_-_Host": { + "actions": { + "Append_Device_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Device:',trim(toLower(items('For_Each_Entity')?['properties']?['hostName'])))" + } + }, + "Append_to_array_forScp-Devices": { + "runAfter": { + "Append_Device_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Devices", + "value": "@items('For_Each_Entity')?['properties']?['hostName']" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['hostName'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForHosts')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipHosts')", + "@toLower(items('For_Each_Entity')?['properties']?['hostName'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_Process": { + "case": "Process", + "actions": { + "Condition_-_Command_Line": { + "actions": { + "Set_variable_CommandLineNormalized": { + "type": "SetVariable", + "inputs": { + "name": "CommandLineNormalized", + "value": "@tolower(trim(replace(replace(items('For_Each_Entity')?['properties']?['commandLine'],' ',' '),'net1 ','net ')))" + } + }, + "Append_CommandLineNormalized_to_ProcessedItem": { + "runAfter": { + "Set_variable_CommandLineNormalized": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('CommandLine:',variables('CommandLineNormalized'))" + } + }, + "Append_to_array_variable_forScp-CommandLines": { + "runAfter": { + "Append_CommandLineNormalized_to_ProcessedItem": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-CommandLines", + "value": "@variables('CommandLineNormalized')" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['commandLine'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForCommandLines')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipCommandLines')", + "@toLower(items('For_Each_Entity')?['properties']?['commandLine'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_-_FileHash": { + "case": "FileHash", + "actions": { + "Condition_-_FileHash": { + "actions": { + "Append_Filehash_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_FileHash_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(items('For_Each_Entity')?['properties']?['hashValue'])" + } + }, + "Append_FileHash_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('FileHash:',trim(toLower(items('For_Each_Entity')?['properties']?['hashValue'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['hashValue'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForFilehashesWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipFilehashes')", + "" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_Url": { + "case": "Url", + "actions": { + "Condition_-_Url": { + "actions": { + "Append_Url_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_URL_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(split(items('For_Each_Entity')?['properties']?['url'],'?')[0])" + } + }, + "Append_URL_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('URL:',trim(toLower(split(items('For_Each_Entity')?['properties']?['url'],'?')[0])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['url'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForUrlsWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipUrls')", + "@toLower(items('For_Each_Entity')?['properties']?['url'])" + ] + } + } + ] + }, + "type": "If" + } + } + }, + "Case_DNS": { + "case": "DNS", + "actions": { + "Condition_-_DNS": { + "actions": { + "Append_Domain_to_array_variable_IndicatorsForMDTI": { + "runAfter": { + "Append_Domain_to_ProcessedItems": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "forScp-Indicators", + "value": "@toLower(items('For_Each_Entity')?['properties']?['domainName'])" + } + }, + "Append_Domain_to_ProcessedItems": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ProcessedItems", + "value": "@concat('Domain:',trim(toLower(items('For_Each_Entity')?['properties']?['domainName'])))" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@empty(items('For_Each_Entity')?['properties']?['domainName'])", + false + ] + }, + { + "equals": [ + "@parameters('CallScpForDomainsWithMDTI')", + true + ] + }, + { + "not": { + "contains": [ + "@parameters('SkipDomains')", + "@toLower(items('For_Each_Entity')?['properties']?['domainName'])" + ] + } + } + ] + }, + "type": "If" + } + } + } + }, + "default": { + "actions": { + } + }, + "expression": "@items('For_Each_Entity')?['kind']", + "type": "Switch" + }, + "Show_item_for_debug_purposes": { + "type": "Compose", + "inputs": "@items('For_Each_Entity')" + } + }, + "type": "Foreach" + }, + "Condition_Call_for_IP_Addresses": { + "actions": { + "For_each_forScp-IPs": { + "foreach": "@variables('Unique-forScp-IPs')", + "actions": { + "Security_Copilot_prompt_for_IP": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "I'm curious about any abuseipdb records for the IP address @{items('For_each_forScp-IPs')}. Can you look that up for me for the last 30 days?" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_IPs_with_AbuseIPBD_was_successful": { + "actions": { + "Set_variable_ResultsAvailable_-_IPs": { + "runAfter": { + "Append_to_Results-IPs_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-IPs_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-IPs", + "value": "\nResults of the analysis for the IP '@{items('For_each_forScp-IPs')}':\n\n@{body('Security_Copilot_prompt_for_IP')?['EvaluationResultContent']}\n " + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_IP": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-IPs_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-IPs", + "value": "\nResults of the analysis for the IP '@{items('For_each_forScp-IPs')}':\n\nError while retrieving the results: @{body('Security_Copilot_prompt_for_IP')?['EvaluationResultContent']}\n " + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_IP')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-IPs_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-IPs_for_debug_purposes": { + "runAfter": { + "Set_variable_-_Unique-forScp-IPs": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-IPs')" + }, + "Set_variable_-_Unique-forScp-IPs": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-IPs", + "value": "@sort(union(variables('forScp-IPs'),variables('forScp-IPs')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-IPs'),null)), greater(length(variables('forScp-IPs')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Users_Details": { + "actions": { + "For_each_forScp-UsersForDetails": { + "foreach": "@variables('Unique-forScp-UsersForDetails')", + "actions": { + "Security_Copilot_prompt_for_Account_": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Give me all usr details for @{items('For_each_forScp-UsersForDetails')}. Does have any registered devices in Microsoft Entra? List the recent risky sign-ins for @{items('For_each_forScp-UsersForDetails')}. Get Microsoft Entra audit logs for @{items('For_each_forScp-UsersForDetails')} for the past 72 hours.", + "SkillName": "GetEntraUserDetailsV1" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_Account_on_Entra_was_successful": { + "actions": { + "Set_variable_-_ResultsAvailable_-_Accounts": { + "runAfter": { + "Append_to_Results-UsersDetails_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-UsersDetails_-_Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersDetails", + "value": "\nResults of the analysis for the account '@{items('For_each_forScp-UsersForDetails')}':\n\n@{body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']}\n " + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Account_": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-UsersDetails_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersDetails", + "value": "\nResults of the analysis for the account '@{items('For_each_forScp-UsersForDetails')}':\n\nError while retrieving the results: @{body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']}\n " + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Account_')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-UsersForDetails_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-UsersForDetails_for_debug_purposes": { + "runAfter": { + "Set_variable_-_Unique-forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-UsersForDetails')" + }, + "Set_variable_-_Unique-forScp-UsersForDetails": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-UsersForDetails", + "value": "@sort(union(variables('forScp-UsersForDetails'),variables('forScp-UsersForDetails')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-UsersForDetails'),null)), greater(length(variables('forScp-UsersForDetails')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Users_Risk": { + "actions": { + "Security_Copilot_prompt_for_Users_Risk": { + "runAfter": { + "Show_Unique-forScp-UsersForRiskAnalysis_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "@join(variables('Unique-forScp-UsersForRiskAnalysis'),', ')", + "SkillName": "GetEntraRiskyUsers" + }, + "path": "/process-prompt" + } + }, + "Condition_-_Users_Risk_retrieved": { + "actions": { + "Compose_Users_Risk": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']" + }, + "Set_variable_-_ResultsAvailable_-_Users_Risk": { + "runAfter": { + "Append_to_Results-UsersRisk_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-UsersRisk_-_Success": { + "runAfter": { + "Compose_Users_Risk": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersRisk", + "value": "@body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Users_Risk": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-UsersRisk_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-UsersRisk", + "value": "Error while retrieving the users risk: '@{body('Security_Copilot_prompt_for_Users_Risk')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Users_Risk')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Show_Unique-forScp-UsersForRiskAnalysis_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-UsersForRiskAnalysis')" + }, + "Set_variable_Unique-forScp-UsersForRiskAnalysis": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-UsersForRiskAnalysis", + "value": "@sort(union(variables('forScp-UsersForRiskAnalysis'),variables('forScp-UsersForRiskAnalysis')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-UsersForRiskAnalysis'),null)), greater(length(variables('forScp-UsersForRiskAnalysis')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Devices_Details": { + "actions": { + "For_each_forScp-Devices": { + "foreach": "@variables('Unique-forScp-Devices')", + "actions": { + "Security_Copilot_prompt_for_Device": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Tell me about device @{items('For_each_forScp-Devices')}.", + "SkillName": "GetIntuneDevices" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_devices_on_Intune_was_successful": { + "actions": { + "Set_variable_-_ResultsAvailable_-_Devices": { + "runAfter": { + "Append_to_Results-Devices-Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-Devices-Success": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-Devices", + "value": "\nResults of the analysis for the device '@{items('For_each_forScp-Devices')}':\n\n@{body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']}\n" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Device": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_results-Devices-Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-Devices", + "value": "\nResults of the analysis for the device '@{items('For_each_forScp-Devices')}': \n\nError while retrieving the results: '@{body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']}'\n" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Device')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-Devices_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "Foreach" + }, + "Show_Unique-forScp-Devices_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-Devices": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-Devices')" + }, + "Set_variable_Unique-forScp-Devices": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-Devices", + "value": "@sort(union(variables('forScp-Devices'),variables('forScp-Devices')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-Devices'),null)), greater(length(variables('forScp-Devices')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_Command_Lines": { + "actions": { + "Security_Copilot_prompt_for_Unique_Processes": { + "runAfter": { + "Show_Unique-forScp-CommandLines_for_debug_purposes": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "From the following list, extract the command lines and related descriptions containing commands that are most likely DIRECTLY CORRELATED to malicious activities. Remove all command lines containing only the names of well-known portable executable files that are part of the Windows or Linux operating system, where no additional parameters are specified. For example, remove 'notepad.exe' and 'cmd.exe' but keep 'notepad.exe -param1' or 'cmd.exe -paramX' or 'unknownfile.exe'. The list starts here:\n \n@{join(variables('Unique-forScp-CommandLines'),'\n')}", + "SkillName": "ScriptAnalyzer" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_Processes_was_successful": { + "actions": { + "Compose_Process_Analysis": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']" + }, + "Set_variable_-_ResultsAvailable_-_Command_Lines": { + "runAfter": { + "Append_to_Results-CommandLines_-_Success": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + }, + "Append_to_Results-CommandLines_-_Success": { + "runAfter": { + "Compose_Process_Analysis": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-CommandLines", + "value": "@body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_Unique_Processes": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_Results-CommandLines_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "Results-CommandLines", + "value": "Error while analyzing the command lines: '@{body('Security_Copilot_prompt_for_Unique_Processes')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_Unique_Processes')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Show_Unique-forScp-CommandLines_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-CommandLines')" + }, + "Set_variable_Unique-forScp-CommandLines": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-CommandLines", + "value": "@sort(union(variables('forScp-CommandLines'),variables('forScp-CommandLines')))" + } + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@and(not(equals(variables('forScp-CommandLines'), null)), greater(length(variables('forScp-CommandLines')), 0))", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_Call_SCP_for_MDTI_indicators": { + "actions": { + "Until": { + "actions": { + "Set_variable_SubarrayIndicatorsForMDTI": { + "type": "SetVariable", + "inputs": { + "name": "SubarrayIndicatorsForMDTI", + "value": "@take(skip(variables('Unique-forScp-Indicators'),variables('CounterIndicatorsForMDTI')),10)" + } + }, + "Set_variable_CsvIndicatorsForMDTI": { + "runAfter": { + "Set_variable_SubarrayIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CsvIndicatorsForMDTI", + "value": "@join(variables('SubarrayIndicatorsForMDTI'),', ')" + } + }, + "Security_Copilot_prompt_for_IoC_to_MDTI": { + "runAfter": { + "Set_variable_CsvIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "By looking at MDTI, tell me the reputation of these indicators: @{variables('CsvIndicatorsForMDTI')} ", + "SkillName": "GetReputationsForIndicators" + }, + "path": "/process-prompt" + } + }, + "Compose_New_Counter_Value": { + "runAfter": { + "Condition_-_SCP_-_Call_for_indicators_was_successful": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@add(variables('CounterIndicatorsForMDTI'),10)" + }, + "Set_variable_Counter": { + "runAfter": { + "Compose_New_Counter_Value": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "CounterIndicatorsForMDTI", + "value": "@outputs('Compose_New_Counter_Value')" + } + }, + "Condition_-_SCP_-_Call_for_indicators_was_successful": { + "actions": { + "Compose_IoC_from_MDTI": { + "type": "Compose", + "inputs": "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']" + }, + "Append_to_array_variable_ResponsesFromMDTI_-_Success": { + "runAfter": { + "Compose_IoC_from_MDTI": [ + "Succeeded" + ] + }, + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponsesFromMDTI", + "value": "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Security_Copilot_prompt_for_IoC_to_MDTI": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Append_to_array_variable_ResponsesFromMDTI_-_Error": { + "type": "AppendToArrayVariable", + "inputs": { + "name": "ResponsesFromMDTI", + "value": "Error during the iteration @{add(div(variables('CounterIndicatorsForMDTI'),10),1)} wirh MDTI: '@{body('Security_Copilot_prompt_for_IoC_to_MDTI')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_prompt_for_IoC_to_MDTI')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Show_Unique-forScp-Indicators_for_debug_purposes": [ + "Succeeded" + ] + }, + "expression": "@equals(greaterOrEquals(length(variables('Unique-forScp-Indicators')),variables('CounterIndicatorsForMDTI')),false)", + "limit": { + "count": 60, + "timeout": "PT1H" + }, + "type": "Until" + }, + "Set_variable_OutputLength_-_MDTI_Output": { + "runAfter": { + "Set_variable_original_Response-MDTI": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "OutputLengthMDTI", + "value": "@length(variables('Response-MDTI'))" + } + }, + "Condition_-_MDTI_Output_Length": { + "actions": { + "Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "From the following detailed list containing the results of the analysis of Indicators of Compromise (IoC) made by MDTI, extract suspicious and malicious indicators with the all the related details on reputation and explanations. Do not return unknown indicators. The list starts here: @{variables('Response-MDTI')}\n", + "SkillName": "SummarizeData" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Call_for_indicator_summarization_was_successful": { + "actions": { + "Set_variable_summarized_Response-MDTI_-_Success": { + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "@body('Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response')?['EvaluationResultContent']" + } + } + }, + "runAfter": { + "Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Set_variable_summarized_Response-MDTI_-_Error": { + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "Error while summarizing the results retrieved from MDTI: '@{body('Submit_a_Security_Copilot_prompt_-_Summarize_MDTI_response')?['EvaluationResultContent']}'" + } + } + } + }, + "expression": { + "and": [ + { + "equals": [ + "", + "" + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Condition_-_MDTI_Results_available": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "greater": [ + "@variables('OutputLengthMDTI')", + "@parameters('MaxMdtiOutputLenghtForPreSummarization')" + ] + } + ] + }, + "type": "If" + }, + "Set_variable_original_Response-MDTI": { + "runAfter": { + "Until": [ + "Succeeded" + ] + }, + "type": "SetVariable", + "inputs": { + "name": "Response-MDTI", + "value": "@join(variables('ResponsesFromMDTI'),' \n\n\n')" + } + }, + "Show_Unique-forScp-Indicators_for_debug_purposes": { + "runAfter": { + "Set_variable_Unique-forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@variables('Unique-forScp-Indicators')" + }, + "Set_variable_Unique-forScp-Indicators": { + "type": "SetVariable", + "inputs": { + "name": "Unique-forScp-Indicators", + "value": "@sort(union(variables('forScp-Indicators'),variables('forScp-Indicators')))" + } + }, + "Condition_-_MDTI_Results_available": { + "actions": { + "Set_variable_-_ResultsAvailable_-_MDTI": { + "type": "SetVariable", + "inputs": { + "name": "ResultsAvailable", + "value": true + } + } + }, + "runAfter": { + "Set_variable_OutputLength_-_MDTI_Output": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "greater": [ + "@variables('OutputLengthMDTI')", + 0 + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "For_Each_Entity": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "or": [ + { + "equals": [ + "@and(not(equals(variables('forScp-Indicators'),null)), greater(length(variables('forScp-Indicators')), 0))", + true + ] + } + ] + }, + "type": "If" + } + }, + "runAfter": { + "Condition_-_Validate_Parameters": [ + "Succeeded" + ] + }, + "else": { + "actions": { + "No_Entities_Found_-_Cancel_Execution": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "greater": [ + "@length(outputs('Compose_Incident_Entities'))", + 0 + ] + } + ] + }, + "type": "If" + }, + "Initialize_variable_CommandLineNormalized": { + "runAfter": { + "Compose_Incident_Entities": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CommandLineNormalized", + "type": "string" + } + ] + } + }, + "Initialize_variable_forScp-CommandLines": { + "runAfter": { + "Initialize_variable_ProcessedCommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-CommandLines", + "type": "array" + } + ] + } + }, + "Initialize_variable_ProcessedCommandLines": { + "runAfter": { + "Initialize_variable_CommandLineNormalized": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ProcessedCommandLines", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-Indicators": { + "runAfter": { + "Initialize_variable_Results-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-Indicators", + "type": "array" + } + ] + } + }, + "Initialize_variable_CounterIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_Unique-forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CounterIndicatorsForMDTI", + "type": "integer", + "value": 0 + } + ] + } + }, + "Initialize_variable_SubarrayIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_CounterIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "SubarrayIndicatorsForMDTI", + "type": "array" + } + ] + } + }, + "Initialize_variable_CsvIndicatorsForMDTI": { + "runAfter": { + "Initialize_variable_SubarrayIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "CsvIndicatorsForMDTI", + "type": "string" + } + ] + } + }, + "Initialize_variable_ResponsesFromMDTI": { + "runAfter": { + "Initialize_variable_CsvIndicatorsForMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResponsesFromMDTI", + "type": "array" + } + ] + } + }, + "Initialize_variable_OutputLength": { + "runAfter": { + "Initialize_variable_Results-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "OutputLengthMDTI", + "type": "integer" + } + ] + } + }, + "Initialize_variable_Response-MDTI": { + "runAfter": { + "Initialize_variable_ResponsesFromMDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Response-MDTI", + "type": "string" + } + ] + } + }, + "Initialize_variable_forScp-UsersForRiskAnalysis": { + "runAfter": { + "Initialize_variable_Results-UsersDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-UsersForRiskAnalysis", + "type": "array" + } + ] + } + }, + "Initialize_variable_ProcessedItems": { + "runAfter": { + "Initialize_variable_OutputLength": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ProcessedItems", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-UsersForDetails": { + "runAfter": { + "Initialize_variable_Response-MDTI": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-UsersForDetails", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-Devices": { + "runAfter": { + "Initialize_variable_Results-UsersRisk": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-Devices", + "type": "array" + } + ] + } + }, + "Initialize_variable_forScp-IPs": { + "runAfter": { + "Initialize_variable_Unique-forScp-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "forScp-IPs", + "type": "array" + } + ] + } + }, + "Initialize_variable_Unique-forScp-CommandLines": { + "runAfter": { + "Initialize_variable_forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-CommandLines", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-Indicators": { + "runAfter": { + "Initialize_variable_forScp-Indicators": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-Indicators", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-UsersForDetails": { + "runAfter": { + "Initialize_variable_forScp-UsersForDetails": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-UsersForDetails", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-UsersForRiskAnalysis": { + "runAfter": { + "Initialize_variable_forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-UsersForRiskAnalysis", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-Devices": { + "runAfter": { + "Initialize_variable_Results-Devices": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-Devices", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Unique-forScp-IPs": { + "runAfter": { + "Initialize_variable_forScp-IPs": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Unique-forScp-IPs", + "type": "array", + "value": [ + ] + } + ] } - } - }, - "actions": { - "Compose_Incident_Entities": { + }, + "Initialize_variable_ResultsAvailable": { "runAfter": { + "Initialize_variable_ProcessedItems": [ + "Succeeded" + ] }, - "type": "Compose", - "inputs": "@triggerBody()?['object']?['properties']?['relatedEntities']" + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "ResultsAvailable", + "type": "boolean", + "value": false + } + ] + } }, - "Condition_-_Validate_Entities": { + "Condition_-_Results_Available": { "actions": { - "For_Each_Entity": { - "foreach": "@outputs('Compose_Incident_Entities')", + "Compose_-_Incident_Data": { + "type": "Compose", + "inputs": "Incident Ttile: \n@{triggerBody()?['object']?['properties']?['title']},\n\nIncident Description: \n@{triggerBody()?['object']?['properties']?['description']},\n\nIncident Severity: \n@{triggerBody()?['object']?['properties']?['severity']},\n\nIncident Tactics: \n@{triggerBody()?['object']?['properties']?['additionalData']?['tactics']},\n\n______________________________________\nIp Addresses Analysis: \n@{if(parameters('CallScpForIPsWithAbuseIPDB'),variables('Results-IPs'),'skipped...')},\n\n______________________________________\nAccounts Analysis: \n@{if(parameters('CallScpForAccounts'),variables('Results-UsersDetails'),'skipped...')},\n\n______________________________________\nUsers Risk Analysis: \n@{if(parameters('CallScpForUserRisk'),outputs('Compose_Users_Risk'),'skipped...')},\n\n______________________________________\nDevices Analysis: \n@{if(parameters('CallScpForHosts'),variables('Results-Devices'),'skipped...')},\n\n______________________________________\nProcesses Analysis: \n@{if(parameters('CallScpForCommandLines'),outputs('Compose_Process_Analysis'),'skipped...')}\n\n______________________________________\nIndicators Analysis from MDTI: \n@{if(or(parameters('CallScpForIPsWithMDTI'),parameters('CallScpForFilehashesWithMDTI'),parameters('CallScpForUrlsWithMDTI'),parameters('CallScpForDomainsWithMDTI')),variables('Response-MDTI'),'skipped...')}" + }, + "Compose_-_Normalized_Incident_Data": { + "runAfter": { + "Compose_-_Incident_Data": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Incident_Data'),',null',''),'[null,','')" + }, + "Condition_-_Send_unsummarized_results_by_email": { "actions": { - "Switch": { - "cases": { - "Case_-_Ip_Address": { - "case": "Ip", - "actions": { - "Security_Copilot_prompt_for_AbuseIPDB": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "I'm curious about any abuseipdb records for the IP address @{items('For_Each_Entity')?['properties']?['address']}. Can you look that up for me for the last 30 days?" - }, - "path": "/process-prompt" - } - }, - "Compose_AbuseDBIP_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_AbuseIPDB": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_AbuseIPDB')?['EvaluationResultContent']" - } + "Send_an_email_(V2)_-_Enrichment_Details": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" } }, - "Case_-_Account": { - "case": "Account", - "actions": { - "Security_Copilot_prompt_for_Account_": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "Give me all usr details for @{items('For_Each_Entity')?['properties']?['accountName']}. Does @{items('For_Each_Entity')?['properties']?['accountName']}have any registered devices in Microsoft Entra? List the recent risky sign-ins for @{items('For_Each_Entity')?['properties']?['accountName']}. Get Microsoft Entra audit logs for @{items('For_Each_Entity')?['properties']?['accountName']} for the past 72 hours.", - "SkillName": "GetEntraUserDetails" - }, - "path": "/process-prompt" + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Details", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Details---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003eIncident URL: @{concat('https://security.microsoft.com/incident2/',triggerBody()?['object']?['properties']?['providerIncidentId'],'/overview')}\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + } + }, + "runAfter": { + "Compose_-_Details_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + } + ] + }, + "type": "If" + }, + "Condition_-_Write_unsummarized_results_on_incident": { + "actions": { + "Add_unsummarized_results_to_incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } + }, + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Details---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e\u003cbr\u003e" + }, + "path": "/Incidents/Comment" + } + } + }, + "runAfter": { + "Compose_-_Details_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteUnsummarizedResultsToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Security_Copilot_summarize_incident_investigation": { + "runAfter": { + "Condition_-_Send_unsummarized_results_by_email": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ], + "Condition_-_Write_unsummarized_results_on_incident": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['securitycopilot']['connectionId']" + } + }, + "method": "post", + "body": { + "PromptContent": "Here is the security incident detail: @{outputs('Compose_-_Normalized_Incident_Data')}\n\nPerform investigation on security incident in following order. Incident Overview, Incident Description, Ip Addresses Analysis , Account Analysis, Users Risk Analysis, Devices Analysis, Processes Analysis, Indicators Analysis from MDTI, Possible mitigation steps and Conclusion. Ignore all error messages", + "SkillName": "SummarizeData" + }, + "path": "/process-prompt" + } + }, + "Condition_-_SCP_-_Final_prompt_was_successful": { + "actions": { + "Compose_-_Incident_Investigation": { + "type": "Compose", + "inputs": "@body('Security_Copilot_summarize_incident_investigation')?['EvaluationResultContent']" + }, + "Condition_-_Write_final_summary_to_incident_-_Success": { + "actions": { + "Add_Investigation_Final_summary_to_Incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" } }, - "Compose_Account_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_Account_": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_Account_')?['EvaluationResultContent']" - } + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Summary ---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003e\u003cbr\u003e@{outputs('Compose_-_Incident_Investigation')}\u003c/p\u003e\u003cbr\u003e" + }, + "path": "/Incidents/Comment" } - }, - "Case_-_Host": { - "case": "Host", - "actions": { - "Security_Copilot_prompt_for_Device": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "Tell me about device @{items('For_Each_Entity')?['properties']?['hostName']}.", - "SkillName": "GetIntuneDevices" - }, - "path": "/process-prompt" + } + }, + "runAfter": { + "Compose_-_Incident_Investigation": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteFinalSummaryToIncident')", + true + ] + } + ] + }, + "type": "If" + }, + "Condition_-_RecipientEmail_-_Final_-_Success": { + "actions": { + "Send_an_email_(V2)_-_Enrichment_Summary_-_Success": { + "runAfter": { + "Compose_-_Summary_-_Success_-_Replace_NewLine_char": [ + "Succeeded" + ] + }, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" } }, - "Compose_Device_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_Device": [ - "Succeeded" - ] + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Summary", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Summary ---\u003c/strong\u003e\u003c/b\u003e\u003cbr\u003eIncident URL: @{concat('https://security.microsoft.com/incident2/',triggerBody()?['object']?['properties']?['providerIncidentId'],'/overview')}\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Summary_-_Success_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + }, + "Compose_-_Summary_-_Success_-_Replace_NewLine_char": { + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Incident_Investigation'),'\\n','\u003cbr\u003e'),'\n','\u003cbr\u003e')" + } + }, + "runAfter": { + "Compose_-_Incident_Investigation": [ + "Succeeded" + ] + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + } + ] + }, + "type": "If" + }, + "Terminate_-_Success": { + "runAfter": { + "Condition_-_Write_final_summary_to_incident_-_Success": [ + "Succeeded" + ], + "Condition_-_RecipientEmail_-_Final_-_Success": [ + "Succeeded" + ] + }, + "type": "Terminate", + "inputs": { + "runStatus": "Succeeded" + } + } + }, + "runAfter": { + "Security_Copilot_summarize_incident_investigation": [ + "Succeeded", + "TimedOut", + "Skipped", + "Failed" + ] + }, + "else": { + "actions": { + "Condition_-_Unsummarized_results_not_already_written_to_incident": { + "actions": { + "Add_Investigation_Final_comment_to_Incident": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azuresentinel']['connectionId']" + } }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_Device')?['EvaluationResultContent']" + "method": "post", + "body": { + "incidentArmId": "@triggerBody()?['object']?['id']", + "message": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Evidences ---\u003c/strong\u003e\u003c/b\u003e\u003c/p\u003e\u003cp class=\"editor-paragraph\"\u003e\u003ci\u003e\u003cem class=\"editor-text-italic\"\u003e(Content not summarized)\u003c/em\u003e\u003c/i\u003e\u003c/p\u003e\u003cbr\u003e\u003cp class=\"editor-paragraph\"\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e" + }, + "path": "/Incidents/Comment" } } }, - "Case_-_Process": { - "case": "Process", + "else": { "actions": { - "Security_Copilot_prompt_for_Process": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" - } - }, - "method": "post", - "body": { - "PromptContent": "Tell me more about the process command line: @{items('For_Each_Entity')?['properties']?['commandLine']}", - "SkillName": "ScriptAnalyzer" - }, - "path": "/process-prompt" - } + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('WriteUnsummarizedResultsToIncident')", + false + ] }, - "Compose_Process_Analysis": { - "runAfter": { - "Security_Copilot_prompt_for_Process": [ - "Succeeded" - ] - }, - "type": "Compose", - "inputs": "@body('Security_Copilot_prompt_for_Process')?['EvaluationResultContent']" + { + "equals": [ + "@parameters('WriteFinalSummaryToIncident')", + true + ] } + ] + }, + "type": "If" + }, + "Terminate_-_Failed": { + "runAfter": { + "Condition_-_Unsummarized_results_not_already_written_to_incident": [ + "Succeeded" + ], + "Condition_-_Unsummarized_results_not_already_sent_by_email": [ + "Succeeded" + ] + }, + "type": "Terminate", + "inputs": { + "runStatus": "Failed", + "runError": { + "message": "Final summarization failed" } } }, - "default": { + "Condition_-_Unsummarized_results_not_already_sent_by_email": { "actions": { - } - }, - "expression": "@items('For_Each_Entity')?['kind']", - "type": "Switch" + "Send_an_email_(V2)_-_Enrichment_Summary_-_Failure": { + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['office365']['connectionId']" + } + }, + "method": "post", + "body": { + "To": "@trim(parameters('RecipientEmail'))", + "Subject": "Incident @{triggerBody()?['object']?['properties']?['providerIncidentId']} - Enrichment Summary - Failure", + "Body": "\u003cp class=\"editor-paragraph\"\u003e\u003cb\u003e\u003cstrong class=\"editor-text-bold\"\u003e--- Incident Analysis - Evidences ---\u003c/strong\u003e\u003c/b\u003e\u003cbr\u003e\u003ci\u003e\u003cem class=\"editor-text-italic\"\u003e(Content not summarized)\u003c/em\u003e\u003c/i\u003e\u003cbr\u003e\u003cbr\u003e@{outputs('Compose_-_Details_-_Replace_NewLine_char')}\u003c/p\u003e", + "Importance": "Normal" + }, + "path": "/v2/Mail" + } + } + }, + "else": { + "actions": { + } + }, + "expression": { + "and": [ + { + "equals": [ + "@parameters('SendUnsummarizedResultsByEmail')", + false + ] + }, + { + "equals": [ + "@parameters('SendFinalSummaryByEmail')", + true + ] + } + ] + }, + "type": "If" + } } }, - "type": "Foreach" + "expression": { + "and": [ + { + "equals": [ + "@body('Security_Copilot_summarize_incident_investigation')?['evaluationResultType']", + "Success" + ] + } + ] + }, + "type": "If" + }, + "Compose_-_Details_-_Replace_NewLine_char": { + "runAfter": { + "Compose_-_Normalized_Incident_Data": [ + "Succeeded" + ] + }, + "type": "Compose", + "inputs": "@replace(replace(outputs('Compose_-_Normalized_Incident_Data'),'\\n','\u003cbr\u003e'),'\n','\u003cbr\u003e')" } }, "runAfter": { - "Compose_Incident_Entities": [ + "Condition_-_Validate_Entities": [ "Succeeded" ] }, "else": { "actions": { - "No_Entities_Found_-_Cancel_Execution": { + "No_results_available": { "type": "Terminate", "inputs": { "runStatus": "Cancelled" @@ -225,73 +2777,149 @@ "expression": { "and": [ { - "greater": [ - "@length(outputs('Compose_Incident_Entities'))", - 0 + "equals": [ + "@variables('ResultsAvailable')", + true ] } ] }, "type": "If" }, - "Compose_-_Incident_Data": { + "Condition_-_Validate_Parameters": { + "actions": { + }, "runAfter": { - "Condition_-_Validate_Entities": [ + "Initialize_variable_ResultsAvailable": [ "Succeeded" ] }, - "type": "Compose", - "inputs": "Incident Ttile: @{triggerBody()?['object']?['properties']?['title']},\nIncident Description: @{triggerBody()?['object']?['properties']?['description']},\nIncident Severity: @{triggerBody()?['object']?['properties']?['severity']},\nIncident Tactics: @{triggerBody()?['object']?['properties']?['additionalData']?['tactics']},\nIp Address Analysis: @{outputs('Compose_AbuseDBIP_Analysis')},\nAccount Analysis: @{outputs('Compose_Account_Analysis')},\nDevice Analysis: @{outputs('Compose_Device_Analysis')},\nProcess Analysis: @{outputs('Compose_Process_Analysis')}" + "else": { + "actions": { + "Invalid_Parameters": { + "type": "Terminate", + "inputs": { + "runStatus": "Cancelled" + } + } + } + }, + "expression": { + "and": [ + { + "contains": [ + "@parameters('RecipientEmail')", + "@" + ] + }, + { + "or": [ + { + "equals": [ + "@parameters('SendUnsummarizedResultsByEmail')", + true + ] + }, + { + "equals": [ + "@parameters('SendFinalSummaryByEmail')", + true + ] + } + ] + } + ] + }, + "type": "If" }, - "Security_Copilot_perform_incident_investigation": { + "Initialize_variable_Results-Devices": { "runAfter": { - "Compose_-_Incident_Data": [ + "Initialize_variable_forScp-Devices": [ "Succeeded" ] }, - "type": "ApiConnection", + "type": "InitializeVariable", "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['securitycopilot']['connectionId']" + "variables": [ + { + "name": "Results-Devices", + "type": "array", + "value": [ + ] } - }, - "method": "post", - "body": { - "PromptContent": "Here is the security incident detail: @{outputs('Compose_-_Incident_Data')}\n\nPerform investigation on security incident in following order. Incident Overview, Incident Description, Analysis on Ip Address, Account, Host and Process, Possible mitigation steps and Conclusion. ", - "SkillName": "SummarizeData" - }, - "path": "/process-prompt" + ] } }, - "Compose_-_Incident_Investigation": { + "Initialize_variable_Results-IPs": { "runAfter": { - "Security_Copilot_perform_incident_investigation": [ + "Initialize_variable_Unique-forScp-IPs": [ "Succeeded" ] }, - "type": "Compose", - "inputs": "@body('Security_Copilot_perform_incident_investigation')?['EvaluationResultContent']" + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-IPs", + "type": "array", + "value": [ + ] + } + ] + } }, - "Add_Investigation_Notes_to_Incident": { + "Initialize_variable_Results-UsersDetails": { "runAfter": { - "Compose_-_Incident_Investigation": [ + "Initialize_variable_Unique-forScp-UsersForDetails": [ "Succeeded" ] }, - "type": "ApiConnection", + "type": "InitializeVariable", "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['azuresentinel']['connectionId']" + "variables": [ + { + "name": "Results-UsersDetails", + "type": "array", + "value": [ + ] } - }, - "method": "post", - "body": { - "incidentArmId": "@triggerBody()?['object']?['id']", - "message": "\u003cbr\u003e@{outputs('Compose_-_Incident_Investigation')}\u003cbr\u003e" - }, - "path": "/Incidents/Comment" + ] + } + }, + "Initialize_variable_Results-UsersRisk": { + "runAfter": { + "Initialize_variable_Unique-forScp-UsersForRiskAnalysis": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-UsersRisk", + "type": "array", + "value": [ + ] + } + ] + } + }, + "Initialize_variable_Results-CommandLines": { + "runAfter": { + "Initialize_variable_Unique-forScp-CommandLines": [ + "Succeeded" + ] + }, + "type": "InitializeVariable", + "inputs": { + "variables": [ + { + "name": "Results-CommandLines", + "type": "array", + "value": [ + ] + } + ] } } }, @@ -315,6 +2943,11 @@ "connectionId": "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", "connectionName": "[variables('SecuritycopilotConnectionName')]", "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" + }, + "office365": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]", + "connectionName": "[variables('Office365ConnectionName')]", + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" } } } @@ -333,7 +2966,8 @@ "apiVersion": "2017-07-01", "dependsOn": [ "[resourceId('Microsoft.Web/connections', variables('MicrosoftSentinelConnectionName'))]", - "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]" + "[resourceId('Microsoft.Web/connections', variables('SecuritycopilotConnectionName'))]", + "[resourceId('Microsoft.Web/connections', variables('Office365ConnectionName'))]" ] }, { @@ -366,6 +3000,21 @@ "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Securitycopilot')]" } } + }, + { + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('Office365ConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('Office365ConnectionName')]", + "customParameterValues": { + }, + "api": { + "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/Office365')]" + } + } } ] -} \ No newline at end of file +} diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png new file mode 100644 index 00000000..e97f11ba Binary files /dev/null and b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/images/params.png differ diff --git a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md index fcb5ece5..1dad3e5d 100644 --- a/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md +++ b/Logic Apps/SecurityCopilot-Sentinel-Incident-Investigation/readme.md @@ -1,24 +1,25 @@ # SecurityCopilot-Sentinel-Incident-Investigation -**Author: Pratik Lal, Innocent Wafula** +**Author: Pratik Lal, Innocent Wafula, Stefano Pescosolido** ## Overview -This Logic App automates incident investigation within Microsoft Sentinel using the Microsoft Copilot for Security prompt and built-in skills. It is designed to: +This Logic App automates incident investigation within Microsoft Sentinel using the Microsoft Security Copilot prompt and built-in skills. It is designed to: - Read Microsoft Sentinel incident entities. - Process and normalize the entities. -- Submit prompts to Copilot for Security to analyze each entity type. +- Submit prompts to Security Copilot to analyze each entity type. - Summarize the outputs in a predefined format. -- Update the Microsoft Sentinel incident comments for the incident investigator to review. +- Update the Microsoft Sentinel incident comments for the incident investigator to review (writing to the Sentinel incident can be disabled for facilitating the initial testing). +- Optionally send the result of the investigation by email. By leveraging this Logic App, Security Operations Center (SOC) and incident response teams can significantly reduce investigation time, gain consistent analysis outputs, and enhance their incident response process. ## Prerequisites - The user or service principal deploying this logic app should have Contributor role on the Azure Resource Group. -- Microsoft Copilot for Security should be enabled on the Azure tenant. -- The user should have access to Microsoft Copilot for Security to submit prompts by authenticating to Microsoft Copilot for Security connector within the logic app. +- Microsoft Security Copilot should be enabled on the Azure tenant. +- The user should have access to Microsoft Security Copilot to submit prompts by authenticating to Microsoft Security Copilot connector within the logic app. - Microsoft Sentinel workspace should be set up. -- Get AbuseIPDB API key to perform IP address reputation check. [Learn more.](https://learn.microsoft.com/en-us/copilot/security/plugin-abuseipdb) -- Enable ProcessAnalyzer, GetEntraUserDetails, GetIntuneDevices and AbuseIPDB skills from copilot settings. +- Optionally get AbuseIPDB API key to perform IP address reputation check. [Learn more.](https://learn.microsoft.com/en-us/copilot/security/plugin-abuseipdb) +- Enable Defender, Sentinel, Entra, Intune, MDTI and AbuseIPDB plugins from copilot settings for the user account configured on the API connection of the Logic App to Security Copilot. ## Deployment @@ -30,34 +31,44 @@ By leveraging this Logic App, Security Operations Center (SOC) and incident resp
## Post-Deployment -- Authenticate Microsoft Sentinel and Copilot for Security connectors within the logic app. You can choose Oauth, Service principal or Managed identity based authentication for Microsoft Sentinel trigger. +- Authenticate Microsoft Sentinel and Security Copilot connectors within the logic app. You can choose Oauth, Service principal or Managed identity based authentication for Microsoft Sentinel trigger.

-- Choose Client Certificate Auth or Oauth (using user account that can submit prompts) to create connection with Microsoft Copilot for Security logic app connector. +- Choose Client Certificate Auth or Oauth (using user account that can submit prompts) to create connection with Microsoft Security Copilot logic app connector.

-- Automate Sentinel incident investigation by adding the Logic App to Microsoft Sentinel automation rule. -- Alternatively, this logic app can be called on-demand by right-clicking on a Sentinel incident, choose **Run Playbook** option. +- Important: the account selected for the authentication to Security Copilot must have the Contributor role on Security Copilot and reader privileges on Defender, Sentinel, Entra and Intune. +- Similarly, authenticate the API connection to Exchange Online (the correct setup of connection is needed even if the Logic App is configured to not send emails) +- Assign the role of 'Microsoft Sentinel Responder' to the Managed Identity of the Logic App within the Resource Group that contains the Sentinel's Log Analytics workspace. +- Set the values for the Logic App parameters + +- Automate Sentinel incident investigation by adding the Logic App to Microsoft Sentinel automation rule. Alternatively, this logic app can be called on-demand by right-clicking on a Sentinel incident, choose **Run Playbook** option. -## Microsoft Copilot for Security skills +## Microsoft Security Copilot skills - Following built-in skills are being used to perform investigation on a given incident entity type -| Incident entity | Copilot for Security skill | +| Incident entity | Security Copilot skill | | --------------- | -------------------------- | | Process name / commandline | ProcessAnalyzer | | Account name | GetEntraUserDetails | +| Account names | GetEntraRiskyUsers | | Device name | GetIntuneDevices | | IP address | AbuseIPDB | +| Indicator of Compromise (IoC) | GetReputationsForIndicators | ## Incident Investigation Template - Automated incident investigation summary is structured in following order. Modify the prompt to re-order or change the investigation template. - - Incident overview: - - Incident description: - - Analysis on incident entities: - - Possible mitigation steps: - - Conclusion + - Incident Overview, + - Incident Description, + - IP Addresses Analysis, + - Account Analysis, + - Users Risk Analysis, + - Devices Analysis, + - Processes Analysis, + - Indicators Analysis from MDTI, + - Possible mitigation steps and Conclusion

diff --git a/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml b/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml index 6f26fafe..bdab9c01 100644 --- a/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml +++ b/Logic Apps/ciso-reporting/ciso-incidents-summary-man.yaml @@ -24,7 +24,7 @@ SkillGroups: DisplayName: CISO - Incidents Status Summary Description: Get a summary of the incidents status in Defender DescriptionForModel : Get a summary of the incidents status in Defender. - If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings (like "High","Medium"). + If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium"). If start date and end date are not specified, consider the last 7 days. If the top number of results is not specified, consider 5 incidents titles. If csv_of_severities is not specified, consider only "High". @@ -81,7 +81,7 @@ SkillGroups: DisplayName: CISO - True Positive Incidents Tactics and Techniques Analysis Description: Get a summary of the tactics and techniques used in the incidents that were classified as true positives DescriptionForModel : Get a summary of the tactics and techniques used in the incidents that were classified as true positives - If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings (like "High","Medium"). + If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium"). If start date and end date are not specified, consider the last 7 days. If severities is not specified, consider only "High". Inputs: diff --git a/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml b/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml index c9a8ff19..2a78fbdf 100644 --- a/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml +++ b/Logic Apps/ciso-reporting/ciso-posture-summary-man.yaml @@ -12,6 +12,7 @@ SkillGroups: DisplayName: CISO - Recommendations by Severity Description: Get the list of the active recommendations by Severity (work in progress) DescriptionForModel : Get the list of the active recommendations by Severity + If severities are specified in the prompt (e.g. High and Medium), concatenate them as a comma separated list of double-quoted strings with the first letter in uppercase (like "High","Medium"). Inputs: - Name: days_back Description: The number of days to be considered in the past, starting from today diff --git a/Logic Apps/ciso-reporting/images/yt.png b/Logic Apps/ciso-reporting/images/yt.png new file mode 100644 index 00000000..0bf5879d Binary files /dev/null and b/Logic Apps/ciso-reporting/images/yt.png differ diff --git a/Logic Apps/ciso-reporting/install-guide.md b/Logic Apps/ciso-reporting/install-guide.md new file mode 100644 index 00000000..4faf3266 --- /dev/null +++ b/Logic Apps/ciso-reporting/install-guide.md @@ -0,0 +1,190 @@ + +# Prerequisites + +> YouTube Mini-video showing the prerequisites (2m): [CISO Reporting with Security Copilot - Prototype setup - **#0** Prerequisites] + +1. The approach and logic used in this solution can be applied with Security Copilot connected to any data source. + However, the sample custom plugins and promptbooks provided are designed to work in an environment where security is managed in the Unified SOC portal (Defender + Sentinel). + +2. This procedure assumes that you already have some experience with Security Copilot and an existing baseline compute capacity deployed. + If this is not the case, before starting the steps described below, create a minimum capacity of at least 1 SCU, which will be needed to access and configure Security Copilot. + Later in the procedure, you will be instructed to increase the number of SCUs for the initial test and for scheduled execution. + +4. The custom Logic Apps that invoke Security Copilot access it through an OAuth connection authenticated with user credentials. + Security Copilot accesses the target source systems through an impersonated access model; therefore, the credentials used in the OAuth connection from the Logic App + must have the right to read the security data in the target source systems. In the promptbooks provided in this solution, the sources are Defender, Entra, and Sentinel. + To my knowledge, at the time of this writing, it is not possible to specify 'Managed Identities' for the connection from Logic Apps to Security Copilot. + 'User credentials' are then required, even though these credentials will generally not be used by humans but only by this Logic App. + As a prerequisite for proceeding with this installation, then, it is necessary to create user credentials with the following minimum 'active' permissions + (note that 'eligible' permissions in PIM are not adequate for use in automation): + - Security Reader at tenant level (for reading data in Defender and Entra) + - Microsoft Sentinel Reader at the level of the Sentinel's resource group (for reading data in the Sentinel workspace) + - Contributor on Microsoft Security Copilot (for accessing and interacting with Security Copilot) + + These credentials also need to have an Microsoft 365 Exchange Online license assigned because they will be used to send the scheduled report email. + It is recommended to create and use a dedicated user with a clear name (such as 'Security Copilot Automation Account') and to monitor its usage to ensure that + any anomalous logon attempts with these credentials generate an alert. + Before starting this setup procedure, it is highly recommended to prepare a browser session authenticated with the credentials of the new Security Copilot Automation Account, + where you have already successfully accessed: + - The user's mailbox in Exchange Online with Outlook web access + - The Unfied SOC Portal (Defender + Sentinel, security.microsoft.com) + - The Sentinel blade in Azure (portal.azure.com) + - The Security Copilot portal (at the very first access, it requires you to acknowledge some introductory topics) + +6. If you want to generate a report on the recommendations identified by Defender for Cloud (as in the third prompt in the sample Posture Analysis promptbook documented and shared here), + you also need to enable Continuous Export from Defender for Cloud (in the relevant Azure subscriptions) to Log Analytics/Sentinel. + +7. Before starting this setup procedure, make sure to have the following information available: + - Credentials for the "Security Copilot Automation Account" + - Identification parameters for Sentinel (Subscription ID, Resource Group Name, Sentinel Workspace Name) +# Setup Procedure + +## [**STEP 1**] Create the promptbooks + + > YouTube Mini-video showing the promptbooks creation (3m): [CISO Reporting with Security Copilot - Prototype setup - **#1** Create promptbooks] + +On the Security Copilot portal, accessed with credentials with the Owner role: +- [promptbook-incident-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-incident-analysis.md) +- [OPTIONAL] [promptbook-posture-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-posture-analysis.md)\ + NOTES: + - This promptbook for posture analysis and the related automation are in a very preliminary draft state. + Not only are these prompts not optimized in terms of compute capacity consumption, but the overall promptbook is also very incomplete. + Please refer to [this article](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) + for further details. + - If you have not enabled the Continuous Export feature in Defender for Cloud, you should not include the third prompt in this sample promptbook. + +## [**STEP 2**] Deploy the Logic App [ciso-get-users-status.json](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-get-users-status.json) used by one of the skills in the custom plugin for Incidents Analysis + + > YouTube Mini-video showing the creation of the Logic App **ciso-get-users-status** (>2m): [CISO Reporting with Security Copilot - Prototype setup - **#2** Install GetUsersStatus] + +On the Azure portal, accessed with credentials with an administrative role: + - NOTE: Currently, it has no "Deploy in Azure" button; it is necessary to perform a manual deployment. + - Edit its connection to Security Copilot and authenticate with the credentials of the Security Copilot Automation Account + +## [**STEP 3**] Deploy the Logic App [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) to create the "**CISO Incidents Analysis**" automation + + > YouTube Mini-video showing the creation of the Logic App **CISO Incidents Analysis** (>5m): [CISO Reporting with Security Copilot - Prototype setup - **#3** Install Incidents Analysis Automation] + +On the Azure portal, accessed with credentials with an administrative role: + - Use the "Deploy in Azure" button + - Edit its connection to Security Copilot and authenticate with the credentials of the Security Copilot Automation Account + - Edit its connection to Office 365 and authenticate with the credentials of the Security Copilot Automation Account + - Chose the promptbook "promptbook-incident-analysis" and specify its parameters. Typically you may want to specify: + - TOP_INCIDENTS_NUMBER = 10 + - SEVERITIES = High and Medium + - TIME_FRAME = last week (NOTE: if you change this value, you have to modify the content of the ReplacePromptsInOutput workflow paramter accordingly) + - RESPONSE_LANGUAGE = set a language supported by Security Copilot + - RESPONSE_LANGUAGE = 10 + - TOP_IMPACTED_USERS_NUMBER = 5 (the value of this number has an important impact on the consumption - and, then, costs - of the solution.) + - Set all the Logic App parameters. Specifically, ensure that you have set: + - The Email Title and Email Subject + - The EmailRecipients + - The SkipPromptsInOutput and ReplacePromptsInOutput as specified at the end of the file + [promptbook-incident-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-incident-analysis.md) + - Set the recurrence. Typically it has to be set to be run once per week, like on Monday morning at 6 am. + - For the time being, keep the Logic App disabled (we will enable it when all the required configurations are in place) + + +## [**STEP 4**] [OPTIONAL] Deploy the Logic App [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) to create the "**CISO Posture Analysis**" automation + NOTE: this automation and the related promptbook are in a very preliminary draft state. + Not only are these prompts not optimized in terms of compute capacity consumption, but the overall promptbook is also very incomplete. + Please refer to [this article](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) + for further details on how this promptbook should be improved. + + > YouTube Mini-video showing the creation of the Logic App **CISO Posture Analysis** (~4m): [CISO Reporting with Security Copilot - Prototype setup - **#4** Install Posture Analysis Automation] + + - Use the "Deploy in Azure" button + - Edit its connection to Security Copilot and authenticate with the credentials of the Security Copilot Automation Account + - Edit its connection to Office 365 and authenticate with the credentials of the Security Copilot Automation Account + - Chose the promptbook "promptbook-posture-analysis" and specify its parameters. Typically you may want to specify: + - WORDS_NUMBER = 50 + - NUMBER_OF_DAYS = 30 + - NUMBER_OF_RECOMMENDATIONS = 10 + - SEVERITIES = High and Medium + - Set all the Logic App parameters. Specifically, ensure that you have set: + - The HtmlBodyHeader to change the color shade of the email + (see [promptbook-posture-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-posture-analysis.md)) + - The Email Title and Email Subject + - The EmailRecipients + - Set the recurrence. Typically it has to be set to be run once per week, like on Monday morning at 6 am. + - For the time being, keep the Logic App disabled (we will enable it when all the required configurations are in place) + +## [**STEP 5**] Deploy the Custom Plugins + + > YouTube Mini-video showing the creation of the Custom Plugins (8m): [CISO Reporting with Security Copilot - Prototype setup - **#5** Custom plugins] + + - Prepare and deploy the [ciso-incidents-summary-man](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) + custom plugin (used by the "**CISO Incidents Analysis**" automation) + NOTES: + - This custom plugin loops through the users specified in the input and queries Entra for their enablement status and risk status. + Due to the existance of the loop, it may consume a high level of compute units. You should decide if this evidence adds value to your report. + If you want to include this evidence in the output report, it is recommended to limit the number of users included in the loop + by setting a low value number in the TOP_IMPACTED_USERS_NUMBER parameter specified in the "**CISO Posture Analysis**" Logic App for the call to the + [promptbook-incident-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-incident-analysis.md) + promptbook. + If, instead, you decide to remove the call to that loop, you should not only remove the related prompt in the promptbook but also reorganize + accordingly the values for the SkipPromptsInOutput and ReplacePromptsInOutput parameters in the same "**CISO Posture Analysis**" Logic App. + - In this first draft release of this solution, the OpenAPI specification file + [ciso-incidents-summary-api-spec.yaml](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-api-spec.yaml) + contains two placeholders whose value must be replaced accorinding to the components of the full URL of the trigger for the deployed Logic App + [ciso-get-users-status.json](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-get-users-status.json). + Specifically, you need to set: + - equal to the base path of that URL (e.g. https://prod-32.northeurope.logic.azure.com:443) + - equal to the Workload ID in that URL (e.g. 836c758b842f46549f57c861ca856c7b) + You have then to save the edited file in a public storage on the Internet that is accessible to Security Copilot, for example in another public GitHub repo. + You then have to modifiy your copy of the file [ciso-incidents-summary-man.yaml](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) + by replacing the placehoder in the OpenApiSpecUrl parameter with the public URL of your modified OpenAPI specification file. + - [OPTIONAL] Deploy the [ciso-posture-summary-man](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) + custom plugin (used by the "**CISO Posture Analysis**" automation). This deployment deployment does not require any specific customization: just download the + file from the template in GitHub and upload it in your Security Copilot. + - [OPTIONAL] Deploy the [Sentinel-SOC-Optimization-CustomPlugin] https://github.com/stefanpems/cfs/tree/main/Sentinel-SOC-Optimization-CustomPlugin + custom plugin (used by the "**CISO Posture Analysis**" automation). This deployment deployment does not require any specific customization: just download the + file from the template in GitHub and upload it in your Security Copilot. + NOTES: + - While adding this custom plugin, you need to provide the Identification parameters for your Sentinel (Subscroption ID, Resource Group Name, Sentinel Workspace Name) + - If you do not want to install this custom plugin, you need to remove the second prompt from the + [promptbook-posture-analysis](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/promptbook-posture-analysis.md) that you deployed in one of the previous steps. + + +## [**STEP 6**] Enable the "**CISO Incidents Analysis**" automation and wait for the completion of its first automatic execution + + > YouTube Mini-video showing the first execution of the Incidents Analysis automation (4m): [CISO Reporting with Security Copilot - Prototype setup - **#6** Run Incidents Analysis] + + On the Security Copilot portal, accessed with credentials with the Security Copilot Automation Account: + - [**STEP 6.1**] Ensure that all the custom plugins just installed are enabled. + Optionally, test the [ciso-get-users-status.json](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-get-users-status.json) Logic App + by invoking the "CISO Get Users Staus" skill in the [ciso-incidents-summary-man](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml). + For this purpose, use the following inputs: + - api-version = 2016-10-01 + - sp = /triggers/AnalyzeUPNsCall/run + - sv = 1.0 + - users_upn = one or more comma separated UPNs of users in your tenant + + + On the Security Copilot portal, accessed with credentials with the Owner role: + - [**STEP 6.2**] Increment temporarily the number of SCUs to ensure the first "test" executions can be completed successfully. + + On the Azure portal, accessed with credentials with an administrative role: + - [**STEP 6.3**] Enable the "**CISO Incidents Analysis**" automation and wait its first automatic execution + +## [**STEP 7**] Enable the "**CISO Posture Analysis**" automation and wait for the completion of its first automatic execution + + > YouTube Mini-video showing the first execution of the Incidents Posture automation (~1m): [CISO Reporting with Security Copilot - Prototype setup - **#7** Run Posture Analysis] + + On the Azure portal, accessed with credentials with an administrative role: + - Enable the "**CISO Posture Analysis**" automation and wait its first automatic execution + +## [**STEP 8**] Schedule the execution of the reporting together with the required periodic increment of SCUs + + > YouTube Mini-video showing the setup of the periodic assignment of SCUs and of the recurrence for the reporting automations (>8m): [CISO Reporting with Security Copilot - Prototype setup - **#8** Scheduling SCUs and reporting] + + - Set the recurrence on the "**CISO Incidents Analysis**" Logic App + - Set the recurrence on the "**CISO Posture Analysis**" Logic App + - Schedule the automatic deployment of the compute units (SCUs) dedicated to the execution of these automations + to ensure that there is enough capacity (on top of your "baseline" capacity used by your users and other automations) everytime that the are executed. + For example, deploy 2 instances of the [cfs-set-X-SCUs](https://github.com/stefanpems/cfs/tree/main/cfs-set-X-SCUs) Logic App: + - One to increase the capacity by N SCUs over the baseline + - One to decrease the capacity by N SCUs when the execution of these automations is completed + + + diff --git a/Logic Apps/ciso-reporting/promptbook-incident-analysis.md b/Logic Apps/ciso-reporting/promptbook-incident-analysis.md index bc0066f5..07236a6d 100644 --- a/Logic Apps/ciso-reporting/promptbook-incident-analysis.md +++ b/Logic Apps/ciso-reporting/promptbook-incident-analysis.md @@ -1,10 +1,10 @@ # Prompts in the Incident Analysis promptbook -This is the sample series of prompts for creating the promptbook dedicated to automating the Security Incident Management status analysis: +This promptbook, containing a (sample) series of prompts for reporting the high-level status of the Security Incident Management process for the current organization, has been designed to be invoked from the [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) Logic App and requires the [CISO Incidents Summary](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-incidents-summary-man.yaml) Custom Plugin as a prerequisite. ## Prompt 1 ``` -/CisoIncidentsStatusSummary Consider the top most occurring incidents with severity in the period of . +/CisoIncidentsStatusSummary Consider the top most occurring incidents in the period of . For the input parameter named "csv_of_severities" specify the following word or words, each surrounded by double quotes, with only the first letter in uppercase and, if more than one, separated by commas: . Respond by applying ALL these 4 indications to this specific response only: 1. Start the response with the following first sentence: "This is the list of Incidents with severity created in the requested period ". Then add, within round brackets, exactly the first day and the last day considered for , as used for invoking the CisoIncidentsStatusSummary skill; use the long date format. 2. Do not return a table: return the results in paragraphs instead; highlight the incident title and write as bulleted list the numbers by status. Do not write classifications where the number is zero. Write in bold underlined the incident classified as true positive. @@ -14,7 +14,7 @@ Respond by applying ALL these 4 indications to this specific response only: ## Prompt 2 ``` -/CisoTruePositiveIncidentsTTAnalysis Consider only the incidents occurred with severity . +/CisoTruePositiveIncidentsTTAnalysis Consider only the incidents occurred . For the input parameter named "csv_of_severities" specify the following word or words, each surrounded by double quotes, with only the first letter in uppercase and, if more than one, separated by commas: . Respond by applying BOTH these 2 indications to this specific response only: 1. Start the response with the following first sentence: "These are the MITRE Tactics and Techniques for the Incidents closed as 'True Positives' in the requested period ". Then add, within round brackets, exactly the first day and the last day considered for , as used for invoking the CisoTruePositiveIncidentsTTAnalysis skill; use the long date format. 2. Do not return a table: return the results in paragraphs instead; highlight the incident title and write in a bullet the the number of occurrences, in a second bullet the list of tactics and in a third bullet the list of techniques. @@ -95,7 +95,7 @@ Call the CisoGetUsersStatus skill of the CisoIncidentsSummaryApiHelper plugin. P --- # Parameters for the Logic App that invoke the Incident Analysis promptbook and sends the results by email -(Logic App template: https://github.com/stefanpems/cfs/tree/main/CfS-SendPromptbookResultsByEmail) +(Logic App template: https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) ## Paramters related to the responses of the promptbook diff --git a/Logic Apps/ciso-reporting/promptbook-posture-analysis.md b/Logic Apps/ciso-reporting/promptbook-posture-analysis.md index c924b0bd..2329ed01 100644 --- a/Logic Apps/ciso-reporting/promptbook-posture-analysis.md +++ b/Logic Apps/ciso-reporting/promptbook-posture-analysis.md @@ -1,6 +1,8 @@ # Prompts in the Incident Analysis promptbook -This is the sample series of prompts for creating the promptbook dedicated to automating the Security Posture status analysis: +This promptbook, containing a (sample) series of prompts for reporting the high-level status of the security Posture for the current organization, has been designed to be invoked from the [CfS-SendPromptbookResultsByEmail](https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) Logic App and requires the [CISO Posture Summary](https://github.com/Azure/Security-Copilot/blob/main/Logic%20Apps/ciso-reporting/ciso-posture-summary-man.yaml) Custom Plugin as a prerequisite. + +NOTE: This promptbook is in a very preliminary draft state. Not only are these prompts not optimized in terms of compute capacity consumption, but it is also very incomplete. Please refer to [this article](https://www.linkedin.com/pulse/periodic-reporting-security-managers-cisos-using-stefano-pescosolido-fm80f/) for further details on how this promptbook should be improved. ## Prompt 1 ``` @@ -14,14 +16,14 @@ Which threats should I focus on based on their exposure scores? For each returne ## Prompt 3 ``` -/CisoRecommendationsBySeverity List the top recurring 10 Recommendations created in the last days with severity +/CisoRecommendationsBySeverity List the top active Recommendations created in the last days. For the input parameter named "csv_of_severities" specify the following word or words, each surrounded by double quotes, with only the first letter in uppercase and, if more than one, separated by commas: ``` --- # Parameters for the Logic App that invoke the Posture Analysis promptbook and sends the results by email -(Logic App template: https://github.com/stefanpems/cfs/tree/main/CfS-SendPromptbookResultsByEmail) +(Logic App template: https://github.com/Azure/Security-Copilot/tree/main/Logic%20Apps/CfS-SendPromptbookResultsByEmail) ## Paramters related to the responses of the promptbook @@ -37,9 +39,8 @@ Indexes of the prompts whose responses should not be included in the delivered e ### Value for the Logic App Paramter 'ReplacePromptsInOutput' Text to be used for replacing the prompts in the delivered email. NOTE: It may include words and numbers that should be consistent with the values specified for the input parameters of the promptbook. -(Leave empty) ``` -[] +["List the global threats that should be prioritized based on their exposure score and the vulnerabilities present in my environment.","List the recommendations for better coverage in our SIEM (Microsoft Sentinel) against the most impactful threats. Also, show the recommendations for better utilization of our collected logs.","List the top 10 high and medium severity recommendations based on the number of impacted resources in my cloud PaaS environments in Azure, AWS, and GCP."] ``` @@ -49,7 +50,7 @@ NOTE: It may include words and numbers that should be consistent with the values ### Value for the Logic App Paramter 'HtmlBodyHeader' (Shades of **green**) ``` -

 

+

 

If you’ve never used an Automation Account before, you may need to follow [these instructions] (https://learn.microsoft.com/en-us/azure/automation/quickstarts/create-azure-automation-account-portal) to enable automation in Azure. -
Once, you have that enabled find the Automation Accounts in Azure and select +Create to create a new one. -
- -![Image1](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_AA.png) -
- -After hitting next, you’ll fill out the Basics information including the Subscription, Resource group and Region. -
You’ll also need to give the Automation Account a name. -
- -![Image2](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_CreateAA.png) -
- -Select Next and confirm that the System assigned check box is checked. -
If you want to change the Networking settings (I change mine to Private access) or add Tags, do so. And then press Review + Create. -
-![Image3](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_system.png) -
- -
Now that the account is created, we need to give it permissions to make the SCU change. -
In another browser tab, locate your Security Copilot Resource and select Access Control (IAM). -
Select the +Add button and choose Add role assignment. -
-![Image4](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_IAM.png) -
- -
Select the Privileged administrator roles tab and select Contributor. -
-![Image5](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_IAM2.png) -
- -
You may be thinking - “Whoa! I’m giving Contributor rights to this account.” -
But managed identities are the preferred method for runbooks because they allow your runbook to easily access other Microsoft Entra protected resources. -
The identity is managed by the Azure platform and doesn't require you to provision or rotate any secrets. If you need more information, find it here. - -
Now let’s pop back to our other tab and create the Runbook. -
Find Runbooks in the Process Automation section. Select +Create a runbook. -
-![Image5](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_runbook.png) -
- -
Give it a name, select PowerShell as the Runbook type and 7.2 as the Runtime version. Then press Review + Create. -
-![Image6](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_runbook2.png) -
- -You should now be at the Edit PowerShell Runbook page. Paste this code in the window. This code will change the number of SCUs from your current setting to one SCU. -``` -Connect-AzAccount -Identity -Set-AzContext -Subscription "your subscription" -$Resource = Get-AzResource -ResourceGroupName YourResourceGroup -ResourceName TheNameofYourCoilotResource -ResourceType microsoft.securitycopilot/capacities -$Resource.Properties.numberOfUnits = 1 -$Resource | Set-AzResource -Force -``` -You can test it if you like in the Test pane. This will show you if there are any errors. Save the runbook. - -Now, we’re going to set it so that this runbook kicks off every night at 10 p.m. Make your way back to the Azure Automation page and select Schedules in the Shared Resources section. Select +Add a schedule. Give the schedule a Name and select the date you want it to start. Select Recurring in the Recurrence section. And set Recur every to 1 Day. Set the expiration if you want to. Now select Create. -
-![Image7](https://github.com/anfisher1967/Copilot-For-Security/blob/main/Images/Azure%20Automation%20Images/_schedule.png) -
- -Now you’re all finished! You can duplicate these steps to create another Automation that increases the SCUs at 7 am or whatever time your team starts their day. - -And remember, if you are working an incident at 2 a.m. and need more SCUs, you can manually change this in the Azure portal at any time. diff --git a/Monitoring/SCU Capacity Optimizer/customrole/azuredeploy.json b/Monitoring/SCU Capacity Optimizer/customrole/azuredeploy.json deleted file mode 100644 index e577b313..00000000 --- a/Monitoring/SCU Capacity Optimizer/customrole/azuredeploy.json +++ /dev/null @@ -1,43 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/subscriptiondeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "SecurityCopilotCreatorRoleName": { - "defaultValue": "Security Copilot Compute Creator", - "type": "string" - } - }, - "variables": { - "rbacGuid": "[guid(deployment().name)]" - }, - "resources": [ - { - "type": "Microsoft.Authorization/roleDefinitions", - "apiVersion": "2017-05-01", - "name": "[variables('rbacGuid')]", - "properties": { - "roleName": "[parameters('SecurityCopilotCreatorRoleName')]", - "IsCustom": true, - "Description": "This role is designed to allow the creation of Security Copilot Compute Capacities, including the scaling SCU count.", - "permissions": [ - { - "Actions": [ - "Microsoft.SecurityCopilot/capacities/read", - "Microsoft.SecurityCopilot/capacities/write" - ], - "NotActions": [] - } - ], - "AssignableScopes": [ - "[subscription().id]" - ] - } - } - ], - "outputs": { - "assignableScope": { - "type": "string", - "value": "[subscription().id]" - } - } -} \ No newline at end of file diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Dark.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Dark.png deleted file mode 100644 index c42b4468..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Dark.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Light.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Light.png deleted file mode 100644 index 51b089c2..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Edit_Light.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png deleted file mode 100644 index cc54ef01..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png deleted file mode 100644 index fd80ff71..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Dark.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Dark.png deleted file mode 100644 index a5eb4b7e..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Dark.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Light.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Light.png deleted file mode 100644 index a0bbe643..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_View_Light.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png b/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png deleted file mode 100644 index 71ee74c5..00000000 Binary files a/Monitoring/SCU Capacity Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png and /dev/null differ diff --git a/Monitoring/SCU Capacity Optimizer/readme.md b/Monitoring/SCU Capacity Optimizer/readme.md deleted file mode 100644 index 3df07fe9..00000000 --- a/Monitoring/SCU Capacity Optimizer/readme.md +++ /dev/null @@ -1,116 +0,0 @@ -# SCU Capacity Optimizer -Author: Lachlan Watson - -This solution will allow you to view and control the Security Copilot Compute Capacity SCUs based on a schedule.
-The solution is created using 2 core components: -- Sentinel Workbook - Security Copilot Optimizer -- Playbook (Logic App) - Security Copilot Scaler - -In the event of a SOC operating in a more limited capacity than 24x7 such as 8x5 this can mean significant cost savings by allowing the playbook to scale the compute capacity according to the schedule that has been applied while still taking advantage of the robust capabilities from Security Copilot.
- -![Solution Architecture](./images/SCU_Capacity_Optimizer_solution-architecture.png) - -The Security Copilot Optimizer Sentinel solution offers 3 core features:
- -1. View - Security teams can view existing scheduling (or lack there of) on a Compute Capacity, and displays predicted costs of the solution - -2. Modify - Security architects can add/modify existing schedules, view cost calculations based on the new schedule, and compare it with the existing config. This is achieved by applying a Resource Tag called: **SecurityCopilotConfig**. - -3. Apply - Finally, the Playbook/LogicApp runs hourly (5 minutes after the hour, to avoid conflict with other automations), to find resources with the SecurityCopilotConfig tag applied, then for each compute capacity evaluates high usage schedule configuration to determine the correct SCU count, then applies if any changes need to be made. - -The SecurityCopilotConfig tag holds the information required by the Playbook to apply separate configs across multiple compute capacities. - -Here is the structure of the Resource Tag: - -```json - { - "SCULimits": { - "Min": 1, "Max": 3 - }, - "Schedule": { - "ActiveDays": [ - 1, 2, 3, 4 - ], - "ActiveStartHour": 8, - "ActiveEndHour": 18, - "TimeOffset": -6 - } - } -``` -Property definitions: -- SCULimits - - Min: SCU count applied for Low Usage days/times - - Max: SCU count applied for High Usage days/times -- Schedule - - ActiveDays: integer representations of days of the week: - - 0 = Sunday - - 1 = Monday - - 2 = Tuesday - - 3 = Wednesday - - 4 = Thursday - - 5 = Friday - - 6 = Saturday - - ActiveStartHour: for high usage days, time when Maximum SCU count should be applied - - ActiveEndHour: for high usage days, time when Minimum SCU count should be applied - - TimeOffset: Hour offset from UTC. This is leveraged to ensure CfS is operating in the desired local time, and is paired with the workbook to validate offset to local comparison. - -## Prerequisites - -### Azure Resource Requirements -- Security Copilot Compute Capacity - So we have something to which we can view/apply a schedule -- Log Analytics Workspace (ideally Sentinel enabled) for operation of the Workbook - -### Azure RBAC Requirements -Least-privilege resource deployment requires: -- Workbook - Monitoring Contributor -- Playbook - Logic App Contributor - -Post-config requirements: -- Subscription Owner is required to create and apply a least-privilege custom role for the Playbook. You may choose to assign the Contributor role if you do not want to use a custom role.
- -Usage requirements: -- Contributor access of some kind is required to the Security Copilot Compute Capacity for any users who will be modifying the schedule since it works by applying Resource Tags to the Compute Capacity. - -## Deployment -**Deploy Solution**
-[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Fmain%2FMonitoring%2FSCU%20Capacity%20Optimizer%2Fsolution%2Fazuredeploy.json)
- -Note: During deployment, the selected Playbook Resource Group must be different than the Sentinel Resource Group. - - -## Post-deployment -1. (Optional) Deploy the Security Copilot Capacity Creator role to each subscription where Security Copilot Compute Capacities are present for a least-privilege approach
-[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FSecurity-Copilot%2Fmain%2FMonitoring%2FSCU%20Capacity%20Optimizer%2Fcustomrole%2Fazuredeploy.json) - -2. Assign the Security Copilot Capacity Creator custom role, or Contributor built-in role to the Playbook's managed identity - https://docs.microsoft.com/azure/logic-apps/create-managed-service-identity?tabs=consumption#assign-managed-identity-role-based-access-in-the-azure-portal - -3. Open the playbook in the Logic App Designer and ensure connections are displaying correctly

- -## Usage -**Basic usage steps:** - -1. The Security Copilot SCU Optimizer workbook should now be present in your Sentinel workspace. If you don't have Sentinel access, or don't have Sentinel enabled, then you can find the Azure Workbook resource in your Sentinel resource group. - -2. Select a Security Copilot Compute Capacity. You should see if there is or is not a schedule applied to that Compute Capacity. - -3. Select the Edit tab, and select the desired schedule to review the cost changes based on your team's standard work hours. - -4. When ready, select the **Apply Configuration** button to deploy the Resource Tags. - -5. When the next hour rolls around, the Playbook/Logic app will trigger, evaluate the Resource Tag, and apply the appropriate SCU limit to the Compute Capacity. - - If you'd like the changes to apply immediately you can manually trigger the Playbook. - -[Learn more about Security Copilot](https://learn.microsoft.com/en-us/copilot/security/microsoft-security-copilot)
-[Learn more about Playbooks (Logic Apps)](https://learn.microsoft.com/en-us/azure/logic-apps/logic-apps-overview)
-[Learn more about Workbooks](https://learn.microsoft.com/en-us/azure/azure-monitor/visualize/workbooks-overview)
- -## Screenshots -**Workbook - View No Schedule**
-![Workbook View - Dark](./images/SCU_Capacity_Optimizer_View_Dark.png) -![Workbook View - Light](./images/SCU_Capacity_Optimizer_View_Light.png)

-**Workbook - Apply/Modify Schedule**
-![Workbook Edit - Dark](./images/SCU_Capacity_Optimizer_Edit_Dark.png) -![Workbook Edit - Light](./images/SCU_Capacity_Optimizer_Edit_Light.png)

-**Playbook**
-![Playbook Overview - Dark](./images/SCU_Capacity_Optimizer_Scaler_Logicapp_Dark.png) -![Playbook Ovewview - Light](./images/SCU_Capacity_Optimizer_Scaler_Logicapp_Light.png) \ No newline at end of file diff --git a/Monitoring/SCU Capacity Optimizer/solution/azuredeploy.json b/Monitoring/SCU Capacity Optimizer/solution/azuredeploy.json deleted file mode 100644 index e2e857de..00000000 --- a/Monitoring/SCU Capacity Optimizer/solution/azuredeploy.json +++ /dev/null @@ -1,635 +0,0 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2018-05-01/subscriptionDeploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "metadata": { - "title": "", - "description": "", - "prerequisites": "", - "postDeployment": [], - "prerequisitesDeployTemplateFile": "", - "lastUpdateTime": "", - "entities": [], - "tags": [], - "support": { - "tier": "community", - "armtemplate": "Generated from https://github.com/Azure/Azure-Sentinel/tree/master/Tools/Playbook-ARM-Template-Generator" - }, - "author": { - "name": "" - } - }, - "parameters": { - "resourceLocation": { - "type": "string", - "allowedValues": [ - "australiaeast", - "australiasoutheast", - "brazilsouth", - "brazilsoutheast", - "canadacentral", - "canadaeast", - "centralindia", - "centralus", - "eastasia", - "eastus", - "eastus2", - "francecentral", - "francesouth", - "germanynorth", - "germanywestcentral", - "israelcentral", - "italynorth", - "japaneast", - "japanwest", - "jioindiawest", - "koreacentral", - "koreasouth", - "northcentralus", - "northeurope", - "norwayeast", - "norwaywest", - "polandcentral", - "qatarcentral", - "southafricanorth", - "southafricawest", - "southcentralus", - "southeastasia", - "southindia", - "swedencentral", - "switzerlandnorth", - "switzerlandwest", - "uaecentral", - "uaenorth", - "uksouth", - "ukwest", - "westcentralus", - "westeurope", - "westindia", - "westus", - "westus2", - "westus3" - ], - "defaultValue": "eastus", - "metadata": { - "description": "The location of the resources to be deployed." - } - }, - "playbookResourceGroup": { - "defaultValue": "", - "type": "string", - "metadata": { - "description": "The name of the Resource Group to store Security Copilot playbooks. This must be different than the Sentinel Resource Group." - } - }, - "playbookName": { - "defaultValue": "lga-securitycopilot-scu-scaler", - "type": "string", - "metadata": { - "description": "The name of the playbook associated with the workbook that applies the schedule hourly to the Security Copilot Compute Capacities." - } - }, - "sentinelResourceGroup": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "Sentinel Workbooks are required be installed in the Sentinel Resource Group." - } - }, - "sentinelWorkspaceName": { - "type": "string", - "defaultValue": "", - "metadata": { - "description": "The name of Sentinel instance to which the workbook will be associated" - } - }, - "sentinelWorkbookDisplayName": { - "type": "string", - "defaultValue": "Security Copilot SCU Capacity Optimizer", - "metadata": { - "description": "The friendly name for the workbook that is used in the Gallery or Saved List. This name must be unique within a resource group." - } - }, - "sentinelWorkbookId": { - "type": "string", - "defaultValue": "[newGuid()]", - "metadata": { - "description": "The unique guid for this workbook instance. If new, leave as default" - } - } - }, - "variables": { - "ArmConnectionName": "[concat('mgid-', parameters('PlaybookName'))]" - }, - "resources": [ - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", - "name": "nestedTemplate", - "resourceGroup": "[parameters('playbookResourceGroup')]", - "subscriptionId": "[subscription().subscriptionId]", - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "type": "Microsoft.Web/connections", - "apiVersion": "2016-06-01", - "name": "[variables('ArmConnectionName')]", - "location": "[parameters('resourceLocation')]", - "kind": "V1", - "properties": { - "displayName": "[variables('ArmConnectionName')]", - "customParameterValues": {}, - "parameterValueType": "Alternative", - "api": { - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', parameters('resourceLocation'), '/managedApis/arm')]" - } - } - }, - { - "name": "[parameters('PlaybookName')]", - "type": "Microsoft.Logic/workflows", - "location": "[parameters('resourceLocation')]", - "tags": {}, - "identity": { - "type": "SystemAssigned" - }, - "apiVersion": "2017-07-01", - "dependsOn": [ - "[resourceId(subscription().subscriptionId,parameters('playbookResourceGroup'),'Microsoft.Web/connections', variables('ArmConnectionName'))]" - ], - "properties": { - "provisioningState": "Succeeded", - "state": "Enabled", - "definition": { - "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "$connections": { - "defaultValue": {}, - "type": "Object" - } - }, - "triggers": { - "Recurrence_-_Every_Hour": { - "recurrence": { - "interval": 1, - "frequency": "Hour", - "timeZone": "Eastern Standard Time", - "startTime": "2024-05-23T17:05:00Z" - }, - "evaluatedRecurrence": { - "interval": 1, - "frequency": "Hour", - "timeZone": "Eastern Standard Time", - "startTime": "2024-05-23T17:05:00Z" - }, - "type": "Recurrence" - } - }, - "actions": { - "List_subscriptions": { - "runAfter": {}, - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "get", - "path": "/subscriptions", - "queries": { - "x-ms-api-version": "2016-06-01" - } - } - }, - "For_Each_-_Subscription": { - "foreach": "@body('List_subscriptions')?['value']", - "actions": { - "List_resources_by_subscription": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "get", - "path": "/subscriptions/@{encodeURIComponent(items('For_Each_-_Subscription')?['subscriptionId'])}/resources", - "queries": { - "x-ms-api-version": "2016-06-01", - "$filter": "tagName eq 'SecurityCopilotConfig'" - } - } - }, - "For_Each_-_Resource": { - "foreach": "@body('List_resources_by_subscription')?['value']", - "actions": { - "Parse_SecurityCopilot_Tag_JSON": { - "runAfter": { - "Read_a_resource": [ - "Succeeded" - ] - }, - "type": "ParseJson", - "inputs": { - "content": "@replace(item()?['tags']['SecurityCopilotConfig'],'\\', '')", - "schema": { - "type": "object", - "properties": { - "SCULimits": { - "type": "object", - "properties": { - "Min": { - "type": "integer" - }, - "Max": { - "type": "integer" - } - } - }, - "Schedule": { - "type": "object", - "properties": { - "ActiveDays": { - "type": "array", - "items": { - "type": "integer" - } - }, - "ActiveStartHour": { - "type": "integer" - }, - "ActiveEndHour": { - "type": "integer" - }, - "TimeOffset": { - "type": "integer" - } - } - } - } - } - } - }, - "Condition_-_Is_Active_SCU_Day": { - "actions": { - "Condition_-_Is_Active_Time": { - "actions": { - "Condition_-_Is_Current_SCU_not_equal_to_Max_SCU": { - "actions": { - "Create_or_update_a_resource_-_High_Usage_Day_-_High_Usage_Hours": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "put", - "body": { - "location": "@item()?['location']", - "tags": "@body('Read_a_resource')?['tags']", - "properties": { - "numberOfUnits": "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Max']", - "crossGeoCompute": "@{body('Read_a_resource')?['properties']['crossGeoCompute']}", - "geo": "@{body('Read_a_resource')?['properties']['geo']}" - } - }, - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent('capacities/',split(item()?['id'], '/')[8])}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - } - }, - "else": { - "actions": {} - }, - "expression": { - "and": [ - { - "not": { - "equals": [ - "@body('Read_a_resource')?['properties']['numberOfUnits']", - "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Max']" - ] - } - } - ] - }, - "type": "If" - } - }, - "else": { - "actions": { - "Condition_-_Is_Current_SCU_not_equal_to_Min_SCU": { - "actions": { - "Create_or_update_a_resource_-_Active_Day_-_Inactive_Hours": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "put", - "body": { - "location": "@item()?['location']", - "tags": "@body('Read_a_resource')?['tags']", - "properties": { - "numberOfUnits": "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']", - "crossGeoCompute": "@{body('Read_a_resource')?['properties']['crossGeoCompute']}", - "geo": "@{body('Read_a_resource')?['properties']['geo']}" - } - }, - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent('capacities/',split(item()?['id'], '/')[8])}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - } - }, - "else": { - "actions": {} - }, - "expression": { - "and": [ - { - "not": { - "equals": [ - "@body('Read_a_resource')?['properties']['numberOfUnits']", - "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']" - ] - } - } - ] - }, - "type": "If" - } - } - }, - "expression": { - "and": [ - { - "greaterOrEquals": [ - "@int(addHours(utcNow(), body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['TimeOffset'],'HH'))", - "@body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['ActiveStartHour']" - ] - }, - { - "less": [ - "@int(addHours(utcNow(), body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['TimeOffset'],'HH'))", - "@body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['ActiveEndHour']" - ] - } - ] - }, - "type": "If" - } - }, - "runAfter": { - "Set_variable_-_varActiveDays": [ - "Succeeded" - ] - }, - "else": { - "actions": { - "Condition_-_Is_Current_SCU_not_equal_to_Min_SCU_2": { - "actions": { - "Create_or_update_a_resource_-_Inactive_Day": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "put", - "body": { - "location": "@item()?['location']", - "tags": "@body('Read_a_resource')?['tags']", - "properties": { - "numberOfUnits": "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']", - "crossGeoCompute": "@{body('Read_a_resource')?['properties']['crossGeoCompute']}", - "geo": "@{body('Read_a_resource')?['properties']['geo']}" - } - }, - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent('capacities/',split(item()?['id'], '/')[8])}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - } - }, - "else": { - "actions": {} - }, - "expression": { - "and": [ - { - "not": { - "equals": [ - "@body('Read_a_resource')?['properties']['numberOfUnits']", - "@body('Parse_SecurityCopilot_Tag_JSON')?['SCULimits']?['Min']" - ] - } - } - ] - }, - "type": "If" - } - } - }, - "expression": { - "and": [ - { - "contains": [ - "@variables('varActiveDays')", - "@variables('varDayOfWeek')" - ] - } - ] - }, - "type": "If" - }, - "Read_a_resource": { - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['arm']['connectionId']" - } - }, - "method": "get", - "path": "/subscriptions/@{encodeURIComponent(split(item()?['id'], '/')[2])}/resourcegroups/@{encodeURIComponent(split(item()?['id'], '/')[4])}/providers/@{encodeURIComponent('Microsoft.SecurityCopilot')}/@{encodeURIComponent(concat('capacities/', split(item()?['id'], '/')[8]))}", - "queries": { - "x-ms-api-version": "2023-12-01-preview" - } - } - }, - "Set_variable_-_varActiveDays": { - "runAfter": { - "Set_variable_-_varDayOfWeek": [ - "Succeeded" - ] - }, - "type": "SetVariable", - "inputs": { - "name": "varActiveDays", - "value": "@body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['ActiveDays']" - } - }, - "Set_variable_-_varDayOfWeek": { - "runAfter": { - "Parse_SecurityCopilot_Tag_JSON": [ - "Succeeded" - ] - }, - "type": "SetVariable", - "inputs": { - "name": "varDayOfWeek", - "value": "@int(dayOfWeek(addHours(utcNow(), body('Parse_SecurityCopilot_Tag_JSON')?['Schedule']?['TimeOffset'])))" - } - } - }, - "runAfter": { - "List_resources_by_subscription": [ - "Succeeded" - ] - }, - "type": "Foreach", - "runtimeConfiguration": { - "concurrency": { - "repetitions": 1 - } - } - } - }, - "runAfter": { - "Initialize_varCleanTags": [ - "Succeeded" - ] - }, - "type": "Foreach", - "runtimeConfiguration": { - "concurrency": { - "repetitions": 1 - } - } - }, - "Initialize_variable_-_varDayOfWeek": { - "runAfter": { - "List_subscriptions": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "varDayOfWeek", - "type": "integer" - } - ] - } - }, - "Initialize_varCleanTags": { - "runAfter": { - "Initialize_variable_-_varActiveDays": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "varCleanTags", - "type": "object" - } - ] - } - }, - "Initialize_variable_-_varActiveDays": { - "runAfter": { - "Initialize_variable_-_varDayOfWeek": [ - "Succeeded" - ] - }, - "type": "InitializeVariable", - "inputs": { - "variables": [ - { - "name": "varActiveDays", - "type": "array" - } - ] - } - } - }, - "outputs": {} - }, - "parameters": { - "$connections": { - "value": { - "arm": { - "connectionId": "[resourceId(subscription().subscriptionId,parameters('playbookResourceGroup'),'Microsoft.Web/connections', variables('ArmConnectionName'))]", - "connectionName": "[variables('ArmConnectionName')]", - "id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', parameters('resourceLocation'), '/managedApis/arm')]", - "connectionProperties": { - "authentication": { - "type": "ManagedServiceIdentity" - } - } - } - } - } - } - } - } - ] - }, - "parameters": {} - } - }, - { - "type": "Microsoft.Resources/deployments", - "apiVersion": "2021-04-01", - "name": "nestedTemplate", - "resourceGroup": "[parameters('sentinelResourceGroup')]", - "subscriptionId": "[subscription().subscriptionId]", - "properties": { - "mode": "Incremental", - "template": { - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": {}, - "variables": {}, - "resources": [ - { - "name": "[parameters('sentinelWorkbookId')]", - "type": "microsoft.insights/workbooks", - "location": "[parameters('resourceLocation')]", - "apiVersion": "2022-04-01", - "dependsOn": [], - "kind": "shared", - "properties": { - "displayName": "[parameters('sentinelWorkbookDisplayName')]", - "serializedData": "{\"version\":\"Notebook/1.0\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"parameters\":[{\"id\":\"f368d9e2-9d09-4574-a430-f502386bbdd0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"Subscription\",\"type\":6,\"isRequired\":true,\"multiSelect\":true,\"quote\":\"'\",\"delimiter\":\",\",\"query\":\"resourcecontainers\\r\\n| where type == \\\"microsoft.resources/subscriptions\\\"\",\"crossComponentResources\":[\"value::all\"],\"typeSettings\":{\"additionalResourceOptions\":[\"value::all\"],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"value\":[\"value::all\"]},{\"id\":\"90fe1617-a8fd-46b3-aa4e-cd86cc1ab277\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LogAnalyticsWorkspace\",\"label\":\"Log Analytics Workspace\",\"type\":5,\"isRequired\":true,\"query\":\"resources\\r\\n| where type == \\\"microsoft.operationalinsights/workspaces\\\"\",\"typeSettings\":{\"resourceTypeFilter\":{\"microsoft.operationalinsights/workspaces\":true},\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\"},{\"id\":\"8d055c65-cd00-4640-af41-693e9d444a73\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SCUPrice\",\"label\":\"Price per SCU\",\"type\":1,\"description\":\"This is used to calculate the estimated costs below. It is not necessary to specify a type of currency, though estimate labels show \\\"$\\\"\",\"isRequired\":true,\"query\":\"print \\\"4\\\"\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"b26debb3-5d28-4ec2-bf0c-bb3304f0aad1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"SCUDropdownLimit\",\"label\":\"SCU Dropdown Limit\",\"type\":2,\"description\":\"Modifies the limit available in the Minimum SCU and Maximum SCU dropdowns under the \\\"Add/Edit Schedule\\\" tab\",\"isRequired\":true,\"query\":\"range SCU from 20 to 100 step 10\\r\\n| project value = SCU, label = strcat(SCU, iff(SCU>1,\\\" SCUs\\\", \\\" SCU\\\")), selected = iff(SCU == toint(20), true, false)\\r\\n\",\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"ef17d7a7-8d54-41cd-91d6-b1ec220276a0\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"📖Help\",\"type\":10,\"description\":\"This displays getting started text to help you validate your solution, and work through visualizing and applying schedules to your Security Copilot Compute Capacities.\",\"isRequired\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"jsonData\":\"[\\r\\n { \\\"value\\\": \\\"Yes\\\", \\\"label\\\": \\\"Yes\\\"},\\r\\n { \\\"value\\\": \\\"No\\\", \\\"label\\\": \\\"No\\\", \\\"selected\\\":true },\\r\\n { \\\"value\\\": \\\"Change Log\\\", \\\"label\\\": \\\"Change Log\\\"}\\r\\n]\",\"timeContext\":{\"durationMs\":86400000},\"value\":\"Yes\"},{\"id\":\"83e8f941-0eeb-4614-b553-ae94d8dd5e1e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceId\",\"type\":1,\"query\":\"print ResourceId = \\\"Blank\\\"\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"3c398c1c-654f-4792-8c16-ac9232983454\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ResourceName\",\"type\":1,\"isHiddenWhenLocked\":true},{\"id\":\"1704ba63-2cf5-44b6-8762-30b24e505223\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"CurrentSCU\",\"type\":1,\"isHiddenWhenLocked\":true},{\"id\":\"4238ba76-54a2-4d73-a1fb-6429780df635\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MinSCU\",\"label\":\"MinSCU Default\",\"type\":1,\"query\":\"print MinSCU = 0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a3a2daa5-d024-4412-bfee-e37e25342d2d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MaxSCU\",\"label\":\"MaxSCU Default\",\"type\":1,\"query\":\"print MaxSCU = 0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"faa23c27-33f9-4587-801c-4e3a53565eec\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveDays\",\"label\":\"High Usage Days Default\",\"type\":2,\"multiSelect\":true,\"quote\":\"\",\"delimiter\":\",\",\"query\":\"let NewActiveDays = datatable(value:int, label:string, selected:boolean) [\\r\\n0, \\\"Sunday\\\", false,\\r\\n1, \\\"Monday\\\", true,\\r\\n2, \\\"Tuesday\\\", true,\\r\\n3, \\\"Wednesday\\\", true,\\r\\n4, \\\"Thursday\\\", true,\\r\\n5, \\\"Friday\\\", true,\\r\\n6, \\\"Saturday\\\",false\\r\\n];\\r\\nNewActiveDays\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"247ee385-d118-4647-9539-3aa0bf4a6a8f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveStartHour\",\"label\":\"High Usage Start Time Default\",\"type\":1,\"isHiddenWhenLocked\":true,\"value\":\"9\"},{\"id\":\"0d08608a-793c-4e02-a5ef-1de3d490a64d\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveEndHour\",\"label\":\"High Usage End Time Default\",\"type\":1,\"isHiddenWhenLocked\":true,\"value\":\"17\"},{\"id\":\"f6a6332c-62f5-4469-b318-276c6207b0bd\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"TimeOffset\",\"label\":\"Time Offset from UTC Default\",\"type\":1,\"isHiddenWhenLocked\":true,\"value\":\"0\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":1,\"content\":{\"json\":\"# Change Log:\\r\\n\\r\\n### 12/Nov/2024\\r\\n - Added Change Log\\r\\n - Added SCU Dropdown Limit parameter to allow modification of Min and Max SCU Dropdown limits\\r\\n - Adjusted Add/Modify graph to display Time Offset based on Dropdown\\r\\n \\t- Previously this was displaying Time Offset from Resource Tags, so it was unset when deploying a new Schedule\\r\\n - Added more details to Deploy Schedule UI to provide instructions for manual deployment\\r\\n - Added Help text to explain Schedule cost calculations\\r\\n - Changed label from \\\"Copilot for Security\\\" to \\\"Security Copilot\\\"\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"📖Help\",\"comparison\":\"isEqualTo\",\"value\":\"Change Log\"},\"name\":\"ChangeLog - 1\"},{\"type\":1,\"content\":{\"json\":\"# Welcome to the Security Copilot SCU Capacity Optimizer\\r\\n\\r\\nAuthor: Lachlan Watson\\r\\n\\r\\nThis solution will allow you to view and control the Security Copilot Compute Capacity SCUs based on a schedule.
\\r\\nThe solution is created using 2 core components:\\r\\n- Sentinel Workbook - Security Copilot SCU Capacity Optimizer\\r\\n- Playbook (Logic App) - Security Copilot SCU Scaler\\r\\n\\r\\nIn the event of a SOC operating in a more limited capacity than 24x7 such as 8x5 this can mean significant cost savings by allowing the playbook to scale the compute capacity according to the schedule that has been applied while still taking advantage of the robust capabilities from Security Copilot.
\\r\\n\\r\\nIf you do not already have the Playbook deployed, refer to the solution documentation for complete install instructions:\\r\\n[Solution Documentation](https://aka.ms/scuoptimizer)\\r\\n\\r\\n![Solution Architecture](https://raw.githubusercontent.com/LSLWatson/CopilotforSecurity/c6fc9484ab610fd6db68c1a41d3d5cb88d7c38ff/SCU_Capacity_Optimizer/images/SCU_Capacity_Optimizer_solution-architecture.png)\\r\\n\\r\\nThe Security Copilot SCU Capacity Optimizer solution offers 3 core features:
\\r\\n\\r\\n1. View - Security teams can view existing scheduling (or lack there of) on a Compute Capacity, and displays predicted costs of the solution\\r\\n\\r\\n2. Modify - Security architects can add/modify existing schedules, view cost calculations based on the new schedule, and compare it with the existing config. This is achieved by applying a Resource Tag called: **SecurityCopilotConfig**.\\r\\n\\r\\n3. Apply - Finally, the Playbook/LogicApp runs hourly (5 minutes after the hour, to avoid conflict with other automations), to find resources with the SecurityCopilotConfig tag applied, then for each compute capacity evaluates high usage schedule configuration to determine the correct SCU count, then applies if any changes need to be made.\\r\\n\\r\\nThe SecurityCopilotConfig tag holds the information required by the Playbook to apply separate configs across multiple compute capacities.\\r\\n\\r\\n## Usage\\r\\n**Requirements:**\\r\\n- View - Reader access is required to see the Security Copilot Compute Capacity\\r\\n\\r\\n- Apply/Edit - Contributor access of some kind is required to the Security Copilot Compute Capacity for any users who will be modifying the schedule since it works by applying Resource Tags.\\r\\n\\r\\n**Steps**\\r\\n\\r\\n1. Select a Security Copilot Compute Capacity. In the View tab you will see if there is or is not a schedule applied to that Compute Capacity, and a visualization of the deployed SCU capacities over time.\\r\\n2. Select the Add/Edit Schedule tab, enter the desired schedule to review the cost changes based on your team's standard work hours.\\r\\n3. When ready, select the **Apply Configuration** button to deploy the Resource Tags.\\r\\n4. When the next hour rolls around, the Playbook/Logic app will trigger, evaluate the Resource Tag, and apply the appropriate SCU limit to the Compute Capacity.\\r\\n - This can also be manually triggered if you'd like to enforce the change immediately.\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"📖Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"help - 1\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"resources\\r\\n| where type =~ 'microsoft.securitycopilot/capacities'\\r\\n| project name, id, location, resourceGroup,subscriptionId,['Current SCU'] = tostring(properties.numberOfUnits),['Cross-Geo Compute'] = tostring(properties.crossGeoCompute), ResourceTags = todynamic(tags)\\r\\n| extend CFSConfig = tostring(ResourceTags.SecurityCopilotConfig)\\r\\n| extend HasConfig = iff (isempty(CFSConfig), False, True)\\r\\n| extend SCULimits = parse_json(CFSConfig).SCULimits, Schedule = parse_json(CFSConfig).Schedule\\r\\n| extend MinSCU = coalesce(toint(SCULimits.Min),toint(['Current SCU'])),\\r\\n MaxSCU = coalesce(toint(SCULimits.Max),toint(['Current SCU'])),\\r\\n ActiveDays = iff(isnotempty(Schedule.ActiveDays), todynamic(Schedule.ActiveDays),dynamic[{ActiveDays}])),\\r\\n ActiveStartHour = coalesce(toint(Schedule.ActiveStartHour),{ActiveStartHour}),\\r\\n ActiveEndHour = coalesce(toint(Schedule.ActiveEndHour),{ActiveEndHour}),\\r\\n TimeOffset = coalesce(toint(Schedule.TimeOffset),{TimeOffset})\\r\\n//| project ActiveEndHour\\r\\n| extend CostPerSCU = {SCUPrice}\\r\\n| extend HighUsageHours = iff(HasConfig==True, (ActiveEndHour - ActiveStartHour)* array_length(ActiveDays), 0)\\r\\n| extend LowUsageHours = ((24*7) - HighUsageHours), HighSCU = HighUsageHours * MaxSCU\\r\\n| extend LowSCU = LowUsageHours * MinSCU\\r\\n| extend TotalSCU = LowSCU + HighSCU\\r\\n| extend WeeklySCUCost = TotalSCU * CostPerSCU\\r\\n| extend MonthlySCUCost = (WeeklySCUCost/7) * 30\\r\\n| extend ConfigStatus = case(\\r\\n isempty(CFSConfig), \\\"❌ Config Missing\\\",\\r\\n \\\"✅ Config Present\\\")\\r\\n\\r\\n\\r\\n\",\"size\":4,\"title\":\"Select a Security Copilot Compute Capacity:\",\"exportedParameters\":[{\"fieldName\":\"id\",\"parameterName\":\"ResourceId\",\"parameterType\":5},{\"fieldName\":\"name\",\"parameterName\":\"ResourceName\",\"parameterType\":1},{\"fieldName\":\"Current SCU\",\"parameterName\":\"CurrentSCU\",\"parameterType\":1},{\"fieldName\":\"MinSCU\",\"parameterName\":\"MinSCU\",\"parameterType\":1},{\"fieldName\":\"MaxSCU\",\"parameterName\":\"MaxSCU\",\"parameterType\":1},{\"fieldName\":\"ActiveDays\",\"parameterName\":\"ActiveDays\",\"parameterType\":1},{\"fieldName\":\"ActiveStartHour\",\"parameterName\":\"ActiveStartHour\",\"parameterType\":1},{\"fieldName\":\"ActiveEndHour\",\"parameterName\":\"ActiveEndHour\",\"parameterType\":1},{\"fieldName\":\"CostPerSCU\",\"parameterName\":\"CostPerSCU\",\"parameterType\":1},{\"fieldName\":\"HighUsageHours\",\"parameterName\":\"HighUsageHours\",\"parameterType\":1},{\"fieldName\":\"LowUsageHours\",\"parameterName\":\"LowUsageHours\",\"parameterType\":1},{\"fieldName\":\"TotalSCU\",\"parameterName\":\"TotalSCU\",\"parameterType\":1},{\"fieldName\":\"WeeklySCUCost\",\"parameterName\":\"WeeklySCUCost\",\"parameterType\":1},{\"fieldName\":\"ConfigStatus\",\"parameterName\":\"ConfigStatus\",\"parameterType\":1},{\"fieldName\":\"TimeOffset\",\"parameterName\":\"TimeOffset\",\"parameterType\":1},{\"fieldName\":\"ResourceTags\",\"parameterName\":\"ResourceTags\",\"parameterType\":1},{\"fieldName\":\"MonthlySCUCost\",\"parameterName\":\"MonthlySCUCost\",\"parameterType\":1},{\"fieldName\":\"HasConfig\",\"parameterName\":\"HasConfig\",\"parameterType\":1}],\"queryType\":1,\"resourceType\":\"microsoft.resourcegraph/resources\",\"crossComponentResources\":[\"{Subscription}\"],\"gridSettings\":{\"formatters\":[{\"columnMatch\":\"name\",\"formatter\":5},{\"columnMatch\":\"ResourceTags\",\"formatter\":5},{\"columnMatch\":\"CFSConfig\",\"formatter\":5},{\"columnMatch\":\"HasConfig\",\"formatter\":5},{\"columnMatch\":\"SCULimits\",\"formatter\":5},{\"columnMatch\":\"Schedule\",\"formatter\":5},{\"columnMatch\":\"MinSCU\",\"formatter\":5},{\"columnMatch\":\"MaxSCU\",\"formatter\":5},{\"columnMatch\":\"ActiveDays\",\"formatter\":5},{\"columnMatch\":\"ActiveStartHour\",\"formatter\":5},{\"columnMatch\":\"ActiveEndHour\",\"formatter\":5},{\"columnMatch\":\"TimeOffset\",\"formatter\":5},{\"columnMatch\":\"CostPerSCU\",\"formatter\":5},{\"columnMatch\":\"HighUsageHours\",\"formatter\":5},{\"columnMatch\":\"LowUsageHours\",\"formatter\":5},{\"columnMatch\":\"HighSCU\",\"formatter\":5},{\"columnMatch\":\"LowSCU\",\"formatter\":5},{\"columnMatch\":\"TotalSCU\",\"formatter\":5},{\"columnMatch\":\"WeeklySCUCost\",\"formatter\":5},{\"columnMatch\":\"MonthlySCUCost\",\"formatter\":5}],\"rowLimit\":10},\"sortBy\":[]},\"name\":\"query - 1\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"0ac976dd-c7bf-4d11-a68b-5956248370ce\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveDays\",\"type\":2,\"multiSelect\":true,\"quote\":\" \",\"delimiter\":\",\",\"query\":\"let ActiveDays = datatable(value:int, label:string) [\\r\\n0, \\\"Sunday\\\",\\r\\n1, \\\"Monday\\\",\\r\\n2, \\\"Tuesday\\\",\\r\\n3, \\\"Wednesday\\\",\\r\\n4, \\\"Thursday\\\",\\r\\n5, \\\"Friday\\\",\\r\\n6, \\\"Saturday\\\"\\r\\n];\\r\\nActiveDays\\r\\n| project value, label, selected = iff(value in ({ActiveDays}), true, false)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 6\"},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"tabs\",\"links\":[{\"id\":\"ba7a9242-cae1-49b8-8807-a35b81f10e6f\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"View Config\",\"subTarget\":\"View\",\"preText\":\"View Config\",\"style\":\"link\"},{\"id\":\"70318539-7291-4d95-85f9-30cd7ccfa38c\",\"cellValue\":\"Nav\",\"linkTarget\":\"parameter\",\"linkLabel\":\"Add/Edit Schedule\",\"subTarget\":\"Edit\",\"preText\":\"Edit Config\",\"style\":\"link\"}]},\"customWidth\":\"50\",\"conditionalVisibility\":{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"},\"name\":\"links - 4\",\"styleSettings\":{\"maxWidth\":\"100\"}},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"d6cb9b36-2e00-4ab0-ac93-4f0d54ba1073\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewMinSCU\",\"label\":\"Minimum SCU\",\"type\":2,\"query\":\"range SCU from 1 to {SCUDropdownLimit} step 1\\r\\n| project value = SCU, label = strcat(SCU, iff(SCU>1,\\\" SCUs\\\", \\\" SCU\\\")), selected = iff(SCU == toint({MinSCU}), true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"994f321e-5368-4269-a564-04208f7b5888\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewMaxSCU\",\"label\":\"Maximum SCU\",\"type\":2,\"query\":\"range SCU from 1 to {SCUDropdownLimit} step 1\\r\\n| project value = SCU, label = strcat(SCU, iff(SCU>1,\\\" SCUs\\\", \\\" SCU\\\")), selected = iff(SCU == toint({MaxSCU}), true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"5d386c57-3b13-43e9-a365-4999c0a6092f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveDays\",\"label\":\"High Usage Days\",\"type\":2,\"multiSelect\":true,\"quote\":\"\",\"delimiter\":\",\",\"query\":\"let NewActiveDays = datatable(value:int, label:string) [\\r\\n0, \\\"Sunday\\\",\\r\\n1, \\\"Monday\\\",\\r\\n2, \\\"Tuesday\\\",\\r\\n3, \\\"Wednesday\\\",\\r\\n4, \\\"Thursday\\\",\\r\\n5, \\\"Friday\\\",\\r\\n6, \\\"Saturday\\\"\\r\\n];\\r\\nNewActiveDays\\r\\n| project value, label, selected = iff(value in ({ActiveDays}), true, false)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"4dd28d31-e64c-4233-acad-d78049f9c639\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveStartHour\",\"label\":\"High Usage Start Hour\",\"type\":2,\"query\":\"let NewActiveStartHour = datatable(value:int, label:string)[\\r\\n 0,\\\"12 AM (Midnight)\\\",\\r\\n 1,\\\"1 AM\\\",\\r\\n 2,\\\"2 AM\\\" ,\\r\\n 3,\\\"3 AM\\\" ,\\r\\n 4,\\\"4 AM\\\" ,\\r\\n 5,\\\"5 AM\\\" ,\\r\\n 6,\\\"6 AM\\\" ,\\r\\n 7,\\\"7 AM\\\" ,\\r\\n 8,\\\"8 AM\\\" ,\\r\\n 9,\\\"9 AM\\\" ,\\r\\n 10,\\\"10 AM\\\" ,\\r\\n 11,\\\"11 AM\\\" ,\\r\\n 12,\\\"12 PM (Noon)\\\" ,\\r\\n 13,\\\"1 PM\\\" ,\\r\\n 14,\\\"2 PM\\\" ,\\r\\n 15,\\\"3 PM\\\" ,\\r\\n 16,\\\"4 PM\\\" ,\\r\\n 17,\\\"5 PM\\\" ,\\r\\n 18,\\\"6 PM\\\" ,\\r\\n 19,\\\"7 PM\\\" ,\\r\\n 20,\\\"8 PM\\\" ,\\r\\n 21,\\\"9 PM\\\" ,\\r\\n 22,\\\"10 PM\\\" ,\\r\\n 23,\\\"11 PM\\\" \\r\\n];\\r\\nNewActiveStartHour\\r\\n|project value, label, selected = iff(value == {ActiveStartHour}, true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"320bfeaa-ee23-4fbf-9e9d-067e5ed915f8\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveEndHour\",\"label\":\"High Usage End Hour\",\"type\":2,\"query\":\"let NewActiveEndHour = datatable(value:int, label:string)[\\r\\n 0,\\\"12 AM (Midnight)\\\",\\r\\n 1,\\\"1 AM\\\",\\r\\n 2,\\\"2 AM\\\" ,\\r\\n 3,\\\"3 AM\\\" ,\\r\\n 4,\\\"4 AM\\\" ,\\r\\n 5,\\\"5 AM\\\" ,\\r\\n 6,\\\"6 AM\\\" ,\\r\\n 7,\\\"7 AM\\\" ,\\r\\n 8,\\\"8 AM\\\" ,\\r\\n 9,\\\"9 AM\\\" ,\\r\\n 10,\\\"10 AM\\\" ,\\r\\n 11,\\\"11 AM\\\" ,\\r\\n 12,\\\"12 PM (Noon)\\\" ,\\r\\n 13,\\\"1 PM\\\" ,\\r\\n 14,\\\"2 PM\\\" ,\\r\\n 15,\\\"3 PM\\\" ,\\r\\n 16,\\\"4 PM\\\" ,\\r\\n 17,\\\"5 PM\\\" ,\\r\\n 18,\\\"6 PM\\\" ,\\r\\n 19,\\\"7 PM\\\" ,\\r\\n 20,\\\"8 PM\\\" ,\\r\\n 21,\\\"9 PM\\\" ,\\r\\n 22,\\\"10 PM\\\" ,\\r\\n 23,\\\"11 PM\\\" \\r\\n];\\r\\nNewActiveEndHour\\r\\n|project value, label, selected = iff(value == {ActiveEndHour}, true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"e72ccd8d-43cd-4d3d-986e-3dd65151809f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewTimeOffset\",\"label\":\"UTC Time Offset\",\"type\":2,\"query\":\"let NewActiveStartHour = datatable(value:int, label:string)[\\r\\n -12,\\\"-12 Hours\\\",\\r\\n -11,\\\"-11 Hours\\\",\\r\\n -10,\\\"-10 Hours\\\" ,\\r\\n -9,\\\"-9 Hours\\\" ,\\r\\n -8,\\\"-8 Hours\\\" ,\\r\\n -7,\\\"-7 Hours\\\" ,\\r\\n -6,\\\"-6 Hours\\\" ,\\r\\n -5,\\\"-5 Hours\\\" ,\\r\\n -4,\\\"-4 Hours\\\" ,\\r\\n -3,\\\"-3 Hours\\\" ,\\r\\n -2,\\\"-2 Hours\\\" ,\\r\\n -1,\\\"-1 Hour\\\" ,\\r\\n 0,\\\"0 Hours\\\" ,\\r\\n 1,\\\"+1 Hour\\\" ,\\r\\n 2,\\\"+2 Hours\\\" ,\\r\\n 3,\\\"+3 Hours\\\" ,\\r\\n 4,\\\"+4 Hours\\\" ,\\r\\n 5,\\\"+5 Hours\\\" ,\\r\\n 6,\\\"+6 Hours\\\" ,\\r\\n 7,\\\"+7 Hours\\\" ,\\r\\n 8,\\\"+8 Hours\\\" ,\\r\\n 9,\\\"+9 Hours\\\" ,\\r\\n 10,\\\"+10 Hours\\\" ,\\r\\n 11,\\\"+11 Hours\\\" ,\\r\\n 12,\\\"+12 Hours\\\" \\r\\n];\\r\\nNewActiveStartHour\\r\\n|project value, label, selected = iff(value == {TimeOffset}, true, false)\\r\\n\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"typeSettings\":{\"additionalResourceOptions\":[],\"showDefault\":false},\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"61c24dd2-b52e-4002-ac46-045e2f07e355\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MinOverMax\",\"type\":1,\"query\":\"print MinOverMax = iff({NewMinSCU} > {NewMaxSCU}, true, false)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"above\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 5\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"1ffcd520-6411-48c3-a8ed-50c353b3efa5\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewHighUsageHours\",\"label\":\"High Usage Hours\",\"type\":1,\"query\":\"let NewActiveDays = dynamic([{NewActiveDays}]);\\r\\nlet NewHighHours = ({NewActiveEndHour} - {NewActiveStartHour})* array_length(NewActiveDays);\\r\\nprint NewHighHours = tostring(NewHighHours)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"2f383967-6870-447d-8370-205672262861\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewLowUsageHours\",\"label\":\"Low Usage Hours\",\"type\":1,\"query\":\"let NewLowUsageHours = (24*7) - tolong({NewHighUsageHours});\\r\\nprint NewLowUsageHours = NewLowUsageHours\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"319da61d-d14d-4588-982e-99d108ae7d26\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewLowSCU\",\"label\":\"Low Total SCU\",\"type\":1,\"query\":\"let LowTotalSCU = {NewLowUsageHours} * {NewMinSCU};\\r\\nprint LowTotalSCU = LowTotalSCU\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"de38bd75-34ae-442e-b1fd-f5522346b9d1\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewHighSCU\",\"label\":\"High Total SCU\",\"type\":1,\"query\":\"let HighUsageSCU = tolong({NewHighUsageHours}) * tolong({NewMaxSCU});\\r\\nprint HighUsageSCU = tostring(HighUsageSCU)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"81ae5141-7c19-4d72-8d83-0251d5f002f6\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewTotalSCU\",\"label\":\"Total Weekly SCU\",\"type\":1,\"query\":\"let NewTotalSCU = tolong({NewLowSCU}) + tolong({NewHighSCU});\\r\\nprint NewTotalSCU = NewTotalSCU\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"5a91a140-2b88-4e8f-b810-e5533ffbfe8f\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewWeeklySCUCost\",\"label\":\"Total Weekly SCU Cost\",\"type\":1,\"query\":\"let NewWeeklySCUCost = tolong({NewTotalSCU}) * tolong({CostPerSCU});\\r\\nprint NewWeeklySCUCost = tostring(NewWeeklySCUCost)\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"b78a669f-30c0-4720-b94c-ec6d49966feb\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewMonthlySCUCost\",\"label\":\"Total Monthly SCU Cost\",\"type\":1,\"query\":\"let NewMonthlySCUCost = (({NewWeeklySCUCost})/7) * 30;\\r\\nprint NewMonthlySCUCost = NewMonthlySCUCost\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"aac7ddd9-5c17-40ab-9262-954d0ef51a28\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"WeeklyDiff\",\"label\":\"Estimated Weekly Change\",\"type\":1,\"query\":\"let WeeklySCUCost = tolong({TotalSCU}) * tolong({CostPerSCU});\\r\\nlet NewWeeklySCUCost = tolong({NewTotalSCU}) * tolong({CostPerSCU});\\r\\nprint WeeklyDifference = NewWeeklySCUCost - WeeklySCUCost\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"9687f227-0cb5-49b7-a636-05cb76a4df1c\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"MonthlyDiff\",\"label\":\"Estimated Monthly Change\",\"type\":1,\"query\":\"let WeeklySCUCost = {TotalSCU} * {CostPerSCU};\\r\\nlet NewWeeklySCUCost = {NewTotalSCU} * {CostPerSCU};\\r\\nlet MonthlySCUCost = round(({WeeklySCUCost}/7)*30,0);\\r\\nlet NewMonthlySCUCost = round(({NewWeeklySCUCost}/7)*30,0);\\r\\nprint MonthlyDifference = NewMonthlySCUCost - MonthlySCUCost\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"a5eef48e-d661-44e6-b81f-86363a6f773e\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"LocalTime\",\"type\":1,\"query\":\"let LocalDate = datetime_add('hour', {NewTimeOffset}, now());\\r\\nprint LocalTime = format_datetime(LocalDate, 'h:mm tt')\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},{\"id\":\"9739a6a5-6b5c-4290-85db-28cfe659828a\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"NewActiveDaysLabel\",\"type\":1,\"query\":\"let ActiveDays= \\\"{NewActiveDays}\\\";\\r\\nprint ActiveDayLabel = ActiveDays\\r\\n| extend ActiveDayLabel = replace_strings(\\r\\n ActiveDays,\\r\\n dynamic(['0', '1', '2', '3', '4', '5', '6']),\\r\\n dynamic([' Sunday', ' Monday', ' Tuesday', ' Wednesday', ' Thursday', ' Friday', 'Saturday']))\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 6\"},{\"type\":1,\"content\":{\"json\":\"**Note**\\r\\n - The Minimum SCU value must be less than, or equal to Maximum SCU\",\"style\":\"error\"},\"conditionalVisibility\":{\"parameterName\":\"MinOverMax\",\"comparison\":\"isEqualTo\",\"value\":\"true\"},\"name\":\"text - 3\"},{\"type\":1,\"content\":{\"json\":\"### Validate UTC Offset with Local time:\\r\\n\\r\\n#### Local Time as of last refresh: {LocalTime}\"},\"name\":\"text - 6\"},{\"type\":1,\"content\":{\"json\":\"**Note**\\r\\n - Make sure you have configured the **UTC Time Offset** value\",\"style\":\"warning\"},\"customWidth\":\"30\",\"conditionalVisibility\":{\"parameterName\":\"NewTimeOffset\",\"comparison\":\"isEqualTo\",\"value\":\"0\"},\"name\":\"TimeOffsetZero\",\"styleSettings\":{\"maxWidth\":\"30\"}},{\"type\":11,\"content\":{\"version\":\"LinkItem/1.0\",\"style\":\"paragraph\",\"links\":[{\"id\":\"1092f1f7-166e-4745-9483-e2adb861f262\",\"linkTarget\":\"ArmAction\",\"linkLabel\":\"Apply Configuration\",\"preText\":\"\",\"style\":\"primary\",\"linkIsContextBlade\":true,\"armActionContext\":{\"path\":\"{ResourceId}/providers/Microsoft.Resources/tags/default?api-version=2022-09-01\",\"headers\":[],\"params\":[],\"body\":\"{\\r\\n \\\"operation\\\": \\\"Merge\\\",\\r\\n \\\"properties\\\": {\\r\\n \\\"tags\\\": {\\r\\n \\\"SecurityCopilotConfig\\\": \\\"{ \\\\\\\"SCULimits\\\\\\\": { \\\\\\\"Min\\\\\\\": {NewMinSCU}, \\\\\\\"Max\\\\\\\": {NewMaxSCU} }, \\\\\\\"Schedule\\\\\\\": { \\\\\\\"ActiveDays\\\\\\\": [{NewActiveDays}], \\\\\\\"ActiveStartHour\\\\\\\": {NewActiveStartHour}, \\\\\\\"ActiveEndHour\\\\\\\": {NewActiveEndHour}, \\\\\\\"TimeOffset\\\\\\\": {NewTimeOffset}}}\\\"\\r\\n }\\r\\n }\\r\\n }\",\"httpMethod\":\"PATCH\",\"title\":\"Add/Modify Security Copilot Resource Tag\",\"description\":\"# This action adds/modifies the Security Copilot resource tags\\n## This change, when paired with the Security Copilot Scaler logic app can impact the performance and costs associated with Security Copilot Compute Capacities\\n\\n## The schedule will be applied the next time the Scaler logic app executes. This can be force by manually running the logic app.\\n\\n## Tag changes will not immediately be reflected in this workbook, and may require 30 seconds before manually refreshing\",\"runLabel\":\"Continue Deploying Schedule\"}}]},\"conditionalVisibility\":{\"parameterName\":\"MinOverMax\",\"comparison\":\"isNotEqualTo\",\"value\":\"true\"},\"name\":\"links - 2\"},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let StartDate = startofweek(datetime_add(\\\"hour\\\", {NewTimeOffset}, now()));\\r\\nlet EndDate = endofweek(datetime_add(\\\"hour\\\", {NewTimeOffset}, now()));\\r\\nlet ActiveDays=dynamic([{NewActiveDays}]);\\r\\nlet ResourceName = \\\"{ResourceName}\\\";\\r\\nlet MinSCU = toint({NewMinSCU});\\r\\nlet MaxSCU = toint({NewMaxSCU});\\r\\nlet ActiveStartHour = {NewActiveStartHour};\\r\\nlet ActiveEndHour = {NewActiveEndHour};\\r\\nrange TimeDate from StartDate to EndDate step 1h\\r\\n| extend DayofWeek = toint(dayofweek(TimeDate)/1d), HourofDay = hourofday(datetime_add(\\\"hour\\\", {NewTimeOffset}, TimeDate))\\r\\n| extend SCU = iff(DayofWeek in (ActiveDays) and (HourofDay >= ActiveStartHour and HourofDay < ActiveEndHour), MaxSCU, MinSCU)\\r\\n| project-away DayofWeek,HourofDay\",\"size\":1,\"title\":\"Projected weekly schedule for {ResourceName}\",\"color\":\"green\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"barchart\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"SCU\",\"color\":\"green\"},{\"seriesName\":\"$_thresholdLine\",\"color\":\"greenDarkDark\"}],\"customThresholdLine\":\"{NewMinSCU}\",\"customThresholdLineStyle\":0,\"ySettings\":{\"numberFormatSettings\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}},\"min\":0}}},\"name\":\"query - 2 - Copy\"},{\"type\":1,\"content\":{\"json\":\"- **Weekly Estimated Cost** calculations are based on the projected schedule\\r\\n\\r\\n- **Monthly Estimated Cost** calculations are based on (Weekly Estimated Cost / 7 days) * 30 days\",\"style\":\"info\"},\"conditionalVisibility\":{\"parameterName\":\"📖Help\",\"comparison\":\"isEqualTo\",\"value\":\"Yes\"},\"name\":\"help - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### Scheduled Capacity\"},\"customWidth\":\"50\",\"name\":\"text - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### High Usage Times\"},\"customWidth\":\"50\",\"name\":\"text - 7\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MinMax = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Low Usage Capacity\\\",\\\"SCU(s)\\\", \\\"{NewMinSCU}\\\",\\r\\n \\\"High Usage Capacity\\\", \\\"SCU(s)\\\", \\\"{NewMaxSCU}\\\"\\r\\n ];\\r\\nMinMax\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"rightContent\":{\"columnMatch\":\"label2\",\"formatter\":1,\"formatOptions\":{\"compositeBarSettings\":{\"labelText\":\"\",\"columnSettings\":[]}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"\\r\\n> ## {NewActiveDaysLabel}\\r\\n\"},\"customWidth\":\"50\",\"name\":\"text - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Weekly High Usage SCUs\\\", \\\"{NewHighUsageHours}\\\",\\r\\n \\\"Weekly Low Usage SCUs\\\", \\\"{NewLowUsageHours}\\\",\\r\\n \\\"Weekly Total SCUs\\\", \\\"{NewTotalSCU}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ActiveHours = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Start Time\\\",\\\"hours\\\", \\\"{NewActiveStartHour}\\\",\\r\\n \\\"End Time\\\",\\\"hours\\\", \\\"{NewActiveEndHour}\\\",\\r\\n \\\"Time Offset from UTC\\\", \\\"hours\\\", \\\"{NewTimeOffset}\\\",\\r\\n ];\\r\\nActiveHours\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"green\"},\"numberFormat\":{\"unit\":26,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Cost Estimates\"},\"customWidth\":\"50\",\"name\":\"text - 8\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Change from Current Schedule\"},\"customWidth\":\"50\",\"name\":\"text - 9\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Cost Per SCU\\\", \\\"{SCUPrice}\\\",\\r\\n \\\"Weekly Estimated Cost\\\", \\\"{NewWeeklySCUCost}\\\",\\r\\n \\\"Monthly Estimated Cost\\\", \\\"{NewMonthlySCUCost}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"turquoise\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Weekly Change\\\", \\\"{WeeklyDiff}\\\",\\r\\n \\\"Monthly Change\\\", \\\"{MonthlyDiff}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":-1000,\"max\":1000,\"palette\":\"purpleBlueGreen\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"customWidth\":\"100\",\"name\":\"StatGroup - Copy\"}]},\"conditionalVisibilities\":[{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"Edit\"},{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"}],\"name\":\"EditConfig\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"\\r\\nlet StartDate = startofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet EndDate = endofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet ActiveDays=dynamic([{ActiveDays}]);\\r\\nlet ResourceName = \\\"{ResourceName}\\\";\\r\\nlet MinSCU = {MinSCU};\\r\\nlet MaxSCU = {MaxSCU};\\r\\nlet CurrentSCU = {CurrentSCU};\\r\\nlet ActiveStartHour = {ActiveStartHour};\\r\\nlet ActiveEndHour = {ActiveEndHour};\\r\\nrange TimeDate from StartDate to EndDate step 1h\\r\\n| extend DayofWeek = toint(dayofweek(TimeDate)/1d), HourofDay = hourofday(datetime_add(\\\"hour\\\", {TimeOffset}, TimeDate))\\r\\n| extend SCU = iff(isnotempty(ActiveDays),iff(DayofWeek in (ActiveDays) and (HourofDay >= ActiveStartHour and HourofDay < ActiveEndHour), MaxSCU, MinSCU),CurrentSCU)\\r\\n| project-away DayofWeek,HourofDay\",\"size\":1,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"barchart\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"SCU\",\"color\":\"blue\"},{\"seriesName\":\"$_thresholdLine\",\"label\":\"Minimum SCU\",\"color\":\"blueDark\"}],\"customThresholdLine\":\"{MinSCU}\",\"customThresholdLineStyle\":0,\"xSettings\":{\"numberFormatSettings\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}}}},\"name\":\"query - 6\"},{\"type\":9,\"content\":{\"version\":\"KqlParameterItem/1.0\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"parameters\":[{\"id\":\"180d4c94-2bd1-4f45-ad4a-066074a286fc\",\"version\":\"KqlParameterItem/1.0\",\"name\":\"ActiveDaysLabel\",\"type\":1,\"query\":\"let ActiveDays= \\\"{ActiveDays}\\\";\\r\\nprint ActiveDayLabel = ActiveDays\\r\\n| extend ActiveDayLabel = replace_strings(\\r\\n ActiveDays,\\r\\n dynamic(['0', '1', '2', '3', '4', '5', '6']),\\r\\n dynamic([' Sunday', ' Monday', ' Tuesday', ' Wednesday', ' Thursday', ' Friday', 'Saturday']))\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"isHiddenWhenLocked\":true,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"}],\"style\":\"pills\",\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\"},\"name\":\"parameters - 2\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### Scheduled Capacity\"},\"customWidth\":\"50\",\"name\":\"text - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### High Usage Times\"},\"customWidth\":\"50\",\"name\":\"text - 7\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let MinMax = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Low Usage Capacity\\\",\\\"SCU(s)\\\", \\\"{MinSCU}\\\",\\r\\n \\\"High Usage Capacity\\\", \\\"SCU(s)\\\", \\\"{MaxSCU}\\\"\\r\\n ];\\r\\nMinMax\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"rightContent\":{\"columnMatch\":\"label2\",\"formatter\":1,\"formatOptions\":{\"compositeBarSettings\":{\"labelText\":\"\",\"columnSettings\":[]}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"\\r\\n> ## {ActiveDaysLabel}\\r\\n\"},\"customWidth\":\"50\",\"name\":\"text - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Weekly High Usage SCUs\\\", \\\"{HighUsageHours}\\\",\\r\\n \\\"Weekly Low Usage SCUs\\\", \\\"{LowUsageHours}\\\",\\r\\n \\\"Weekly Total SCUs\\\", \\\"{TotalSCU}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"purple\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 5\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let ActiveHours = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Start Time\\\",\\\"hours\\\", \\\"{ActiveStartHour}\\\",\\r\\n \\\"End Time\\\",\\\"hours\\\", \\\"{ActiveEndHour}\\\",\\r\\n \\\"Time Offset from UTC\\\", \\\"hours\\\", \\\"{TimeOffset}\\\",\\r\\n ];\\r\\nActiveHours\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"green\"},\"numberFormat\":{\"unit\":26,\"options\":{\"style\":\"decimal\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 4\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Cost Estimates\"},\"customWidth\":\"100\",\"name\":\"text - 8\",\"styleSettings\":{\"maxWidth\":\"100%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Cost Per SCU\\\", \\\"{SCUPrice}\\\",\\r\\n \\\"Weekly Estimated Cost\\\", \\\"{WeeklySCUCost}\\\",\\r\\n \\\"Monthly Estimated Cost\\\", \\\"{MonthlySCUCost}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"crossComponentResources\":[\"{LogAnalyticsWorkspace}\"],\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"turquoise\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"customWidth\":\"100\",\"name\":\"StatGroup - ViewGroup\"}],\"exportParameters\":true},\"conditionalVisibilities\":[{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"View\"},{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"},{\"parameterName\":\"HasConfig\",\"comparison\":\"isEqualTo\",\"value\":\"1\"}],\"name\":\"ViewScheduleGroup\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"items\":[{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"\\r\\nlet StartDate = startofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet EndDate = endofweek(datetime_add(\\\"hour\\\", {TimeOffset}, now()));\\r\\nlet ActiveDays=dynamic([{ActiveDays}]);\\r\\nlet ResourceName = \\\"{ResourceName}\\\";\\r\\nlet MinSCU = {MinSCU};\\r\\nlet MaxSCU = {MaxSCU};\\r\\nlet CurrentSCU = {CurrentSCU};\\r\\nlet ActiveStartHour = {ActiveStartHour};\\r\\nlet ActiveEndHour = {ActiveEndHour};\\r\\nrange TimeDate from StartDate to EndDate step 1h\\r\\n| extend DayofWeek = toint(dayofweek(TimeDate)/1d), HourofDay = hourofday(datetime_add(\\\"hour\\\", {TimeOffset}, TimeDate))\\r\\n| extend SCU = iff(isnotempty(ActiveDays),iff(DayofWeek in (ActiveDays) and (HourofDay >= ActiveStartHour and HourofDay < ActiveEndHour), MaxSCU, MinSCU),CurrentSCU)\\r\\n| project-away DayofWeek,HourofDay\",\"size\":1,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"barchart\",\"chartSettings\":{\"seriesLabelSettings\":[{\"seriesName\":\"SCU\",\"color\":\"blue\"},{\"seriesName\":\"$_thresholdLine\",\"label\":\"Minimum SCU\",\"color\":\"blueDark\"}],\"customThresholdLine\":\"{MinSCU}\",\"customThresholdLineStyle\":0,\"xSettings\":{\"numberFormatSettings\":{\"unit\":17,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}},\"ySettings\":{\"numberFormatSettings\":{\"unit\":0,\"options\":{\"style\":\"decimal\",\"useGrouping\":true}}}}},\"name\":\"query - 6\"},{\"type\":12,\"content\":{\"version\":\"NotebookGroup/1.0\",\"groupType\":\"editable\",\"loadType\":\"always\",\"items\":[{\"type\":1,\"content\":{\"json\":\"### Configured Capacity\"},\"customWidth\":\"50\",\"name\":\"text - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":1,\"content\":{\"json\":\"### Cost Estimates\"},\"customWidth\":\"50\",\"name\":\"text - 8\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let SCUCapacity = datatable(label:string, label2: string, value:string, )[\\r\\n \\\"Hourly Capacity\\\",\\\"SCU(s)\\\", \\\"{CurrentSCU}\\\",\\r\\n \\\"Weekly Total SCUs\\\",\\\"SCU(s)\\\", \\\"{TotalSCU}\\\"\\r\\n ];\\r\\nSCUCapacity\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\",\"formatter\":1},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"min\":0,\"palette\":\"blue\"},\"numberFormat\":{\"unit\":17,\"options\":{\"style\":\"decimal\"}}},\"rightContent\":{\"columnMatch\":\"label2\",\"formatter\":1,\"formatOptions\":{\"compositeBarSettings\":{\"labelText\":\"\",\"columnSettings\":[]}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 3\",\"styleSettings\":{\"maxWidth\":\"50%\"}},{\"type\":3,\"content\":{\"version\":\"KqlItem/1.0\",\"query\":\"let Summaries = datatable(label:string,value:string, )[\\r\\n \\\"Cost Per SCU\\\", \\\"{SCUPrice}\\\",\\r\\n \\\"Weekly Estimated Cost\\\", \\\"{WeeklySCUCost}\\\",\\r\\n \\\"Monthly Estimated Cost\\\", \\\"{MonthlySCUCost}\\\"\\r\\n ];\\r\\nSummaries\",\"size\":4,\"timeContext\":{\"durationMs\":86400000},\"queryType\":0,\"resourceType\":\"microsoft.operationalinsights/workspaces\",\"visualization\":\"tiles\",\"tileSettings\":{\"titleContent\":{\"columnMatch\":\"label\"},\"leftContent\":{\"columnMatch\":\"value\",\"formatter\":12,\"formatOptions\":{\"palette\":\"turquoise\"},\"numberFormat\":{\"unit\":0,\"options\":{\"currency\":\"USD\",\"style\":\"currency\"}}},\"showBorder\":false}},\"customWidth\":\"50\",\"name\":\"query - 6\",\"styleSettings\":{\"maxWidth\":\"50%\"}}]},\"customWidth\":\"100\",\"name\":\"StatGroup - ViewGroup\"}],\"exportParameters\":true},\"conditionalVisibilities\":[{\"parameterName\":\"Nav\",\"comparison\":\"isEqualTo\",\"value\":\"View\"},{\"parameterName\":\"ResourceId\",\"comparison\":\"isNotEqualTo\",\"value\":\"Blank\"},{\"parameterName\":\"HasConfig\",\"comparison\":\"isNotEqualTo\",\"value\":\"1\"}],\"name\":\"ViewNoScheduleGroup\"}],\"isLocked\":false,\"fromTemplateId\":\"sentinel-UserWorkbook\"}", - "version": "1.0", - "sourceId": "[concat('/subscriptions/',subscription().subscriptionId,'/resourcegroups/',parameters('sentinelResourceGroup'),'/providers/Microsoft.OperationalInsights/workspaces/',parameters('sentinelWorkspaceName'))]", - "category": "sentinel" - } - } - ] - }, - "parameters": {} - } - } - ] -} diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml index 1bd8fffc..ca869f2f 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/DeviceEnrichment.yaml @@ -22,10 +22,10 @@ Descriptor: SettingType: String Required: true - - Name: SubscriptionName - Label: SubscriptionName - Description: This is the subscription name that security copilot will use for sentinel. - HintText: yoursubscriptionname + - Name: SubscriptionId + Label: SubscriptionId + Description: This is the subscription ID that security copilot will use for sentinel. + HintText: SubscriptionId SettingType: String Required: true @@ -130,7 +130,7 @@ SkillGroups: Settings: Target: Sentinel TenantId: "{{TenantId}}" - SubscriptionName: "{{SubscriptionName}}" + SubscriptionId: "{{SubscriptionId}}" ResourceGroupName: "{{ResourceGroupName}}" WorkspaceName: "{{WorkspaceName}}" Template: |- @@ -176,7 +176,6 @@ SkillGroups: - Name: DeviceCVESearch DisplayName: Device CVE Search DescriptionForModel: |- - Identify specific CVEs affecting a specified device based on the selected severity level. For each vulnerability, it: Identify specific CVEs affecting a specified device based on the selected severity level. For each vulnerability, it: 1. **Analyzes Software Vendors**: Evaluates the type of software the vendor produces and the potential risks associated with the vulnerability, considering the software's role in the system and possible exploitation impacts. 2. **Summarizes Device Security State**: Assesses the device's security posture by reviewing the number and severity of CVEs and identifying critical areas requiring immediate attention. @@ -209,18 +208,6 @@ SkillGroups: IsExploitAvailable | order by EpssScore desc, CvssScore desc - | lookup DeviceTvmSoftwareVulnerabilitiesKB on CveId - | project - DeviceName, - SoftwareVendor, - SoftwareName, - CveId, - CvssScore, - EpssScore, - CvssVector, - IsExploitAvailable - | order by EpssScore desc, CvssScore desc - - Name: CVEHostSearch DisplayName: CVE Host Search diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md index 5c7b2519..8ad23517 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/USERENRICHMENT.md @@ -2,58 +2,93 @@ **Author: Craig Freyman** ## User Enrichment Plugin -This suite of enrichment skills is designed to enhance security investigations by providing detailed insights into user activities, such as login behavior, password changes, MFA modifications, location analysis, and mailbox activities. By leveraging these tools, security teams can identify potential threats, monitor user risks, and respond to suspicious behavior effectively. +This suite of enrichment skills is designed to enhance security investigations by providing detailed insights into user activities, such as login behavior, password changes, MFA modifications, location analysis, mailbox activities, and IP-based sign-in tracking. By leveraging these tools, security teams can identify potential threats, monitor user risks, and respond to suspicious behavior effectively. + +--- ### UnusualSigninActivity Identify unusual sign-in patterns for a specific user based on anomalies such as high-risk locations, login failures, and sudden changes in device, application, or IP address usage. Highlights deviations from normal behavior and provides recommendations for security actions. -Example Prompt: Identify unusual sign-in patterns for mscott@paper.com +**Example Prompt**: Identify unusual sign-in patterns for mscott@paper.com. + +--- ### PasswordChangeCount Analyze the number of password changes for a specific user over the past 7 days. Identify potential security threats, such as account compromise or phishing attacks, by detecting anomalies in password change patterns. -Example Prompt: Detect recent password changes for mscott@paper.com over the past 7 days. +**Example Prompt**: Detect recent password changes for mscott@paper.com over the past 7 days. + +--- ### RecentMFAChanges Track recent MFA changes for a user, identifying added, removed, or modified authentication methods. Provides detailed insights into changes and highlights anomalies. -Example Prompt: Track MFA changes for mscott@paper.com in the last 7 days. +**Example Prompt**: Track MFA changes for mscott@paper.com in the last 7 days. + +--- ### UserHomeOfficeLocation Retrieve and compare a user's home office location (city, country, state) with recent sign-in locations to detect unusual activity. Highlights deviations that may indicate unauthorized access. -Example Prompt: Compare home office location with sign-in patterns for mscott@paper.com. +**Example Prompt**: Compare home office location with sign-in patterns for mscott@paper.com. + +--- ### UserSigninBaseline Establish a baseline of a user's typical sign-in activity over the past 30 days, including their most frequently used IP address, location, device, application, and client application. Detect deviations to identify potential security risks. -Example Prompt: Establish sign-in baseline for mscott@paper.com over the last 30 days. +**Example Prompt**: Establish sign-in baseline for mscott@paper.com over the last 30 days. + +--- ### FailedUserSignInSpecificDay -Identify failed sign-in attempts for a specific user over a defined lookback period. Provides insights into failed attempts, including timestamps, IP addresses, and failure reasons. +Identify failed sign-in attempts for a specific user over a defined lookback period. Provides insights into failed attempts, including timestamps, IP addresses, locations, and failure reasons. -Example Prompt: Identify failed sign-in attempts for mscott@paper.com in the last 7 days. +**Example Prompt**: Identify failed sign-in attempts for mscott@paper.com in the last 7 days. -### UserRiskDetections -Analyze and prioritize medium and high-risk detections for a specific user over the past 7 days. Provides insights into risk events, such as risk types, details, and locations. +--- -Example Prompt: Analyze medium and high-risk events for mscott@paper.com in the last 7 days. +### EnhancedUserRiskAssessment +Combine user risk events, recent sign-ins, high-risk user indicators, and security alerts to deliver detailed context around risky user activities for a specific user. Supports threat detection, incident prioritization, and remediation planning. -### HighRiskUserDetection -Identify high-risk users with recent activity by correlating risk events and user status. Focus on unresolved risks and prioritize remediation steps. +**Example Prompt**: Fetch enhanced user risk details for mscott@paper.com. -Example Prompt: Identify high-risk user activity for mscott@paper.com in the past week. +--- ### ImpossibleTravel -Detect impossible travel by analyzing login patterns and identifying logins from geographically distant locations within an unrealistically short time frame. +Detect impossible travel by analyzing login patterns and identifying logins from geographically distant locations within an unrealistically short time frame. Supports detection of potential credential misuse. -Example Prompt: Detect impossible travel events for mscott@paper.com in the past 7 days. +**Example Prompt**: Detect impossible travel events for mscott@paper.com in the past 7 days. + +--- ### SuspiciousMailboxActivities Investigate recent mailbox activities to detect potential security threats, such as: -- Newly created or modified rules that redirect or filter emails suspiciously. -- Mailbox permissions changes that might indicate unauthorized access. +- Newly created or modified inbox rules that redirect or filter emails suspiciously. +- Mailbox permission changes that might indicate unauthorized access. Focus on identifying anomalies that could lead to exfiltration or business email compromise (BEC) attempts. -Example Prompt: Investigate suspicious mailbox activities for mscott@test.com in the last 30 days. +**Example Prompt**: Investigate suspicious mailbox activities for mscott@test.com in the last 30 days. + +--- + +### DailyEntraIDRiskDetectionReview +Summarize and prioritize Entra ID risk detections from the past day, highlighting interesting or critical alerts that may require SOC analyst review. + +**Example Prompt**: Summarize Entra ID risk detections from the past 24 hours. + +--- + +### SuccessfulLoginsFromIP +Find all successful user sign-ins originating from a specified IP address. Returns detailed event information including timestamp, user, location, device, and application. + +**Example Prompt**: List all successful logins from 1.2.3.4. + +--- + +### SuccessfulLoginCountFromIP +Return the total number of successful sign-in attempts from a given IP address. Useful for quickly determining if a specific IP has ever been successfully used. + +**Example Prompt**: Count successful logins from 1.2.3.4. +--- diff --git a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml index f19c17fa..a941e497 100644 --- a/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml +++ b/Plugins/Community Based Plugins/Microsoft Defender XDR Custom Plugin Scenarios/EnrichmentPlugins/UserEnrichment.yaml @@ -30,6 +30,14 @@ SkillGroups: - Name: upn Description: User principal name, e.g., mscott@woodgrove.ms Required: true + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false + - Name: unusualsignin_context + Description: Additional context to tune the output appropriately + Default: "" + Required: false Settings: Target: Defender Template: |- @@ -37,11 +45,13 @@ SkillGroups: // It analyzes login patterns, focusing on anomalies such as high failure rates, sign-ins from unfamiliar IPs or devices, or abnormal client applications. // Deviations from the usual patterns may indicate suspicious activity, prompting security actions like MFA review or password reset. // The output includes IP addresses, device IDs, and user agents to assist in identifying unusual behavior. + // The additional requests from the user are {{unusualsignin_context}} let UPN = "{{upn}}"; // Replace with the specific users upn let TimeFrame = ago(30d); // Retrieve and summarize the user's login activity, focusing on successful vs failed attempts and identifying unusual patterns. SigninLogs | where TimeGenerated > TimeFrame + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where UserPrincipalName =~ UPN | summarize FirstSeen = min(TimeGenerated), LastObserved = max(TimeGenerated), SuccessfullCount = count(ResultType = 0), FailureCount = count(ResultType != 0) by UserPrincipalName, IPAddress, Location, tostring(DeviceDetail.deviceId), UserAgent, ClientAppUsed, AppDisplayName @@ -222,30 +232,42 @@ SkillGroups: - Name: upn Description: User principle name. i.e., mscott@woodgrove.ms Required: true + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false + - Name: usersignin_context + Description: Additional context to tune the output appropriately + Default: "" + Required: false Settings: Target: Defender Template: |- + // The additional context from the user is {{usersignin_context}} // This query establishes a baseline for a user's sign-in activity over the past 30 days by identifying the most frequently used IP address, location, device, application, and client application. // It helps detect deviations that may indicate suspicious behavior, such as sign-ins from unfamiliar locations, new devices, or different apps. // Anomalies can point to account compromise or unauthorized access, triggering actions like MFA enforcement or security reviews. // The query outputs the user's typical behavior patterns and highlights any deviations. // Provide a summary of the users baseline and analyze any deviations, recommending actions like enforcing MFA, resetting credentials, or initiating further security reviews if abnormal patterns are detected. + // The additional context from the user is {{usersignin_context}} let UPN = "{{upn}}"; // Replace with the specific users email let TimeFrame = ago(30d); // Identify the most frequently used IP address for the user. let MostCommonIP = toscalar( - SigninLogs - | where TimeGenerated > TimeFrame - | where UserPrincipalName =~ UPN - | summarize IPCount = count() by IPAddress - | top 1 by IPCount desc - | project IPAddress + SigninLogs + | where TimeGenerated > TimeFrame + | where UserPrincipalName =~ UPN + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query + | summarize IPCount = count() by IPAddress + | top 1 by IPCount desc + | project IPAddress ); // Identify the most frequently used location (e.g., city, country) for the user. let MostCommonLocation = toscalar( SigninLogs | where TimeGenerated > TimeFrame | where UserPrincipalName =~ UPN + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | summarize LocationCount = count() by Location | top 1 by LocationCount desc | project Location @@ -255,6 +277,7 @@ SkillGroups: SigninLogs | where TimeGenerated > TimeFrame | where UserPrincipalName =~ UPN + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | summarize DeviceCount = count() by tostring(DeviceDetail.deviceId) | top 1 by DeviceCount desc | project tostring(DeviceDetail_deviceId) @@ -263,6 +286,7 @@ SkillGroups: let MostCommonApp = toscalar( SigninLogs | where TimeGenerated > TimeFrame + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where UserPrincipalName =~ UPN | summarize AppCount = count() by AppDisplayName | top 1 by AppCount desc @@ -272,6 +296,7 @@ SkillGroups: let MostCommonClientApp = toscalar( SigninLogs | where TimeGenerated > TimeFrame + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where UserPrincipalName =~ UPN | summarize ClientAppCount = count() by ClientAppUsed | top 1 by ClientAppCount desc @@ -314,92 +339,123 @@ SkillGroups: - Name: lookback_period Description: The time range to look back. i.e., 1d, 7d, 30d (default is 1d) Required: false + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false Settings: Target: Defender Template: |- SigninLogs | where TimeGenerated >= ago(1d) // Specify the lookback_period, default 1d + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | where ResultType != 0 // Filtering for failed sign-ins; 0 indicates success | where UserPrincipalName == "{{upn}}" | summarize FailedAttempts = count(), UniqueIPs = dcount(IPAddress), Locations = dcount(Location), ResultDescriptions = makeset(ResultDescription) by bin(TimeGenerated, 1d) | project TimeGenerated, FailedAttempts, UniqueIPs, Locations, ResultDescriptions | order by TimeGenerated desc - - Name: UserRiskDetections - DisplayName: Entra User Risk Detections - Description: "Analyze risk events for a specific user by joining user risk detections with security alerts to provide enriched insights. This query identifies and correlates risk activities with security alerts, highlighting types, details, timing, and severity. It supports detailed investigations by surfacing connected risks and alerts for prioritization and actionable response." - DescriptionForModel: |- - Performs a KQL query on the AADUserRiskEvents table, filtering for a specified user's principal name (UserPrincipalName) and joining with the SecurityAlert table to correlate user risk events with associated security alerts. Key features include: - - - **Risk and Alert Correlation**: - - Links risk events (Id) with related security alerts (VendorOriginalId) for enhanced context. - - Provides detailed information on RiskLevel, RiskDetail, and RiskState alongside alert-specific data like AlertName, AlertSeverity, and CompromisedEntity. - - - **User-Focused Insights**: - - Focuses on a single user's activity, helping analysts investigate incidents involving that user. - - Highlights the date and time of events (ActivityDateTime and StartTime) for timeline reconstruction. - - - **Enhanced Investigation**: - - Surfaces patterns in risk and alert data, aiding in anomaly detection and prioritization. - - Supports actions like user-specific remediations and incident containment. - - This skill enables efficient monitoring and investigation of user-specific risks and alerts, helping prioritize and mitigate potential threats effectively. - + - Name: EnhancedUserRiskAssessment + DisplayName: Enhanced User Risk Assessment + Description: > + Combines user risk events, recent sign-ins, high-risk user indicators, and security alerts to deliver detailed context around risky user activities for a specific user. Useful for threat detection, incident prioritization, and remediation planning. Inputs: - Name: upn - Description: The UPN to search for. For example, mscott@dunder.com + Description: User principal name. i.e., mscott@woodgrove.ms Required: true + ExamplePrompt: + - "Get enhanced user risk details" + - "Fetch detailed user risk assessment" + - "Run comprehensive user risk check" Settings: Target: Defender Template: |- - AADUserRiskEvents - | where TimeGenerated > ago(7d) - | where UserPrincipalName == "{{upn}}" - | join kind=inner (SecurityAlert) on $left.Id == $right.VendorOriginalId - - - Name: HighRiskUserDetection - DisplayName: High-Risk User Detection - Description: Identify high-risk users with recent activity by correlating risk events and user status. Highlights unresolved risks, focusing on key details such as risk type, IP address, and recent activity for prioritizing remediation steps. - DescriptionForModel: |- - Performs a KQL query to analyze high-risk users by joining the `AADRiskyUsers` and `AADUserRiskEvents` tables. Key features include: - - - **Risk Correlation**: - - Retrieves recent high-risk events (`DetectedDateTime` within the last 7 days) for a specified user. - - Joins data with high-risk user statuses from `AADRiskyUsers` to focus on unresolved risks. - - - **Detailed Insights**: - - Columns include: - - `RiskEventType`: Type of risk detected. - - `RiskDetail`: Specific details about the risk. - - `IpAddress`: Location associated with risky activity. - - `DetectedDateTime`: Timestamp of the detection. - - `RiskLastUpdatedDateTime`: Last update time for the user's risk status. - - - **Prioritization**: - - Orders results by `RiskLastUpdatedDateTime` to highlight users with the most recent unresolved high-risk activity. - - Ensures focus on persistent risks and users requiring immediate action. - - - **Goal**: - - Enables security analysts to identify patterns of risky behavior, such as multiple high-risk sign-ins or repeated attempts from unusual locations. - - Supports prioritized investigations and swift remediation actions, such as enforcing MFA, reviewing user activity, or disabling accounts. + let TimeFrame = ago(7d); + let upn = "{{upn}}"; + let UserRiskEvents = AADUserRiskEvents + | where TimeGenerated > TimeFrame and RiskLevel != "none" and RiskState != "remediated" and UserPrincipalName == upn + | project + UserPrincipalName, + RiskLevel, + RiskEventType, + RiskDetail, + Activity, + ActivityDateTime, + DetectedDateTime, + IpAddress, + Location, + RiskState, + Id, + CorrelationId; + let RecentSignIns = SigninLogs + | where TimeGenerated > TimeFrame + | project + SignInId = Id, + UserPrincipalName, + SignInTime = TimeGenerated, + ResultType, + ResultDescription, + AppDisplayName, + ClientAppUsed, + SignInIPAddress = IPAddress, + SignInLocationDetails = LocationDetails, + DeviceDetail, + RiskLevelDuringSignIn, + ConditionalAccessStatus, + AuthenticationMethodsUsed, + CorrelationId; + let HighRiskUsers = AADRiskyUsers + | where TimeGenerated > TimeFrame and RiskLevel in ("high", "medium") + | project + UserPrincipalName, + UserRiskLevel = RiskLevel, + RiskState, + RiskLastUpdatedDateTime; + let Alerts = SecurityAlert + | where TimeGenerated > TimeFrame + | project + AlertName, + AlertSeverity, + CompromisedEntity, + AlertStartTime = StartTime, + AlertEndTime = EndTime, + VendorOriginalId; + UserRiskEvents + | join kind=leftouter RecentSignIns on CorrelationId + | join kind=leftouter HighRiskUsers on UserPrincipalName + | join kind=leftouter Alerts on $left.Id == $right.VendorOriginalId + | project + UserPrincipalName, + RiskLevel, + RiskEventType, + RiskDetail, + RiskState, + Activity, + ActivityDateTime, + DetectedDateTime, + IpAddress, + Location, + SignInTime, + ResultType, + ResultDescription, + AppDisplayName, + ClientAppUsed, + SignInIPAddress, + SignInLocationDetails, + DeviceDetail, + RiskLevelDuringSignIn, + ConditionalAccessStatus, + AuthenticationMethodsUsed, + UserRiskLevel, + RiskLastUpdatedDateTime, + AlertName, + AlertSeverity, + CompromisedEntity, + AlertStartTime, + AlertEndTime + | order by RiskLevel desc, DetectedDateTime desc + | take 10 - This skill helps analysts detect and respond to unresolved risks in high-risk users effectively. - Inputs: - - Name: upn - Description: The UPN to search for. For example, mscott@dunder.com - Required: true - Settings: - Target: Defender - Template: |- - let recentEvents = AADUserRiskEvents - | where DetectedDateTime > ago(7d) - | where RiskLevel == "high" - | where UserPrincipalName == "{{upn}}" - | project UserPrincipalName, Activity, DetectedDateTime, RiskEventType, RiskDetail, IpAddress; - AADRiskyUsers - | where RiskLevel == "high" - | join kind=inner (recentEvents) on UserPrincipalName - | order by RiskLastUpdatedDateTime desc - Name: ImpossibleTravel DisplayName: Impossible Travel @@ -432,6 +488,10 @@ SkillGroups: - Name: upn Description: The UPN (User Principal Name) to search for. Required: true + - Name: exclude_ip + Description: Any IP address that should be excluded from the results. + Default: "" + Required: false Settings: Target: Defender Template: |- @@ -450,6 +510,7 @@ SkillGroups: SigninLogs | where ResultType == 0 // Filter for successful logins | where TimeGenerated > ago(7d) + | where IPAddress != "{{exclude_ip}}" // exclude_ip from query | extend LocationDetails = parse_json(LocationDetails) // Parse location details | extend Latitude = tostring(LocationDetails.geoCoordinates.latitude), Longitude = tostring(LocationDetails.geoCoordinates.longitude), @@ -504,4 +565,95 @@ SkillGroups: | extend packed = pack(Name, Value) | where Parameters has_any("DeleteMessage") or (Parameters has_any("MoveToFolder") and Parameters has_any("Conversation History")) | summarize PackedInfo = make_bag(packed), ActionType=any(ActionType) by UPN, AccountObjectId, IPAddress, ISP, ReportId, TimeGenerated - | evaluate bag_unpack(PackedInfo) \ No newline at end of file + | evaluate bag_unpack(PackedInfo) + + - Name: DailyEntraIDRiskDetectionReview + DisplayName: Daily Entra ID Risk Detection Review + DescriptionForModel: |- + Performs a KQL query on the `AADUserRiskEvents` table to summarize Entra ID risk detections from the previous day. Key features include: + - **Risk Detection Summary**: Groups risk events by `RiskEventType`, `RiskLevel`, `Activity`, `RiskDetail`, and `DetectionTimingType`. + - **Prioritization**: Assigns priority based on the severity and frequency of events: + - High priority for infrequent, high-risk detections. + - Medium priority for moderate-risk detections. + - Low priority for common or lower-severity detections. + - **Detailed Insights**: + - Total number of detections. + - Number of impacted users. + - Time range of detected risks (`FirstDetected`, `LastDetected`). + - **Actionable Recommendations**: Highlights detections of interest that may require further investigation or review, assisting analysts in identifying overlooked or significant risk events. + This skill aids security teams in efficiently reviewing and prioritizing Entra ID risk detections daily, reducing alert fatigue and ensuring critical threats are investigated. + Description: Summarize and prioritize Entra ID risk detections from the past day, highlighting interesting or critical alerts that may require SOC analyst review. Results include impactedUPNs if further analysis is required. + Settings: + Target: Defender + Template: |- + // Summarizes and prioritizes Entra ID risk detections from the previous day. + let TimeFrame = ago(1d); + AADUserRiskEvents + | where TimeGenerated >= TimeFrame + | summarize + TotalDetections = count(), + ImpactedUsers = dcount(UserPrincipalName), + ImpactedUPNs = make_set(UserPrincipalName, 50), // Include impacted users explicitly + FirstDetected = min(DetectedDateTime), + LastDetected = max(DetectedDateTime) + by RiskEventType, RiskLevel, Activity, RiskDetail, DetectionTimingType, Source + | extend RiskPriority = case( + RiskLevel == "high" and TotalDetections < 10, "High", + RiskLevel == "medium" and TotalDetections < 5, "Medium", + "Low") + | order by RiskPriority asc, TotalDetections asc + + - Name: SuccessfulLoginsFromIP + DisplayName: Successful Logins from an IP + DescriptionForModel: | + Perform a KQL query to identify all successful sign-in events from a specified IP address. Provides full event details including timestamp, user, location, device, and app information. + Description: Find all successful user sign-ins originating from a specified IP address. + Inputs: + - Name: ip_address + Description: IP address to search for. Example 1.2.3.4 + Required: true + - Name: additional_context + Description: Any additional context for security copilot + Required: false + Settings: + Target: Defender + Template: |- + //Query to return known successful connnections from a particular IP address. + //Useful for troubleshooting false vs true positive alerts + //Here is any additional user context: {{additional_context}} + let TargetIP = "{{ip_address}}"; // Target IP address for investigation + SigninLogs + | where IPAddress == TargetIP // Filter events matching the specified IP + | where ResultType == 0 // Keep only successful sign-in events (ResultType = 0) + | project + TimeGenerated, // Time of the sign-in + UserPrincipalName, // User who signed in + IPAddress, // IP address used + Location, // Geolocation (if available) + DeviceDetail, // Device information + AppDisplayName, // Application accessed + ClientAppUsed // Client app used (browser, native app, etc.) + | order by TimeGenerated desc // Show newest events first + + - Name: SuccessfulLoginCountFromIP + DisplayName: Successful Login Count from an IP + DescriptionForModel: | + Perform a KQL query to check if there have been any successful sign-ins from a specified IP address. Returns the number of successful events. + Description: Return the total number of successful sign-in attempts from a given IP address. + Inputs: + - Name: ip_address + Description: IP address to search for. Example 1.2.3.4 + Required: true + - Name: additional_context + Description: Any additional context for security copilot + Required: false + Settings: + Target: Defender + Template: |- + //Query to count how many successful logins from a particular IP address to give additional perspective to help with troubleshooting true vs fales positive login events. + //Here is any additional user context: {{additional_context}} + let TargetIP = "{{ip_address}}"; // Target IP address for counting successful logins + SigninLogs + | where IPAddress == TargetIP // Filter events matching the specified IP + | where ResultType == 0 // Keep only successful sign-in events + | summarize SuccessCount = count() // Return the number of successful sign-ins diff --git a/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevices-OpenAPISpec.yaml b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevices-OpenAPISpec.yaml new file mode 100644 index 00000000..ed43098f --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevices-OpenAPISpec.yaml @@ -0,0 +1,101 @@ +openapi: 3.0.0 + +# This section provides metadata about the plugin, including its title, description, and version. +info: + title: Retrieve devices onboarded into Microsoft Defender for Endpoint (MDE) + description: Plugin to retrieve a list of devices onboarded into Microsoft Defender for Endpoint (MDE) using Microsoft Graph API. + version: 1.0.0 + +# Defines the base URL for the Microsoft Graph API, which is used to make requests. +servers: + - url: https://graph.microsoft.com/v1.0 + description: Microsoft Graph API url + +# Specifies the available API endpoints and their operations, in this case, retrieving a list of devices. +paths: + /devices: + get: + summary: Get devices + description: Retrieve a list of devices onboarded into Microsoft Defender for Endpoint + responses: + '200': + description: List of devices + content: + application/json: + schema: + type: object + properties: + value: + type: array + items: + type: object + properties: + id: + type: string + description: Device ID. + deviceName: + type: string + description: Name of the device. + osPlatform: + type: string + description: Operating system of the device. + complianceState: + type: string + description: Compliance state of the device. + lastSeenDateTime: + type: string + format: date-time + description: Last seen date and time. + onboardedDate: + type: string + format: date + description: Date the device was onboarded. + manufacturer: + type: string + description: Manufacturer of the device. + model: + type: string + description: Model of the device. + serialNumber: + type: string + description: Serial number of the device. + macAddress: + type: string + description: MAC address of the device. + ipAddress: + type: string + description: IP address of the device. + isAzureAdJoined: + type: boolean + description: Indicates if the device is Azure AD joined. + isCompliant: + type: boolean + description: Indicates if the device is compliant. + riskLevel: + type: string + description: Risk level assigned to the device. + '401': + description: Unauthorized error. + '403': + description: Forbidden access. + '500': + description: Internal server error. + +# Defines security mechanisms, such as authentication methods, that the API uses. +components: + securitySchemes: + bearerAuth: + type: http + scheme: bearer + +security: + - bearerAuth: [] + +# Provides example natural language queries that users can use to interact with the API via Security Copilot and retrieve specific device information. +x-example-prompts: + - Retrieve devices onboarded to MDE that have a compliance state of 'Non-compliant' + - Get the list of non-compliant devices from devices onboarded to MDE + - retrieve a list of devices onboarded onto MDE that have a Risk Level of "High" + - Retrieve devices from MDE that registered less than 14 days ago from today + - Retrieve a list of MDE endpoints that were last seen less than 1 day ago + - Get devices from MDE that have the IoS operating system diff --git a/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevicesPlugin.yaml b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevicesPlugin.yaml new file mode 100644 index 00000000..36e2dc79 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/MDEOnboardedDevicesPlugin.yaml @@ -0,0 +1,13 @@ +Descriptor: + Name: RetrieveMDEOnboardedDevices + DisplayName: Retrieve details for devices onboarded onto Microsoft Defender for Endpoint (MDE) + Description: Retrieve details for devices onboarded onto Microsoft Defender for Endpoint (MDE) + DescriptionForModel: Retrieves detailed information about devices onboarded into Microsoft Defender for Endpoint (MDE),including device name, operating system, compliance state, risk level, last seen timestamp,onboarded date, manufacturer, model, serial number, IP address, MAC address, and Azure AD join status.This API enables security analysts to assess device security posture, identify non-compliant or high-risk devices,and correlate endpoint data with ongoing security incidents. Results provide actionable insights for threat detection, response, and asset visibility within enterprise environments, supporting Security Copilot in enhancing situational awareness and proactive threat mitigation. + + Authorization: + Type: AADDelegated + EntraScopes: https://graph.microsoft.com/.default +SkillGroups: + - Format: API + Settings: + OpenApiSpecUrl: https://gist.githubusercontent.com/inwafula/88a0e5b5f3a2bdc1840771b97937d8f0/raw/ff2211080321d6e74881ab960d9c2e0f060f3e28/MDEOnboardedDevices-OpenAPISpec.yaml \ No newline at end of file diff --git a/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/Readme.md b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/Readme.md new file mode 100644 index 00000000..b0fc1399 --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Graph API plugins/Devices Onboarded into MDE/Readme.md @@ -0,0 +1,36 @@ +# Plugin to retrieve Devices Onboarded into Microsoft Defender for Endpoint (MDE) Plugin + +## Overview +This plugin enables users to retrieve a list of devices onboarded into **Microsoft Defender for Endpoint (MDE)** using the **Microsoft Graph API**. It provides valuable insights into device details, compliance status, risk levels, and more, facilitating security monitoring and incident response. + +## Features +- Retrieves device details including **device name, OS, manufacturer, model, serial number, and MAC address**. +- Identifies **compliance state** and **risk level** of onboarded devices. +- Fetches devices based on **last seen date** +- Supports filtering for devices based on **Azure AD join status, compliance status, and OS platform**. + +## API Endpoint +**Base URL:** `https://graph.microsoft.com/v1.0` + + +### Setup instructions +#### Upload the Plugin manifest + +1. Obtain the manifest [MDEOnboardedDevicesPlugin.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Graph%20API%20/Devices%20Onboarded%20into%20MDE/MDEOnboardedDevicesPlugin.yaml) and the OpenAPI Specification [MDEOnboardedDevices-OpenAPISpec.yaml](https://github.com/Azure/Copilot-For-Security/blob/main/Plugins/Community%20Based%20Plugins/Microsoft%20Graph%20API%20/Devices%20Onboarded%20into%20MDE/MDEOnboardedDevices-OpenAPISpec.yaml) files from this directory. +2. Download the Spec file and move it to your preferred location, ensuring it is reachable by Security Copilot + + +3. [Upload the custom plugin](https://learn.microsoft.com/en-us/security-copilot/manage-plugins?tabs=securitycopilotplugin#add-custom-plugins) + +## Example Natural Language Queries +Use these sample prompts to retrieve relevant data via **Security Copilot**: +- "Retrieve devices onboarded to MDE that have a compliance state of 'Non-compliant'" +- "Get the list of non-compliant devices from devices onboarded to MDE" +- "Retrieve a list of devices onboarded onto MDE that have a Risk Level of 'High'" +- "Retrieve devices from MDE that registered less than 14 days ago from today" +- "Retrieve a list of MDE endpoints that were last seen less than 1 day ago" +- "Get devices from MDE that have the iOS operating system" + + +--- +For more details, visit the **[Microsoft Graph API documentation](https://learn.microsoft.com/en-us/graph/)**. diff --git a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml index 05b597a0..de1d5613 100644 --- a/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml +++ b/Plugins/Community Based Plugins/Microsoft Security Experts/Microsoft Defender Experts Plugin/Dexplugin.yaml @@ -1,3 +1,4 @@ +--- Descriptor: Name: MicrosoftDefenderExpertsPlugin DisplayName: Microsoft Defender Experts Security Copilot Plugin @@ -7,6 +8,7 @@ Descriptor: SkillGroups: - Format: KQL Skills: + - Name: DEX-SuspiciousUseOfAzureHound DisplayName: Suspicious Use of AzureHound Description: Detects potential suspicious usage of AzureHound on devices. @@ -21,7 +23,6 @@ SkillGroups: | where TimeGenerated > ago({{days}}d) | where FileName has "azurehound.exe" | where ProcessCommandLine has "azurehound" and ProcessCommandLine has_any ("az-rm", "az-ad", "list", "Invoke-azurehound") - - Name: DEX-ReconnaissanceActivityUsingNetworkLogs DisplayName: Reconnaissance Activity Using Network Logs Description: Detects reconnaissance activity leveraging network logs and specific URLs. @@ -42,7 +43,7 @@ SkillGroups: - Name: DEX-RoadreconAndRoadtxActivity DisplayName: ROADrecon and ROADtx Activity - Description: Detects known filenames and command line activity associated with ROADrecon and ROADtx tools. + Description: Detects known filenames and command line activity associated with ROADrecon and roadtx tools. Inputs: - Name: days Description: Look back x amount of days, for example 10, 20, 30. @@ -269,8 +270,8 @@ SkillGroups: Template: |- DeviceFileEvents | where TimeGenerated > ago({{days}}d) - | where InitiatingProcessFileName =~ 'ping.exe' and InitiatingProcessCommandLine == 'ping.exe -t 127.0.0.1' - and InitiatingProcessParentFileName in~('msra.exe', 'mobsync.exe') and FolderPath endswith ".eml" + | where InitiatingProcessFileName =~ "ping.exe" and InitiatingProcessCommandLine == "ping.exe -t 127.0.0.1" + and InitiatingProcessParentFileName in~ ("msra.exe", "mobsync.exe") and FolderPath endswith ".eml" - Name: DEX-QakbotReconnaissanceActivities DisplayName: Qakbot Reconnaissance Activities Detection @@ -286,8 +287,8 @@ SkillGroups: | where TimeGenerated > ago({{days}}d) | where InitiatingProcessFileName == InitiatingProcessCommandLine | where ProcessCommandLine has_any ( - "whoami /all","cmd /c set","arp -a","ipconfig /all","net view /all","nslookup -querytype=ALL -timeout=10", - "net share","route print","netstat -nao","net localgroup") + "whoami /all", "cmd /c set", "arp -a", "ipconfig /all", "net view /all", "nslookup -querytype=ALL -timeout=10", + "net share", "route print", "netstat -nao", "net localgroup") | summarize dcount(FileName), make_set(ProcessCommandLine) by DeviceId, bin(Timestamp, 1d), InitiatingProcessFileName, InitiatingProcessCommandLine | where dcount_FileName >= 8 @@ -333,13 +334,13 @@ SkillGroups: Template: |- DeviceProcessEvents | where TimeGenerated > ago({{days}}d) - | where InitiatingProcessFileName =~ 'wmiprvse.exe' - | where FileName =~ 'powershell.exe' - and (ProcessCommandLine hasprefix '-e' or ProcessCommandLine contains 'frombase64') - | where ProcessCommandLine matches regex '[A-Za-z0-9+/]{50,}[=]{0,2}' - | where ProcessCommandLine !has 'Windows\\CCM\\' + | where InitiatingProcessFileName =~ "wmiprvse.exe" + | where FileName =~ "powershell.exe" + and (ProcessCommandLine hasprefix "-e" or ProcessCommandLine contains "frombase64") + | where ProcessCommandLine matches regex "[A-Za-z0-9+/]{50,}[=]{0,2}" + | where ProcessCommandLine !has "Windows\\CCM\\" | project DeviceId, Timestamp, InitiatingProcessId, InitiatingProcessFileName, ProcessId, FileName, ProcessCommandLine - + - Name: DEX-AutomatedEmailNotificationsAndSuspiciousSignInActivity DisplayName: Automated Email Notifications and Suspicious Sign-In Activity Detection Description: Correlates automated email notifications with suspicious sign-in activity to identify potential compromises. @@ -439,6 +440,202 @@ SkillGroups: | summarize dcount(UserSharedWith) by FileShared, AccountObjectId | where dcount_UserSharedWith >= 20 + - Name: DEX-TeamsActivityAfterSuspiciousRMM + DisplayName: Teams Activity After Suspicious RMM + Description: Detects Teams activity followed by suspicious Remote Monitoring and Management + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let interestingUsers = DeviceProcessEvents + | where Timestamp > ago(1h) + | where isnotempty(InitiatingProcessAccountObjectId) + | where FileName has_any ("quickassist.exe", "anydesk.exe", "teamviewer_service.exe") // Multiple RMM tools can be abused here + | project InitiatingProcessAccountUpn; + CloudAppEvents + | where Timestamp > ago(1d) + | where Application == "Microsoft Teams" + | where ActionType == "ChatCreated" + | where isempty(AccountObjectId) + | where RawEventData.ParticipantInfo.HasForeignTenantUsers == true + | where RawEventData.CommunicationType == "OneOnOne" + | where RawEventData.ParticipantInfo.HasGuestUsers == false + | where RawEventData.ParticipantInfo.HasOtherGuestUsers == false + | where AccountId has "@" + | extend TargetUPN = tolower(tostring(RawEventData.Members[1].UPN)) + | where TargetUPN in (interestingUsers) + | extend VictimTenant = tostring(RawEventData.OrganizationId) + | extend AttackerTenant = RawEventData.Members[0].OrganizationId + | extend AttackerUPN = RawEventData.Members[0].UPN + | extend AttackerName = RawEventData.Members[0].DisplayName + + - Name: DEX-TeamsPhishingviaUrlClickEvents + DisplayName: Teams Phishing via Url Click Events + Description: Correlate suspicious device alerts with URL click events in MS Teams. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let alertedDevices = AlertEvidence + | where Timestamp > ago(1h) + | where isnotempty(DeviceId) + | distinct DeviceId; + let interestedUsers = DeviceProcessEvents + | where Timestamp > ago(1h) + | where DeviceId in (alertedDevices) + | where isnotempty(InitiatingProcessAccountUpn) + | distinct InitiatingProcessAccountUpn; + UrlClickEvents + | where Timestamp > ago(1d) + | where ActionType == "ClickAllowed" or IsClickedThrough != "0" + | where Workload has "Teams" + | where AccountUpn in (interestedUsers) + + - Name: DEX-TeamsAdversary-in-The-Middle(AiTM) + DisplayName: TeamsAdversary-in-The-Middle(AiTM) + Description: Correlate risky sign-in attempts with Teams phishing from external tenants to identify AiTM. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + let usersWithRiskySignIn = AADSignInEventsBeta + | where Timestamp > ago(1h) + | where RiskLevelDuringSignIn == 100 + | project AccountUpn; + CloudAppEvents + | where Timestamp > ago(1d) + | where Application == "Microsoft Teams" + | where ActionType == "ChatCreated" + | where isempty(AccountObjectId) + | where RawEventData.ParticipantInfo.HasForeignTenantUsers == true + | where RawEventData.CommunicationType == "OneOnOne" + | where RawEventData.ParticipantInfo.HasGuestUsers == false + | where RawEventData.ParticipantInfo.HasOtherGuestUsers == false + | where AccountId has "@" + | extend TargetUPN = tolower(tostring(RawEventData.Members[1].UPN)) + | where TargetUPN in (usersWithRiskySignIn) + | extend TargetTenant = tostring(RawEventData.OrganizationId) + | extend AttackerTenant = RawEventData.Members[0].OrganizationId + | where TargetTenant != AttackerTenant + | extend AttackerUPN = RawEventData.Members[0].UPN + | extend AttackerName = RawEventData.Members[0].DisplayName + | project-reorder Timestamp, AttackerTenant, AttackerUPN, AttackerName, TargetUPN + + - Name: DEX-SuspiciousFileDownloadsviaLOLBINs + DisplayName: Suspicious File Downloads via LOLBINs + Description: Detects Living-off-the-Land Binaries (LOLBINs) commonly used by adversaries to download additional payloads. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + DeviceNetworkEvents + | where TimeGenerated > ago({{days}}d) + | where InitiatingProcessFileName in~ ("curl.exe", "powershell.exe", "certutil.exe", "bitsadmin.exe") + | where RemoteIPType == "Public" + | where RemoteUrl endswith ".exe" or RemoteUrl endswith ".dll" or RemoteUrl endswith ".zip" + | project Timestamp, DeviceId, InitiatingProcessFileName, RemoteIP, RemoteUrl + | extend AlertType = "Suspicious file download from unknown IP address" + + - Name: DEX-CSVDEDownloadandADEnumeration + DisplayName: CSVDE Download and AD Enumeration + Description: Detects the download and execution of Csvde, a command-line tool that can be exploited by adversaries to extract sensitive data from AD DS or introduce malicious entries. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + // Detect curl downloading csvde.exe + let csvde_download = DeviceProcessEvents + | where InitiatingProcessFileName =~ "cmd.exe" + | where ProcessCommandLine has_all ("curl", "-o", "csvde.exe", "http:") + | project DeviceId, Timestamp, CurlCommandLine = ProcessCommandLine, CurlProcessId = ProcessId; + // Detect execution of csvde.exe with specific parameters + let csvde_execution = DeviceProcessEvents + | where FileName =~ "csvde.exe" + | where ProcessCommandLine has_all ("-r", "objectClass=Computer") + | where ProcessCommandLine has_all ("-l", "samAccountName", "description", "info", "operatingSystem") + | where ProcessCommandLine contains "-f" + | project DeviceId, Timestamp, CsvdeCommandLine = ProcessCommandLine, CsvdeProcessId = ProcessId; + // Join the two events and look for them occurring within 5 minutes + csvde_download + | join kind=inner (csvde_execution) on DeviceId + | where Timestamp between (Timestamp1 .. Timestamp1 + 5m) + | extend AlertType = "Potential Active Directory Enumeration", Details = strcat("curl.exe was used to download csvde.exe, which was then executed to enumerate AD computers.") + + - Name: DEX-DataCompressionandExfiltration + DisplayName: Data Compression and Exfiltration + Description: Detects the use of 7-Zip for extracting files with a password and joins that process with network events to identify exfiltration of data. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + // Detect potential data compression and exfiltration + let compress_exfil = DeviceProcessEvents + | where FileName =~ "7z.exe" + | where ProcessCommandLine contains "x -p" + | project Timestamp, DeviceId, FileName, ProcessCommandLine, ProcessId + | join kind=inner ( + DeviceNetworkEvents + | where InitiatingProcessId != 0 + ) on $left.ProcessId == $right.InitiatingProcessId + | project Timestamp, DeviceId, FileName, ProcessCommandLine, RemoteIP, RemotePort + | extend AlertType = "Potential data compression and exfiltration"; + + - Name: DEX-PersistenceThroughStartupOperations + DisplayName: Persistence Through Startup Operations + Description: Detects adversary payloads used to create persistence via ASEP Registry Keys, Startup Folder, or Scheduled Tasks. + Inputs: + - Name: days + Description: Look back x amount of days, for example 10, 20, 30. + Required: true + Settings: + Target: Defender + Template: |- + // Persistence through Startup operations + let regKeys = pack_array(@"CurrentVersion\Run", + @"CurrentVersion\RunOnce", + @"CurrentVersion\RunOnceEx", + @"Programs\Startup", + @"CurrentVersion\RunServicesOnce", + @"CurrentVersion\RunServices", + @"CurrentVersion\Policies\Explorer\Run", + @"Windows NT\CurrentVersion\Windows", + @"System\CurrentControlSet\Control\Session Manager"); + let startUpOperations = DeviceFileEvents + | where FolderPath has @"Start Menu\Programs\Startup\" + | where ActionType in ("FileCreated", "FileModified", "FileRenamed"); + // Persistence through Registry Tampering + let regOperations = DeviceRegistryEvents + | where hasAlertDevices + | where Timestamp between (startTime .. endTime) + | where DeviceId in (alertedDevices) + | where RegistryKey has_any (regKeys) + | where ActionType in ("SetValue", "CreateKey", "RenameKey"); + // Persistence through Scheduled task creation + let scheduledOperations = DeviceProcessEvents + | where Timestamp between (startTime .. endTime) + | where DeviceId in (alertedDevices) + | where (InitiatingProcessCommandLine has "schtasks" and InitiatingProcessCommandLine has_any ("run", "create", "change")); + union startUpOperations, regOperations, scheduledOperations + | summarize arg_min(Timestamp, *) by DeviceId + - Name: DEX-PossibleAiTMPhishingAttemptAgainstEntraID DisplayName: Possible AiTM Phishing Attempt Against Entra ID Description: | @@ -566,3 +763,157 @@ SkillGroups: | extend UEBARiskScore = IPInvestigationPriority | where UEBARiskScore > {{riskScoreCutoff}} | sort by UEBARiskScore desc + - Name: DEX-BrowserRemoteDebugging + DisplayName: Browser Remote Debugging + Description: Identifies AutoIT scripts launching Chromium-based browsers (e.g., chrome.exe, msedge.exe, brave.exe) in remote debugging mode. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h", "4h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where TimeGenerated > ago({{timeframe}}) + | where InitiatingProcessVersionInfoInternalFileName == "AutoIt3.exe" + | where ProcessCommandLine has "--remote-debugging-port" + | project DeviceId, Timestamp, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessFolderPath, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, FileName, ProcessCommandLine + + - Name: DEX-DPAPI-AutoIT + DisplayName: DPAPI Decryption via AutoIT + Description: Identifies DPAPI decryption activity originating from AutoIT scripts. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where ActionType == "DpapiAccessed" + | where InitiatingProcessVersionInfoInternalFileName == "AutoIt3.exe" + | where (AdditionalFields has_any ("Google Chrome", "Microsoft Edge") and AdditionalFields has_any ("SPCryptUnprotect")) + | extend json = parse_json(AdditionalFields) + | extend dataDesp = tostring(json.DataDescription.PropertyValue) + | extend opType = tostring(json.OperationType.PropertyValue) + | where (dataDesp in~ ("Google Chrome", "Microsoft Edge") and opType =~ "SPCryptUnprotect") + | project Timestamp, ReportId, DeviceId, ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, AdditionalFields, dataDesp, opType + + - Name: DEX-DPAPI-LOLBAS + DisplayName: DPAPI Decryption via LOLBAS Binaries + Description: Identifies DPAPI decryption activity originating from LOLBAS binaries such as RegAsm.exe and MSBuild.exe. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where ActionType == "DpapiAccessed" + | where InitiatingProcessFileName has_any ("RegAsm.exe", "MSBuild.exe") + | where (AdditionalFields has_any ("Google Chrome", "Microsoft Edge") and AdditionalFields has_any ("SPCryptUnprotect")) + | extend json = parse_json(AdditionalFields) + | extend dataDesp = tostring(json.DataDescription.PropertyValue) + | extend opType = tostring(json.OperationType.PropertyValue) + | where (dataDesp in~ ("Google Chrome", "Microsoft Edge") and opType =~ "SPCryptUnprotect") + | project Timestamp, ReportId, DeviceId, ActionType, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, AdditionalFields, dataDesp, opType + + - Name: DEX-SensitiveBrowserAccessAutoIT + DisplayName: Sensitive Browser File Access via AutoIT + Description: Identifies AutoIT scripts (renamed or otherwise) accessing sensitive browser files. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + let browserDirs = pack_array(@"\Google\Chrome\User Data\", @"\Microsoft\Edge\User Data\", @"\Mozilla\Firefox\Profiles\"); + let browserSensitiveFiles = pack_array("Web Data", "Login Data", "key4.db", "formhistory.sqlite", "cookies.sqlite", "logins.json", "places.sqlite", "cert9.db"); + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where AdditionalFields has_any ("FileOpenSource") + | where InitiatingProcessVersionInfoInternalFileName == "AutoIt3.exe" + | where (AdditionalFields has_any (browserDirs) or AdditionalFields has_any (browserSensitiveFiles)) + | extend json = parse_json(AdditionalFields) + | extend File_Name = tostring(json.FileName.PropertyValue) + | where (File_Name has_any (browserDirs) and File_Name has_any (browserSensitiveFiles)) + | project Timestamp, ReportId, DeviceId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, File_Name + + - Name: DEX-SensitiveBrowserAccessLOLBAS + DisplayName: Sensitive Browser File Access via LOLBAS + Description: Identifies LOLBAS binaries (e.g., RegAsm.exe, MSBuild.exe) accessing sensitive browser files. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + let browserDirs = pack_array(@"\Google\Chrome\User Data\", @"\Microsoft\Edge\User Data\", @"\Mozilla\Firefox\Profiles\"); + let browserSensitiveFiles = pack_array("Web Data", "Login Data", "key4.db", "formhistory.sqlite", "cookies.sqlite", "logins.json", "places.sqlite", "cert9.db"); + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where AdditionalFields has_any ("FileOpenSource") + | where InitiatingProcessFileName has_any ("RegAsm.exe", "MSBuild.exe") + | where (AdditionalFields has_any (browserDirs) or AdditionalFields has_any (browserSensitiveFiles)) + | extend json = parse_json(AdditionalFields) + | extend File_Name = tostring(json.FileName.PropertyValue) + | where (File_Name has_any (browserDirs) and File_Name has_any (browserSensitiveFiles)) + | project Timestamp, ReportId, DeviceId, InitiatingProcessParentFileName, InitiatingProcessFileName, InitiatingProcessVersionInfoInternalFileName, InitiatingProcessCommandLine, File_Name + + - Name: DEX-Node.jsInfostealerActivity + DisplayName: Node.js-based infostealer activity + Description: Detect malicious access to sensitive credentials using Windows DPAPI. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceEvents + | where Timestamp > ago({{timeframe}}) + | where isnotempty(DeviceId) + | where ActionType == "DpapiAccessed" + | where InitiatingProcessParentFileName endswith "powershell.exe" + | where InitiatingProcessFileName =~ "node.exe" + | where InitiatingProcessCommandLine has_all ("-r", ".js") and InitiatingProcessCommandLine endswith ".jsc" + | where AdditionalFields has "SPCryptUnprotect" + + - Name: DEX-SuspiciousInlineJavaScriptExecution + DisplayName: Suspicious inline JavaScript execution + Description: Identify suspicious inline JavaScript. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where Timestamp > ago({{timeframe}}) + | where isnotempty(DeviceId) + | where ProcessVersionInfoOriginalFileName == 'node.exe' + | where ProcessCommandLine has_all ('http', 'execSync', 'spawn', 'fs', 'path', 'zlib') + + - Name: DEX-SuspiciousJSCFile + DisplayName: Suspicious JSC file + Description: Identify execution of suspicious JSC file. + Inputs: + - Name: timeframe + Description: Time frame to search (e.g., "1h", "2h"). + Required: true + Settings: + Target: Defender + Template: |- + DeviceProcessEvents + | where Timestamp > ago({{timeframe}}) + | where isnotempty(DeviceId) + | where ProcessVersionInfoOriginalFileName == 'node.exe' + | where (ProcessCommandLine has_all (".jsc", ".js") and ProcessCommandLine matches regex @"\\\w*.jsc") + +... diff --git a/Plugins/Community Based Plugins/Microsoft Security Exposure Management/Microsoft Security Exposure Management.yaml b/Plugins/Community Based Plugins/Microsoft Security Exposure Management/Microsoft Security Exposure Management.yaml new file mode 100644 index 00000000..ce9764cb --- /dev/null +++ b/Plugins/Community Based Plugins/Microsoft Security Exposure Management/Microsoft Security Exposure Management.yaml @@ -0,0 +1,99 @@ +Descriptor: + Name: Microsoft Exposure Management + DisplayName: Microsoft Exposure Management + Description: > + This KQL plugin integrates capabilities of Microsoft Security Exposure Management (MSEM) with Security Copilot to show exposed devices. + MSEM provides situational awareness, while Security Copilot (SCP) offers quick and consistent retrieval capabilities. + Together, MSEM and SCP empower SOC Engineers with a natural-language front door into exposure insights and attack paths. + This combination also enables the inclusion of MSEM content and reasoning over this content in Security Copilot prompts, prompt books, and automation scenarios that leverage Security Copilot. + The two skills of the plugin correspond to the following use cases - + 1) Obtain exposure of an asset hosted on a particular cloud platform by your organization. + 2) Obtain exposure of an asset belonging to a specific user. + In each of the above use cases, you can also specify the exposure level for which you want to extract the data. + + SupportedAuthTypes: + - None + +##################################################################################### +# SkillGroups can be a single type (KQL, GPT, API) or contain a mix +##################################################################################### + +SkillGroups: + - Format: KQL + + Skills: + - Name: GetExposedDevicesByCloudPlatform + DisplayName: Get exposed Devices by Cloud Platform and exposure level. + Description: For a specific cloud platform (aka "Azure" or "GCP" or "AWS") or a list of cloud platform of the device separated by commas, semi-colons, or using the word 'or', get a list of devices + based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. + DescriptionForModel: For a specific cloud platform of the device (aka "Azure" or "GCP" or "AWS") or a list of cloud platform of the device separated by commas, get a list of devices based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. The results will be returned as a response to the query containing multiple entries, the user needs to be shown the below details from the response about each device DeviceName, DeviceId, ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture. + VERY IMPORTANT - Never truncate the list of devices retrieved by launching the KQL query related to this skill. Also if the user wants to search on premises then the value for CloudPlatforms should be blank in the query + + ExamplePrompts: + - Get all the devices exposed on Azure platform + - Get all the devices exposed on AWS platform + - Get all the devices exposed on GCP platform + - Get all the devices with exposure level High on Azure platform + - Get all the devices with exposure level Medium on Azure platform + - Get all the devices with exposure level Low on Azure platform + - Get all the devices with exposure level High on AWS platform + - Get all the devices with exposure level Medium on AWS platform + - Get all the devices with exposure level Low on AWS platform + - Get all the devices with exposure level High on GCP platform + - Get all the devices with exposure level Medium on GCP platform + - Get all the devices with exposure level Low on GCP platform + + Inputs: + - Name: inputExposureLevel + Description: Provide the exposure level you want to consider - e.g. High, Medium, Low (only one level) + Required: true + - Name: inputCloudPlatforms + Description: Provide the cloud platform of the device you want to consider - e.g. Azure or GCP or AWS + Required: false + + Settings: + Target: Defender + Template: |- + let userExposureLevel='{{inputExposureLevel}}'; + let userCloudPlatform='{{inputCloudPlatforms}}'; + DeviceInfo + | where ExposureLevel == userExposureLevel + | where + (CloudPlatforms != '' and (CloudPlatforms contains userCloudPlatform or isempty(userCloudPlatform))) + | project DeviceName, DeviceId, ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture, TimeGenerated + + - Name: GetExposedDevicesByUser + DisplayName: Get exposed devices by UPN and optionally exposure level + Description: For a specific user UPN (aka "user" or "UPN") or a list of users separated by commas, get a list of devices + based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. + DescriptionForModel: For a specific user UPN (aka "user" or "UPN") or a list of users separated by commas, get a list of devices + based on the exposure level specified by the user (aka "Low" or "Medium" or "High") or a list of exposures separated by commas. + The results will be returned as a response to the query containing multiple entries, the user needs to be shown the below details from the response about each device UPN, DeviceName, DeviceId, ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture. + VERY IMPORTANT - Never truncate the list of devices retrieved by launching the KQL query related to this skill. Also if the user wants to search on premises then the value for CloudPlatforms should be blank in the query. + + ExamplePrompts: + - Get all the devices with exposure level High belonging to user john.doe@contoso.com + - Get all the devices with exposure level Medium belonging to user john.doe@contoso.com + - Get all the devices with exposure level Low belonging to user john.doe@contoso.com + + Inputs: + - Name: inputExposureLevel + Description: Provide the exposure level you want to consider - e.g. High, Medium, Low + Required: false + - Name: inputUPN + Description: Provide the user primary name (UPN) + Required: true + Settings: + Target: Defender + Template: |- + let userExposureLevel='{{inputExposureLevel}}'; + let userUPN='{{inputUPN}}'; + IntuneDevices + | where UPN == userUPN + | project DeviceName = tolower(DeviceName) + | join kind=inner ( + DeviceInfo + | where ExposureLevel == userExposureLevel or isempty(userExposureLevel) + | project DeviceName = tolower(DeviceName), DeviceId,ExposureLevel, CloudPlatforms, OSPlatform, OSArchitecture, TimeGenerated + ) on DeviceName + diff --git a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml index 85dbb803..80955111 100644 --- a/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml +++ b/Plugins/Community Based Plugins/Microsoft Sentinel Custom Plugin Scenarios/High Blast Radius User investigation/HighBlastRaidusUserInvestigation.yaml @@ -45,8 +45,7 @@ SkillGroups: TimeGenerated, AccountUPN, AccountDisplayName, - AccountCreationTime, - RiskState, + AccountCreationTime, BlastRadius, GroupMembership, AssignedRoles, diff --git a/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt b/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt index 100cabad..225c854c 100644 --- a/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt +++ b/Technical Workshops/Custom Plugin Calling Webservice/FlaskWebsite/requirements.txt @@ -1 +1 @@ -Flask==3.0.2 \ No newline at end of file +Flask==3.1.3 \ No newline at end of file