340 строки
14 KiB
Bicep
340 строки
14 KiB
Bicep
var deploymentVersion = '1.1.1'
|
|
@description('A globally unique name for the Function App to be created which will run the code to ingest MDVM data into Sentinel.')
|
|
param FunctionAppName string = 'fa-mdvm-${uniqueString(resourceGroup().id)}'
|
|
@description('Select to enable Application Insights for the Function App. This will allow you to monitor the status of the Function App for any errors. The Log Analytics Workspace specified in the "Log Analytics Resource Id" Parameter will be used to store the Application Insights data.')
|
|
param DeployApplicationInsights bool = true
|
|
@description('A globally unique name for the Key Vault to be created which will store Function App secrets.')
|
|
param KeyVaultName string = 'kv-mdvm-${uniqueString(resourceGroup().id)}'
|
|
@description('A globally unique name for the Function App Storage Account. Must be between 3 and 24 characters in length and use numbers and lower-case letters only.')
|
|
param StorageAccountName string = 'samdvm${uniqueString(resourceGroup().id)}'
|
|
@description('Name of custom role to be created at the Log Analytics resource group level. The name needs to be unique across the entire tenant. This role provides the Function App read access to the MDVM custom tables.')
|
|
param CustomRoleName string = 'Custom Role - Sentinel MDVM Table Reader'
|
|
@description('Name for Data Collection Endpoint used to ingest data into Log Analytics workspace.')
|
|
param DataCollectionEndpointName string = 'dce-mdvm-${uniqueString(resourceGroup().id)}'
|
|
@description('Name for Data Collection Rule used to ingest data into Log Analytics workspace.')
|
|
param DataCollectionRuleName string = 'dcr-mdvm-${uniqueString(resourceGroup().id)}'
|
|
@description('Azure Resource ID (NOT THE WORKSPACE ID) of the existing Log Analytics Workspace where you would like the MDVM data and optional Function App Application Insights data to reside. This can be found by clicking the "JSON View" link within the Overview page of the Log Analytics workspace resource. The format is: "/subscriptions/xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx/resourcegroups/xxxxxxxx/providers/microsoft.operationalinsights/workspaces/xxxxxxxx"')
|
|
param LogAnalyticsWorkspaceResourceID string = '/subscriptions/xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx-xxxxxxxx/resourcegroups/xxxxxxxx/providers/microsoft.operationalinsights/workspaces/xxxxxxxx'
|
|
@description('Deploy Azure workbooks to help visualize the MDVM data.')
|
|
param DeployWorkbooks bool = true
|
|
@description('Use the Azure Deployment Script resource to automatically deploy the Function App code. This requires the Microsoft.ContainerInstance resource provider to be registred on the subsription.')
|
|
param DeployFunctionCode bool = true
|
|
@description('GitHub repo where Azure Function package and post deployment script is located. Leave the default value unless you are using content from a different location. This is not applicable if the Deploy Function Code parameter is set to false.')
|
|
param RepoUri string = 'https://raw.githubusercontent.com/Azure/Azure-Sentinel/master'
|
|
@description('Recommended when processing a large number of events but increases cost.')
|
|
param EnableElasticPremiumPlan bool = false
|
|
@description('Enabling Private Networking will restrict public access to the Function App for additional security. A Virtual Network with the below address space and subnets, along with an NSG, Private Endpoints, and Private DNS Zones will be deployed to support this configuration. This will also leverage the Dedicated App Service Premium plan (P0v3) instead of the Consumption plan (If the Elastic Premium Plan is selected, it will be used instead of the Dedicated App Service Premium Plan.).')
|
|
param EnablePrivateNetworking bool = true
|
|
@description('If enabling Private Networking, choose the desired address space for the Virtual Network or leave the default.')
|
|
param PrivateNetworkAddressSpace string = '10.0.0.0/24'
|
|
@description('If enabling Private Networking, choose the desired address space for the Private Endpoints subnet or leave the default. Do not make subnets any smaller than the default.')
|
|
param PrivateEndpointsSubnet string = '10.0.0.0/26'
|
|
@description('If enabling Private Networking, choose the desired address space for the Function App vnet integration subnet or leave the default. Do not make subnets any smaller than the default.')
|
|
param FunctionAppSubnet string = '10.0.0.64/26'
|
|
|
|
var location = resourceGroup().location
|
|
var functionAppPackageUri = '${RepoUri}/DataConnectors/M365Defender-VulnerabilityManagement/functionPackage.zip'
|
|
var deploymentScriptUri = '${RepoUri}/DataConnectors/M365Defender-VulnerabilityManagement/deploymentScript.ps1'
|
|
var roleIdOwner = '/providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635'
|
|
|
|
resource law 'Microsoft.OperationalInsights/workspaces@2022-10-01' existing = {
|
|
name: split(LogAnalyticsWorkspaceResourceID, '/')[8]
|
|
scope: resourceGroup(split(LogAnalyticsWorkspaceResourceID, '/')[2], split(LogAnalyticsWorkspaceResourceID, '/')[4])
|
|
}
|
|
|
|
resource userAssignedMi 'Microsoft.ManagedIdentity/userAssignedIdentities@2022-01-31-preview' = {
|
|
name: 'uami-${FunctionAppName}'
|
|
location: location
|
|
}
|
|
|
|
module createCustomTables 'modules/customDcrTables.bicep' = {
|
|
name: 'createCustomTables'
|
|
params: {
|
|
LogAnalyticsWorkspaceLocation: law.location
|
|
LogAnalyticsWorkspaceResourceId: law.id
|
|
DataCollectionEndpointName: DataCollectionEndpointName
|
|
DataCollectionRuleName: DataCollectionRuleName
|
|
ServicePrincipalId: userAssignedMi.properties.principalId
|
|
}
|
|
}
|
|
|
|
resource storageAccount 'Microsoft.Storage/storageAccounts@2021-08-01' = {
|
|
name: StorageAccountName
|
|
dependsOn: [
|
|
createCustomTables
|
|
]
|
|
location: location
|
|
kind: 'StorageV2'
|
|
sku: {
|
|
name: 'Standard_LRS'
|
|
}
|
|
properties: {
|
|
allowBlobPublicAccess: false
|
|
publicNetworkAccess: EnablePrivateNetworking == true ? 'Disabled' : 'Enabled'
|
|
minimumTlsVersion: 'TLS1_2'
|
|
networkAcls: {
|
|
defaultAction: EnablePrivateNetworking == true ? 'Deny' : 'Allow'
|
|
bypass: 'AzureServices'
|
|
}
|
|
}
|
|
}
|
|
|
|
resource fileShare 'Microsoft.Storage/storageAccounts/fileServices/shares@2022-09-01' = {
|
|
name: '${storageAccount.name}/default/${toLower(FunctionAppName)}'
|
|
}
|
|
|
|
resource keyVault 'Microsoft.KeyVault/vaults@2022-07-01' = {
|
|
name: KeyVaultName
|
|
location: location
|
|
properties: {
|
|
sku: {
|
|
family: 'A'
|
|
name: 'premium'
|
|
}
|
|
tenantId: subscription().tenantId
|
|
accessPolicies: [
|
|
{
|
|
objectId: userAssignedMi.properties.principalId
|
|
permissions: {
|
|
secrets: [
|
|
'get'
|
|
'set'
|
|
'list'
|
|
'delete'
|
|
]
|
|
}
|
|
tenantId: subscription().tenantId
|
|
}
|
|
]
|
|
publicNetworkAccess: EnablePrivateNetworking == true ? 'Disabled' : 'Enabled'
|
|
networkAcls: {
|
|
defaultAction: EnablePrivateNetworking == true ? 'Deny' :'Allow'
|
|
bypass: EnablePrivateNetworking == true ? 'None' : 'AzureServices'
|
|
}
|
|
}
|
|
}
|
|
|
|
resource keyVaultSecretStorageAccountConnectionString 'Microsoft.KeyVault/vaults/secrets@2022-07-01' = {
|
|
parent: keyVault
|
|
name: 'StorageAccountConnectionString'
|
|
properties: {
|
|
value: 'DefaultEndpointsProtocol=https;AccountName=${StorageAccountName};EndpointSuffix=${environment().suffixes.storage};AccountKey=${storageAccount.listKeys().keys[0].value}'
|
|
}
|
|
}
|
|
|
|
resource hostingPlan 'Microsoft.Web/serverfarms@2021-03-01' = {
|
|
name: FunctionAppName
|
|
location: location
|
|
sku: {
|
|
name: EnableElasticPremiumPlan == true ? 'EP1' : EnablePrivateNetworking == true ? 'P0v3' : 'P0v3'
|
|
tier: EnableElasticPremiumPlan == true ? 'ElasticPremium' : EnablePrivateNetworking == true ? 'PremiumV3' : 'PremiumV3'
|
|
}
|
|
}
|
|
|
|
resource functionApp 'Microsoft.Web/sites@2022-03-01' = {
|
|
name: FunctionAppName
|
|
location: location
|
|
identity: {
|
|
type: 'UserAssigned'
|
|
userAssignedIdentities: {
|
|
'${userAssignedMi.id}': {}
|
|
}
|
|
}
|
|
kind: 'functionapp'
|
|
properties: {
|
|
serverFarmId: hostingPlan.id
|
|
keyVaultReferenceIdentity: userAssignedMi.id
|
|
httpsOnly: true
|
|
clientCertEnabled: true
|
|
clientCertMode: 'OptionalInteractiveUser'
|
|
virtualNetworkSubnetId: EnablePrivateNetworking == true ? privateNetwork.outputs.functionAppSubnetId : (null)
|
|
vnetContentShareEnabled: EnablePrivateNetworking == true ? true : false
|
|
vnetRouteAllEnabled: EnablePrivateNetworking == true ? true : false
|
|
siteConfig: {
|
|
appSettings: [
|
|
{
|
|
name: 'AzureWebJobsStorage'
|
|
value: '@Microsoft.KeyVault(VaultName=${KeyVaultName};SecretName=StorageAccountConnectionString)'
|
|
}
|
|
{
|
|
name: 'WEBSITE_CONTENTAZUREFILECONNECTIONSTRING'
|
|
value: '@Microsoft.KeyVault(VaultName=${KeyVaultName};SecretName=StorageAccountConnectionString)'
|
|
}
|
|
{
|
|
name: 'AzureWebJobsSecretStorageType'
|
|
value: 'keyvault'
|
|
}
|
|
{
|
|
name: 'AzureWebJobsSecretStorageKeyVaultUri'
|
|
value: 'https://${KeyVaultName}${environment().suffixes.keyvaultDns}/'
|
|
}
|
|
{
|
|
name: 'AzureWebJobsSecretStorageKeyVaultClientId'
|
|
value: userAssignedMi.properties.clientId
|
|
}
|
|
{
|
|
name: 'WEBSITE_CONTENTSHARE'
|
|
value: toLower(FunctionAppName)
|
|
}
|
|
{
|
|
name: 'WEBSITE_SKIP_CONTENTSHARE_VALIDATION'
|
|
value: '1'
|
|
}
|
|
{
|
|
name: 'FUNCTIONS_EXTENSION_VERSION'
|
|
value: '~4'
|
|
}
|
|
{
|
|
name: 'APPINSIGHTS_INSTRUMENTATIONKEY'
|
|
value: DeployApplicationInsights == true ? applicationInsights.properties.InstrumentationKey : ''
|
|
}
|
|
{
|
|
name: 'FUNCTIONS_WORKER_RUNTIME'
|
|
value: 'powershell'
|
|
}
|
|
{
|
|
name: 'WEBSITE_RUN_FROM_PACKAGE'
|
|
value: '1'
|
|
}
|
|
{
|
|
name: 'WEBSITE_CONTENTOVERVNET'
|
|
value: EnablePrivateNetworking == true ? '1' : '0'
|
|
}
|
|
{
|
|
name: 'LawResourceId'
|
|
value: law.id
|
|
}
|
|
{
|
|
name: 'DcrImmutableId'
|
|
value: createCustomTables.outputs.DcrImmutableId
|
|
}
|
|
{
|
|
name: 'DceUri'
|
|
value: createCustomTables.outputs.DceUri
|
|
}
|
|
{
|
|
name: 'UamiClientId'
|
|
value: userAssignedMi.properties.clientId
|
|
}
|
|
{
|
|
name: 'FullImport'
|
|
value: '0'
|
|
}
|
|
{
|
|
name: 'DeploymentVersion'
|
|
value: deploymentVersion
|
|
}
|
|
]
|
|
powerShellVersion: '7.2'
|
|
minTlsVersion: '1.2'
|
|
ftpsState: 'Disabled'
|
|
http20Enabled: true
|
|
alwaysOn: true
|
|
publicNetworkAccess: 'Enabled'
|
|
}
|
|
}
|
|
dependsOn: [
|
|
keyVaultSecretStorageAccountConnectionString
|
|
storageAccount
|
|
fileShare
|
|
]
|
|
}
|
|
|
|
resource applicationInsights 'Microsoft.Insights/components@2020-02-02' = if (DeployApplicationInsights == true) {
|
|
name: 'appInsights-${FunctionAppName}'
|
|
location: location
|
|
kind: 'web'
|
|
properties: {
|
|
Application_Type: 'web'
|
|
Request_Source: 'rest'
|
|
WorkspaceResourceId: law.id
|
|
}
|
|
}
|
|
|
|
module roleAssignmentLaw 'modules/lawRoleAssignment.bicep' = {
|
|
scope: resourceGroup(split(law.id, '/')[2], split(law.id, '/')[4])
|
|
dependsOn: [
|
|
createCustomTables
|
|
]
|
|
name: 'rbacAssignmentLaw'
|
|
params: {
|
|
PrincipalId: userAssignedMi.properties.principalId
|
|
LawName: split(law.id, '/')[8]
|
|
RoleName: CustomRoleName
|
|
}
|
|
}
|
|
|
|
resource roleAssignmentFa 'Microsoft.Authorization/roleAssignments@2022-04-01' = if(DeployFunctionCode == true) {
|
|
name: guid(subscription().id, resourceGroup().id, functionApp.id)
|
|
scope: functionApp
|
|
properties: {
|
|
principalId: userAssignedMi.properties.principalId
|
|
roleDefinitionId: roleIdOwner
|
|
principalType: 'ServicePrincipal'
|
|
}
|
|
}
|
|
|
|
module sentinelWorkbooks 'modules/sentinelWorkbooks.bicep' = if(DeployWorkbooks == true) {
|
|
name: 'sentinelWorkbooks'
|
|
scope: resourceGroup(split(law.id, '/')[2], split(law.id, '/')[4])
|
|
dependsOn: [
|
|
createCustomTables
|
|
functionApp
|
|
]
|
|
params: {
|
|
WorkbookSourceId: law.id
|
|
Location: law.location
|
|
}
|
|
}
|
|
|
|
module privateNetwork 'modules/privateNetwork.bicep' = if(EnablePrivateNetworking == true) {
|
|
name: 'privateNetwork'
|
|
params: {
|
|
FunctionAppName: FunctionAppName
|
|
KeyVaultId: keyVault.id
|
|
location: location
|
|
StorageAccountId: storageAccount.id
|
|
StorageAccountName: StorageAccountName
|
|
KeyVaultName: KeyVaultName
|
|
PrivateNetworkAddressSpace: PrivateNetworkAddressSpace
|
|
FunctionAppSubnet: FunctionAppSubnet
|
|
PrivateEndpointsSubnet: PrivateEndpointsSubnet
|
|
PrincipalId: userAssignedMi.properties.principalId
|
|
DeployCode: DeployFunctionCode
|
|
}
|
|
}
|
|
|
|
module functionAppPe 'modules/functionAppPE.bicep' = if(EnablePrivateNetworking == true) {
|
|
name: 'functionAppPe'
|
|
params: {
|
|
Location: location
|
|
FunctionAppId: functionApp.id
|
|
FunctionAppName: FunctionAppName
|
|
PrivateEndpointSubnetId: EnablePrivateNetworking == true ? privateNetwork.outputs.privateEndpointSubnetId : (null)
|
|
VnetId: EnablePrivateNetworking == true ? privateNetwork.outputs.vnetId : (null)
|
|
}
|
|
}
|
|
|
|
resource deploymentScript 'Microsoft.Resources/deploymentScripts@2020-10-01' = if(DeployFunctionCode == true) {
|
|
name: 'deployCode'
|
|
location: location
|
|
kind: 'AzurePowerShell'
|
|
identity: {
|
|
type: 'UserAssigned'
|
|
userAssignedIdentities: {
|
|
'${userAssignedMi.id}': {}
|
|
}
|
|
}
|
|
properties: {
|
|
azPowerShellVersion: '11.0'
|
|
retentionInterval: 'PT1H'
|
|
timeout: 'PT10M'
|
|
cleanupPreference: 'Always'
|
|
primaryScriptUri: deploymentScriptUri
|
|
arguments: EnablePrivateNetworking == true ? '-PackageUri ${functionAppPackageUri} -SubscriptionId ${split(subscription().id, '/')[2]} -ResourceGroupName ${resourceGroup().name} -FunctionAppName ${functionAppPe.outputs.functionAppName} -FAScope ${functionApp.id} -UAMIPrincipalId ${userAssignedMi.properties.principalId} -VnetScope ${privateNetwork.outputs.vnetId} -RestrictedIPs "None"' : '-PackageUri ${functionAppPackageUri} -SubscriptionId ${split(subscription().id, '/')[2]} -ResourceGroupName ${resourceGroup().name} -FunctionAppName ${functionApp.name} -FAScope ${functionApp.id} -UAMIPrincipalId ${userAssignedMi.properties.principalId}'
|
|
}
|
|
}
|
|
|
|
output UserAssignedManagedIdentityPrincipalId string = userAssignedMi.properties.principalId
|
|
output UserAssignedManagedIdentityPrincipalName string = userAssignedMi.name
|