├── 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 |
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 | 
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 | 
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 | }
--------------------------------------------------------------------------------