├── samplefile.md ├── DFSNotifierResourcesDeployment ├── Lab-resourcedeployment │ ├── ResourceDeployment.md │ └── ResourceDeploymentGuidance.docx ├── modules │ ├── storage │ │ ├── blobcontainers.bicep │ │ └── storageaccount-prod.bicep │ ├── configurationstore │ │ ├── setSecret.bicep │ │ ├── keyVaultAddAccessPolicy.bicep │ │ ├── appConfiguration.bicep │ │ └── keyVault.bicep │ ├── webapp │ │ ├── appConfiguration.bicep │ │ ├── appServicePlan.bicep │ │ ├── signalrService.bicep │ │ ├── functionApp.bicep │ │ └── functionAppSettingsWithStorage.bicep │ └── monitoring │ │ ├── appInsights.bicep │ │ └── loganalyticsworkspace.bicep ├── deployabletemplates │ ├── commonResources │ │ ├── parameters │ │ │ └── common-westus2.parameters.json │ │ └── common.bicep │ └── malwarescanner │ │ ├── parameters │ │ └── malwarescanner-easus.parameters.json │ │ └── malwarescanner.bicep ├── azure-pipelines-malwarescanner.yml └── ResourceDeployment.md ├── signalr-wrapper ├── src │ ├── react-app-env.d.ts │ ├── index.ts │ ├── setupTests.ts │ ├── components │ │ └── SignalRWrapper │ │ │ ├── SignalRWrapper.types.ts │ │ │ └── SignalRWrapper.tsx │ └── reportWebVitals.ts ├── .gitignore ├── tsconfig.json ├── package.json ├── public │ └── index.html └── README.md ├── DefenderFileScanNotifierFunction ├── DefenderFileScanNotifierKey.snk ├── DefenderFileScanNotifier.Function │ ├── Properties │ │ ├── serviceDependencies.json │ │ ├── serviceDependencies.local.json │ │ └── launchSettings.json │ ├── host.json │ ├── DefenderFileScanNotifier.Function.csproj │ ├── FileUploadHub.cs │ ├── Startup.cs │ ├── StartupConstants.cs │ ├── MalwareScannerHost.cs │ └── .gitignore ├── DefenderFileScanNotifier.Function.Core │ ├── ValidationHelper │ │ ├── IValidation.cs │ │ └── GuardHelper.cs │ ├── Logger │ │ ├── IApplicationInsightsLogger.cs │ │ └── ApplicationInsightsLogger.cs │ ├── DefenderFileScanNotifier.Function.Core.csproj │ ├── Services │ │ ├── IMalwareScannerManager.cs │ │ ├── IBlobClientRepository.cs │ │ ├── BlobClientRepository.cs │ │ └── MalwareScannerManager.cs │ ├── CoreConstants │ │ ├── BlobConfigOptions.cs │ │ ├── MalwareScannerConfigs.cs │ │ ├── MalwareScannerContainerMapper.cs │ │ └── CoreConstants.cs │ └── ServiceCollectionExtensions.cs └── DefenderFileScanNotifier.sln ├── CODE_OF_CONDUCT.md ├── LICENSE ├── SUPPORT.md ├── SECURITY.md ├── README.md └── .gitignore /samplefile.md: -------------------------------------------------------------------------------- 1 | This is sample for test -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/Lab-resourcedeployment/ResourceDeployment.md: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /signalr-wrapper/src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /signalr-wrapper/src/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./components/SignalRWrapper/SignalRWrapper"; -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifierKey.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/DefenderFileScanNotifier/HEAD/DefenderFileScanNotifierFunction/DefenderFileScanNotifierKey.snk -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/Properties/serviceDependencies.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "appInsights": { 4 | "type": "appInsights" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/Properties/serviceDependencies.local.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "appInsights1": { 4 | "type": "appInsights.sdk" 5 | } 6 | } 7 | } -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/Lab-resourcedeployment/ResourceDeploymentGuidance.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/microsoft/DefenderFileScanNotifier/HEAD/DFSNotifierResourcesDeployment/Lab-resourcedeployment/ResourceDeploymentGuidance.docx -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "DefenderFileScanNotifier": { 4 | "commandName": "Project", 5 | "commandLineArgs": "--port 7159", 6 | "launchBrowser": false 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /signalr-wrapper/src/setupTests.ts: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/storage/blobcontainers.bicep: -------------------------------------------------------------------------------- 1 | param storageAccountName string 2 | param blobContainers array 3 | 4 | resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2021-04-01' = [for container in blobContainers: { 5 | name: '${storageAccountName}/default/${container}' 6 | }] 7 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/host.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0", 3 | "logging": { 4 | "applicationInsights": { 5 | "samplingSettings": { 6 | "isEnabled": true, 7 | "excludedTypes": "Request" 8 | } 9 | } 10 | } 11 | } -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/configurationstore/setSecret.bicep: -------------------------------------------------------------------------------- 1 | param keyVaultName string 2 | param secretName string 3 | 4 | @secure() 5 | param secretValue string 6 | 7 | resource secret 'Microsoft.KeyVault/vaults/secrets@2021-10-01' = { 8 | name: '${keyVaultName}/${secretName}' 9 | properties: { 10 | value: secretValue 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /signalr-wrapper/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | -------------------------------------------------------------------------------- /signalr-wrapper/src/components/SignalRWrapper/SignalRWrapper.types.ts: -------------------------------------------------------------------------------- 1 | export type ISignalRWrapperProps = { 2 | connectionData: ISignalRConnectionData; 3 | eventName: string; 4 | eventHandlerCallback: EventCallback; 5 | }; 6 | 7 | export interface ISignalRConnectionData { 8 | userAccessToken: string; 9 | negotiateEndpoint: string; 10 | signalRUserId: string; 11 | negotiateCustomHeaders: Record; 12 | } 13 | 14 | type EventCallback = (data: any) => void; 15 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Microsoft Open Source Code of Conduct 2 | 3 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 4 | 5 | Resources: 6 | 7 | - [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) 8 | - [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) 9 | - Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns 10 | -------------------------------------------------------------------------------- /signalr-wrapper/src/reportWebVitals.ts: -------------------------------------------------------------------------------- 1 | import { ReportHandler } from 'web-vitals'; 2 | 3 | const reportWebVitals = (onPerfEntry?: ReportHandler) => { 4 | if (onPerfEntry && onPerfEntry instanceof Function) { 5 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 6 | getCLS(onPerfEntry); 7 | getFID(onPerfEntry); 8 | getFCP(onPerfEntry); 9 | getLCP(onPerfEntry); 10 | getTTFB(onPerfEntry); 11 | }); 12 | } 13 | }; 14 | 15 | export default reportWebVitals; 16 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/webapp/appConfiguration.bicep: -------------------------------------------------------------------------------- 1 | param appServiceName string 2 | param appSettings array 3 | param currentAppSettings object 4 | 5 | var appSettingsDictionary = reduce(appSettings, {}, (cur, next) => union(cur, { 6 | '${next.key}': next.value 7 | })) 8 | 9 | resource appService 'Microsoft.Web/sites@2021-03-01' existing = { 10 | name: appServiceName 11 | } 12 | 13 | resource appServicesAppsetting 'Microsoft.Web/sites/config@2021-03-01' = { 14 | name: 'appsettings' 15 | kind: 'string' 16 | parent: appService 17 | properties: union(currentAppSettings, appSettingsDictionary) 18 | } 19 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/webapp/appServicePlan.bicep: -------------------------------------------------------------------------------- 1 | param appServicePlanName string 2 | param location string 3 | param tags object 4 | param skuName string 5 | param skuTier string 6 | 7 | var appServicePlanProperties = (location != 'southeastasia') ? {} : { 8 | zoneRedundant: false 9 | } 10 | 11 | resource appServicePlan 'Microsoft.Web/serverfarms@2021-03-01' = { 12 | name: appServicePlanName 13 | location: location 14 | tags: tags 15 | sku: { 16 | name: skuName 17 | tier: skuTier 18 | } 19 | properties: appServicePlanProperties 20 | } 21 | 22 | output appServicePlanId string = appServicePlan.id 23 | -------------------------------------------------------------------------------- /signalr-wrapper/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": [ 5 | "dom", 6 | "dom.iterable", 7 | "esnext" 8 | ], 9 | "allowJs": true, 10 | "skipLibCheck": true, 11 | "esModuleInterop": true, 12 | "allowSyntheticDefaultImports": true, 13 | "strict": true, 14 | "forceConsistentCasingInFileNames": true, 15 | "noFallthroughCasesInSwitch": true, 16 | "module": "esnext", 17 | "moduleResolution": "node", 18 | "resolveJsonModule": true, 19 | "isolatedModules": true, 20 | "noEmit": true, 21 | "jsx": "react-jsx" 22 | }, 23 | "include": [ 24 | "src" 25 | ] 26 | } 27 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/configurationstore/keyVaultAddAccessPolicy.bicep: -------------------------------------------------------------------------------- 1 | param keyVaultName string 2 | param secretPermissionList array 3 | param certificatePermissionList array 4 | param objectIds array 5 | 6 | var tenantId = subscription().tenantId 7 | 8 | resource appendAccessPolicy 'Microsoft.KeyVault/vaults/accessPolicies@2019-09-01' = { 9 | name: '${keyVaultName}/add' 10 | properties: { 11 | accessPolicies: [for object in objectIds: { 12 | objectId: object.key 13 | tenantId: tenantId 14 | permissions: { 15 | secrets: secretPermissionList 16 | certificates: certificatePermissionList 17 | } 18 | }] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/ValidationHelper/IValidation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DefenderFileScanNotifier.Function.Core.ValidationHelper 8 | { 9 | /// 10 | /// The provides mechanism to validate the object. 11 | /// 12 | public interface IValidation 13 | { 14 | /// 15 | /// Validates this instance. 16 | /// 17 | /// true if the instance is valid; otherwise false. 18 | bool Validate(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/Logger/IApplicationInsightsLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace DefenderFileScanNotifier.Function.Core.Logger 10 | { 11 | public interface IApplicationInsightsLogger 12 | { 13 | void TraceInformation(string message); 14 | 15 | void LogWarning(string message); 16 | 17 | void TraceError(string message); 18 | 19 | void WriteException(Exception ex, Dictionary properties = null); 20 | 21 | void WriteCustomEvent(string eventName, Dictionary properties = null, Dictionary metrics = null); 22 | 23 | void Flush(); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/DefenderFileScanNotifier.Function.Core.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | True 8 | ..\..\DefenderFileScanNotifierKey.snk 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/Services/IMalwareScannerManager.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function.Core.Services 6 | { 7 | using Azure.Messaging.EventGrid; 8 | using Microsoft.Azure.WebJobs; 9 | using Microsoft.Azure.WebJobs.Extensions.SignalRService; 10 | 11 | public interface IMalwareScannerManager 12 | { 13 | /// 14 | /// Move Blob item from malware scanner container to main container. 15 | /// 16 | /// The event grid event. 17 | /// The Task. 18 | /// Retruns if event grid doesn't have proper blob URL. 19 | Task MoveBlobItemToMainContainerAsync(EventGridEvent eventGridEvent, IAsyncCollector signalRMessages); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/CoreConstants/BlobConfigOptions.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | namespace DefenderFileScanNotifier.Function.Core.CoreConstants 5 | { 6 | /// 7 | /// The BLOB Configuration. 8 | /// 9 | public class BlobConfigOptions 10 | { 11 | /// 12 | /// Gets or sets retry delay in seconds. 13 | /// 14 | public double RetryDelayInSeconds { get; set; } = 2; 15 | 16 | /// 17 | /// Gets or sets max retries. 18 | /// 19 | public int MaxRetries { get; set; } = 5; 20 | 21 | /// 22 | /// Gets or sets max retry delay. 23 | /// 24 | public double RetryMaxDelayInSeconds { get; set; } = 10; 25 | 26 | /// 27 | /// Gets or sets network time out in seconds 28 | /// 29 | public double RetryNetworkTimeoutInSeconds { get; set; } = 100; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/configurationstore/appConfiguration.bicep: -------------------------------------------------------------------------------- 1 | param featureName string 2 | param subFeatureName string 3 | @allowed([ 4 | 'westus2' 5 | 'southeastasia' 6 | 'westus' 7 | 'eastus' 8 | ]) 9 | param deploymentLocation string 10 | @allowed([ 11 | 'ppe' 12 | 'prod' 13 | 'perf' 14 | ]) 15 | param environment string 16 | param tagsObject object 17 | param shortLocation string = '' 18 | 19 | var appConfigurationName = environment != 'prod' ? '${featureName}-${subFeatureName}-configuration-${empty(shortLocation) ? deploymentLocation : shortLocation}-${environment}' : '${featureName}-${subFeatureName}-configuration-${empty(shortLocation) ? deploymentLocation : shortLocation}' 20 | 21 | resource appConfiguration 'Microsoft.AppConfiguration/configurationStores@2020-06-01' = { 22 | name: appConfigurationName 23 | location: deploymentLocation 24 | properties: { 25 | } 26 | tags: tagsObject 27 | identity: { 28 | type: 'SystemAssigned' 29 | } 30 | sku: { 31 | name: 'standard' 32 | } 33 | } 34 | 35 | output appConfigurationName string = appConfiguration.name 36 | output appConfigPrincipalId string = appConfiguration.identity.principalId 37 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Microsoft Corporation. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE 22 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/ServiceCollectionExtensions.cs: -------------------------------------------------------------------------------- 1 | 2 | 3 | namespace DefenderFileScanNotifier.Function.Core.Services 4 | { 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | 8 | 9 | using System.Diagnostics.CodeAnalysis; 10 | 11 | /// 12 | /// The class helps to create injected dependencies. 13 | /// 14 | [ExcludeFromCodeCoverage] 15 | public static class ServiceCollectionExtensions 16 | { 17 | 18 | /// 19 | /// Adds core dependencies. 20 | /// 21 | /// The instance for . 22 | /// The instance for . 23 | public static void AddCoreDependencies(this IServiceCollection services, [NotNull] IConfiguration configuration) 24 | { 25 | _ = services.AddSingleton(); 26 | _ = services.AddSingleton(); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/CoreConstants/MalwareScannerConfigs.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function.Core.CoreConstants 6 | { 7 | /// 8 | /// The Malware scanner configuration. 9 | /// 10 | public class MalwareScannerConfigs 11 | { 12 | /// 13 | /// Gets or sets antimalware scan result. 14 | /// 15 | public string? AntimalwareScanSuccessIdentifier { get; set; } 16 | 17 | /// 18 | /// Gets or sets anti malware scan event types. 19 | /// 20 | public string? AntimalwareScanEventTypes { get; set; } 21 | 22 | /// 23 | /// Gets or sets container config mapping information. 24 | /// 25 | public string? ScannerContainerMapping { get; set; } 26 | 27 | /// 28 | /// Gets or sets container config mapping information. 29 | /// 30 | public double SourceBlobReadSasTokenPeriodInMinutes { get; set; } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /SUPPORT.md: -------------------------------------------------------------------------------- 1 | # TODO: The maintainer of this repo has not yet edited this file 2 | 3 | **REPO OWNER**: Do you want Customer Service & Support (CSS) support for this product/project? 4 | 5 | - **No CSS support:** Fill out this template with information about how to file issues and get help. 6 | - **Yes CSS support:** Fill out an intake form at [aka.ms/onboardsupport](https://aka.ms/onboardsupport). CSS will work with/help you to determine next steps. 7 | - **Not sure?** Fill out an intake as though the answer were "Yes". CSS will help you decide. 8 | 9 | *Then remove this first heading from this SUPPORT.MD file before publishing your repo.* 10 | 11 | # Support 12 | 13 | ## How to file issues and get help 14 | 15 | This project uses GitHub Issues to track bugs and feature requests. Please search the existing 16 | issues before filing new issues to avoid duplicates. For new issues, file your bug or 17 | feature request as a new Issue. 18 | 19 | For help and questions about using this project, please **REPO MAINTAINER: INSERT INSTRUCTIONS HERE 20 | FOR HOW TO ENGAGE REPO OWNERS OR COMMUNITY FOR HELP. COULD BE A STACK OVERFLOW TAG OR OTHER 21 | CHANNEL. WHERE WILL YOU HELP PEOPLE?**. 22 | 23 | ## Microsoft Support Policy 24 | 25 | Support for this **PROJECT or PRODUCT** is limited to the resources listed above. 26 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/Services/IBlobClientRepository.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function.Core.Services 6 | { 7 | using DefenderFileScanNotifier.Function.Core.CoreConstants; 8 | 9 | /// 10 | /// The BLOB client repository. 11 | /// 12 | public interface IBlobClientRepository 13 | { 14 | /// 15 | /// The BLOB copy operation. 16 | /// 17 | /// The scanner container properties. 18 | /// The source container. 19 | /// The BLOB name. 20 | /// The event id. 21 | /// The SAS Token validate period in minutes. 22 | /// The source BLOB uri. 23 | /// The Task. 24 | Task StartCopyFromUriAsync(MalwareScannerContainerMapper malwareScannerContainerMapper, string sourceContainer, string blobName, string eventId, double sasTokenPeriodInMinutes, Uri sourceBlobUri); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /signalr-wrapper/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "signalr-wrapper", 3 | "version": "0.1.0", 4 | "main": "dist/index.js", 5 | "publishConfig": { 6 | "access": "restricted" 7 | }, 8 | "private": true, 9 | "dependencies": { 10 | "@microsoft/signalr": "^7.0.11", 11 | "@testing-library/jest-dom": "^5.14.1", 12 | "@testing-library/react": "^13.0.0", 13 | "@testing-library/user-event": "^13.2.1", 14 | "@types/jest": "^27.0.1", 15 | "@types/node": "^16.7.13", 16 | "@types/react": "^18.0.0", 17 | "@types/react-dom": "^18.0.0", 18 | "axios": "^1.5.0", 19 | "react-scripts": "5.0.1", 20 | "typescript": "^4.4.2", 21 | "web-vitals": "^2.1.0" 22 | }, 23 | "peerDependencies": { 24 | "react": "^18.2.0", 25 | "react-dom": "^18.2.0" 26 | }, 27 | "devDependencies": { 28 | "react": "^18.2.0", 29 | "react-dom": "^18.2.0" 30 | }, 31 | "scripts": { 32 | "start": "react-scripts start", 33 | "build": "react-scripts build", 34 | "test": "react-scripts test", 35 | "eject": "react-scripts eject" 36 | }, 37 | "eslintConfig": { 38 | "extends": [ 39 | "react-app", 40 | "react-app/jest" 41 | ] 42 | }, 43 | "browserslist": { 44 | "production": [ 45 | ">0.2%", 46 | "not dead", 47 | "not op_mini all" 48 | ], 49 | "development": [ 50 | "last 1 chrome version", 51 | "last 1 firefox version", 52 | "last 1 safari version" 53 | ] 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/DefenderFileScanNotifier.Function.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | net6.0 4 | v4 5 | True 6 | ..\..\DefenderFileScanNotifierKey.snk 7 | <_FunctionsSkipCleanOutput>true 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | PreserveNewest 23 | 24 | 25 | PreserveNewest 26 | Never 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/monitoring/appInsights.bicep: -------------------------------------------------------------------------------- 1 | param subFeatureName string 2 | param featureName string 3 | @allowed([ 4 | 'westus' 5 | 'westus2' 6 | 'eastus' 7 | 'southeastasia' 8 | ]) 9 | param deploymentLocation string 10 | @allowed([ 11 | 'ppe' 12 | 'prod' 13 | 'perf' 14 | ]) 15 | param environment string 16 | 17 | //'services' value is considered for only SEA region. 18 | @allowed([ 19 | '' 20 | 'ui' 21 | 'service' 22 | 'services' 23 | 'svc' 24 | 'svcs' 25 | ]) 26 | param applicationType string = '' 27 | param tagsObject object 28 | param workspaceRescId string = '' 29 | param preferredAppInsightsName string='' 30 | param shortLocation string = '' 31 | var appInsightsName = environment != 'prod' ? '${featureName}-${subFeatureName}${applicationType}-ai-${empty(shortLocation) ? deploymentLocation : shortLocation}-${environment}' : '${featureName}-${subFeatureName}${applicationType}-ai-${empty(shortLocation) ? deploymentLocation : shortLocation}' 32 | 33 | resource appInsightsComponent 'Microsoft.Insights/components@2020-02-02' = { 34 | name: !empty(preferredAppInsightsName) ? preferredAppInsightsName : appInsightsName 35 | location: deploymentLocation 36 | kind: 'web' 37 | properties: { 38 | Application_Type: 'web' 39 | RetentionInDays: 90 40 | publicNetworkAccessForIngestion: 'Enabled' 41 | publicNetworkAccessForQuery: 'Enabled' 42 | WorkspaceResourceId: !empty(workspaceRescId) ? workspaceRescId : json('null') 43 | } 44 | tags: tagsObject 45 | } 46 | 47 | output appInsightsName string = appInsightsComponent.name 48 | output appInsightInsKey string = appInsightsComponent.properties.InstrumentationKey 49 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/monitoring/loganalyticsworkspace.bicep: -------------------------------------------------------------------------------- 1 | param subFeatureName string 2 | param featureName string 3 | @allowed([ 4 | 'westus' 5 | 'westus2' 6 | 'eastus' 7 | 'southeastasia' 8 | ]) 9 | param deploymentLocation string 10 | 11 | @allowed([ 12 | 'ppe' 13 | 'prod' 14 | 'perf' 15 | ]) 16 | param environment string 17 | 18 | @allowed([ 19 | '' 20 | 'ui' 21 | 'service' 22 | ]) 23 | param applicationType string 24 | param tagsObject object 25 | 26 | @allowed([ 27 | 'CapacityReservation' 28 | 'Free' 29 | 'LACluster' 30 | 'PerGB2018' 31 | 'PerNode' 32 | 'Premium' 33 | 'Standalone' 34 | 'Standard' 35 | ]) 36 | param logAnalyticsWorkspaceSKU string 37 | param retentionInDays int 38 | 39 | var logAnalyticsName = environment != 'prod' ? '${featureName}-${subFeatureName}${applicationType}-law-${deploymentLocation}-${environment}' : '${featureName}-${subFeatureName}${applicationType}-law-${deploymentLocation}' 40 | 41 | resource logAnalyticsWorkspace 'Microsoft.OperationalInsights/workspaces@2021-06-01' = { 42 | name: logAnalyticsName 43 | location: deploymentLocation 44 | tags: tagsObject 45 | properties: { 46 | features: { 47 | enableLogAccessUsingOnlyResourcePermissions: true 48 | } 49 | publicNetworkAccessForIngestion: 'Enabled' 50 | publicNetworkAccessForQuery: 'Enabled' 51 | retentionInDays: retentionInDays 52 | sku: { 53 | name: logAnalyticsWorkspaceSKU 54 | } 55 | workspaceCapping: { 56 | dailyQuotaGb: -1 57 | } 58 | } 59 | } 60 | 61 | output logAnalyticsWorkspaceId string = logAnalyticsWorkspace.id 62 | output logAnalyticsWorkspaceName string = logAnalyticsWorkspace.name 63 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/CoreConstants/MalwareScannerContainerMapper.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function.Core.CoreConstants 6 | { 7 | /// 8 | /// The malware scanner container mapping information. 9 | /// 10 | public class MalwareScannerContainerMapper 11 | { 12 | /// 13 | /// The source blob conatiner name. 14 | /// 15 | public string? sourceBlobContainerName { get; set; } 16 | 17 | /// 18 | /// The destination blob container name. 19 | /// 20 | public string? destinationBlobContainerName { get; set; } 21 | 22 | /// 23 | /// The source storage connection string application configuration name. 24 | /// 25 | public string? sourceStorageConstringAppConfigName { get; set; } 26 | 27 | /// 28 | /// The destination storage connection string application configuration name. 29 | /// 30 | public string? destinationStorageConstringAppConfigName { get; set; } 31 | 32 | /// 33 | /// The destination storage folder structure. 34 | /// 35 | public string? destinationFolderstructure { get; set; } 36 | 37 | /// 38 | /// The destination path is replaced by tags based on this identifier. 39 | /// 40 | public bool isTagEnabled { get;set; } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/webapp/signalrService.bicep: -------------------------------------------------------------------------------- 1 | 2 | param name string 3 | param subFeatureName string 4 | param featureName string 5 | param signalrkvconnectionSecretName string='' 6 | param location string 7 | @description('The number of SignalR Unit.') 8 | @allowed([ 9 | 1 10 | 2 11 | 5 12 | 10 13 | 20 14 | 50 15 | 100 16 | ]) 17 | param capacity int = 1 18 | 19 | @description('The pricing tier of the SignalR resource.') 20 | @allowed([ 21 | 'Free_F1' 22 | 'Standard_S1' 23 | 'Premium_P1' 24 | ]) 25 | param pricingTier string = 'Free_F1' 26 | 27 | @allowed([ 28 | 'Default' 29 | 'Serverless' 30 | 'Classic' 31 | ]) 32 | param serviceMode string = 'Serverless' 33 | 34 | param tags object 35 | 36 | param keyVaultName string='' 37 | 38 | var kvSignalSecretName=empty(signalrkvconnectionSecretName)?'${featureName}-${subFeatureName}-azuresignalrconnectionstring':signalrkvconnectionSecretName 39 | resource signalR 'Microsoft.SignalRService/signalR@2022-02-01' = { 40 | name: name 41 | location: location 42 | sku: { 43 | capacity: capacity 44 | name: pricingTier 45 | } 46 | tags:tags 47 | kind: 'SignalR' 48 | identity: { 49 | type: 'SystemAssigned' 50 | } 51 | properties: { 52 | features: [ { 53 | flag: 'ServiceMode' 54 | value: serviceMode 55 | }] 56 | } 57 | } 58 | 59 | module setFunctionAzureFileConnStringToKV '../../modules/configurationstore/setSecret.bicep' = if (!empty(keyVaultName)) { 60 | name: 'dfsn-${subFeatureName}-azuresignalrconnectionstring1' 61 | scope: resourceGroup() 62 | params: { 63 | keyVaultName: keyVaultName 64 | secretName: kvSignalSecretName 65 | secretValue: signalR.listKeys().primaryConnectionString 66 | } 67 | dependsOn:[signalR] 68 | } 69 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/deployabletemplates/commonResources/parameters/common-westus2.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "featureName": { 6 | "value": "dfn" 7 | }, 8 | "subFeatureNameForKeyVault": { 9 | "value": "kv" 10 | }, 11 | "kvKeyPermissionList": { 12 | "value": [ 13 | "get", 14 | "list", 15 | "update", 16 | "create" 17 | ] 18 | }, 19 | "kvSecretPermissionList": { 20 | "value": [ 21 | "get", 22 | "list", 23 | "set" 24 | ] 25 | }, 26 | "kvCertificatePermissionList": { 27 | "value": [ 28 | "get", 29 | "list" 30 | ] 31 | }, 32 | "keyVaultCreateMode": { 33 | "value": "default" 34 | }, 35 | "deploymentLocation": { 36 | "value": "westus2" 37 | }, 38 | "environment": { 39 | "value": "prod" 40 | }, 41 | "servicesKeyPermissionList": { 42 | "value": [ 43 | "get", 44 | "list" 45 | ] 46 | }, 47 | "servicesSecretPermissionList": { 48 | "value": [ 49 | "get", 50 | "list" 51 | ] 52 | }, 53 | "servicesCertificatePermissionList": { 54 | "value": [ 55 | "get", 56 | "list" 57 | ] 58 | }, 59 | "featureNameForAppConfig": { 60 | "value": "common" 61 | }, 62 | "applicationType": { 63 | "value": "service" 64 | }, 65 | "subFeatureName": { 66 | "value": "common" 67 | }, 68 | "logAnalyticsWorkspaceSKU": { 69 | "value": "PerGB2018" 70 | }, 71 | "logAnalyticsRetentionInDays": { 72 | "value": 90 73 | }, 74 | "shortLocation": { 75 | "value": "westus2" 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33530.505 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DefenderFileScanNotifier.Function", "DefenderFileScanNotifier.Function\DefenderFileScanNotifier.Function.csproj", "{6D04C5AA-F41A-4F27-971B-680558171FF8}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DefenderFileScanNotifier.Function.Core", "DefenderFileScanNotifier.Function.Core\DefenderFileScanNotifier.Function.Core.csproj", "{DE076A4F-D9F4-454C-A35C-AC8413CDF779}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {6D04C5AA-F41A-4F27-971B-680558171FF8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {6D04C5AA-F41A-4F27-971B-680558171FF8}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {6D04C5AA-F41A-4F27-971B-680558171FF8}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {6D04C5AA-F41A-4F27-971B-680558171FF8}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {DE076A4F-D9F4-454C-A35C-AC8413CDF779}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {DE076A4F-D9F4-454C-A35C-AC8413CDF779}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {DE076A4F-D9F4-454C-A35C-AC8413CDF779}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {DE076A4F-D9F4-454C-A35C-AC8413CDF779}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {B2948EA0-A37E-4A6D-BEBC-5105744D2158} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/Logger/ApplicationInsightsLogger.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.DataContracts; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | using Microsoft.ApplicationInsights; 4 | 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using System.Threading.Tasks; 10 | 11 | namespace DefenderFileScanNotifier.Function.Core.Logger 12 | { 13 | public class ApplicationInsightsLogger : IApplicationInsightsLogger 14 | { 15 | private TelemetryClient telemetryClient; 16 | 17 | public ApplicationInsightsLogger(string instrumentationKey) 18 | { 19 | var config = new TelemetryConfiguration(instrumentationKey); 20 | telemetryClient = new TelemetryClient(config); 21 | } 22 | 23 | public void TraceInformation(string message) 24 | { 25 | telemetryClient.TrackTrace(message, SeverityLevel.Information); 26 | } 27 | 28 | public void LogWarning(string message) 29 | { 30 | telemetryClient.TrackTrace(message, SeverityLevel.Warning); 31 | } 32 | 33 | public void TraceError(string message) 34 | { 35 | telemetryClient.TrackTrace(message, SeverityLevel.Error); 36 | } 37 | 38 | public void WriteException(Exception ex, Dictionary properties = null) 39 | { 40 | telemetryClient.TrackException(ex, properties); 41 | } 42 | 43 | public void WriteCustomEvent(string eventName, Dictionary properties = null, Dictionary metrics = null) 44 | { 45 | telemetryClient.TrackEvent(eventName, properties,metrics); 46 | } 47 | 48 | public void Flush() 49 | { 50 | telemetryClient.Flush(); 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /signalr-wrapper/public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /signalr-wrapper/src/components/SignalRWrapper/SignalRWrapper.tsx: -------------------------------------------------------------------------------- 1 | import * as signalR from "@microsoft/signalr"; 2 | 3 | import React, { useEffect } from "react"; 4 | 5 | import { ISignalRWrapperProps } from "./SignalRWrapper.types"; 6 | import axios from "axios"; 7 | 8 | export const SignalRWrapper: React.FC = ( 9 | props: ISignalRWrapperProps 10 | ) => { 11 | const { connectionData, eventName, eventHandlerCallback, } = props; 12 | let connection: signalR.HubConnection | null = null; 13 | 14 | const startConnection = () => { 15 | axios 16 | .get(connectionData.negotiateEndpoint, { 17 | headers: { 18 | Authorization: "Bearer " + connectionData.userAccessToken, 19 | "x-ms-signalr-user-id": connectionData.signalRUserId, 20 | ...connectionData.negotiateCustomHeaders 21 | }, 22 | }) 23 | .then((response) => { 24 | const negotiateResponse = response.data; 25 | connection = new signalR.HubConnectionBuilder() 26 | .withUrl(negotiateResponse.url, { 27 | accessTokenFactory: () => negotiateResponse.accessToken, 28 | }) 29 | .configureLogging(signalR.LogLevel.Information) 30 | .build(); 31 | connection 32 | .start() 33 | .then(() => { 34 | console.log("SignalR connection established."); 35 | connection?.on(eventName, eventHandlerCallback); 36 | }) 37 | .catch((error) => { 38 | console.error("Error starting SignalR connection:", error); 39 | }); 40 | }); 41 | }; 42 | 43 | const stopConnection = () => { 44 | if (connection) { 45 | connection 46 | .stop() 47 | .then(() => { 48 | console.log("SignalR connection stopped."); 49 | }) 50 | .catch((error) => { 51 | console.error("Error stopping SignalR connection:", error); 52 | }); 53 | } 54 | }; 55 | 56 | useEffect(() => { 57 | startConnection(); 58 | return () => { 59 | stopConnection(); 60 | }; 61 | }, []); 62 | 63 | return null; 64 | }; 65 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/azure-pipelines-malwarescanner.yml: -------------------------------------------------------------------------------- 1 | name: Defender File Scanner Resources 2 | trigger: 3 | branches: 4 | include: 5 | - main 6 | paths: 7 | include: 8 | - deployabletemplates/malwarescanner 9 | exclude: 10 | - azure-pipelines-*.yml 11 | variables: 12 | pool: 13 | vmImage: windows-latest 14 | stages: 15 | - stage: NonProd_MalwareScannerResourcesDeployment 16 | jobs: 17 | - deployment: NonProd_MalwareScannerResourcesDeployment 18 | displayName: "Deploy Malware Scanner PPE Resources to Azure" 19 | environment: $(PreProd_CommonEnvironment) 20 | strategy: 21 | runOnce: 22 | deploy: 23 | steps: 24 | - checkout: self 25 | clean: true 26 | - task: AzureCLI@2 27 | displayName: "deploy common bicep template" 28 | inputs: 29 | azureSubscription: $(PreProd_AzureSubscriptionName) 30 | scriptType: "pscore" 31 | scriptLocation: "inlineScript" 32 | inlineScript: | 33 | az deployment group create ` 34 | --template-file $(Build.SourcesDirectory)/deployabletemplates/commonResources/common.bicep ` 35 | --parameters $(Build.SourcesDirectory)/deployabletemplates/commonResources/parameters/common-westus2.parameters.json ` 36 | --resource-group $(PreProd_CommonResourceGroupName) 37 | - task: AzureCLI@2 38 | displayName: "deploy bicep template" 39 | inputs: 40 | azureSubscription: $(PreProd_AzureSubscriptionName) 41 | scriptType: "pscore" 42 | scriptLocation: "inlineScript" 43 | inlineScript: | 44 | az deployment group create ` 45 | --template-file $(Build.SourcesDirectory)/deployabletemplates/malwarescanner/malwarescanner-ppe.bicep ` 46 | --parameters $(Build.SourcesDirectory)/deployabletemplates/malwarescanner/parameters/malwarescanner-ppe.parameters.json ` 47 | --resource-group $(PreProd_CommonResourceGroupName) -------------------------------------------------------------------------------- /signalr-wrapper/README.md: -------------------------------------------------------------------------------- 1 | # Getting Started with Create React App 2 | 3 | This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app). 4 | 5 | ## Available Scripts 6 | 7 | In the project directory, you can run: 8 | 9 | ### `yarn start` 10 | 11 | Runs the app in the development mode.\ 12 | Open [http://localhost:3000](http://localhost:3000) to view it in the browser. 13 | 14 | The page will reload if you make edits.\ 15 | You will also see any lint errors in the console. 16 | 17 | ### `yarn test` 18 | 19 | Launches the test runner in the interactive watch mode.\ 20 | See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information. 21 | 22 | ### `yarn build` 23 | 24 | Builds the app for production to the `build` folder.\ 25 | It correctly bundles React in production mode and optimizes the build for the best performance. 26 | 27 | The build is minified and the filenames include the hashes.\ 28 | Your app is ready to be deployed! 29 | 30 | See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information. 31 | 32 | ### `yarn eject` 33 | 34 | **Note: this is a one-way operation. Once you `eject`, you can’t go back!** 35 | 36 | If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project. 37 | 38 | Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own. 39 | 40 | You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it. 41 | 42 | ## Learn More 43 | 44 | You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started). 45 | 46 | To learn React, check out the [React documentation](https://reactjs.org/). 47 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/deployabletemplates/malwarescanner/parameters/malwarescanner-easus.parameters.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", 3 | "contentVersion": "1.0.0.0", 4 | "parameters": { 5 | "featureName": { 6 | "value": "dfn" 7 | }, 8 | "environment": { 9 | "value": "ppe" 10 | }, 11 | "deploymentLocation": { 12 | "value": "eastus" 13 | }, 14 | "subFeatureName": { 15 | "value": "malwarescanner" 16 | }, 17 | "appconfigName": { 18 | "value": "dfn-common-configuration-westus2" 19 | }, 20 | "applicationType": { 21 | "value": "service" 22 | }, 23 | "commonLogAnalyticsWorkspace": { 24 | "value": "dfn-commonservice-law-westus2" 25 | }, 26 | "appConfigReadRoleDefName": { 27 | "value": "appConfigReadRoleDefinition" 28 | }, 29 | "malwareScannerFunctionAppSkuName": { 30 | "value": "Y1" 31 | }, 32 | "malwareScannerFunctionAppSkuTier": { 33 | "value": "Dynamic" 34 | }, 35 | "malwareScannerFunctionAppName": { 36 | "value": "malwarescannerfn" 37 | }, 38 | "shortLocation": { 39 | "value": "eastus" 40 | }, 41 | "kvName": { 42 | "value": "dfn-kv-westus2" 43 | }, 44 | "storageAccountName_Variable": { 45 | "value": "dfsnstorageaccount" 46 | }, 47 | "storageAccountNameFile_Variable": { 48 | "value": "dfsnfilestorageacnt" 49 | }, 50 | "primaryDeploymentLocation": { 51 | "value": "eastus" 52 | }, 53 | "storageAccountSKU": { 54 | "value": "Standard_LRS" 55 | }, 56 | "blobContainers": { 57 | "value": [ 58 | "malwarescanner", 59 | "scanresults-dfsncommon-evntgrid-deadletter" 60 | ] 61 | }, 62 | "servicesCertificatePermissionList": { 63 | "value": [ 64 | "get", 65 | "list" 66 | ] 67 | }, 68 | "servicesSecretPermissionList": { 69 | "value": [ 70 | "get", 71 | "list" 72 | ] 73 | }, 74 | "signalRname": { 75 | "value": "dfn-signalR-easus" 76 | }, 77 | "signalRcapacity": { 78 | "value": 1 79 | }, 80 | "signalrkvconnectionSecretName": { 81 | "value": "dfsn-malwarescanner-azuresignalrconnectionstring" 82 | } 83 | } 84 | 85 | } -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/webapp/functionApp.bicep: -------------------------------------------------------------------------------- 1 | param location string 2 | param appConfigName string 3 | param appConfigReadRoleDefName string = '' 4 | param skuName string = '' 5 | param skuTier string = '' 6 | param roleExists bool = false 7 | param featureName string 8 | @allowed([ 9 | 'ppe' 10 | 'prod' 11 | 'perf' 12 | ]) 13 | param env string 14 | param functionAppName string 15 | param tags object 16 | param appServicePlanName string 17 | param shortLocation string = '' 18 | var functionName = env != 'prod' ? '${featureName}-${functionAppName}-${empty(shortLocation) ? location : shortLocation}-${env}' : '${featureName}-${functionAppName}-${empty(shortLocation) ? location : shortLocation}' 19 | 20 | module funcAppServicePlan './appServicePlan.bicep' = { 21 | name: appServicePlanName 22 | params: { 23 | appServicePlanName: appServicePlanName 24 | location: location 25 | tags: tags 26 | skuName: empty(skuName) ? 'Y1' : skuName 27 | skuTier: empty(skuTier) ? 'Dynamic' : skuTier 28 | } 29 | } 30 | 31 | resource functionApp 'Microsoft.Web/sites@2021-03-01' = { 32 | name: functionName 33 | location: location 34 | kind: 'functionapp' 35 | identity: { 36 | type: 'SystemAssigned' 37 | } 38 | properties: { 39 | httpsOnly: true 40 | serverFarmId: funcAppServicePlan.outputs.appServicePlanId 41 | siteConfig: { 42 | ftpsState: 'Disabled' 43 | } 44 | } 45 | dependsOn: [ 46 | funcAppServicePlan 47 | ] 48 | } 49 | 50 | resource appConfigInSameRg 'Microsoft.AppConfiguration/configurationStores@2020-06-01' existing = { 51 | name: appConfigName 52 | scope: resourceGroup() 53 | } 54 | 55 | resource appConfigRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = if (!empty(appConfigReadRoleDefName) && !roleExists) { 56 | scope: appConfigInSameRg 57 | name: guid(appConfigReadRoleDefName, functionApp.id, appConfigReadRoleDefName) 58 | properties: { 59 | roleDefinitionId: '${subscription().id}/providers/Microsoft.Authorization/roleDefinitions/516239f1-63e1-4d78-a4de-a74fb236a071' 60 | principalId: functionApp.identity.principalId 61 | principalType: 'ServicePrincipal' 62 | } 63 | 64 | dependsOn: [ 65 | appConfigInSameRg 66 | ] 67 | } 68 | 69 | output functionPrincipalIdentity string = functionApp.identity.principalId 70 | output functionAppName string = functionApp.name 71 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/FileUploadHub.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function 6 | { 7 | using System.Net; 8 | 9 | using DefenderFileScanNotifier.Function.Core.Logger; 10 | 11 | using Microsoft.AspNetCore.Http; 12 | using Microsoft.AspNetCore.Mvc; 13 | using Microsoft.Azure.WebJobs; 14 | using Microsoft.Azure.WebJobs.Extensions.Http; 15 | using Microsoft.Azure.WebJobs.Extensions.SignalRService; 16 | 17 | 18 | /// 19 | /// The implementation of resume upload hub. 20 | /// 21 | public class FileUploadHub 22 | { 23 | /// 24 | /// The logger object. 25 | /// 26 | private readonly IApplicationInsightsLogger logger; 27 | 28 | /// 29 | /// Initializes a new instance of the class. 30 | /// 31 | /// The logger. 32 | public FileUploadHub(IApplicationInsightsLogger logger) 33 | { 34 | this.logger = logger; 35 | } 36 | 37 | /// 38 | /// The Negotiate endpoint. 39 | /// 40 | /// The request. 41 | /// The action result. 42 | [FunctionName("negotiate")] 43 | //[CustomAuthorize(UserRole.Customer)]//TODO: Here custom authorization need to be implemented. 44 | public IActionResult Negotiate([HttpTrigger(AuthorizationLevel.Anonymous)] HttpRequest req, [SignalRConnectionInfo(HubName = "%AzureSignalRHubName%", UserId = "%AzureSignalRUserHeader%")] SignalRConnectionInfo info) 45 | { 46 | this.logger.WriteCustomEvent("TestRunning", new System.Collections.Generic.Dictionary { { "Test", "Test" } }); 47 | this.logger.TraceInformation($"Method: {nameof(this.Negotiate)} test by ajith started and with authorization status code {req.HttpContext.Response.StatusCode} ."); 48 | switch (req.HttpContext.Response.StatusCode) 49 | { 50 | case (int)HttpStatusCode.OK: 51 | return new OkObjectResult(info); 52 | 53 | case (int)HttpStatusCode.Forbidden: 54 | return new StatusCodeResult(StatusCodes.Status403Forbidden); 55 | 56 | default: 57 | return new UnauthorizedResult(); 58 | } 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /SECURITY.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Security 4 | 5 | Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet) and [Xamarin](https://github.com/xamarin). 6 | 7 | If you believe you have found a security vulnerability in any Microsoft-owned repository that meets [Microsoft's definition of a security vulnerability](https://aka.ms/security.md/definition), please report it to us as described below. 8 | 9 | ## Reporting Security Issues 10 | 11 | **Please do not report security vulnerabilities through public GitHub issues.** 12 | 13 | Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://aka.ms/security.md/msrc/create-report). 14 | 15 | If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the [Microsoft Security Response Center PGP Key page](https://aka.ms/security.md/msrc/pgp). 16 | 17 | You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). 18 | 19 | Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: 20 | 21 | * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) 22 | * Full paths of source file(s) related to the manifestation of the issue 23 | * The location of the affected source code (tag/branch/commit or direct URL) 24 | * Any special configuration required to reproduce the issue 25 | * Step-by-step instructions to reproduce the issue 26 | * Proof-of-concept or exploit code (if possible) 27 | * Impact of the issue, including how an attacker might exploit the issue 28 | 29 | This information will help us triage your report more quickly. 30 | 31 | If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://aka.ms/security.md/msrc/bounty) page for more details about our active programs. 32 | 33 | ## Preferred Languages 34 | 35 | We prefer all communications to be in English. 36 | 37 | ## Policy 38 | 39 | Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://aka.ms/security.md/cvd). 40 | 41 | 42 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/deployabletemplates/commonResources/common.bicep: -------------------------------------------------------------------------------- 1 | @allowed([ 2 | 'westus2' 3 | 'southeastasia' 4 | 'eastus' 5 | ]) 6 | param deploymentLocation string 7 | 8 | @allowed([ 9 | 'ppe' 10 | 'prod' 11 | 'perf' 12 | ]) 13 | param environment string 14 | param featureName string 15 | param subFeatureNameForKeyVault string 16 | param kvKeyPermissionList array 17 | param kvSecretPermissionList array 18 | param kvCertificatePermissionList array 19 | param keyVaultCreateMode string 20 | 21 | param servicesKeyPermissionList array 22 | param servicesSecretPermissionList array 23 | param servicesCertificatePermissionList array 24 | 25 | param subFeatureName string 26 | param featureNameForAppConfig string 27 | param logAnalyticsWorkspaceSKU string 28 | param logAnalyticsRetentionInDays int 29 | param shortLocation string 30 | @allowed([ 31 | 'ui' 32 | 'service' 33 | ]) 34 | param applicationType string 35 | 36 | var envString = environment != 'prod' ? 'PRE-PRODUCTION' : 'PRODUCTION' 37 | var tagsObject = { 38 | 'Environment': environment 39 | 'FeatureName': featureName 40 | 'Env': envString 41 | } 42 | var keyvaultAccessObjectIds = [ 43 | ] 44 | 45 | module keyvaultDeployment '../../modules/configurationstore/keyVault.bicep' = { 46 | name: 'keyvault' 47 | params: { 48 | tagsObject: tagsObject 49 | subFeatureNameForKeyVault: subFeatureNameForKeyVault 50 | deploymentLocation: deploymentLocation 51 | environment: environment 52 | accessPolicyObjectIds: keyvaultAccessObjectIds 53 | keyPermissionList: kvKeyPermissionList 54 | secretPermissionList: kvSecretPermissionList 55 | certificatePermissionList: kvCertificatePermissionList 56 | serviceKeyPermissionList: servicesKeyPermissionList 57 | serviceSecretPermissionList: servicesSecretPermissionList 58 | serviceCertificatePermissionList: servicesCertificatePermissionList 59 | createMode: keyVaultCreateMode 60 | shortLocation:shortLocation 61 | featureName:featureName 62 | } 63 | } 64 | 65 | module appConfigurationDeployment '../../modules/configurationstore/appConfiguration.bicep' = { 66 | name: 'appConfiguration' 67 | params: { 68 | tagsObject: tagsObject 69 | featureName: featureName 70 | subFeatureName:featureNameForAppConfig 71 | deploymentLocation: deploymentLocation 72 | environment: environment 73 | shortLocation:shortLocation 74 | } 75 | } 76 | 77 | module logAnalyticsWorkplace '../../modules/monitoring/loganalyticsworkspace.bicep' = if (deploymentLocation == 'westus2') { 78 | name: 'logAnalyticsWorkplace' 79 | params: { 80 | applicationType: applicationType 81 | deploymentLocation: deploymentLocation 82 | environment: environment 83 | subFeatureName: subFeatureName 84 | tagsObject: tagsObject 85 | logAnalyticsWorkspaceSKU: logAnalyticsWorkspaceSKU 86 | retentionInDays: logAnalyticsRetentionInDays 87 | featureName:featureName 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/CoreConstants/CoreConstants.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function.Core.CoreConstants 6 | { 7 | public class CoreConstants 8 | { 9 | /// 10 | /// The Malware scan SignalR notification status 11 | /// 12 | public enum SignalRNotificationStatus { Failed, Success } 13 | 14 | /// 15 | /// The constant for class. 16 | /// 17 | public static readonly string ClassKey = "Class"; 18 | 19 | /// 20 | /// The constant for method. 21 | /// 22 | public static readonly string MethodKey = "Method"; 23 | 24 | /// 25 | /// The constant for inner exception message. 26 | /// 27 | public static readonly string InnerExceptionMessageKey = "InnerExceptionMessage"; 28 | 29 | /// 30 | /// The constant for exception message. 31 | /// 32 | public static readonly string ExceptionMessageKey = "ExceptionMessage"; 33 | 34 | /// 35 | /// The constant for event id. 36 | /// 37 | public static readonly string EventId = "EventId"; 38 | 39 | /// 40 | /// The constant for event subject. 41 | /// 42 | public static readonly string EventSubject = "EventSubject"; 43 | 44 | /// 45 | /// The constant for stack trace. 46 | /// 47 | public static readonly string StackTraceConstant = "StackTrace"; 48 | 49 | /// 50 | /// The constant for Total time. 51 | /// 52 | public static readonly string MalwareProcessingTotalTimeCustomEvent = "MalwareProcessingTotalTime"; 53 | 54 | /// 55 | /// The constant for container mapping configuration missed exception message purpose. 56 | /// 57 | public static readonly string MalwareContainerMappingConfigMissedException = "Malware container mapping configuration not available. Please verify ScannerContainerMapping configuration."; 58 | 59 | /// 60 | /// Gets the Identity Provider custom claim. 61 | /// 62 | public static string IdentityProviderClaim { get; } = "http://schemas.microsoft.com/identity/claims/identityprovider"; 63 | 64 | /// 65 | /// Gets the Identity Provider user ID custom claim. 66 | /// 67 | public static string IdentityProviderUserIdClaim { get; } = "issuerUserId"; 68 | 69 | /// 70 | /// Gets the header candidate id. Some places it is hardcoded due to limitations we can search with this text. 71 | /// 72 | public static string UserHeaderKey { get; } = "userid"; 73 | 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/storage/storageaccount-prod.bicep: -------------------------------------------------------------------------------- 1 | param subFeatureName string 2 | param featureName string 3 | param blobContainers array = [] 4 | @allowed([ 5 | 'westus' 6 | 'westus2' 7 | 'eastus' 8 | 'southeastasia' 9 | ]) 10 | param deploymentLocation string 11 | param allowedOrigins array = [] 12 | @allowed([ 13 | 'ppe' 14 | 'prod' 15 | 'perf' 16 | ]) 17 | param environment string 18 | param location string 19 | @allowed([ 20 | '' 21 | 'ui' 22 | 'service' 23 | ]) 24 | param applicationType string 25 | param tags object 26 | @allowed([ 27 | 'Standard_LRS' 28 | 'Standard_GRS' 29 | 'Standard_RAGRS' 30 | 'Standard_ZRS' 31 | 'Premium_LRS' 32 | 'Premium_ZRS' 33 | 'Standard_GZRS' 34 | 'Standard_RAGZRS' 35 | ]) 36 | param storageAccountSKU string 37 | @allowed([ 38 | 'ppe' 39 | 'prod' 40 | 'perf' 41 | ]) 42 | param executionEnv string 43 | param preferreddStorageAccountName string = '' 44 | var storageAccountName = environment != 'prod' ? '${featureName}${subFeatureName}${applicationType}sa${deploymentLocation}${environment}' : '${featureName}${subFeatureName}${applicationType}sa${deploymentLocation}' 45 | 46 | var storageAccountNameVar = executionEnv != 'prod' ? storageAccountName : '${storageAccountName}prd' 47 | var finalStorageAccountName=(preferreddStorageAccountName != '') ? preferreddStorageAccountName : storageAccountNameVar 48 | resource storageAccount 'Microsoft.Storage/storageAccounts@2022-05-01' = { 49 | name: finalStorageAccountName 50 | location: location 51 | tags: tags 52 | sku: { 53 | name: storageAccountSKU 54 | } 55 | kind: 'StorageV2' 56 | identity: { 57 | type: 'SystemAssigned' 58 | } 59 | properties: { 60 | allowBlobPublicAccess: false 61 | } 62 | 63 | } 64 | 65 | resource blobServicesAllowOrigins 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01' = if (!empty(allowedOrigins)) { 66 | name: 'default' 67 | parent: storageAccount 68 | properties: { 69 | cors: { 70 | corsRules: [ 71 | { 72 | allowedHeaders: [ 73 | '*' 74 | ] 75 | allowedMethods: [ 76 | 'GET' 77 | 'POST' 78 | 'OPTIONS' 79 | 'PUT' 80 | 'PATCH' 81 | ] 82 | allowedOrigins: allowedOrigins 83 | exposedHeaders: [ 84 | '*' 85 | ] 86 | maxAgeInSeconds: 0 87 | } 88 | ] 89 | } 90 | } 91 | dependsOn: [ containers ] 92 | } 93 | resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2022-09-01' = [for container in blobContainers: { 94 | name: '${storageAccount.name}/default/${container}' 95 | }] 96 | 97 | resource defenderForStorageSettings 'Microsoft.Security/DefenderForStorageSettings@2022-12-01-preview' = { 98 | name: 'current' 99 | scope: storageAccount 100 | properties: { 101 | isEnabled: true 102 | malwareScanning: { 103 | onUpload: { 104 | isEnabled: true 105 | capGBPerMonth: 5000 106 | } 107 | } 108 | sensitiveDataDiscovery: { 109 | isEnabled: true 110 | } 111 | overrideSubscriptionLevelSettings: false 112 | } 113 | } 114 | 115 | 116 | output storageAccountId string = storageAccount.id 117 | output storageAccountName string = storageAccount.name 118 | output storagePrimaryEndpoint string = replace(replace(storageAccount.properties.primaryEndpoints.web, '.net/', '.net'), 'https://', '') 119 | output storageAccountApiVersion string = storageAccount.apiVersion 120 | 121 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/ValidationHelper/GuardHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace DefenderFileScanNotifier.Function.Core.ValidationHelper 8 | { 9 | public static class GuardHelper 10 | { 11 | /// 12 | /// Throws Argument Null Exception when the parameter is null. 13 | /// 14 | /// Type parameter. 15 | /// Parameter value. 16 | /// Name of the parameter which has null value. 17 | public static void AgainstNull(T value, string paramName) 18 | where T : class 19 | { 20 | if (value == null) 21 | { 22 | throw new ArgumentNullException(paramName, $"{typeof(T)} object value must not be null."); 23 | } 24 | } 25 | 26 | /// 27 | /// Throws when the value is null or empty or white space. 28 | /// 29 | /// Value to be tested. 30 | /// Name of the parameter. 31 | public static void AgainstNullOrWhiteSpace(string value, string paramName) 32 | { 33 | if (string.IsNullOrWhiteSpace(value)) 34 | { 35 | throw new ArgumentException("string value must not be null or empty or white-space.", paramName); 36 | } 37 | } 38 | 39 | /// 40 | /// Throws if invalid. 41 | /// 42 | /// Any reference type. 43 | /// Name of the parameter. 44 | /// The parameter value. 45 | /// The error message for only. 46 | /// Argument is null. 47 | /// Invalid argument value. 48 | public static void ThrowIfInvalid(string parameterName, T parameterValue, string message = "Invalid argument value.") 49 | where T : class 50 | { 51 | if (parameterValue is null) 52 | { 53 | throw new ArgumentNullException(parameterName); 54 | } 55 | 56 | if (parameterValue is string valueStr && string.IsNullOrWhiteSpace(valueStr) || 57 | parameterValue is IValidation checkValue && !checkValue.Validate()) 58 | { 59 | throw new ArgumentException(message, parameterName); 60 | } 61 | } 62 | 63 | /// 64 | /// Throws exception if parameter value is zero or negative. 65 | /// 66 | /// Name of the parameter. 67 | /// The parameter value. 68 | /// The message for . 69 | /// The provided message. 70 | public static void ThrowIfZeroOrNegative(string parameterName, int parameterValue, string message = "Invalid value.") 71 | { 72 | if (parameterValue <= 0) 73 | { 74 | throw new ArgumentException(message, parameterName); 75 | } 76 | } 77 | 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/configurationstore/keyVault.bicep: -------------------------------------------------------------------------------- 1 | param featureName string 2 | param subFeatureNameForKeyVault string 3 | @allowed([ 4 | 'westus2' 5 | 'southeastasia' 6 | 'eastus' 7 | ]) 8 | param deploymentLocation string 9 | @allowed([ 10 | 'ppe' 11 | 'prod' 12 | 'perf' 13 | ]) 14 | param environment string 15 | param tagsObject object 16 | param accessPolicyObjectIds array 17 | param keyPermissionList array 18 | param secretPermissionList array 19 | param certificatePermissionList array 20 | 21 | param createMode string 22 | param shortLocation string = '' 23 | param serviceKeyPermissionList array = [] 24 | param serviceSecretPermissionList array = [] 25 | param serviceCertificatePermissionList array = [] 26 | 27 | var envBasedName = environment != 'prod' ? '${featureName}-${subFeatureNameForKeyVault}-${empty(shortLocation) ? deploymentLocation : shortLocation}-${environment}' : '${featureName}-${subFeatureNameForKeyVault}-${empty(shortLocation) ? deploymentLocation : shortLocation}' 28 | var keyVaultName_var = length(envBasedName) > 24 ? '${featureName}-${subFeatureNameForKeyVault}-${environment}' : envBasedName 29 | var tenantId = subscription().tenantId 30 | 31 | resource keyVault 'Microsoft.KeyVault/vaults@2019-09-01' = { 32 | name: keyVaultName_var 33 | location: deploymentLocation 34 | tags: tagsObject 35 | properties: { 36 | createMode: createMode // This needs to be set to 'default' first time with below commented access policies 37 | enableSoftDelete: true 38 | enabledForDeployment: true 39 | enabledForTemplateDeployment: true 40 | enabledForDiskEncryption: true 41 | tenantId: tenantId 42 | accessPolicies: [for object in accessPolicyObjectIds: createMode == 'default' ? object.type == 'system-principal' ? { 43 | tenantId: tenantId 44 | objectId: object.key 45 | permissions: { 46 | keys: keyPermissionList 47 | secrets: secretPermissionList 48 | certificates: certificatePermissionList 49 | } 50 | } : { 51 | tenantId: tenantId 52 | objectId: object.key 53 | permissions: { 54 | keys: serviceKeyPermissionList 55 | secrets: serviceSecretPermissionList 56 | certificates: serviceCertificatePermissionList 57 | } 58 | } : {}] 59 | sku: { 60 | name: 'standard' 61 | family: 'A' 62 | } 63 | } 64 | } 65 | 66 | // accessPolicies: [for object in accessPolicyObjectIds: createMode == 'default' ? object.type == 'system-principal' ? { 67 | // tenantId: tenantId 68 | // objectId: object.key 69 | // permissions: { 70 | // keys: keyPermissionList 71 | // secrets: secretPermissionList 72 | // certificates: certificatePermissionList 73 | // } 74 | // } : { 75 | // tenantId: tenantId 76 | // objectId: object.key 77 | // permissions: { 78 | // keys: serviceKeyPermissionList 79 | // secrets: serviceSecretPermissionList 80 | // certificates: serviceCertificatePermissionList 81 | // } 82 | // } : {}] 83 | 84 | // resource keyVaultPolicies 'Microsoft.KeyVault/vaults/accessPolicies@2021-06-01-preview' = if (createMode == 'recover') { 85 | // name: '${keyVault.name}/replace' 86 | // properties: { 87 | // accessPolicies: [for object in accessPolicyObjectIds: object.type == 'system-principal' ? { 88 | // tenantId: tenantId 89 | // objectId: object.key 90 | // permissions: { 91 | // keys: keyPermissionList 92 | // secrets: secretPermissionList 93 | // certificates: certificatePermissionList 94 | // } 95 | // } : { 96 | // tenantId: tenantId 97 | // objectId: object.key 98 | // permissions: { 99 | // keys: serviceKeyPermissionList 100 | // secrets: serviceSecretPermissionList 101 | // certificates: serviceCertificatePermissionList 102 | // } 103 | // }] 104 | // } 105 | // } 106 | 107 | output keyVaultName string = keyVaultName_var 108 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/modules/webapp/functionAppSettingsWithStorage.bicep: -------------------------------------------------------------------------------- 1 | param functionAppName string 2 | param rootFunctionName string 3 | param storageAccountName string 4 | param location string 5 | param tags object 6 | param appInsightKey string 7 | param keyVaultName string 8 | param appConfigName string 9 | param blobContainers array = [] 10 | param allowedOrigins array = [] 11 | param signalrkvconnectionSecretName string 12 | param keyVaultRGName string = '' 13 | param functionAppRGName string = '' 14 | var functionAppScopeRG = empty(functionAppRGName) ? empty(keyVaultRGName) ? resourceGroup() : resourceGroup(keyVaultRGName) : resourceGroup(functionAppRGName) 15 | @allowed([ 16 | 'ppe' 17 | 'prod' 18 | 'perf' 19 | ]) 20 | param executionEnv string 21 | 22 | var keyVaultScopeRGName = empty(keyVaultRGName) ? resourceGroup().name : keyVaultRGName 23 | 24 | resource FunctionAppInRG 'Microsoft.Web/sites@2021-03-01' existing = { 25 | name: functionAppName 26 | scope: functionAppScopeRG 27 | } 28 | 29 | var storageAccountNameVar = executionEnv == 'prod' ? storageAccountName : '${storageAccountName}${executionEnv}' 30 | resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = { 31 | name: storageAccountNameVar 32 | location: location 33 | tags: tags 34 | sku: { 35 | name: 'Standard_LRS' 36 | } 37 | kind: 'StorageV2' 38 | identity: { 39 | type: 'SystemAssigned' 40 | } 41 | properties: { 42 | allowBlobPublicAccess: false 43 | } 44 | 45 | } 46 | resource blobServicesAllowOrigins 'Microsoft.Storage/storageAccounts/blobServices@2022-09-01' = if (!empty(allowedOrigins)) { 47 | name: 'default' 48 | parent: storageAccount 49 | properties: { 50 | cors: { 51 | corsRules: [ 52 | { 53 | allowedHeaders: [ 54 | '*' 55 | ] 56 | allowedMethods: [ 57 | 'GET' 58 | 'POST' 59 | 'OPTIONS' 60 | 'PUT' 61 | 'PATCH' 62 | ] 63 | allowedOrigins: allowedOrigins 64 | exposedHeaders: [ 65 | '*' 66 | ] 67 | maxAgeInSeconds: 0 68 | } 69 | ] 70 | } 71 | } 72 | dependsOn: [ containers ] 73 | } 74 | resource containers 'Microsoft.Storage/storageAccounts/blobServices/containers@2022-09-01' = [for container in blobContainers: { 75 | name: '${storageAccount.name}/default/${container}' 76 | }] 77 | 78 | var fnAzureFileConnStringKey = '${rootFunctionName}fileconnstrkey' 79 | 80 | module setFunctionAzureFileConnStringToKV '../configurationstore/setSecret.bicep' = { 81 | name: fnAzureFileConnStringKey 82 | scope: resourceGroup(keyVaultScopeRGName) 83 | params: { 84 | keyVaultName: keyVaultName 85 | secretName: fnAzureFileConnStringKey 86 | secretValue: 'DefaultEndpointsProtocol=https;AccountName=${storageAccount.name};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}' 87 | } 88 | } 89 | 90 | var generalSettings = { 91 | 'ExecutionEnv': executionEnv != 'prod' ? 'Development' : 'Production' 92 | 'AppConfigurationBaseUrl': 'https://${appConfigName}.azconfig.io' 93 | 'FUNCTIONS_EXTENSION_VERSION': '~4' 94 | 'FUNCTIONS_WORKER_RUNTIME': 'dotnet' 95 | 'WEBSITE_CONTENTSHARE': functionAppName 96 | 'AzureWebJobsStorage': '@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${fnAzureFileConnStringKey})' 97 | 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING': '@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${fnAzureFileConnStringKey})' 98 | 'APPINSIGHTS_INSTRUMENTATIONKEY': appInsightKey 99 | 'WEBSITE_RUN_FROM_PACKAGE': '1' 100 | 'AzureSignalRHubName':'FileUploadHub' 101 | 'AzureSignalRUserHeader':'{headers.userid}' 102 | 'AzureSignalRConnectionString':'@Microsoft.KeyVault(VaultName=${keyVaultName};SecretName=${signalrkvconnectionSecretName})' 103 | } 104 | 105 | 106 | resource functionAppSettings 'Microsoft.Web/sites/config@2021-03-01' = { 107 | name: 'appsettings' 108 | kind: 'functionapp' 109 | parent: FunctionAppInRG 110 | properties: generalSettings 111 | dependsOn: [ 112 | storageAccount 113 | setFunctionAzureFileConnStringToKV 114 | ] 115 | } 116 | 117 | output storageAccountName string = storageAccount.name 118 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/Startup.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | using Microsoft.Azure.Functions.Extensions.DependencyInjection; 5 | 6 | [assembly: FunctionsStartup(typeof(DefenderFileScanNotifier.Function.Startup))] 7 | 8 | namespace DefenderFileScanNotifier.Function 9 | { 10 | using System; 11 | using System.Diagnostics.CodeAnalysis; 12 | 13 | using Azure.Identity; 14 | 15 | using DefenderFileScanNotifier.Function.Core.Logger; 16 | using DefenderFileScanNotifier.Function.Core.Services; 17 | using Microsoft.ApplicationInsights.DataContracts; 18 | using Microsoft.ApplicationInsights.Extensibility; 19 | using Microsoft.Extensions.Configuration; 20 | using Microsoft.Extensions.DependencyInjection; 21 | 22 | using Guard=DefenderFileScanNotifier.Function.Core.ValidationHelper.GuardHelper; 23 | using DefenderFileScanNotifier.Function.Core.CoreConstants; 24 | 25 | /// 26 | /// The class represents the startup setup of the function app. 27 | /// 28 | [ExcludeFromCodeCoverage] 29 | public class Startup : FunctionsStartup 30 | { 31 | /// 32 | /// Gets the configuration. 33 | /// 34 | /// 35 | /// The configuration. 36 | /// 37 | public IConfiguration Configuration { get; private set; } 38 | 39 | /// 40 | public override void Configure(IFunctionsHostBuilder builder) 41 | { 42 | if (builder is null) 43 | { 44 | throw new ArgumentNullException(nameof(builder)); 45 | } 46 | 47 | _ = builder.Services.AddAzureAppConfiguration(); 48 | this.ConfigureTelemetry(builder.Services); 49 | builder.Services.AddCoreDependencies(Configuration); 50 | this.ConfigureMalwareScanService(builder.Services); 51 | } 52 | 53 | /// 54 | /// Method to Configure Telemetry Services. 55 | /// 56 | /// Service Collection. 57 | private void ConfigureTelemetry(IServiceCollection services) 58 | { 59 | var telemetryConnectionString = this.Configuration[StartupConstants.InstrumentationConnectionStringConstant]; 60 | _ = services.AddSingleton(_ => new ApplicationInsightsLogger(telemetryConnectionString));//.AILogger(loggingConfiguration, telemetryconfig, telemetryInitializers)); 61 | } 62 | 63 | 64 | 65 | /// 66 | public override void ConfigureAppConfiguration(IFunctionsConfigurationBuilder builder) 67 | { 68 | Guard.ThrowIfInvalid(nameof(builder), builder); 69 | string executionEnv = Environment.GetEnvironmentVariable(StartupConstants.AzureFunctionsEnvironment); 70 | IConfigurationRoot configurationRoot = builder.ConfigurationBuilder.Build(); 71 | 72 | _ = builder.ConfigurationBuilder.AddAzureAppConfiguration(options => 73 | { 74 | _ = options.Connect(new Uri(configurationRoot[StartupConstants.AppConfigurationBaseUrl]), new DefaultAzureCredential()) 75 | .Select($"{StartupConstants.CommonPrefix}*", executionEnv) 76 | .Select($"{StartupConstants.MalwareScannerPrefix}*", executionEnv) 77 | .TrimKeyPrefix(StartupConstants.CommonPrefix) 78 | .TrimKeyPrefix(StartupConstants.MalwareScannerPrefix) 79 | .ConfigureKeyVault(keyVaultOptions => 80 | { 81 | _ = keyVaultOptions.SetCredential(new DefaultAzureCredential()); 82 | }) 83 | .ConfigureRefresh(refreshOptions => 84 | { 85 | _ = refreshOptions.Register($"{StartupConstants.CommonPrefix}*", label: executionEnv, refreshAll: true) 86 | .Register($"{StartupConstants.MalwareScannerPrefix}*", label: executionEnv, refreshAll: true) 87 | .SetCacheExpiration(TimeSpan.FromMinutes(Convert.ToDouble(configurationRoot[StartupConstants.CacheExpirationTimeInMinutes]))); 88 | }); 89 | }); 90 | 91 | this.Configuration = builder.ConfigurationBuilder.Build(); 92 | } 93 | 94 | /// 95 | /// Method to configure blob service. 96 | /// 97 | /// Service Collection 98 | private void ConfigureMalwareScanService(IServiceCollection services) 99 | { 100 | services.Configure(this.Configuration.GetSection(nameof(MalwareScannerConfigs))); 101 | services.Configure(this.Configuration.GetSection(nameof(BlobConfigOptions))); 102 | } 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The components specified in this repository helps you in consuming [Azure Defender's file malware scan](https://learn.microsoft.com/en-us/azure/defender-for-cloud/defender-for-storage-malware-scan) status efficiently to the connected clients (UI Web app) in a push based architecture. 4 | 5 | # Architecture Diagram 6 | image 7 | 8 | File uploads of Multiple features within the application are uploaded to Single DMZ (Demelitarized Container) Secured container which acts as single point of contact for file uploads. 9 | 10 | - DMZ Container is enabled with Defender capability to scan presence of malware in every file upload. 11 | - Defender service sends event to event grid service to communicate the file scan status. 12 | 13 | The generic bootstrap solution has following components to facilitate the end-to-end experience from file upload process of multiple features to file scan status communication to connected client (Web UI) 14 | 15 | Bicep Infra files : Spin up required resources responsible for solution consumption in seconds.
16 | 17 | SignalR Negotiate Azure function : Facilitates secured way of establishing connection with signalR instance in a serverless methodology by exchanging connection string and short lived authentication code.
18 | 19 | SignalRWrapper NPM Package: 20 | - The Generic npm package takes care of establishing connection handshake process with Azure SignalR Service. 21 | - Registers event listeners on interested topics. 22 | - Clients can configure event handlers responsible for processing file malware status 23 | - Connection cleanup. 24 | 25 | Generic File Scan Status Checker Azure function :
26 | - File scan status is sent to Event grid which will trigger the File Scan status checker function 27 | - File Scan Status Checker function sends the scan status to signalR hub. 28 | - and the status checker function moves the file to respective feature container if the status is non-malicious result. 29 | - or else if the status is Malicious then the file is deleted from the DMZ container and appropriate status is sent to signalR hub. 30 | 31 | ### Key Notations 32 | Client application has to upload file with following path to the DMZ container. 33 | > //. 34 | - identifier can be a unique identifier for user within the client system. 35 | - attachmentType can be related to feature name for which the attachment belongs to, so that in later section we will see the signification of this field. 36 | - .extension Uploaded file name with an extension. 37 | 38 | Lets take an example of typical HR system. 39 | 40 | Resume Upload use-case:
41 | File Path: 12345/resume/AlexResume.pdf
42 | Event Name: 12345_resume_AlexResume.pdf
43 | 44 | Expense upload use-case file path:
45 | File Path: 12345/expense/hotelreceipt.pdf
46 | Event Name: 12345_expense_hotelreceipt.pdf
47 | 48 | Above notation has to be followed by client during file upload process and the event name will be derived based on file path by the file status checker and the status is pushed to signalR server using the computed event name based on file path of the uploaded blob. 49 | 50 | Client has to listen to this computed event name for their respective feature specific file scan status. 51 | 52 | ## Contributing 53 | 54 | This project welcomes contributions and suggestions. Most contributions require you to agree to a 55 | Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us 56 | the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. 57 | 58 | When you submit a pull request, a CLA bot will automatically determine whether you need to provide 59 | a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions 60 | provided by the bot. You will only need to do this once across all repos using our CLA. 61 | 62 | This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). 63 | For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or 64 | contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. 65 | 66 | ## Trademarks 67 | 68 | This project may contain trademarks or logos for projects, products, or services. Authorized use of Microsoft 69 | trademarks or logos is subject to and must follow 70 | [Microsoft's Trademark & Brand Guidelines](https://www.microsoft.com/en-us/legal/intellectualproperty/trademarks/usage/general). 71 | Use of Microsoft trademarks or logos in modified versions of this project must not cause confusion or imply Microsoft sponsorship. 72 | Any use of third-party trademarks or logos are subject to those third-party's policies. 73 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/StartupConstants.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | namespace DefenderFileScanNotifier.Function 6 | { 7 | using System.Diagnostics.CodeAnalysis; 8 | 9 | /// 10 | /// The stores configuration key constants. 11 | /// 12 | [ExcludeFromCodeCoverage] 13 | internal class StartupConstants 14 | { 15 | /// 16 | /// The icto identifier constant. 17 | /// 18 | internal const string IctoIdConstant = "IctoId"; 19 | 20 | /// 21 | /// The azure functions environment key constant. 22 | /// 23 | internal const string AzureFunctionsEnvironment = "ExecutionEnv"; 24 | 25 | /// 26 | /// The App Configuration service base url constant. 27 | /// 28 | internal const string AppConfigurationBaseUrl = "AppConfigurationBaseUrl"; 29 | 30 | /// 31 | /// The common prefix constant. 32 | /// 33 | internal const string MalwareScannerPrefix = "MalwareScanner:"; 34 | 35 | /// 36 | /// The common prefix constant. 37 | /// 38 | internal const string CommonPrefix = "Common:"; 39 | 40 | /// 41 | /// The cache expiration duration constant. 42 | /// 43 | internal const string CacheExpirationTimeInMinutes = "CacheExpirationTimeInMinutes"; 44 | 45 | /// 46 | /// The instrumentation connection string constant. 47 | /// 48 | internal const string InstrumentationConnectionStringConstant = "APPLICATIONINSIGHTS_CONNECTION_STRING"; 49 | 50 | /// 51 | /// The instrumentation key constant. 52 | /// 53 | private const string InstrumentationKeyConstant = "InstrumentationKey"; 54 | 55 | /// 56 | /// The trace level constant. 57 | /// 58 | private const string TraceLevelConstant = "TraceLevel"; 59 | 60 | /// 61 | /// The Component service tree. 62 | /// 63 | internal static class ComponentServiceTree 64 | { 65 | /// 66 | /// The environment name constant. 67 | /// 68 | internal const string EnvironmentNameConstant = $"{nameof(ComponentServiceTree)}:EnvironmentName"; 69 | 70 | /// 71 | /// The service constant. 72 | /// 73 | internal const string ServiceConstant = $"{nameof(ComponentServiceTree)}:Service"; 74 | 75 | /// 76 | /// The service line constant. 77 | /// 78 | internal const string ServiceLineConstant = $"{nameof(ComponentServiceTree)}:ServiceLine"; 79 | 80 | /// 81 | /// The service offering constant. 82 | /// 83 | internal const string ServiceOfferingConstant = $"{nameof(ComponentServiceTree)}:ServiceOffering"; 84 | 85 | /// 86 | /// The component identifier constant. 87 | /// 88 | internal const string ComponentIdConstant = $"{nameof(ComponentServiceTree)}:ComponentId"; 89 | 90 | /// 91 | /// The component name constant. 92 | /// 93 | internal const string ComponentNameConstant = $"{nameof(ComponentServiceTree)}:ComponentName"; 94 | 95 | /// 96 | /// The sub component name constant. 97 | /// 98 | internal const string SubComponentNameConstant = $"{nameof(ComponentServiceTree)}:SubComponentName"; 99 | } 100 | 101 | /// 102 | /// The class holds application instrumentation constants. 103 | /// 104 | [System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.MaintainabilityRules", "SA1401:Fields should be private", Justification = "Structural representation of config file, properties are used in start up file for invoking specific config structure.")] 105 | internal static class ApplicationInsights 106 | { 107 | /// 108 | /// The instrumentation key. 109 | /// 110 | public static string InstrumentationKey = $"{nameof(ApplicationInsights)}:{InstrumentationKeyConstant}"; 111 | 112 | /// 113 | /// The trace level. 114 | /// 115 | public static string TraceLevel = $"{nameof(ApplicationInsights)}:{TraceLevelConstant}"; 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/MalwareScannerHost.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | // Default URL for triggering event grid function in the local environment. 6 | // http://localhost:7071/runtime/webhooks/EventGrid?functionName={functionname} 7 | using DefenderFileScanNotifier.Function.Core.CoreConstants; 8 | 9 | namespace DefenderFileScanNotifier.Function 10 | { 11 | using System; 12 | using Microsoft.Azure.WebJobs; 13 | using Microsoft.Azure.WebJobs.Extensions.EventGrid; 14 | using Azure.Messaging.EventGrid; 15 | using System.Text.Json; 16 | using Microsoft.Extensions.Options; 17 | using Azure.Identity; 18 | using Azure.Storage.Blobs; 19 | using System.Threading.Tasks; 20 | using System.Collections.Generic; 21 | using System.Diagnostics; 22 | using Microsoft.Azure.WebJobs.Extensions.SignalRService; 23 | using DefenderFileScanNotifier.Function.Core.Logger; 24 | using Guard=DefenderFileScanNotifier.Function.Core.ValidationHelper.GuardHelper; 25 | using CoreConstants = DefenderFileScanNotifier.Function.Core.CoreConstants.CoreConstants; 26 | using DefenderFileScanNotifier.Function.Core.Services; 27 | 28 | /// 29 | /// The defines the azure functions. 30 | /// 31 | public class MalwareScannerHost 32 | { 33 | /// 34 | /// The logger object. 35 | /// 36 | private readonly IApplicationInsightsLogger logger; 37 | 38 | /// 39 | /// The Malware scan properties configuration. 40 | /// 41 | private readonly IMalwareScannerManager malwareScannerManager; 42 | 43 | public MalwareScannerHost(IMalwareScannerManager malwareScannerManager, IApplicationInsightsLogger logger) 44 | { 45 | Guard.ThrowIfInvalid(nameof(logger), logger); 46 | Guard.ThrowIfInvalid(nameof(malwareScannerManager), malwareScannerManager); 47 | this.logger = logger; 48 | this.malwareScannerManager = malwareScannerManager; 49 | } 50 | 51 | [FunctionName("MalwareScanner")] 52 | public async Task MalwareScannerRunAsync([EventGridTrigger] EventGridEvent eventGridEvent, [SignalR(HubName = "%AzureSignalRHubName%")] IAsyncCollector signalRMessages) 53 | { 54 | Guard.ThrowIfInvalid(nameof(eventGridEvent), eventGridEvent); 55 | this.logger.TraceInformation($"Started AzFunction: {nameof(this.MalwareScannerRunAsync)} for event id: {eventGridEvent.Id}, data: {eventGridEvent.Data} at : {DateTime.UtcNow}."); 56 | Stopwatch stopwatch = Stopwatch.StartNew(); 57 | try 58 | { 59 | await this.malwareScannerManager.MoveBlobItemToMainContainerAsync(eventGridEvent, signalRMessages).ConfigureAwait(false); 60 | this.logger.TraceInformation($"Ended AzFunction: {nameof(this.MalwareScannerRunAsync)} for event id: {eventGridEvent.Id} at : {DateTime.UtcNow}."); 61 | } 62 | catch (Exception ex) 63 | { 64 | this.logger.TraceError($"Error occurred while processing request {ex.Message}"); 65 | this.logger.WriteException(ex, new Dictionary 66 | { 67 | { CoreConstants.ClassKey, nameof(MalwareScannerHost) }, 68 | { CoreConstants.MethodKey, nameof(this.MalwareScannerRunAsync) }, 69 | { CoreConstants.ExceptionMessageKey, $"Unexpected exception with the message {ex.Message}" }, 70 | { CoreConstants.InnerExceptionMessageKey, ex.InnerException?.Message ?? string.Empty }, 71 | { CoreConstants.EventId,eventGridEvent.Id }, 72 | { CoreConstants.EventSubject,eventGridEvent.Subject }, 73 | { CoreConstants.StackTraceConstant,ex.StackTrace??"No stack trace available." }, 74 | 75 | }); 76 | throw; 77 | } 78 | finally 79 | { 80 | stopwatch.Stop(); 81 | Dictionary traceProps = new Dictionary(); 82 | traceProps.Add(CoreConstants.MalwareProcessingTotalTimeCustomEvent, stopwatch.ElapsedMilliseconds); 83 | this.logger.WriteCustomEvent(CoreConstants.MalwareProcessingTotalTimeCustomEvent, new Dictionary 84 | { 85 | { CoreConstants.ClassKey, nameof(MalwareScannerHost) }, 86 | { CoreConstants.MethodKey, nameof(this.MalwareScannerRunAsync) }, 87 | { CoreConstants.EventId,eventGridEvent.Id }, 88 | { CoreConstants.EventSubject,eventGridEvent.Subject }, 89 | { CoreConstants.MalwareProcessingTotalTimeCustomEvent,stopwatch.ElapsedMilliseconds.ToString() }, 90 | }, 91 | traceProps); 92 | } 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function/.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # Azure Functions localsettings file 5 | local.settings.json 6 | 7 | # User-specific files 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Build results 17 | [Dd]ebug/ 18 | [Dd]ebugPublic/ 19 | [Rr]elease/ 20 | [Rr]eleases/ 21 | x64/ 22 | x86/ 23 | bld/ 24 | [Bb]in/ 25 | [Oo]bj/ 26 | [Ll]og/ 27 | 28 | # Visual Studio 2015 cache/options directory 29 | .vs/ 30 | # Uncomment if you have tasks that create the project's static files in wwwroot 31 | #wwwroot/ 32 | 33 | # MSTest test Results 34 | [Tt]est[Rr]esult*/ 35 | [Bb]uild[Ll]og.* 36 | 37 | # NUNIT 38 | *.VisualState.xml 39 | TestResult.xml 40 | 41 | # Build Results of an ATL Project 42 | [Dd]ebugPS/ 43 | [Rr]eleasePS/ 44 | dlldata.c 45 | 46 | # DNX 47 | project.lock.json 48 | project.fragment.lock.json 49 | artifacts/ 50 | 51 | *_i.c 52 | *_p.c 53 | *_i.h 54 | *.ilk 55 | *.meta 56 | *.obj 57 | *.pch 58 | *.pdb 59 | *.pgc 60 | *.pgd 61 | *.rsp 62 | *.sbr 63 | *.tlb 64 | *.tli 65 | *.tlh 66 | *.tmp 67 | *.tmp_proj 68 | *.log 69 | *.vspscc 70 | *.vssscc 71 | .builds 72 | *.pidb 73 | *.svclog 74 | *.scc 75 | 76 | # Chutzpah Test files 77 | _Chutzpah* 78 | 79 | # Visual C++ cache files 80 | ipch/ 81 | *.aps 82 | *.ncb 83 | *.opendb 84 | *.opensdf 85 | *.sdf 86 | *.cachefile 87 | *.VC.db 88 | *.VC.VC.opendb 89 | 90 | # Visual Studio profiler 91 | *.psess 92 | *.vsp 93 | *.vspx 94 | *.sap 95 | 96 | # TFS 2012 Local Workspace 97 | $tf/ 98 | 99 | # Guidance Automation Toolkit 100 | *.gpState 101 | 102 | # ReSharper is a .NET coding add-in 103 | _ReSharper*/ 104 | *.[Rr]e[Ss]harper 105 | *.DotSettings.user 106 | 107 | # JustCode is a .NET coding add-in 108 | .JustCode 109 | 110 | # TeamCity is a build add-in 111 | _TeamCity* 112 | 113 | # DotCover is a Code Coverage Tool 114 | *.dotCover 115 | 116 | # NCrunch 117 | _NCrunch_* 118 | .*crunch*.local.xml 119 | nCrunchTemp_* 120 | 121 | # MightyMoose 122 | *.mm.* 123 | AutoTest.Net/ 124 | 125 | # Web workbench (sass) 126 | .sass-cache/ 127 | 128 | # Installshield output folder 129 | [Ee]xpress/ 130 | 131 | # DocProject is a documentation generator add-in 132 | DocProject/buildhelp/ 133 | DocProject/Help/*.HxT 134 | DocProject/Help/*.HxC 135 | DocProject/Help/*.hhc 136 | DocProject/Help/*.hhk 137 | DocProject/Help/*.hhp 138 | DocProject/Help/Html2 139 | DocProject/Help/html 140 | 141 | # Click-Once directory 142 | publish/ 143 | 144 | # Publish Web Output 145 | *.[Pp]ublish.xml 146 | *.azurePubxml 147 | # TODO: Comment the next line if you want to checkin your web deploy settings 148 | # but database connection strings (with potential passwords) will be unencrypted 149 | #*.pubxml 150 | *.publishproj 151 | 152 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 153 | # checkin your Azure Web App publish settings, but sensitive information contained 154 | # in these scripts will be unencrypted 155 | PublishScripts/ 156 | 157 | # NuGet Packages 158 | *.nupkg 159 | # The packages folder can be ignored because of Package Restore 160 | **/packages/* 161 | # except build/, which is used as an MSBuild target. 162 | !**/packages/build/ 163 | # Uncomment if necessary however generally it will be regenerated when needed 164 | #!**/packages/repositories.config 165 | # NuGet v3's project.json files produces more ignoreable files 166 | *.nuget.props 167 | *.nuget.targets 168 | 169 | # Microsoft Azure Build Output 170 | csx/ 171 | *.build.csdef 172 | 173 | # Microsoft Azure Emulator 174 | ecf/ 175 | rcf/ 176 | 177 | # Windows Store app package directories and files 178 | AppPackages/ 179 | BundleArtifacts/ 180 | Package.StoreAssociation.xml 181 | _pkginfo.txt 182 | 183 | # Visual Studio cache files 184 | # files ending in .cache can be ignored 185 | *.[Cc]ache 186 | # but keep track of directories ending in .cache 187 | !*.[Cc]ache/ 188 | 189 | # Others 190 | ClientBin/ 191 | ~$* 192 | *~ 193 | *.dbmdl 194 | *.dbproj.schemaview 195 | *.jfm 196 | *.pfx 197 | *.publishsettings 198 | node_modules/ 199 | orleans.codegen.cs 200 | 201 | # Since there are multiple workflows, uncomment next line to ignore bower_components 202 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 203 | #bower_components/ 204 | 205 | # RIA/Silverlight projects 206 | Generated_Code/ 207 | 208 | # Backup & report files from converting an old project file 209 | # to a newer Visual Studio version. Backup files are not needed, 210 | # because we have git ;-) 211 | _UpgradeReport_Files/ 212 | Backup*/ 213 | UpgradeLog*.XML 214 | UpgradeLog*.htm 215 | 216 | # SQL Server files 217 | *.mdf 218 | *.ldf 219 | 220 | # Business Intelligence projects 221 | *.rdl.data 222 | *.bim.layout 223 | *.bim_*.settings 224 | 225 | # Microsoft Fakes 226 | FakesAssemblies/ 227 | 228 | # GhostDoc plugin setting file 229 | *.GhostDoc.xml 230 | 231 | # Node.js Tools for Visual Studio 232 | .ntvs_analysis.dat 233 | 234 | # Visual Studio 6 build log 235 | *.plg 236 | 237 | # Visual Studio 6 workspace options file 238 | *.opt 239 | 240 | # Visual Studio LightSwitch build output 241 | **/*.HTMLClient/GeneratedArtifacts 242 | **/*.DesktopClient/GeneratedArtifacts 243 | **/*.DesktopClient/ModelManifest.xml 244 | **/*.Server/GeneratedArtifacts 245 | **/*.Server/ModelManifest.xml 246 | _Pvt_Extensions 247 | 248 | # Paket dependency manager 249 | .paket/paket.exe 250 | paket-files/ 251 | 252 | # FAKE - F# Make 253 | .fake/ 254 | 255 | # JetBrains Rider 256 | .idea/ 257 | *.sln.iml 258 | 259 | # CodeRush 260 | .cr/ 261 | 262 | # Python Tools for Visual Studio (PTVS) 263 | __pycache__/ 264 | *.pyc -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/deployabletemplates/malwarescanner/malwarescanner.bicep: -------------------------------------------------------------------------------- 1 | param featureName string 2 | param subFeatureName string 3 | @allowed([ 4 | 'westus2' 5 | 'eastus' 6 | ]) 7 | param deploymentLocation string 8 | @allowed([ 9 | 'ui' 10 | 'service' 11 | ]) 12 | param applicationType string 13 | @allowed([ 14 | 'ppe' 15 | 'prod' 16 | 'perf' 17 | ]) 18 | param environment string 19 | param shortLocation string 20 | param commonLogAnalyticsWorkspace string 21 | param appconfigName string 22 | param kvName string 23 | param appConfigReadRoleDefName string 24 | 25 | param blobContainers array 26 | param signalrkvconnectionSecretName string 27 | param signalRname string 28 | @allowed([ 29 | 1 30 | 2 31 | 5 32 | 10 33 | 20 34 | 50 35 | 100 36 | ]) 37 | param signalRcapacity int = 1 38 | param storageAccountNameFile_Variable string 39 | param storageAccountName_Variable string 40 | param primaryDeploymentLocation string 41 | param storageAccountSKU string 42 | param malwareScannerFunctionAppName string 43 | param malwareScannerFunctionAppSkuName string 44 | param malwareScannerFunctionAppSkuTier string 45 | param servicesCertificatePermissionList array 46 | param servicesSecretPermissionList array 47 | var envString = environment != 'prod' ? 'PRE-PRODUCTION' : 'PRODUCTION' 48 | 49 | var tagsObject = { 50 | 'Environment': environment 51 | 'FeatureName': featureName 52 | 'Env': envString 53 | } 54 | 55 | resource appConfig 'Microsoft.AppConfiguration/configurationStores@2020-06-01' existing = { 56 | name: appconfigName 57 | scope: resourceGroup() 58 | } 59 | 60 | resource kvConfigInSameRg 'Microsoft.KeyVault/vaults@2019-09-01' existing = { 61 | name: kvName 62 | scope: resourceGroup() 63 | 64 | } 65 | 66 | resource logAnalyticsWorkplace 'Microsoft.OperationalInsights/workspaces@2021-06-01' existing = { 67 | name: commonLogAnalyticsWorkspace 68 | scope: resourceGroup() 69 | } 70 | 71 | module appInsightsDeployment '../../modules/monitoring/appInsights.bicep' = { 72 | name: 'appInsights' 73 | params: { 74 | applicationType: applicationType 75 | deploymentLocation: deploymentLocation 76 | environment: environment 77 | subFeatureName: subFeatureName 78 | tagsObject: tagsObject 79 | workspaceRescId: logAnalyticsWorkplace.id 80 | featureName:featureName 81 | } 82 | dependsOn: [ 83 | logAnalyticsWorkplace 84 | ] 85 | } 86 | 87 | module malwarescannerFunctionApp '../../modules/webapp/functionApp.bicep' = { 88 | name: 'MalwareScannerFunctionApp' 89 | params: { 90 | appConfigReadRoleDefName: appConfigReadRoleDefName 91 | appConfigName: appconfigName 92 | functionAppName: malwareScannerFunctionAppName 93 | location: deploymentLocation 94 | env: environment 95 | tags: tagsObject 96 | appServicePlanName: 'dfsn${malwareScannerFunctionAppName}${shortLocation}srvcplan' 97 | skuName: malwareScannerFunctionAppSkuName 98 | skuTier: malwareScannerFunctionAppSkuTier 99 | shortLocation:shortLocation 100 | featureName:featureName 101 | } 102 | dependsOn: [ 103 | appConfig 104 | ] 105 | } 106 | 107 | module updatesupportFunctionAppSetting '../../modules/webapp/functionAppSettingsWithStorage.bicep' = { 108 | name: 'updateFunctionAppSetting' 109 | params: { 110 | rootFunctionName: 'malwareScannerfn' 111 | functionAppName: malwarescannerFunctionApp.outputs.functionAppName 112 | storageAccountName: storageAccountName_Variable 113 | appInsightKey: appInsightsDeployment.outputs.appInsightInsKey 114 | appConfigName: appconfigName 115 | executionEnv: environment 116 | location: deploymentLocation 117 | tags: tagsObject 118 | keyVaultName: kvName 119 | signalrkvconnectionSecretName:signalrkvconnectionSecretName 120 | } 121 | dependsOn: [ 122 | malwarescannerFunctionApp 123 | appInsightsDeployment 124 | appendAccessPolicy 125 | ] 126 | } 127 | 128 | var keyvaultAccessObjectIds = [ 129 | { 130 | 'key': malwarescannerFunctionApp.outputs.functionPrincipalIdentity 131 | 'type': 'funtionAppMSIClientId' 132 | } 133 | ] 134 | 135 | module appendAccessPolicy '../../modules/configurationstore/keyVaultAddAccessPolicy.bicep' = { 136 | name: 'appendSupportAccessPolicy' 137 | params: { 138 | certificatePermissionList: servicesCertificatePermissionList 139 | secretPermissionList: servicesSecretPermissionList 140 | 141 | keyVaultName: kvName 142 | objectIds: keyvaultAccessObjectIds 143 | } 144 | dependsOn: [ 145 | kvConfigInSameRg 146 | malwarescannerFunctionApp 147 | ] 148 | } 149 | 150 | module signalRDeployment '../../modules/webapp/signalrService.bicep'={ 151 | name:'signalRDeployment' 152 | params:{ 153 | location:primaryDeploymentLocation 154 | name:signalRname 155 | capacity:signalRcapacity 156 | tags:tagsObject 157 | subFeatureName: subFeatureName 158 | signalrkvconnectionSecretName:signalrkvconnectionSecretName 159 | keyVaultName:kvName 160 | featureName:featureName 161 | } 162 | } 163 | 164 | module dfsnProcessorStorageAccount '../../modules/storage/storageaccount-prod.bicep' = { 165 | name: 'dfsnProcessorStorageAccount' 166 | params: { 167 | applicationType: '' 168 | deploymentLocation: primaryDeploymentLocation 169 | environment: environment 170 | subFeatureName: subFeatureName 171 | tags: tagsObject 172 | storageAccountSKU: storageAccountSKU 173 | blobContainers: blobContainers 174 | executionEnv: environment 175 | location: primaryDeploymentLocation 176 | preferreddStorageAccountName: storageAccountNameFile_Variable 177 | featureName:featureName 178 | } 179 | } 180 | 181 | module createContainer '../../modules/storage/blobcontainers.bicep' = { 182 | name: 'createcontainers' 183 | params: { 184 | storageAccountName: dfsnProcessorStorageAccount.outputs.storageAccountName 185 | blobContainers: blobContainers 186 | } 187 | dependsOn:[ 188 | dfsnProcessorStorageAccount 189 | ] 190 | } 191 | 192 | resource topic 'Microsoft.EventGrid/topics@2022-06-15' = { 193 | name: 'ScanResults-${dfsnProcessorStorageAccount.name}' 194 | location: deploymentLocation 195 | dependsOn: [ 196 | dfsnProcessorStorageAccount 197 | ] 198 | } -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/Services/BlobClientRepository.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | using DefenderFileScanNotifier.Function.Core.ValidationHelper; 6 | 7 | namespace DefenderFileScanNotifier.Function.Core.Services 8 | { 9 | using System; 10 | using System.Diagnostics.CodeAnalysis; 11 | using System.Linq; 12 | using System.Threading.Tasks; 13 | 14 | using Azure.Core; 15 | using Azure.Storage.Blobs; 16 | using Azure.Storage.Sas; 17 | 18 | using DefenderFileScanNotifier.Function.Core.CoreConstants; 19 | using DefenderFileScanNotifier.Function.Core.Logger; 20 | 21 | using Microsoft.Extensions.Configuration; 22 | using Microsoft.Extensions.Options; 23 | 24 | using Guard = GuardHelper; 25 | [ExcludeFromCodeCoverage] 26 | public class BlobClientRepository : IBlobClientRepository 27 | { 28 | /// 29 | /// The configuration. 30 | /// 31 | private readonly IConfiguration configuration; 32 | 33 | /// 34 | /// The logger object. 35 | /// 36 | private readonly IApplicationInsightsLogger logger; 37 | 38 | /// 39 | /// The BLOB client options. 40 | /// 41 | private readonly BlobClientOptions blobClientOptions; 42 | 43 | /// 44 | /// Initializes a new instance of the class. 45 | /// 46 | /// The configuration. 47 | /// The BLOB configuration. 48 | /// The logger. 49 | public BlobClientRepository(IConfiguration configuration, IOptionsMonitor blobConfigOptions, IApplicationInsightsLogger logger) 50 | { 51 | Guard.ThrowIfInvalid(nameof(logger), logger); 52 | Guard.ThrowIfInvalid(nameof(blobConfigOptions), blobConfigOptions); 53 | Guard.ThrowIfInvalid(nameof(blobConfigOptions), blobConfigOptions.CurrentValue); 54 | BlobConfigOptions blobConfigOptionsValue = blobConfigOptions.CurrentValue; 55 | this.configuration = configuration; 56 | this.logger = logger; 57 | this.blobClientOptions = new BlobClientOptions() 58 | { 59 | Retry = { 60 | Delay = TimeSpan.FromSeconds(blobConfigOptionsValue.RetryDelayInSeconds), 61 | MaxRetries = blobConfigOptionsValue.MaxRetries, 62 | Mode = RetryMode.Exponential, 63 | MaxDelay = TimeSpan.FromSeconds(blobConfigOptionsValue.RetryMaxDelayInSeconds), 64 | NetworkTimeout = TimeSpan.FromSeconds(blobConfigOptionsValue.RetryNetworkTimeoutInSeconds) 65 | }, 66 | }; 67 | } 68 | 69 | /// 70 | public async Task StartCopyFromUriAsync(MalwareScannerContainerMapper malwareScannerContainerMapper, 71 | string sourceContainer, 72 | string blobName, 73 | string eventId, 74 | double sasTokenPeriodInMinutes, 75 | Uri sourceBlobUri) 76 | { 77 | var srcBlobClient = new BlobClient(configuration[malwareScannerContainerMapper.sourceStorageConstringAppConfigName], sourceContainer, blobName, this.blobClientOptions); 78 | var destContainerClient = new BlobContainerClient(configuration[malwareScannerContainerMapper.destinationStorageConstringAppConfigName], malwareScannerContainerMapper.destinationBlobContainerName, this.blobClientOptions); 79 | 80 | BlobClient? destBlobClient; 81 | if (!string.IsNullOrWhiteSpace(malwareScannerContainerMapper.destinationFolderstructure)) 82 | { 83 | logger.TraceInformation($"Destination folder structure is {malwareScannerContainerMapper.destinationFolderstructure} from {nameof(this.StartCopyFromUriAsync)} method for event id: {eventId}"); 84 | int index = blobName.LastIndexOf('/'); 85 | string fileName = index != -1 ? blobName.Substring(blobName.LastIndexOf('/') + 1) : blobName; 86 | if (malwareScannerContainerMapper.isTagEnabled) 87 | { 88 | logger.TraceInformation($"Tag formation is enabled and it is from {nameof(this.StartCopyFromUriAsync)} method for event id: {eventId}"); 89 | var tags = await srcBlobClient.GetTagsAsync(); 90 | if (tags.Value != null && tags.Value.Tags.Any()) 91 | { 92 | string[] spiltFolderstructure = malwareScannerContainerMapper.destinationFolderstructure.Split("/"); 93 | for (int splitIndex = 0; splitIndex < spiltFolderstructure.Length; splitIndex++) 94 | { 95 | if (!string.IsNullOrWhiteSpace(spiltFolderstructure[splitIndex]) && tags.Value.Tags.Any(x => string.Equals(x.Key, spiltFolderstructure[splitIndex], StringComparison.OrdinalIgnoreCase))) 96 | { 97 | malwareScannerContainerMapper.destinationFolderstructure = malwareScannerContainerMapper.destinationFolderstructure.Replace(spiltFolderstructure[splitIndex], tags.Value.Tags.Where(x => string.Equals(x.Key, spiltFolderstructure[splitIndex], StringComparison.OrdinalIgnoreCase)).FirstOrDefault().Value); 98 | } 99 | } 100 | } 101 | } 102 | 103 | blobName = string.IsNullOrWhiteSpace(fileName) ? blobName : malwareScannerContainerMapper.destinationFolderstructure + fileName; 104 | } 105 | 106 | destBlobClient = new BlobClient(configuration[malwareScannerContainerMapper.destinationStorageConstringAppConfigName], malwareScannerContainerMapper.destinationBlobContainerName, blobName, this.blobClientOptions); 107 | 108 | if (!await srcBlobClient.ExistsAsync()) 109 | { 110 | logger.TraceError($"blob {sourceBlobUri} doesn't exist might be it's moved for event id {eventId}"); 111 | return; 112 | } 113 | logger.TraceInformation($"MoveBlob: Copying blob to {destBlobClient.Uri} for event id {eventId}"); 114 | var sourceBlobSasToken = srcBlobClient.GenerateSasUri(BlobSasPermissions.Read, DateTimeOffset.Now.AddMinutes(sasTokenPeriodInMinutes)); 115 | var copyFromUriOperation = await destBlobClient.StartCopyFromUriAsync(sourceBlobSasToken); 116 | await copyFromUriOperation.WaitForCompletionAsync(); 117 | logger.TraceInformation($"MoveBlob: Deleting source blob {srcBlobClient.Uri} for event id {eventId}"); 118 | await srcBlobClient.DeleteAsync(); 119 | } 120 | 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /DFSNotifierResourcesDeployment/ResourceDeployment.md: -------------------------------------------------------------------------------- 1 | 2 | ****DefenderFileScanNotifier Resource Deployment Guidance**** 3 | 4 | As part of resource deployments, we have two bicep files as mentioned below 5 | 1. common.bicep 6 | 2. malwarescanner.bicep 7 | 8 | **common bicep file** 9 | 10 | **Parameter file**: DFSNotifierResourcesDeployment\deployabletemplates\commonResources\parameters\common-westus2.parameters.json 11 | 12 | **Bicep File**: DFSNotifierResourcesDeployment\deployabletemplates\commonResources\common.bicep 13 | 14 | • It will create these resource types: App Configuration, Key Vault & Log Analytics workspace. 15 | • Please find naming conventions related parameters to avoid deployment errors 16 | 17 | featureName is important parameter and used by all resource types 18 | o**App Configuration:** In Parameter file please update featureName as per your project requirement or go with any available name. 19 | 20 | Final name will be decided by appConfiguration.bicep module file as mentioned below, 21 | 22 | var appConfigurationName = environment != 'prod' ? '${featureName}-${subFeatureName}-configuration-${empty(shortLocation) ? deploymentLocation : shortLocation}-${environment}' : '${featureName}-${subFeatureName}-configuration-${empty(shortLocation) ? deploymentLocation : shortLocation}' 23 | 24 | Example: dfn-common-configuration-westus2 25 | Refer App configuration resource naming rules: 26 | https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.AppConfig.Name/ 27 | 28 | o **Key Vault**: 29 | Module file: DFSNotifierResourcesDeployment\modules\configurationstore\keyVault.bicep 30 | 31 | var envBasedName = environment != 'prod' ? '${featureName}-${subFeatureNameForKeyVault}-${empty(shortLocation) ? deploymentLocation : shortLocation}-${environment}' : '${featureName}-${subFeatureNameForKeyVault}-${empty(shortLocation) ? deploymentLocation : shortLocation}' 32 | 33 | var keyVaultName_var = length(envBasedName) > 24 ? '${featureName}-${subFeatureNameForKeyVault}-${environment}' : envBasedName 34 | 35 | Example: dfn-kv-westus2 36 | 37 | Refer App configuration resource naming rules: 38 | https://azure.github.io/PSRule.Rules.Azure/en/rules/Azure.KeyVault.Name/ 39 | 40 | o **Log Analytics workspace**: 41 | 42 | Module file: DFSNotifierResourcesDeployment\modules\monitoring\loganalyticsworkspace.bicep 43 | var logAnalyticsName = environment != 'prod' ? '${featureName}-${subFeatureName}${applicationType}-law-${deploymentLocation}-${environment}' : '${featureName}-${subFeatureName}${applicationType}-law-${deploymentLocation}' 44 | 45 | Example: dfn-commonservice-law-westus2 46 | 47 | You can update resource names as per your requirements. 48 | 49 | Pre-requisites 50 | Install Azure Modules through powershell script (Az modules) 51 | 52 | **Steps to execute common bicep:** 53 | 54 | 1. Open Powershell or CMD CLI window and execute azure login 55 | az login --tenant 56 | 57 | 2. Execute bicep: 58 | 59 | az deployment group create --resource-group "" --template-file "\DFSNotifierResourcesDeployment\deployabletemplates\commonResources\common.bicep" --parameters "\DFSNotifierResourcesDeployment\deployabletemplates\commonResources\parameters\common-westus2.parameters.json" 60 | 61 | ![image](https://github.com/raiajithkumarr/DefenderFileScanNotifier/assets/22548964/3668860d-b150-43bb-a5f7-582a80c29aee) 62 | 63 | 64 | **malwarescanner.bicep** 65 | 66 | Parameter file: DFSNotifierResourcesDeployment\deployabletemplates\malwarescanner\parameters\malwarescanner-easus.parameters.json 67 | 68 | Bicep File: DFSNotifierResourcesDeployment\deployabletemplates\malwarescanner\malwarescanner.bicep 69 | 70 | Before executing above bicep file we need to update few attributes in “malwarescanner-easus.parameters.json” file as mentioned below. 71 | appconfigName value should be updated asper resource created at common bicep file For example this value should be dfn-common-configuration-westus2 because it’s created by common bicep file. 72 | commonLogAnalyticsWorkspace This is also same as appconfigName. 73 | For example: dfn-commonservice-law-westus2, it’s created by common bicep file. 74 | kvName This is also same as appconfigName. 75 | For example: dfn-kv-westus2, it’s created by common bicep file. 76 | 77 | 78 | Script to execute malwarescanner bicep file 79 | az deployment group create --resource-group "" --template-file "\DFSNotifierResourcesDeployment\deployabletemplates\malwarescanner\malwarescanner.bicep" --parameters "\DFSNotifierResourcesDeployment\deployabletemplates\malwarescanner\parameters\malwarescanner-easus.parameters.json" 80 | 81 | ![image](https://github.com/raiajithkumarr/DefenderFileScanNotifier/assets/22548964/584e6121-bdb9-47e9-821e-7c59cf4018b6) 82 | 83 | 84 | And we need to configure Microsoft Defender for Cloud for storage account to send scan result to Event Grid Topic. 85 | Storage account that is used for processing files to verify malware results, 86 | 87 | Parameter Path: DFSNotifierResourcesDeployment\deployabletemplates\malwarescanner\parameters\malwarescanner-easus.parameters.json 88 | 89 | "storageAccountNameFile_Variable": { 90 | "value": "dfsnfilestorageacnt" 91 | } 92 | Bicep Path: 93 | DFSNotifierResourcesDeployment\deployabletemplates\malwarescanner\malwarescanner.bicep 94 | By using above bicep file, it will generate Event grid topic as mentioned below 95 | name: 'ScanResults-${dfsnProcessorStorageAccount.name}' 96 | Finally the resource name will be : ScanResults- 97 | Example: ScanResults-dfsnProcessorStorageAccount 98 | 99 | Steps to configure Microsoft Defender for Cloud for storage account 100 | 101 | 102 | 103 | 104 | 105 | 106 | And save the configuration. 107 | 108 | 109 | 110 | 111 | 112 | 113 | Next Deploy the azure function code base, And then configuration Subscription under Event Grid Topic: ScanResults-dfsnProcessorStorageAccount 114 | 115 | 116 | 117 | 118 | 119 | 120 | Provide name and select azure function as an Endpoint Type, then Configure function name under endpoint settings save all these settings. 121 | 122 | 123 | 124 | 125 | 126 | Add below settings in Azure App Configuration section for example: Example: This is App Configuration Name dfn-common-configuration-westus2 127 | 128 | 129 | Sno Configuration Name Configuration Value 130 | 1 MalwareScanner:MalwareScannerConfigs:AntimalwareScanEventTypes Microsoft.Security.AntiMalwareScan,Microsoft.Security.MalwareScanningResult 131 | 2 MalwareScanner:MalwareScannerConfigs:AntimalwareScanSuccessIdentifier No threats found 132 | 3 MalwareScanner:MalwareScannerConfigs:ScannerContainerMapping [{"sourceBlobContainerName":"malwarescanner","sourceStorageConstringAppConfigName":"BlobConfigOptions:ConnectionString","destinationBlobContainerName":"malwarefreedocuments","destinationStorageConstringAppConfigName":"BlobConfigOptions:ConnectionString","destinationFolderstructure":"userid/attachmenttype/","isTagEnabled":false}] 133 | 4 MalwareScanner:MalwareScannerConfigs:SourceBlobReadSasTokenPeriodInMinutes 2 134 | 5 Common:BlobConfigOptions:ConnectionString Here we need to add keyvault reference of malware storage account (for example : here we select CommonStorage KeyVault secret key related to dfsnrfilestgacnt storage account connection string) 135 | 136 | 137 | 138 | 139 | 140 | 141 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Ll]og/ 33 | [Ll]ogs/ 34 | 35 | # Visual Studio 2015/2017 cache/options directory 36 | .vs/ 37 | # Uncomment if you have tasks that create the project's static files in wwwroot 38 | #wwwroot/ 39 | 40 | # Visual Studio 2017 auto generated files 41 | Generated\ Files/ 42 | 43 | # MSTest test Results 44 | [Tt]est[Rr]esult*/ 45 | [Bb]uild[Ll]og.* 46 | 47 | # NUnit 48 | *.VisualState.xml 49 | TestResult.xml 50 | nunit-*.xml 51 | 52 | # Build Results of an ATL Project 53 | [Dd]ebugPS/ 54 | [Rr]eleasePS/ 55 | dlldata.c 56 | 57 | # Benchmark Results 58 | BenchmarkDotNet.Artifacts/ 59 | 60 | # .NET Core 61 | project.lock.json 62 | project.fragment.lock.json 63 | artifacts/ 64 | 65 | # ASP.NET Scaffolding 66 | ScaffoldingReadMe.txt 67 | 68 | # StyleCop 69 | StyleCopReport.xml 70 | 71 | # Files built by Visual Studio 72 | *_i.c 73 | *_p.c 74 | *_h.h 75 | *.ilk 76 | *.meta 77 | *.obj 78 | *.iobj 79 | *.pch 80 | *.pdb 81 | *.ipdb 82 | *.pgc 83 | *.pgd 84 | *.rsp 85 | *.sbr 86 | *.tlb 87 | *.tli 88 | *.tlh 89 | *.tmp 90 | *.tmp_proj 91 | *_wpftmp.csproj 92 | *.log 93 | *.tlog 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio 6 auto-generated project file (contains which files were open etc.) 298 | *.vbp 299 | 300 | # Visual Studio 6 workspace and project file (working project files containing files to include in project) 301 | *.dsw 302 | *.dsp 303 | 304 | # Visual Studio 6 technical files 305 | *.ncb 306 | *.aps 307 | 308 | # Visual Studio LightSwitch build output 309 | **/*.HTMLClient/GeneratedArtifacts 310 | **/*.DesktopClient/GeneratedArtifacts 311 | **/*.DesktopClient/ModelManifest.xml 312 | **/*.Server/GeneratedArtifacts 313 | **/*.Server/ModelManifest.xml 314 | _Pvt_Extensions 315 | 316 | # Paket dependency manager 317 | .paket/paket.exe 318 | paket-files/ 319 | 320 | # FAKE - F# Make 321 | .fake/ 322 | 323 | # CodeRush personal settings 324 | .cr/personal 325 | 326 | # Python Tools for Visual Studio (PTVS) 327 | __pycache__/ 328 | *.pyc 329 | 330 | # Cake - Uncomment if you are using it 331 | # tools/** 332 | # !tools/packages.config 333 | 334 | # Tabs Studio 335 | *.tss 336 | 337 | # Telerik's JustMock configuration file 338 | *.jmconfig 339 | 340 | # BizTalk build output 341 | *.btp.cs 342 | *.btm.cs 343 | *.odx.cs 344 | *.xsd.cs 345 | 346 | # OpenCover UI analysis results 347 | OpenCover/ 348 | 349 | # Azure Stream Analytics local run output 350 | ASALocalRun/ 351 | 352 | # MSBuild Binary and Structured Log 353 | *.binlog 354 | 355 | # NVidia Nsight GPU debugger configuration file 356 | *.nvuser 357 | 358 | # MFractors (Xamarin productivity tool) working folder 359 | .mfractor/ 360 | 361 | # Local History for Visual Studio 362 | .localhistory/ 363 | 364 | # Visual Studio History (VSHistory) files 365 | .vshistory/ 366 | 367 | # BeatPulse healthcheck temp database 368 | healthchecksdb 369 | 370 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 371 | MigrationBackup/ 372 | 373 | # Ionide (cross platform F# VS Code tools) working folder 374 | .ionide/ 375 | 376 | # Fody - auto-generated XML schema 377 | FodyWeavers.xsd 378 | 379 | # VS Code files for those working on multiple tools 380 | .vscode/* 381 | !.vscode/settings.json 382 | !.vscode/tasks.json 383 | !.vscode/launch.json 384 | !.vscode/extensions.json 385 | *.code-workspace 386 | 387 | # Local History for Visual Studio Code 388 | .history/ 389 | 390 | # Windows Installer files from build outputs 391 | *.cab 392 | *.msi 393 | *.msix 394 | *.msm 395 | *.msp 396 | 397 | # JetBrains Rider 398 | *.sln.iml 399 | -------------------------------------------------------------------------------- /DefenderFileScanNotifierFunction/DefenderFileScanNotifier.Function.Core/Services/MalwareScannerManager.cs: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright (c) Microsoft Corporation. All rights reserved. 3 | // 4 | 5 | using DefenderFileScanNotifier.Function.Core.ValidationHelper; 6 | 7 | namespace DefenderFileScanNotifier.Function.Core.Services 8 | { 9 | using Azure.Messaging.EventGrid; 10 | using Azure.Storage.Blobs; 11 | 12 | using DefenderFileScanNotifier.Function.Core.CoreConstants; 13 | using DefenderFileScanNotifier.Function.Core.Logger; 14 | 15 | using Microsoft.Azure.WebJobs; 16 | using Microsoft.Azure.WebJobs.Extensions.SignalRService; 17 | using Microsoft.Extensions.Options; 18 | using Microsoft.Extensions.Primitives; 19 | 20 | using Newtonsoft.Json; 21 | 22 | using System; 23 | using System.Text; 24 | using System.Text.Json; 25 | 26 | using Guard = GuardHelper; 27 | /// 28 | /// The Malware scanner manager. 29 | /// 30 | public class MalwareScannerManager : IMalwareScannerManager 31 | { 32 | /// 33 | /// The logger object. 34 | /// 35 | private readonly IApplicationInsightsLogger logger; 36 | 37 | /// 38 | /// The Malware scan properties configuration. 39 | /// 40 | private readonly MalwareScannerConfigs malwareScannerConfigs; 41 | 42 | /// 43 | /// The Malware scan properties configuration. 44 | /// 45 | private readonly List? malwareScannerContainerMappers; 46 | 47 | /// 48 | /// The Malware scan allowed event types. 49 | /// 50 | private readonly string[]? allowedEventTypes; 51 | 52 | /// 53 | /// The Malware scan allowed event types. 54 | /// 55 | private readonly IBlobClientRepository blobClientRepository; 56 | 57 | /// 58 | /// Initializes a new instance of the class. 59 | /// 60 | /// The malware scanner configurations. 61 | /// The BLOB Repository. 62 | /// The logger 63 | 64 | public MalwareScannerManager(IOptionsMonitor malwareScannerConfigs, IBlobClientRepository blobClientRepository, IApplicationInsightsLogger logger) 65 | { 66 | Guard.ThrowIfInvalid(nameof(logger), logger); 67 | Guard.ThrowIfInvalid(nameof(malwareScannerConfigs), malwareScannerConfigs); 68 | Guard.ThrowIfInvalid(nameof(malwareScannerConfigs), malwareScannerConfigs.CurrentValue); 69 | Guard.ThrowIfInvalid(nameof(malwareScannerConfigs), malwareScannerConfigs.CurrentValue.ScannerContainerMapping); 70 | Guard.ThrowIfInvalid(nameof(malwareScannerConfigs), malwareScannerConfigs.CurrentValue.AntimalwareScanEventTypes); 71 | this.logger = logger; 72 | this.malwareScannerConfigs = malwareScannerConfigs.CurrentValue; 73 | malwareScannerContainerMappers = JsonConvert.DeserializeObject>(this.malwareScannerConfigs.ScannerContainerMapping); 74 | allowedEventTypes = this.malwareScannerConfigs?.AntimalwareScanEventTypes?.Split(','); 75 | this.blobClientRepository = blobClientRepository; 76 | } 77 | 78 | /// 79 | public async Task MoveBlobItemToMainContainerAsync(EventGridEvent eventGridEvent, IAsyncCollector signalRMessages) 80 | { 81 | Guard.ThrowIfInvalid(nameof(eventGridEvent), eventGridEvent); 82 | 83 | logger.TraceInformation($"Started {nameof(this.MoveBlobItemToMainContainerAsync)} method for event id: {eventGridEvent.Id}, data: {eventGridEvent.Data} at : {DateTime.UtcNow}."); 84 | if (allowedEventTypes == null || !allowedEventTypes.Contains(eventGridEvent.EventType)) 85 | { 86 | logger.TraceInformation($"Event type is not an {malwareScannerConfigs.AntimalwareScanEventTypes} event, event type:{eventGridEvent.EventType} for event id: {eventGridEvent.Id}"); 87 | return; 88 | } 89 | 90 | var storageAccountName = eventGridEvent?.Subject?.Split('/');//[^1]; 91 | if (storageAccountName == null || storageAccountName.Length < 4) 92 | { 93 | logger.TraceInformation($"storage account name is not valid {eventGridEvent?.Subject} for event id: {eventGridEvent.Id}"); 94 | return; 95 | } 96 | 97 | string containerName = storageAccountName[3]; 98 | logger.TraceInformation($"Received new scan result for storage {containerName} for event id: {eventGridEvent?.Id}"); 99 | var decodedEventData = JsonDocument.Parse(eventGridEvent?.Data).RootElement.ToString(); 100 | var eventData = JsonDocument.Parse(decodedEventData).RootElement; 101 | var verdict = eventData.GetProperty("scanResultType").GetString(); 102 | var blobUriString = eventData.GetProperty("blobUri").GetString(); 103 | string blobsIdentifier = "blobs/"; 104 | string[] blobFilePath = (eventGridEvent?.Subject.Substring((int)(eventGridEvent?.Subject?.IndexOf(blobsIdentifier) + blobsIdentifier.Length))).Split("/"); 105 | StringBuilder sb = new StringBuilder(); 106 | for (int blobItemCount = 0; blobItemCount <= blobFilePath.Length - 2; blobItemCount++) 107 | { 108 | sb.Append(blobFilePath[blobItemCount].Trim()); 109 | sb.Append("_"); 110 | } 111 | 112 | if (!string.IsNullOrWhiteSpace(blobFilePath[blobFilePath.Length - 1])) 113 | { 114 | sb.Append(blobFilePath[blobFilePath.Length - 1].Substring(0, blobFilePath[blobFilePath.Length - 1].LastIndexOf('.'))); 115 | } 116 | 117 | logger.TraceInformation($"SignalR sent event message target name is {sb.ToString()} for event id: {eventGridEvent?.Id}"); 118 | 119 | if (verdict == null || blobUriString == null) 120 | { 121 | await signalRMessages.AddAsync(new SignalRMessage 122 | { 123 | Target = sb.ToString(), 124 | Arguments = new[] { CoreConstants.SignalRNotificationStatus.Failed.ToString() } 125 | }); 126 | logger.TraceError($"Event data doesn't contain 'verdict' or 'blobUri' fields for event id: {eventGridEvent?.Id}"); 127 | throw new ArgumentException($"Event data doesn't contain 'verdict' or 'blobUri' fields for event id: {eventGridEvent?.Id}"); 128 | } 129 | 130 | if (verdict == malwareScannerConfigs.AntimalwareScanSuccessIdentifier) 131 | { 132 | await signalRMessages.AddAsync(new SignalRMessage 133 | { 134 | Target = sb.ToString(), 135 | Arguments = new[] { CoreConstants.SignalRNotificationStatus.Success.ToString() }, 136 | }); 137 | 138 | var blobUri = new Uri(blobUriString); 139 | try 140 | { 141 | await MoveBlobAsync(blobUri, containerName, eventGridEvent.Id).ConfigureAwait(false); 142 | } 143 | catch (Exception ex) 144 | { 145 | logger.TraceError($"Error occurred while processing request {ex.Message}"); 146 | logger.WriteException(ex, new Dictionary 147 | { 148 | { CoreConstants.ClassKey, nameof(MalwareScannerManager) }, 149 | { CoreConstants.MethodKey, nameof(this.MoveBlobItemToMainContainerAsync) }, 150 | { CoreConstants.ExceptionMessageKey, $"Unexpected exception with the message {ex.Message}" }, 151 | { CoreConstants.InnerExceptionMessageKey, ex.InnerException?.Message ?? string.Empty }, 152 | { CoreConstants.EventId,eventGridEvent.Id }, 153 | { CoreConstants.EventSubject,eventGridEvent.Subject }, 154 | { CoreConstants.StackTraceConstant,ex.StackTrace??"No stack trace available." }, 155 | 156 | }); 157 | throw; 158 | } 159 | } 160 | else 161 | { 162 | await signalRMessages.AddAsync(new SignalRMessage 163 | { 164 | Target = sb.ToString(), 165 | Arguments = new[] { CoreConstants.SignalRNotificationStatus.Failed.ToString() } 166 | }); 167 | } 168 | } 169 | 170 | /// 171 | /// Main business logic to move data from source container to destination container based on configuration. 172 | /// 173 | /// The source BLOB URI. 174 | /// The source container. 175 | /// The event id. 176 | /// The task. 177 | /// returns exception on failure. 178 | private async Task MoveBlobAsync(Uri blobUri, string sourceContainer, string eventId) 179 | { 180 | logger.TraceInformation($"{nameof(this.MoveBlobAsync)} method started for event id {eventId}"); 181 | var blobUriBuilder = new BlobUriBuilder(blobUri); 182 | MalwareScannerContainerMapper? destinationConfig = malwareScannerContainerMappers?.Where(x => x.sourceBlobContainerName == sourceContainer)?.FirstOrDefault(); 183 | if (destinationConfig == null) 184 | { 185 | logger.TraceError($"{CoreConstants.MalwareContainerMappingConfigMissedException} for blob {blobUri} and event id {eventId}"); 186 | throw new Exception(CoreConstants.MalwareContainerMappingConfigMissedException); 187 | } 188 | string blobName = blobUriBuilder.BlobName; 189 | await this.blobClientRepository.StartCopyFromUriAsync(destinationConfig, sourceContainer, blobName, eventId, malwareScannerConfigs.SourceBlobReadSasTokenPeriodInMinutes, blobUri).ConfigureAwait(false); 190 | logger.TraceInformation($"MoveBlob: blob moved successfully for event id {eventId}"); 191 | } 192 | } 193 | } --------------------------------------------------------------------------------