-
Notifications
You must be signed in to change notification settings - Fork 781
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
6 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
Binary file added
BIN
+187 KB
...Blob LogicApp Defender for Storage/Figures/system-assigned-managed-identity.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added
BIN
+138 KB
...ation/Delete Blob LogicApp Defender for Storage/Figures/workflow-automation.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions
35
Workflow automation/Delete Blob LogicApp Defender for Storage/ReadMe.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Logic App based on Microsoft Defender for Cloud security alerts | ||
|
||
The ARM template DeleteBlobLogicApp will create a LogicApp that removes malicious files that trigger the security alert "Malicious file uploaded to storage account". | ||
|
||
|
||
## Instructions | ||
1. Deploy the DeleteBlobLogicApp Azure Resource Manager (ARM) template using the Azure portal. | ||
<a href="https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2FAzure%2FMicrosoft-Defender-for-Cloud%2Fmain%2FWorkflow%20automation%2FDelete%20Blob%20LogicApp%20Defender%20for%20Storage%2Ftemplate.json" target="_blank"> | ||
<img src="https://aka.ms/deploytoazurebutton"/> | ||
</a> | ||
|
||
2. Select the Logic App you deployed. | ||
|
||
3. Add a role assignment to the Logic App to allow it to delete blobs from your storage account: | ||
|
||
1. Go to **Identity** in the side menu and select **Azure role assignments**. | ||
![Screenshot that shows how to set up a role assignment for workflow automation to respond to scan results](Figures/system-assigned-managed-identity.png) | ||
2. Add a role assignment in the subscription level with the **Storage Blob Data Contributor** role. | ||
3. Create workflow automation for Microsoft Defender for Cloud alerts: | ||
1. Go to Microsoft Defender for Cloud in the Azure portal. | ||
2. Go to Workflow automation in the side menu. | ||
3. Add a new workflow: In the Alert name contains field, fill in Malicious file uploaded to storage account and choose your Logic app in the Actions section. | ||
4. Select Create. | ||
![Screenshot that shows how to set up workflow automation to respond to scan results](Figures/workflow-automation.png) | ||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
||
|
113 changes: 113 additions & 0 deletions
113
Workflow automation/Delete Blob LogicApp Defender for Storage/template.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
{ | ||
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", | ||
"contentVersion": "1.0.0.0", | ||
"parameters": { | ||
"PlaybookName": { | ||
"defaultValue": "Remove-MalwareBlob", | ||
"type": "string" | ||
} | ||
}, | ||
"variables": { | ||
"mdcalertConnectionName": "[concat('mdcalert-', parameters('PlaybookName'))]" | ||
}, | ||
"resources": [ | ||
{ | ||
"type": "Microsoft.Web/connections", | ||
"apiVersion": "2016-06-01", | ||
"name": "[variables('mdcalertConnectionName')]", | ||
"location": "[resourceGroup().location]", | ||
"properties": { | ||
"displayName": "Microsoft Defender for Cloud alerts", | ||
"customParameterValues": {}, | ||
"api": { | ||
"id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/ascalert')]" | ||
} | ||
} | ||
}, | ||
{ | ||
"type": "Microsoft.Logic/workflows", | ||
"apiVersion": "2017-07-01", | ||
"name": "[parameters('PlaybookName')]", | ||
"location": "[resourceGroup().location]", | ||
"tags": { | ||
"LogicAppsCategory": "security" | ||
}, | ||
"dependsOn": [ | ||
"[resourceId('Microsoft.Web/connections', variables('mdcalertConnectionName'))]" | ||
], | ||
"identity": { | ||
"type": "SystemAssigned" | ||
}, | ||
"properties": { | ||
"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": { | ||
"When_an_Microsoft_Defender_for_Cloud_Alert_is_created_or_triggered": { | ||
"type": "ApiConnectionWebhook", | ||
"inputs": { | ||
"body": { | ||
"callback_url": "@{listCallbackUrl()}" | ||
}, | ||
"host": { | ||
"connection": { | ||
"name": "@parameters('$connections')['mdcalert']['connectionId']" | ||
} | ||
}, | ||
"path": "/Microsoft.Security/Alert/subscribe" | ||
} | ||
} | ||
}, | ||
"actions": { | ||
"Delete_Blob": { | ||
"runAfter": { | ||
"GetBlobEntity": [ | ||
"Succeeded" | ||
] | ||
}, | ||
"type": "Http", | ||
"inputs": { | ||
"authentication": { | ||
"audience": "https://@{triggerBody()?['CompromisedEntity']}.blob.core.windows.net/", | ||
"type": "ManagedServiceIdentity" | ||
}, | ||
"headers": { | ||
"x-ms-version": "2019-07-07" | ||
}, | ||
"method": "DELETE", | ||
"uri": "@{body('GetBlobEntity')[0].Url}" | ||
} | ||
}, | ||
"GetBlobEntity": { | ||
"runAfter": {}, | ||
"type": "Query", | ||
"inputs": { | ||
"from": "@triggerBody()?['Entities']", | ||
"where": "@equals(item().type, 'blob')" | ||
} | ||
} | ||
}, | ||
"outputs": {} | ||
}, | ||
"parameters": { | ||
"$connections": { | ||
"value": { | ||
"mdcalert": { | ||
"connectionId": "[resourceId('Microsoft.Web/connections', variables('mdcalertConnectionName'))]", | ||
"connectionName": "[variables('mdcalertConnectionName')]", | ||
"id": "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/', resourceGroup().location, '/managedApis/ascalert')]" | ||
} | ||
} | ||
} | ||
} | ||
} | ||
} | ||
] | ||
} |
143 changes: 143 additions & 0 deletions
143
...ion/Move Malicious Blob FunctionApp Defender for Storage/MoveMaliciousBlobEventTrigger.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,143 @@ | ||
using Azure.Identity; | ||
using Azure.Messaging.EventGrid; | ||
using Azure.Storage.Blobs; | ||
using Microsoft.Azure.WebJobs; | ||
using Microsoft.Azure.WebJobs.Extensions.EventGrid; | ||
using Microsoft.Extensions.Logging; | ||
using System; | ||
using System.Text.Json; | ||
using System.Threading.Tasks; | ||
|
||
namespace FunctionEventTrigger | ||
{ | ||
public static class MoveMaliciousBlobEventTrigger | ||
{ | ||
private const string AntimalwareScanEventType = "Microsoft.Security.MalwareScanningResult"; | ||
private const string MaliciousVerdict = "Malicious"; | ||
private const string CleanVerdict = "No threats found"; | ||
private const string MalwareContainer = "maliciousfiles"; | ||
private const string CleanContainer = "cleanfiles"; | ||
private const string InterestedContainer = "unscannedcontent"; | ||
|
||
|
||
[FunctionName("MoveMaliciousBlobEventTrigger")] | ||
public static async Task RunAsync([EventGridTrigger] EventGridEvent eventGridEvent, ILogger log) | ||
{ | ||
if (eventGridEvent.EventType != AntimalwareScanEventType) | ||
{ | ||
log.LogInformation("Event type is not an {0} event, event type:{1}", AntimalwareScanEventType, eventGridEvent.EventType); | ||
return; | ||
} | ||
|
||
var storageAccountName = eventGridEvent?.Subject?.Split("/")[^1]; | ||
log.LogInformation("Received new scan result for storage {0}", storageAccountName); | ||
var eventData = JsonDocument.Parse(eventGridEvent.Data).RootElement; | ||
var verdict = eventData.GetProperty("scanResultType").GetString(); | ||
var blobUriString = eventData.GetProperty("blobUri").GetString(); | ||
var blobUri = new Uri(blobUriString); | ||
var blobUriBuilder = new BlobUriBuilder(blobUri); | ||
|
||
// Filter events from interested containers | ||
if (blobUriBuilder.BlobContainerName != InterestedContainer) | ||
{ | ||
log.LogInformation("Event is not from the interested containers, ignoring"); | ||
return; | ||
} | ||
|
||
if (verdict == null || blobUriString == null) | ||
{ | ||
log.LogError("Event data doesn't contain 'verdict' or 'blobUri' fields"); | ||
throw new ArgumentException("Event data doesn't contain 'verdict' or 'blobUri' fields"); | ||
} | ||
|
||
if (verdict == MaliciousVerdict) | ||
{ | ||
log.LogInformation("blob {0} is malicious, moving it to {1} container", blobUri, MalwareContainer); | ||
try | ||
{ | ||
await MoveMaliciousBlobAsync(blobUri, log); | ||
} | ||
catch (Exception e) | ||
{ | ||
log.LogError(e, "Can't move blob to container '{0}'", MalwareContainer); | ||
throw; | ||
} | ||
} | ||
|
||
if (verdict == CleanVerdict) | ||
{ | ||
log.LogInformation("blob {0} is malicious, moving it to {1} container", blobUri, CleanContainer); | ||
try | ||
{ | ||
await MoveCleanBlobAsync(blobUri, log); | ||
} | ||
catch (Exception e) | ||
{ | ||
log.LogError(e, "Can't move blob to container '{0}'", CleanContainer); | ||
throw; | ||
} | ||
} | ||
} | ||
|
||
private static async Task MoveMaliciousBlobAsync(Uri blobUri, ILogger log) | ||
|
||
{ | ||
var blobUriBuilder = new BlobUriBuilder(blobUri); | ||
if (blobUriBuilder.BlobContainerName == MalwareContainer) | ||
{ | ||
log.LogInformation("blob {0} is already in {1} container, skipping", blobUriBuilder.BlobName, MalwareContainer); | ||
return; | ||
} | ||
var destContainerUri = new Uri($"https://{blobUriBuilder.Host}/{MalwareContainer}"); | ||
var defaultAzureCredential = new DefaultAzureCredential(); | ||
var srcBlobClient = new BlobClient(blobUri, defaultAzureCredential); | ||
var destContainerClient = new BlobContainerClient(destContainerUri, defaultAzureCredential); | ||
log.LogInformation("Creating {0} container if it doesn't exist", MalwareContainer); | ||
await destContainerClient.CreateIfNotExistsAsync(); | ||
var destBlobClient = destContainerClient.GetBlobClient(blobUriBuilder.BlobName); | ||
|
||
if (!await srcBlobClient.ExistsAsync()) | ||
{ | ||
log.LogError("blob {0} doesn't exist", blobUri); | ||
return; | ||
} | ||
|
||
log.LogInformation("MoveBlob: Copying blob to {0}", destBlobClient.Uri); | ||
var copyFromUriOperation = await destBlobClient.StartCopyFromUriAsync(srcBlobClient.Uri); | ||
await copyFromUriOperation.WaitForCompletionAsync(); | ||
log.LogInformation("MoveBlob: Deleting source blob {0}", srcBlobClient.Uri); | ||
await srcBlobClient.DeleteAsync(); | ||
log.LogInformation("MoveBlob: blob moved successfully"); | ||
} | ||
|
||
private static async Task MoveCleanBlobAsync(Uri blobUri, ILogger log) | ||
{ | ||
var blobUriBuilder = new BlobUriBuilder(blobUri); | ||
if (blobUriBuilder.BlobContainerName == CleanContainer) | ||
{ | ||
log.LogInformation("blob {0} is already in {1} container, skipping", blobUriBuilder.BlobName, CleanContainer); | ||
return; | ||
} | ||
var destContainerUri = new Uri($"https://{blobUriBuilder.Host}/{CleanContainer}"); | ||
var defaultAzureCredential = new DefaultAzureCredential(); | ||
var srcBlobClient = new BlobClient(blobUri, defaultAzureCredential); | ||
var destContainerClient = new BlobContainerClient(destContainerUri, defaultAzureCredential); | ||
log.LogInformation("Creating {0} container if it doesn't exist", CleanContainer); | ||
await destContainerClient.CreateIfNotExistsAsync(); | ||
var destBlobClient = destContainerClient.GetBlobClient(blobUriBuilder.BlobName); | ||
|
||
if (!await srcBlobClient.ExistsAsync()) | ||
{ | ||
log.LogError("blob {0} doesn't exist", blobUri); | ||
return; | ||
} | ||
|
||
log.LogInformation("MoveBlob: Copying blob to {0}", destBlobClient.Uri); | ||
var copyFromUriOperation = await destBlobClient.StartCopyFromUriAsync(srcBlobClient.Uri); | ||
await copyFromUriOperation.WaitForCompletionAsync(); | ||
log.LogInformation("MoveBlob: Deleting source blob {0}", srcBlobClient.Uri); | ||
await srcBlobClient.DeleteAsync(); | ||
log.LogInformation("MoveBlob: blob moved successfully"); | ||
} | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
Workflow automation/Move Malicious Blob FunctionApp Defender for Storage/ReadMe.md
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
# Function App based on Event Grid events to Move Malicious Blobs | ||
|
||
This FunctionApp moves files that are "Malicious" into the storage blob container "maliciousfiles" (you can modify this in the MoveMaliciousBlobEventTrigger.cs code); it will also move files that have "No threats found" to the storage blob container "cleanfiles". For a step-by-step on how to configure the FunctionApp, follow the instructions of the [NinjaLab](https://github.com/Azure/Microsoft-Defender-for-Cloud/blob/main/Labs/Modules/Module%2019%20-%20Defender%20for%20Storage.md#%EF%B8%8F-exercise-10-function-app-based-on-event-grid-events). | ||
|
||
|
||
## Instructions | ||
A Function App provides high performance with a low latency response time. | ||
|
||
1. Create a [Function App](https://learn.microsoft.com/en-us/azure/azure-functions/functions-overview?pivots=programming-language-csharp) in the same resource group as your protected storage account. | ||
|
||
1. Add a role assignment for the Function app identity. | ||
|
||
1. Go to **Identity** in the side menu, make sure the **System assigned** identity status is **On**, and select **Azure role assignments**. | ||
|
||
1. Add a role assignment in the subscription or storage account levels with the **Storage Blob Data Contributor** role. | ||
|
||
1. Consume Event Grid events and connect an Azure Function as the endpoint type. | ||
|
||
1. When writing the Azure Function code, you can use our premade function sample - [MoveMaliciousBlobEventTrigger](/MoveMaliciousBlobEventTrigger.cs), or [write your own code](https://learn.microsoft.com/en-us/azure/storage/blobs/storage-blob-copy) to copy the blob elsewhere, then delete it from the source. | ||
|
||
For each scan result, an event is sent according to the following schema. |