diff --git a/TemplateFiles/DeploymentFiles/Add-AztsFeatureConfigurationValues.ps1 b/TemplateFiles/DeploymentFiles/Add-AztsFeatureConfigurationValues.ps1 index 99186417..491efa7a 100644 --- a/TemplateFiles/DeploymentFiles/Add-AztsFeatureConfigurationValues.ps1 +++ b/TemplateFiles/DeploymentFiles/Add-AztsFeatureConfigurationValues.ps1 @@ -1,254 +1,254 @@ -<### -# Overview: - This script is used to add feature configuration value for AzTS in a Subscription. - -# Instructions to execute the script: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to add feature configuration value for AzTS in a Subscription. Refer `Examples`, below. - -# Examples: - To add user's object id into configuration of AzTS: - Add-AztsFeatureConfigurationValues -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FeatureName "CMET" -FeatureConfigValues "00000000-xxxx-0000-xxxx-000000000001,00000000-xxxx-0000-xxxx-000000000002,00000000-xxxx-0000-xxxx-000000000003" - -###> - -function Add-AztsFeatureConfigurationValues { - Param( - [string] - [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which Azure Tenant Security Solution is installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where Azure Tenant Security Solution is installed.")] - $ScanHostRGName, - - [string] - [Parameter(Mandatory = $false, HelpMessage = "File path for Azts Control Configuration file AztsControlConfiguration.json.")] - $FilePath = "./AddAztsFeatureConfiguraValuesTemplate.json", - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Azts Feature Name to add Configuration values. Values for this parameter are 'CMET', 'MG Compliance Initiate Editor'")] - [ValidateSet("CMET", "MG Compliance Initiate Editor")] - $FeatureName, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Pass multiple Configuration Value as comma seperated")] - $FeatureConfigValues - ) - - $inputParams = $PSBoundParameters - $logger = [Logger]::new($SubscriptionId) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Add-AztsFeatureConfigurationValues `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Starting process to add Configuration Values for $FeatureName feature. This may take 2-3 mins...", $([Constants]::MessageType.Info)) - - - #checking if ConfigurationValues enetered is having single or multiple values - if ( !$FeatureConfigValues.Contains(",")) { - $FeatureConfigValues += ","; - } - - #Splitting the UserObjectIds from (,) - [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); - - # Set the context to host subscription - Set-AzContext -SubscriptionId $SubscriptionId | Out-null - - # AzTS resources name preparation - $ResourceId = '/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId, $ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0, 5).ToString().ToLower() - - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage("Loading File: [$($FilePath)]...") - - # Getting Control Configuration from file path - if (-not (Test-Path -Path $FilePath)) { - $logger.PublishCustomMessage("ERROR: File - $($FilePath) not found. Exiting...", $([Constants]::MessageType.Error)) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - break - } - - $JsonContent = Get-content -path $FilePath | ConvertFrom-Json - - $logger.PublishLogMessage("Loading File: [$($FilePath)] completed.") - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - - $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } - if ($null -ne $FilteredfeatureSetting) { - - - #Checking if any Dependent Features needs to be enabled - foreach ($dependentConfiguration in $FilteredfeatureSetting.ConfigurationDependencies) { - $IntPrivilegedEditorIds = @() - $NewAppSettings = @{} - $NewConfigurationList = @{} - $ExistingConfigurationList = @{} - - $ComponentName = $dependentConfiguration.ComponentName + $ResourceHash; - - #Getting Existing configuration value - $AzTSAppConfigurationSettings = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -ErrorAction Stop - - foreach ($Configuration in $dependentConfiguration.Configuration) { - if ($null -ne $AzTSAppConfigurationSettings) { - - if ($FeatureName -ieq "CMET" -or $FeatureName -ieq "MG Compliance Initiate Editor") { - - #Splitting the UserObjectIds from (,) - [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); - $IntPrivilegedEditorIds = @() - # Existing app settings - $AppSettings = $AzTSAppConfigurationSettings.SiteConfig.AppSettings - - # Moving existing app settings in new app settings list to avoid being overridden - ForEach ($appSetting in $AppSettings) { - $NewAppSettings[$appSetting.Name] = $appSetting.Value - - #Checking if Configuration Key exist to get the exisiting array value - if ($appSetting.Name.Contains($Configuration)) { - - $appSettingNameArray = $appSetting.Name.Split('_'); - $IntPrivilegedEditorIds += $appSettingNameArray[6]; - - #checking if the Configuration value exist (Key and Value) to avoid duplication - if ($FeatureConfigValueArray.Contains($appSetting.Value)) { - $ExistingConfigurationList["$($appSetting.Name)"] =$appSetting.Value - $FeatureConfigValueArray.Remove($appSetting.Value); - } - } - } - - #If exisitng configuration values does not exist, then setting it to 0 - if ($IntPrivilegedEditorIds.Count -eq 0) { - $IntPrivilegedEditorIds = 0 - } - - #Fetching max value - $IntPrivilegedEditorIdsMaxValue = ($IntPrivilegedEditorIds | Measure-Object -Maximum).Maximum - - #Adding configuration - foreach ($FeatureConfig in $FeatureConfigValueArray) { - if ($FeatureConfig -ne "") { - $NewAppSettings["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig - $NewConfigurationList["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig - $IntPrivilegedEditorIdsMaxValue++ - } - } - - } - } - } - - try { - if ($FeatureConfigValueArray.Count -gt 0) { - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Updating configuration for [$($ComponentName)]...", $([Constants]::MessageType.Info)) - $logger.PublishLogMessage($NewConfigurationList) - - #uncomment below line to see data in output console window - #$(( $NewConfigurationList | Out-String).TrimEnd()) | Write-Host -ForegroundColor $([Constants]::MessageType.Info) - - #Updating the new configuration values - $AzTSAppConfigurationSettings = Set-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -AppSettings $NewAppSettings -ErrorAction Stop - - $logger.PublishCustomMessage("Updated configuration for [$($ComponentName)]." , $([Constants]::MessageType.Update)) - - if ($ExistingConfigurationList.Count -gt 0) - { - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage("Existing Configuration found:") - $logger.PublishLogMessage($ExistingConfigurationList) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - } - } - else { - $logger.PublishCustomMessage("Entered configuration values are already present in $ComponentName.", $([Constants]::MessageType.Error)) - $logger.PublishLogMessage("Existing Configuration found:") - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage($ExistingConfigurationList) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - break - } - } - catch { - $logger.PublishCustomMessage("Error occurred while updating Configuration. Error: $($_)", $([Constants]::MessageType.Error)) - break - } - } - if ($FeatureConfigValueArray.Count -gt 0) { - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Successfully added configuration(s) for [$FeatureName] feature." , $([Constants]::MessageType.Update)) - } - $logger.PublishLogFilePath() - } - else { - $availableFeatureName = $JsonContent.FeatureName -join ", " - $logger.PublishCustomMessage("The value entered for FeatureName: $FeatureName is invalid. Valid values are [$availableFeatureName]. Exiting..." , $([Constants]::MessageType.Error)) - $logger.PublishLogFilePath() - } -} - - -function get-hash([string]$textToHash) { - $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider - $toHash = [System.Text.Encoding]::UTF8.GetBytes($textToHash) - $hashByteArray = $hasher.ComputeHash($toHash) - $result = [string]::Empty; - foreach ($byte in $hashByteArray) { - $result += "{0:X2}" -f $byte - } - return $result; -} - -class Constants { - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [string] $DoubleDashLine = "================================================================================" - static [string] $SingleDashLine = "--------------------------------------------------------------------------------" -} - -class Logger { - [string] $logFilePath = ""; - - Logger([string] $HostSubscriptionId) { - $logFolerPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\FeatureUpdate\Subscriptions\$($HostSubscriptionId.replace('-','_'))"; - $logFileName = "\$('ConfigurationUpdateLogs_' + $(Get-Date).ToString('yyyyMMddhhmm') + '.txt')"; - $this.logFilePath = $logFolerPath + $logFileName - # Create folder if not exist - if (-not (Test-Path -Path $logFolerPath)) { - New-Item -ItemType Directory -Path $logFolerPath | Out-Null - } - # Create log file - - New-Item -Path $this.logFilePath -ItemType File | Out-Null - - } - - PublishCustomMessage ([string] $message, [string] $foregroundColor) { - $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor $foregroundColor - } - - PublishCustomMessage ([string] $message) { - $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor White - } - - PublishLogMessage ([string] $message) { - $($message) | Add-Content $this.logFilePath - } - - PublishLogMessage ([hashtable] $message) { - $($message) | Format-Table -Wrap -AutoSize | Out-File $this.logFilePath -Append utf8 -Width 100 - } - - PublishLogFilePath() { - Write-Host $([Constants]::DoubleDashLine)"`r`nLogs have been exported to: $($this.logFilePath)`n"$([Constants]::DoubleDashLine) -ForegroundColor Cyan - } +<### +# Overview: + This script is used to add feature configuration value for AzTS in a Subscription. + +# Instructions to execute the script: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to add feature configuration value for AzTS in a Subscription. Refer `Examples`, below. + +# Examples: + To add user's object id into configuration of AzTS: + Add-AztsFeatureConfigurationValues -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FeatureName "CMET" -FeatureConfigValues "00000000-xxxx-0000-xxxx-000000000001,00000000-xxxx-0000-xxxx-000000000002,00000000-xxxx-0000-xxxx-000000000003" + +###> + +function Add-AztsFeatureConfigurationValues { + Param( + [string] + [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which Azure Tenant Security Solution is installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where Azure Tenant Security Solution is installed.")] + $ScanHostRGName, + + [string] + [Parameter(Mandatory = $false, HelpMessage = "File path for Azts Control Configuration file AztsControlConfiguration.json.")] + $FilePath = "./AddAztsFeatureConfiguraValuesTemplate.json", + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Azts Feature Name to add Configuration values. Values for this parameter are 'CMET', 'MG Compliance Initiate Editor'")] + [ValidateSet("CMET", "MG Compliance Initiate Editor")] + $FeatureName, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Pass multiple Configuration Value as comma seperated")] + $FeatureConfigValues + ) + + $inputParams = $PSBoundParameters + $logger = [Logger]::new($SubscriptionId) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Add-AztsFeatureConfigurationValues `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Starting process to add Configuration Values for $FeatureName feature. This may take 2-3 mins...", $([Constants]::MessageType.Info)) + + + #checking if ConfigurationValues enetered is having single or multiple values + if ( !$FeatureConfigValues.Contains(",")) { + $FeatureConfigValues += ","; + } + + #Splitting the UserObjectIds from (,) + [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); + + # Set the context to host subscription + Set-AzContext -SubscriptionId $SubscriptionId | Out-null + + # AzTS resources name preparation + $ResourceId = '/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId, $ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0, 5).ToString().ToLower() + + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage("Loading File: [$($FilePath)]...") + + # Getting Control Configuration from file path + if (-not (Test-Path -Path $FilePath)) { + $logger.PublishCustomMessage("ERROR: File - $($FilePath) not found. Exiting...", $([Constants]::MessageType.Error)) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + break + } + + $JsonContent = Get-content -path $FilePath | ConvertFrom-Json + + $logger.PublishLogMessage("Loading File: [$($FilePath)] completed.") + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + + $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } + if ($null -ne $FilteredfeatureSetting) { + + + #Checking if any Dependent Features needs to be enabled + foreach ($dependentConfiguration in $FilteredfeatureSetting.ConfigurationDependencies) { + $IntPrivilegedEditorIds = @() + $NewAppSettings = @{} + $NewConfigurationList = @{} + $ExistingConfigurationList = @{} + + $ComponentName = $dependentConfiguration.ComponentName + $ResourceHash; + + #Getting Existing configuration value + $AzTSAppConfigurationSettings = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -ErrorAction Stop + + foreach ($Configuration in $dependentConfiguration.Configuration) { + if ($null -ne $AzTSAppConfigurationSettings) { + + if ($FeatureName -ieq "CMET" -or $FeatureName -ieq "MG Compliance Initiate Editor") { + + #Splitting the UserObjectIds from (,) + [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); + $IntPrivilegedEditorIds = @() + # Existing app settings + $AppSettings = $AzTSAppConfigurationSettings.SiteConfig.AppSettings + + # Moving existing app settings in new app settings list to avoid being overridden + ForEach ($appSetting in $AppSettings) { + $NewAppSettings[$appSetting.Name] = $appSetting.Value + + #Checking if Configuration Key exist to get the exisiting array value + if ($appSetting.Name.Contains($Configuration)) { + + $appSettingNameArray = $appSetting.Name.Split('_'); + $IntPrivilegedEditorIds += $appSettingNameArray[6]; + + #checking if the Configuration value exist (Key and Value) to avoid duplication + if ($FeatureConfigValueArray.Contains($appSetting.Value)) { + $ExistingConfigurationList["$($appSetting.Name)"] =$appSetting.Value + $FeatureConfigValueArray.Remove($appSetting.Value); + } + } + } + + #If exisitng configuration values does not exist, then setting it to 0 + if ($IntPrivilegedEditorIds.Count -eq 0) { + $IntPrivilegedEditorIds = 0 + } + + #Fetching max value + $IntPrivilegedEditorIdsMaxValue = ($IntPrivilegedEditorIds | Measure-Object -Maximum).Maximum + + #Adding configuration + foreach ($FeatureConfig in $FeatureConfigValueArray) { + if ($FeatureConfig -ne "") { + $NewAppSettings["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig + $NewConfigurationList["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig + $IntPrivilegedEditorIdsMaxValue++ + } + } + + } + } + } + + try { + if ($FeatureConfigValueArray.Count -gt 0) { + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Updating configuration for [$($ComponentName)]...", $([Constants]::MessageType.Info)) + $logger.PublishLogMessage($NewConfigurationList) + + #uncomment below line to see data in output console window + #$(( $NewConfigurationList | Out-String).TrimEnd()) | Write-Host -ForegroundColor $([Constants]::MessageType.Info) + + #Updating the new configuration values + $AzTSAppConfigurationSettings = Set-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -AppSettings $NewAppSettings -ErrorAction Stop + + $logger.PublishCustomMessage("Updated configuration for [$($ComponentName)]." , $([Constants]::MessageType.Update)) + + if ($ExistingConfigurationList.Count -gt 0) + { + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage("Existing Configuration found:") + $logger.PublishLogMessage($ExistingConfigurationList) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + } + } + else { + $logger.PublishCustomMessage("Entered configuration values are already present in $ComponentName.", $([Constants]::MessageType.Error)) + $logger.PublishLogMessage("Existing Configuration found:") + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage($ExistingConfigurationList) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + break + } + } + catch { + $logger.PublishCustomMessage("Error occurred while updating Configuration. Error: $($_)", $([Constants]::MessageType.Error)) + break + } + } + if ($FeatureConfigValueArray.Count -gt 0) { + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Successfully added configuration(s) for [$FeatureName] feature." , $([Constants]::MessageType.Update)) + } + $logger.PublishLogFilePath() + } + else { + $availableFeatureName = $JsonContent.FeatureName -join ", " + $logger.PublishCustomMessage("The value entered for FeatureName: $FeatureName is invalid. Valid values are [$availableFeatureName]. Exiting..." , $([Constants]::MessageType.Error)) + $logger.PublishLogFilePath() + } +} + + +function get-hash([string]$textToHash) { + $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider + $toHash = [System.Text.Encoding]::UTF8.GetBytes($textToHash) + $hashByteArray = $hasher.ComputeHash($toHash) + $result = [string]::Empty; + foreach ($byte in $hashByteArray) { + $result += "{0:X2}" -f $byte + } + return $result; +} + +class Constants { + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [string] $DoubleDashLine = "================================================================================" + static [string] $SingleDashLine = "--------------------------------------------------------------------------------" +} + +class Logger { + [string] $logFilePath = ""; + + Logger([string] $HostSubscriptionId) { + $logFolerPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\FeatureUpdate\Subscriptions\$($HostSubscriptionId.replace('-','_'))"; + $logFileName = "\$('ConfigurationUpdateLogs_' + $(Get-Date).ToString('yyyyMMddhhmm') + '.txt')"; + $this.logFilePath = $logFolerPath + $logFileName + # Create folder if not exist + if (-not (Test-Path -Path $logFolerPath)) { + New-Item -ItemType Directory -Path $logFolerPath | Out-Null + } + # Create log file + + New-Item -Path $this.logFilePath -ItemType File | Out-Null + + } + + PublishCustomMessage ([string] $message, [string] $foregroundColor) { + $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor $foregroundColor + } + + PublishCustomMessage ([string] $message) { + $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor White + } + + PublishLogMessage ([string] $message) { + $($message) | Add-Content $this.logFilePath + } + + PublishLogMessage ([hashtable] $message) { + $($message) | Format-Table -Wrap -AutoSize | Out-File $this.logFilePath -Append utf8 -Width 100 + } + + PublishLogFilePath() { + Write-Host $([Constants]::DoubleDashLine)"`r`nLogs have been exported to: $($this.logFilePath)`n"$([Constants]::DoubleDashLine) -ForegroundColor Cyan + } } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/AddAzTSFeatureConfigurationValuesTemplate.json b/TemplateFiles/DeploymentFiles/AddAzTSFeatureConfigurationValuesTemplate.json index 946fc575..8e846b7d 100644 --- a/TemplateFiles/DeploymentFiles/AddAzTSFeatureConfigurationValuesTemplate.json +++ b/TemplateFiles/DeploymentFiles/AddAzTSFeatureConfigurationValuesTemplate.json @@ -1,20 +1,20 @@ -[ - { - "FeatureName": "CMET", - "ConfigurationDependencies": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": ["ControlActionItem__0__PrivilegedEditorIds__"] - } - ] - }, - { - "FeatureName": "MG Compliance Initiate Editor", - "ConfigurationDependencies": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": ["ControlActionItem__0__PrivilegedEditorIds__","ComplianceInitiativeActionItem__0__PrivilegedEditorIds__"] - } - ] - } +[ + { + "FeatureName": "CMET", + "ConfigurationDependencies": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": ["ControlActionItem__0__PrivilegedEditorIds__"] + } + ] + }, + { + "FeatureName": "MG Compliance Initiate Editor", + "ConfigurationDependencies": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": ["ControlActionItem__0__PrivilegedEditorIds__","ComplianceInitiativeActionItem__0__PrivilegedEditorIds__"] + } + ] + } ] \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/AzTSConsolidatedSetup.ps1 b/TemplateFiles/DeploymentFiles/AzTSConsolidatedSetup.ps1 index 48c4dff7..e23435d1 100644 --- a/TemplateFiles/DeploymentFiles/AzTSConsolidatedSetup.ps1 +++ b/TemplateFiles/DeploymentFiles/AzTSConsolidatedSetup.ps1 @@ -1,551 +1,551 @@ -function Install-AzSKTenantSecuritySolutionConsolidated -{ - <# - .SYNOPSIS - This command would help in installing Azure Tenant Security Solution in your subscription. - .DESCRIPTION - This command will installing all components of Azure Tenant Security Solution which runs security scan on subscription in a Tenant. - Security scan results will be populated in Log Analytics workspace and Azure Storage account which is configured during installation. - - .PARAMETER ScanningIdentityHostSubId - Subscription id in which scanner identity (MI) is to be created. - .PARAMETER ScanningIdentityHostRGName - Name of ResourceGroup where scanner identity (MI) will be created. - .PARAMETER ScanningIdentityName - Name of the scanning identity (MI) to be used by the scanner. - .PARAMETER SubscriptionId - Subscription id in which Azure Tenant Security Solution needs to be installed. - .PARAMETER ScanHostRGName - Name of ResourceGroup where setup resources will be created. - .PARAMETER SubscriptionsToScan - List of subscription(s) to be scanned by Azure Tenant Security scanning solution. - .PARAMETER ManagementGroupsToScan - List of target management group(s) to be scanned by Azure Tenant Security scanning solution. - .PARAMETER GrantGraphPermissionToScanIdentity - Switch to grant Graph permission to scanning identity. - .PARAMETER GrantGraphPermissionToInternalIdentity - Specify if internal managed identity to be granted Graph permission. - .PARAMETER ScanIdentityHasGraphPermission - Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission. - .PARAMETER SetupAzModules - Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system. - .PARAMETER Location - Location where all resources will get created. Default location is EastUS2. - .PARAMETER SendAlertNotificationToEmailIds - Email ids to which alert notification should be sent. - .PARAMETER EnableVnetIntegration - Switch to enable vnet integration. Resources required for vnet setup will be deployed only if this switch is ON. - .PARAMETER EnableAutoUpdater - Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. - .PARAMETER EnableAzTSUI - Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan. - .PARAMETER EnableWAF - Switch to enable WAF. Resources required for implementing WAF will be deployed only if this switch is ON. - .PARAMETER TemplateFilePath - Azure ARM template path used to deploy Azure Tenant Security Solution. - .PARAMETER TemplateParameters - Azure ARM template parameters used to deploy Azure Tenant Security Solution. - .PARAMETER SendUsageTelemetry - Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features. - .PARAMETER CentralStorageAccountConnectionString - Connection string of the storage account to be used to store the scan logs centrally. - .NOTES - - - .LINK - https://aka.ms/azts-docs - - #> - - Param( - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which scanner identity is to be created.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Subscription id in which scanner identity is to be created.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Subscription id in which scanner identity is to be created.")] - $ScanningIdentityHostSubId, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where scanner identity will be created.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Name of ResourceGroup where scanner identity will be created.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Name of ResourceGroup where scanner identity will be created.")] - $ScanningIdentityHostRGName, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of the scanning identity to be used by the scanner.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Name of the scanning identity to be used by the scanner.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Name of the scanning identity to be used by the scanner.")] - $ScanningIdentityName, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - $ScanHostRGName = "AzSK-AzTS-RG", - - [string[]] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="List of subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="List of subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="List of subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] - [Alias("SubscriptionsToScan")] - $TargetSubscriptionIds = @(), - - [string[]] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] - [Alias("ManagementGroupsToScan")] - $TargetManagementGroupNames = @(), - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify to grant Graph permission to scanning identity. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify to grant Graph permission to scanning identity. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify to grant Graph permission to scanning identity. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - $GrantGraphPermissionToScanIdentity = $false, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify if internal managed identity to be granted Graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify if internal managed identity to be granted Graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify if internal managed identity to be granted Graph permission.")] - $GrantGraphPermissionToInternalIdentity = $false, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Location where all resources and scanner MI will get created. Default location is EastUS2.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Location where all resources and scanner MI will get created. Default location is EastUS2.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Location where all resources and scanner MI will get created. Default location is EastUS2.")] - $Location = 'EastUS2', - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] - $TemplateFilePath = ".\AzTSDeploymentTemplate.json", - - [Hashtable] - [Parameter(Mandatory = $false, ParameterSetName = "Default")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] - $TemplateParameters = @{}, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - $SendUsageTelemetry = $false, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system.")] - $SetupAzModules = $false, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - $ScanIdentityHasGraphPermission = $false, - - [string[]] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Email ids to which alert notification should be sent.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Email ids to which alert notification should be sent.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Email ids to which alert notification should be sent.")] - [Alias("SREEmailIds")] - $SendAlertNotificationToEmailIds = @(), - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] - $AzureEnvironmentName = "AzureCloud", - - [switch] - [Parameter(Mandatory = $false, HelpMessage="Switch to enable vnet integration. Resources required for vnet setup will be deployed only if this switch is ON.")] - $EnableVnetIntegration = $false, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - [Alias("EnableAutoUpdates")] - $EnableAutoUpdater, - - [switch] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] - $EnableAzTSUI, - - [switch] - [Parameter(Mandatory = $false, HelpMessage="Switch to enable WAF. Resources required for implementing WAF will be deployed only if this switch is ON.")] - $EnableWAF = $false, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Connection string of the storage account to be used to store the scan logs centrally.")] - [Alias("StorageAccountConnectionString")] - $CentralStorageAccountConnectionString - ) - - Begin - { - $inputParams = $PSBoundParameters - # Load AzTS Setup script in session - . ".\AzTSSetup.ps1" - # Get logger instance - $logger = [Logger]::new($SubscriptionId) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Install-AzSKTenantSecuritySolutionConsolidated `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Starting Azure Tenant Security Solution installation. This may take 5 mins...", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($($([Constants]::QuickInstallSolutionInstructionMsg)), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) - - if ($SetupAzModules) - { - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("**Step 0**: Validate prerequisites.", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $allPrerequisiteMet = Setup-Prerequisites - - if (-not $allPrerequisiteMet) - { - $logger.PublishCustomMessage("One or more required Az modules are missing. AzTS setup will be skipped.", $([Constants]::MessageType.Error)) - break; - } - else - { - $logger.PublishLogMessage("All required modules are available.") - } - - } - - - # Disconnect from current AD/Azure session - try - { - #Disconnect-AzAccount - #Disconnect-AzureAD - } - catch - { - # If user is not already connected to Azure, Disconnect command will return an error. In this case, please ignore the error and continue to next step. - } - - # Connect to AzureAD and AzAccount - #Connect-AzAccount -Tenant $HostTenantId - #Connect-AzureAD -TenantId $HostTenantId - - ############################################################################## - $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("**Step 1.A**: Set up scanning identity.", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("Setting up Azure Tenant Security scanner identity...`n", $([Constants]::MessageType.Info)) - # Step 1: Setting up scanning identity - # 1- Create scanner MI and grant 'Reader' permission on target subscriptions. - $UserAssignedIdentity = Set-AzSKTenantSecuritySolutionScannerIdentity -SubscriptionId $ScanningIdentityHostSubId ` - -ResourceGroupName $ScanningIdentityHostRGName ` - -Location $Location ` - -UserAssignedIdentityName $ScanningIdentityName ` - -TargetSubscriptionIds $TargetSubscriptionIds ` - -TargetManagementGroupNames $TargetManagementGroupNames ` - -ConsolidatedSetup - - if ([string]::IsNullOrWhiteSpace($UserAssignedIdentity)) - { - # No need to log Error message, it's done by the above command itself - # If UserAssignedIdentity is null stop script execution, as it represent error occurred while setting up scanner identity - $logger.PublishLogMessage("Error occurred while setting up scanning identity.") - $logger.PublishLogFilePath() - return; - } - else - { - $logger.PublishLogMessage("Resource id and principal Id generated for user identity:`r`nPrincipalId: $($UserAssignedIdentity.PrincipalId) `r`nResourceId: $($UserAssignedIdentity.Id) `r`n$([Constants]::DoubleDashLine)") - } - - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("**Step 1.B**: Grant Graph permissions to scanning identity.", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - - $graphPermissionGranted = $false - if ($GrantGraphPermissionToScanIdentity -eq $true) - { - try{ - Grant-AzSKGraphPermissionToUserAssignedIdentity -UserAssignedIdentityObjectId $UserAssignedIdentity.PrincipalId -MSGraphPermissionsRequired @("PrivilegedAccess.Read.AzureResources", "Directory.Read.All") -ADGraphPermissionsRequired @("Directory.Read.All") - $graphPermissionGranted = $true - $logger.PublishLogMessage("Graph permissions granted to scanning identity.") - } - catch{ - $graphPermissionGranted = $false - $logger.PublishLogMessage("Error occurred while granting Graph permissions to scanning identity.") - } - } - else - { - $graphPermissionGranted = $false - $logger.PublishCustomMessage("Skipped: Graph permissions not granted to scanner identity.", $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage($($([constants]::ScanningIdentitySetupNextSteps)), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - } - - # ***** Initialize required parameters ****** - - # Subscription id in which Azure Tenant Security Solution needs to be installed. - $HostSubscriptionId = $SubscriptionId - - # Specify if scanner MI has Graph permission. This is to exclude controls dependent on Graph API reponse from the scan result, if scanner identity does not have graph permission. - # Users can choose to either grant Graph permission in consoliadted command itself (use -GrantGraphPermissionToScanIdentity) or Grant manually (and pass -ScanIdentityHasGraphPermission switch) - $ScanIdentityHasGraphPermission = $graphPermissionGranted -or $ScanIdentityHasGraphPermission - - ############################################################################## - - # Step 2: Create Azure AD application for secure authentication - # Setup AD application for AzTS UI and API - $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("**Step 2**: Setup AD application for AzTS UI and API.", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - - if ($EnableAzTSUI -eq $true) - { - $ADApplicationDetails = Set-AzSKTenantSecurityADApplication -SubscriptionId $HostSubscriptionId -ScanHostRGName $ScanHostRGName -ConsolidatedSetup - - if ($ADApplicationDetails -eq $null) - { - # No need to log Error message, it's done by the above command itself - # If ADApplicationDetails is null stop script execution, as it represent error occurred while setting up AD App - $logger.PublishLogFilePath() - return; - } - else - { - # Else $ADApplicationDetails object contains UI & Web API AD App details - $logger.PublishLogMessage("App Id of UI & Web API Azure AD Applications:`r`WebAPIAzureADAppId: $($ADApplicationDetails.WebAPIAzureADAppId) `r`nUIAzureADAppId: $($ADApplicationDetails.UIAzureADAppId) `r`n$([Constants]::DoubleDashLine)") - } - - } - else{ - $logger.PublishCustomMessage("Skipped: This step has been skipped as AzTS UI is not enabled for the setup.", $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - } - - ############################################################################## - # Step 3. Set context and deploy AzTS resources in Host RG and Subscription **** - $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("**Step 3.A**: Install AzTS setup.", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("Started setting up Azure Tenant Security Solution..." ,$([Constants]::MessageType.Info)) - # Set the context to hosting subscription - $azContext = Set-AzContext -SubscriptionId $HostSubscriptionId - - # Invoke install solution command - $CommandArguments = @{ - SubscriptionId = $HostSubscriptionId - ScanHostRGName = $ScanHostRGName - Location = $Location - ScanIdentityId = $UserAssignedIdentity.Id - AzureEnvironmentName = $AzureEnvironmentName - SendAlertNotificationToEmailIds = $SendAlertNotificationToEmailIds - TemplateFilePath = $TemplateFilePath - TemplateParameters = $TemplateParameters - SendUsageTelemetry = $SendUsageTelemetry - ScanIdentityHasGraphPermission = $ScanIdentityHasGraphPermission - EnableAutoUpdater = $EnableAutoUpdater - EnableVnetIntegration = $EnableVnetIntegration - EnableWAF = $EnableWAF - ConsolidatedSetup = $true - } - - if ($EnableAzTSUI -eq $true) - { - $CommandArguments += @{ - EnableAzTSUI = $true - WebAPIAzureADAppId = $ADApplicationDetails.WebAPIAzureADAppId - UIAzureADAppId = $ADApplicationDetails.UIAzureADAppId - } - } - - if (-not [string]::IsNullOrWhiteSpace($CentralStorageAccountConnectionString)) - { - $CommandArguments.Add('CentralStorageAccountConnectionString', $CentralStorageAccountConnectionString) - } - - $DeploymentResult = Install-AzSKTenantSecuritySolution @CommandArguments - - if ((($DeploymentResult | Measure-Object).Count -eq 0) -or (($DeploymentResult.Outputs | Measure-Object).Count -eq 0)) - { - # No need to log Error message in console, it's done by the above command itself - # If deployment result is null stop script execution, as it represent error occurred while deploying AzTS resources - $logger.PublishLogMessage("Error occurred during deployment of AzTS components in subscription.") - $logger.PublishLogFilePath() - return; - } - - # Save internal user-assigned managed identity name generated using below command. This will be used to grant Graph permission to internal MI. - $InternalIdentityObjectId = $DeploymentResult.Outputs.internalMIObjectId.Value - $logger.PublishLogMessage("Internal MI Object Id: $($InternalIdentityObjectId)") - $logger.PublishLogMessage("Deployment of AzTS components completed.") - - $deploymentOutputs = $DeploymentResult.Outputs | ConvertTo-Json | ConvertFrom-Json - - $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - $logger.PublishCustomMessage("**Step 3.B**: Grant internal MI required Graph permissions.", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - - # Grant internal MI 'User.Read.All' permission. - if ($GrantGraphPermissionToInternalIdentity -eq $true) - { - # No need for exception handling, present in command itself - Grant-AzSKGraphPermissionToUserAssignedIdentity ` - -UserAssignedIdentityObjectId $InternalIdentityObjectId ` - -MSGraphPermissionsRequired @('User.Read.All') - - } - else - { - $logger.PublishCustomMessage("Skipped: Graph permissions not granted to internal MI identity.", $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage($($([constants]::InternalIdentitySetupNextSteps)),$([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::SingleDashLine)) - } - - $UIUrl = [string]::Empty - - if($EnableAzTSUI -and $deploymentOutputs.PSObject.Properties.Name.Contains("uiAppName")) - { - $azureUIAppName= $DeploymentResult.Outputs.uiAppName.Value - $UIUrl = $([string]::Concat($([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $azureUIAppName)), "/")) - } - - $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) - if($EnableAzTSUI -and $EnableWAF) - { - $AzTSUIFrontDoorUrl = $DeploymentResult.Outputs.azTSUIFrontDoorUrl.Value - $logger.PublishCustomMessage("$([Constants]::NextSteps -f $AzTSUIFrontDoorUrl)", $([Constants]::MessageType.Info)); - $logger.PublishCustomMessage("IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($AzTSUIFrontDoorUrl)", $([Constants]::MessageType.Warning)) - } - elseif($EnableAzTSUI) - { - $logger.PublishCustomMessage("$([Constants]::NextSteps -f $UIUrl)", $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($UIUrl)", $([Constants]::MessageType.Warning)) - } - else - { - $logger.PublishCustomMessage("$([Constants]::NextSteps -f $UIUrl)", $([Constants]::MessageType.Info)) - } - $logger.PublishLogFilePath() - - return $DeploymentResult - - } -} - -function Setup-Prerequisites { - <# - .SYNOPSIS - Checks if all required Az modules are present, else, sets them up. - .DESCRIPTION - Checks if all required Az modules are present, else, sets them up. - Includes installing any required Azure modules. - .INPUTS - None. You cannot pipe objects to Setup-Prerequisites. - .OUTPUTS - Boolean. 'True' if all pre-requisites are met 'False' otherwise. - .EXAMPLE - PS> Setup-Prerequisites - .LINK - None - #> - $allPrerequisiteMet = $true - # List of required modules - $requiredModules = @{ - "Az.Accounts" = "2.9.0"; - "Az.Resources" = "1.10.0"; - "Az.Storage" = "2.0.0"; - "Az.ManagedServiceIdentity" = "0.7.3"; - "Az.Monitor" = "1.5.0"; - "Az.OperationalInsights" = "1.3.4"; - "Az.ApplicationInsights" = "1.0.3"; - "Az.Websites" = "2.8.1"; - "Az.Network" = "2.5.0"; - "Az.FrontDoor" = "1.8.0"; - "Az.CosmosDB" = "1.8.2"; - "AzureAD" = "2.0.2.130"; - } - - $requiredModuleNames = @() - $requiredModules.Keys | ForEach-Object { $requiredModuleNames += $_.ToString()} - - try{ - - Write-Host "Checking if all required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) - $availableModules = $(Get-Module -ListAvailable $requiredModuleNames -ErrorAction Stop) | Select-Object Name, Version | Sort-Object -Property "Version" -Descending - - # Check if the required modules are installed. - $installedModules = @() - $missingModules = @() - $requiredModules.Keys | ForEach-Object { - $requiredModule = "" | Select-Object "Name", "RequiredVersion" - $requiredModule.RequiredVersion = $requiredModules[$_] - $requiredModule.Name = $_ - $modulePresent = $availableModules | Where-Object {($_.Name -eq $requiredModule.Name) -and ($_.Version -ge $requiredModule.RequiredVersion)} | Select-Object -First 1 - if ($modulePresent) - { - $installedModules += $requiredModule - } - else - { - $missingModules += $requiredModule - } - } - - if (($missingModules | Measure-Object).Count -eq 0) - { - $allPrerequisiteMet = $true - Write-Host "All required modules are present." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - Write-Host "Installing missing modules..." -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "Following modules (with required version) are not present:" -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $($missingModules | FT | Out-String) -ForegroundColor $([Constants]::MessageType.Info) - - $userChoice = Read-Host -Prompt "Do you want to install all missing modules (Y/N)" - if ($userChoice -eq "Y") - { - $missingModules | ForEach-Object { - Write-Host "Installing $($_.Name) module..." -ForegroundColor $([Constants]::MessageType.Info) - Install-Module -Name $_.Name -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop -Force - if ($?) - { - $allPrerequisiteMet = $allPrerequisiteMet -and $true - Write-Host "Successfully installed $($_.Name) module." -ForegroundColor $([Constants]::MessageType.Update) - } - else - { - $allPrerequisiteMet = $allPrerequisiteMet -and $false - Write-Host "Unable to install $($_.Name) module." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - - } - else - { - $allPrerequisiteMet = $false - Write-Host "Module installation skipped based on user's choice." -ForegroundColor $([Constants]::MessageType.Info) - } - } - } - catch - { - $allPrerequisiteMet = $false - } - - return $allPrerequisiteMet; -} +function Install-AzSKTenantSecuritySolutionConsolidated +{ + <# + .SYNOPSIS + This command would help in installing Azure Tenant Security Solution in your subscription. + .DESCRIPTION + This command will installing all components of Azure Tenant Security Solution which runs security scan on subscription in a Tenant. + Security scan results will be populated in Log Analytics workspace and Azure Storage account which is configured during installation. + + .PARAMETER ScanningIdentityHostSubId + Subscription id in which scanner identity (MI) is to be created. + .PARAMETER ScanningIdentityHostRGName + Name of ResourceGroup where scanner identity (MI) will be created. + .PARAMETER ScanningIdentityName + Name of the scanning identity (MI) to be used by the scanner. + .PARAMETER SubscriptionId + Subscription id in which Azure Tenant Security Solution needs to be installed. + .PARAMETER ScanHostRGName + Name of ResourceGroup where setup resources will be created. + .PARAMETER SubscriptionsToScan + List of subscription(s) to be scanned by Azure Tenant Security scanning solution. + .PARAMETER ManagementGroupsToScan + List of target management group(s) to be scanned by Azure Tenant Security scanning solution. + .PARAMETER GrantGraphPermissionToScanIdentity + Switch to grant Graph permission to scanning identity. + .PARAMETER GrantGraphPermissionToInternalIdentity + Specify if internal managed identity to be granted Graph permission. + .PARAMETER ScanIdentityHasGraphPermission + Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission. + .PARAMETER SetupAzModules + Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system. + .PARAMETER Location + Location where all resources will get created. Default location is EastUS2. + .PARAMETER SendAlertNotificationToEmailIds + Email ids to which alert notification should be sent. + .PARAMETER EnableVnetIntegration + Switch to enable vnet integration. Resources required for vnet setup will be deployed only if this switch is ON. + .PARAMETER EnableAutoUpdater + Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. + .PARAMETER EnableAzTSUI + Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan. + .PARAMETER EnableWAF + Switch to enable WAF. Resources required for implementing WAF will be deployed only if this switch is ON. + .PARAMETER TemplateFilePath + Azure ARM template path used to deploy Azure Tenant Security Solution. + .PARAMETER TemplateParameters + Azure ARM template parameters used to deploy Azure Tenant Security Solution. + .PARAMETER SendUsageTelemetry + Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features. + .PARAMETER CentralStorageAccountConnectionString + Connection string of the storage account to be used to store the scan logs centrally. + .NOTES + + + .LINK + https://aka.ms/azts-docs + + #> + + Param( + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which scanner identity is to be created.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Subscription id in which scanner identity is to be created.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Subscription id in which scanner identity is to be created.")] + $ScanningIdentityHostSubId, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where scanner identity will be created.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Name of ResourceGroup where scanner identity will be created.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Name of ResourceGroup where scanner identity will be created.")] + $ScanningIdentityHostRGName, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of the scanning identity to be used by the scanner.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Name of the scanning identity to be used by the scanner.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Name of the scanning identity to be used by the scanner.")] + $ScanningIdentityName, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + $ScanHostRGName = "AzSK-AzTS-RG", + + [string[]] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="List of subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="List of subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="List of subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] + [Alias("SubscriptionsToScan")] + $TargetSubscriptionIds = @(), + + [string[]] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] + [Alias("ManagementGroupsToScan")] + $TargetManagementGroupNames = @(), + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify to grant Graph permission to scanning identity. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify to grant Graph permission to scanning identity. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify to grant Graph permission to scanning identity. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + $GrantGraphPermissionToScanIdentity = $false, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify if internal managed identity to be granted Graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify if internal managed identity to be granted Graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify if internal managed identity to be granted Graph permission.")] + $GrantGraphPermissionToInternalIdentity = $false, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Location where all resources and scanner MI will get created. Default location is EastUS2.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Location where all resources and scanner MI will get created. Default location is EastUS2.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Location where all resources and scanner MI will get created. Default location is EastUS2.")] + $Location = 'EastUS2', + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] + $TemplateFilePath = ".\AzTSDeploymentTemplate.json", + + [Hashtable] + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] + $TemplateParameters = @{}, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + $SendUsageTelemetry = $false, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to validate required modules. If passed command will check needed modules with required version, will install required modules if not available in the system.")] + $SetupAzModules = $false, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + $ScanIdentityHasGraphPermission = $false, + + [string[]] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Email ids to which alert notification should be sent.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Email ids to which alert notification should be sent.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Email ids to which alert notification should be sent.")] + [Alias("SREEmailIds")] + $SendAlertNotificationToEmailIds = @(), + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] + $AzureEnvironmentName = "AzureCloud", + + [switch] + [Parameter(Mandatory = $false, HelpMessage="Switch to enable vnet integration. Resources required for vnet setup will be deployed only if this switch is ON.")] + $EnableVnetIntegration = $false, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + [Alias("EnableAutoUpdates")] + $EnableAutoUpdater, + + [switch] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] + $EnableAzTSUI, + + [switch] + [Parameter(Mandatory = $false, HelpMessage="Switch to enable WAF. Resources required for implementing WAF will be deployed only if this switch is ON.")] + $EnableWAF = $false, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Connection string of the storage account to be used to store the scan logs centrally.")] + [Alias("StorageAccountConnectionString")] + $CentralStorageAccountConnectionString + ) + + Begin + { + $inputParams = $PSBoundParameters + # Load AzTS Setup script in session + . ".\AzTSSetup.ps1" + # Get logger instance + $logger = [Logger]::new($SubscriptionId) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Install-AzSKTenantSecuritySolutionConsolidated `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Starting Azure Tenant Security Solution installation. This may take 5 mins...", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($($([Constants]::QuickInstallSolutionInstructionMsg)), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) + + if ($SetupAzModules) + { + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("**Step 0**: Validate prerequisites.", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $allPrerequisiteMet = Setup-Prerequisites + + if (-not $allPrerequisiteMet) + { + $logger.PublishCustomMessage("One or more required Az modules are missing. AzTS setup will be skipped.", $([Constants]::MessageType.Error)) + break; + } + else + { + $logger.PublishLogMessage("All required modules are available.") + } + + } + + + # Disconnect from current AD/Azure session + try + { + #Disconnect-AzAccount + #Disconnect-AzureAD + } + catch + { + # If user is not already connected to Azure, Disconnect command will return an error. In this case, please ignore the error and continue to next step. + } + + # Connect to AzureAD and AzAccount + #Connect-AzAccount -Tenant $HostTenantId + #Connect-AzureAD -TenantId $HostTenantId + + ############################################################################## + $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("**Step 1.A**: Set up scanning identity.", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("Setting up Azure Tenant Security scanner identity...`n", $([Constants]::MessageType.Info)) + # Step 1: Setting up scanning identity + # 1- Create scanner MI and grant 'Reader' permission on target subscriptions. + $UserAssignedIdentity = Set-AzSKTenantSecuritySolutionScannerIdentity -SubscriptionId $ScanningIdentityHostSubId ` + -ResourceGroupName $ScanningIdentityHostRGName ` + -Location $Location ` + -UserAssignedIdentityName $ScanningIdentityName ` + -TargetSubscriptionIds $TargetSubscriptionIds ` + -TargetManagementGroupNames $TargetManagementGroupNames ` + -ConsolidatedSetup + + if ([string]::IsNullOrWhiteSpace($UserAssignedIdentity)) + { + # No need to log Error message, it's done by the above command itself + # If UserAssignedIdentity is null stop script execution, as it represent error occurred while setting up scanner identity + $logger.PublishLogMessage("Error occurred while setting up scanning identity.") + $logger.PublishLogFilePath() + return; + } + else + { + $logger.PublishLogMessage("Resource id and principal Id generated for user identity:`r`nPrincipalId: $($UserAssignedIdentity.PrincipalId) `r`nResourceId: $($UserAssignedIdentity.Id) `r`n$([Constants]::DoubleDashLine)") + } + + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("**Step 1.B**: Grant Graph permissions to scanning identity.", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + + $graphPermissionGranted = $false + if ($GrantGraphPermissionToScanIdentity -eq $true) + { + try{ + Grant-AzSKGraphPermissionToUserAssignedIdentity -UserAssignedIdentityObjectId $UserAssignedIdentity.PrincipalId -MSGraphPermissionsRequired @("PrivilegedAccess.Read.AzureResources", "Directory.Read.All") -ADGraphPermissionsRequired @("Directory.Read.All") + $graphPermissionGranted = $true + $logger.PublishLogMessage("Graph permissions granted to scanning identity.") + } + catch{ + $graphPermissionGranted = $false + $logger.PublishLogMessage("Error occurred while granting Graph permissions to scanning identity.") + } + } + else + { + $graphPermissionGranted = $false + $logger.PublishCustomMessage("Skipped: Graph permissions not granted to scanner identity.", $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage($($([constants]::ScanningIdentitySetupNextSteps)), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + } + + # ***** Initialize required parameters ****** + + # Subscription id in which Azure Tenant Security Solution needs to be installed. + $HostSubscriptionId = $SubscriptionId + + # Specify if scanner MI has Graph permission. This is to exclude controls dependent on Graph API reponse from the scan result, if scanner identity does not have graph permission. + # Users can choose to either grant Graph permission in consoliadted command itself (use -GrantGraphPermissionToScanIdentity) or Grant manually (and pass -ScanIdentityHasGraphPermission switch) + $ScanIdentityHasGraphPermission = $graphPermissionGranted -or $ScanIdentityHasGraphPermission + + ############################################################################## + + # Step 2: Create Azure AD application for secure authentication + # Setup AD application for AzTS UI and API + $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("**Step 2**: Setup AD application for AzTS UI and API.", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + + if ($EnableAzTSUI -eq $true) + { + $ADApplicationDetails = Set-AzSKTenantSecurityADApplication -SubscriptionId $HostSubscriptionId -ScanHostRGName $ScanHostRGName -ConsolidatedSetup + + if ($ADApplicationDetails -eq $null) + { + # No need to log Error message, it's done by the above command itself + # If ADApplicationDetails is null stop script execution, as it represent error occurred while setting up AD App + $logger.PublishLogFilePath() + return; + } + else + { + # Else $ADApplicationDetails object contains UI & Web API AD App details + $logger.PublishLogMessage("App Id of UI & Web API Azure AD Applications:`r`WebAPIAzureADAppId: $($ADApplicationDetails.WebAPIAzureADAppId) `r`nUIAzureADAppId: $($ADApplicationDetails.UIAzureADAppId) `r`n$([Constants]::DoubleDashLine)") + } + + } + else{ + $logger.PublishCustomMessage("Skipped: This step has been skipped as AzTS UI is not enabled for the setup.", $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + } + + ############################################################################## + # Step 3. Set context and deploy AzTS resources in Host RG and Subscription **** + $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("**Step 3.A**: Install AzTS setup.", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("Started setting up Azure Tenant Security Solution..." ,$([Constants]::MessageType.Info)) + # Set the context to hosting subscription + $azContext = Set-AzContext -SubscriptionId $HostSubscriptionId + + # Invoke install solution command + $CommandArguments = @{ + SubscriptionId = $HostSubscriptionId + ScanHostRGName = $ScanHostRGName + Location = $Location + ScanIdentityId = $UserAssignedIdentity.Id + AzureEnvironmentName = $AzureEnvironmentName + SendAlertNotificationToEmailIds = $SendAlertNotificationToEmailIds + TemplateFilePath = $TemplateFilePath + TemplateParameters = $TemplateParameters + SendUsageTelemetry = $SendUsageTelemetry + ScanIdentityHasGraphPermission = $ScanIdentityHasGraphPermission + EnableAutoUpdater = $EnableAutoUpdater + EnableVnetIntegration = $EnableVnetIntegration + EnableWAF = $EnableWAF + ConsolidatedSetup = $true + } + + if ($EnableAzTSUI -eq $true) + { + $CommandArguments += @{ + EnableAzTSUI = $true + WebAPIAzureADAppId = $ADApplicationDetails.WebAPIAzureADAppId + UIAzureADAppId = $ADApplicationDetails.UIAzureADAppId + } + } + + if (-not [string]::IsNullOrWhiteSpace($CentralStorageAccountConnectionString)) + { + $CommandArguments.Add('CentralStorageAccountConnectionString', $CentralStorageAccountConnectionString) + } + + $DeploymentResult = Install-AzSKTenantSecuritySolution @CommandArguments + + if ((($DeploymentResult | Measure-Object).Count -eq 0) -or (($DeploymentResult.Outputs | Measure-Object).Count -eq 0)) + { + # No need to log Error message in console, it's done by the above command itself + # If deployment result is null stop script execution, as it represent error occurred while deploying AzTS resources + $logger.PublishLogMessage("Error occurred during deployment of AzTS components in subscription.") + $logger.PublishLogFilePath() + return; + } + + # Save internal user-assigned managed identity name generated using below command. This will be used to grant Graph permission to internal MI. + $InternalIdentityObjectId = $DeploymentResult.Outputs.internalMIObjectId.Value + $logger.PublishLogMessage("Internal MI Object Id: $($InternalIdentityObjectId)") + $logger.PublishLogMessage("Deployment of AzTS components completed.") + + $deploymentOutputs = $DeploymentResult.Outputs | ConvertTo-Json | ConvertFrom-Json + + $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + $logger.PublishCustomMessage("**Step 3.B**: Grant internal MI required Graph permissions.", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + + # Grant internal MI 'User.Read.All' permission. + if ($GrantGraphPermissionToInternalIdentity -eq $true) + { + # No need for exception handling, present in command itself + Grant-AzSKGraphPermissionToUserAssignedIdentity ` + -UserAssignedIdentityObjectId $InternalIdentityObjectId ` + -MSGraphPermissionsRequired @('User.Read.All') + + } + else + { + $logger.PublishCustomMessage("Skipped: Graph permissions not granted to internal MI identity.", $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage($($([constants]::InternalIdentitySetupNextSteps)),$([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::SingleDashLine)) + } + + $UIUrl = [string]::Empty + + if($EnableAzTSUI -and $deploymentOutputs.PSObject.Properties.Name.Contains("uiAppName")) + { + $azureUIAppName= $DeploymentResult.Outputs.uiAppName.Value + $UIUrl = $([string]::Concat($([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $azureUIAppName)), "/")) + } + + $logger.PublishCustomMessage($([Constants]::DoubleDashLine)) + if($EnableAzTSUI -and $EnableWAF) + { + $AzTSUIFrontDoorUrl = $DeploymentResult.Outputs.azTSUIFrontDoorUrl.Value + $logger.PublishCustomMessage("$([Constants]::NextSteps -f $AzTSUIFrontDoorUrl)", $([Constants]::MessageType.Info)); + $logger.PublishCustomMessage("IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($AzTSUIFrontDoorUrl)", $([Constants]::MessageType.Warning)) + } + elseif($EnableAzTSUI) + { + $logger.PublishCustomMessage("$([Constants]::NextSteps -f $UIUrl)", $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($UIUrl)", $([Constants]::MessageType.Warning)) + } + else + { + $logger.PublishCustomMessage("$([Constants]::NextSteps -f $UIUrl)", $([Constants]::MessageType.Info)) + } + $logger.PublishLogFilePath() + + return $DeploymentResult + + } +} + +function Setup-Prerequisites { + <# + .SYNOPSIS + Checks if all required Az modules are present, else, sets them up. + .DESCRIPTION + Checks if all required Az modules are present, else, sets them up. + Includes installing any required Azure modules. + .INPUTS + None. You cannot pipe objects to Setup-Prerequisites. + .OUTPUTS + Boolean. 'True' if all pre-requisites are met 'False' otherwise. + .EXAMPLE + PS> Setup-Prerequisites + .LINK + None + #> + $allPrerequisiteMet = $true + # List of required modules + $requiredModules = @{ + "Az.Accounts" = "2.9.0"; + "Az.Resources" = "1.10.0"; + "Az.Storage" = "2.0.0"; + "Az.ManagedServiceIdentity" = "0.7.3"; + "Az.Monitor" = "1.5.0"; + "Az.OperationalInsights" = "1.3.4"; + "Az.ApplicationInsights" = "1.0.3"; + "Az.Websites" = "2.8.1"; + "Az.Network" = "2.5.0"; + "Az.FrontDoor" = "1.8.0"; + "Az.CosmosDB" = "1.8.2"; + "AzureAD" = "2.0.2.130"; + } + + $requiredModuleNames = @() + $requiredModules.Keys | ForEach-Object { $requiredModuleNames += $_.ToString()} + + try{ + + Write-Host "Checking if all required modules are present..." -ForegroundColor $([Constants]::MessageType.Info) + $availableModules = $(Get-Module -ListAvailable $requiredModuleNames -ErrorAction Stop) | Select-Object Name, Version | Sort-Object -Property "Version" -Descending + + # Check if the required modules are installed. + $installedModules = @() + $missingModules = @() + $requiredModules.Keys | ForEach-Object { + $requiredModule = "" | Select-Object "Name", "RequiredVersion" + $requiredModule.RequiredVersion = $requiredModules[$_] + $requiredModule.Name = $_ + $modulePresent = $availableModules | Where-Object {($_.Name -eq $requiredModule.Name) -and ($_.Version -ge $requiredModule.RequiredVersion)} | Select-Object -First 1 + if ($modulePresent) + { + $installedModules += $requiredModule + } + else + { + $missingModules += $requiredModule + } + } + + if (($missingModules | Measure-Object).Count -eq 0) + { + $allPrerequisiteMet = $true + Write-Host "All required modules are present." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + Write-Host "Installing missing modules..." -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "Following modules (with required version) are not present:" -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $($missingModules | FT | Out-String) -ForegroundColor $([Constants]::MessageType.Info) + + $userChoice = Read-Host -Prompt "Do you want to install all missing modules (Y/N)" + if ($userChoice -eq "Y") + { + $missingModules | ForEach-Object { + Write-Host "Installing $($_.Name) module..." -ForegroundColor $([Constants]::MessageType.Info) + Install-Module -Name $_.Name -Scope CurrentUser -Repository 'PSGallery' -ErrorAction Stop -Force + if ($?) + { + $allPrerequisiteMet = $allPrerequisiteMet -and $true + Write-Host "Successfully installed $($_.Name) module." -ForegroundColor $([Constants]::MessageType.Update) + } + else + { + $allPrerequisiteMet = $allPrerequisiteMet -and $false + Write-Host "Unable to install $($_.Name) module." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + + } + else + { + $allPrerequisiteMet = $false + Write-Host "Module installation skipped based on user's choice." -ForegroundColor $([Constants]::MessageType.Info) + } + } + } + catch + { + $allPrerequisiteMet = $false + } + + return $allPrerequisiteMet; +} diff --git a/TemplateFiles/DeploymentFiles/AzTSDeploymentTemplate.json b/TemplateFiles/DeploymentFiles/AzTSDeploymentTemplate.json index e7eeb20d..81ea33ee 100644 --- a/TemplateFiles/DeploymentFiles/AzTSDeploymentTemplate.json +++ b/TemplateFiles/DeploymentFiles/AzTSDeploymentTemplate.json @@ -1,2374 +1,2352 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "EnableVnetIntegration": { - "type": "bool", - "defaultValue": false - }, - "EnableWAF": { - "type": "bool", - "defaultValue": false - }, - "hostingPlanName": { - "type": "string", - "defaultValue": "AzSK-AzTS-AppServicePlan" - }, - "apiHostingPlanName": { - "type": "string", - "defaultValue": "AzSK-AzTS-API-AppServicePlan" - }, - "skuName": { - "type": "string", - "defaultValue": "[if(parameters('EnableVnetIntegration'), 'EP1', 'Y1')]", - "allowedValues": [ - "F1", - "D1", - "B1", - "B2", - "B3", - "S1", - "S2", - "S3", - "P1", - "P2", - "P3", - "P3V2", - "P2V2", - "P4", - "EP3", - "EP1", - "Y1" - ], - "metadata": { - "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" - } - }, - "apiSkuName": { - "type": "string", - "defaultValue": "S1", - "allowedValues": [ - "F1", - "D1", - "B1", - "B2", - "B3", - "S1", - "S2", - "S3", - "P1", - "P2", - "P3", - "P4" - ], - "metadata": { - "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" - } - }, - "skuCapacity": { - "type": "int", - "defaultValue": 1, - "minValue": 1, - "metadata": { - "description": "Describes plan's instance count" - } - }, - "storageSKU": { - "type": "string", - "defaultValue": "Standard_LRS", - "allowedValues": [ - "Standard_LRS", - "Standard_ZRS", - "Standard_GRS", - "Standard_RAGRS", - "Premium_LRS" - ] - }, - "runtime": { - "type": "string", - "defaultValue": "dotnet", - "allowedValues": [ - "node", - "dotnet", - "java" - ], - "metadata": { - "description": "The language worker runtime to load in the function app." - } - }, - "storageContainerName": { - "type": "string", - "defaultValue": "azskatsscanresult" - }, - "storageQueueNamePrefix": { - "type": "string", - "defaultValue": "subjobqueue" - }, - "laWorkspaceName": { - "type": "string", - "defaultValue": "AzSK-AzTS-LAWorkspace" - }, - "applicationInsightsName": { - "type": "string", - "defaultValue": "AzSK-AzTS-AppInsights" - }, - "internalMIName": { - "type": "string", - "defaultValue": "AzSK-AzTS-InternalMI" - }, - "laSkuName": { - "type": "string", - "allowedValues": [ - "pergb2018", - "Free", - "Standalone", - "PerNode", - "Standard", - "Premium" - ], - "defaultValue": "pergb2018", - "metadata": { - "description": "Pricing tier: PerGB2018 or legacy tiers (Free, Standalone, PerNode, Standard or Premium) which are not available to all customers." - } - }, - "MIResourceId": { - "type": "string" - }, - "TenantId": { - "type": "string" - }, - "UIClientId": { - "type": "string" - }, - "WebApiClientId": { - "type": "string" - }, - "ResourceHash": { - "type": "string" - }, - "TelemetryIdentifier": { - "type": "string" - }, - "OrganizationName": { - "type": "string", - "defaultValue": "NA" - }, - "DivisionName": { - "type": "string", - "defaultValue": "NA" - }, - "ContactEmailAddressList": { - "type": "string", - "defaultValue": "NA" - }, - "HashedTenantId": { - "type": "string", - "defaultValue": "NA" - }, - "AnonymousUsageTelemetryLogLevel": { - "type": "string", - "allowedValues": ["None", "Onboarding", "Anonymous", "All"], - "defaultValue": "None" - }, - "RuleEngineWorkflowName": { - "type": "string" - }, - "IsGraphFeatureEnabled": { - "type": "string" - }, - "MetadataAggregatorPackageURL": { - "type": "string" - }, - "WorkItemProcessorPackageURL": { - "type": "string" - }, - "WebApiPackageURL": { - "type": "string" - }, - "UIPackageURL": { - "type": "string" - }, - "AzureEnvironmentName": { - "type" : "string" - }, - "virtualNetworkAddressPrefix": { - "type": "string", - "defaultValue": "10.100.0.0/16", - "metadata": { - "description": "VNET address space." - } - }, - "functionSubnetAddressPrefix": { - "type": "string", - "defaultValue": "10.100.0.0/24", - "metadata": { - "description": "Function App's subnet address range." - } - }, - "privateEndpointSubnetAddressPrefix": { - "type": "string", - "defaultValue": "10.100.1.0/24", - "metadata": { - "description": "Storage account's private endpoint's subnet address range." - } - }, - "appserviceSubnetAddressPrefix": { - "type": "string", - "defaultValue": "10.100.2.0/24", - "metadata": { - "description": "App service subnet address range." - } - }, - "functionsSubnetName": { - "type": "string", - "defaultValue": "FunctionsSubnet", - "metadata": { - "description": "The subnet that the Function App will use for VNET traffic." - } - }, - "PrivateEndpointSubnetName": { - "type": "string", - "defaultValue": "PrivateEndpointSubnet", - "metadata": { - "description": "The subnet that will be used for private endpoints." - } - }, - "appserviceSubnetName": { - "type": "string", - "defaultValue": "AppServiceSubnet", - "metadata": { - "description": "The subnet that the App service will use for VNET traffic." - } - }, - "IsPIMEnabled": { - "type" : "bool", - "defaultValue": false - }, - "IsAutoUpdaterEnabled": { - "type" : "bool", - "defaultValue": true - }, - "IsAzTSUIEnabled": { - "type" : "bool", - "defaultValue": true - }, - "FrontDoorEndpointSuffix":{ - "type": "string" - }, - "WebAppEndpointSuffix":{ - "type": "string" - }, - "CentralStorageAccountConnectionString": { - "type" : "string" - }, - "IsMultiTenantSetUp": { - "type" : "bool", - "defaultValue": false - }, - "ScannerIdentitySecretUri":{ - "type": "string", - "metadata": { - "description": "Key Vault Secret Uri for Central scanning App's credential in multi-tenant setup" - } - }, - "ScannerIdentityApplicationId":{ - "type": "string", - "metadata": { - "description": "Application Id of central scanning identity in multi-tenant setup" - } - }, - "AutoUpdaterFunctionTimeout": { - "type": "string", - "defaultValue": "00:09:00" - }, - "WorkItemProcessorFunctionTimeout": { - "type": "string", - "defaultValue": "00:09:00" - }, - "MetadataAggregatorFunctionTimeout": { - "type": "string", - "defaultValue": "00:09:00" - }, - "AADClientAppDetailsInstance": { - "type": "string", - "defaultValue": "https://login.microsoftonline.com/" - }, - "AzureEnvironmentPortalURI": { - "type": "string", - "defaultValue": "https://portal.azure.com/" - } - }, - "variables": { - "MetadataAggregator": "[concat('AzSK-AzTS-MetadataAggregator-', parameters('ResourceHash'))]", - "APIFrontDoorName": "[concat('AzSK-AzTS-API-FrontDoor-', parameters('ResourceHash'))]", - "UIFrontDoorName": "[concat('AzSK-AzTS-UI-FrontDoor-', parameters('ResourceHash'))]", - "wafPolicyName": "[concat('AzSKAzTSWAFPolicy', parameters('ResourceHash'))]", - "APIFrontDoorUrl": "[concat('https://',toLower(variables('APIFrontDoorName')), parameters('FrontDoorEndpointSuffix'))]", - "UIFrontDoorUrl": "[concat('https://',toLower(variables('UIFrontDoorName')), parameters('FrontDoorEndpointSuffix'))]", - "WebUIUrl": "[concat('https://',variables('WebApi'), parameters('WebAppEndpointSuffix'))]", - "WorkItemProcessor": "[concat('AzSK-AzTS-WorkItemProcessor-', parameters('ResourceHash'))]", - "AutoUpdater": "[concat('AzSK-AzTS-AutoUpdater-', parameters('ResourceHash'))]", - "WebApi": "[concat('AzSK-AzTS-WebApi-', parameters('ResourceHash'))]", - "WebUI": "[concat('AzSK-AzTS-UI-', parameters('ResourceHash'))]", - "WebUISlotName": "[concat('Staging-', parameters('ResourceHash'))]", - "functionWorkerRuntime": "[parameters('runtime')]", - "storageName": "[concat('azskaztsstorage', parameters('ResourceHash'))]", - "workspaceName": "[concat('AzSK-AzTS-LAWorkspace-', parameters('ResourceHash'))]", - "applicationInsightsName": "[concat(parameters('applicationInsightsName'), '-', parameters('ResourceHash'))]", - "internalMIName": "[concat(parameters('internalMIName'), '-', parameters('ResourceHash'))]", - "rgRoleAssignmentGuid": "[guid(resourceGroup().id)]", - "contributorRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c')]", - "rgRoleAssignmentName": "[variables('rgRoleAssignmentGuid')]", - "LogicAppWorkflowName" : "[concat('AzSK-AzTS-AutoUpdater-LogicApp-', parameters('ResourceHash'))]", - "AutoUpdaterAPIConnectionName" : "[concat('azsk-azts-autoupdater-connection-', parameters('ResourceHash'))]", - "AutoUpdaterConnectionAPI" : "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/',resourceGroup().location,'/managedApis/azureappservice')]", - "vnetName": "[concat('AzSK-AzTS-Vnet-', parameters('ResourceHash'))]", - "BlobDnsZoneName": "[concat('privatelink.blob.', environment().suffixes.storage)]", - "azureMoinitorDnsZoneName": "privatelink.monitor.azure.com", - "azureAutomationDnsZoneName": "privatelink.agentsvc.azure-automation.net", - "OMSopinsightsDnsZoneName": "privatelink.oms.opinsights.azure.com", - "ODSopinsightsDnsZoneName": "privatelink.ods.opinsights.azure.com", - "privateEndpointForAMPLS": "[concat(variables('private_link_scope_name'), '-private-endpoint')]", - "virtualNetworkLinksSuffixBlobStorageName": "[concat(variables('BlobDnsZoneName'), '-link')]", - "virtualNetworkLinksSuffixAzureMonitorName": "[concat(variables('azureMoinitorDnsZoneName'), '-link')]", - "virtualNetworkLinksSuffixAzureAutomationName": "[concat(variables('azureAutomationDnsZoneName'), '-link')]", - "virtualNetworkLinksSuffixOMSinsightsName": "[concat(variables('OMSopinsightsDnsZoneName'), '-link')]", - "virtualNetworkLinksSuffixODSinsightsName": "[concat(variables('ODSopinsightsDnsZoneName'), '-link')]", - "MAfileShare": "[toLower(variables('MetadataAggregator'))]", - "WIfileShare": "[toLower(variables('WorkItemProcessor'))]", - "AUfileShare": "[toLower(variables('AutoUpdater'))]", - "WebApifileShare": "[toLower(variables('WebApi'))]", - "UIfileShare": "[toLower(variables('WebUI'))]", - "functionAppSubnetId":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('functionsSubnetName'))]", - "appserviceSubnetId":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('appserviceSubnetName'))]", - "private_link_scope_name": "[concat('AzSK-AzTS-private-link-scope-', parameters('ResourceHash'))]", - "storageQueueName" : "[concat(parameters('storageQueueNamePrefix'), toLower(parameters('ResourceHash')))]", - "userAssignedIdentities" : { - "singleTenantSetUp": { - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": {}, - "[parameters('MIResourceId')]": {} - }, - "multiTenantSetUp": { - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": {} - } - } - }, - "resources": [ - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/virtualNetworks", - "apiVersion": "2020-07-01", - "location": "[resourceGroup().location]", - "name": "[variables('vnetName')]", - "properties": { - "addressSpace": { - "addressPrefixes": [ - "[parameters('virtualNetworkAddressPrefix')]" - ] - }, - "subnets": [ - { - "name": "[parameters('functionsSubnetName')]", - "properties": { - "addressPrefix": "[parameters('functionSubnetAddressPrefix')]", - "privateEndpointNetworkPolicies": "Enabled", - "privateLinkServiceNetworkPolicies": "Enabled", - "serviceEndpoints": [ - { - "service": "Microsoft.Storage" - }, - { - "service": "Microsoft.Web" - } - ], - "delegations": [ - { - "name": "webapp", - "properties": { - "serviceName": "Microsoft.Web/serverFarms", - "actions": [ - "Microsoft.Network/virtualNetworks/subnets/action" - ] - } - } - ] - } - }, - { - "name": "[parameters('appserviceSubnetName')]", - "properties": { - "addressPrefix": "[parameters('appserviceSubnetAddressPrefix')]", - "privateEndpointNetworkPolicies": "Enabled", - "privateLinkServiceNetworkPolicies": "Enabled", - "serviceEndpoints": [ - { - "service": "Microsoft.Storage" - } - ], - "delegations": [ - { - "name": "webapp", - "properties": { - "serviceName": "Microsoft.Web/serverFarms", - "actions": [ - "Microsoft.Network/virtualNetworks/subnets/action" - ] - } - } - ] - } - }, - { - "name": "[parameters('PrivateEndpointSubnetName')]", - "properties": { - "addressPrefix": "[parameters('privateEndpointSubnetAddressPrefix')]", - "privateLinkServiceNetworkPolicies": "Enabled", - "privateEndpointNetworkPolicies": "Disabled", - "serviceEndpoints": [ - { - "service": "Microsoft.Storage" - } - ] - } - } - ], - "enableDdosProtection": false, - "enableVmProtection": false - } - }, - { - "name": "[variables('storageName')]", - "type": "Microsoft.Storage/storageAccounts", - "location": "[resourceGroup().location]", - "apiVersion": "2019-06-01", - "sku": { - "name": "[parameters('storageSKU')]" - }, - "dependsOn": [], - "properties": { - "supportsHttpsTrafficOnly": true, - "allowBlobPublicAccess": false, - "minimumTlsVersion": "TLS1_2" - }, - "tags": { - "displayName": "azskaztsapp" - }, - "kind": "StorageV2", - "resources": [ - { - "type": "blobServices/containers", - "apiVersion": "2019-06-01", - "name": "[concat('default/', parameters('StorageContainerName'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - ], - "properties": { - "publicAccess": "None" - } - } - ] - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2019-06-01", - "name": "[concat(variables('storageName'), '/default/', variables('MAfileShare'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - ] - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2019-06-01", - "name": "[concat(variables('storageName'), '/default/', variables('WIfileShare'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - ] - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2019-06-01", - "name": "[concat(variables('storageName'), '/default/', variables('AUfileShare'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - ] - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2019-06-01", - "name": "[concat(variables('storageName'), '/default/', variables('WebApifileShare'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - ] - }, - { - "type": "Microsoft.Storage/storageAccounts/fileServices/shares", - "apiVersion": "2019-06-01", - "name": "[concat(variables('storageName'), '/default/', variables('UIfileShare'))]", - "dependsOn": [ - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - ] - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('BlobDnsZoneName')]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ] - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('azureMoinitorDnsZoneName')]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ] - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('azureAutomationDnsZoneName')]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ] - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('OMSopinsightsDnsZoneName')]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ] - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones", - "apiVersion": "2020-06-01", - "name": "[variables('ODSopinsightsDnsZoneName')]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ] - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2020-06-01", - "name": "[concat(variables('BlobDnsZoneName'), '/', variables('virtualNetworkLinksSuffixBlobStorageName'))]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones',variables('BlobDnsZoneName'))]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ], - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - } - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2020-06-01", - "name": "[concat(variables('azureMoinitorDnsZoneName'), '/', variables('virtualNetworkLinksSuffixAzureMonitorName'))]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones',variables('azureMoinitorDnsZoneName'))]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ], - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - } - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2020-06-01", - "name": "[concat(variables('azureAutomationDnsZoneName'), '/', variables('virtualNetworkLinksSuffixAzureAutomationName'))]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones',variables('azureAutomationDnsZoneName'))]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ], - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - } - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2020-06-01", - "name": "[concat(variables('OMSopinsightsDnsZoneName'), '/', variables('virtualNetworkLinksSuffixOMSinsightsName'))]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones',variables('OMSopinsightsDnsZoneName'))]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ], - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - } - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", - "apiVersion": "2020-06-01", - "name": "[concat(variables('ODSopinsightsDnsZoneName'), '/', variables('virtualNetworkLinksSuffixODSinsightsName'))]", - "location": "global", - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones',variables('ODSopinsightsDnsZoneName'))]", - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - ], - "properties": { - "registrationEnabled": false, - "virtualNetwork": { - "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" - } - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateEndpoints", - "name": "[variables('privateEndpointForAMPLS')]", - "apiVersion": "2020-06-01", - "location": "[resourceGroup().location]", - "dependsOn": [ - "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]", - "[resourceId('microsoft.insights/privatelinkscopes', variables('private_link_scope_name'))]" - ], - "properties": { - "subnet": { - "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('PrivateEndpointSubnetName'))]" - }, - "privateLinkServiceConnections": [ - { - "name": "AMPLSPrivateLinkConnection", - "properties": { - "privateLinkServiceId": "[resourceId('microsoft.insights/privateLinkScopes', variables('private_link_scope_name'))]", - "groupIds": [ - "azuremonitor" - ] - } - } - ] - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", - "apiVersion": "2020-06-01", - "location": "[resourceGroup().location]", - "name": "[concat(variables('privateEndpointForAMPLS'), '/default')]", - "dependsOn": [ - "[resourceId('Microsoft.Network/privateDnsZones', variables('BlobDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointForAMPLS'))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('azureMoinitorDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('azureAutomationDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('OMSopinsightsDnsZoneName'))]", - "[resourceId('Microsoft.Network/privateDnsZones', variables('ODSopinsightsDnsZoneName'))]" - ], - "properties": { - "privateDnsZoneConfigs": [ - { - "name": "config1", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('BlobDnsZoneName'))]" - } - }, - { - "name": "config2", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('azureMoinitorDnsZoneName'))]" - } - }, - { - "name": "config3", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('azureAutomationDnsZoneName'))]" - } - }, - { - "name": "config4", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('OMSopinsightsDnsZoneName'))]" - } - }, - { - "name": "config5", - "properties": { - "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('ODSopinsightsDnsZoneName'))]" - } - } - ] - } - }, - { - "type": "Microsoft.OperationalInsights/workspaces", - "name": "[variables('workspaceName')]", - "apiVersion": "2017-03-15-preview", - "location": "[resourceGroup().location]", - "properties": { - "sku": { - "name": "[parameters('laSkuName')]" - }, - "publicNetworkAccessForIngestion": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", - "publicNetworkAccessForQuery": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", - "retentionInDays": 120, - "features": { - "searchVersion": 1, - "legacy": 0, - "enableLogAccessUsingOnlyResourcePermissions": true - } - } - }, - { - "type": "Microsoft.ManagedIdentity/userAssignedIdentities", - "name": "[variables('internalMIName')]", - "apiVersion": "2018-11-30", - "location": "[resourceGroup().location]" - }, - { - "apiVersion": "2015-08-01", - "name": "[parameters('hostingPlanName')]", - "type": "Microsoft.Web/serverfarms", - "location": "[resourceGroup().location]", - "tags": { - "displayName": "HostingPlan" - }, - "sku": { - "name": "[parameters('skuName')]", - "capacity": "[parameters('skuCapacity')]", - "tier": "Dynamic" - }, - "kind": "functionapp", - "properties": { - "name": "[parameters('hostingPlanName')]", - "computeMode": "Dynamic" - } - }, - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "apiVersion": "2015-08-01", - "name": "[parameters('apiHostingPlanName')]", - "type": "Microsoft.Web/serverfarms", - "location": "[resourceGroup().location]", - "tags": { - "displayName": "ApiHostingPlan" - }, - "sku": { - "name": "[parameters('apiSkuName')]", - "capacity": "[parameters('skuCapacity')]" - }, - "kind": "app", - "properties": { - "name": "[parameters('apiHostingPlanName')]" - } - }, - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "name": "[variables('WebApi')]", - "type": "Microsoft.Web/sites", - "location": "[resourceGroup().location]", - "apiVersion": "2015-08-01", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": { - } - } - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('WebApifileShare'))]" - ], - "tags": { - "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName')))]": "Resource", - "displayName": "WebApi" - }, - "properties": { - "name": "[variables('WebApi')]", - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", - "keyVaultReferenceIdentity": "[if(parameters('IsMultiTenantSetUp'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), 'SystemAssigned')]", - "siteConfig": { - "remoteDebuggingEnabled": false, - "webSocketsEnabled": false, - "requestTracingEnabled": true, - "httpLoggingEnabled": true, - "detailedErrorLoggingEnabled": true, - "minTlsVersion": 1.2, - "ftpsState": "Disabled", - "alwaysOn": true, - "appSettings": [ - { - "name": "AzureWebJobsStorage", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value), parameters('CentralStorageAccountConnectionString'))]" - }, - { - "name": "AuthNSettings__ScannerIdentityConnectionString", - "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" - }, - { - "name": "AuthNSettings__InternalIdentityConnectionString", - "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" - }, - { - "name": "AuthNSettings__ScannerApplicationPassword", - "value": "[if(parameters('IsMultiTenantSetUp'), concat('@Microsoft.KeyVault(SecretUri=',parameters('ScannerIdentitySecretUri'), ')'), json('null'))]" - }, - { - "name": "AuthNSettings__ScannerApplicationId", - "value": "[if(parameters('IsMultiTenantSetUp'), parameters('ScannerIdentityApplicationId'), json('null'))]" - }, - { - "name": "AADClientAppDetails__ResourceId", - "value": "[concat('api://',parameters('WebApiClientId'))]" - }, - { - "name": "AADClientAppDetails__Instance", - "value": "[concat(parameters('AADClientAppDetailsInstance'),'/')]" - }, - { - "name": "AADClientAppDetails__ClientId", - "value": "[parameters('WebApiClientId')]" - }, - { - "name": "AADClientAppDetails__TenantId", - "value": "[parameters('TenantId')]" - }, - { - "name": "AADClientAppDetails__Issuer", - "value": "[concat(parameters('AADClientAppDetailsInstance'),'/',parameters('TenantId'),'/v2.0')]" - }, - { - "name": "AADClientAppDetails__ApplicationId", - "value": "[parameters('UIClientId')]" - }, - { - "name": "WEBSITE_CONTENTSHARE", - "value": "[toLower(variables('WebApi'))]" - }, - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "~10" - }, - { - "name": "APPINSIGHTS_INSTRUMENTATIONKEY", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - { - "name": "ApplicationInsights__InstrumentationKey", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - { - "name": "AIConfigurations__ResourceId", - "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - }, - { - "name": "AzureStorageSettings__ResourceId", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), resourceId('Microsoft.Storage/storageAccounts/', variables('storageName')), json('null'))]" - }, - { - "name": "AzureStorageSettings__ConnectionString", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), json('null'), parameters('CentralStorageAccountConnectionString'))]" - }, - { - "name": "AzureStorageSettings__QueueName", - "value": "[variables('storageQueueName')]" - }, - { - "name": "LAConfigurations__ResourceId", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]" - }, - { - "name": "LAConfigurations__WorkspaceId", - "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')), '2017-03-15-preview').customerId]" - }, - { - "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", - "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" - }, - { - "name": "AppMetadata__AppName", - "value": "[variables('WebApi')]" - }, - { - "name": "AppMetadata__LinkedAppName", - "value": "[variables('WebUI')]" - }, - { - "name": "WebJobConfigurations__CloudEnvironmentName", - "value": "[parameters('AzureEnvironmentName')]" - }, - { - "name": "FeatureManagement__OrgPolicy", - "value": true - }, - { - "name": "FeatureManagement__AddNewControl", - "value": true - }, - { - "name": "UIConfigurations__ControlEditorFeatureConfiguration__IsAddNewControlEnabled", - "value": true - }, - { - "name": "UIConfigurations__ControlEditorFeatureConfiguration__IsEnabled", - "value": true - }, - { - "name": "UIConfigurations__AzureAPI", - "value": "[parameters('AzureEnvironmentPortalURI')]" - }, - { - "name": "CorsPolicySettings__OriginUrl", - "value": "[concat('https://',toLower(variables('WebUI')), parameters('WebAppEndpointSuffix'))]" - }, - { - "name": "WorkItemProcessorSettings__AppName", - "value": "[variables('WorkItemProcessor')]" - }, - { - "name": "WorkItemProcessorSettings__HostResourceGroupName", - "value": "[resourceGroup().name]" - }, - { - "name": "WorkItemProcessorSettings__HostSubscriptionId", - "value": "[subscription().subscriptionId]" - }, - { - "name": "WEBSITE_VNET_ROUTE_ALL", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_CONTENTOVERVNET", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_DNS_SERVER", - "value": "168.63.129.16" - }, - { - "name": "MultiTenantConfigurations__IsFeatureEnabled", - "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" - }, - { - "name": "MultiTenantConfigurations__HostTenant", - "value": "[if(parameters('IsMultiTenantSetUp'), parameters('TenantId'), json('null'))]" - }, - { - "name": "UIConfigurations__IsMultiTenantSetup", - "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" - } - ], - "cors": { - "allowedOrigins": [ - "[concat('https://',toLower(variables('WebUI')), parameters('WebAppEndpointSuffix'))]", - "[if(parameters('EnableWAF'), variables('APIFrontDoorUrl'), json('null'))]", - "[if(parameters('EnableWAF'), variables('UIFrontDoorUrl'), json('null'))]" - ] - } - } - }, - "resources": [ - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "apiVersion": "2018-11-01", - "name": "MSDeploy", - "type": "Extensions", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebApi'))]" - ], - "properties": { - "packageUri": "[parameters('WebApiPackageURL')]" - } - }, - { - "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAzTSUIEnabled'))]", - "name": "virtualNetwork", - "type": "config", - "apiVersion": "2018-02-01", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebApi'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('WebApi'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "subnetResourceId": "[variables('appserviceSubnetId')]", - "swiftSupported": true - } - }, - { - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "name": "ftp", - "location": "[resourceGroup().location]", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebApi'))]", - "[concat('Microsoft.Web/sites/', variables('WebApi'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "location": "[resourceGroup().location]", - "name": "scm", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebApi'))]", - "[concat('Microsoft.Web/sites/', variables('WebApi'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - } - ] - }, - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "name": "[variables('WebUI')]", - "type": "Microsoft.Web/sites", - "location": "[resourceGroup().location]", - "apiVersion": "2015-08-01", - "identity": { - "type": "SystemAssigned" - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('UIfileShare'))]" - ], - "tags": { - "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName')))]": "Resource", - "displayName": "UI" - }, - "properties": { - "name": "[variables('WebUI')]", - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", - "siteConfig": { - "remoteDebuggingEnabled": false, - "webSocketsEnabled": false, - "requestTracingEnabled": true, - "httpLoggingEnabled": true, - "detailedErrorLoggingEnabled": true, - "minTlsVersion": 1.2, - "ftpsState": "Disabled", - "alwaysOn": true, - "appSettings": [ - { - "name": "APPINSIGHTS_INSTRUMENTATIONKEY", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - { - "name": "REACT_APP_API", - "value": "[if(parameters('EnableWAF'), variables('APIFrontDoorUrl'), variables('WebUIUrl'))]" - }, - { - "name": "REACT_APP_clientId", - "value": "[parameters('UIClientId')]" - }, - { - "name": "REACT_APP_tenantId", - "value": "[parameters('TenantId')]" - }, - { - "name": "REACT_APP_userImpersonation", - "value": "[parameters('WebApiClientId')]" - } - ] - } - }, - "resources": [ - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "apiVersion": "2018-11-01", - "name": "MSDeploy", - "type": "Extensions", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebUI'))]" - ], - "properties": { - "packageUri": "[parameters('UIPackageURL')]" - } - }, - { - "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAzTSUIEnabled'))]", - "name": "virtualNetwork", - "type": "config", - "apiVersion": "2018-02-01", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebUI'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "subnetResourceId": "[variables('appserviceSubnetId')]", - "swiftSupported": true - } - }, - { - "condition":"[parameters('IsAzTSUIEnabled')]", - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "name": "ftp", - "location": "[resourceGroup().location]", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebUI'))]", - "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "condition":"[parameters('IsAzTSUIEnabled')]", - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "location": "[resourceGroup().location]", - "name": "scm", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WebUI'))]", - "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - } - ] - }, - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "name": "[concat(variables('WebUI'), '/', variables('WebUISlotName'))]", - "type": "Microsoft.Web/sites/slots", - "location": "[resourceGroup().location]", - "apiVersion": "2015-08-01", - "kind": "app", - "comments": "This specifies the web app slots.", - "identity": { - "type": "SystemAssigned" - }, - "tags": { - "displayName": "WebAppSlots" - }, - "properties": { - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", - "httpsOnly": true - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/Sites', variables('WebUI'))]", - "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/MSDeploy')]" - ], - "resources":[ - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "name": "ftp", - "location": "[resourceGroup().location]", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/slots', variables('WebUI'), variables('WebUISlotName'))]" - ], - "properties": { - "allow": "false" - } - }, - { - "condition": "[parameters('IsAzTSUIEnabled')]", - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "location": "[resourceGroup().location]", - "name": "scm", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites/slots', variables('WebUI') , variables('WebUISlotName'))]" - ], - "properties": { - "allow": "false" - } - } - ], - "copy": { - "name": "webPortalSlot", - "count": 1 - } - }, - { - "apiVersion": "2018-11-01", - "name": "[variables('MetadataAggregator')]", - "type": "Microsoft.Web/sites", - "location": "[resourceGroup().location]", - "kind": "functionapp", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": "[if(parameters('IsMultiTenantSetUp'), variables('userAssignedIdentities')['multiTenantSetUp'], variables('userAssignedIdentities')['singleTenantSetUp'])]" - }, - "tags": { - "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", - "displayName": "Website" - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('MAfileShare'))]" - ], - "properties": { - "name": "[variables('MetadataAggregator')]", - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", - "keyVaultReferenceIdentity": "[if(parameters('IsMultiTenantSetUp'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), 'SystemAssigned')]", - "siteConfig": { - "remoteDebuggingEnabled": false, - "webSocketsEnabled": false, - "requestTracingEnabled": true, - "httpLoggingEnabled": true, - "detailedErrorLoggingEnabled": true, - "reservedInstanceCount": 1, - "minTlsVersion": 1.2, - "netFrameworkVersion": "v6.0", - "ftpsState": "Disabled", - "appSettings": [ - { - "name": "AzureFunctionsJobHost__functionTimeout", - "value": "[parameters('MetadataAggregatorFunctionTimeout')]" - }, - { - "name": "AzureWebJobsStorage", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value), parameters('CentralStorageAccountConnectionString'))]" - }, - { - "name": "AuthNSettings__ScannerIdentityConnectionString", - "value": "[if(parameters('IsMultiTenantSetUp'), json('null'), concat('RunAs=App;AppId=',reference(parameters('MIResourceId'),'2018-11-30').clientId))]" - }, - { - "name": "AuthNSettings__ScannerApplicationPassword", - "value": "[if(parameters('IsMultiTenantSetUp'), concat('@Microsoft.KeyVault(SecretUri=',parameters('ScannerIdentitySecretUri'), ')'), json('null'))]" - }, - { - "name": "AuthNSettings__ScannerApplicationId", - "value": "[if(parameters('IsMultiTenantSetUp'), parameters('ScannerIdentityApplicationId'), json('null'))]" - }, - { - "name": "AuthNSettings__InternalIdentityConnectionString", - "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" - }, - { - "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", - "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" - }, - { - "name": "WEBSITE_CONTENTSHARE", - "value": "[toLower(variables('MetadataAggregator'))]" - }, - { - "name": "FUNCTIONS_EXTENSION_VERSION", - "value": "~4" - }, - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "~10" - }, - { - "name": "APPINSIGHTS_INSTRUMENTATIONKEY", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - { - "name": "AIConfigurations__ResourceId", - "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - }, - { - "name": "AzureStorageSettings__ResourceId", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), resourceId('Microsoft.Storage/storageAccounts/', variables('storageName')), json('null'))]" - }, - { - "name": "AzureStorageSettings__ConnectionString", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), json('null'), parameters('CentralStorageAccountConnectionString'))]" - }, - { - "name": "AzureStorageSettings__QueueName", - "value": "[variables('storageQueueName')]" - }, - { - "name": "LAConfigurations__ResourceId", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]" - }, - { - "name": "LAConfigurations__WorkspaceId", - "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')), '2017-03-15-preview').customerId]" - }, - { - "name": "FUNCTIONS_WORKER_RUNTIME", - "value": "[variables('functionWorkerRuntime')]" - }, - { - "name": "RuleEngine__WorkflowName", - "value": "[parameters('RuleEngineWorkflowName')]" - }, - { - "name": "AppMetadata__AppName", - "value": "[variables('MetadataAggregator')]" - }, - { - "name": "GraphConfigurations__IsFeatureEnabled", - "value": "[parameters('IsGraphFeatureEnabled')]" - }, - { - "name": "AuthzSettings__IsPIMEnabled", - "value": "[parameters('IsPIMEnabled')]" - }, - { - "name": "AuthzSettings__IsRGPIMEnabled", - "value": "[parameters('IsPIMEnabled')]" - }, - { - "name": "WebJobConfigurations__CloudEnvironmentName", - "value": "[parameters('AzureEnvironmentName')]" - }, - { - "name": "OnDemandProcessingQueue", - "value": "ondemandprocessingqueue" - }, - { - "name": "FeatureManagement__OrgPolicy", - "value": "[if(parameters('IsAzTSUIEnabled'), 'true', 'false')]" - }, - { - "name": "MultiTenantConfigurations__IsFeatureEnabled", - "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" - }, - { - "name": "MultiTenantConfigurations__HostTenant", - "value": "[if(parameters('IsMultiTenantSetUp'), parameters('TenantId'), json('null'))]" - }, - { - "name": "WEBSITE_VNET_ROUTE_ALL", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_CONTENTOVERVNET", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_DNS_SERVER", - "value": "168.63.129.16" - } - ], - "functionsRuntimeScaleMonitoringEnabled": "[if(parameters('EnableVnetIntegration'), 'true', 'false')]" - } - }, - "resources": [ - { - "apiVersion": "2018-11-01", - "name": "MSDeploy", - "type": "Extensions", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]" - ], - "properties": { - "packageUri": "[parameters('MetadataAggregatorPackageURL')]" - } - }, - { - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "name": "ftp", - "location": "[resourceGroup().location]", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]", - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "location": "[resourceGroup().location]", - "name": "scm", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]", - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/MSDeploy')]" - - ], - "properties": { - "allow": "false" - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "name": "virtualNetwork", - "type": "config", - "apiVersion": "2018-02-01", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "subnetResourceId": "[variables('functionAppSubnetId')]", - "swiftSupported": true - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "name": "[concat(variables('MetadataAggregator'), '/web')]", - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', variables('MetadataAggregator'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "ipSecurityRestrictions": [ - { - "vnetSubnetResourceId": "[variables('functionAppSubnetId')]", - "action": "Allow", - "priority": 100, - "name": "allowtrafficfromfunctionsubnet" - } - ] - } - } - ] - }, - { - "apiVersion": "2018-11-01", - "name": "[variables('WorkItemProcessor')]", - "type": "Microsoft.Web/sites", - "location": "[resourceGroup().location]", - "kind": "functionapp", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": "[if(parameters('IsMultiTenantSetUp'), variables('userAssignedIdentities')['multiTenantSetUp'], variables('userAssignedIdentities')['singleTenantSetUp'])]" - }, - "tags": { - "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", - "displayName": "Website" - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('WIfileShare'))]" - ], - "properties": { - "name": "[variables('WorkItemProcessor')]", - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", - "keyVaultReferenceIdentity": "[if(parameters('IsMultiTenantSetUp'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), 'SystemAssigned')]", - "siteConfig": { - "remoteDebuggingEnabled": false, - "webSocketsEnabled": false, - "requestTracingEnabled": true, - "reservedInstanceCount": 1, - "httpLoggingEnabled": true, - "detailedErrorLoggingEnabled": true, - "minTlsVersion": 1.2, - "netFrameworkVersion": "v6.0", - "ftpsState": "Disabled", - "appSettings": [ - { - "name": "AzureFunctionsJobHost__functionTimeout", - "value": "[parameters('WorkItemProcessorFunctionTimeout')]" - }, - { - "name": "AzureWebJobsStorage", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value), parameters('CentralStorageAccountConnectionString'))]" - }, - { - "name": "AuthNSettings__InternalIdentityConnectionString", - "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" - }, - { - "name": "AuthNSettings__ScannerIdentityConnectionString", - "value": "[if(parameters('IsMultiTenantSetUp'), json('null'), concat('RunAs=App;AppId=',reference(parameters('MIResourceId'),'2018-11-30').clientId))]" - }, - { - "name": "AuthNSettings__ScannerApplicationPassword", - "value": "[if(parameters('IsMultiTenantSetUp'), concat('@Microsoft.KeyVault(SecretUri=',parameters('ScannerIdentitySecretUri'), ')'), json('null'))]" - }, - { - "name": "AuthNSettings__ScannerApplicationId", - "value": "[if(parameters('IsMultiTenantSetUp'), parameters('ScannerIdentityApplicationId'), json('null'))]" - }, - { - "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", - "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" - }, - { - "name": "WEBSITE_CONTENTSHARE", - "value": "[toLower(variables('WorkItemProcessor'))]" - }, - { - "name": "FUNCTIONS_EXTENSION_VERSION", - "value": "~4" - }, - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "~10" - }, - { - "name": "APPINSIGHTS_INSTRUMENTATIONKEY", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - { - "name": "AIConfigurations__ResourceId", - "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - }, - { - "name": "AzureStorageSettings__ResourceId", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), resourceId('Microsoft.Storage/storageAccounts/', variables('storageName')), json('null'))]" - }, - { - "name": "AzureStorageSettings__ConnectionString", - "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), json('null'), parameters('CentralStorageAccountConnectionString'))]" - }, - { - "name": "LAConfigurations__ResourceId", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]" - }, - { - "name": "LAConfigurations__WorkspaceId", - "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')), '2017-03-15-preview').customerId]" - }, - { - "name": "FUNCTIONS_WORKER_RUNTIME", - "value": "[variables('functionWorkerRuntime')]" - }, - { - "name": "queueTriggerName", - "value": "[variables('storageQueueName')]" - }, - { - "name": "AppMetadata__AppName", - "value": "[variables('WorkItemProcessor')]" - }, - { - "name": "GraphConfigurations__IsFeatureEnabled", - "value": "[parameters('IsGraphFeatureEnabled')]" - }, - { - "name": "WebJobConfigurations__CloudEnvironmentName", - "value": "[parameters('AzureEnvironmentName')]" - }, - { - "name": "FeatureManagement__OrgPolicy", - "value": "[if(parameters('IsAzTSUIEnabled'), 'true', 'false')]" - }, - { - "name": "WEBSITE_VNET_ROUTE_ALL", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_CONTENTOVERVNET", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_DNS_SERVER", - "value": "168.63.129.16" - }, - { - "name": "MultiTenantConfigurations__IsFeatureEnabled", - "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" - }, - { - "name": "MultiTenantConfigurations__HostTenant", - "value": "[if(parameters('IsMultiTenantSetUp'), parameters('TenantId'), json('null'))]" - } - ], - "functionsRuntimeScaleMonitoringEnabled": "[if(parameters('EnableVnetIntegration'), 'true', 'false')]" - } - }, - "resources": [ - { - "apiVersion": "2018-11-01", - "name": "MSDeploy", - "type": "Extensions", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'))]" - ], - "properties": { - "packageUri": "[parameters('WorkItemProcessorPackageURL')]" - } - }, - { - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "name": "ftp", - "location": "[resourceGroup().location]", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'))]", - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "location": "[resourceGroup().location]", - "name": "scm", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/',variables('WorkItemProcessor'))]", - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "name": "virtualNetwork", - "type": "config", - "apiVersion": "2018-02-01", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "subnetResourceId": "[variables('functionAppSubnetId')]", - "swiftSupported": true - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "name": "[concat(variables('WorkItemProcessor'), '/web')]", - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', variables('WorkItemProcessor'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "ipSecurityRestrictions": [ - { - "vnetSubnetResourceId": "[variables('functionAppSubnetId')]", - "action": "Allow", - "priority": 100, - "name": "allowtrafficfromfunctionsubnet" - } - ] - } - } - ] - }, - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "apiVersion": "2018-11-01", - "name": "[variables('AutoUpdater')]", - "type": "Microsoft.Web/sites", - "location": "[resourceGroup().location]", - "kind": "functionapp", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": { - } - } - }, - "tags": { - "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", - "displayName": "Website" - }, - "dependsOn": [ - "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('AUfileShare'))]" - ], - "properties": { - "name": "[variables('AutoUpdater')]", - "httpsOnly": true, - "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", - "siteConfig": { - "remoteDebuggingEnabled": false, - "webSocketsEnabled": false, - "requestTracingEnabled": true, - "reservedInstanceCount": 1, - "httpLoggingEnabled": true, - "detailedErrorLoggingEnabled": true, - "minTlsVersion": 1.2, - "netFrameworkVersion": "v6.0", - "ftpsState": "Disabled", - "appSettings": [ - { - "name": "AIConfigurations__TelemetryIdentifier", - "value": "[parameters('TelemetryIdentifier')]" - }, - { - "name": "Basic_Authentication_Disabled", - "value": "true" - }, - { - "name": "AzureFunctionsJobHost__functionTimeout", - "value": "[parameters('AutoUpdaterFunctionTimeout')]" - }, - { - "name": "AzureWebJobsStorage", - "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" - }, - { - "name": "AuthNSettings__ScannerIdentityConnectionString", - "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" - }, - { - "name": "AuthNSettings__InternalIdentityConnectionString", - "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" - }, - { - "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", - "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" - }, - { - "name": "WEBSITE_CONTENTSHARE", - "value": "[toLower(variables('AutoUpdater'))]" - }, - { - "name": "FUNCTIONS_EXTENSION_VERSION", - "value": "~4" - }, - { - "name": "WEBSITE_NODE_DEFAULT_VERSION", - "value": "~10" - }, - { - "name": "APPINSIGHTS_INSTRUMENTATIONKEY", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - { - "name": "AIConfigurations__ResourceId", - "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - }, - { - "name": "FUNCTIONS_WORKER_RUNTIME", - "value": "[variables('functionWorkerRuntime')]" - }, - { - "name": "HostEnvironmentDetails__HostSubscriptionId", - "value": "[subscription().subscriptionId]" - }, - { - "name": "HostEnvironmentDetails__HostResourceGroupName", - "value": "[resourceGroup().name]" - }, - { - "name": "HostEnvironmentDetails__AppPostfix", - "value": "[parameters('ResourceHash')]" - }, - { - "name": "AIConfigurations__AnonymousUsageTelemetry__LogLevel", - "value": "[parameters('AnonymousUsageTelemetryLogLevel')]" - }, - { - "name": "WEBSITE_RUN_FROM_PACKAGE", - "value": "https://aka.ms/AzTS/V4/AutoUpdater" - }, - { - "name": "WebJobConfigurations__CloudEnvironmentName", - "value": "[parameters('AzureEnvironmentName')]" - }, - { - "name": "WEBSITE_VNET_ROUTE_ALL", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_CONTENTOVERVNET", - "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" - }, - { - "name": "WEBSITE_DNS_SERVER", - "value": "168.63.129.16" - }, - { - "name": "OnboardingDetails__Organization", - "value": "[parameters('OrganizationName')]" - }, - { - "name": "OnboardingDetails__Division", - "value": "[parameters('DivisionName')]" - }, - { - "name": "OnboardingDetails__ContactEmailAddressList", - "value": "[parameters('ContactEmailAddressList')]" - }, - { - "name": "OnboardingDetails__TenantId", - "value": "[parameters('HashedTenantId')]" - } - ], - "functionsRuntimeScaleMonitoringEnabled": "[if(parameters('EnableVnetIntegration'), 'true', 'false')]" - } - }, - "resources": [ - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "apiVersion": "2018-11-01", - "name": "MSDeploy", - "type": "Extensions", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]" - ], - "properties": { - "packageUri": "https://aka.ms/AzTS/V4/AutoUpdater" - } - }, - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "name": "ftp", - "location": "[resourceGroup().location]", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]", - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "type": "basicPublishingCredentialsPolicies", - "apiVersion": "2022-03-01", - "location": "[resourceGroup().location]", - "name": "scm", - "kind": "basicPublishingCredentialsPolicies", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]", - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'), '/Extensions/MSDeploy')]" - ], - "properties": { - "allow": "false" - } - }, - { - "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAutoUpdaterEnabled'))]", - "name": "virtualNetwork", - "type": "config", - "apiVersion": "2018-02-01", - "dependsOn": [ - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "subnetResourceId": "[variables('functionAppSubnetId')]", - "swiftSupported": true - } - }, - { - "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAutoUpdaterEnabled'))]", - "name": "[concat(variables('AutoUpdater'), '/web')]", - "type": "Microsoft.Web/sites/config", - "apiVersion": "2018-11-01", - "dependsOn": [ - "[resourceId('Microsoft.Web/sites', variables('AutoUpdater'))]", - "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", - "[concat('Microsoft.Web/sites/', variables('AutoUpdater'), '/Extensions/MSDeploy')]" - ], - "properties": - { - "ipSecurityRestrictions": [ - { - "vnetSubnetResourceId": "[variables('functionAppSubnetId')]", - "action": "Allow", - "priority": 100, - "name": "allowtrafficfromfunctionsubnet" - } - ] - } - } - ] - }, - { - "type": "Microsoft.Authorization/roleAssignments", - "apiVersion": "2017-09-01", - "name": "[variables('rgRoleAssignmentName')]", - "properties": { - "roleDefinitionId": "[variables('contributorRoleId')]", - "principalId": "[reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').principalId]", - "scope": "[resourceGroup().id]" - }, - "dependsOn": [ - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", - "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", - "[resourceId('Microsoft.Logic/workflows', variables('LogicAppWorkflowName'))]" - ] - }, - { - "type": "microsoft.insights/components", - "apiVersion": "2020-02-02-preview", - "name": "[variables('applicationInsightsName')]", - "location": "[resourceGroup().location]", - "tags": { - "[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" - }, - "properties": { - "ApplicationId": "[variables('applicationInsightsName')]", - "Request_Source": "WebAppCreate", - "WorkspaceResourceId": "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]", - "IngestionMode": "LogAnalytics", - "publicNetworkAccessForIngestion": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", - "publicNetworkAccessForQuery": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", - "dependsOn" :[ - "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]" - ] - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "microsoft.insights/privatelinkscopes", - "apiVersion": "2019-10-17-preview", - "name": "[variables('private_link_scope_name')]", - "location": "global", - "properties": {} - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "microsoft.insights/privatelinkscopes/scopedresources", - "apiVersion": "2019-10-17-preview", - "name": "[concat(variables('private_link_scope_name'), '/', concat(variables('workspaceName'), '-connection'))]", - "dependsOn": [ - "[resourceId('microsoft.insights/privatelinkscopes', variables('private_link_scope_name'))]", - "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]" - ], - "properties": { - "linkedResourceId": "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]" - } - }, - { - "condition": "[parameters('EnableVnetIntegration')]", - "type": "microsoft.insights/privatelinkscopes/scopedresources", - "apiVersion": "2019-10-17-preview", - "name": "[concat(variables('private_link_scope_name'), '/', concat(variables('applicationInsightsName'), '-connection'))]", - "dependsOn": [ - "[resourceId('microsoft.insights/privatelinkscopes', variables('private_link_scope_name'))]", - "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - ], - "properties": { - "linkedResourceId": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - } - }, - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "type": "Microsoft.Web/connections", - "apiVersion": "2016-06-01", - "name": "[variables('AutoUpdaterAPIConnectionName')]", - "location": "[resourceGroup().location]", - "kind": "V1", - "properties": { - "displayName": "[variables('AutoUpdaterAPIConnectionName')]", - "customParameterValues": {}, - "api": { - "id": "[variables('AutoUpdaterConnectionAPI')]" - }, - "parameterValueType": "Alternative" - } - }, - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "type": "Microsoft.Logic/workflows", - "apiVersion": "2017-07-01", - "name": "[variables('LogicAppWorkflowName')]", - "location": "[resourceGroup().location]", - "identity": { - "type": "UserAssigned", - "userAssignedIdentities": { - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": {} - } - }, - "dependsOn": [ - "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", - "[resourceId('Microsoft.Web/connections', variables('AutoUpdaterAPIConnectionName'))]" - ], - "properties": { - "state": "Enabled", - "definition": { - "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "$connections": { - "defaultValue": {}, - "type": "Object" - } - }, - "triggers": { - "Recurrence": { - "recurrence": { - "frequency": "Day", - "interval": 1, - "schedule": { - "hours": [ - "23" - ] - } - }, - "type": "Recurrence" - } - }, - "actions": { - "Restart_AutoUpdater": { - "runAfter": {}, - "type": "ApiConnection", - "inputs": { - "host": { - "connection": { - "name": "@parameters('$connections')['azureappservice']['connectionId']" - } - }, - "method": "post", - "path": "[concat('/subscriptions/@{encodeURIComponent(''',subscription().subscriptionId,''')}/resourcegroups/@{encodeURIComponent(''',resourceGroup().name,''')}/providers/Microsoft.Web/sites/@{encodeURIComponent(''',variables('AutoUpdater'),''')}/restart')]", - "queries": { - "api-version": "2019-08-01" - } - } - } - }, - "outputs": {} - }, - "parameters": { - "$connections": { - "value": { - "azureappservice": { - "connectionId": "[resourceId('Microsoft.Web/connections', variables('AutoUpdaterAPIConnectionName'))]", - "connectionProperties": { - "authentication": { - "type": "ManagedServiceIdentity", - "identity" : "[resourceId('Microsoft.managedidentity/userassignedidentities', variables('internalMIName'))]" - } - }, - "id": "[variables('AutoUpdaterConnectionAPI')]" - } - } - } - } - } - }, - { - "condition": "[and(parameters('EnableWAF'), parameters('IsAzTSUIEnabled'))]", - "type": "Microsoft.Network/frontDoors", - "apiVersion": "2020-05-01", - "name": "[variables('APIFrontDoorName')]", - "location": "Global", - "dependsOn": [ - "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" - ], - "properties": { - "routingRules": [ - { - "name": "AzSK-AzTS-API-RoutingRule", - "properties": { - "frontendEndpoints": [ - { - "id": "[resourceId('Microsoft.Network/frontDoors/frontendEndpoints', variables('APIFrontDoorName'), 'AzSK-AzTS-API-FrontendEndpoints')]" - } - ], - "acceptedProtocols": [ - "Https" - ], - "patternsToMatch": [ - "/*" - ], - "routeConfiguration": { - "@odata.type": "#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration", - "forwardingProtocol": "HttpsOnly", - "backendPool": { - "id": "[resourceId('Microsoft.Network/frontDoors/backendPools', variables('APIFrontDoorName'), 'AzSK-AzTS-API-BackendPool')]" - } - }, - "enabledState": "Enabled" - } - } - ], - "healthProbeSettings": [ - { - "name": "APIHealthProbeSettings", - "properties": { - "path": "/", - "protocol": "Https", - "intervalInSeconds": 30, - "enabledState": "Enabled", - "healthProbeMethod": "HEAD" - } - } - ], - "loadBalancingSettings": [ - { - "name": "APIloadBalancingSettings", - "properties": { - "sampleSize": 4, - "successfulSamplesRequired": 2 - } - } - ], - "backendPools": [ - { - "name": "AzSK-AzTS-API-BackendPool", - "properties": { - "backends": [ - { - "address": "[concat(variables('WebApi'), parameters('WebAppEndpointSuffix'))]", - "httpPort": 80, - "httpsPort": 443, - "priority": 1, - "weight": 50, - "backendHostHeader": "[concat(variables('WebApi'), parameters('WebAppEndpointSuffix'))]", - "enabledState": "Enabled" - } - ], - "loadBalancingSettings": { - "id": "[resourceId('Microsoft.Network/frontDoors/loadBalancingSettings', variables('APIFrontDoorName'), 'APIloadBalancingSettings')]" - }, - "healthProbeSettings": { - "id": "[resourceId('Microsoft.Network/frontDoors/healthProbeSettings', variables('APIFrontDoorName'), 'APIHealthProbeSettings')]" - } - } - } - ], - "frontendEndpoints": [ - { - "name": "AzSK-AzTS-API-FrontendEndpoints", - "properties": { - "hostName": "[concat(variables('APIFrontDoorName'), parameters('FrontDoorEndpointSuffix'))]", - "sessionAffinityEnabledState": "Disabled", - "webApplicationFirewallPolicyLink": { - "id": "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" - } - } - } - ], - "enabledState": "Enabled" - } - }, - { - "condition": "[and(parameters('EnableWAF'), parameters('IsAzTSUIEnabled'))]", - "type": "Microsoft.Network/frontDoors", - "apiVersion": "2020-05-01", - "name": "[variables('UIFrontDoorName')]", - "location": "Global", - "dependsOn": [ - "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" - ], - "properties": { - "routingRules": [ - { - "name": "AzSK-AzTS-UI-RoutingRule", - "properties": { - "frontendEndpoints": [ - { - "id": "[resourceId('Microsoft.Network/frontDoors/frontendEndpoints', variables('UIFrontDoorName'), 'AzSK-AzTS-UI-FrontendEndpoints')]" - } - ], - "acceptedProtocols": [ - "Https" - ], - "patternsToMatch": [ - "/*" - ], - "routeConfiguration": { - "@odata.type": "#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration", - "forwardingProtocol": "HttpsOnly", - "backendPool": { - "id": "[resourceId('Microsoft.Network/frontDoors/backendPools', variables('UIFrontDoorName'), 'AzSK-AzTS-UI-BackendPool')]" - } - }, - "enabledState": "Enabled" - } - } - ], - "healthProbeSettings": [ - { - "name": "UIHealthProbeSettings", - "properties": { - "path": "/", - "protocol": "Https", - "intervalInSeconds": 30, - "enabledState": "Enabled", - "healthProbeMethod": "HEAD" - } - } - ], - "loadBalancingSettings": [ - { - "name": "UIloadBalancingSettings", - "properties": { - "sampleSize": 4, - "successfulSamplesRequired": 2 - } - } - ], - "backendPools": [ - { - "name": "AzSK-AzTS-UI-BackendPool", - "properties": { - "backends": [ - { - "address": "[concat(variables('WebUI'), parameters('WebAppEndpointSuffix'))]", - "httpPort": 80, - "httpsPort": 443, - "priority": 1, - "weight": 50, - "backendHostHeader": "[concat(variables('WebUI'), parameters('WebAppEndpointSuffix'))]", - "enabledState": "Enabled" - } - ], - "loadBalancingSettings": { - "id": "[resourceId('Microsoft.Network/frontDoors/loadBalancingSettings', variables('UIFrontDoorName'), 'UIloadBalancingSettings')]" - }, - "healthProbeSettings": { - "id": "[resourceId('Microsoft.Network/frontDoors/healthProbeSettings', variables('UIFrontDoorName'), 'UIHealthProbeSettings')]" - } - } - } - ], - "frontendEndpoints": [ - { - "name": "AzSK-AzTS-UI-FrontendEndpoints", - "properties": { - "hostName": "[concat(variables('UIFrontDoorName'), parameters('FrontDoorEndpointSuffix'))]", - "sessionAffinityEnabledState": "Disabled", - "webApplicationFirewallPolicyLink": { - "id": "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" - } - } - } - ], - "enabledState": "Enabled" - } - }, - { - "condition": "[and(parameters('EnableWAF'), parameters('IsAzTSUIEnabled'))]", - "type": "Microsoft.Network/FrontDoorWebApplicationFirewallPolicies", - "apiVersion": "2020-11-01", - "name": "[variables('wafPolicyName')]", - "location": "global", - "sku": { - "name": "Classic_AzureFrontDoor" - }, - "properties": { - "policySettings": { - "enabledState": "Enabled", - "mode": "Prevention" - }, - "managedRules": { - "managedRuleSets": [ - { - "ruleSetType": "DefaultRuleSet", - "ruleSetVersion": "1.0" - } - ] - } - } - } - ], - "outputs": { - "storageId": { - "type": "string", - "value": "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" - }, - "serverfarmId": { - "type": "string", - "value": "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]" - }, - "storageQueueName": { - "type": "string", - "value": "[variables('storageQueueName')]" - }, - "uiAppName": { - "type": "string", - "value": "[variables('WebUI')]" - }, - "webApiName": { - "type": "string", - "value": "[variables('WebApi')]" - }, - "internalMIObjectId": { - "type": "string", - "value": "[reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').principalId]" - }, - "applicationInsightsId": { - "type": "string", - "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" - }, - "logAnalyticsResourceId": { - "type": "string", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]" - }, - "applicationInsightsIKey": { - "type": "string", - "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" - }, - "vnet" : { - "type": "string", - "value": "[variables('vnetName')]" - }, - "azTSUIFrontDoorUrl": { - "type": "string", - "value": "[concat('https://',variables('UIFrontDoorName'), parameters('FrontDoorEndpointSuffix'))]" - }, - "azTSAPIFrontDoorUrl": { - "type": "string", - "value": "[variables('APIFrontDoorUrl')]" - }, - "uiFrontDoorName": { - "type": "string", - "value": "[variables('UIFrontDoorName')]" - }, - "apiFrontDoorName": { - "type": "string", - "value": "[variables('APIFrontDoorName')]" - } - } +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "EnableVnetIntegration": { + "type": "bool", + "defaultValue": false + }, + "EnableWAF": { + "type": "bool", + "defaultValue": false + }, + "hostingPlanName": { + "type": "string", + "defaultValue": "AzSK-AzTS-AppServicePlan" + }, + "apiHostingPlanName": { + "type": "string", + "defaultValue": "AzSK-AzTS-API-AppServicePlan" + }, + "skuName": { + "type": "string", + "defaultValue": "[if(parameters('EnableVnetIntegration'), 'EP1', 'Y1')]", + "allowedValues": [ + "F1", + "D1", + "B1", + "B2", + "B3", + "S1", + "S2", + "S3", + "P1", + "P2", + "P3", + "P3V2", + "P2V2", + "P4", + "EP3", + "EP1", + "Y1" + ], + "metadata": { + "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" + } + }, + "apiSkuName": { + "type": "string", + "defaultValue": "S1", + "allowedValues": [ + "F1", + "D1", + "B1", + "B2", + "B3", + "S1", + "S2", + "S3", + "P1", + "P2", + "P3", + "P4" + ], + "metadata": { + "description": "Describes plan's pricing tier and capacity. Check details at https://azure.microsoft.com/en-us/pricing/details/app-service/" + } + }, + "skuCapacity": { + "type": "int", + "defaultValue": 1, + "minValue": 1, + "metadata": { + "description": "Describes plan's instance count" + } + }, + "storageSKU": { + "type": "string", + "defaultValue": "Standard_LRS", + "allowedValues": [ + "Standard_LRS", + "Standard_ZRS", + "Standard_GRS", + "Standard_RAGRS", + "Premium_LRS" + ] + }, + "runtime": { + "type": "string", + "defaultValue": "dotnet", + "allowedValues": [ + "node", + "dotnet", + "java" + ], + "metadata": { + "description": "The language worker runtime to load in the function app." + } + }, + "storageContainerName": { + "type": "string", + "defaultValue": "azskatsscanresult" + }, + "storageQueueNamePrefix": { + "type": "string", + "defaultValue": "subjobqueue" + }, + "laWorkspaceName": { + "type": "string", + "defaultValue": "AzSK-AzTS-LAWorkspace" + }, + "applicationInsightsName": { + "type": "string", + "defaultValue": "AzSK-AzTS-AppInsights" + }, + "internalMIName": { + "type": "string", + "defaultValue": "AzSK-AzTS-InternalMI" + }, + "laSkuName": { + "type": "string", + "allowedValues": [ + "pergb2018", + "Free", + "Standalone", + "PerNode", + "Standard", + "Premium" + ], + "defaultValue": "pergb2018", + "metadata": { + "description": "Pricing tier: PerGB2018 or legacy tiers (Free, Standalone, PerNode, Standard or Premium) which are not available to all customers." + } + }, + "MIResourceId": { + "type": "string" + }, + "TenantId": { + "type": "string" + }, + "UIClientId": { + "type": "string" + }, + "WebApiClientId": { + "type": "string" + }, + "ResourceHash": { + "type": "string" + }, + "TelemetryIdentifier": { + "type": "string" + }, + "OrganizationName": { + "type": "string", + "defaultValue": "NA" + }, + "DivisionName": { + "type": "string", + "defaultValue": "NA" + }, + "ContactEmailAddressList": { + "type": "string", + "defaultValue": "NA" + }, + "HashedTenantId": { + "type": "string", + "defaultValue": "NA" + }, + "AnonymousUsageTelemetryLogLevel": { + "type": "string", + "allowedValues": ["None", "Onboarding", "Anonymous", "All"], + "defaultValue": "None" + }, + "RuleEngineWorkflowName": { + "type": "string" + }, + "IsGraphFeatureEnabled": { + "type": "string" + }, + "MetadataAggregatorPackageURL": { + "type": "string" + }, + "WorkItemProcessorPackageURL": { + "type": "string" + }, + "WebApiPackageURL": { + "type": "string" + }, + "UIPackageURL": { + "type": "string" + }, + "AzureEnvironmentName": { + "type" : "string" + }, + "virtualNetworkAddressPrefix": { + "type": "string", + "defaultValue": "10.100.0.0/16", + "metadata": { + "description": "VNET address space." + } + }, + "functionSubnetAddressPrefix": { + "type": "string", + "defaultValue": "10.100.0.0/24", + "metadata": { + "description": "Function App's subnet address range." + } + }, + "privateEndpointSubnetAddressPrefix": { + "type": "string", + "defaultValue": "10.100.1.0/24", + "metadata": { + "description": "Storage account's private endpoint's subnet address range." + } + }, + "appserviceSubnetAddressPrefix": { + "type": "string", + "defaultValue": "10.100.2.0/24", + "metadata": { + "description": "App service subnet address range." + } + }, + "functionsSubnetName": { + "type": "string", + "defaultValue": "FunctionsSubnet", + "metadata": { + "description": "The subnet that the Function App will use for VNET traffic." + } + }, + "PrivateEndpointSubnetName": { + "type": "string", + "defaultValue": "PrivateEndpointSubnet", + "metadata": { + "description": "The subnet that will be used for private endpoints." + } + }, + "appserviceSubnetName": { + "type": "string", + "defaultValue": "AppServiceSubnet", + "metadata": { + "description": "The subnet that the App service will use for VNET traffic." + } + }, + "IsPIMEnabled": { + "type" : "bool", + "defaultValue": false + }, + "IsAutoUpdaterEnabled": { + "type" : "bool", + "defaultValue": true + }, + "IsAzTSUIEnabled": { + "type" : "bool", + "defaultValue": true + }, + "FrontDoorEndpointSuffix":{ + "type": "string" + }, + "WebAppEndpointSuffix":{ + "type": "string" + }, + "CentralStorageAccountConnectionString": { + "type" : "string" + }, + "IsMultiTenantSetUp": { + "type" : "bool", + "defaultValue": false + }, + "ScannerIdentitySecretUri":{ + "type": "string", + "metadata": { + "description": "Key Vault Secret Uri for Central scanning App's credential in multi-tenant setup" + } + }, + "ScannerIdentityApplicationId":{ + "type": "string", + "metadata": { + "description": "Application Id of central scanning identity in multi-tenant setup" + } + }, + "AutoUpdaterFunctionTimeout": { + "type": "string", + "defaultValue": "00:09:00" + }, + "WorkItemProcessorFunctionTimeout": { + "type": "string", + "defaultValue": "00:09:00" + }, + "MetadataAggregatorFunctionTimeout": { + "type": "string", + "defaultValue": "00:09:00" + }, + "AADClientAppDetailsInstance": { + "type": "string", + "defaultValue": "https://login.microsoftonline.com/" + }, + "AzureEnvironmentPortalURI": { + "type": "string", + "defaultValue": "https://portal.azure.com/" + } + }, + "variables": { + "MetadataAggregator": "[concat('AzSK-AzTS-MetadataAggregator-', parameters('ResourceHash'))]", + "APIFrontDoorName": "[concat('AzSK-AzTS-API-FrontDoor-', parameters('ResourceHash'))]", + "UIFrontDoorName": "[concat('AzSK-AzTS-UI-FrontDoor-', parameters('ResourceHash'))]", + "wafPolicyName": "[concat('AzSKAzTSWAFPolicy', parameters('ResourceHash'))]", + "APIFrontDoorUrl": "[concat('https://',toLower(variables('APIFrontDoorName')), parameters('FrontDoorEndpointSuffix'))]", + "UIFrontDoorUrl": "[concat('https://',toLower(variables('UIFrontDoorName')), parameters('FrontDoorEndpointSuffix'))]", + "WebUIUrl": "[concat('https://',variables('WebApi'), parameters('WebAppEndpointSuffix'))]", + "WorkItemProcessor": "[concat('AzSK-AzTS-WorkItemProcessor-', parameters('ResourceHash'))]", + "AutoUpdater": "[concat('AzSK-AzTS-AutoUpdater-', parameters('ResourceHash'))]", + "WebApi": "[concat('AzSK-AzTS-WebApi-', parameters('ResourceHash'))]", + "WebUI": "[concat('AzSK-AzTS-UI-', parameters('ResourceHash'))]", + "WebUISlotName": "[concat('Staging-', parameters('ResourceHash'))]", + "functionWorkerRuntime": "[parameters('runtime')]", + "storageName": "[concat('azskaztsstorage', parameters('ResourceHash'))]", + "workspaceName": "[concat('AzSK-AzTS-LAWorkspace-', parameters('ResourceHash'))]", + "applicationInsightsName": "[concat(parameters('applicationInsightsName'), '-', parameters('ResourceHash'))]", + "internalMIName": "[concat(parameters('internalMIName'), '-', parameters('ResourceHash'))]", + "rgRoleAssignmentGuid": "[guid(resourceGroup().id)]", + "contributorRoleId": "[concat(subscription().Id, '/providers/Microsoft.Authorization/roleDefinitions/b24988ac-6180-42a0-ab88-20f7382dd24c')]", + "rgRoleAssignmentName": "[variables('rgRoleAssignmentGuid')]", + "LogicAppWorkflowName" : "[concat('AzSK-AzTS-AutoUpdater-LogicApp-', parameters('ResourceHash'))]", + "AutoUpdaterAPIConnectionName" : "[concat('azsk-azts-autoupdater-connection-', parameters('ResourceHash'))]", + "AutoUpdaterConnectionAPI" : "[concat('/subscriptions/', subscription().subscriptionId, '/providers/Microsoft.Web/locations/',resourceGroup().location,'/managedApis/azureappservice')]", + "vnetName": "[concat('AzSK-AzTS-Vnet-', parameters('ResourceHash'))]", + "BlobDnsZoneName": "[concat('privatelink.blob.', environment().suffixes.storage)]", + "azureMoinitorDnsZoneName": "privatelink.monitor.azure.com", + "azureAutomationDnsZoneName": "privatelink.agentsvc.azure-automation.net", + "OMSopinsightsDnsZoneName": "privatelink.oms.opinsights.azure.com", + "ODSopinsightsDnsZoneName": "privatelink.ods.opinsights.azure.com", + "privateEndpointForAMPLS": "[concat(variables('private_link_scope_name'), '-private-endpoint')]", + "virtualNetworkLinksSuffixBlobStorageName": "[concat(variables('BlobDnsZoneName'), '-link')]", + "virtualNetworkLinksSuffixAzureMonitorName": "[concat(variables('azureMoinitorDnsZoneName'), '-link')]", + "virtualNetworkLinksSuffixAzureAutomationName": "[concat(variables('azureAutomationDnsZoneName'), '-link')]", + "virtualNetworkLinksSuffixOMSinsightsName": "[concat(variables('OMSopinsightsDnsZoneName'), '-link')]", + "virtualNetworkLinksSuffixODSinsightsName": "[concat(variables('ODSopinsightsDnsZoneName'), '-link')]", + "MAfileShare": "[toLower(variables('MetadataAggregator'))]", + "WIfileShare": "[toLower(variables('WorkItemProcessor'))]", + "AUfileShare": "[toLower(variables('AutoUpdater'))]", + "WebApifileShare": "[toLower(variables('WebApi'))]", + "UIfileShare": "[toLower(variables('WebUI'))]", + "functionAppSubnetId":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('functionsSubnetName'))]", + "appserviceSubnetId":"[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('appserviceSubnetName'))]", + "private_link_scope_name": "[concat('AzSK-AzTS-private-link-scope-', parameters('ResourceHash'))]", + "storageQueueName" : "[concat(parameters('storageQueueNamePrefix'), toLower(parameters('ResourceHash')))]", + "userAssignedIdentities" : { + "singleTenantSetUp": { + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": {}, + "[parameters('MIResourceId')]": {} + }, + "multiTenantSetUp": { + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": {} + } + } + }, + "resources": [ + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/virtualNetworks", + "apiVersion": "2020-07-01", + "location": "[resourceGroup().location]", + "name": "[variables('vnetName')]", + "properties": { + "addressSpace": { + "addressPrefixes": [ + "[parameters('virtualNetworkAddressPrefix')]" + ] + }, + "subnets": [ + { + "name": "[parameters('functionsSubnetName')]", + "properties": { + "addressPrefix": "[parameters('functionSubnetAddressPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "serviceEndpoints": [ + { + "service": "Microsoft.Storage" + }, + { + "service": "Microsoft.Web" + } + ], + "delegations": [ + { + "name": "webapp", + "properties": { + "serviceName": "Microsoft.Web/serverFarms", + "actions": [ + "Microsoft.Network/virtualNetworks/subnets/action" + ] + } + } + ] + } + }, + { + "name": "[parameters('appserviceSubnetName')]", + "properties": { + "addressPrefix": "[parameters('appserviceSubnetAddressPrefix')]", + "privateEndpointNetworkPolicies": "Enabled", + "privateLinkServiceNetworkPolicies": "Enabled", + "serviceEndpoints": [ + { + "service": "Microsoft.Storage" + } + ], + "delegations": [ + { + "name": "webapp", + "properties": { + "serviceName": "Microsoft.Web/serverFarms", + "actions": [ + "Microsoft.Network/virtualNetworks/subnets/action" + ] + } + } + ] + } + }, + { + "name": "[parameters('PrivateEndpointSubnetName')]", + "properties": { + "addressPrefix": "[parameters('privateEndpointSubnetAddressPrefix')]", + "privateLinkServiceNetworkPolicies": "Enabled", + "privateEndpointNetworkPolicies": "Disabled", + "serviceEndpoints": [ + { + "service": "Microsoft.Storage" + } + ] + } + } + ], + "enableDdosProtection": false, + "enableVmProtection": false + } + }, + { + "name": "[variables('storageName')]", + "type": "Microsoft.Storage/storageAccounts", + "location": "[resourceGroup().location]", + "apiVersion": "2019-06-01", + "sku": { + "name": "[parameters('storageSKU')]" + }, + "dependsOn": [], + "properties": { + "supportsHttpsTrafficOnly": true, + "allowBlobPublicAccess": false, + "minimumTlsVersion": "TLS1_2" + }, + "tags": { + "displayName": "azskaztsapp" + }, + "kind": "StorageV2", + "resources": [ + { + "type": "blobServices/containers", + "apiVersion": "2019-06-01", + "name": "[concat('default/', parameters('StorageContainerName'))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + ], + "properties": { + "publicAccess": "None" + } + } + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageName'), '/default/', variables('MAfileShare'))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageName'), '/default/', variables('WIfileShare'))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageName'), '/default/', variables('AUfileShare'))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageName'), '/default/', variables('WebApifileShare'))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + ] + }, + { + "type": "Microsoft.Storage/storageAccounts/fileServices/shares", + "apiVersion": "2019-06-01", + "name": "[concat(variables('storageName'), '/default/', variables('UIfileShare'))]", + "dependsOn": [ + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + ] + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[variables('BlobDnsZoneName')]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ] + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[variables('azureMoinitorDnsZoneName')]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ] + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[variables('azureAutomationDnsZoneName')]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ] + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[variables('OMSopinsightsDnsZoneName')]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ] + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones", + "apiVersion": "2020-06-01", + "name": "[variables('ODSopinsightsDnsZoneName')]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ] + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[concat(variables('BlobDnsZoneName'), '/', variables('virtualNetworkLinksSuffixBlobStorageName'))]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones',variables('BlobDnsZoneName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ], + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + } + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[concat(variables('azureMoinitorDnsZoneName'), '/', variables('virtualNetworkLinksSuffixAzureMonitorName'))]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones',variables('azureMoinitorDnsZoneName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ], + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + } + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[concat(variables('azureAutomationDnsZoneName'), '/', variables('virtualNetworkLinksSuffixAzureAutomationName'))]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones',variables('azureAutomationDnsZoneName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ], + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + } + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[concat(variables('OMSopinsightsDnsZoneName'), '/', variables('virtualNetworkLinksSuffixOMSinsightsName'))]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones',variables('OMSopinsightsDnsZoneName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ], + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + } + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateDnsZones/virtualNetworkLinks", + "apiVersion": "2020-06-01", + "name": "[concat(variables('ODSopinsightsDnsZoneName'), '/', variables('virtualNetworkLinksSuffixODSinsightsName'))]", + "location": "global", + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones',variables('ODSopinsightsDnsZoneName'))]", + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + ], + "properties": { + "registrationEnabled": false, + "virtualNetwork": { + "id": "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]" + } + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateEndpoints", + "name": "[variables('privateEndpointForAMPLS')]", + "apiVersion": "2020-06-01", + "location": "[resourceGroup().location]", + "dependsOn": [ + "[resourceId('Microsoft.Network/virtualNetworks', variables('vnetName'))]", + "[resourceId('microsoft.insights/privatelinkscopes', variables('private_link_scope_name'))]" + ], + "properties": { + "subnet": { + "id": "[resourceId('Microsoft.Network/virtualNetworks/subnets', variables('vnetName'), parameters('PrivateEndpointSubnetName'))]" + }, + "privateLinkServiceConnections": [ + { + "name": "AMPLSPrivateLinkConnection", + "properties": { + "privateLinkServiceId": "[resourceId('microsoft.insights/privateLinkScopes', variables('private_link_scope_name'))]", + "groupIds": [ + "azuremonitor" + ] + } + } + ] + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "Microsoft.Network/privateEndpoints/privateDnsZoneGroups", + "apiVersion": "2020-06-01", + "location": "[resourceGroup().location]", + "name": "[concat(variables('privateEndpointForAMPLS'), '/default')]", + "dependsOn": [ + "[resourceId('Microsoft.Network/privateDnsZones', variables('BlobDnsZoneName'))]", + "[resourceId('Microsoft.Network/privateEndpoints', variables('privateEndpointForAMPLS'))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('azureMoinitorDnsZoneName'))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('azureAutomationDnsZoneName'))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('OMSopinsightsDnsZoneName'))]", + "[resourceId('Microsoft.Network/privateDnsZones', variables('ODSopinsightsDnsZoneName'))]" + ], + "properties": { + "privateDnsZoneConfigs": [ + { + "name": "config1", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('BlobDnsZoneName'))]" + } + }, + { + "name": "config2", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('azureMoinitorDnsZoneName'))]" + } + }, + { + "name": "config3", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('azureAutomationDnsZoneName'))]" + } + }, + { + "name": "config4", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('OMSopinsightsDnsZoneName'))]" + } + }, + { + "name": "config5", + "properties": { + "privateDnsZoneId": "[resourceId('Microsoft.Network/privateDnsZones', variables('ODSopinsightsDnsZoneName'))]" + } + } + ] + } + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "name": "[variables('workspaceName')]", + "apiVersion": "2017-03-15-preview", + "location": "[resourceGroup().location]", + "properties": { + "sku": { + "name": "[parameters('laSkuName')]" + }, + "publicNetworkAccessForIngestion": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", + "publicNetworkAccessForQuery": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", + "retentionInDays": 120, + "features": { + "searchVersion": 1, + "legacy": 0, + "enableLogAccessUsingOnlyResourcePermissions": true + } + } + }, + { + "type": "Microsoft.ManagedIdentity/userAssignedIdentities", + "name": "[variables('internalMIName')]", + "apiVersion": "2018-11-30", + "location": "[resourceGroup().location]" + }, + { + "apiVersion": "2015-08-01", + "name": "[parameters('hostingPlanName')]", + "type": "Microsoft.Web/serverfarms", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "HostingPlan" + }, + "sku": { + "name": "[parameters('skuName')]", + "capacity": "[parameters('skuCapacity')]", + "tier": "Dynamic" + }, + "kind": "functionapp", + "properties": { + "name": "[parameters('hostingPlanName')]", + "computeMode": "Dynamic" + } + }, + { + "condition": "[parameters('IsAzTSUIEnabled')]", + "apiVersion": "2015-08-01", + "name": "[parameters('apiHostingPlanName')]", + "type": "Microsoft.Web/serverfarms", + "location": "[resourceGroup().location]", + "tags": { + "displayName": "ApiHostingPlan" + }, + "sku": { + "name": "[parameters('apiSkuName')]", + "capacity": "[parameters('skuCapacity')]" + }, + "kind": "app", + "properties": { + "name": "[parameters('apiHostingPlanName')]" + } + }, + { + "condition": "[parameters('IsAzTSUIEnabled')]", + "name": "[variables('WebApi')]", + "type": "Microsoft.Web/sites", + "location": "[resourceGroup().location]", + "apiVersion": "2015-08-01", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": { + } + } + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('WebApifileShare'))]" + ], + "tags": { + "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName')))]": "Resource", + "displayName": "WebApi" + }, + "properties": { + "name": "[variables('WebApi')]", + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", + "keyVaultReferenceIdentity": "[if(parameters('IsMultiTenantSetUp'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), 'SystemAssigned')]", + "siteConfig": { + "remoteDebuggingEnabled": false, + "webSocketsEnabled": false, + "requestTracingEnabled": true, + "httpLoggingEnabled": true, + "detailedErrorLoggingEnabled": true, + "minTlsVersion": 1.2, + "ftpsState": "Disabled", + "alwaysOn": true, + "appSettings": [ + { + "name": "AzureWebJobsStorage", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value), parameters('CentralStorageAccountConnectionString'))]" + }, + { + "name": "AuthNSettings__ScannerIdentityConnectionString", + "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" + }, + { + "name": "AuthNSettings__InternalIdentityConnectionString", + "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" + }, + { + "name": "AuthNSettings__ScannerApplicationPassword", + "value": "[if(parameters('IsMultiTenantSetUp'), concat('@Microsoft.KeyVault(SecretUri=',parameters('ScannerIdentitySecretUri'), ')'), json('null'))]" + }, + { + "name": "AuthNSettings__ScannerApplicationId", + "value": "[if(parameters('IsMultiTenantSetUp'), parameters('ScannerIdentityApplicationId'), json('null'))]" + }, + { + "name": "AADClientAppDetails__ResourceId", + "value": "[concat('api://',parameters('WebApiClientId'))]" + }, + { + "name": "AADClientAppDetails__Instance", + "value": "[concat(parameters('AADClientAppDetailsInstance'),'/')]" + }, + { + "name": "AADClientAppDetails__ClientId", + "value": "[parameters('WebApiClientId')]" + }, + { + "name": "AADClientAppDetails__TenantId", + "value": "[parameters('TenantId')]" + }, + { + "name": "AADClientAppDetails__Issuer", + "value": "[concat(parameters('AADClientAppDetailsInstance'),'/',parameters('TenantId'),'/v2.0')]" + }, + { + "name": "AADClientAppDetails__ApplicationId", + "value": "[parameters('UIClientId')]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[toLower(variables('WebApi'))]" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "~10" + }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + { + "name": "ApplicationInsights__InstrumentationKey", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + { + "name": "AIConfigurations__ResourceId", + "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + }, + { + "name": "AzureStorageSettings__ResourceId", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), resourceId('Microsoft.Storage/storageAccounts/', variables('storageName')), json('null'))]" + }, + { + "name": "AzureStorageSettings__ConnectionString", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), json('null'), parameters('CentralStorageAccountConnectionString'))]" + }, + { + "name": "AzureStorageSettings__QueueName", + "value": "[variables('storageQueueName')]" + }, + { + "name": "LAConfigurations__ResourceId", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]" + }, + { + "name": "LAConfigurations__WorkspaceId", + "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')), '2017-03-15-preview').customerId]" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" + }, + { + "name": "AppMetadata__AppName", + "value": "[variables('WebApi')]" + }, + { + "name": "AppMetadata__LinkedAppName", + "value": "[variables('WebUI')]" + }, + { + "name": "WebJobConfigurations__CloudEnvironmentName", + "value": "[parameters('AzureEnvironmentName')]" + }, + { + "name": "FeatureManagement__OrgPolicy", + "value": true + }, + { + "name": "FeatureManagement__AddNewControl", + "value": true + }, + { + "name": "UIConfigurations__ControlEditorFeatureConfiguration__IsAddNewControlEnabled", + "value": true + }, + { + "name": "UIConfigurations__ControlEditorFeatureConfiguration__IsEnabled", + "value": true + }, + { + "name": "UIConfigurations__AzureAPI", + "value": "[parameters('AzureEnvironmentPortalURI')]" + }, + { + "name": "CorsPolicySettings__OriginUrl", + "value": "[concat('https://',toLower(variables('WebUI')), parameters('WebAppEndpointSuffix'))]" + }, + { + "name": "WorkItemProcessorSettings__AppName", + "value": "[variables('WorkItemProcessor')]" + }, + { + "name": "WorkItemProcessorSettings__HostResourceGroupName", + "value": "[resourceGroup().name]" + }, + { + "name": "WorkItemProcessorSettings__HostSubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "WEBSITE_VNET_ROUTE_ALL", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_CONTENTOVERVNET", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_DNS_SERVER", + "value": "168.63.129.16" + }, + { + "name": "MultiTenantConfigurations__IsFeatureEnabled", + "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" + }, + { + "name": "MultiTenantConfigurations__HostTenant", + "value": "[if(parameters('IsMultiTenantSetUp'), parameters('TenantId'), json('null'))]" + }, + { + "name": "UIConfigurations__IsMultiTenantSetup", + "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" + } + ], + "cors": { + "allowedOrigins": [ + "[concat('https://',toLower(variables('WebUI')), parameters('WebAppEndpointSuffix'))]", + "[if(parameters('EnableWAF'), variables('APIFrontDoorUrl'), json('null'))]", + "[if(parameters('EnableWAF'), variables('UIFrontDoorUrl'), json('null'))]" + ] + } + } + }, + "resources": [ + { + "condition": "[parameters('IsAzTSUIEnabled')]", + "apiVersion": "2018-11-01", + "name": "ZipDeploy", + "type": "Extensions", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebApi'))]" + ], + "properties": { + "packageUri": "[parameters('WebApiPackageURL')]" + } + }, + { + "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAzTSUIEnabled'))]", + "name": "virtualNetwork", + "type": "config", + "apiVersion": "2018-02-01", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebApi'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", + "[concat('Microsoft.Web/sites/', variables('WebApi'), '/Extensions/ZipDeploy')]" + ], + "properties": + { + "subnetResourceId": "[variables('appserviceSubnetId')]", + "swiftSupported": true + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "ftp", + "location": "[resourceGroup().location]", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebApi'))]", + "[concat('Microsoft.Web/sites/', variables('WebApi'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "location": "[resourceGroup().location]", + "name": "scm", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebApi'))]", + "[concat('Microsoft.Web/sites/', variables('WebApi'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + } + ] + }, + { + "condition": "[parameters('IsAzTSUIEnabled')]", + "name": "[variables('WebUI')]", + "type": "Microsoft.Web/sites", + "location": "[resourceGroup().location]", + "apiVersion": "2015-08-01", + "identity": { + "type": "SystemAssigned" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('UIfileShare'))]" + ], + "tags": { + "[concat('hidden-related:', resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName')))]": "Resource", + "displayName": "UI" + }, + "properties": { + "name": "[variables('WebUI')]", + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", + "siteConfig": { + "remoteDebuggingEnabled": false, + "webSocketsEnabled": false, + "requestTracingEnabled": true, + "httpLoggingEnabled": true, + "detailedErrorLoggingEnabled": true, + "minTlsVersion": 1.2, + "ftpsState": "Disabled", + "alwaysOn": true, + "appSettings": [ + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + { + "name": "REACT_APP_API", + "value": "[if(parameters('EnableWAF'), variables('APIFrontDoorUrl'), variables('WebUIUrl'))]" + }, + { + "name": "REACT_APP_clientId", + "value": "[parameters('UIClientId')]" + }, + { + "name": "REACT_APP_tenantId", + "value": "[parameters('TenantId')]" + }, + { + "name": "REACT_APP_userImpersonation", + "value": "[parameters('WebApiClientId')]" + } + ] + } + }, + "resources": [ + { + "condition": "[parameters('IsAzTSUIEnabled')]", + "apiVersion": "2018-11-01", + "name": "ZipDeploy", + "type": "Extensions", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebUI'))]" + ], + "properties": { + "packageUri": "[parameters('UIPackageURL')]" + } + }, + { + "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAzTSUIEnabled'))]", + "name": "virtualNetwork", + "type": "config", + "apiVersion": "2018-02-01", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebUI'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", + "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/ZipDeploy')]" + ], + "properties": + { + "subnetResourceId": "[variables('appserviceSubnetId')]", + "swiftSupported": true + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "ftp", + "location": "[resourceGroup().location]", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebUI'))]", + "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "location": "[resourceGroup().location]", + "name": "scm", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WebUI'))]", + "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + } + ] + }, + { + "condition": "[parameters('IsAzTSUIEnabled')]", + "name": "[concat(variables('WebUI'), '/', variables('WebUISlotName'))]", + "type": "Microsoft.Web/sites/slots", + "location": "[resourceGroup().location]", + "apiVersion": "2015-08-01", + "kind": "app", + "comments": "This specifies the web app slots.", + "identity": { + "type": "SystemAssigned" + }, + "tags": { + "displayName": "WebAppSlots" + }, + "properties": { + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('apiHostingPlanName'))]", + "httpsOnly": true + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/Sites', variables('WebUI'))]", + "[concat('Microsoft.Web/sites/', variables('WebUI'), '/Extensions/ZipDeploy')]" + ], + "resources":[ + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "ftp", + "location": "[resourceGroup().location]", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/slots', variables('WebUI'), variables('WebUISlotName'))]" + ], + "properties": { + "allow": "false" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "location": "[resourceGroup().location]", + "name": "scm", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites/slots', variables('WebUI') , variables('WebUISlotName'))]" + ], + "properties": { + "allow": "false" + } + } + ], + "copy": { + "name": "webPortalSlot", + "count": 1 + } + }, + { + "apiVersion": "2018-11-01", + "name": "[variables('MetadataAggregator')]", + "type": "Microsoft.Web/sites", + "location": "[resourceGroup().location]", + "kind": "functionapp", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": "[if(parameters('IsMultiTenantSetUp'), variables('userAssignedIdentities')['multiTenantSetUp'], variables('userAssignedIdentities')['singleTenantSetUp'])]" + }, + "tags": { + "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", + "displayName": "Website" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('MAfileShare'))]" + ], + "properties": { + "name": "[variables('MetadataAggregator')]", + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", + "keyVaultReferenceIdentity": "[if(parameters('IsMultiTenantSetUp'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), 'SystemAssigned')]", + "siteConfig": { + "remoteDebuggingEnabled": false, + "webSocketsEnabled": false, + "requestTracingEnabled": true, + "httpLoggingEnabled": true, + "detailedErrorLoggingEnabled": true, + "reservedInstanceCount": 1, + "minTlsVersion": 1.2, + "netFrameworkVersion": "v6.0", + "ftpsState": "Disabled", + "appSettings": [ + { + "name": "AzureFunctionsJobHost__functionTimeout", + "value": "[parameters('MetadataAggregatorFunctionTimeout')]" + }, + { + "name": "AzureWebJobsStorage", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value), parameters('CentralStorageAccountConnectionString'))]" + }, + { + "name": "AuthNSettings__ScannerIdentityConnectionString", + "value": "[if(parameters('IsMultiTenantSetUp'), json('null'), concat('RunAs=App;AppId=',reference(parameters('MIResourceId'),'2018-11-30').clientId))]" + }, + { + "name": "AuthNSettings__ScannerApplicationPassword", + "value": "[if(parameters('IsMultiTenantSetUp'), concat('@Microsoft.KeyVault(SecretUri=',parameters('ScannerIdentitySecretUri'), ')'), json('null'))]" + }, + { + "name": "AuthNSettings__ScannerApplicationId", + "value": "[if(parameters('IsMultiTenantSetUp'), parameters('ScannerIdentityApplicationId'), json('null'))]" + }, + { + "name": "AuthNSettings__InternalIdentityConnectionString", + "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[toLower(variables('MetadataAggregator'))]" + }, + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~4" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "~10" + }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + { + "name": "AIConfigurations__ResourceId", + "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + }, + { + "name": "AzureStorageSettings__ResourceId", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), resourceId('Microsoft.Storage/storageAccounts/', variables('storageName')), json('null'))]" + }, + { + "name": "AzureStorageSettings__ConnectionString", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), json('null'), parameters('CentralStorageAccountConnectionString'))]" + }, + { + "name": "AzureStorageSettings__QueueName", + "value": "[variables('storageQueueName')]" + }, + { + "name": "LAConfigurations__ResourceId", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]" + }, + { + "name": "LAConfigurations__WorkspaceId", + "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')), '2017-03-15-preview').customerId]" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "[variables('functionWorkerRuntime')]" + }, + { + "name": "RuleEngine__WorkflowName", + "value": "[parameters('RuleEngineWorkflowName')]" + }, + { + "name": "AppMetadata__AppName", + "value": "[variables('MetadataAggregator')]" + }, + { + "name": "GraphConfigurations__IsFeatureEnabled", + "value": "[parameters('IsGraphFeatureEnabled')]" + }, + { + "name": "AuthzSettings__IsPIMEnabled", + "value": "[parameters('IsPIMEnabled')]" + }, + { + "name": "AuthzSettings__IsRGPIMEnabled", + "value": "[parameters('IsPIMEnabled')]" + }, + { + "name": "WebJobConfigurations__CloudEnvironmentName", + "value": "[parameters('AzureEnvironmentName')]" + }, + { + "name": "OnDemandProcessingQueue", + "value": "ondemandprocessingqueue" + }, + { + "name": "FeatureManagement__OrgPolicy", + "value": "[if(parameters('IsAzTSUIEnabled'), 'true', 'false')]" + }, + { + "name": "MultiTenantConfigurations__IsFeatureEnabled", + "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" + }, + { + "name": "MultiTenantConfigurations__HostTenant", + "value": "[if(parameters('IsMultiTenantSetUp'), parameters('TenantId'), json('null'))]" + }, + { + "name": "WEBSITE_VNET_ROUTE_ALL", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_CONTENTOVERVNET", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_DNS_SERVER", + "value": "168.63.129.16" + } + ], + "functionsRuntimeScaleMonitoringEnabled": "[if(parameters('EnableVnetIntegration'), 'true', 'false')]" + } + }, + "resources": [ + { + "apiVersion": "2018-11-01", + "name": "ZipDeploy", + "type": "Extensions", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]" + ], + "properties": { + "packageUri": "[parameters('MetadataAggregatorPackageURL')]" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "ftp", + "location": "[resourceGroup().location]", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]", + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "location": "[resourceGroup().location]", + "name": "scm", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]", + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/ZipDeploy')]" + + ], + "properties": { + "allow": "false" + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "name": "virtualNetwork", + "type": "config", + "apiVersion": "2018-02-01", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/ZipDeploy')]" + ], + "properties": + { + "subnetResourceId": "[variables('functionAppSubnetId')]", + "swiftSupported": true + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "name": "[concat(variables('MetadataAggregator'), '/web')]", + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('MetadataAggregator'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", + "[concat('Microsoft.Web/sites/', variables('MetadataAggregator'), '/Extensions/ZipDeploy')]" + ], + "properties": + { + "ipSecurityRestrictions": [ + { + "vnetSubnetResourceId": "[variables('functionAppSubnetId')]", + "action": "Allow", + "priority": 100, + "name": "allowtrafficfromfunctionsubnet" + } + ] + } + } + ] + }, + { + "apiVersion": "2018-11-01", + "name": "[variables('WorkItemProcessor')]", + "type": "Microsoft.Web/sites", + "location": "[resourceGroup().location]", + "kind": "functionapp", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": "[if(parameters('IsMultiTenantSetUp'), variables('userAssignedIdentities')['multiTenantSetUp'], variables('userAssignedIdentities')['singleTenantSetUp'])]" + }, + "tags": { + "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", + "displayName": "Website" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('WIfileShare'))]" + ], + "properties": { + "name": "[variables('WorkItemProcessor')]", + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", + "keyVaultReferenceIdentity": "[if(parameters('IsMultiTenantSetUp'), resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), 'SystemAssigned')]", + "siteConfig": { + "remoteDebuggingEnabled": false, + "webSocketsEnabled": false, + "requestTracingEnabled": true, + "reservedInstanceCount": 1, + "httpLoggingEnabled": true, + "detailedErrorLoggingEnabled": true, + "minTlsVersion": 1.2, + "netFrameworkVersion": "v6.0", + "ftpsState": "Disabled", + "appSettings": [ + { + "name": "AzureFunctionsJobHost__functionTimeout", + "value": "[parameters('WorkItemProcessorFunctionTimeout')]" + }, + { + "name": "AzureWebJobsStorage", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value), parameters('CentralStorageAccountConnectionString'))]" + }, + { + "name": "AuthNSettings__InternalIdentityConnectionString", + "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" + }, + { + "name": "AuthNSettings__ScannerIdentityConnectionString", + "value": "[if(parameters('IsMultiTenantSetUp'), json('null'), concat('RunAs=App;AppId=',reference(parameters('MIResourceId'),'2018-11-30').clientId))]" + }, + { + "name": "AuthNSettings__ScannerApplicationPassword", + "value": "[if(parameters('IsMultiTenantSetUp'), concat('@Microsoft.KeyVault(SecretUri=',parameters('ScannerIdentitySecretUri'), ')'), json('null'))]" + }, + { + "name": "AuthNSettings__ScannerApplicationId", + "value": "[if(parameters('IsMultiTenantSetUp'), parameters('ScannerIdentityApplicationId'), json('null'))]" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[toLower(variables('WorkItemProcessor'))]" + }, + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~4" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "~10" + }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + { + "name": "AIConfigurations__ResourceId", + "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + }, + { + "name": "AzureStorageSettings__ResourceId", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), resourceId('Microsoft.Storage/storageAccounts/', variables('storageName')), json('null'))]" + }, + { + "name": "AzureStorageSettings__ConnectionString", + "value": "[if(empty(parameters('CentralStorageAccountConnectionString')), json('null'), parameters('CentralStorageAccountConnectionString'))]" + }, + { + "name": "LAConfigurations__ResourceId", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]" + }, + { + "name": "LAConfigurations__WorkspaceId", + "value": "[reference(resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName')), '2017-03-15-preview').customerId]" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "[variables('functionWorkerRuntime')]" + }, + { + "name": "queueTriggerName", + "value": "[variables('storageQueueName')]" + }, + { + "name": "AppMetadata__AppName", + "value": "[variables('WorkItemProcessor')]" + }, + { + "name": "GraphConfigurations__IsFeatureEnabled", + "value": "[parameters('IsGraphFeatureEnabled')]" + }, + { + "name": "WebJobConfigurations__CloudEnvironmentName", + "value": "[parameters('AzureEnvironmentName')]" + }, + { + "name": "FeatureManagement__OrgPolicy", + "value": "[if(parameters('IsAzTSUIEnabled'), 'true', 'false')]" + }, + { + "name": "WEBSITE_VNET_ROUTE_ALL", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_CONTENTOVERVNET", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_DNS_SERVER", + "value": "168.63.129.16" + }, + { + "name": "MultiTenantConfigurations__IsFeatureEnabled", + "value": "[if(parameters('IsMultiTenantSetUp'), 'true', 'false')]" + }, + { + "name": "MultiTenantConfigurations__HostTenant", + "value": "[if(parameters('IsMultiTenantSetUp'), parameters('TenantId'), json('null'))]" + } + ], + "functionsRuntimeScaleMonitoringEnabled": "[if(parameters('EnableVnetIntegration'), 'true', 'false')]" + } + }, + "resources": [ + { + "apiVersion": "2018-11-01", + "name": "ZipDeploy", + "type": "Extensions", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'))]" + ], + "properties": { + "packageUri": "[parameters('WorkItemProcessorPackageURL')]" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "ftp", + "location": "[resourceGroup().location]", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'))]", + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "location": "[resourceGroup().location]", + "name": "scm", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/',variables('WorkItemProcessor'))]", + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/ZipDeploy')]" + ], + "properties": { + "allow": "false" + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "name": "virtualNetwork", + "type": "config", + "apiVersion": "2018-02-01", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/ZipDeploy')]" + ], + "properties": + { + "subnetResourceId": "[variables('functionAppSubnetId')]", + "swiftSupported": true + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "name": "[concat(variables('WorkItemProcessor'), '/web')]", + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('WorkItemProcessor'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]", + "[concat('Microsoft.Web/sites/', variables('WorkItemProcessor'), '/Extensions/ZipDeploy')]" + ], + "properties": + { + "ipSecurityRestrictions": [ + { + "vnetSubnetResourceId": "[variables('functionAppSubnetId')]", + "action": "Allow", + "priority": 100, + "name": "allowtrafficfromfunctionsubnet" + } + ] + } + } + ] + }, + { + "condition": "[parameters('IsAutoUpdaterEnabled')]", + "apiVersion": "2018-11-01", + "name": "[variables('AutoUpdater')]", + "type": "Microsoft.Web/sites", + "location": "[resourceGroup().location]", + "kind": "functionapp", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": { + } + } + }, + "tags": { + "[concat('hidden-related:', resourceGroup().id, '/providers/Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]": "Resource", + "displayName": "Website" + }, + "dependsOn": [ + "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/fileServices/shares', variables('storageName'), 'default', variables('AUfileShare'))]" + ], + "properties": { + "name": "[variables('AutoUpdater')]", + "httpsOnly": true, + "serverFarmId": "[resourceId('Microsoft.Web/serverfarms', parameters('hostingPlanName'))]", + "siteConfig": { + "remoteDebuggingEnabled": false, + "webSocketsEnabled": false, + "requestTracingEnabled": true, + "reservedInstanceCount": 1, + "httpLoggingEnabled": true, + "detailedErrorLoggingEnabled": true, + "minTlsVersion": 1.2, + "netFrameworkVersion": "v6.0", + "ftpsState": "Disabled", + "appSettings": [ + { + "name": "AIConfigurations__TelemetryIdentifier", + "value": "[parameters('TelemetryIdentifier')]" + }, + { + "name": "Basic_Authentication_Disabled", + "value": "true" + }, + { + "name": "AzureFunctionsJobHost__functionTimeout", + "value": "[parameters('AutoUpdaterFunctionTimeout')]" + }, + { + "name": "AzureWebJobsStorage", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" + }, + { + "name": "AuthNSettings__ScannerIdentityConnectionString", + "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" + }, + { + "name": "AuthNSettings__InternalIdentityConnectionString", + "value": "[concat('RunAs=App;AppId=',reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').clientId)]" + }, + { + "name": "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING", + "value": "[concat('DefaultEndpointsProtocol=https;AccountName=', variables('storageName'), ';EndpointSuffix=', environment().suffixes.storage, ';AccountKey=',listKeys(resourceId('Microsoft.Storage/storageAccounts', variables('storageName')), '2019-06-01').keys[0].value)]" + }, + { + "name": "WEBSITE_CONTENTSHARE", + "value": "[toLower(variables('AutoUpdater'))]" + }, + { + "name": "FUNCTIONS_EXTENSION_VERSION", + "value": "~4" + }, + { + "name": "WEBSITE_NODE_DEFAULT_VERSION", + "value": "~10" + }, + { + "name": "APPINSIGHTS_INSTRUMENTATIONKEY", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + { + "name": "AIConfigurations__ResourceId", + "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + }, + { + "name": "FUNCTIONS_WORKER_RUNTIME", + "value": "[variables('functionWorkerRuntime')]" + }, + { + "name": "HostEnvironmentDetails__HostSubscriptionId", + "value": "[subscription().subscriptionId]" + }, + { + "name": "HostEnvironmentDetails__HostResourceGroupName", + "value": "[resourceGroup().name]" + }, + { + "name": "HostEnvironmentDetails__AppPostfix", + "value": "[parameters('ResourceHash')]" + }, + { + "name": "AIConfigurations__AnonymousUsageTelemetry__LogLevel", + "value": "[parameters('AnonymousUsageTelemetryLogLevel')]" + }, + { + "name": "WEBSITE_RUN_FROM_PACKAGE", + "value": "https://aka.ms/AzTS/V4/AutoUpdater" + }, + { + "name": "WebJobConfigurations__CloudEnvironmentName", + "value": "[parameters('AzureEnvironmentName')]" + }, + { + "name": "WEBSITE_VNET_ROUTE_ALL", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_CONTENTOVERVNET", + "value": "[if(parameters('EnableVnetIntegration'), '1', json('null'))]" + }, + { + "name": "WEBSITE_DNS_SERVER", + "value": "168.63.129.16" + }, + { + "name": "OnboardingDetails__Organization", + "value": "[parameters('OrganizationName')]" + }, + { + "name": "OnboardingDetails__Division", + "value": "[parameters('DivisionName')]" + }, + { + "name": "OnboardingDetails__ContactEmailAddressList", + "value": "[parameters('ContactEmailAddressList')]" + }, + { + "name": "OnboardingDetails__TenantId", + "value": "[parameters('HashedTenantId')]" + } + ], + "functionsRuntimeScaleMonitoringEnabled": "[if(parameters('EnableVnetIntegration'), 'true', 'false')]" + } + }, + "resources": [ + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "name": "ftp", + "location": "[resourceGroup().location]", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]" + ], + "properties": { + "allow": "false" + } + }, + { + "type": "basicPublishingCredentialsPolicies", + "apiVersion": "2022-03-01", + "location": "[resourceGroup().location]", + "name": "scm", + "kind": "basicPublishingCredentialsPolicies", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]" + ], + "properties": { + "allow": "false" + } + }, + { + "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAutoUpdaterEnabled'))]", + "name": "virtualNetwork", + "type": "config", + "apiVersion": "2018-02-01", + "dependsOn": [ + "[concat('Microsoft.Web/sites/', variables('AutoUpdater'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]" + ], + "properties": + { + "subnetResourceId": "[variables('functionAppSubnetId')]", + "swiftSupported": true + } + }, + { + "condition": "[and(parameters('EnableVnetIntegration'), parameters('IsAutoUpdaterEnabled'))]", + "name": "[concat(variables('AutoUpdater'), '/web')]", + "type": "Microsoft.Web/sites/config", + "apiVersion": "2018-11-01", + "dependsOn": [ + "[resourceId('Microsoft.Web/sites', variables('AutoUpdater'))]", + "[concat('Microsoft.Network/virtualNetworks/', variables('vnetName'))]" + ], + "properties": + { + "ipSecurityRestrictions": [ + { + "vnetSubnetResourceId": "[variables('functionAppSubnetId')]", + "action": "Allow", + "priority": 100, + "name": "allowtrafficfromfunctionsubnet" + } + ] + } + } + ] + }, + { + "type": "Microsoft.Authorization/roleAssignments", + "apiVersion": "2017-09-01", + "name": "[variables('rgRoleAssignmentName')]", + "properties": { + "roleDefinitionId": "[variables('contributorRoleId')]", + "principalId": "[reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').principalId]", + "scope": "[resourceGroup().id]" + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]", + "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces/', variables('workspaceName'))]", + "[resourceId('Microsoft.Logic/workflows', variables('LogicAppWorkflowName'))]" + ] + }, + { + "type": "microsoft.insights/components", + "apiVersion": "2020-02-02-preview", + "name": "[variables('applicationInsightsName')]", + "location": "[resourceGroup().location]", + "tags": { + "[concat('hidden-link:', resourceId('Microsoft.Web/sites', variables('applicationInsightsName')))]": "Resource" + }, + "properties": { + "ApplicationId": "[variables('applicationInsightsName')]", + "Request_Source": "WebAppCreate", + "WorkspaceResourceId": "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]", + "IngestionMode": "LogAnalytics", + "publicNetworkAccessForIngestion": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", + "publicNetworkAccessForQuery": "[if(parameters('EnableVnetIntegration'), 'Disabled', 'Enabled')]", + "dependsOn" :[ + "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]" + ] + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "microsoft.insights/privatelinkscopes", + "apiVersion": "2019-10-17-preview", + "name": "[variables('private_link_scope_name')]", + "location": "global", + "properties": {} + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "microsoft.insights/privatelinkscopes/scopedresources", + "apiVersion": "2019-10-17-preview", + "name": "[concat(variables('private_link_scope_name'), '/', concat(variables('workspaceName'), '-connection'))]", + "dependsOn": [ + "[resourceId('microsoft.insights/privatelinkscopes', variables('private_link_scope_name'))]", + "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]" + ], + "properties": { + "linkedResourceId": "[resourceId('microsoft.operationalinsights/workspaces', variables('workspaceName'))]" + } + }, + { + "condition": "[parameters('EnableVnetIntegration')]", + "type": "microsoft.insights/privatelinkscopes/scopedresources", + "apiVersion": "2019-10-17-preview", + "name": "[concat(variables('private_link_scope_name'), '/', concat(variables('applicationInsightsName'), '-connection'))]", + "dependsOn": [ + "[resourceId('microsoft.insights/privatelinkscopes', variables('private_link_scope_name'))]", + "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + ], + "properties": { + "linkedResourceId": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + } + }, + { + "condition": "[parameters('IsAutoUpdaterEnabled')]", + "type": "Microsoft.Web/connections", + "apiVersion": "2016-06-01", + "name": "[variables('AutoUpdaterAPIConnectionName')]", + "location": "[resourceGroup().location]", + "kind": "V1", + "properties": { + "displayName": "[variables('AutoUpdaterAPIConnectionName')]", + "customParameterValues": {}, + "api": { + "id": "[variables('AutoUpdaterConnectionAPI')]" + }, + "parameterValueType": "Alternative" + } + }, + { + "condition": "[parameters('IsAutoUpdaterEnabled')]", + "type": "Microsoft.Logic/workflows", + "apiVersion": "2017-07-01", + "name": "[variables('LogicAppWorkflowName')]", + "location": "[resourceGroup().location]", + "identity": { + "type": "UserAssigned", + "userAssignedIdentities": { + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]": {} + } + }, + "dependsOn": [ + "[resourceId('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName'))]", + "[resourceId('Microsoft.Web/connections', variables('AutoUpdaterAPIConnectionName'))]" + ], + "properties": { + "state": "Enabled", + "definition": { + "$schema": "https://schema.management.azure.com/providers/Microsoft.Logic/schemas/2016-06-01/workflowdefinition.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "$connections": { + "defaultValue": {}, + "type": "Object" + } + }, + "triggers": { + "Recurrence": { + "recurrence": { + "frequency": "Day", + "interval": 1, + "schedule": { + "hours": [ + "23" + ] + } + }, + "type": "Recurrence" + } + }, + "actions": { + "Restart_AutoUpdater": { + "runAfter": {}, + "type": "ApiConnection", + "inputs": { + "host": { + "connection": { + "name": "@parameters('$connections')['azureappservice']['connectionId']" + } + }, + "method": "post", + "path": "[concat('/subscriptions/@{encodeURIComponent(''',subscription().subscriptionId,''')}/resourcegroups/@{encodeURIComponent(''',resourceGroup().name,''')}/providers/Microsoft.Web/sites/@{encodeURIComponent(''',variables('AutoUpdater'),''')}/restart')]", + "queries": { + "api-version": "2019-08-01" + } + } + } + }, + "outputs": {} + }, + "parameters": { + "$connections": { + "value": { + "azureappservice": { + "connectionId": "[resourceId('Microsoft.Web/connections', variables('AutoUpdaterAPIConnectionName'))]", + "connectionProperties": { + "authentication": { + "type": "ManagedServiceIdentity", + "identity" : "[resourceId('Microsoft.managedidentity/userassignedidentities', variables('internalMIName'))]" + } + }, + "id": "[variables('AutoUpdaterConnectionAPI')]" + } + } + } + } + } + }, + { + "condition": "[and(parameters('EnableWAF'), parameters('IsAzTSUIEnabled'))]", + "type": "Microsoft.Network/frontDoors", + "apiVersion": "2020-05-01", + "name": "[variables('APIFrontDoorName')]", + "location": "Global", + "dependsOn": [ + "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" + ], + "properties": { + "routingRules": [ + { + "name": "AzSK-AzTS-API-RoutingRule", + "properties": { + "frontendEndpoints": [ + { + "id": "[resourceId('Microsoft.Network/frontDoors/frontendEndpoints', variables('APIFrontDoorName'), 'AzSK-AzTS-API-FrontendEndpoints')]" + } + ], + "acceptedProtocols": [ + "Https" + ], + "patternsToMatch": [ + "/*" + ], + "routeConfiguration": { + "@odata.type": "#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration", + "forwardingProtocol": "HttpsOnly", + "backendPool": { + "id": "[resourceId('Microsoft.Network/frontDoors/backendPools', variables('APIFrontDoorName'), 'AzSK-AzTS-API-BackendPool')]" + } + }, + "enabledState": "Enabled" + } + } + ], + "healthProbeSettings": [ + { + "name": "APIHealthProbeSettings", + "properties": { + "path": "/", + "protocol": "Https", + "intervalInSeconds": 30, + "enabledState": "Enabled", + "healthProbeMethod": "HEAD" + } + } + ], + "loadBalancingSettings": [ + { + "name": "APIloadBalancingSettings", + "properties": { + "sampleSize": 4, + "successfulSamplesRequired": 2 + } + } + ], + "backendPools": [ + { + "name": "AzSK-AzTS-API-BackendPool", + "properties": { + "backends": [ + { + "address": "[concat(variables('WebApi'), parameters('WebAppEndpointSuffix'))]", + "httpPort": 80, + "httpsPort": 443, + "priority": 1, + "weight": 50, + "backendHostHeader": "[concat(variables('WebApi'), parameters('WebAppEndpointSuffix'))]", + "enabledState": "Enabled" + } + ], + "loadBalancingSettings": { + "id": "[resourceId('Microsoft.Network/frontDoors/loadBalancingSettings', variables('APIFrontDoorName'), 'APIloadBalancingSettings')]" + }, + "healthProbeSettings": { + "id": "[resourceId('Microsoft.Network/frontDoors/healthProbeSettings', variables('APIFrontDoorName'), 'APIHealthProbeSettings')]" + } + } + } + ], + "frontendEndpoints": [ + { + "name": "AzSK-AzTS-API-FrontendEndpoints", + "properties": { + "hostName": "[concat(variables('APIFrontDoorName'), parameters('FrontDoorEndpointSuffix'))]", + "sessionAffinityEnabledState": "Disabled", + "webApplicationFirewallPolicyLink": { + "id": "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" + } + } + } + ], + "enabledState": "Enabled" + } + }, + { + "condition": "[and(parameters('EnableWAF'), parameters('IsAzTSUIEnabled'))]", + "type": "Microsoft.Network/frontDoors", + "apiVersion": "2020-05-01", + "name": "[variables('UIFrontDoorName')]", + "location": "Global", + "dependsOn": [ + "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" + ], + "properties": { + "routingRules": [ + { + "name": "AzSK-AzTS-UI-RoutingRule", + "properties": { + "frontendEndpoints": [ + { + "id": "[resourceId('Microsoft.Network/frontDoors/frontendEndpoints', variables('UIFrontDoorName'), 'AzSK-AzTS-UI-FrontendEndpoints')]" + } + ], + "acceptedProtocols": [ + "Https" + ], + "patternsToMatch": [ + "/*" + ], + "routeConfiguration": { + "@odata.type": "#Microsoft.Azure.FrontDoor.Models.FrontdoorForwardingConfiguration", + "forwardingProtocol": "HttpsOnly", + "backendPool": { + "id": "[resourceId('Microsoft.Network/frontDoors/backendPools', variables('UIFrontDoorName'), 'AzSK-AzTS-UI-BackendPool')]" + } + }, + "enabledState": "Enabled" + } + } + ], + "healthProbeSettings": [ + { + "name": "UIHealthProbeSettings", + "properties": { + "path": "/", + "protocol": "Https", + "intervalInSeconds": 30, + "enabledState": "Enabled", + "healthProbeMethod": "HEAD" + } + } + ], + "loadBalancingSettings": [ + { + "name": "UIloadBalancingSettings", + "properties": { + "sampleSize": 4, + "successfulSamplesRequired": 2 + } + } + ], + "backendPools": [ + { + "name": "AzSK-AzTS-UI-BackendPool", + "properties": { + "backends": [ + { + "address": "[concat(variables('WebUI'), parameters('WebAppEndpointSuffix'))]", + "httpPort": 80, + "httpsPort": 443, + "priority": 1, + "weight": 50, + "backendHostHeader": "[concat(variables('WebUI'), parameters('WebAppEndpointSuffix'))]", + "enabledState": "Enabled" + } + ], + "loadBalancingSettings": { + "id": "[resourceId('Microsoft.Network/frontDoors/loadBalancingSettings', variables('UIFrontDoorName'), 'UIloadBalancingSettings')]" + }, + "healthProbeSettings": { + "id": "[resourceId('Microsoft.Network/frontDoors/healthProbeSettings', variables('UIFrontDoorName'), 'UIHealthProbeSettings')]" + } + } + } + ], + "frontendEndpoints": [ + { + "name": "AzSK-AzTS-UI-FrontendEndpoints", + "properties": { + "hostName": "[concat(variables('UIFrontDoorName'), parameters('FrontDoorEndpointSuffix'))]", + "sessionAffinityEnabledState": "Disabled", + "webApplicationFirewallPolicyLink": { + "id": "[resourceId('Microsoft.Network/FrontDoorWebApplicationFirewallPolicies', variables('wafPolicyName'))]" + } + } + } + ], + "enabledState": "Enabled" + } + }, + { + "condition": "[and(parameters('EnableWAF'), parameters('IsAzTSUIEnabled'))]", + "type": "Microsoft.Network/FrontDoorWebApplicationFirewallPolicies", + "apiVersion": "2020-11-01", + "name": "[variables('wafPolicyName')]", + "location": "global", + "sku": { + "name": "Classic_AzureFrontDoor" + }, + "properties": { + "policySettings": { + "enabledState": "Enabled", + "mode": "Prevention" + }, + "managedRules": { + "managedRuleSets": [ + { + "ruleSetType": "DefaultRuleSet", + "ruleSetVersion": "1.0" + } + ] + } + } + } + ], + "outputs": { + "storageId": { + "type": "string", + "value": "[resourceId('Microsoft.Storage/storageAccounts/', variables('storageName'))]" + }, + "serverfarmId": { + "type": "string", + "value": "[resourceId('Microsoft.Web/serverfarms/', parameters('hostingPlanName'))]" + }, + "storageQueueName": { + "type": "string", + "value": "[variables('storageQueueName')]" + }, + "uiAppName": { + "type": "string", + "value": "[variables('WebUI')]" + }, + "webApiName": { + "type": "string", + "value": "[variables('WebApi')]" + }, + "internalMIObjectId": { + "type": "string", + "value": "[reference(concat('Microsoft.ManagedIdentity/userAssignedIdentities/', variables('internalMIName')), '2018-11-30').principalId]" + }, + "applicationInsightsId": { + "type": "string", + "value": "[resourceId('microsoft.insights/components', variables('applicationInsightsName'))]" + }, + "logAnalyticsResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('workspaceName'))]" + }, + "applicationInsightsIKey": { + "type": "string", + "value": "[reference(resourceId('microsoft.insights/components', variables('applicationInsightsName')), '2020-02-02-preview').InstrumentationKey]" + }, + "vnet" : { + "type": "string", + "value": "[variables('vnetName')]" + }, + "azTSUIFrontDoorUrl": { + "type": "string", + "value": "[concat('https://',variables('UIFrontDoorName'), parameters('FrontDoorEndpointSuffix'))]" + }, + "azTSAPIFrontDoorUrl": { + "type": "string", + "value": "[variables('APIFrontDoorUrl')]" + }, + "uiFrontDoorName": { + "type": "string", + "value": "[variables('UIFrontDoorName')]" + }, + "apiFrontDoorName": { + "type": "string", + "value": "[variables('APIFrontDoorName')]" + } + } } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/AzTSKeyVaultTemplate.json b/TemplateFiles/DeploymentFiles/AzTSKeyVaultTemplate.json index db698dcd..812ba279 100644 --- a/TemplateFiles/DeploymentFiles/AzTSKeyVaultTemplate.json +++ b/TemplateFiles/DeploymentFiles/AzTSKeyVaultTemplate.json @@ -1,167 +1,167 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "resourceHash": { - "type": "string" - }, - "keyVaultName": { - "type": "string", - "metadata": { - "description": "Specifies the name of the key vault." - } - }, - "location": { - "type": "string", - "defaultValue": "[resourceGroup().location]", - "metadata": { - "description": "Specifies the Azure location where the key vault should be created." - } - }, - "tenantId": { - "type": "string", - "defaultValue": "[subscription().tenantId]", - "metadata": { - "description": "Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet." - } - }, - "skuName": { - "type": "string", - "defaultValue": "standard", - "allowedValues": [ - "standard", - "premium" - ], - "metadata": { - "description": "Specifies whether the key vault is a standard vault or a premium vault." - } - }, - "secretName": { - "type": "string", - "defaultValue": "AzTSScannerIdentityConnectionString", - "metadata": { - "description": "Specifies the name of the secret that you want to create." - } - }, - "secretValue": { - "type": "secureString", - "metadata": { - "description": "Specifies the value of the secret that you want to create." - } - }, - "laRetentionInDays": { - "type": "int", - "defaultValue": 365, - "metadata": { - "description": "The workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku." - } - }, - "laDailyQuotaGb": { - "type": "int", - "defaultValue": -1, - "metadata": { - "description": "The workspace daily quota for ingestion. -1 means unlimited." - } - }, - "laSkuName": { - "type": "string", - "defaultValue": "pergb2018", - "allowedValues": [ - "pergb2018", - "Premium", - "Standalone", - "Standard" - ], - "metadata": { - "description": "Specifies whether the key vault is a standard vault or a premium vault." - } - } - }, - "variables": { - "laWorkspaceName": "[concat('AzSK-AzTS-LAWSForAuditing-', parameters('ResourceHash'))]", - "diagnosticSettingsName": "[concat('AzSK-AzTS-AuditSetting-', parameters('ResourceHash'))]" - }, - "functions": [], - "resources": [ - { - "type": "Microsoft.KeyVault/vaults", - "apiVersion": "2021-04-01-preview", - "name": "[parameters('keyVaultName')]", - "location": "[parameters('location')]", - "properties": { - "enabledForDeployment": false, - "enabledForDiskEncryption": false, - "enabledForTemplateDeployment": false, - "tenantId": "[parameters('tenantId')]", - "sku": { - "name": "[parameters('skuName')]", - "family": "A" - }, - "accessPolicies": [] - } - }, - { - "type": "Microsoft.KeyVault/vaults/secrets", - "apiVersion": "2021-04-01-preview", - "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", - "properties": { - "value": "[parameters('secretValue')]" - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" - ] - }, - { - "type": "Microsoft.OperationalInsights/workspaces", - "apiVersion": "2020-03-01-preview", - "name": "[variables('laWorkspaceName')]", - "location": "[parameters('location')]", - "properties": { - "publicNetworkAccessForIngestion": "Enabled", - "publicNetworkAccessForQuery": "Enabled", - "retentionInDays": "[parameters('laRetentionInDays')]", - "sku": { - "name": "[parameters('laSkuName')]" - }, - "workspaceCapping": { - "dailyQuotaGb": "[parameters('laDailyQuotaGb')]" - } - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" - ] - }, - { - "type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings", - "apiVersion": "2017-05-01-preview", - "name": "[concat(parameters('keyVaultName'), '/Microsoft.Insights/', variables('diagnosticSettingsName'))]", - "properties": { - "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('laWorkspaceName'))]", - "logs": [ - { - "category": "AuditEvent", - "enabled": true - } - ] - }, - "dependsOn": [ - "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", - "[resourceId('Microsoft.OperationalInsights/workspaces', variables('laWorkspaceName'))]" - ] - } - ], - "outputs": { - "keyVaultResourceId": { - "type": "string", - "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" - }, - "secretURI": { - "type": "string", - "value": "[reference(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretName'))).secretUri]" - }, - "logAnalyticsResourceId": { - "type": "string", - "value": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('laWorkspaceName'))]" - } - } +{ + "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "resourceHash": { + "type": "string" + }, + "keyVaultName": { + "type": "string", + "metadata": { + "description": "Specifies the name of the key vault." + } + }, + "location": { + "type": "string", + "defaultValue": "[resourceGroup().location]", + "metadata": { + "description": "Specifies the Azure location where the key vault should be created." + } + }, + "tenantId": { + "type": "string", + "defaultValue": "[subscription().tenantId]", + "metadata": { + "description": "Specifies the Azure Active Directory tenant ID that should be used for authenticating requests to the key vault. Get it by using Get-AzSubscription cmdlet." + } + }, + "skuName": { + "type": "string", + "defaultValue": "standard", + "allowedValues": [ + "standard", + "premium" + ], + "metadata": { + "description": "Specifies whether the key vault is a standard vault or a premium vault." + } + }, + "secretName": { + "type": "string", + "defaultValue": "AzTSScannerIdentityConnectionString", + "metadata": { + "description": "Specifies the name of the secret that you want to create." + } + }, + "secretValue": { + "type": "secureString", + "metadata": { + "description": "Specifies the value of the secret that you want to create." + } + }, + "laRetentionInDays": { + "type": "int", + "defaultValue": 365, + "metadata": { + "description": "The workspace data retention in days. -1 means Unlimited retention for the Unlimited Sku." + } + }, + "laDailyQuotaGb": { + "type": "int", + "defaultValue": -1, + "metadata": { + "description": "The workspace daily quota for ingestion. -1 means unlimited." + } + }, + "laSkuName": { + "type": "string", + "defaultValue": "pergb2018", + "allowedValues": [ + "pergb2018", + "Premium", + "Standalone", + "Standard" + ], + "metadata": { + "description": "Specifies whether the key vault is a standard vault or a premium vault." + } + } + }, + "variables": { + "laWorkspaceName": "[concat('AzSK-AzTS-LAWSForAuditing-', parameters('ResourceHash'))]", + "diagnosticSettingsName": "[concat('AzSK-AzTS-AuditSetting-', parameters('ResourceHash'))]" + }, + "functions": [], + "resources": [ + { + "type": "Microsoft.KeyVault/vaults", + "apiVersion": "2021-04-01-preview", + "name": "[parameters('keyVaultName')]", + "location": "[parameters('location')]", + "properties": { + "enabledForDeployment": false, + "enabledForDiskEncryption": false, + "enabledForTemplateDeployment": false, + "tenantId": "[parameters('tenantId')]", + "sku": { + "name": "[parameters('skuName')]", + "family": "A" + }, + "accessPolicies": [] + } + }, + { + "type": "Microsoft.KeyVault/vaults/secrets", + "apiVersion": "2021-04-01-preview", + "name": "[format('{0}/{1}', parameters('keyVaultName'), parameters('secretName'))]", + "properties": { + "value": "[parameters('secretValue')]" + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ] + }, + { + "type": "Microsoft.OperationalInsights/workspaces", + "apiVersion": "2020-03-01-preview", + "name": "[variables('laWorkspaceName')]", + "location": "[parameters('location')]", + "properties": { + "publicNetworkAccessForIngestion": "Enabled", + "publicNetworkAccessForQuery": "Enabled", + "retentionInDays": "[parameters('laRetentionInDays')]", + "sku": { + "name": "[parameters('laSkuName')]" + }, + "workspaceCapping": { + "dailyQuotaGb": "[parameters('laDailyQuotaGb')]" + } + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + ] + }, + { + "type": "Microsoft.KeyVault/vaults/providers/diagnosticSettings", + "apiVersion": "2017-05-01-preview", + "name": "[concat(parameters('keyVaultName'), '/Microsoft.Insights/', variables('diagnosticSettingsName'))]", + "properties": { + "workspaceId": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('laWorkspaceName'))]", + "logs": [ + { + "category": "AuditEvent", + "enabled": true + } + ] + }, + "dependsOn": [ + "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]", + "[resourceId('Microsoft.OperationalInsights/workspaces', variables('laWorkspaceName'))]" + ] + } + ], + "outputs": { + "keyVaultResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.KeyVault/vaults', parameters('keyVaultName'))]" + }, + "secretURI": { + "type": "string", + "value": "[reference(resourceId('Microsoft.KeyVault/vaults/secrets', parameters('keyVaultName'), parameters('secretName'))).secretUri]" + }, + "logAnalyticsResourceId": { + "type": "string", + "value": "[resourceId('Microsoft.OperationalInsights/workspaces', variables('laWorkspaceName'))]" + } + } } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/AzTSSetup.ps1 b/TemplateFiles/DeploymentFiles/AzTSSetup.ps1 index b88921ea..3d835954 100644 --- a/TemplateFiles/DeploymentFiles/AzTSSetup.ps1 +++ b/TemplateFiles/DeploymentFiles/AzTSSetup.ps1 @@ -1,2936 +1,2946 @@ -# Load all other scripts that are required by this script. -. "$PSScriptRoot\OnDemandScan.ps1" - -# Standard configuration -$AzureEnvironmentAppServiceURI = @{ - "AzureCloud" = "https://{0}.azurewebsites.net"; - "AzureGovernmentCloud" = "https://{0}.azurewebsites.us"; - "AzureChinaCloud" = "https://{0}.chinacloudsites.cn"; -} - -$AzureEnvironmentToADAuthUrlMap = @{ - "AzureCloud" = "https://login.microsoftonline.com"; - "AzureGovernmentCloud" = "https://login.microsoftonline.us"; - "AzureChinaCloud" = "https://login.microsoftonline.cn"; -} - -$AzureEnvironmentPortalURI = @{ - "AzureCloud" = "https://portal.azure.com/"; - "AzureGovernmentCloud" = "https://portal.azure.us/"; - "AzureChinaCloud" = "https://portal.azure.cn/"; -} - -function Install-AzSKTenantSecuritySolution -{ - <# - .SYNOPSIS - This command would help in installing Azure Tenant Security Solution in your subscription. - .DESCRIPTION - This command will install an Azure Tenant Security Solution which runs security scan on subscription in a Tenant. - Security scan results will be populated in Log Analytics workspace and Azure Storage account which is configured during installation. - - .PARAMETER SubscriptionId - Subscription id in which Azure Tenant Security Solution needs to be installed. - .PARAMETER ScanHostRGName - Name of ResourceGroup where setup resources will be created. - .PARAMETER Location - Location where all resources will get created. Default location is EastUS2. - .PARAMETER ScanIdentityId - Resource id of user managed identity used to scan subscriptions. - .PARAMETER TemplateFilePath - Azure ARM template path used to deploy Azure Tenant Security Solution. - .PARAMETER TemplateParameters - Azure ARM template parameters used to deploy Azure Tenant Security Solution. - .PARAMETER SendUsageTelemetry - Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features. - .PARAMETER CentralStorageAccountConnectionString - Connection string of the storage account to be used to store the scan logs centrally. - .NOTES - - - .LINK - https://aka.ms/azts-docs - - #> - Param( - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - $ScanHostRGName = "AzSK-AzTS-RG", - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] - [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] - $Location, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] - $ScanIdentityId, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Application Id of central scanning identity.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Application Id of central scanning identity.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Application Id of central scanning identity.")] - [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Application Id of central scanning identity.")] - $ScanIdentityApplicationId, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] - [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] - $ScanIdentitySecretUri, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup")] - $TemplateFilePath = ".\AzTSDeploymentTemplate.json", - - [Hashtable] - [Parameter(Mandatory = $false, ParameterSetName = "Default")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup")] - $TemplateParameters = @{}, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] - $SendUsageTelemetry = $false, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] - $ScanIdentityHasGraphPermission = $false, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] - $WebAPIAzureADAppId, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] - $UIAzureADAppId, - - [string[]] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Email ids to which alert notification should be sent.")] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Email ids to which alert notification should be sent.")] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Email ids to which alert notification should be sent.")] - [Alias("SREEmailIds")] - [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Email ids to which alert notification should be sent.")] - $SendAlertNotificationToEmailIds = @(), - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] - $AzureEnvironmentName = "AzureCloud", - - [switch] - [Parameter(Mandatory = $false, HelpMessage="Switch to enable vnet integration. Resources required for vnet setup will be deployed only if this switch is ON.")] - $EnableVnetIntegration = $false, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - [Alias("EnableAutoUpdates")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] - $EnableAutoUpdater, - - [switch] - [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] - [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] - $EnableAzTSUI, - - [switch] - [Parameter(Mandatory = $false, HelpMessage="Switch to enable WAF. Resources required for implementing WAF will be deployed only if this switch is ON.")] - $EnableWAF = $false, - - [switch] - [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Switch to enable multi-tenant scanning. Configurations required for multi tenant scanning will be deployed.")] - $EnableMultiTenantScan, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Connection string of the storage account to be used to store the scan logs centrally.")] - [Alias("StorageAccountConnectionString")] - $CentralStorageAccountConnectionString, - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] - [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] - [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] - $ConsolidatedSetup = $false - ) - Begin - { - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($SubscriptionId) - if(-not $currentContext) - { - return; - } - } - - Process - { - $deploymentResult = $null; - $UIUrl = [string]::Empty; - $FunctionApps = $null; - $AppServiceSlots = @(); - - # flag to decide whether to prompt user for telemetry acceptance - [bool] $PromptUserAcceptance = $true - # Check if Auto Updater is already present. - try - { - [string] $OnboardingTenant = [String]::Empty; - [string] $OnboardingOrg = [String]::Empty; - [string] $OnboardingDiv = [String]::Empty; - [string] $OnboardingContactEmail = [String]::Empty; - [string] $AnonymousUsageTelemetryLogLevel = [String]::Empty; - - $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() - $AutoUpdaterName = "AzSK-AzTS-AutoUpdater-" + $ResourceHash - $au = Get-AzWebApp -Name $AutoUpdaterName -ResourceGroupName $ScanHostRGName -ErrorAction SilentlyContinue - #If Auto updater is not present then user will be prompted for telemetry acceptance. - if($au) - { - - $au.SiteConfig.AppSettings | foreach { - if($_.Name -eq "AIConfigurations__AnonymousUsageTelemetry__LogLevel") - { - $AnonymousUsageTelemetryLogLevel = $_.Value; - } - if($_.Name -eq "OnboardingDetails__Organization") - { - $OnboardingOrg = $_.Value; - } - if($_.Name -eq "OnboardingDetails__Division") - { - $OnboardingDiv = $_.Value; - } - if($_.Name -eq "OnboardingDetails__ContactEmailAddressList") - { - $OnboardingContactEmail = $_.Value; - } - if($_.Name -eq "OnboardingDetails__TenantId") - { - $OnboardingTenant = $_.Value; - } - } - - if([String]::IsNullOrWhiteSpace($AnonymousUsageTelemetryLogLevel)` - -or [String]::IsNullOrWhiteSpace($OnboardingOrg)` - -or [String]::IsNullOrWhiteSpace($OnboardingDiv)` - -or [String]::IsNullOrWhiteSpace($OnboardingContactEmail)` - -or [String]::IsNullOrWhiteSpace($OnboardingTenant)) - { - $PromptUserAcceptance = $true - } - else - { - $PromptUserAcceptance = $false - $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", $AnonymousUsageTelemetryLogLevel) - $TemplateParameters.Add("OrganizationName", $OnboardingOrg) - $TemplateParameters.Add("DivisionName", $OnboardingDiv) - $TemplateParameters.Add("ContactEmailAddressList", $OnboardingContactEmail) - $TemplateParameters.Add("HashedTenantId", $OnboardingTenant) - } - - } - } - catch - { - $PromptUserAcceptance = $true - } - - - try - { - if($PromptUserAcceptance) - { - # Take acceptance from the user for the telemetry to be collected - [string] $TelemetryAcceptanceMsg = "For the purpose of improving quality of AzTS features and better customer service, the AzTS solution needs to collect the below mentioned data :`r`n`n" + - " [1] Anonymized AzTS usage data -> this helps us improve product quality`r`n" + - " [2] Organization/team contact details -> these help us provide your team with:`r`n" + - " [a] Updates about AzTS feature change`r`n" + - " [b] Support channel options (e.g., office hours)`r`n" + - " [c] Occasional requests for feedback on specific features`r`n" + - "You may choose to opt in or opt out of either or both of these by choosing Y/N at the prompts coming up. (Note that you can change your choice later too.)`r`n" - - Write-Host $TelemetryAcceptanceMsg -ForegroundColor $([Constants]::MessageType.warning) - - - $AnonymousUsageCaptureFlag = Read-Host -Prompt "`n`rAllow collection of anonymized usage data (Y/N)" - $ContactDataCaptureFlag = Read-Host -Prompt "`n`Provide org/team contact info (Y/N)" - if($ContactDataCaptureFlag -eq 'Y') - { - # Capturing Onboarding details is enabled - if($AnonymousUsageCaptureFlag -eq 'Y') - { - # Capturing anonymous usage data is enabled - $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "All") - } - else - { - # Capturing anonymous usage data is disabled - $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "Onboarding") - } - - Write-Host "`n`rPlease provide details about your org, divison and team." -ForegroundColor $([Constants]::MessageType.warning) - $OrganizationName = Read-Host -Prompt "Organization Name " - $TemplateParameters.Add("OrganizationName", $OrganizationName) - $DivisionName = Read-Host -Prompt "Division Name within your Organization " - $TemplateParameters.Add("DivisionName", $DivisionName) - $ContactEmailAddressList = Read-Host -Prompt "Contact DL to use for our communication " - $TemplateParameters.Add("ContactEmailAddressList", $ContactEmailAddressList) - - } - else - { - # Capturing Onboarding details is disabled - if($AnonymousUsageCaptureFlag -eq 'Y') - { - # Capturing anonymous usage data is enabled - $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "Anonymous") - } - else - { - # Capturing anonymous usage data is disabled - $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "None") - } - } - - Write-Host "`n`rThank you for your choices. To make changes to these preferences refer the FAQs by visiting https://aka.ms/AzTS-docs/UpdateTelemetryPreference." -ForegroundColor $([Constants]::MessageType.Update) - } - - } - catch - { - #silently continue with installation. - } - - if ($ConsolidatedSetup -ne $true) - { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "Running Azure Tenant Security Solution setup...`n" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::InstallSolutionInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - Write-Host "`r`nStarted setting up Azure Tenant Security Solution. This may take 5 mins..." -ForegroundColor $([Constants]::MessageType.Info) - } - - - - - # Create resource group if not exist - try - { - Write-Verbose "$(Get-TimeStamp)Checking resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) - $rg = Get-AzResourceGroup -Name $ScanHostRGName -ErrorAction SilentlyContinue - if(-not $rg) - { - Write-Verbose "$(Get-TimeStamp)Creating resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) - $rg = New-AzResourceGroup -Name $ScanHostRGName -Location $Location -ErrorAction Stop - } - else{ - Write-Verbose "$(Get-TimeStamp)Resource group already exists." #-ForegroundColor $([Constants]::MessageType.Info) - } - - } - catch - { - Write-Host "`n`rFailed to create resource group for deployment." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # start arm template deployment - try - { - - # Set EnableVnetIntegration value based on switch selected by user - if($EnableVnetIntegration) - { - $TemplateParameters.Add("EnableVnetIntegration", $true) - if (-not $TemplateParameters.ContainsKey("MetadataAggregatorFunctionTimeout")) - { - $TemplateParameters.Add("MetadataAggregatorFunctionTimeout", "00:45:00") - } - } - else - { - $TemplateParameters.Add("EnableVnetIntegration", $false) - } - - # Set EnableWAF value based on switch selected by user - if($EnableWAF) - { - $TemplateParameters.Add("EnableWAF", $true) - } - else - { - $TemplateParameters.Add("EnableWAF", $false) - } - - # set frontdoor and web app endpoint suffixf - if($AzureEnvironmentName -eq 'AzureGovernmentCloud') - { - $TemplateParameters.Add("FrontDoorEndpointSuffix", ".azurefd.us") - $TemplateParameters.Add("WebAppEndpointSuffix", ".azurewebsites.us") - } - elseif($AzureEnvironmentName -eq 'AzureChinaCloud') - { - $TemplateParameters.Add("FrontDoorEndpointSuffix", ".azurefd.cn") - $TemplateParameters.Add("WebAppEndpointSuffix", ".chinacloudsites.cn") - } - else - { - $TemplateParameters.Add("FrontDoorEndpointSuffix", ".azurefd.net") - $TemplateParameters.Add("WebAppEndpointSuffix", ".azurewebsites.net") - } - - # Select rule based on graph permission. - if($ScanIdentityHasGraphPermission) - { - $TemplateParameters.Add("RuleEngineWorkflowName", "FullTenantScan") - $TemplateParameters.Add("IsGraphFeatureEnabled", "true") - } - else - { - $TemplateParameters.Add("RuleEngineWorkflowName", "FullTenantScanExcludeGraph") - $TemplateParameters.Add("IsGraphFeatureEnabled", "false") - } - - # Set up multi-tenant scan config (if enabled) - $TemplateParameters.Add("ScannerIdentitySecretUri", $ScanIdentitySecretUri) - $TemplateParameters.Add("ScannerIdentityApplicationId", $ScanIdentityApplicationId) - if($EnableMultiTenantScan) - { - $TemplateParameters.Add("IsMultiTenantSetUp", $true) - # If multi-tenat mode is enabled then Scanner Connection Secret URI must not be null - if([string]::IsNullOrWhiteSpace($ScanIdentitySecretUri)) - { - Write-Host "`n`rValue for parameter '-ScanIdentitySecretUri' can not be null in multi tenant scan setup." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # If multi-tenat mode is enabled then ApplicationId of scanner identity must not be null - if([string]::IsNullOrWhiteSpace($ScanIdentityApplicationId)) - { - Write-Host "`n`rValue for parameter '-ScanIdentityApplicationId' can not be null in multi tenant scan setup." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - } - else - { - $TemplateParameters.Add("IsMultiTenantSetUp", $false) - } - - $TemplateParameters.Add("AzureEnvironmentName", $AzureEnvironmentName) - $TemplateParameters.Add("CentralStorageAccountConnectionString", $CentralStorageAccountConnectionString) - - # Get package version - - - $CentralPackageInfo = [CentralPackageInfo]::new() - - $TemplateParameters.Add("MetadataAggregatorPackageURL", $CentralPackageInfo.MetadataAggregatorPackageURL) - $TemplateParameters.Add("WorkItemProcessorPackageURL", $CentralPackageInfo.WorkItemProcessorPackageURL) - $TemplateParameters.Add("WebApiPackageURL", $CentralPackageInfo.WebApiPackageURL) - $TemplateParameters.Add("UIPackageURL", $CentralPackageInfo.UIPackageURL) - - # if($TemplateParameters.Count -eq 0) - # { - # Write-Host "`n`rPlease enter the parameter required for template deployment:" -ForegroundColor $([Constants]::MessageType.Info) - # Write-Host "Note: Alternatively you can use '-TemplateParameters' to pass these parameters.`n`r" -ForegroundColor $([Constants]::MessageType.Warning) - # } - $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() #considering only first 5 characters - $TelemetryIdentifier = $ResourceIdHash.Substring(0,16).ToString().ToLower() - - #adding ResourceHash to TemplateParameters - $TemplateParameters.Add("TelemetryIdentifier", $TelemetryIdentifier) - $TemplateParameters.Add("ResourceHash", $ResourceHash) - $TemplateParameters.Add("MIResourceId", $ScanIdentityId) - - #Enable autoupdater template parameter - if($EnableAutoUpdater) - { - $TemplateParameters.Add("IsAutoUpdaterEnabled", $true) - } - else - { - $TemplateParameters.Add("IsAutoUpdaterEnabled", $false) - } - - #Enable AzTSUI template parameter - if($EnableAzTSUI) - { - $TemplateParameters.Add("IsAzTSUIEnabled", $true) - } - else - { - $TemplateParameters.Add("IsAzTSUIEnabled", $false) - } - - #Get the tenant Id from the current subscription contex - $context=Get-AzContext - $TemplateParameters.Add("TenantId", $context.Tenant.Id) - # We also collect hashed TenantId as part of on boarding details - if($PromptUserAcceptance) - { - $HashedTenantId = get-hash($context.Tenant.Id) - $TemplateParameters.Add("HashedTenantId", $HashedTenantId) - } - - - # Creating Azure AD application: Web API - $TemplateParameters.Add("WebApiClientId", $WebAPIAzureADAppId) - $TemplateParameters.Add("UIClientId", $UIAzureADAppId) - $TemplateParameters.Add("AADClientAppDetailsInstance", $AzureEnvironmentToADAuthUrlMap.$AzureEnvironmentName) - $TemplateParameters.Add("AzureEnvironmentPortalURI", $AzureEnvironmentPortalURI.$AzureEnvironmentName) - - - #updating UI app settings for already existing UI, this will require deletion of pre-existing UI and re-deploying it with updated settings. - $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() - $UIName = "AzSK-AzTS-UI-" + $ResourceHash - #getting app setting details of UI if UI exists - $ui = Get-AzResource -Name $UIName -ResourceType "Microsoft.Web/sites" -ResourceGroupName $ScanHostRGName -ErrorAction SilentlyContinue - if($ui) - { - $webapp = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ui.Name -ErrorAction SilentlyContinue - if(($null -ne $webapp) -and (($webapp.SiteConfig.AppSettings.Name -contains "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING") -or ($webapp.SiteConfig.AppSettings.Name -contains "WEBSITE_CONTENTSHARE")) ) - { - $UiDeletionwarningMsg = "[Warning]: Running installation command on an existing AzTS setup requires removing the current AzTS UI [$($UIName)] & redeploying it with updated the app settings." - - Write-Host $UiDeletionwarningMsg -ForegroundColor $([Constants]::MessageType.warning) - $UiDeletionFlag = Read-Host -Prompt "`n`rAllow removal of current AzTS UI Y/N" - if($UiDeletionFlag -eq 'Y') - { - Write-Host "Removing current AzTS UI [$($UIName)]. This will take 1-2 min.." -ForegroundColor Yellow - # delete UI slot - $UiSlotName = (Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $ui.Name).Name - $UiSlotName = $UiSlotName.split('/')[1] - $DeletedUIslot = Remove-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $ui.Name -Slot $UiSlotName -Force - #delete UI - $DeletedUi = Remove-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ui.Name -Force - Write-Host "Removed existing AzTS UI [$($ui.Name)].`nContinuing with re-deployment of AzTS UI with updated app settings..." -ForegroundColor Yellow - } - else - { - Write-Host "Terminating Azure Tenant Security Solution setup..." -ForegroundColor Cyan - Write-Host $([ScannerConstants]::DoubleDashLine) - break; - } - } - } - - # Stop existing app services to unlock files; If any file is locked, deployment will fail - $AppServices = Get-AzWebApp -ResourceGroupName $ScanHostRGName - - if($AppServices -ne $null -and $AppServices.Count-gt 0) - { - $FunctionApps = $AppServices | Where-Object { $_.Kind -eq 'functionapp'} - $AppServices | ForEach-Object { $AppServiceSlots += Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $_.Name } - } - - # Stop function apps - if($FunctionApps -ne $null -and $FunctionApps.Count-gt 0) - { - Write-Verbose "$(Get-TimeStamp)Stopping function app(s) for deployment. This is required to unblock any file in use..." - $FunctionApps | Stop-AzWebApp - Write-Verbose "$(Get-TimeStamp)Stopped function app(s): $([string]::Join(", ", ($FunctionApps | Select Name -Unique).Name))" - - } - - # Start deployment slot - if($AppServiceSlots -ne $null -and $AppServiceSlots.Count-gt 0) - { - Write-Verbose "$(Get-TimeStamp)Starting app service slot for deployment. This is required as an inactive slot cannot be updated." - $AppServiceSlots | Start-AzWebAppSlot - Write-Verbose "$(Get-TimeStamp)Started app service slot(s): $([string]::Join(", ", ($AppServiceSlots | Select Name -Unique).Name))" - - } - - Write-Verbose "$(Get-TimeStamp)Checking resource deployment template..." #-ForegroundColor $([Constants]::MessageType.Info) - - $validationResult = Test-AzResourceGroupDeployment -Mode Incremental -ResourceGroupName $ScanHostRGName -TemplateFile $TemplateFilePath -TemplateParameterObject $TemplateParameters - if($validationResult) - { - Write-Host "`n`rTemplate deployment validation returned following errors:" -ForegroundColor $([Constants]::MessageType.Error) - $validationResult | FL Code, Message | Out-String | Out-Host; - return; - } - else - { - # Deploy template - $deploymentName = "AzTSenvironmentsetup-$([datetime]::Now.ToString("yyyymmddThhmmss"))" - $deploymentResult = New-AzResourceGroupDeployment -Name $deploymentName -Mode Incremental -ResourceGroupName $ScanHostRGName -TemplateFile $TemplateFilePath -TemplateParameterObject $TemplateParameters -ErrorAction Stop -verbose - Write-Verbose "$(Get-TimeStamp)Completed resources deployment for azure tenant security solution." - - #Update App registered in AAD - #Web App - - # update the re-direct uri of azts ui app if WAF is enabled - if($EnableWAF -and $deploymentResult.Outputs.ContainsKey('azTSUIFrontDoorUrl') -and $deploymentResult.Outputs.ContainsKey('uiAppName') ) - { - $AzTSUIFrontDoorUrl = $deploymentResult.Outputs.azTSUIFrontDoorUrl.Value - $UIAzureADAppName = $deploymentResult.Outputs.uiAppName.Value - $replyUris = New-Object Collections.Generic.List[string] - $replyUris.Add(($AzTSUIFrontDoorUrl)); - $replyUris.Add($([string]::Join("/", $([string]::Format($AzTSUIFrontDoorUrl)), "auth.html"))); - $replyUris.Add(($AzureEnvironmentAppServiceURI.$AzureEnvironmentName -f $UIAzureADAppName)); - $replyUris.Add($([string]::Join("/", $([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $UIAzureADAppName)), "auth.html"))); - - $webUIApp = Get-AzureADApplication -Filter "AppId eq '$UIAzureADAppId'" - - Set-AzureADApplication -ObjectId $webUIApp.ObjectId -ReplyUrls $replyUris - } - - if($EnableAzTSUI -and $deploymentResult.Outputs.ContainsKey('uiAppName') -and $deploymentResult.Outputs.ContainsKey('webApiName')) - { - $azureUIAppName= $DeploymentResult.Outputs.uiAppName.Value - $azureWebApiName= $DeploymentResult.Outputs.webApiName.Value - $UIUrl = $([string]::Concat($([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $azureUIAppName)), "/")) - - # assigning value of azts api uri based on whether WAF is anabled or not. - if($EnableWAF -and $deploymentResult.Outputs.ContainsKey('azTSAPIFrontDoorUrl')) - { - $AzTSAPIFrontDoorUrl = $deploymentResult.Outputs.azTSAPIFrontDoorUrl.Value - $apiUri = $AzTSAPIFrontDoorUrl - } - else - { - $apiUri = $([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $azureWebApiName)) - } - - # Load all other scripts that are required by this script. - . "$PSScriptRoot\ConfigureWebUI.ps1" - - $InstrumentationKey = $DeploymentResult.Outputs.applicationInsightsIKey.Value - - Configure-WebUI -TenantId $context.Tenant.Id -ScanHostRGName $ScanHostRGName -UIAppName $azureUIAppName -ApiUrl $apiUri -UIClientId $UIAzureADAppId -WebApiClientId $WebAPIAzureADAppId -AzureEnvironmentName $AzureEnvironmentName -InstrumentationKey $InstrumentationKey - } - - # Custom event is required to Autoupdater setup event to application insight - if($EnableAutoUpdater) - { - SendCustomAIEvent -DeploymentResult $deploymentResult -TelemetryIdentifier $TelemetryIdentifier - } - # Applying access restriction to UI and API, if Enable WAF is turned 'ON'. We are applying access restriction from script because front door id is required here which cannot be determined at the time of template deployment. - if($EnableWAF -and $deploymentResult.Outputs.ContainsKey('apiFrontDoorName') -and $deploymentResult.Outputs.ContainsKey('uiFrontDoorName')) - { - $UIFrontDoorName = $deploymentResult.Outputs.uiFrontDoorName.Value - $APIFrntDoorName = $deploymentResult.Outputs.apiFrontDoorName.Value - $UIFrontDoor = Get-AzFrontDoor -ResourceGroupName $ScanHostRGName -Name $UIFrontDoorName - $APIFrontDoor = Get-AzFrontDoor -ResourceGroupName $ScanHostRGName -Name $APIFrntDoorName - - $UiAccessRestriction = Get-AzWebAppAccessRestrictionConfig -ResourceGroupName $ScanHostRGName -Name $azureUIAppName - $ApiAccessRestriction = Get-AzWebAppAccessRestrictionConfig -ResourceGroupName $ScanHostRGName -Name $azureWebApiName - - # applying restriction on UI - if($UiAccessRestriction.MainSiteAccessRestrictions.RuleName -notcontains 'AllowAccessFromFrontDoor') - { - Add-AzWebAppAccessRestrictionRule -ResourceGroupName $ScanHostRGName -WebAppName $azureUIAppName -Name "AllowAccessFromFrontDoor" -Priority 100 -Action Allow -ServiceTag AzureFrontDoor.Backend -HttpHeader @{'x-azure-fdid' = $UIFrontDoor.FrontDoorId} - } - # applying restriction on API - if($ApiAccessRestriction.MainSiteAccessRestrictions.RuleName -notcontains 'AllowAccessFromFrontDoor') - { - Add-AzWebAppAccessRestrictionRule -ResourceGroupName $ScanHostRGName -WebAppName $azureWebApiName -Name "AllowAccessFromFrontDoor" -Priority 100 -Action Allow -ServiceTag AzureFrontDoor.Backend -HttpHeader @{'x-azure-fdid' = $APIFrontDoor.FrontDoorId} - } - } - - } - } - catch - { - Write-Host "`rTemplate deployment returned following errors: [$($_)]." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - finally - { - # Start app services if it was stopped before deployment - if($FunctionApps -ne $null -and $FunctionApps.Count -gt 0) - { - Write-Verbose "$(Get-TimeStamp)Starting function app(s)..." - $FunctionApps | Start-AzWebApp - Write-Verbose "$(Get-TimeStamp)Started function app(s): $([string]::Join(", ", ($FunctionApps | Select Name -Unique).Name))" - } - - # Stop deployment slot - if($EnableAzTSUI -and $AppServiceSlots -ne $null -and $AppServiceSlots.Count-gt 0) - { - Write-Verbose "$(Get-TimeStamp)Stopping app service slot after updating the slot. This is required as an inactive slot cannot be updated." - $AppServiceSlots | Stop-AzWebAppSlot - Write-Verbose "$(Get-TimeStamp)Stopped app service slot(s): $([string]::Join(", ", ($AppServiceSlots | Select Name -Unique).Name))" - - } - } - # Post deployment steps - Write-Verbose "$(Get-TimeStamp)Starting post deployment environment steps.." - try - { - # Check if queue exist; else create new queue - $storageAccountName = [string]::Empty; - $storageQueueName = [string]::Empty; - Write-Verbose "$(Get-TimeStamp)Creating Storage queue to queue the subscriptions for scan.." #-ForegroundColor $([Constants]::MessageType.Info) - if( $deploymentResult.Outputs.ContainsKey('storageId') -and $deploymentResult.Outputs.ContainsKey('storageQueueName')) - { - $storageAccountName = $deploymentResult.Outputs.storageId.Value.Split("/")[-1] - $storageQueueName = $deploymentResult.Outputs.storageQueueName.Value - - # Create a queue in central storage account - try - { - Write-Verbose "$(Get-TimeStamp)Creating Storage queue in central storage account. This queue will be used to request subscription scan." - if(![string]::IsNullOrWhiteSpace($CentralStorageAccountConnectionString)) - { - $storageContext = New-AzStorageContext -ConnectionString $CentralStorageAccountConnectionString -ErrorAction Stop - $storageQueue = Get-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction SilentlyContinue - if(-not $storageQueue) - { - $storageQueue = New-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction Stop - } - } - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - Write-Host "Failed to create storage queue [$($storageQueueName)] in central storage account. You can create this queue directly from portal with the name [$($storageQueueName)]. For steps to create a queue, please refer https://docs.microsoft.com/en-us/azure/storage/queues/storage-quickstart-queues-portal#create-a-queue.`n`nPlease note that central storage repository feature is currently not supported if your central storage account has network restrictions. In this case, you will have to switch to the standalone mode by running this installation command again without '-CentralStorageAccountConnectionString' parameter." -ForegroundColor $([Constants]::MessageType.Error) - } - - - #fetching storage network settings - if ($EnableVnetIntegration) - { - $StorageNetworkSetting = (Get-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -AccountName $storageAccountName).DefaultAction - - if ($StorageNetworkSetting -eq 'Deny') - { - # Changing storage network setting to allow "All networks", in order to fetch queue details - # This is required as storage with network restriction will not be accessible from user's machine and in such case fetching queue details will result into error - Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -Name $storageAccountName -Bypass AzureServices -DefaultAction Allow - # wait for 30 sec for the settings to get updated on storage account - Start-Sleep -Seconds 30 - } - } - - $storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -UseConnectedAccount -ErrorAction Stop - $storageQueue = Get-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction SilentlyContinue - if(-not $storageQueue) - { - $storageQueue = New-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction Stop - } - - if($EnableVnetIntegration) - { - # Setting storage network settings back to "restricted networks" - Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -Name $storageAccountName -Bypass AzureServices -DefaultAction Deny - } - } - else - { - Write-Host "Failed to create Storage queue." -ForegroundColor $([Constants]::MessageType.Error) - return - } - - # Adding virtual network rules to storage, to configure service endpoints - # Virtual network rules are not added from template, as all function apps have dependency on storage, so storage gets created first and network rules are applied to it. But once network is restricted, function app cannot communicate with storage to get values like - connection string as they are still not a part of vnet and hence the deployment fails. - if($EnableVnetIntegration) - { - $subnetId = @(); - $subnetTobeAdded = @(); - - if($deploymentResult.Outputs.ContainsKey('vnet')) - { - $vnetName = $deploymentResult.Outputs.vnet.Value - - #configuring virtual ntwork rules - $subnetId += Get-AzVirtualNetwork -ResourceGroupName $ScanHostRGName -Name $vnetName | Get-AzVirtualNetworkSubnetConfig - $subnetTobeAdded += $subnetId | Where-Object { $_.Name -notmatch "PrivateEndpointSubnet"} - $subnetTobeAdded | ForEach-Object { - Add-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -Name $storageAccountName -VirtualNetworkResourceId $_.Id - } - - } - } - - - # Set monitoring alert - Set-AzTSMonitoringAlert -DeploymentResult $deploymentResult ` - -SendAlertNotificationToEmailIds $SendAlertNotificationToEmailIds ` - -Location $Location ` - -ScanHostRGName $ScanHostRGName ` - -TelemetryIdentifier $TelemetryIdentifier ` - -IsAutoUpdaterEnabled $EnableAutoUpdater - - if ($ConsolidatedSetup -ne $true) - { - Write-Host "`rCompleted installation for Azure Tenant Security Solution." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host "$([Constants]::DoubleDashLine)" #-ForegroundColor $([Constants]::MessageType.Info) - if($EnableAzTSUI -and $EnableWAF) - { - Write-Host "$([Constants]::NextSteps -f $AzTSUIFrontDoorUrl)" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($AzTSUIFrontDoorUrl)" -ForegroundColor $([Constants]::MessageType.Warning) - } - elseif($EnableAzTSUI) - { - Write-Host "$([Constants]::NextSteps -f $UIUrl)" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host "IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($UIUrl)" -ForegroundColor $([Constants]::MessageType.Warning) - } - else - { - Write-Host "$([Constants]::NextSteps -f $UIUrl)" -ForegroundColor $([Constants]::MessageType.Info) - } - Write-Host "$([Constants]::DoubleDashLine)" - } - else - { - Write-Host "`rCompleted installation for Azure Tenant Security Solution." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host "$([Constants]::SingleDashLine)" - } - - } - catch - { - Write-Host "Error occurred while executing post deployment steps. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - } - - return $deploymentResult - } -} - -class Constants -{ - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [string] $InstallSolutionInstructionMsg = "This command will perform 5 important operations. It will:`r`n`n" + - " [1] Create resources needed to support Azure Tenant Security scan `r`n" + - " [2] Deploy AzTS packages to azure function app `r`n" + - " [3] Deploy UI and API packages to respective azure web service apps `r`n" + - " [4] Schedule daily subscription scan `r`n" + - " [5] Setup monitoring alerts `r`n`n" + - "More details about resources created can be found in the link: http://aka.ms/DevOpsKit/TenantSecuritySetup `r`n" - - static [string] $QuickInstallSolutionInstructionMsg = "This command will perform following major steps. It will:`r`n`n" + - " [0] Validate and install required Az modules (Optional) `r`n" + - " [1] Setup central scanning managed identity `r`n" + - " [2] Create Azure AD application for secure authentication (Optional)`r`n" + - " [3] Setup infra resources and schedule daily security control scan on target subscriptions `r`n" - - static [string] $DoubleDashLine = "================================================================================" - static [string] $SingleDashLine = "--------------------------------------------------------------------------------" - - static [string] $NextSteps = "** Next steps **`r`n" + - " a) Azure Tenant security scan will start on scheduled time (UTC 01:00).`r`n" + - " ** OR **`r`n"+ - " You can trigger scan using 'Start-AzSKTenantSecuritySolutionOnDemandScan -SubscriptionId -ScanHostRGName ' command.`r`n"+ - " b) After scan completion, all security control results will be sent to LA workspace and Storage account present in your hosting subscription scan RG.`r`n" + - " c) Using the AzTS UI ({0}) you can monitor all the compliance detail of the controls for all the subscriptions which were scanned through 'step a'. or you can trigger new adhoc scan for subscriptions of your choice through UI itself and monitor new compliance details.`r`n"+ - " d) You can create compliance monitoring Power BI dashboard using link: http://aka.ms/DevOpsKit/TenantSecurityDashboard.`r`n" + - "`r`nFor any feedback contact us at: azsksup@microsoft.com.`r`n" - - static [string] $AADUserNotFound = "UserNotFound"; - - static [string] $AzureADAppSetupInstructionMsg = "This command will perform 5 important operations. It will:`r`n`n" + - " [1] Create Azure AD application for UI, if it does not exist. `r`n" + - " [2] Create Azure AD application for API, if it does not exist. `r`n" + - " [3] Update UI AD application redirection URL. `r`n" + - " [4] Grant AD applications permission to request OAuth2.0 implicit flow access tokens. This is required for browser-based apps. `r`n" + - " [5] Grant 'User.Read' permission to UI AD application. This permission is used to read logged in user's details such as name, email, and photo."; - - static [string] $AzureADAppSetupNextSteps = "** Next steps **`r`n" + - "Use Install-AzSKTenantSecuritySolution command to complete the setup. `r`n" + - "The AD application (client) ids listed below needs to be passed as input parameters to the installation command: `r`n" + - " [1] WebAPIAzureADAppId: {0} `r`n" + - " [2] UIAzureADAppId: {1} `r`n"; - - static [string] $ScanningIdentitySetupInstructionMsg = "This command will perform 2 important operations. It will:`r`n`n" + - " [1] Create user-assigned managed identity which will be used for centrally scanning your subscriptions. `r`n" + - " [2] Assign 'Reader' access to user-assigned managed identity on target subscription(s) that needs to be scanned. `r`n"; - - static [string] $ScanningIdentitySetupNextSteps = "** Next steps **`r`n" + - "Use Grant-AzSKGraphPermissionToUserAssignedIdentity command to grant graph permission to this scanner identity. This permission will be required to read data in your organization's directory such as Privileged Identity Management (PIM), users, groups and apps details.`r`n"; - - static [string] $KeyVaultSecretStoreSetupInstructionMsg = "This command will perform following important operations. It will:`r`n`n" + - " [1] Create a resource group (if not already exist) for Key vault. `r`n" + - " [2] Create a Key vault (if not already exist) to store Scanner Identity (App) credentials. `r`n" + - " [3] Enable Audit Event logging for the Key Vault. `r`n" + - " [4] Store Scanner App credentials in Key Vault as secret. `r`n"; - - static [string] $InternalIdentitySetupNextSteps = "** Next steps **`r`n" + - "Use Grant-AzSKGraphPermissionToUserAssignedIdentity command to grant graph permission to Internal MI identity. Internal MI is also used by AzTS UI to read the list of security groups that the user is a member of. For this purpose, internal MI requires 'User.Read.All' permission.`r`n"; - - static [string] $KeyVaultSecretStoreSetupNextSteps = "** Next steps **`r`n" + - "Use Grant-AzSKAccessOnKeyVaultToUserAssignedIdentity command to grant internal scanner identity access over secret stored in Key Vault. This permission will be required to read scanner identity credentials for scanning. This step need to be performed once AzTS solution installtion (after you have successfully run the command 'Install-AzSKTenantSecuritySolution') is done.`r`n"; - - static [string] $EnableByDesignExpInstructionMsg = "This command will perform following important operations. It will:`r`n`n" + - " [1] Create cosmos DB account and table for exception management. `r`n" + - " [2] Create a Key vault (if not already exist). `r`n" + - " [3] Store cosmos DB connection string in Key vault as secret. `r`n" + - " [4] Grant AzTS internal MI access to get secrets from Key vault. `r`n" + - " [5] Update AzTS API to use user assigned identity (internal MI) for KV reference. `r`n" + - " [6] Update AzTS API app settings to enable exception feature. `r`n" + - " [7] Create password credential for AzTS UI AAD app. `r`n" + - " [8] Store AzTS UI AAD app password credential in Key vault as secret. `r`n" + - " [9] Update AzTS Scanner to use user assigned identity (internal MI) for KV reference. `r`n" + - " [10] Update AzTS Scanner app settings to enable exception feature. `r`n" + - "This setup will take approximately 10 mins. `r`n`n"; - - static [string] $AutoUpdaterFailureAlertQuery = "customEvents - | where name == 'AzTS_Service_AutoUpdateOverallProgressTracker' - | where customDimensions.Id == '{0}' - | where timestamp > ago(3d) - | extend isOverallSuccess = tostring(customDimensions.isOverallSuccess) - | extend EventType = tostring(customDimensions.EventType) - | where EventType =~ 'Completed' - | summarize arg_max(timestamp, *) - | project EventType, isOverallSuccess - | where isOverallSuccess == true"; - - - static [string] $NewReleaseAlertQuery = "// Get currently installed version for an app service. - customEvents - | where name == 'AzTS_Service_AutoUpdateVersionTracker' - | where customDimensions.Id == '{0}' - | where timestamp > ago(2d) - | extend FeatureName = tolower(tostring(customDimensions.FeatureName)) - | extend AppName = tolower(tostring(customDimensions.AppName)) - | extend Status= tostring(customDimensions.Status), CurrentVersion= tostring(customDimensions.CurrentVersion) - | where FeatureName != dynamic(null) and AppName != dynamic(null) - | where Status =~ 'Succeeded' or Status =~ 'AlreadyUpToDate' - | summarize arg_max(timestamp, LatestRecord = CurrentVersion), arg_min(timestamp, PrevRecord = CurrentVersion) by FeatureName, AppName - | where LatestRecord != PrevRecord - | project FeatureName, AppName, UpgradedFrom = PrevRecord , UpgradedTo = LatestRecord" - - static [string] $SubscriptionInvRefreshFailureQuery = "let TablePlaceholder = view () {print SubscriptionId = 'SubscriptionIdNotFound'}; - let SubInventory_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_SubInventory_CL | where TimeGenerated > ago(24h) - | distinct SubscriptionId - )) - | where SubscriptionId !~ 'SubscriptionIdNotFound'; - SubInventory_CL"; - static [string] $BaselineControlsInvRefreshFailureQuery = "let TablePlaceholder = view () {print ControlId_s = 'NA'}; - let BaselineControlsInv_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_BaselineControlsInv_CL | where TimeGenerated > ago(24h) - | distinct ControlId_s - )) - | where ControlId_s !~ 'NA'; - BaselineControlsInv_CL"; - static [string] $RBACInvRefreshFailureQuery = "let TablePlaceholder = view () {print NameId = 'NA', RoleId = 'NA'}; - let RBAC_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_RBAC_CL | where TimeGenerated > ago(24h) - | take 10 - )) - | where NameId !~ 'NA'; - RBAC_CL"; - static [string] $ControlResultsRefreshFailureQuery = "let TablePlaceholder = view () {print SubscriptionId = 'SubscriptionIdNotFound'}; - let ControlResults_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_ControlResults_CL | where TimeGenerated > ago(24h) - | distinct SubscriptionId - )) - | where SubscriptionId !~ 'SubscriptionIdNotFound'; - ControlResults_CL - | take 10"; - - static [string] $ScanProgressSummaryQuery = "let TablePlaceholder = view () { print SubscriptionId = 'SubscriptionIdNotFound' }; - let ProcessedSubscriptions_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_ProcessedSubscriptions_CL - | where TimeGenerated > ago(2d) - | where JobId_d == toint(format_datetime(now(), 'yyyyMMdd')) - | where EventType_s == 'Completed' and OverallProcessCompleted_b == true - | distinct SubscriptionId - )) - | where SubscriptionId <> 'SubscriptionIdNotFound'; - let SubInventory_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_SubInventory_CL - | where TimeGenerated > ago(2d) - | where JobId_d == toint(format_datetime(now(), 'yyyyMMdd')) - | where State_s != 'Disabled' - | distinct SubscriptionId - )) - | where SubscriptionId <> 'SubscriptionIdNotFound'; - SubInventory_CL - | project SubscriptionId - | join kind= leftouter - ( - ProcessedSubscriptions_CL - ) - on SubscriptionId - | extend Type = iff(SubscriptionId1 != dynamic(null), 'Completed', 'NotCompleted') - | summarize count() by Type"; - - static [string] $UnintendedSecretAccessAlertQuery =" - let TablePlaceholder = view () {{ print IdentityObjectId = 'NA', Count = '0' }}; - let secretAccessEvent = union isfuzzy=true TablePlaceholder, (union ( - AzureDiagnostics - | where ResourceId =~ '{0}' - | where OperationName =~ 'SecretGet' - | where requestUri_s contains '{1}' - | where isnotempty(identity_claim_oid_g) and identity_claim_oid_g !~ '{2}' - | summarize Count = count() by IdentityObjectId = tostring(identity_claim_oid_g))) - | where IdentityObjectId <> 'NA'; - secretAccessEvent" - -} - -function Set-AzTSMonitoringAlert -{ - param ( - [PSObject] $DeploymentResult, - [string[]] $SendAlertNotificationToEmailIds, - [string] $Location, - [string] $ScanHostRGName, - [string] $TelemetryIdentifier, - [bool] $IsAutoUpdaterEnabled - ) - - try - { - Write-Verbose "$(Get-TimeStamp)Creating monitoring alerts..." - - $EmailReceivers = @() - $SendAlertNotificationToEmailIds | ForEach-Object { - $EmailReceivers += New-AzActionGroupReceiver -Name "Notify_$($_)" -EmailReceiver -EmailAddress $_ - } - - $alertActionGroup = Set-AzActionGroup -Name ‘AzTSAlertActionGroup’ -ResourceGroupName $ScanHostRGName -ShortName ‘AzTSAlert’ -Receiver $EmailReceivers -WarningAction SilentlyContinue - - - if($DeploymentResult.Outputs.ContainsKey('logAnalyticsResourceId') -and $DeploymentResult.Outputs.ContainsKey('applicationInsightsId')) - { - $LADataSourceId = $DeploymentResult.Outputs.logAnalyticsResourceId.Value - $AIDataSourceId = $DeploymentResult.Outputs.applicationInsightsId.Value - - $deploymentName = "AzTSenvironmentmonitoringsetup-$([datetime]::Now.ToString("yyyymmddThhmmss"))" - - New-AzResourceGroupDeployment -Name $deploymentName ` - -Mode Incremental ` - -ResourceGroupName $ScanHostRGName ` - -TemplateFile ".\MonitoringAlertTemplate.json" ` - -AutoUpdaterFailureAlertQuery ([string]::Format([Constants]::AutoUpdaterFailureAlertQuery, $TelemetryIdentifier)) ` - -AutoUpdaterNewReleaseAlertQuery ([string]::Format([Constants]::NewReleaseAlertQuery, $TelemetryIdentifier)) ` - -SubscriptionInvRefreshFailureAlertQuery ([Constants]::SubscriptionInvRefreshFailureQuery) ` - -BaselineControlsInvRefreshFailureAlertQuery ([Constants]::BaselineControlsInvRefreshFailureQuery) ` - -RBACInvRefreshFailureAlertQuery ([Constants]::RBACInvRefreshFailureQuery) ` - -ControlResultsRefreshFailureAlertQuery ([Constants]::ControlResultsRefreshFailureQuery) ` - -ScanProgressSummaryQuery ([Constants]::ScanProgressSummaryQuery) ` - -ActionGroupId $alertActionGroup.Id ` - -AIResourceId $AIDataSourceId ` - -LAResourceId $LADataSourceId ` - -IsAutoUpdaterEnabled $IsAutoUpdaterEnabled - - Write-Verbose "$(Get-TimeStamp)Completed monitoring alert setup." - - } - else - { - Write-Host "Failed to setup monitoring alert. Either Application Insight ID or Log Analytics ID is either null or empty." -ForegroundColor $([Constants]::MessageType.Error) - return - } - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } -} - -# Execute this function to send initial setup event to application insight. This is required as the auto-updater can take up to 24 hours to start after the initial setup. -# In the meantime, this custom events suppresses the Auto-Updater failure alert. -function SendCustomAIEvent -{ - param ( - [PSObject] $DeploymentResult, - [string] $TelemetryIdentifier - ) - try - { - if( $DeploymentResult.Outputs.ContainsKey('applicationInsightsIKey')) - { - $InstrumentationKey = $DeploymentResult.Outputs.applicationInsightsIKey.Value - $azAccountsModuleInfo = Get-Module Az.Accounts -ListAvailable -Verbose:$false | Select-Object -First 1 - if($null -eq $azAccountsModuleInfo) - { - Install-Module -Name Az.Accounts -Scope CurrentUser -Repository 'PSGallery' -AllowClobber -Verbose:$false - $azAccountsModuleInfo = Get-Module Az.Accounts -ListAvailable -Verbose:$false | Select-Object -First 1 - } - $AssemblyPath = Get-ChildItem -Path $azAccountsModuleInfo.ModuleBase -Filter "Microsoft.ApplicationInsights.dll" -Recurse - $AssemblyPathFullName = $AssemblyPath.FullName | Sort-Object -Descending | Select-Object -First 1 - Add-Type -Path $AssemblyPathFullName - $client = [Microsoft.ApplicationInsights.TelemetryClient]::new() - $client.InstrumentationKey= $InstrumentationKey - $event = [Microsoft.ApplicationInsights.DataContracts.EventTelemetry]::new() - $event.Name = "AzTS_Service_AutoUpdateOverallProgressTracker" - $customProperties = @{ - isOverallSuccess = $true; - Id = $TelemetryIdentifier - isAppServiceAutoUpdateSuccess = $true; - isCheckForAutoUpdateSuccess = $true; - isGetAppServicesByResourceGroupSuccess = $true; - isReconcilationCheckSuccess = $true; - message = "This is an initial setup event." - } - $customProperties.Keys | ForEach-Object { - try { - $event.Properties[$_] = $customProperties[$_].ToString(); - } - catch - { - $_ - # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again - # No need to break execution - } - } - - $client.TrackEvent($event) - $client.Flush() - } - } - catch - { - # Eat the current exception which typically happens when dll is not available - } - -} - -class ContextHelper -{ - $currentContext = $null; - - [PSObject] SetContext([string] $SubscriptionId) - { - $this.currentContext = $null - if(-not $SubscriptionId) - { - - Write-Host "The argument 'SubscriptionId' is null. Please specify a valid subscription id." -ForegroundColor $([Constants]::MessageType.Error) - return $null; - } - - # Login to Azure and set context - try - { - if(Get-Command -Name Get-AzContext -ErrorAction Stop) - { - $this.currentContext = Get-AzContext -ErrorAction Stop - $isLoginRequired = (-not $this.currentContext) -or (-not $this.currentContext | GM Subscription) -or (-not $this.currentContext | GM Account) - - # Request login if context is empty - if($isLoginRequired) - { - Write-Host "No active Azure login session found. Initiating login flow..." -ForegroundColor $([Constants]::MessageType.Warning) - $this.currentContext = Connect-AzAccount -ErrorAction Stop # -SubscriptionId $SubscriptionId - } - - # Switch context if the subscription in the current context does not the subscription id given by the user - $isContextValid = ($this.currentContext) -and ($this.currentContext | GM Subscription) -and ($this.currentContext.Subscription | GM Id) - if($isContextValid) - { - # Switch context - if($this.currentContext.Subscription.Id -ne $SubscriptionId) - { - $this.currentContext = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force - } - } - else - { - Write-Host "Invalid PS context. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - } - } - else - { - Write-Host "Az command not found. Please run the following command 'Install-Module Az -Scope CurrentUser -Repository 'PSGallery' -AllowClobber -SkipPublisherCheck' to install Az module." -ForegroundColor $([Constants]::MessageType.Error) - } - } - catch - { - Write-Host "Error occurred while logging into Azure. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - return $null; - } - - return $this.currentContext; - - } - -} - -class CentralPackageInfo -{ - [PSObject] $CentralPackageObject = $null - [string] $CentralPackageURL = [string]::Empty - [string] $MetadataAggregatorPackageURL = [string]::Empty - [string] $WorkItemProcessorPackageURL = [string]::Empty - [string] $WebApiPackageURL = [string]::Empty - [string] $UIPackageURL = [string]::Empty - - CentralPackageInfo() - { - $CentralPackageVersionResponse = Invoke-WebRequest -UseBasicParsing -Uri "https://aka.ms/AzTS/CentralPackageURL" -Method Get - if(($CentralPackageVersionResponse | Measure-Object).Count -gt 0 -and $CentralPackageVersionResponse.StatusCode -eq 200) - { - $this.CentralPackageObject = $CentralPackageVersionResponse.Content | ConvertFrom-Json - - if(($this.CentralPackageObject | Get-Member BasePackageLink -ErrorAction SilentlyContinue) -ne $null) - { - $this.CentralPackageURL = $this.CentralPackageObject.BasePackageLink - } - } - - $this.MetadataAggregatorPackageURL = $this.GetPackageURL("MetadataAggregator") - $this.WorkItemProcessorPackageURL = $this.GetPackageURL("WorkItemProcessor") - $this.WebApiPackageURL = $this.GetPackageURL("WebApi") - $this.UIPackageURL = $this.GetPackageURL("UI") - } - - [string] GetPackageVersion([string] $featureName) - { - $packageVersion = "1.0.0" - if(($this.CentralPackageObject | Get-Member Packages -ErrorAction SilentlyContinue) -ne $null) - { - $packageDetails = $this.CentralPackageObject.Packages | Where-Object { $_.Name -eq $featureName } - - if(($packageDetails | Get-Member Stable -ErrorAction SilentlyContinue) -ne $null) - { - $packageVersion = $packageDetails.Stable - } - } - - return $packageVersion - } - - [string] GetPackageName([string] $featureName) - { - $packageName = $featureName + ".zip"; - if(($this.CentralPackageObject | Get-Member Packages -ErrorAction SilentlyContinue) -ne $null) - { - $packageDetails = $this.CentralPackageObject.Packages | Where-Object { $_.Name -eq $featureName } - - if(($packageDetails | Get-Member PackageName -ErrorAction SilentlyContinue) -ne $null) - { - $packageName = $packageDetails.PackageName - } - } - - return $packageName - } - - [string] GetPackageURL([string] $featureName) - { - return [string]::Join("/",$this.CentralPackageURL, - $featureName, - $this.GetPackageVersion($featureName), - $this.GetPackageName($featureName)) - } - -} - -class Logger{ - [string] $logFilePath = ""; - - Logger([string] $HostSubscriptionId) - { - $logFolerPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Setup\Subscriptions\$($HostSubscriptionId.replace('-','_'))"; - $logFileName = "\$('DeploymentLogs_' + $(Get-Date).ToString('yyyyMMddhhmm') + '.txt')"; - $this.logFilePath = $logFolerPath + $logFileName - # Create folder if not exist - if (-not (Test-Path -Path $logFolerPath)) - { - New-Item -ItemType Directory -Path $logFolerPath | Out-Null - } - # Create log file - - New-Item -Path $this.logFilePath -ItemType File | Out-Null - - } - - PublishCustomMessage ([string] $message, [string] $foregroundColor){ - $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor $foregroundColor - } - - PublishCustomMessage ([string] $message){ - $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor White - } - - PublishLogMessage ([string] $message){ - $($message) | Add-Content $this.logFilePath - } - - PublishLogFilePath() - { - Write-Host $([Constants]::DoubleDashLine)"`r`nLogs have been exported to: $($this.logFilePath)`r`n"$([Constants]::DoubleDashLine) -ForegroundColor Cyan - } -} - -function Get-TimeStamp { - return "{0:h:m:ss tt} - " -f (Get-Date -UFormat %T) -} - -Function CreateAzureADApplication -{ - param ( - [string] - [Parameter(Mandatory = $true, HelpMessage="Display name for the App to be created.")] - $displayName, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] - $AdditionalOwnerUPNs = @() - ) - - Write-Host "Checking if Azure AD application [$($displayName)] already exist..." -ForegroundColor $([Constants]::MessageType.Info) - - if (!(Get-AzureADApplication -SearchString $displayName)) { - - Write-Host "Creating new AD application [$($displayName)]..." -ForegroundColor $([Constants]::MessageType.Info) - # create new application - $app = New-AzureADApplication -DisplayName $displayName - - # create a service principal for your application - $spForApp = New-AzureADServicePrincipal -AppId $app.AppId - - } - else - { - Write-Host "AD application [$($displayName)] already exists." -ForegroundColor $([Constants]::MessageType.Info) - $app = Get-AzureADApplication -SearchString $displayName - } - - # Adding additional owners (if any) - if (($AdditionalOwnerUPNs| Measure-Object).Count -gt 0) - { - Add-OwnersToAADApplication -AppObjectId $app.ObjectId -UserPrincipalNames $AdditionalOwnerUPNs - } - - #endregion - return $app -} - -function GetADPermissionToBeGranted -{ - param - ( - [string] $targetServicePrincipalAppId, - $appPermissionsRequired - ) - - $targetSp = Get-AzureADServicePrincipal -Filter "AppId eq '$($targetServicePrincipalAppId)'" - - $RoleAssignments = @() - Foreach ($AppPermission in $appPermissionsRequired) { - $RoleAssignment = $targetSp.Oauth2Permissions | Where-Object { $_.Value -eq $AppPermission} - $RoleAssignments += $RoleAssignment - } - - $ResourceAccessObjects = New-Object 'System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]' - foreach ($RoleAssignment in $RoleAssignments) { - $resourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" - $resourceAccess.Id = $RoleAssignment.Id - $resourceAccess.Type = 'Scope' - $ResourceAccessObjects.Add($resourceAccess) - } - $requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess" - $requiredResourceAccess.ResourceAppId = $targetSp.AppId - $requiredResourceAccess.ResourceAccess = $ResourceAccessObjects - - return $requiredResourceAccess - -} - -function get-hash([string]$textToHash) { - $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider - $toHash = [System.Text.Encoding]::UTF8.GetBytes($textToHash) - $hashByteArray = $hasher.ComputeHash($toHash) - $result = [string]::Empty; - foreach($byte in $hashByteArray) - { - $result += "{0:X2}" -f $byte - } - return $result; - } - - -function Grant-AzSKGraphPermissionToUserAssignedIdentity -{ - Param - ( - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where user identity has been created.")] - $ResourceGroupName, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Object id of user managed identity used to scan subscriptions.")] - $IdentityName, - - [string[]] - [Parameter(Mandatory = $true, HelpMessage="List of Microsoft Graph permission to be granted to the Managed Identity")] - $MSGraphPermissionsRequired, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of Azure AD Graph permission to be granted to the Managed Identity")] - $ADGraphPermissionsRequired, - - [string] - [ValidateNotNullOrEmpty()] - [Parameter(Mandatory = $true, ParameterSetName = "UserIdentity", HelpMessage="Object id of user managed identity used to scan subscriptions.")] - $UserAssignedIdentityObjectId - ) - - try { - - Write-Host "WARNING: To grant Graph API permission, the signed-in user must be a member of one of the following administrator roles: Global Administrator or Privileged Role Administrator." -ForegroundColor $([Constants]::MessageType.Warning) - - - # Validate input - if([string]::IsNullOrWhiteSpace($UserAssignedIdentityObjectId)) - { - Write-Host "Getting Azure AD enterprise application details..." -ForegroundColor $([Constants]::MessageType.Info) - $UserAssignedIdentity = Get-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $IdentityName - $UserAssignedIdentityObjectId = $UserAssignedIdentity.PrincipalId - } - - GrantMSGraphPermissionsToAADIdentity -AADIdentityObjectId $UserAssignedIdentityObjectId -MSGraphPermissionsRequired $MSGraphPermissionsRequired -ADGraphPermissionsRequired $ADGraphPermissionsRequired - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } -} - -function GrantMSGraphPermissionsToAADIdentity -{ - param ( - [string] - $AADIdentityObjectId, - - [string[]] - $MSGraphPermissionsRequired, - - [string[]] - $ADGraphPermissionsRequired - ) - - $msi = Get-AzureADServicePrincipal -ObjectId $AADIdentityObjectId - $groupPermissions = @() - - if(($MSGraphPermissionsRequired | Measure-Object).Count -gt 0) - { - # MS Graph ID - $targetServicePrincipalAppId='00000003-0000-0000-c000-000000000000' - $graph = Get-AzureADServicePrincipal -Filter "AppId eq '$($targetServicePrincipalAppId)'" - $groupPermissions += @($graph.AppRoles | Where { $MSGraphPermissionsRequired -contains $_.Value -and $_.AllowedMemberTypes -contains "Application"} ` - | Select *, @{ Label = "PermissionScope"; Expression = {"MS Graph"}}, ` - @{ Label = "GraphServicePrincipalObjectId"; Expression = {$graph.ObjectId}}) - } - - if(($ADGraphPermissionsRequired | Measure-Object).Count -gt 0) - { - # Azure AD Graph ID - $targetServicePrincipalAppId='00000002-0000-0000-c000-000000000000' - $graph = Get-AzureADServicePrincipal -Filter "AppId eq '$($targetServicePrincipalAppId)'" - $groupPermissions += @($graph.AppRoles | Where { $ADGraphPermissionsRequired -contains $_.Value -and $_.AllowedMemberTypes -contains "Application"} ` - | Select *, @{ Label = "PermissionScope"; Expression = {"Azure AD Graph"}}, ` - @{ Label = "GraphServicePrincipalObjectId"; Expression = {$graph.ObjectId}}) - } - - # Grant permission to managed identity. - if(($groupPermissions | Measure-Object).Count -gt 0) - { - $groupPermissions | ForEach-Object { - - Write-Host "Granting $($_.PermissionScope) [$($_.Value)] permission to Azure AD enterprise application [ObjectId: $($UserAssignedIdentityObjectId)]." -ForegroundColor $([Constants]::MessageType.Info) - - try - { - $RoleAssignment = New-AzureADServiceAppRoleAssignment ` - -Id $_.Id ` - -ObjectId $msi.ObjectId ` - -PrincipalId $msi.ObjectId ` - -ResourceId $_.GraphServicePrincipalObjectId - Write-Host "Successfully granted [$($_.Value)] permission to Azure AD enterprise application." -ForegroundColor $([Constants]::MessageType.Update) - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } - } - } - else - { - Write-Host "WARNING: No match found for permissions provided as input." -ForegroundColor $([Constants]::MessageType.Warning) - } -} - -function Set-AzSKTenantSecurityADApplication -{ - - Param( - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where setup resources will be created.")] - $ScanHostRGName = "AzSK-AzTS-RG", - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Custom", HelpMessage="Name of the Azure AD application to be used by the API.")] - $WebAPIAzureADAppName, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Custom", HelpMessage="Name of the Azure AD application to be used by the UI.")] - $UIAzureADAppName, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] - $AzureEnvironmentName = "AzureCloud", - - [switch] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] - [Parameter(Mandatory = $false, ParameterSetName = "Custom", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] - $ConsolidatedSetup = $false, - - [string[]] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "Custom", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] - $AdditionalOwnerUPNs = @() - - ) - - try - { - $output = "" | Select WebAPIAzureADAppId,UIAzureADAppId - - if (-not $ConsolidatedSetup) - { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "$([Constants]::AzureADAppSetupInstructionMsg)" -ForegroundColor $([Constants]::MessageType.Info) - } - Write-Host "NOTE: If you do not have the permission to perform aforementioned operations, please contact your administrator to complete the setup." -ForegroundColor $([Constants]::MessageType.Warning) - Write-Host $([Constants]::SingleDashLine) - - $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() #considering only first 5 characters - - Write-Host "Starting Azure AD application setup..." -ForegroundColor $([Constants]::MessageType.Info) - - # Creating Azure AD application: Web API - if([string]::IsNullOrWhiteSpace($WebAPIAzureADAppName)) - { - $WebAPIAzureADAppName = "AzSK-AzTS-WebApi-$ResourceHash"; - } - - $webApi = CreateAzureADApplication -displayName $WebAPIAzureADAppName -AdditionalOwnerUPNs $AdditionalOwnerUPNs - - # Creating Azure AD application: UI - if([string]::IsNullOrWhiteSpace($UIAzureADAppName)) - { - $UIAzureADAppName="AzSK-AzTS-UI-$ResourceHash" - } - - $webUIApp = CreateAzureADApplication -displayName $UIAzureADAppName -AdditionalOwnerUPNs $AdditionalOwnerUPNs - - Write-Host "Updating Azure AD application registration..." -ForegroundColor $([Constants]::MessageType.Info) - - $identifierUri = 'api://{0}' -f $webUIApp.AppId - $replyUris = New-Object Collections.Generic.List[string] - $replyUris.Add(($AzureEnvironmentAppServiceURI.$AzureEnvironmentName -f $UIAzureADAppName)); - $replyUris.Add($([string]::Join("/", $([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $UIAzureADAppName)), "auth.html"))); - Set-AzureADApplication -ObjectId $webUIApp.ObjectId -ReplyUrls $replyUris -IdentifierUris $identifierUri -Oauth2AllowImplicitFlow $true - - $identifierUri = 'api://{0}' -f $webApi.AppId - Set-AzureADApplication -ObjectId $webApi.ObjectId -IdentifierUris $identifierUri -Oauth2AllowImplicitFlow $true - - Write-Host "Updated Azure AD applications redirection URL and OAuth 2.0 implicit grant flow." -ForegroundColor $([Constants]::MessageType.Info) - - try - { - Write-Host "Granting 'User.Read' permission to UI AD application..." -ForegroundColor $([Constants]::MessageType.Info) - - # MS Graph ID - $targetServicePrincipalAppId='00000003-0000-0000-c000-000000000000'; - # Grant MS Graph permission - # Get Azure AD App for UI. Grant graph permission. - $appPermissionsRequired = @('User.Read') - $permission = GetADPermissionToBeGranted -targetServicePrincipalAppId $targetServicePrincipalAppId -appPermissionsRequired $appPermissionsRequired - Set-AzureADApplication -ObjectId $webUIApp.ObjectId -RequiredResourceAccess $permission - Write-Host "Granted UI AD application 'User.Read' permission." -ForegroundColor $([Constants]::MessageType.Info) - } - catch - { - Write-Host "Failed to grant 'User.Read' permission. ExceptionMessage $($_)" -ForegroundColor Red - } - $output.WebAPIAzureADAppId = $webApi.AppId - $output.UIAzureADAppId = $webUIApp.AppId - - Write-Host "Completed Azure AD application setup." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - if (-not $ConsolidatedSetup) - { - $NextStepMessage = $([Constants]::AzureADAppSetupNextSteps) -f $webApi.AppId, $webUIApp.AppId - Write-Host "$($NextStepMessage)" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::DoubleDashLine) - } - - return $output; - - } - catch - { - Write-Host "Error occurred while setting up Azure AD application. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - return; - } -} - - -function Set-AzSKTenantSecuritySolutionScannerIdentity -{ - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription id in which scanner identity is to be created.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $false, HelpMessage="Name of ResourceGroup where scanner identity will be created.")] - $ResourceGroupName = "AzSK-AzTS-RG", - - [string] - [Parameter(Mandatory = $true, HelpMessage="Location where scanner identity will get created. Default location is EastUS2.")] - $Location = 'EastUS2', - - [string] - [Parameter(Mandatory = $false, HelpMessage="Name of the Azure AD application to be used by the API.")] - $UserAssignedIdentityName, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of target subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] - [Alias("SubscriptionsToScan")] - $TargetSubscriptionIds = @(), - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] - [Alias("ManagementGroupsToScan")] - $TargetManagementGroupNames = @(), - - [switch] - [Parameter(Mandatory = $false, HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] - $ConsolidatedSetup = $false - ) - - Begin - { - # Step 1: Set context to subscription where user-assigned managed identity needs to be created. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($SubscriptionId) - if(-not $currentContext) - { - return; - } - } - - Process - { - try - { - - if (-not $ConsolidatedSetup) - { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "Running Azure Tenant Security scanner identity setup...`n" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::ScanningIdentitySetupInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - } - - # Step 2: Create resource group where user-assigned MI resource will be created. - try - { - Write-Verbose "$(Get-TimeStamp)Checking resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) - $rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue - if(-not $rg) - { - Write-Verbose "$(Get-TimeStamp)Creating resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) - $rg = New-AzResourceGroup -Name $ResourceGroupName -Location $Location -ErrorAction Stop - } - else{ - Write-Verbose "$(Get-TimeStamp)Resource group already exists." #-ForegroundColor $([Constants]::MessageType.Info) - } - - } - catch - { - Write-Host "`n`rFailed to create resource group for deployment." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 3: Create user-assigned MI resource. - Write-Host "Checking if user-assigned identity [$($UserAssignedIdentityName)] exists..." -ForegroundColor $([Constants]::MessageType.Info) - $UserAssignedIdentity = Get-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $UserAssignedIdentityName -ErrorAction SilentlyContinue - if($UserAssignedIdentity -eq $null) - { - Write-Host "Creating a new user-assigned identity [$($UserAssignedIdentityName)]." -ForegroundColor $([Constants]::MessageType.Info) - $UserAssignedIdentity = New-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $UserAssignedIdentityName -Location $Location - Start-Sleep -Seconds 60 - } - else - { - Write-Host "User-assigned identity [$($UserAssignedIdentityName)] already exists." -ForegroundColor $([Constants]::MessageType.Info) - } - - # Grant User Identity Reader permission on target subscription(s). - Write-Host "Granting user-assigned identity 'Reader' permission on target scope(s)..." -ForegroundColor $([Constants]::MessageType.Info) - $targetSubscriptionCount = ($TargetSubscriptionIds | Measure-Object).Count - $targetMgtGroupCount = ($TargetManagementGroupNames | Measure-Object).Count - if($targetSubscriptionCount -gt 0) - { - $TargetSubscriptionIds | % { - - try - { - Write-Host "Assigning 'Reader' access to user-assigned managed identity on target subscription [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) - New-AzRoleAssignment -ApplicationId $UserAssignedIdentity.ClientId -Scope "/subscriptions/$_" -RoleDefinitionName "Reader" -ErrorAction Stop - } - catch - { - if($_.Exception.Body.Code -eq "RoleAssignmentExists") - { - Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) - - } - else - { - Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) - - } - } - } - } - - if ($targetMgtGroupCount -gt 0) - { - $TargetManagementGroupNames | % { - - try - { - Write-Host "Assigning 'Reader' access to user-assigned managed identity on target management group [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) - New-AzRoleAssignment -ApplicationId $UserAssignedIdentity.ClientId -Scope "/providers/Microsoft.Management/managementGroups/$_" -RoleDefinitionName "Reader" -ErrorAction Stop - } - catch - { - if($_.Exception.Body.Code -eq "RoleAssignmentExists") - { - Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) - - } - else - { - Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) - - } - } - } - } - - if (-not(($targetSubscriptionCount -gt 0) -or ($targetMgtGroupCount -gt 0))) - { - Write-Host "No target subscription or management group specified." -ForegroundColor $([Constants]::MessageType.Warning) - } - - Write-Host "Completed Azure Tenant Security scanner identity setup." -ForegroundColor $([Constants]::MessageType.Update) - - Write-Host $([Constants]::SingleDashLine) - if (-not $ConsolidatedSetup) - { - Write-Host ([constants]::ScanningIdentitySetupNextSteps) -ForegroundColor $([Constants]::MessageType.Info) - } - - Write-Host $([Constants]::DoubleDashLine) - - return $UserAssignedIdentity; - } - catch - { - Write-Host "Error occurred while setting up scanner identity. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - return; - } - } -} - - - -function AddAlert( - - [string] $Query, - [ValidateSet("Equal", "GreaterThan", "LessThan" )] - [string] $ThresholdOperator, - [int] $Threshold = 0, - [string] $AlertDescription, - [string] $AlertName, - [ValidateSet(0,1,2,3,4)] - [int] $AlertSeverity = 2, - [int] $FrequencyInMinutes = 5, - [int] $TimeWindowInMinutes = 5, - [string] $DataSourceId, - [string] $ActionGroupResourceId, - [string] $Location, - [string] $ResourceGroupName - - ) -{ - $source = New-AzScheduledQueryRuleSource -Query $Query -DataSourceId $DataSourceId -WarningAction SilentlyContinue - $schedule = New-AzScheduledQueryRuleSchedule -FrequencyInMinutes $FrequencyInMinutes -TimeWindowInMinutes $TimeWindowInMinutes -WarningAction SilentlyContinue - $triggerCondition = New-AzScheduledQueryRuleTriggerCondition -ThresholdOperator $ThresholdOperator -Threshold $Threshold -WarningAction SilentlyContinue - $aznsActionGroup = New-AzScheduledQueryRuleAznsActionGroup -ActionGroup $ActionGroupResourceId -EmailSubject "TDMON ALERT: $($AlertName)" -WarningAction SilentlyContinue - $alertingAction = New-AzScheduledQueryRuleAlertingAction -AznsAction $aznsActionGroup -Severity $AlertSeverity -Trigger $triggerCondition -WarningAction SilentlyContinue - - - # create alert - New-AzScheduledQueryRule -ResourceGroupName $ResourceGroupName -Location $Location ` - -Action $alertingAction -Enabled $true ` - -Description $AlertDescription ` - -Schedule $schedule ` - -Source $source ` - -Name $AlertName -WarningAction SilentlyContinue -} - -function Set-AzSKTenantSecuritySolutionSecretStorage -{ - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription id in which key vault is to be created.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where key vault will be created.")] - $ResourceGroupName = "AzSK-AzTS-KV-RG", - - [string] - [Parameter(Mandatory = $false, HelpMessage="Location where the resource group and key vault will get created. Default location is EastUS2.")] - $Location = 'EastUS2', - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of the Key Vault to be created.")] - $KeyVaultName, - - [string] - [Parameter(Mandatory = $true, HelpMessage="AzTS Scanner AAD Application's Password credentials.")] - $AADAppPasswordCredential - - ) - - Begin - { - # Step 1: Set context to subscription where key vault needs to be created. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($SubscriptionId) - if(-not $currentContext) - { - return; - } - } - - Process - { - try - { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "Running Azure Tenant Security scanner secret storage setup...`n" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::KeyVaultSecretStoreSetupInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - # Step 2: Create resource group where KV resource will be created. - try - { - Write-Verbose "Checking resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) - $rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue - if(-not $rg) - { - Write-Verbose "Creating resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) - $rg = New-AzResourceGroup -Name $ResourceGroupName -Location $Location -ErrorAction Stop - } - else{ - Write-Verbose "Resource group already exists." #-ForegroundColor $([Constants]::MessageType.Info) - } - - } - catch - { - Write-Host "`n`rFailed to create resource group for deployment." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 3: Deploy KV - # Check if Key Vault already exist. - $keyVault = Get-AzKeyVault -ResourceGroupName $ResourceGroupName -VaultName $KeyVaultName -ErrorAction SilentlyContinue - - if ($keyVault -ne $null) - { - Write-Host "Key Vault already exist. All existing 'Access Policies' will be removed." -ForegroundColor $([Constants]::MessageType.Warning) - $userChoice = Read-Host -Prompt "`n`Do you want to continue (Y/N)?" - if ($userChoice -ne 'Y') - { - Write-Host "Please provide another name to create new Key Vault. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - # Else continue with deployment - } - - $secretName = "AzTSScannerIdentityCredentials" - $credentialSecureString = $AADAppPasswordCredential | ConvertTo-SecureString -AsPlainText -Force - - $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ResourceGroupName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() - - $validationResult = Test-AzResourceGroupDeployment -Mode Incremental ` - -ResourceGroupName $ResourceGroupName ` - -TemplateFile ".\AzTSKeyVaultTemplate.json" ` - -keyVaultName $KeyVaultName ` - -secretName $secretName ` - -secretValue $credentialSecureString ` - -ResourceHash $ResourceHash ` - -location $Location - if($validationResult) - { - Write-Host "`n`rTemplate deployment validation returned following errors:" -ForegroundColor $([Constants]::MessageType.Error) - $validationResult | FL Code, Message | Out-String | Out-Host; - return; - } - else - { - $deploymentName = "AzTSenvironmentkeyvaultsetup-$([datetime]::Now.ToString("yyyymmddThhmmss"))" - $deploymentOutput = New-AzResourceGroupDeployment -Name $deploymentName ` - -Mode Incremental ` - -ResourceGroupName $ResourceGroupName ` - -TemplateFile ".\AzTSKeyVaultTemplate.json" ` - -keyVaultName $KeyVaultName ` - -secretName $secretName ` - -secretValue $credentialSecureString ` - -ResourceHash $ResourceHash ` - -location $Location - - Write-Host "Completed Azure Tenant Security scanner secret storage setup." -ForegroundColor $([Constants]::MessageType.Info) - - Write-Host $([Constants]::SingleDashLine) - Write-Host ([constants]::KeyVaultSecretStoreSetupNextSteps) -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::DoubleDashLine) - - return $deploymentOutput; - } - - } - catch - { - Write-Host "Error occurred while setting up scanner secret storage. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - return; - } - } -} - -function Grant-AzSKAccessOnKeyVaultToUserAssignedIdentity -{ - Param - ( - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Key Vault exist.")] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Subscription id in which Key Vault exist.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Resource Id of existing Key Vault.")] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Resource Id of existing Key Vault.")] - $ResourceId, - - [string] - [ValidateNotNullOrEmpty()] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Object id of user managed identity.")] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Object id of user managed identity.")] - $UserAssignedIdentityObjectId, - - [string[]] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="User email Ids to whom monitoring alert mails should be sent.")] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="User email Ids to whom monitoring alert mails should be sent.")] - $SendAlertsToEmailIds, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] - $ScanIdentitySecretUri, - - [string] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] - $LAWorkspaceResourceId, - - [switch] - [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Switch to deploy alerts on top of Key Vault auditing logs.")] - $DeployMonitoringAlert - ) - - try { - - Write-Host $([Constants]::DoubleDashLine) - Write-Host "Granting access over Key Valut to MI..." -ForegroundColor $([Constants]::MessageType.Info) - # Validate input - # Check UserAssignedObject must be non null - if([string]::IsNullOrWhiteSpace($UserAssignedIdentityObjectId)) - { - Write-Host "Object Id of managed identity must not be null..." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - - #Set context to subscription where key vault exist - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($SubscriptionId) - if(-not $currentContext) - { - return; - } - - # Check if Key Vault Exist - $keyVault = Get-AzResource -ResourceId $ResourceId -ErrorAction SilentlyContinue - - if(-not $keyVault) - { - Write-Host "Unable to find any Key Vault with resourceId [$($ResourceId)] in subscription [$($SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Assigne Secret Get permission to MI - Set-AzKeyVaultAccessPolicy -ResourceId $ResourceId -ObjectId $UserAssignedIdentityObjectId -PermissionsToSecrets get - - Write-Host "Successfully granted access over Key Valut to MI." -ForegroundColor $([Constants]::MessageType.Update) - Write-Host $([Constants]::SingleDashLine) - if ($DeployMonitoringAlert -eq $true) - { - try{ - - Write-Host "Creating monitoring alerts..." -ForegroundColor $([Constants]::MessageType.Info) - $EmailReceivers = @() - $SendAlertsToEmailIds | ForEach-Object { - $EmailReceivers += New-AzActionGroupReceiver -Name "Notify_$($_)" -EmailReceiver -EmailAddress $_ - } - - $keyVaultRGName = $ResourceId.Split("/")[4] # ResourceId is in format - /subscriptions/SubIdGuid/resourceGroups/RGName/providers/Microsoft.KeyVault/vaults/KeyVaultName - $alertActionGroupForKV = Set-AzActionGroup -Name ‘AzTSAlertActionGroupForKV’ -ResourceGroupName $keyVaultRGName -ShortName ‘AzTSKVAlert’ -Receiver $EmailReceivers -WarningAction SilentlyContinue - - $deploymentName = "AzTSenvironmentmonitoringsetupforkv-$([datetime]::Now.ToString("yyyymmddThhmmss"))" - - $alertQuery = [string]::Format([Constants]::UnintendedSecretAccessAlertQuery, $ResourceId, $ScanIdentitySecretUri, $UserAssignedIdentityObjectId) - $deploymentOutput = New-AzResourceGroupDeployment -Name $deploymentName ` - -Mode Incremental ` - -ResourceGroupName $keyVaultRGName ` - -TemplateFile ".\KeyVaultMonitoringAlertTemplate.json" ` - -UnintendedSecretAccessAlertQuery $alertQuery ` - -ActionGroupId $alertActionGroupForKV.Id ` - -LAResourceId $laWorkspaceResourceId ` - -Location $keyVault.Location - - Write-Host "Completed monitoring alert setup." -ForegroundColor $([Constants]::MessageType.Update) - - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } - - } - - Write-Host $([Constants]::SingleDashLine) - Write-Host $([Constants]::DoubleDashLine) - - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } -} - -function Create-AzSKTenantSecuritySolutionMultiTenantScannerIdentity -{ - param ( - [string] - [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Display Name of the Scanner Identity.")] - $displayName, - - [string] - [Parameter(Mandatory = $true, ParameterSetName = "PreExistApp", HelpMessage="Object Id of the Scanner Identity.")] - $objectId, - - [string[]] - [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] - [Parameter(Mandatory = $false, ParameterSetName = "PreExistApp", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] - $AdditionalOwnerUPNs = @() - - ) - try - { - $appDetails = "" | Select-Object "ApplicationId", "ObjectId", "Secret" - if ([string]::IsNullOrWhiteSpace($objectId)) - { - Write-Host "Checking if Azure AD application [$($displayName)] already exist..." -ForegroundColor $([Constants]::MessageType.Info) - $aadApp = Get-AzureADApplication -SearchString $displayName - if (!$aadApp) { - - Write-Host "Creating new AD application [$($displayName)]..." -ForegroundColor $([Constants]::MessageType.Info) - # create new application - $aadApp = New-AzureADApplication -DisplayName $displayName -AvailableToOtherTenants $true - Write-Host "Created [$($displayName)] app successfully." -ForegroundColor $([Constants]::MessageType.Update) - } - elseif(($aadApp | Measure-Object).Count -gt 1) - { - Write-Host "Multiple AD application with display name [$($displayName)] exists in AAD.`n Either choose different name to create new AAD app or provide Object Id of App as input to use existing App." -ForegroundColor $([Constants]::MessageType.error) - return; - } - else - { - Write-Host "AD application [$($displayName)] already exists." -ForegroundColor $([Constants]::MessageType.Info) - } - - } - else - { - $aadApp = Get-AzureADApplication -ObjectId $objectId - - if(!$aadApp) - { - Write-Host "AD application with object Id [$($objectId)] not found in AAD." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - } - - # Create new password credential for App - $startDateTime = Get-Date - $endDateTime = $startDateTime.AddMonths(6) - $pwdCredentials = New-AzureADApplicationPasswordCredential -ObjectId $aadApp.ObjectId -StartDate $startDateTime -EndDate $endDateTime - - # Adding additional owners (if any) - if (($AdditionalOwnerUPNs| Measure-Object).Count -gt 0) - { - Add-OwnersToAADApplication -AppObjectId $aadApp.ObjectId -UserPrincipalNames $AdditionalOwnerUPNs - } - - # Prepare output object - $appDetails.ApplicationId = $aadApp.AppId - $appDetails.ObjectId = $aadApp.ObjectId - $appDetails.Secret = $pwdCredentials.Value - - return $appDetails - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } - -} - -function Create-AzSKTenantSecuritySolutionMultiTenantIdentitySPN -{ - param ( - [string] - [Parameter(Mandatory = $true, HelpMessage="unique identifier of the AAD application of which ServicePrincipal need to be created")] - $AppId - ) - try - { - - Write-Host "Checking if Azure AD service principal for App [$($AppId)] already exist..." -ForegroundColor $([Constants]::MessageType.Info) - $spn = Get-AzureADServicePrincipal -Filter "AppId eq '$($AppId)'" - if (!$spn) { - - Write-Host "Creating new Azure AD service principal for App [$($AppId)]..." -ForegroundColor $([Constants]::MessageType.Info) - # create new spn - $spn = New-AzureADServicePrincipal -AppId $AppId -AppRoleAssignmentRequired $false - Write-Host "Successfully created service principal." -ForegroundColor $([Constants]::MessageType.Info) - } - else - { - Write-Host "AD service principal for App [$($AppId)] already exists." -ForegroundColor $([Constants]::MessageType.Info) - } - - # return spn object ($spn.ObjectId) - return $spn - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } -} - -function Grant-AzSKGraphPermissionToMultiTenantScannerIdentity -{ - Param( - [string] - [ValidateNotNullOrEmpty()] - [Parameter(Mandatory = $true, HelpMessage="Object id of the identity used to scan subscriptions.")] - $AADIdentityObjectId, - - [string[]] - [Parameter(Mandatory = $true, HelpMessage="List of Microsoft Graph permission to be granted to the Identity")] - $MSGraphPermissionsRequired, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of Azure AD Graph permission to be granted to the Identity")] - $ADGraphPermissionsRequired - ) - - try { - - Write-Host "WARNING: To grant Graph API permission, the signed-in user must be a member of one of the following administrator roles: Global Administrator or Privileged Role Administrator." -ForegroundColor $([Constants]::MessageType.Warning) - - GrantMSGraphPermissionsToAADIdentity -AADIdentityObjectId $AADIdentityObjectId -MSGraphPermissionsRequired $MSGraphPermissionsRequired -ADGraphPermissionsRequired $ADGraphPermissionsRequired - } - catch - { - if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) - { - Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) - } - else - { - Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) - } - } -} - -function Grant-AzSKAzureRoleToMultiTenantIdentitySPN -{ - Param( - [string] - [ValidateNotNullOrEmpty()] - [Parameter(Mandatory = $true, HelpMessage="Object id of the identity used to scan subscriptions.")] - $AADIdentityObjectId, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of target subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] - [Alias("SubscriptionsToScan")] - $TargetSubscriptionIds = @(), - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] - [Alias("ManagementGroupsToScan")] - $TargetManagementGroupNames = @() - ) - - Write-Host "Granting 'Reader' permission to service principal on target scope(s)..." -ForegroundColor $([Constants]::MessageType.Info) - $targetSubscriptionCount = ($TargetSubscriptionIds | Measure-Object).Count - $targetMgtGroupCount = ($TargetManagementGroupNames | Measure-Object).Count - if($targetSubscriptionCount -gt 0) - { - # Set Azure Context for first random subscription in current tenant - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($TargetSubscriptionIds[0]) - if(-not $currentContext) - { - return; - } - - $TargetSubscriptionIds | % { - - try - { - Write-Host "Assigning 'Reader' access to service principal on target subscription [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) - New-AzRoleAssignment -ObjectId $AADIdentityObjectId -Scope "/subscriptions/$_" -RoleDefinitionName "Reader" -ErrorAction Stop - } - catch - { - if($_.Exception.Body.Code -eq "RoleAssignmentExists") - { - Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) - - } - else - { - Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) - - } - } - } - } - - if ($targetMgtGroupCount -gt 0) - { - $TargetManagementGroupNames | % { - - try - { - Write-Host "Assigning 'Reader' access to service principal on target management group [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) - New-AzRoleAssignment -ObjectId $AADIdentityObjectId -Scope "/providers/Microsoft.Management/managementGroups/$_" -RoleDefinitionName "Reader" -ErrorAction Stop - } - catch - { - if($_.Exception.Body.Code -eq "RoleAssignmentExists") - { - Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) - - } - else - { - Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) - - } - } - } - } - - if (-not(($targetSubscriptionCount -gt 0) -or ($targetMgtGroupCount -gt 0))) - { - Write-Host "No target subscription or management group specified." -ForegroundColor $([Constants]::MessageType.Warning) - } - else{ - Write-Host "Completed 'Reader' role assignment for service principal." -ForegroundColor $([Constants]::MessageType.Update) - } - -} - -function Add-AADApplicationOwners() -{ - Param( - [string] - [ValidateNotNullOrEmpty()] - [Parameter(Mandatory = $true, HelpMessage="Object id of the Azure AD Application.")] - $AppObjectId, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of owners to be added to an application.")] - $OwnerObjectIds = @() - ) - - $allOwnerAdded = $true - if (($OwnerObjectIds| Measure-Object).Count -gt 0) - { - $OwnerObjectIds | ForEach-Object{ - $objectId = $_ - try - { - Add-AzureADApplicationOwner -ObjectId $AppObjectId -RefObjectId $objectId - } - catch - { - $allOwnerAdded = $allOwnerAdded -and $false - Write-Host "Error occurred while adding owner [ObjectId: $($objectId)] to application . ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) - } - } - } - - return $allOwnerAdded; - -} - -function Get-AADUserDetails() -{ - Param( - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of owners to be added to an application.")] - $UserPrincipalNames = @() - ) - - $aadUsers = @(); - if (($UserPrincipalNames| Measure-Object).Count -gt 0) - { - $UserPrincipalNames | ForEach-Object{ - $aadUser = "" | Select-Object "UPN", "ObjectId" - $aadUser.UPN = $_; - $aadUser.ObjectId = [Constants]::AADUserNotFound - try - { - $user = Get-AzureADUser -ObjectId $_ - $aadUser.ObjectId = $user.ObjectId - } - catch - { - Write-Host "Error occurred while fetching AAD user [UPN: $($aadUser.UPN)]. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) - } - $aadUsers += $aadUser - } - } - - return $aadUsers; - -} - -function Add-OwnersToAADApplication() -{ - Param( - [string] - [ValidateNotNullOrEmpty()] - [Parameter(Mandatory = $true, HelpMessage="Object id of the Azure AD Application.")] - $AppObjectId, - - [string[]] - [Parameter(Mandatory = $false, HelpMessage="List of owners to be added to an application.")] - $UserPrincipalNames = @() - ) - - Write-Host "Adding additional owners to AAD Application [App ObjectId: $($AppObjectId)]" - - if (($UserPrincipalNames|Measure-Object).Count -gt 0) - { - $aadUsers = Get-AADUserDetails -UserPrincipalNames $UserPrincipalNames - $validAADUsers = $aadUsers | where-Object { $_.ObjectId -ne $([Constants]::UserNotFound)} - - if (($validAADUsers | Measure-Object).Count -gt 0) - { - $userObjectIds = $validAADUsers.ObjectId - $allOwnersAdded = Add-AADApplicationOwners -AppObjectId $AppObjectId -OwnerObjectIds $userObjectIds - - if ($allOwnersAdded) - { - Write-Host "Owners for application added successfully." -ForegroundColor $([Constants]::MessageType.Update) - } - else{ - Write-Host "One or more owners not added to application." -ForegroundColor $([Constants]::MessageType.Warning) - } - } - else{ - Write-Host "No valid users found." - } - } - else - { - Write-Host "UserPrincipalNames is empty. No owners added to application." - } -} - -function Enable-ByDesignExceptionFeature -{ - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription id in which AzTS is installed.")] - $HostSubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where AzTS is installed.")] - $HostResourceGroupName, - - [string[]] - [Parameter(Mandatory = $true, HelpMessage="Array of location containing primary and secondary location for cosmos DB.")] - $CosmosDBLocationArray, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription id in which AzTS KV is available/is to be created.")] - $KVSubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where AzTS KV is available/is to be created.")] - $KVResourceGroupName, - - [string] - [Parameter(Mandatory = $false, HelpMessage="Location where the resource group and key vault will get created. Default location is EastUS2.")] - $KVLocation = 'EastUS2', - - [string] - [Parameter(Mandatory = $false, HelpMessage="Name of the Key Vault which is to be used/is to be created.")] - $KeyVaultName - - ) - - Begin - { - # Step 1: Set context to subscription where AzTS is hosted. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($HostSubscriptionId) - if(-not $currentContext) - { - return; - } - } - - Process - { - try - { - Write-Host $([Constants]::DoubleDashLine) - Write-Host "Enabling By Design exception feature in Azure Tenant Security scanner...`n" -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::EnableByDesignExpInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) - Write-Host $([Constants]::SingleDashLine) - - $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $HostSubscriptionId,$HostResourceGroupName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() - - $InternalMI = $null - $CosmosDbConnString = $null - $CosmosConnStringSecret = $null - $AzTSUIAppPwdCrdential = $null - $AzTSUIAppPwdCrdentialSecret = $null - $AzTSAPI = $null - $NewAPIAppSettings = @{} - - - # Step 2: Validate AzTS host RG. - try - { - Write-Verbose "Validating the AzTS host resource group..." #-ForegroundColor $([Constants]::MessageType.Info) - # Step 2a: Validate AzTS host RG exists. - $rg = Get-AzResourceGroup -Name $HostResourceGroupName -ErrorAction SilentlyContinue - if(-not $rg) - { - Write-Host "`n`rAzTS host resource group does not exists." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - else - { - # Step 2a: Validate AzTS host RG have AzTS components. - $InternalMIName = "AzSK-AzTS-InternalMI-" + $ResourceHash - $InternalMI = Get-AzUserAssignedIdentity -ResourceGroupName $HostResourceGroupName -Name $InternalMIName -ErrorAction SilentlyContinue - if(-not $InternalMI) - { - Write-Host "`n`rAzTS host resource group does not contain AzTS components." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - else - { - Write-Verbose "AzTS host resource group validated successfully." #-ForegroundColor $([Constants]::MessageType.Info) - } - } - - } - catch - { - Write-Host "`n`rFailed to validate AzTS host resource group." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 3: Create cosmos db account and table for exception management. - try - { - Write-Verbose "Creating cosmos DB account and table for exception management if not already present..." - # Step 3a: Creating cosmos DB account if not already present. - $cosmosaccname = "aztscosmosdb" + $ResourceHash - Write-Verbose "Checking if cosmos DB account is already present..." - $cosmosacc = Get-AzCosmosDBAccount -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname -ErrorAction SilentlyContinue - if(-not $cosmosacc) - { - Write-Verbose "Cosmos DB account is not present..." - Write-Verbose "Creating Cosmos DB account..." - $cosmosacc = New-AzCosmosDBAccount -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname -ApiKind Table -EnableAutomaticFailover -Location $CosmosDBLocationArray -ErrorAction SilentlyContinue - } - - if(-not $cosmosacc) - { - throw [System.Exception] - } - Write-Verbose "Created Cosmos DB account successfully..." - - # Step 3b: Creating cosmos DB table for exception management if not already present. - Write-Verbose "Checking if cosmos DB table is already present..." - $exceptiontable = Get-AzCosmosDBTable -ResourceGroupName $HostResourceGroupName -AccountName $cosmosaccname -Name "Exceptions" -ErrorAction SilentlyContinue - if(-not $exceptiontable) - { - Write-Verbose "Cosmos DB table is not present..." - Write-Verbose "Creating Cosmos DB table..." - $exceptiontable = New-AzCosmosDBTable -ResourceGroupName $HostResourceGroupName -AccountName $cosmosaccname -Name "Exceptions" -AutoscaleMaxThroughput 1000 -ErrorAction SilentlyContinue - } - - if(-not $exceptiontable) - { - throw [System.Exception] - } - - Write-Verbose "Cosmos DB account and table created successfully." - - # Step 3c: Getting cosmos DB connection string. - Write-Verbose "Getting cosmos DB connection string..." - $connectionStrings = Get-AzCosmosDBAccountKey -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname -Type "ConnectionStrings" -ErrorAction SilentlyContinue - if(-not $connectionStrings) - { - Write-Host "`n`rFailed to get cosmos DB connection string." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - $CosmosDbConnString = $connectionStrings["Primary Table Connection String"] - Write-Verbose "Fetched cosmos DB connection string successfully." - } - catch - { - # Step 3c: Remove cosmos DB account if the creation fails. - $cosmosaccname = "aztscosmosdb" + $ResourceHash - Remove-AzCosmosDBAccount -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname - Write-Host "`n`rFailed to create cosmos DB for exception management." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 4: Create key vault if not already exists. - try - { - # Step 4a: Set context to subscription where KV is present/is to be created. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($KVSubscriptionId) - if(-not $currentContext) - { - throw [System.Exception] - } - - Write-Verbose "Checking if key vault's the resource group exists or not..." #-ForegroundColor $([Constants]::MessageType.Info) - # Step 4b: Check if KV's RG exists or not. - $rg = Get-AzResourceGroup -Name $KVResourceGroupName -ErrorAction SilentlyContinue - if(-not $rg) - { - Write-Verbose "Creating resource group for key vault..." #-ForegroundColor $([Constants]::MessageType.Info) - # Step 4c: Create RG for KV.. - $rg = New-AzResourceGroup -Name $KVResourceGroupName -Location $KVLocation -ErrorAction SilentlyContinue - if(-not $rg) - { - throw [System.Exception] - } - Write-Verbose "Key vault's resource group created successfully." - } - else - { - Write-Verbose "Key vault's resource group already exists..." - # Step 4d: Check if the KV already exists. - $kv = Get-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $KVResourceGroupName -ErrorAction SilentlyContinue - if(-not $kv) - { - Write-Verbose "Creating key vault..." - # Step 4e: Create KV. - $kv = New-AzKeyVault -Name $KeyVaultName -ResourceGroupName $KVResourceGroupName -Location $KVLocation -ErrorAction SilentlyContinue - if(-not $kv) - { - throw [System.Exception] - } - Write-Verbose "Key vault created successfully." - } - else - { - Write-Verbose "Key vault already exists..." - } - } - } - catch - { - # Step 4f: Failed to validate/create KV. - Write-Host "`n`rFailed to validate/create key vault." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 5: Update KV's access policy and store cosmos connection string as secret. - try - { - # Step 5a: Update KV's access policy so that current user is able to set and get secrets. - Write-Verbose "Updating key vault's access policy so that current user is able to set and get secrets..." - $context = Get-AzContext - Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -UserPrincipalName $context.Account.Id -PermissionsToSecrets get,set,list - - # Step 5b: Update KV's access policy so that internal MI is able to get secrets. - Write-Verbose "Updating key vault's access policy so that internal MI is able to get secrets..." - Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -PermissionsToSecrets get -ObjectId $InternalMI.PrincipalId - - # Step 5c: Storing cosmos DB connection string in KV as secret. - Write-Verbose "Storing cosmos DB connection string in Key vault as secret..." - $CosmosConnStringSecure = ConvertTo-SecureString $CosmosDbConnString -AsPlainText -Force - $CosmosConnStringSecret = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name "CosmosDBConnString" -SecretValue $CosmosConnStringSecure - - Write-Verbose "Successfully stored cosmos DB connection string in Key vault." - - } - catch - { - # Step 5d: Failed to store cosmos DB connection string. - Write-Host "`n`rFailed to store cosmos DB connection string in Key vault." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 6: Configure AzTS API to enable By Design exception feature. - try - { - # Step 6a: Set context to subscription where AzTS is hosted. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($HostSubscriptionId) - if(-not $currentContext) - { - throw [System.Exception] - } - - # Step 6b: Get AzTS API. - Write-Verbose "Getting AzTS API..." - $AzTSAPIName = "AzSK-AzTS-WebApi-" + $ResourceHash - $AzTSAPI = Get-AzWebApp -Name $AzTSAPIName -ResourceGroupName $HostResourceGroupName -ErrorAction SilentlyContinue - if(-not $AzTSAPI) - { - Write-Host "`n`rFailed to get AzTS API." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 6c: Update AzTS API's KV reference identity to user assigned identity. - Write-Verbose "Updating AzTS API's KV reference identity to user assigned identity..." - $userAssignedIdentityResourceId = $InternalMI.Id - $Path = "{0}?api-version=2021-01-01" -f $AzTSAPI.Id - Invoke-AzRestMethod -Method PATCH -Path $Path -Payload "{'properties':{'keyVaultReferenceIdentity':'$userAssignedIdentityResourceId'}}" - Write-Verbose "Successfully updated AzTS API's KV reference identity to user assigned identity." - - - # Step 6d: Update AzTS API's app settings. - Write-Verbose "Updating AzTS API's app settings..." - $cosmosConnStringAppSetting = "@Microsoft.KeyVault(SecretUri=" + $CosmosConnStringSecret.Id + ")" - $currentAPIAppSettings = $AzTSAPI.SiteConfig.AppSettings - - ForEach ($appSetting in $currentAPIAppSettings) { - $NewAPIAppSettings[$appSetting.Name] = $appSetting.Value - } - - $NewAPIAppSettings["AzureCosmosDBSettings__IsFeatureEnabled"] = "true" - $NewAPIAppSettings["AzureCosmosDBSettings__ConnectionString"] = $cosmosConnStringAppSetting - $NewAPIAppSettings["FeatureManagement__ExceptionLoggingFeature"] = "true" - $NewAPIAppSettings["UIConfigurations__ExceptionFeatureConfiguration__IsEnabled"] = "true" - $NewAPIAppSettings["UIConfigurations__ExceptionFeatureConfiguration__HelpPageUrl"] = "https://aka.ms/AzTS-Docs/Exception" - $NewAPIAppSettings["UIConfigurations__ExceptionFeatureConfiguration__WarningText"] = "When attesting controls, the highest level of discretion is required. Justification for each attested control is mandatory in order to document the rationale for bypassing the security control." - $NewAPIAppSettings.Remove("WEBSITE_DNS_SERVER") - - Set-AzWebApp -Name $AzTSAPIName -ResourceGroupName $HostResourceGroupName -AppSettings $NewAPIAppSettings - - Write-Verbose "Successfully updated AzTS API's app settings." - } - catch - { - # Step 6e: Failed to Configure AzTS API to enable By Design exception feature. - Write-Host "`n`rFailed to Configure AzTS API to enable By Design exception feature." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 7: Create password credential for AzTS UI AAD App. - try - { - # Step 7a: Get AzTS UI AAD App. - Write-Verbose "Getting AzTS UI AAD App..." - $aztsUIAADAppName = "AzSK-AzTS-UI-" + $ResourceHash - $aztsUIAADApp = Get-AzureADApplication -SearchString $aztsUIAADAppName -ErrorAction SilentlyContinue - if(-not $aztsUIAADApp) - { - Write-Host "`n`rFailed to get AzTS UI AAD App." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 7b: Create password credential for AzTS UI AAD App. - Write-Verbose "Creating password credential for AzTS UI AAD App..." - $startDateTime = Get-Date - $endDateTime = $startDateTime.AddMonths(6) - $pwdCredentials = New-AzureADApplicationPasswordCredential -ObjectId $aztsUIAADApp.ObjectId -StartDate $startDateTime -EndDate $endDateTime -ErrorAction SilentlyContinue - if(-not $pwdCredentials) - { - Write-Host "`n`rFailed to create password credential for AzTS UI AAD App." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - Write-Verbose "Successfully created password credential for AzTS UI AAD App." - - $AzTSUIAppPwdCrdential = $pwdCredentials.Value - } - catch - { - # Step 6e: Failed to Create password credential for AzTS UI AAD App. - Write-Host "`n`rFailed to Create password credential for AzTS UI AAD App." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 8: Store AzTS UI AAD App password credential in Key vault as secret. - try - { - # Step 8a: Set context to subscription where KV is present. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($KVSubscriptionId) - if(-not $currentContext) - { - throw [System.Exception] - } - - # Step 8b: Storing AzTS UI AAD App password credential in KV as secret. - Write-Verbose "Storing AzTS UI AAD App password credential in Key vault as secret..." - $AzTSUIAppPwdCrdentialSecure = ConvertTo-SecureString $AzTSUIAppPwdCrdential -AsPlainText -Force - $AzTSUIAppPwdCrdentialSecret = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name "APIClientSecret" -SecretValue $AzTSUIAppPwdCrdentialSecure - Write-Verbose "Successfully stored AzTS UI AAD App password credential in Key vault." - - } - catch - { - # Step 5d: Failed to store cosmos DB connection string. - Write-Host "`n`rFailed to store AzTS UI AAD App password credential in Key vault." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 9: Configure AzTS Scanner to enable By Design exception feature. - try - { - # Step 9a: Set context to subscription where AzTS is hosted. - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($HostSubscriptionId) - if(-not $currentContext) - { - throw [System.Exception] - } - - # Step 9b: Get AzTS Scanner. - Write-Verbose "Getting AzTS Scanner..." - $aztswipname = "AzSK-AzTS-WorkItemProcessor-" + $ResourceHash - $aztswip = Get-AzWebApp -Name $aztswipname -ResourceGroupName $HostResourceGroupName -ErrorAction SilentlyContinue - if(-not $aztswip) - { - Write-Host "`n`rFailed to get AzTS Scanner." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - # Step 9c: Update AzTS Scanner's KV reference identity to user assigned identity. - Write-Verbose "Updating AzTS Scanner's KV reference identity to user assigned identity..." - $userAssignedIdentityResourceId = $InternalMI.Id - $Path = "{0}?api-version=2021-01-01" -f $aztswip.Id - Invoke-AzRestMethod -Method PATCH -Path $Path -Payload "{'properties':{'keyVaultReferenceIdentity':'$userAssignedIdentityResourceId'}}" - Write-Verbose "Successfully updated AzTS Scanner's KV reference identity to user assigned identity." - - - # Step 9d: Update AzTS Scanner's app settings. - Write-Verbose "Updating AzTS API's app settings..." - $apiClientSecretAppSetting = "@Microsoft.KeyVault(SecretUri=" + $AzTSUIAppPwdCrdentialSecret.Id + ")" - $currentWIPAppSettings = $aztswip.SiteConfig.AppSettings - $newWIPAppSettings = @{} - ForEach ($appSetting in $currentWIPAppSettings) { - $newWIPAppSettings[$appSetting.Name] = $appSetting.Value - } - - $newWIPAppSettings["AzureCosmosDBSettings__IsFeatureEnabled"] = "true" - $newWIPAppSettings["APIClientConfiguration__ClientId"] = $NewAPIAppSettings["AADClientAppDetails__ApplicationId"] - $newWIPAppSettings["APIClientConfiguration__ClientSecret"] = $apiClientSecretAppSetting - $newWIPAppSettings["APIClientConfiguration__Scope"] = "api://" + $NewAPIAppSettings["AADClientAppDetails__ClientId"] + "/.default" - $newWIPAppSettings["APIClientConfiguration__ApiBaseUrl"] = "https://" + $AzTSAPI.DefaultHostName - - $context = Get-AzContext - $TenantId = $context.Tenant.Id - $newWIPAppSettings["AuthNSettings__TenantId"] = $TenantId - - $newWIPAppSettings.Remove("WEBSITE_DNS_SERVER") - - Set-AzWebApp -Name $aztswipname -ResourceGroupName $HostResourceGroupName -AppSettings $newWIPAppSettings - - Write-Verbose "Successfully updated AzTS Scanner's app settings." - } - catch - { - # Step 9e: Failed to Configure AzTS Scanner to enable By Design exception feature. - Write-Host "`n`rFailed to Configure AzTS Scanner to enable By Design exception feature." -ForegroundColor $([Constants]::MessageType.Error) - return; - } - - } - catch - { - Write-Host "Error occurred while enabling By Design Exception. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - return; - } - } +# Load all other scripts that are required by this script. +. "$PSScriptRoot\OnDemandScan.ps1" + +# Standard configuration +$AzureEnvironmentAppServiceURI = @{ + "AzureCloud" = "https://{0}.azurewebsites.net"; + "AzureGovernmentCloud" = "https://{0}.azurewebsites.us"; + "AzureChinaCloud" = "https://{0}.chinacloudsites.cn"; +} + +$AzureEnvironmentToADAuthUrlMap = @{ + "AzureCloud" = "https://login.microsoftonline.com"; + "AzureGovernmentCloud" = "https://login.microsoftonline.us"; + "AzureChinaCloud" = "https://login.microsoftonline.cn"; +} + +$AzureEnvironmentPortalURI = @{ + "AzureCloud" = "https://portal.azure.com/"; + "AzureGovernmentCloud" = "https://portal.azure.us/"; + "AzureChinaCloud" = "https://portal.azure.cn/"; +} + +function Install-AzSKTenantSecuritySolution +{ + <# + .SYNOPSIS + This command would help in installing Azure Tenant Security Solution in your subscription. + .DESCRIPTION + This command will install an Azure Tenant Security Solution which runs security scan on subscription in a Tenant. + Security scan results will be populated in Log Analytics workspace and Azure Storage account which is configured during installation. + + .PARAMETER SubscriptionId + Subscription id in which Azure Tenant Security Solution needs to be installed. + .PARAMETER ScanHostRGName + Name of ResourceGroup where setup resources will be created. + .PARAMETER Location + Location where all resources will get created. Default location is EastUS2. + .PARAMETER ScanIdentityId + Resource id of user managed identity used to scan subscriptions. + .PARAMETER TemplateFilePath + Azure ARM template path used to deploy Azure Tenant Security Solution. + .PARAMETER TemplateParameters + Azure ARM template parameters used to deploy Azure Tenant Security Solution. + .PARAMETER SendUsageTelemetry + Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features. + .PARAMETER CentralStorageAccountConnectionString + Connection string of the storage account to be used to store the scan logs centrally. + .NOTES + + + .LINK + https://aka.ms/azts-docs + + #> + Param( + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + $ScanHostRGName = "AzSK-AzTS-RG", + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] + [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Location where all resources will get created. Default location is EastUS2.")] + $Location, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Resource id of user managed identity used to scan subscriptions.")] + $ScanIdentityId, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Application Id of central scanning identity.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Application Id of central scanning identity.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Application Id of central scanning identity.")] + [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Application Id of central scanning identity.")] + $ScanIdentityApplicationId, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] + [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Key Vault SecretUri of the Scanner App's Credential.")] + $ScanIdentitySecretUri, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup")] + $TemplateFilePath = ".\AzTSDeploymentTemplate.json", + + [Hashtable] + [Parameter(Mandatory = $false, ParameterSetName = "Default")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup")] + $TemplateParameters = @{}, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Usage telemetry captures anonymous usage data and sends it to Microsoft servers. This will help in improving the product quality and prioritize meaningfully on the highly used features.")] + $SendUsageTelemetry = $false, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Specify if user managed identity has Graph permission. This is to exclude controls dependent on Graph API response from the scan result, if scanner identity does not have graph permission.")] + $ScanIdentityHasGraphPermission = $false, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Application (client) id of the Azure AD application to be used by API.")] + $WebAPIAzureADAppId, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Application (client) id of the Azure AD application to be used by UI.")] + $UIAzureADAppId, + + [string[]] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Email ids to which alert notification should be sent.")] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Email ids to which alert notification should be sent.")] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Email ids to which alert notification should be sent.")] + [Alias("SREEmailIds")] + [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Email ids to which alert notification should be sent.")] + $SendAlertNotificationToEmailIds = @(), + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] + $AzureEnvironmentName = "AzureCloud", + + [switch] + [Parameter(Mandatory = $false, HelpMessage="Switch to enable vnet integration. Resources required for vnet setup will be deployed only if this switch is ON.")] + $EnableVnetIntegration = $false, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + [Alias("EnableAutoUpdates")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Switch to enable AzTS auto updater. Autoupdater helps to get latest feature released for AzTS components covering updates for security controls. If this is disabled, you can manually update AzTS components by re-running setup command.")] + $EnableAutoUpdater, + + [switch] + [Parameter(Mandatory = $true, ParameterSetName = "AzTSUI", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] + [Parameter(Mandatory = $false, ParameterSetName = "MultiTenantSetup", HelpMessage="Switch to enable AzTS UI. AzTS UI is created to see compliance status for subscription owners and perform adhoc scan.")] + $EnableAzTSUI, + + [switch] + [Parameter(Mandatory = $false, HelpMessage="Switch to enable WAF. Resources required for implementing WAF will be deployed only if this switch is ON.")] + $EnableWAF = $false, + + [switch] + [Parameter(Mandatory = $true, ParameterSetName = "MultiTenantSetup", HelpMessage="Switch to enable multi-tenant scanning. Configurations required for multi tenant scanning will be deployed.")] + $EnableMultiTenantScan, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "CentralVisibility", HelpMessage="Connection string of the storage account to be used to store the scan logs centrally.")] + [Alias("StorageAccountConnectionString")] + $CentralStorageAccountConnectionString, + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] + [Parameter(Mandatory = $false, ParameterSetName = "AzTSUI", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] + [Parameter(Mandatory = $false, ParameterSetName = "CentralVisibility", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] + $ConsolidatedSetup = $false + ) + Begin + { + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($SubscriptionId) + if(-not $currentContext) + { + return; + } + } + + Process + { + $deploymentResult = $null; + $UIUrl = [string]::Empty; + $FunctionApps = $null; + $AppServiceSlots = @(); + + # flag to decide whether to prompt user for telemetry acceptance + [bool] $PromptUserAcceptance = $true + # Check if Auto Updater is already present. + try + { + [string] $OnboardingTenant = [String]::Empty; + [string] $OnboardingOrg = [String]::Empty; + [string] $OnboardingDiv = [String]::Empty; + [string] $OnboardingContactEmail = [String]::Empty; + [string] $AnonymousUsageTelemetryLogLevel = [String]::Empty; + + $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() + $AutoUpdaterName = "AzSK-AzTS-AutoUpdater-" + $ResourceHash + $au = Get-AzWebApp -Name $AutoUpdaterName -ResourceGroupName $ScanHostRGName -ErrorAction SilentlyContinue + #If Auto updater is not present then user will be prompted for telemetry acceptance. + if($au) + { + + $au.SiteConfig.AppSettings | foreach { + if($_.Name -eq "AIConfigurations__AnonymousUsageTelemetry__LogLevel") + { + $AnonymousUsageTelemetryLogLevel = $_.Value; + } + if($_.Name -eq "OnboardingDetails__Organization") + { + $OnboardingOrg = $_.Value; + } + if($_.Name -eq "OnboardingDetails__Division") + { + $OnboardingDiv = $_.Value; + } + if($_.Name -eq "OnboardingDetails__ContactEmailAddressList") + { + $OnboardingContactEmail = $_.Value; + } + if($_.Name -eq "OnboardingDetails__TenantId") + { + $OnboardingTenant = $_.Value; + } + } + + if([String]::IsNullOrWhiteSpace($AnonymousUsageTelemetryLogLevel)` + -or [String]::IsNullOrWhiteSpace($OnboardingOrg)` + -or [String]::IsNullOrWhiteSpace($OnboardingDiv)` + -or [String]::IsNullOrWhiteSpace($OnboardingContactEmail)` + -or [String]::IsNullOrWhiteSpace($OnboardingTenant)) + { + $PromptUserAcceptance = $true + } + else + { + $PromptUserAcceptance = $false + $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", $AnonymousUsageTelemetryLogLevel) + $TemplateParameters.Add("OrganizationName", $OnboardingOrg) + $TemplateParameters.Add("DivisionName", $OnboardingDiv) + $TemplateParameters.Add("ContactEmailAddressList", $OnboardingContactEmail) + $TemplateParameters.Add("HashedTenantId", $OnboardingTenant) + } + + } + } + catch + { + $PromptUserAcceptance = $true + } + + + try + { + if($PromptUserAcceptance) + { + # Take acceptance from the user for the telemetry to be collected + [string] $TelemetryAcceptanceMsg = "For the purpose of improving quality of AzTS features and better customer service, the AzTS solution needs to collect the below mentioned data :`r`n`n" + + " [1] Anonymized AzTS usage data -> this helps us improve product quality`r`n" + + " [2] Organization/team contact details -> these help us provide your team with:`r`n" + + " [a] Updates about AzTS feature change`r`n" + + " [b] Support channel options (e.g., office hours)`r`n" + + " [c] Occasional requests for feedback on specific features`r`n" + + "You may choose to opt in or opt out of either or both of these by choosing Y/N at the prompts coming up. (Note that you can change your choice later too.)`r`n" + + Write-Host $TelemetryAcceptanceMsg -ForegroundColor $([Constants]::MessageType.warning) + + + $AnonymousUsageCaptureFlag = Read-Host -Prompt "`n`rAllow collection of anonymized usage data (Y/N)" + $ContactDataCaptureFlag = Read-Host -Prompt "`n`Provide org/team contact info (Y/N)" + if($ContactDataCaptureFlag -eq 'Y') + { + # Capturing Onboarding details is enabled + if($AnonymousUsageCaptureFlag -eq 'Y') + { + # Capturing anonymous usage data is enabled + $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "All") + } + else + { + # Capturing anonymous usage data is disabled + $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "Onboarding") + } + + Write-Host "`n`rPlease provide details about your org, divison and team." -ForegroundColor $([Constants]::MessageType.warning) + $OrganizationName = Read-Host -Prompt "Organization Name " + $TemplateParameters.Add("OrganizationName", $OrganizationName) + $DivisionName = Read-Host -Prompt "Division Name within your Organization " + $TemplateParameters.Add("DivisionName", $DivisionName) + $ContactEmailAddressList = Read-Host -Prompt "Contact DL to use for our communication " + $TemplateParameters.Add("ContactEmailAddressList", $ContactEmailAddressList) + + } + else + { + # Capturing Onboarding details is disabled + if($AnonymousUsageCaptureFlag -eq 'Y') + { + # Capturing anonymous usage data is enabled + $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "Anonymous") + } + else + { + # Capturing anonymous usage data is disabled + $TemplateParameters.Add("AnonymousUsageTelemetryLogLevel", "None") + } + } + + Write-Host "`n`rThank you for your choices. To make changes to these preferences refer the FAQs by visiting https://aka.ms/AzTS-docs/UpdateTelemetryPreference." -ForegroundColor $([Constants]::MessageType.Update) + } + + } + catch + { + #silently continue with installation. + } + + if ($ConsolidatedSetup -ne $true) + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Running Azure Tenant Security Solution setup...`n" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::InstallSolutionInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + Write-Host "`r`nStarted setting up Azure Tenant Security Solution. This may take 5 mins..." -ForegroundColor $([Constants]::MessageType.Info) + } + + + + + # Create resource group if not exist + try + { + Write-Verbose "$(Get-TimeStamp)Checking resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) + $rg = Get-AzResourceGroup -Name $ScanHostRGName -ErrorAction SilentlyContinue + if(-not $rg) + { + Write-Verbose "$(Get-TimeStamp)Creating resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) + $rg = New-AzResourceGroup -Name $ScanHostRGName -Location $Location -ErrorAction Stop + } + else{ + Write-Verbose "$(Get-TimeStamp)Resource group already exists." #-ForegroundColor $([Constants]::MessageType.Info) + } + + } + catch + { + Write-Host "`n`rFailed to create resource group for deployment." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # start arm template deployment + try + { + + # Set EnableVnetIntegration value based on switch selected by user + if($EnableVnetIntegration) + { + $TemplateParameters.Add("EnableVnetIntegration", $true) + if (-not $TemplateParameters.ContainsKey("MetadataAggregatorFunctionTimeout")) + { + $TemplateParameters.Add("MetadataAggregatorFunctionTimeout", "00:45:00") + } + } + else + { + $TemplateParameters.Add("EnableVnetIntegration", $false) + } + + # Set EnableWAF value based on switch selected by user + if($EnableWAF) + { + $TemplateParameters.Add("EnableWAF", $true) + } + else + { + $TemplateParameters.Add("EnableWAF", $false) + } + + # set frontdoor and web app endpoint suffixf + if($AzureEnvironmentName -eq 'AzureGovernmentCloud') + { + $TemplateParameters.Add("FrontDoorEndpointSuffix", ".azurefd.us") + $TemplateParameters.Add("WebAppEndpointSuffix", ".azurewebsites.us") + } + elseif($AzureEnvironmentName -eq 'AzureChinaCloud') + { + $TemplateParameters.Add("FrontDoorEndpointSuffix", ".azurefd.cn") + $TemplateParameters.Add("WebAppEndpointSuffix", ".chinacloudsites.cn") + } + else + { + $TemplateParameters.Add("FrontDoorEndpointSuffix", ".azurefd.net") + $TemplateParameters.Add("WebAppEndpointSuffix", ".azurewebsites.net") + } + + # Select rule based on graph permission. + if($ScanIdentityHasGraphPermission) + { + $TemplateParameters.Add("RuleEngineWorkflowName", "FullTenantScan") + $TemplateParameters.Add("IsGraphFeatureEnabled", "true") + } + else + { + $TemplateParameters.Add("RuleEngineWorkflowName", "FullTenantScanExcludeGraph") + $TemplateParameters.Add("IsGraphFeatureEnabled", "false") + } + + # Set up multi-tenant scan config (if enabled) + $TemplateParameters.Add("ScannerIdentitySecretUri", $ScanIdentitySecretUri) + $TemplateParameters.Add("ScannerIdentityApplicationId", $ScanIdentityApplicationId) + if($EnableMultiTenantScan) + { + $TemplateParameters.Add("IsMultiTenantSetUp", $true) + # If multi-tenat mode is enabled then Scanner Connection Secret URI must not be null + if([string]::IsNullOrWhiteSpace($ScanIdentitySecretUri)) + { + Write-Host "`n`rValue for parameter '-ScanIdentitySecretUri' can not be null in multi tenant scan setup." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # If multi-tenat mode is enabled then ApplicationId of scanner identity must not be null + if([string]::IsNullOrWhiteSpace($ScanIdentityApplicationId)) + { + Write-Host "`n`rValue for parameter '-ScanIdentityApplicationId' can not be null in multi tenant scan setup." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + } + else + { + $TemplateParameters.Add("IsMultiTenantSetUp", $false) + } + + $TemplateParameters.Add("AzureEnvironmentName", $AzureEnvironmentName) + $TemplateParameters.Add("CentralStorageAccountConnectionString", $CentralStorageAccountConnectionString) + + # Get package version + + + $CentralPackageInfo = [CentralPackageInfo]::new() + + $TemplateParameters.Add("MetadataAggregatorPackageURL", $CentralPackageInfo.MetadataAggregatorPackageURL) + $TemplateParameters.Add("WorkItemProcessorPackageURL", $CentralPackageInfo.WorkItemProcessorPackageURL) + $TemplateParameters.Add("WebApiPackageURL", $CentralPackageInfo.WebApiPackageURL) + $TemplateParameters.Add("UIPackageURL", $CentralPackageInfo.UIPackageURL) + + # if($TemplateParameters.Count -eq 0) + # { + # Write-Host "`n`rPlease enter the parameter required for template deployment:" -ForegroundColor $([Constants]::MessageType.Info) + # Write-Host "Note: Alternatively you can use '-TemplateParameters' to pass these parameters.`n`r" -ForegroundColor $([Constants]::MessageType.Warning) + # } + $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() #considering only first 5 characters + $TelemetryIdentifier = $ResourceIdHash.Substring(0,16).ToString().ToLower() + + #adding ResourceHash to TemplateParameters + $TemplateParameters.Add("TelemetryIdentifier", $TelemetryIdentifier) + $TemplateParameters.Add("ResourceHash", $ResourceHash) + $TemplateParameters.Add("MIResourceId", $ScanIdentityId) + + #Enable autoupdater template parameter + if($EnableAutoUpdater) + { + $TemplateParameters.Add("IsAutoUpdaterEnabled", $true) + } + else + { + $TemplateParameters.Add("IsAutoUpdaterEnabled", $false) + } + + #Enable AzTSUI template parameter + if($EnableAzTSUI) + { + $TemplateParameters.Add("IsAzTSUIEnabled", $true) + } + else + { + $TemplateParameters.Add("IsAzTSUIEnabled", $false) + } + + #Get the tenant Id from the current subscription contex + $context=Get-AzContext + $TemplateParameters.Add("TenantId", $context.Tenant.Id) + # We also collect hashed TenantId as part of on boarding details + if($PromptUserAcceptance) + { + $HashedTenantId = get-hash($context.Tenant.Id) + $TemplateParameters.Add("HashedTenantId", $HashedTenantId) + } + + + # Creating Azure AD application: Web API + $TemplateParameters.Add("WebApiClientId", $WebAPIAzureADAppId) + $TemplateParameters.Add("UIClientId", $UIAzureADAppId) + $TemplateParameters.Add("AADClientAppDetailsInstance", $AzureEnvironmentToADAuthUrlMap.$AzureEnvironmentName) + $TemplateParameters.Add("AzureEnvironmentPortalURI", $AzureEnvironmentPortalURI.$AzureEnvironmentName) + + + #updating UI app settings for already existing UI, this will require deletion of pre-existing UI and re-deploying it with updated settings. + $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() + $UIName = "AzSK-AzTS-UI-" + $ResourceHash + #getting app setting details of UI if UI exists + $ui = Get-AzResource -Name $UIName -ResourceType "Microsoft.Web/sites" -ResourceGroupName $ScanHostRGName -ErrorAction SilentlyContinue + if($ui) + { + $webapp = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ui.Name -ErrorAction SilentlyContinue + if(($null -ne $webapp) -and (($webapp.SiteConfig.AppSettings.Name -contains "WEBSITE_CONTENTAZUREFILECONNECTIONSTRING") -or ($webapp.SiteConfig.AppSettings.Name -contains "WEBSITE_CONTENTSHARE")) ) + { + $UiDeletionwarningMsg = "[Warning]: Running installation command on an existing AzTS setup requires removing the current AzTS UI [$($UIName)] & redeploying it with updated the app settings." + + Write-Host $UiDeletionwarningMsg -ForegroundColor $([Constants]::MessageType.warning) + $UiDeletionFlag = Read-Host -Prompt "`n`rAllow removal of current AzTS UI Y/N" + if($UiDeletionFlag -eq 'Y') + { + Write-Host "Removing current AzTS UI [$($UIName)]. This will take 1-2 min.." -ForegroundColor Yellow + # delete UI slot + $UiSlotName = (Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $ui.Name).Name + $UiSlotName = $UiSlotName.split('/')[1] + $DeletedUIslot = Remove-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $ui.Name -Slot $UiSlotName -Force + #delete UI + $DeletedUi = Remove-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ui.Name -Force + Write-Host "Removed existing AzTS UI [$($ui.Name)].`nContinuing with re-deployment of AzTS UI with updated app settings..." -ForegroundColor Yellow + } + else + { + Write-Host "Terminating Azure Tenant Security Solution setup..." -ForegroundColor Cyan + Write-Host $([ScannerConstants]::DoubleDashLine) + break; + } + } + } + + # Stop existing app services to unlock files; If any file is locked, deployment will fail + $AppServices = Get-AzWebApp -ResourceGroupName $ScanHostRGName + + if($AppServices -ne $null -and $AppServices.Count-gt 0) + { + $FunctionApps = $AppServices | Where-Object { $_.Kind -eq 'functionapp'} + $AppServices | ForEach-Object { $AppServiceSlots += Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $_.Name } + } + + # Stop function apps + if($FunctionApps -ne $null -and $FunctionApps.Count-gt 0) + { + Write-Verbose "$(Get-TimeStamp)Stopping function app(s) for deployment. This is required to unblock any file in use..." + $FunctionApps | Stop-AzWebApp + Write-Verbose "$(Get-TimeStamp)Stopped function app(s): $([string]::Join(", ", ($FunctionApps | Select Name -Unique).Name))" + + } + + # Start deployment slot + if($AppServiceSlots -ne $null -and $AppServiceSlots.Count-gt 0) + { + Write-Verbose "$(Get-TimeStamp)Starting app service slot for deployment. This is required as an inactive slot cannot be updated." + $AppServiceSlots | Start-AzWebAppSlot + Write-Verbose "$(Get-TimeStamp)Started app service slot(s): $([string]::Join(", ", ($AppServiceSlots | Select Name -Unique).Name))" + + } + + Write-Verbose "$(Get-TimeStamp)Checking resource deployment template..." #-ForegroundColor $([Constants]::MessageType.Info) + + $validationResult = Test-AzResourceGroupDeployment -Mode Incremental -ResourceGroupName $ScanHostRGName -TemplateFile $TemplateFilePath -TemplateParameterObject $TemplateParameters + if($validationResult) + { + Write-Host "`n`rTemplate deployment validation returned following errors:" -ForegroundColor $([Constants]::MessageType.Error) + $validationResult | FL Code, Message | Out-String | Out-Host; + return; + } + else + { + # Deploy template + $deploymentName = "AzTSenvironmentsetup-$([datetime]::Now.ToString("yyyymmddThhmmss"))" + $deploymentResult = New-AzResourceGroupDeployment -Name $deploymentName -Mode Incremental -ResourceGroupName $ScanHostRGName -TemplateFile $TemplateFilePath -TemplateParameterObject $TemplateParameters -ErrorAction Stop -verbose + Write-Verbose "$(Get-TimeStamp)Completed resources deployment for azure tenant security solution." + + #Update App registered in AAD + #Web App + + # update the re-direct uri of azts ui app if WAF is enabled + if($EnableWAF -and $deploymentResult.Outputs.ContainsKey('azTSUIFrontDoorUrl') -and $deploymentResult.Outputs.ContainsKey('uiAppName') ) + { + $AzTSUIFrontDoorUrl = $deploymentResult.Outputs.azTSUIFrontDoorUrl.Value + $UIAzureADAppName = $deploymentResult.Outputs.uiAppName.Value + $replyUris = New-Object Collections.Generic.List[string] + $replyUris.Add(($AzTSUIFrontDoorUrl)); + $replyUris.Add($([string]::Join("/", $([string]::Format($AzTSUIFrontDoorUrl)), "auth.html"))); + $replyUris.Add(($AzureEnvironmentAppServiceURI.$AzureEnvironmentName -f $UIAzureADAppName)); + $replyUris.Add($([string]::Join("/", $([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $UIAzureADAppName)), "auth.html"))); + + $webUIApp = Get-AzureADApplication -Filter "AppId eq '$UIAzureADAppId'" + + Set-AzureADApplication -ObjectId $webUIApp.ObjectId -ReplyUrls $replyUris + } + + if($EnableAzTSUI -and $deploymentResult.Outputs.ContainsKey('uiAppName') -and $deploymentResult.Outputs.ContainsKey('webApiName')) + { + $azureUIAppName= $DeploymentResult.Outputs.uiAppName.Value + $azureWebApiName= $DeploymentResult.Outputs.webApiName.Value + $UIUrl = $([string]::Concat($([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $azureUIAppName)), "/")) + + # assigning value of azts api uri based on whether WAF is anabled or not. + if($EnableWAF -and $deploymentResult.Outputs.ContainsKey('azTSAPIFrontDoorUrl')) + { + $AzTSAPIFrontDoorUrl = $deploymentResult.Outputs.azTSAPIFrontDoorUrl.Value + $apiUri = $AzTSAPIFrontDoorUrl + } + else + { + $apiUri = $([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $azureWebApiName)) + } + + # Load all other scripts that are required by this script. + . "$PSScriptRoot\ConfigureWebUI.ps1" + + $InstrumentationKey = $DeploymentResult.Outputs.applicationInsightsIKey.Value + + Configure-WebUI -TenantId $context.Tenant.Id -ScanHostRGName $ScanHostRGName -UIAppName $azureUIAppName -ApiUrl $apiUri -UIClientId $UIAzureADAppId -WebApiClientId $WebAPIAzureADAppId -AzureEnvironmentName $AzureEnvironmentName -InstrumentationKey $InstrumentationKey + } + + # Custom event is required to Autoupdater setup event to application insight + if($EnableAutoUpdater) + { + SendCustomAIEvent -DeploymentResult $deploymentResult -TelemetryIdentifier $TelemetryIdentifier + } + # Applying access restriction to UI and API, if Enable WAF is turned 'ON'. We are applying access restriction from script because front door id is required here which cannot be determined at the time of template deployment. + if($EnableWAF -and $deploymentResult.Outputs.ContainsKey('apiFrontDoorName') -and $deploymentResult.Outputs.ContainsKey('uiFrontDoorName')) + { + $UIFrontDoorName = $deploymentResult.Outputs.uiFrontDoorName.Value + $APIFrntDoorName = $deploymentResult.Outputs.apiFrontDoorName.Value + $UIFrontDoor = Get-AzFrontDoor -ResourceGroupName $ScanHostRGName -Name $UIFrontDoorName + $APIFrontDoor = Get-AzFrontDoor -ResourceGroupName $ScanHostRGName -Name $APIFrntDoorName + + $UiAccessRestriction = Get-AzWebAppAccessRestrictionConfig -ResourceGroupName $ScanHostRGName -Name $azureUIAppName + $ApiAccessRestriction = Get-AzWebAppAccessRestrictionConfig -ResourceGroupName $ScanHostRGName -Name $azureWebApiName + + # applying restriction on UI + if($UiAccessRestriction.MainSiteAccessRestrictions.RuleName -notcontains 'AllowAccessFromFrontDoor') + { + Add-AzWebAppAccessRestrictionRule -ResourceGroupName $ScanHostRGName -WebAppName $azureUIAppName -Name "AllowAccessFromFrontDoor" -Priority 100 -Action Allow -ServiceTag AzureFrontDoor.Backend -HttpHeader @{'x-azure-fdid' = $UIFrontDoor.FrontDoorId} + } + # applying restriction on API + if($ApiAccessRestriction.MainSiteAccessRestrictions.RuleName -notcontains 'AllowAccessFromFrontDoor') + { + Add-AzWebAppAccessRestrictionRule -ResourceGroupName $ScanHostRGName -WebAppName $azureWebApiName -Name "AllowAccessFromFrontDoor" -Priority 100 -Action Allow -ServiceTag AzureFrontDoor.Backend -HttpHeader @{'x-azure-fdid' = $APIFrontDoor.FrontDoorId} + } + } + + } + } + catch + { + Write-Host "`rTemplate deployment returned following errors: [$($_)]." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + finally + { + # Start app services if it was stopped before deployment + if($FunctionApps -ne $null -and $FunctionApps.Count -gt 0) + { + Write-Verbose "$(Get-TimeStamp)Starting function app(s)..." + $FunctionApps | Start-AzWebApp + Write-Verbose "$(Get-TimeStamp)Started function app(s): $([string]::Join(", ", ($FunctionApps | Select Name -Unique).Name))" + } + + # Stop deployment slot + if($EnableAzTSUI -and $AppServiceSlots -ne $null -and $AppServiceSlots.Count-gt 0) + { + Write-Verbose "$(Get-TimeStamp)Stopping app service slot after updating the slot. This is required as an inactive slot cannot be updated." + $AppServiceSlots | Stop-AzWebAppSlot + Write-Verbose "$(Get-TimeStamp)Stopped app service slot(s): $([string]::Join(", ", ($AppServiceSlots | Select Name -Unique).Name))" + + } + } + # Post deployment steps + Write-Verbose "$(Get-TimeStamp)Starting post deployment environment steps.." + try + { + # Check if queue exist; else create new queue + $storageAccountName = [string]::Empty; + $storageQueueName = [string]::Empty; + Write-Verbose "$(Get-TimeStamp)Creating Storage queue to queue the subscriptions for scan.." #-ForegroundColor $([Constants]::MessageType.Info) + if( $deploymentResult.Outputs.ContainsKey('storageId') -and $deploymentResult.Outputs.ContainsKey('storageQueueName')) + { + $storageAccountName = $deploymentResult.Outputs.storageId.Value.Split("/")[-1] + $storageQueueName = $deploymentResult.Outputs.storageQueueName.Value + + # Create a queue in central storage account + try + { + Write-Verbose "$(Get-TimeStamp)Creating Storage queue in central storage account. This queue will be used to request subscription scan." + if(![string]::IsNullOrWhiteSpace($CentralStorageAccountConnectionString)) + { + $storageContext = New-AzStorageContext -ConnectionString $CentralStorageAccountConnectionString -ErrorAction Stop + $storageQueue = Get-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction SilentlyContinue + if(-not $storageQueue) + { + $storageQueue = New-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction Stop + } + } + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + Write-Host "Failed to create storage queue [$($storageQueueName)] in central storage account. You can create this queue directly from portal with the name [$($storageQueueName)]. For steps to create a queue, please refer https://docs.microsoft.com/en-us/azure/storage/queues/storage-quickstart-queues-portal#create-a-queue.`n`nPlease note that central storage repository feature is currently not supported if your central storage account has network restrictions. In this case, you will have to switch to the standalone mode by running this installation command again without '-CentralStorageAccountConnectionString' parameter." -ForegroundColor $([Constants]::MessageType.Error) + } + + + #fetching storage network settings + if ($EnableVnetIntegration) + { + $StorageNetworkSetting = (Get-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -AccountName $storageAccountName).DefaultAction + + if ($StorageNetworkSetting -eq 'Deny') + { + # Changing storage network setting to allow "All networks", in order to fetch queue details + # This is required as storage with network restriction will not be accessible from user's machine and in such case fetching queue details will result into error + Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -Name $storageAccountName -Bypass AzureServices -DefaultAction Allow + # wait for 30 sec for the settings to get updated on storage account + Start-Sleep -Seconds 30 + } + } + + $storageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $ScanHostRGName -Name $storageAccountName -ErrorAction Stop + if(-not $storageAccountKey) + { + throw [System.ArgumentException] ("Unable to fetch 'storageAccountKey'. Please check if you have the access to read storage key."); + } + else + { + $storageAccountKey = $storageAccountKey.Value[0] + } + + $storageContext = New-AzStorageContext -StorageAccountName $storageAccountName -StorageAccountKey $storageAccountKey -ErrorAction Stop + $storageQueue = Get-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction SilentlyContinue + if(-not $storageQueue) + { + $storageQueue = New-AzStorageQueue -Name $storageQueueName -Context $storageContext -ErrorAction Stop + } + + if($EnableVnetIntegration) + { + # Setting storage network settings back to "restricted networks" + Update-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -Name $storageAccountName -Bypass AzureServices -DefaultAction Deny + } + } + else + { + Write-Host "Failed to create Storage queue." -ForegroundColor $([Constants]::MessageType.Error) + return + } + + # Adding virtual network rules to storage, to configure service endpoints + # Virtual network rules are not added from template, as all function apps have dependency on storage, so storage gets created first and network rules are applied to it. But once network is restricted, function app cannot communicate with storage to get values like - connection string as they are still not a part of vnet and hence the deployment fails. + if($EnableVnetIntegration) + { + $subnetId = @(); + $subnetTobeAdded = @(); + + if($deploymentResult.Outputs.ContainsKey('vnet')) + { + $vnetName = $deploymentResult.Outputs.vnet.Value + + #configuring virtual ntwork rules + $subnetId += Get-AzVirtualNetwork -ResourceGroupName $ScanHostRGName -Name $vnetName | Get-AzVirtualNetworkSubnetConfig + $subnetTobeAdded += $subnetId | Where-Object { $_.Name -notmatch "PrivateEndpointSubnet"} + $subnetTobeAdded | ForEach-Object { + Add-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -Name $storageAccountName -VirtualNetworkResourceId $_.Id + } + + } + } + + + # Set monitoring alert + Set-AzTSMonitoringAlert -DeploymentResult $deploymentResult ` + -SendAlertNotificationToEmailIds $SendAlertNotificationToEmailIds ` + -Location $Location ` + -ScanHostRGName $ScanHostRGName ` + -TelemetryIdentifier $TelemetryIdentifier ` + -IsAutoUpdaterEnabled $EnableAutoUpdater + + if ($ConsolidatedSetup -ne $true) + { + Write-Host "`rCompleted installation for Azure Tenant Security Solution." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "$([Constants]::DoubleDashLine)" #-ForegroundColor $([Constants]::MessageType.Info) + if($EnableAzTSUI -and $EnableWAF) + { + Write-Host "$([Constants]::NextSteps -f $AzTSUIFrontDoorUrl)" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($AzTSUIFrontDoorUrl)" -ForegroundColor $([Constants]::MessageType.Warning) + } + elseif($EnableAzTSUI) + { + Write-Host "$([Constants]::NextSteps -f $UIUrl)" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host "IMPORTANT: AzTS UI will be available only after completing 'step a' listed under Next steps. AzTS UI URL for your tenant: $($UIUrl)" -ForegroundColor $([Constants]::MessageType.Warning) + } + else + { + Write-Host "$([Constants]::NextSteps -f $UIUrl)" -ForegroundColor $([Constants]::MessageType.Info) + } + Write-Host "$([Constants]::DoubleDashLine)" + } + else + { + Write-Host "`rCompleted installation for Azure Tenant Security Solution." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host "$([Constants]::SingleDashLine)" + } + + } + catch + { + Write-Host "Error occurred while executing post deployment steps. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + } + + return $deploymentResult + } +} + +class Constants +{ + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [string] $InstallSolutionInstructionMsg = "This command will perform 5 important operations. It will:`r`n`n" + + " [1] Create resources needed to support Azure Tenant Security scan `r`n" + + " [2] Deploy AzTS packages to azure function app `r`n" + + " [3] Deploy UI and API packages to respective azure web service apps `r`n" + + " [4] Schedule daily subscription scan `r`n" + + " [5] Setup monitoring alerts `r`n`n" + + "More details about resources created can be found in the link: http://aka.ms/DevOpsKit/TenantSecuritySetup `r`n" + + static [string] $QuickInstallSolutionInstructionMsg = "This command will perform following major steps. It will:`r`n`n" + + " [0] Validate and install required Az modules (Optional) `r`n" + + " [1] Setup central scanning managed identity `r`n" + + " [2] Create Azure AD application for secure authentication (Optional)`r`n" + + " [3] Setup infra resources and schedule daily security control scan on target subscriptions `r`n" + + static [string] $DoubleDashLine = "================================================================================" + static [string] $SingleDashLine = "--------------------------------------------------------------------------------" + + static [string] $NextSteps = "** Next steps **`r`n" + + " a) Azure Tenant security scan will start on scheduled time (UTC 01:00).`r`n" + + " ** OR **`r`n"+ + " You can trigger scan using 'Start-AzSKTenantSecuritySolutionOnDemandScan -SubscriptionId -ScanHostRGName ' command.`r`n"+ + " b) After scan completion, all security control results will be sent to LA workspace and Storage account present in your hosting subscription scan RG.`r`n" + + " c) Using the AzTS UI ({0}) you can monitor all the compliance detail of the controls for all the subscriptions which were scanned through 'step a'. or you can trigger new adhoc scan for subscriptions of your choice through UI itself and monitor new compliance details.`r`n"+ + " d) You can create compliance monitoring Power BI dashboard using link: http://aka.ms/DevOpsKit/TenantSecurityDashboard.`r`n" + + "`r`nFor any feedback contact us at: azsksup@microsoft.com.`r`n" + + static [string] $AADUserNotFound = "UserNotFound"; + + static [string] $AzureADAppSetupInstructionMsg = "This command will perform 5 important operations. It will:`r`n`n" + + " [1] Create Azure AD application for UI, if it does not exist. `r`n" + + " [2] Create Azure AD application for API, if it does not exist. `r`n" + + " [3] Update UI AD application redirection URL. `r`n" + + " [4] Grant AD applications permission to request OAuth2.0 implicit flow access tokens. This is required for browser-based apps. `r`n" + + " [5] Grant 'User.Read' permission to UI AD application. This permission is used to read logged in user's details such as name, email, and photo."; + + static [string] $AzureADAppSetupNextSteps = "** Next steps **`r`n" + + "Use Install-AzSKTenantSecuritySolution command to complete the setup. `r`n" + + "The AD application (client) ids listed below needs to be passed as input parameters to the installation command: `r`n" + + " [1] WebAPIAzureADAppId: {0} `r`n" + + " [2] UIAzureADAppId: {1} `r`n"; + + static [string] $ScanningIdentitySetupInstructionMsg = "This command will perform 2 important operations. It will:`r`n`n" + + " [1] Create user-assigned managed identity which will be used for centrally scanning your subscriptions. `r`n" + + " [2] Assign 'Reader' access to user-assigned managed identity on target subscription(s) that needs to be scanned. `r`n"; + + static [string] $ScanningIdentitySetupNextSteps = "** Next steps **`r`n" + + "Use Grant-AzSKGraphPermissionToUserAssignedIdentity command to grant graph permission to this scanner identity. This permission will be required to read data in your organization's directory such as Privileged Identity Management (PIM), users, groups and apps details.`r`n"; + + static [string] $KeyVaultSecretStoreSetupInstructionMsg = "This command will perform following important operations. It will:`r`n`n" + + " [1] Create a resource group (if not already exist) for Key vault. `r`n" + + " [2] Create a Key vault (if not already exist) to store Scanner Identity (App) credentials. `r`n" + + " [3] Enable Audit Event logging for the Key Vault. `r`n" + + " [4] Store Scanner App credentials in Key Vault as secret. `r`n"; + + static [string] $InternalIdentitySetupNextSteps = "** Next steps **`r`n" + + "Use Grant-AzSKGraphPermissionToUserAssignedIdentity command to grant graph permission to Internal MI identity. Internal MI is also used by AzTS UI to read the list of security groups that the user is a member of. For this purpose, internal MI requires 'User.Read.All' permission.`r`n"; + + static [string] $KeyVaultSecretStoreSetupNextSteps = "** Next steps **`r`n" + + "Use Grant-AzSKAccessOnKeyVaultToUserAssignedIdentity command to grant internal scanner identity access over secret stored in Key Vault. This permission will be required to read scanner identity credentials for scanning. This step need to be performed once AzTS solution installtion (after you have successfully run the command 'Install-AzSKTenantSecuritySolution') is done.`r`n"; + + static [string] $EnableByDesignExpInstructionMsg = "This command will perform following important operations. It will:`r`n`n" + + " [1] Create cosmos DB account and table for exception management. `r`n" + + " [2] Create a Key vault (if not already exist). `r`n" + + " [3] Store cosmos DB connection string in Key vault as secret. `r`n" + + " [4] Grant AzTS internal MI access to get secrets from Key vault. `r`n" + + " [5] Update AzTS API to use user assigned identity (internal MI) for KV reference. `r`n" + + " [6] Update AzTS API app settings to enable exception feature. `r`n" + + " [7] Create password credential for AzTS UI AAD app. `r`n" + + " [8] Store AzTS UI AAD app password credential in Key vault as secret. `r`n" + + " [9] Update AzTS Scanner to use user assigned identity (internal MI) for KV reference. `r`n" + + " [10] Update AzTS Scanner app settings to enable exception feature. `r`n" + + "This setup will take approximately 10 mins. `r`n`n"; + + static [string] $AutoUpdaterFailureAlertQuery = "customEvents + | where name == 'AzTS_Service_AutoUpdateOverallProgressTracker' + | where customDimensions.Id == '{0}' + | where timestamp > ago(3d) + | extend isOverallSuccess = tostring(customDimensions.isOverallSuccess) + | extend EventType = tostring(customDimensions.EventType) + | where EventType =~ 'Completed' + | summarize arg_max(timestamp, *) + | project EventType, isOverallSuccess + | where isOverallSuccess == true"; + + + static [string] $NewReleaseAlertQuery = "// Get currently installed version for an app service. + customEvents + | where name == 'AzTS_Service_AutoUpdateVersionTracker' + | where customDimensions.Id == '{0}' + | where timestamp > ago(2d) + | extend FeatureName = tolower(tostring(customDimensions.FeatureName)) + | extend AppName = tolower(tostring(customDimensions.AppName)) + | extend Status= tostring(customDimensions.Status), CurrentVersion= tostring(customDimensions.CurrentVersion) + | where FeatureName != dynamic(null) and AppName != dynamic(null) + | where Status =~ 'Succeeded' or Status =~ 'AlreadyUpToDate' + | summarize arg_max(timestamp, LatestRecord = CurrentVersion), arg_min(timestamp, PrevRecord = CurrentVersion) by FeatureName, AppName + | where LatestRecord != PrevRecord + | project FeatureName, AppName, UpgradedFrom = PrevRecord , UpgradedTo = LatestRecord" + + static [string] $SubscriptionInvRefreshFailureQuery = "let TablePlaceholder = view () {print SubscriptionId = 'SubscriptionIdNotFound'}; + let SubInventory_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_SubInventory_CL | where TimeGenerated > ago(24h) + | distinct SubscriptionId + )) + | where SubscriptionId !~ 'SubscriptionIdNotFound'; + SubInventory_CL"; + static [string] $BaselineControlsInvRefreshFailureQuery = "let TablePlaceholder = view () {print ControlId_s = 'NA'}; + let BaselineControlsInv_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_BaselineControlsInv_CL | where TimeGenerated > ago(24h) + | distinct ControlId_s + )) + | where ControlId_s !~ 'NA'; + BaselineControlsInv_CL"; + static [string] $RBACInvRefreshFailureQuery = "let TablePlaceholder = view () {print NameId = 'NA', RoleId = 'NA'}; + let RBAC_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_RBAC_CL | where TimeGenerated > ago(24h) + | take 10 + )) + | where NameId !~ 'NA'; + RBAC_CL"; + static [string] $ControlResultsRefreshFailureQuery = "let TablePlaceholder = view () {print SubscriptionId = 'SubscriptionIdNotFound'}; + let ControlResults_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_ControlResults_CL | where TimeGenerated > ago(24h) + | distinct SubscriptionId + )) + | where SubscriptionId !~ 'SubscriptionIdNotFound'; + ControlResults_CL + | take 10"; + + static [string] $ScanProgressSummaryQuery = "let TablePlaceholder = view () { print SubscriptionId = 'SubscriptionIdNotFound' }; + let ProcessedSubscriptions_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_ProcessedSubscriptions_CL + | where TimeGenerated > ago(2d) + | where JobId_d == toint(format_datetime(now(), 'yyyyMMdd')) + | where EventType_s == 'Completed' and OverallProcessCompleted_b == true + | distinct SubscriptionId + )) + | where SubscriptionId <> 'SubscriptionIdNotFound'; + let SubInventory_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_SubInventory_CL + | where TimeGenerated > ago(2d) + | where JobId_d == toint(format_datetime(now(), 'yyyyMMdd')) + | where State_s != 'Disabled' + | distinct SubscriptionId + )) + | where SubscriptionId <> 'SubscriptionIdNotFound'; + SubInventory_CL + | project SubscriptionId + | join kind= leftouter + ( + ProcessedSubscriptions_CL + ) + on SubscriptionId + | extend Type = iff(SubscriptionId1 != dynamic(null), 'Completed', 'NotCompleted') + | summarize count() by Type"; + + static [string] $UnintendedSecretAccessAlertQuery =" + let TablePlaceholder = view () {{ print IdentityObjectId = 'NA', Count = '0' }}; + let secretAccessEvent = union isfuzzy=true TablePlaceholder, (union ( + AzureDiagnostics + | where ResourceId =~ '{0}' + | where OperationName =~ 'SecretGet' + | where requestUri_s contains '{1}' + | where isnotempty(identity_claim_oid_g) and identity_claim_oid_g !~ '{2}' + | summarize Count = count() by IdentityObjectId = tostring(identity_claim_oid_g))) + | where IdentityObjectId <> 'NA'; + secretAccessEvent" + +} + +function Set-AzTSMonitoringAlert +{ + param ( + [PSObject] $DeploymentResult, + [string[]] $SendAlertNotificationToEmailIds, + [string] $Location, + [string] $ScanHostRGName, + [string] $TelemetryIdentifier, + [bool] $IsAutoUpdaterEnabled + ) + + try + { + Write-Verbose "$(Get-TimeStamp)Creating monitoring alerts..." + + $EmailReceivers = @() + $SendAlertNotificationToEmailIds | ForEach-Object { + $EmailReceivers += New-AzActionGroupReceiver -Name "Notify_$($_)" -EmailReceiver -EmailAddress $_ + } + + $alertActionGroup = Set-AzActionGroup -Name ‘AzTSAlertActionGroup’ -ResourceGroupName $ScanHostRGName -ShortName ‘AzTSAlert’ -Receiver $EmailReceivers -WarningAction SilentlyContinue + + + if($DeploymentResult.Outputs.ContainsKey('logAnalyticsResourceId') -and $DeploymentResult.Outputs.ContainsKey('applicationInsightsId')) + { + $LADataSourceId = $DeploymentResult.Outputs.logAnalyticsResourceId.Value + $AIDataSourceId = $DeploymentResult.Outputs.applicationInsightsId.Value + + $deploymentName = "AzTSenvironmentmonitoringsetup-$([datetime]::Now.ToString("yyyymmddThhmmss"))" + + New-AzResourceGroupDeployment -Name $deploymentName ` + -Mode Incremental ` + -ResourceGroupName $ScanHostRGName ` + -TemplateFile ".\MonitoringAlertTemplate.json" ` + -AutoUpdaterFailureAlertQuery ([string]::Format([Constants]::AutoUpdaterFailureAlertQuery, $TelemetryIdentifier)) ` + -AutoUpdaterNewReleaseAlertQuery ([string]::Format([Constants]::NewReleaseAlertQuery, $TelemetryIdentifier)) ` + -SubscriptionInvRefreshFailureAlertQuery ([Constants]::SubscriptionInvRefreshFailureQuery) ` + -BaselineControlsInvRefreshFailureAlertQuery ([Constants]::BaselineControlsInvRefreshFailureQuery) ` + -RBACInvRefreshFailureAlertQuery ([Constants]::RBACInvRefreshFailureQuery) ` + -ControlResultsRefreshFailureAlertQuery ([Constants]::ControlResultsRefreshFailureQuery) ` + -ScanProgressSummaryQuery ([Constants]::ScanProgressSummaryQuery) ` + -ActionGroupId $alertActionGroup.Id ` + -AIResourceId $AIDataSourceId ` + -LAResourceId $LADataSourceId ` + -IsAutoUpdaterEnabled $IsAutoUpdaterEnabled + + Write-Verbose "$(Get-TimeStamp)Completed monitoring alert setup." + + } + else + { + Write-Host "Failed to setup monitoring alert. Either Application Insight ID or Log Analytics ID is either null or empty." -ForegroundColor $([Constants]::MessageType.Error) + return + } + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } +} + +# Execute this function to send initial setup event to application insight. This is required as the auto-updater can take up to 24 hours to start after the initial setup. +# In the meantime, this custom events suppresses the Auto-Updater failure alert. +function SendCustomAIEvent +{ + param ( + [PSObject] $DeploymentResult, + [string] $TelemetryIdentifier + ) + try + { + if( $DeploymentResult.Outputs.ContainsKey('applicationInsightsIKey')) + { + $InstrumentationKey = $DeploymentResult.Outputs.applicationInsightsIKey.Value + $azAccountsModuleInfo = Get-Module Az.Accounts -ListAvailable -Verbose:$false | Select-Object -First 1 + if($null -eq $azAccountsModuleInfo) + { + Install-Module -Name Az.Accounts -Scope CurrentUser -Repository 'PSGallery' -AllowClobber -Verbose:$false + $azAccountsModuleInfo = Get-Module Az.Accounts -ListAvailable -Verbose:$false | Select-Object -First 1 + } + $AssemblyPath = Get-ChildItem -Path $azAccountsModuleInfo.ModuleBase -Filter "Microsoft.ApplicationInsights.dll" -Recurse + $AssemblyPathFullName = $AssemblyPath.FullName | Sort-Object -Descending | Select-Object -First 1 + Add-Type -Path $AssemblyPathFullName + $client = [Microsoft.ApplicationInsights.TelemetryClient]::new() + $client.InstrumentationKey= $InstrumentationKey + $event = [Microsoft.ApplicationInsights.DataContracts.EventTelemetry]::new() + $event.Name = "AzTS_Service_AutoUpdateOverallProgressTracker" + $customProperties = @{ + isOverallSuccess = $true; + Id = $TelemetryIdentifier + isAppServiceAutoUpdateSuccess = $true; + isCheckForAutoUpdateSuccess = $true; + isGetAppServicesByResourceGroupSuccess = $true; + isReconcilationCheckSuccess = $true; + message = "This is an initial setup event." + } + $customProperties.Keys | ForEach-Object { + try { + $event.Properties[$_] = $customProperties[$_].ToString(); + } + catch + { + $_ + # Eat the current exception which typically happens when the property already exist in the object and try to add the same property again + # No need to break execution + } + } + + $client.TrackEvent($event) + $client.Flush() + } + } + catch + { + # Eat the current exception which typically happens when dll is not available + } + +} + +class ContextHelper +{ + $currentContext = $null; + + [PSObject] SetContext([string] $SubscriptionId) + { + $this.currentContext = $null + if(-not $SubscriptionId) + { + + Write-Host "The argument 'SubscriptionId' is null. Please specify a valid subscription id." -ForegroundColor $([Constants]::MessageType.Error) + return $null; + } + + # Login to Azure and set context + try + { + if(Get-Command -Name Get-AzContext -ErrorAction Stop) + { + $this.currentContext = Get-AzContext -ErrorAction Stop + $isLoginRequired = (-not $this.currentContext) -or (-not $this.currentContext | GM Subscription) -or (-not $this.currentContext | GM Account) + + # Request login if context is empty + if($isLoginRequired) + { + Write-Host "No active Azure login session found. Initiating login flow..." -ForegroundColor $([Constants]::MessageType.Warning) + $this.currentContext = Connect-AzAccount -ErrorAction Stop # -SubscriptionId $SubscriptionId + } + + # Switch context if the subscription in the current context does not the subscription id given by the user + $isContextValid = ($this.currentContext) -and ($this.currentContext | GM Subscription) -and ($this.currentContext.Subscription | GM Id) + if($isContextValid) + { + # Switch context + if($this.currentContext.Subscription.Id -ne $SubscriptionId) + { + $this.currentContext = Set-AzContext -SubscriptionId $SubscriptionId -ErrorAction Stop -Force + } + } + else + { + Write-Host "Invalid PS context. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + } + } + else + { + Write-Host "Az command not found. Please run the following command 'Install-Module Az -Scope CurrentUser -Repository 'PSGallery' -AllowClobber -SkipPublisherCheck' to install Az module." -ForegroundColor $([Constants]::MessageType.Error) + } + } + catch + { + Write-Host "Error occurred while logging into Azure. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + return $null; + } + + return $this.currentContext; + + } + +} + +class CentralPackageInfo +{ + [PSObject] $CentralPackageObject = $null + [string] $CentralPackageURL = [string]::Empty + [string] $MetadataAggregatorPackageURL = [string]::Empty + [string] $WorkItemProcessorPackageURL = [string]::Empty + [string] $WebApiPackageURL = [string]::Empty + [string] $UIPackageURL = [string]::Empty + + CentralPackageInfo() + { + $CentralPackageVersionResponse = Invoke-WebRequest -UseBasicParsing -Uri "https://aka.ms/AzTS/CentralPackageURL" -Method Get + if(($CentralPackageVersionResponse | Measure-Object).Count -gt 0 -and $CentralPackageVersionResponse.StatusCode -eq 200) + { + $this.CentralPackageObject = $CentralPackageVersionResponse.Content | ConvertFrom-Json + + if(($this.CentralPackageObject | Get-Member BasePackageLink -ErrorAction SilentlyContinue) -ne $null) + { + $this.CentralPackageURL = $this.CentralPackageObject.BasePackageLink + } + } + + $this.MetadataAggregatorPackageURL = $this.GetPackageURL("MetadataAggregator") + $this.WorkItemProcessorPackageURL = $this.GetPackageURL("WorkItemProcessor") + $this.WebApiPackageURL = $this.GetPackageURL("WebApi") + $this.UIPackageURL = $this.GetPackageURL("UI") + } + + [string] GetPackageVersion([string] $featureName) + { + $packageVersion = "1.0.0" + if(($this.CentralPackageObject | Get-Member Packages -ErrorAction SilentlyContinue) -ne $null) + { + $packageDetails = $this.CentralPackageObject.Packages | Where-Object { $_.Name -eq $featureName } + + if(($packageDetails | Get-Member Stable -ErrorAction SilentlyContinue) -ne $null) + { + $packageVersion = $packageDetails.Stable + } + } + + return $packageVersion + } + + [string] GetPackageName([string] $featureName) + { + $packageName = $featureName + ".zip"; + if(($this.CentralPackageObject | Get-Member Packages -ErrorAction SilentlyContinue) -ne $null) + { + $packageDetails = $this.CentralPackageObject.Packages | Where-Object { $_.Name -eq $featureName } + + if(($packageDetails | Get-Member PackageName -ErrorAction SilentlyContinue) -ne $null) + { + $packageName = $packageDetails.PackageName + } + } + + return $packageName + } + + [string] GetPackageURL([string] $featureName) + { + return [string]::Join("/",$this.CentralPackageURL, + $featureName, + $this.GetPackageVersion($featureName), + $this.GetPackageName($featureName)) + } + +} + +class Logger{ + [string] $logFilePath = ""; + + Logger([string] $HostSubscriptionId) + { + $logFolerPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\Setup\Subscriptions\$($HostSubscriptionId.replace('-','_'))"; + $logFileName = "\$('DeploymentLogs_' + $(Get-Date).ToString('yyyyMMddhhmm') + '.txt')"; + $this.logFilePath = $logFolerPath + $logFileName + # Create folder if not exist + if (-not (Test-Path -Path $logFolerPath)) + { + New-Item -ItemType Directory -Path $logFolerPath | Out-Null + } + # Create log file + + New-Item -Path $this.logFilePath -ItemType File | Out-Null + + } + + PublishCustomMessage ([string] $message, [string] $foregroundColor){ + $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor $foregroundColor + } + + PublishCustomMessage ([string] $message){ + $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor White + } + + PublishLogMessage ([string] $message){ + $($message) | Add-Content $this.logFilePath + } + + PublishLogFilePath() + { + Write-Host $([Constants]::DoubleDashLine)"`r`nLogs have been exported to: $($this.logFilePath)`r`n"$([Constants]::DoubleDashLine) -ForegroundColor Cyan + } +} + +function Get-TimeStamp { + return "{0:h:m:ss tt} - " -f (Get-Date -UFormat %T) +} + +Function CreateAzureADApplication +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="Display name for the App to be created.")] + $displayName, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] + $AdditionalOwnerUPNs = @() + ) + + Write-Host "Checking if Azure AD application [$($displayName)] already exist..." -ForegroundColor $([Constants]::MessageType.Info) + + if (!(Get-AzureADApplication -SearchString $displayName)) { + + Write-Host "Creating new AD application [$($displayName)]..." -ForegroundColor $([Constants]::MessageType.Info) + # create new application + $app = New-AzureADApplication -DisplayName $displayName + + # create a service principal for your application + $spForApp = New-AzureADServicePrincipal -AppId $app.AppId + + } + else + { + Write-Host "AD application [$($displayName)] already exists." -ForegroundColor $([Constants]::MessageType.Info) + $app = Get-AzureADApplication -SearchString $displayName + } + + # Adding additional owners (if any) + if (($AdditionalOwnerUPNs| Measure-Object).Count -gt 0) + { + Add-OwnersToAADApplication -AppObjectId $app.ObjectId -UserPrincipalNames $AdditionalOwnerUPNs + } + + #endregion + return $app +} + +function GetADPermissionToBeGranted +{ + param + ( + [string] $targetServicePrincipalAppId, + $appPermissionsRequired + ) + + $targetSp = Get-AzureADServicePrincipal -Filter "AppId eq '$($targetServicePrincipalAppId)'" + + $RoleAssignments = @() + Foreach ($AppPermission in $appPermissionsRequired) { + $RoleAssignment = $targetSp.Oauth2Permissions | Where-Object { $_.Value -eq $AppPermission} + $RoleAssignments += $RoleAssignment + } + + $ResourceAccessObjects = New-Object 'System.Collections.Generic.List[Microsoft.Open.AzureAD.Model.ResourceAccess]' + foreach ($RoleAssignment in $RoleAssignments) { + $resourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.ResourceAccess" + $resourceAccess.Id = $RoleAssignment.Id + $resourceAccess.Type = 'Scope' + $ResourceAccessObjects.Add($resourceAccess) + } + $requiredResourceAccess = New-Object -TypeName "Microsoft.Open.AzureAD.Model.RequiredResourceAccess" + $requiredResourceAccess.ResourceAppId = $targetSp.AppId + $requiredResourceAccess.ResourceAccess = $ResourceAccessObjects + + return $requiredResourceAccess + +} + +function get-hash([string]$textToHash) { + $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider + $toHash = [System.Text.Encoding]::UTF8.GetBytes($textToHash) + $hashByteArray = $hasher.ComputeHash($toHash) + $result = [string]::Empty; + foreach($byte in $hashByteArray) + { + $result += "{0:X2}" -f $byte + } + return $result; + } + + +function Grant-AzSKGraphPermissionToUserAssignedIdentity +{ + Param + ( + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where user identity has been created.")] + $ResourceGroupName, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Object id of user managed identity used to scan subscriptions.")] + $IdentityName, + + [string[]] + [Parameter(Mandatory = $true, HelpMessage="List of Microsoft Graph permission to be granted to the Managed Identity")] + $MSGraphPermissionsRequired, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of Azure AD Graph permission to be granted to the Managed Identity")] + $ADGraphPermissionsRequired, + + [string] + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true, ParameterSetName = "UserIdentity", HelpMessage="Object id of user managed identity used to scan subscriptions.")] + $UserAssignedIdentityObjectId + ) + + try { + + Write-Host "WARNING: To grant Graph API permission, the signed-in user must be a member of one of the following administrator roles: Global Administrator or Privileged Role Administrator." -ForegroundColor $([Constants]::MessageType.Warning) + + + # Validate input + if([string]::IsNullOrWhiteSpace($UserAssignedIdentityObjectId)) + { + Write-Host "Getting Azure AD enterprise application details..." -ForegroundColor $([Constants]::MessageType.Info) + $UserAssignedIdentity = Get-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $IdentityName + $UserAssignedIdentityObjectId = $UserAssignedIdentity.PrincipalId + } + + GrantMSGraphPermissionsToAADIdentity -AADIdentityObjectId $UserAssignedIdentityObjectId -MSGraphPermissionsRequired $MSGraphPermissionsRequired -ADGraphPermissionsRequired $ADGraphPermissionsRequired + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } +} + +function GrantMSGraphPermissionsToAADIdentity +{ + param ( + [string] + $AADIdentityObjectId, + + [string[]] + $MSGraphPermissionsRequired, + + [string[]] + $ADGraphPermissionsRequired + ) + + $msi = Get-AzureADServicePrincipal -ObjectId $AADIdentityObjectId + $groupPermissions = @() + + if(($MSGraphPermissionsRequired | Measure-Object).Count -gt 0) + { + # MS Graph ID + $targetServicePrincipalAppId='00000003-0000-0000-c000-000000000000' + $graph = Get-AzureADServicePrincipal -Filter "AppId eq '$($targetServicePrincipalAppId)'" + $groupPermissions += @($graph.AppRoles | Where { $MSGraphPermissionsRequired -contains $_.Value -and $_.AllowedMemberTypes -contains "Application"} ` + | Select *, @{ Label = "PermissionScope"; Expression = {"MS Graph"}}, ` + @{ Label = "GraphServicePrincipalObjectId"; Expression = {$graph.ObjectId}}) + } + + if(($ADGraphPermissionsRequired | Measure-Object).Count -gt 0) + { + # Azure AD Graph ID + $targetServicePrincipalAppId='00000002-0000-0000-c000-000000000000' + $graph = Get-AzureADServicePrincipal -Filter "AppId eq '$($targetServicePrincipalAppId)'" + $groupPermissions += @($graph.AppRoles | Where { $ADGraphPermissionsRequired -contains $_.Value -and $_.AllowedMemberTypes -contains "Application"} ` + | Select *, @{ Label = "PermissionScope"; Expression = {"Azure AD Graph"}}, ` + @{ Label = "GraphServicePrincipalObjectId"; Expression = {$graph.ObjectId}}) + } + + # Grant permission to managed identity. + if(($groupPermissions | Measure-Object).Count -gt 0) + { + $groupPermissions | ForEach-Object { + + Write-Host "Granting $($_.PermissionScope) [$($_.Value)] permission to Azure AD enterprise application [ObjectId: $($UserAssignedIdentityObjectId)]." -ForegroundColor $([Constants]::MessageType.Info) + + try + { + $RoleAssignment = New-AzureADServiceAppRoleAssignment ` + -Id $_.Id ` + -ObjectId $msi.ObjectId ` + -PrincipalId $msi.ObjectId ` + -ResourceId $_.GraphServicePrincipalObjectId + Write-Host "Successfully granted [$($_.Value)] permission to Azure AD enterprise application." -ForegroundColor $([Constants]::MessageType.Update) + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } + } + } + else + { + Write-Host "WARNING: No match found for permissions provided as input." -ForegroundColor $([Constants]::MessageType.Warning) + } +} + +function Set-AzSKTenantSecurityADApplication +{ + + Param( + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Name of ResourceGroup where setup resources will be created.")] + $ScanHostRGName = "AzSK-AzTS-RG", + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Custom", HelpMessage="Name of the Azure AD application to be used by the API.")] + $WebAPIAzureADAppName, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Custom", HelpMessage="Name of the Azure AD application to be used by the UI.")] + $UIAzureADAppName, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] + $AzureEnvironmentName = "AzureCloud", + + [switch] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] + [Parameter(Mandatory = $false, ParameterSetName = "Custom", HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] + $ConsolidatedSetup = $false, + + [string[]] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "Custom", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] + $AdditionalOwnerUPNs = @() + + ) + + try + { + $output = "" | Select WebAPIAzureADAppId,UIAzureADAppId + + if (-not $ConsolidatedSetup) + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "$([Constants]::AzureADAppSetupInstructionMsg)" -ForegroundColor $([Constants]::MessageType.Info) + } + Write-Host "NOTE: If you do not have the permission to perform aforementioned operations, please contact your administrator to complete the setup." -ForegroundColor $([Constants]::MessageType.Warning) + Write-Host $([Constants]::SingleDashLine) + + $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() #considering only first 5 characters + + Write-Host "Starting Azure AD application setup..." -ForegroundColor $([Constants]::MessageType.Info) + + # Creating Azure AD application: Web API + if([string]::IsNullOrWhiteSpace($WebAPIAzureADAppName)) + { + $WebAPIAzureADAppName = "AzSK-AzTS-WebApi-$ResourceHash"; + } + + $webApi = CreateAzureADApplication -displayName $WebAPIAzureADAppName -AdditionalOwnerUPNs $AdditionalOwnerUPNs + + # Creating Azure AD application: UI + if([string]::IsNullOrWhiteSpace($UIAzureADAppName)) + { + $UIAzureADAppName="AzSK-AzTS-UI-$ResourceHash" + } + + $webUIApp = CreateAzureADApplication -displayName $UIAzureADAppName -AdditionalOwnerUPNs $AdditionalOwnerUPNs + + Write-Host "Updating Azure AD application registration..." -ForegroundColor $([Constants]::MessageType.Info) + + $identifierUri = 'api://{0}' -f $webUIApp.AppId + $replyUris = New-Object Collections.Generic.List[string] + $replyUris.Add(($AzureEnvironmentAppServiceURI.$AzureEnvironmentName -f $UIAzureADAppName)); + $replyUris.Add($([string]::Join("/", $([string]::Format($AzureEnvironmentAppServiceURI.$AzureEnvironmentName, $UIAzureADAppName)), "auth.html"))); + Set-AzureADApplication -ObjectId $webUIApp.ObjectId -ReplyUrls $replyUris -IdentifierUris $identifierUri -Oauth2AllowImplicitFlow $true + + $identifierUri = 'api://{0}' -f $webApi.AppId + Set-AzureADApplication -ObjectId $webApi.ObjectId -IdentifierUris $identifierUri -Oauth2AllowImplicitFlow $true + + Write-Host "Updated Azure AD applications redirection URL and OAuth 2.0 implicit grant flow." -ForegroundColor $([Constants]::MessageType.Info) + + try + { + Write-Host "Granting 'User.Read' permission to UI AD application..." -ForegroundColor $([Constants]::MessageType.Info) + + # MS Graph ID + $targetServicePrincipalAppId='00000003-0000-0000-c000-000000000000'; + # Grant MS Graph permission + # Get Azure AD App for UI. Grant graph permission. + $appPermissionsRequired = @('User.Read') + $permission = GetADPermissionToBeGranted -targetServicePrincipalAppId $targetServicePrincipalAppId -appPermissionsRequired $appPermissionsRequired + Set-AzureADApplication -ObjectId $webUIApp.ObjectId -RequiredResourceAccess $permission + Write-Host "Granted UI AD application 'User.Read' permission." -ForegroundColor $([Constants]::MessageType.Info) + } + catch + { + Write-Host "Failed to grant 'User.Read' permission. ExceptionMessage $($_)" -ForegroundColor Red + } + $output.WebAPIAzureADAppId = $webApi.AppId + $output.UIAzureADAppId = $webUIApp.AppId + + Write-Host "Completed Azure AD application setup." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + if (-not $ConsolidatedSetup) + { + $NextStepMessage = $([Constants]::AzureADAppSetupNextSteps) -f $webApi.AppId, $webUIApp.AppId + Write-Host "$($NextStepMessage)" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::DoubleDashLine) + } + + return $output; + + } + catch + { + Write-Host "Error occurred while setting up Azure AD application. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + return; + } +} + + +function Set-AzSKTenantSecuritySolutionScannerIdentity +{ + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription id in which scanner identity is to be created.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $false, HelpMessage="Name of ResourceGroup where scanner identity will be created.")] + $ResourceGroupName = "AzSK-AzTS-RG", + + [string] + [Parameter(Mandatory = $true, HelpMessage="Location where scanner identity will get created. Default location is EastUS2.")] + $Location = 'EastUS2', + + [string] + [Parameter(Mandatory = $false, HelpMessage="Name of the Azure AD application to be used by the API.")] + $UserAssignedIdentityName, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of target subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] + [Alias("SubscriptionsToScan")] + $TargetSubscriptionIds = @(), + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] + [Alias("ManagementGroupsToScan")] + $TargetManagementGroupNames = @(), + + [switch] + [Parameter(Mandatory = $false, HelpMessage="Switch to mark if command is invoked through consolidated installation command. This will result in masking of few instrcution messages. Using this switch is not recommended while running this command in standalone mode.")] + $ConsolidatedSetup = $false + ) + + Begin + { + # Step 1: Set context to subscription where user-assigned managed identity needs to be created. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($SubscriptionId) + if(-not $currentContext) + { + return; + } + } + + Process + { + try + { + + if (-not $ConsolidatedSetup) + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Running Azure Tenant Security scanner identity setup...`n" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::ScanningIdentitySetupInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + } + + # Step 2: Create resource group where user-assigned MI resource will be created. + try + { + Write-Verbose "$(Get-TimeStamp)Checking resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) + $rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue + if(-not $rg) + { + Write-Verbose "$(Get-TimeStamp)Creating resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) + $rg = New-AzResourceGroup -Name $ResourceGroupName -Location $Location -ErrorAction Stop + } + else{ + Write-Verbose "$(Get-TimeStamp)Resource group already exists." #-ForegroundColor $([Constants]::MessageType.Info) + } + + } + catch + { + Write-Host "`n`rFailed to create resource group for deployment." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 3: Create user-assigned MI resource. + Write-Host "Checking if user-assigned identity [$($UserAssignedIdentityName)] exists..." -ForegroundColor $([Constants]::MessageType.Info) + $UserAssignedIdentity = Get-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $UserAssignedIdentityName -ErrorAction SilentlyContinue + if($UserAssignedIdentity -eq $null) + { + Write-Host "Creating a new user-assigned identity [$($UserAssignedIdentityName)]." -ForegroundColor $([Constants]::MessageType.Info) + $UserAssignedIdentity = New-AzUserAssignedIdentity -ResourceGroupName $ResourceGroupName -Name $UserAssignedIdentityName + Start-Sleep -Seconds 60 + } + else + { + Write-Host "User-assigned identity [$($UserAssignedIdentityName)] already exists." -ForegroundColor $([Constants]::MessageType.Info) + } + + # Grant User Identity Reader permission on target subscription(s). + Write-Host "Granting user-assigned identity 'Reader' permission on target scope(s)..." -ForegroundColor $([Constants]::MessageType.Info) + $targetSubscriptionCount = ($TargetSubscriptionIds | Measure-Object).Count + $targetMgtGroupCount = ($TargetManagementGroupNames | Measure-Object).Count + if($targetSubscriptionCount -gt 0) + { + $TargetSubscriptionIds | % { + + try + { + Write-Host "Assigning 'Reader' access to user-assigned managed identity on target subscription [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) + New-AzRoleAssignment -ApplicationId $UserAssignedIdentity.ClientId -Scope "/subscriptions/$_" -RoleDefinitionName "Reader" -ErrorAction Stop + } + catch + { + if($_.Exception.Body.Code -eq "RoleAssignmentExists") + { + Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) + + } + else + { + Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) + + } + } + } + } + + if ($targetMgtGroupCount -gt 0) + { + $TargetManagementGroupNames | % { + + try + { + Write-Host "Assigning 'Reader' access to user-assigned managed identity on target management group [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) + New-AzRoleAssignment -ApplicationId $UserAssignedIdentity.ClientId -Scope "/providers/Microsoft.Management/managementGroups/$_" -RoleDefinitionName "Reader" -ErrorAction Stop + } + catch + { + if($_.Exception.Body.Code -eq "RoleAssignmentExists") + { + Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) + + } + else + { + Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) + + } + } + } + } + + if (-not(($targetSubscriptionCount -gt 0) -or ($targetMgtGroupCount -gt 0))) + { + Write-Host "No target subscription or management group specified." -ForegroundColor $([Constants]::MessageType.Warning) + } + + Write-Host "Completed Azure Tenant Security scanner identity setup." -ForegroundColor $([Constants]::MessageType.Update) + + Write-Host $([Constants]::SingleDashLine) + if (-not $ConsolidatedSetup) + { + Write-Host ([constants]::ScanningIdentitySetupNextSteps) -ForegroundColor $([Constants]::MessageType.Info) + } + + Write-Host $([Constants]::DoubleDashLine) + + return $UserAssignedIdentity; + } + catch + { + Write-Host "Error occurred while setting up scanner identity. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + return; + } + } +} + + + +function AddAlert( + + [string] $Query, + [ValidateSet("Equal", "GreaterThan", "LessThan" )] + [string] $ThresholdOperator, + [int] $Threshold = 0, + [string] $AlertDescription, + [string] $AlertName, + [ValidateSet(0,1,2,3,4)] + [int] $AlertSeverity = 2, + [int] $FrequencyInMinutes = 5, + [int] $TimeWindowInMinutes = 5, + [string] $DataSourceId, + [string] $ActionGroupResourceId, + [string] $Location, + [string] $ResourceGroupName + + ) +{ + $source = New-AzScheduledQueryRuleSource -Query $Query -DataSourceId $DataSourceId -WarningAction SilentlyContinue + $schedule = New-AzScheduledQueryRuleSchedule -FrequencyInMinutes $FrequencyInMinutes -TimeWindowInMinutes $TimeWindowInMinutes -WarningAction SilentlyContinue + $triggerCondition = New-AzScheduledQueryRuleTriggerCondition -ThresholdOperator $ThresholdOperator -Threshold $Threshold -WarningAction SilentlyContinue + $aznsActionGroup = New-AzScheduledQueryRuleAznsActionGroup -ActionGroup $ActionGroupResourceId -EmailSubject "TDMON ALERT: $($AlertName)" -WarningAction SilentlyContinue + $alertingAction = New-AzScheduledQueryRuleAlertingAction -AznsAction $aznsActionGroup -Severity $AlertSeverity -Trigger $triggerCondition -WarningAction SilentlyContinue + + + # create alert + New-AzScheduledQueryRule -ResourceGroupName $ResourceGroupName -Location $Location ` + -Action $alertingAction -Enabled $true ` + -Description $AlertDescription ` + -Schedule $schedule ` + -Source $source ` + -Name $AlertName -WarningAction SilentlyContinue +} + +function Set-AzSKTenantSecuritySolutionSecretStorage +{ + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription id in which key vault is to be created.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where key vault will be created.")] + $ResourceGroupName = "AzSK-AzTS-KV-RG", + + [string] + [Parameter(Mandatory = $false, HelpMessage="Location where the resource group and key vault will get created. Default location is EastUS2.")] + $Location = 'EastUS2', + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of the Key Vault to be created.")] + $KeyVaultName, + + [string] + [Parameter(Mandatory = $true, HelpMessage="AzTS Scanner AAD Application's Password credentials.")] + $AADAppPasswordCredential + + ) + + Begin + { + # Step 1: Set context to subscription where key vault needs to be created. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($SubscriptionId) + if(-not $currentContext) + { + return; + } + } + + Process + { + try + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Running Azure Tenant Security scanner secret storage setup...`n" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::KeyVaultSecretStoreSetupInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + # Step 2: Create resource group where KV resource will be created. + try + { + Write-Verbose "Checking resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) + $rg = Get-AzResourceGroup -Name $ResourceGroupName -ErrorAction SilentlyContinue + if(-not $rg) + { + Write-Verbose "Creating resource group for deployment..." #-ForegroundColor $([Constants]::MessageType.Info) + $rg = New-AzResourceGroup -Name $ResourceGroupName -Location $Location -ErrorAction Stop + } + else{ + Write-Verbose "Resource group already exists." #-ForegroundColor $([Constants]::MessageType.Info) + } + + } + catch + { + Write-Host "`n`rFailed to create resource group for deployment." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 3: Deploy KV + # Check if Key Vault already exist. + $keyVault = Get-AzKeyVault -ResourceGroupName $ResourceGroupName -VaultName $KeyVaultName -ErrorAction SilentlyContinue + + if ($keyVault -ne $null) + { + Write-Host "Key Vault already exist. All existing 'Access Policies' will be removed." -ForegroundColor $([Constants]::MessageType.Warning) + $userChoice = Read-Host -Prompt "`n`Do you want to continue (Y/N)?" + if ($userChoice -ne 'Y') + { + Write-Host "Please provide another name to create new Key Vault. Exiting..." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + # Else continue with deployment + } + + $secretName = "AzTSScannerIdentityCredentials" + $credentialSecureString = $AADAppPasswordCredential | ConvertTo-SecureString -AsPlainText -Force + + $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId,$ResourceGroupName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() + + $validationResult = Test-AzResourceGroupDeployment -Mode Incremental ` + -ResourceGroupName $ResourceGroupName ` + -TemplateFile ".\AzTSKeyVaultTemplate.json" ` + -keyVaultName $KeyVaultName ` + -secretName $secretName ` + -secretValue $credentialSecureString ` + -ResourceHash $ResourceHash ` + -location $Location + if($validationResult) + { + Write-Host "`n`rTemplate deployment validation returned following errors:" -ForegroundColor $([Constants]::MessageType.Error) + $validationResult | FL Code, Message | Out-String | Out-Host; + return; + } + else + { + $deploymentName = "AzTSenvironmentkeyvaultsetup-$([datetime]::Now.ToString("yyyymmddThhmmss"))" + $deploymentOutput = New-AzResourceGroupDeployment -Name $deploymentName ` + -Mode Incremental ` + -ResourceGroupName $ResourceGroupName ` + -TemplateFile ".\AzTSKeyVaultTemplate.json" ` + -keyVaultName $KeyVaultName ` + -secretName $secretName ` + -secretValue $credentialSecureString ` + -ResourceHash $ResourceHash ` + -location $Location + + Write-Host "Completed Azure Tenant Security scanner secret storage setup." -ForegroundColor $([Constants]::MessageType.Info) + + Write-Host $([Constants]::SingleDashLine) + Write-Host ([constants]::KeyVaultSecretStoreSetupNextSteps) -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::DoubleDashLine) + + return $deploymentOutput; + } + + } + catch + { + Write-Host "Error occurred while setting up scanner secret storage. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + return; + } + } +} + +function Grant-AzSKAccessOnKeyVaultToUserAssignedIdentity +{ + Param + ( + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Subscription id in which Key Vault exist.")] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Subscription id in which Key Vault exist.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Resource Id of existing Key Vault.")] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Resource Id of existing Key Vault.")] + $ResourceId, + + [string] + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Object id of user managed identity.")] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Object id of user managed identity.")] + $UserAssignedIdentityObjectId, + + [string[]] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="User email Ids to whom monitoring alert mails should be sent.")] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="User email Ids to whom monitoring alert mails should be sent.")] + $SendAlertsToEmailIds, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] + $ScanIdentitySecretUri, + + [string] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Key Vault SecretUri of the Scanner App's credentials.")] + $LAWorkspaceResourceId, + + [switch] + [Parameter(Mandatory = $true, ParameterSetName = "EnableMonitoring", HelpMessage="Switch to deploy alerts on top of Key Vault auditing logs.")] + $DeployMonitoringAlert + ) + + try { + + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Granting access over Key Valut to MI..." -ForegroundColor $([Constants]::MessageType.Info) + # Validate input + # Check UserAssignedObject must be non null + if([string]::IsNullOrWhiteSpace($UserAssignedIdentityObjectId)) + { + Write-Host "Object Id of managed identity must not be null..." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + + #Set context to subscription where key vault exist + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($SubscriptionId) + if(-not $currentContext) + { + return; + } + + # Check if Key Vault Exist + $keyVault = Get-AzResource -ResourceId $ResourceId -ErrorAction SilentlyContinue + + if(-not $keyVault) + { + Write-Host "Unable to find any Key Vault with resourceId [$($ResourceId)] in subscription [$($SubscriptionId)]..." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Assigne Secret Get permission to MI + Set-AzKeyVaultAccessPolicy -ResourceId $ResourceId -ObjectId $UserAssignedIdentityObjectId -PermissionsToSecrets get + + Write-Host "Successfully granted access over Key Valut to MI." -ForegroundColor $([Constants]::MessageType.Update) + Write-Host $([Constants]::SingleDashLine) + if ($DeployMonitoringAlert -eq $true) + { + try{ + + Write-Host "Creating monitoring alerts..." -ForegroundColor $([Constants]::MessageType.Info) + $EmailReceivers = @() + $SendAlertsToEmailIds | ForEach-Object { + $EmailReceivers += New-AzActionGroupReceiver -Name "Notify_$($_)" -EmailReceiver -EmailAddress $_ + } + + $keyVaultRGName = $ResourceId.Split("/")[4] # ResourceId is in format - /subscriptions/SubIdGuid/resourceGroups/RGName/providers/Microsoft.KeyVault/vaults/KeyVaultName + $alertActionGroupForKV = Set-AzActionGroup -Name ‘AzTSAlertActionGroupForKV’ -ResourceGroupName $keyVaultRGName -ShortName ‘AzTSKVAlert’ -Receiver $EmailReceivers -WarningAction SilentlyContinue + + $deploymentName = "AzTSenvironmentmonitoringsetupforkv-$([datetime]::Now.ToString("yyyymmddThhmmss"))" + + $alertQuery = [string]::Format([Constants]::UnintendedSecretAccessAlertQuery, $ResourceId, $ScanIdentitySecretUri, $UserAssignedIdentityObjectId) + $deploymentOutput = New-AzResourceGroupDeployment -Name $deploymentName ` + -Mode Incremental ` + -ResourceGroupName $keyVaultRGName ` + -TemplateFile ".\KeyVaultMonitoringAlertTemplate.json" ` + -UnintendedSecretAccessAlertQuery $alertQuery ` + -ActionGroupId $alertActionGroupForKV.Id ` + -LAResourceId $laWorkspaceResourceId ` + -Location $keyVault.Location + + Write-Host "Completed monitoring alert setup." -ForegroundColor $([Constants]::MessageType.Update) + + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } + + } + + Write-Host $([Constants]::SingleDashLine) + Write-Host $([Constants]::DoubleDashLine) + + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } +} + +function Create-AzSKTenantSecuritySolutionMultiTenantScannerIdentity +{ + param ( + [string] + [Parameter(Mandatory = $true, ParameterSetName = "Default", HelpMessage="Display Name of the Scanner Identity.")] + $displayName, + + [string] + [Parameter(Mandatory = $true, ParameterSetName = "PreExistApp", HelpMessage="Object Id of the Scanner Identity.")] + $objectId, + + [string[]] + [Parameter(Mandatory = $false, ParameterSetName = "Default", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] + [Parameter(Mandatory = $false, ParameterSetName = "PreExistApp", HelpMessage="UserPrinicipalNames of the additional owners for the App to be created.")] + $AdditionalOwnerUPNs = @() + + ) + try + { + $appDetails = "" | Select-Object "ApplicationId", "ObjectId", "Secret" + if ([string]::IsNullOrWhiteSpace($objectId)) + { + Write-Host "Checking if Azure AD application [$($displayName)] already exist..." -ForegroundColor $([Constants]::MessageType.Info) + $aadApp = Get-AzureADApplication -SearchString $displayName + if (!$aadApp) { + + Write-Host "Creating new AD application [$($displayName)]..." -ForegroundColor $([Constants]::MessageType.Info) + # create new application + $aadApp = New-AzureADApplication -DisplayName $displayName -AvailableToOtherTenants $true + Write-Host "Created [$($displayName)] app successfully." -ForegroundColor $([Constants]::MessageType.Update) + } + elseif(($aadApp | Measure-Object).Count -gt 1) + { + Write-Host "Multiple AD application with display name [$($displayName)] exists in AAD.`n Either choose different name to create new AAD app or provide Object Id of App as input to use existing App." -ForegroundColor $([Constants]::MessageType.error) + return; + } + else + { + Write-Host "AD application [$($displayName)] already exists." -ForegroundColor $([Constants]::MessageType.Info) + } + + } + else + { + $aadApp = Get-AzureADApplication -ObjectId $objectId + + if(!$aadApp) + { + Write-Host "AD application with object Id [$($objectId)] not found in AAD." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + } + + # Create new password credential for App + $startDateTime = Get-Date + $endDateTime = $startDateTime.AddMonths(6) + $pwdCredentials = New-AzureADApplicationPasswordCredential -ObjectId $aadApp.ObjectId -StartDate $startDateTime -EndDate $endDateTime + + # Adding additional owners (if any) + if (($AdditionalOwnerUPNs| Measure-Object).Count -gt 0) + { + Add-OwnersToAADApplication -AppObjectId $aadApp.ObjectId -UserPrincipalNames $AdditionalOwnerUPNs + } + + # Prepare output object + $appDetails.ApplicationId = $aadApp.AppId + $appDetails.ObjectId = $aadApp.ObjectId + $appDetails.Secret = $pwdCredentials.Value + + return $appDetails + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } + +} + +function Create-AzSKTenantSecuritySolutionMultiTenantIdentitySPN +{ + param ( + [string] + [Parameter(Mandatory = $true, HelpMessage="unique identifier of the AAD application of which ServicePrincipal need to be created")] + $AppId + ) + try + { + + Write-Host "Checking if Azure AD service principal for App [$($AppId)] already exist..." -ForegroundColor $([Constants]::MessageType.Info) + $spn = Get-AzureADServicePrincipal -Filter "AppId eq '$($AppId)'" + if (!$spn) { + + Write-Host "Creating new Azure AD service principal for App [$($AppId)]..." -ForegroundColor $([Constants]::MessageType.Info) + # create new spn + $spn = New-AzureADServicePrincipal -AppId $AppId -AppRoleAssignmentRequired $false + Write-Host "Successfully created service principal." -ForegroundColor $([Constants]::MessageType.Info) + } + else + { + Write-Host "AD service principal for App [$($AppId)] already exists." -ForegroundColor $([Constants]::MessageType.Info) + } + + # return spn object ($spn.ObjectId) + return $spn + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } +} + +function Grant-AzSKGraphPermissionToMultiTenantScannerIdentity +{ + Param( + [string] + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true, HelpMessage="Object id of the identity used to scan subscriptions.")] + $AADIdentityObjectId, + + [string[]] + [Parameter(Mandatory = $true, HelpMessage="List of Microsoft Graph permission to be granted to the Identity")] + $MSGraphPermissionsRequired, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of Azure AD Graph permission to be granted to the Identity")] + $ADGraphPermissionsRequired + ) + + try { + + Write-Host "WARNING: To grant Graph API permission, the signed-in user must be a member of one of the following administrator roles: Global Administrator or Privileged Role Administrator." -ForegroundColor $([Constants]::MessageType.Warning) + + GrantMSGraphPermissionsToAADIdentity -AADIdentityObjectId $AADIdentityObjectId -MSGraphPermissionsRequired $MSGraphPermissionsRequired -ADGraphPermissionsRequired $ADGraphPermissionsRequired + } + catch + { + if(($_.Exception | GM ErrorContent -ErrorAction SilentlyContinue) -and ($_.Exception.ErrorContent | GM Message -ErrorAction SilentlyContinue)) + { + Write-Host "ErrorCode [$($_.Exception.ErrorCode)] ErrorMessage [$($_.Exception.ErrorContent.Message.Value)]" -ForegroundColor $([Constants]::MessageType.Error) + } + else + { + Write-Host $_.Exception.Message -ForegroundColor $([Constants]::MessageType.Error) + } + } +} + +function Grant-AzSKAzureRoleToMultiTenantIdentitySPN +{ + Param( + [string] + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true, HelpMessage="Object id of the identity used to scan subscriptions.")] + $AADIdentityObjectId, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of target subscription(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target subscription.")] + [Alias("SubscriptionsToScan")] + $TargetSubscriptionIds = @(), + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of target management group(s) to be scanned by Azure Tenant Security scanning solution. Scanning identity will be granted 'Reader' access on target management group. Providing root management group name is recommended.")] + [Alias("ManagementGroupsToScan")] + $TargetManagementGroupNames = @() + ) + + Write-Host "Granting 'Reader' permission to service principal on target scope(s)..." -ForegroundColor $([Constants]::MessageType.Info) + $targetSubscriptionCount = ($TargetSubscriptionIds | Measure-Object).Count + $targetMgtGroupCount = ($TargetManagementGroupNames | Measure-Object).Count + if($targetSubscriptionCount -gt 0) + { + # Set Azure Context for first random subscription in current tenant + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($TargetSubscriptionIds[0]) + if(-not $currentContext) + { + return; + } + + $TargetSubscriptionIds | % { + + try + { + Write-Host "Assigning 'Reader' access to service principal on target subscription [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) + New-AzRoleAssignment -ObjectId $AADIdentityObjectId -Scope "/subscriptions/$_" -RoleDefinitionName "Reader" -ErrorAction Stop + } + catch + { + if($_.Exception.Body.Code -eq "RoleAssignmentExists") + { + Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) + + } + else + { + Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) + + } + } + } + } + + if ($targetMgtGroupCount -gt 0) + { + $TargetManagementGroupNames | % { + + try + { + Write-Host "Assigning 'Reader' access to service principal on target management group [$($_)]" -ForegroundColor $([Constants]::MessageType.Info) + New-AzRoleAssignment -ObjectId $AADIdentityObjectId -Scope "/providers/Microsoft.Management/managementGroups/$_" -RoleDefinitionName "Reader" -ErrorAction Stop + } + catch + { + if($_.Exception.Body.Code -eq "RoleAssignmentExists") + { + Write-Host "$($_.Exception.Message)" -ForegroundColor $([Constants]::MessageType.Warning) + + } + else + { + Write-Host "Error occurred while granting permission. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) + + } + } + } + } + + if (-not(($targetSubscriptionCount -gt 0) -or ($targetMgtGroupCount -gt 0))) + { + Write-Host "No target subscription or management group specified." -ForegroundColor $([Constants]::MessageType.Warning) + } + else{ + Write-Host "Completed 'Reader' role assignment for service principal." -ForegroundColor $([Constants]::MessageType.Update) + } + +} + +function Add-AADApplicationOwners() +{ + Param( + [string] + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true, HelpMessage="Object id of the Azure AD Application.")] + $AppObjectId, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of owners to be added to an application.")] + $OwnerObjectIds = @() + ) + + $allOwnerAdded = $true + if (($OwnerObjectIds| Measure-Object).Count -gt 0) + { + $OwnerObjectIds | ForEach-Object{ + $objectId = $_ + try + { + Add-AzureADApplicationOwner -ObjectId $AppObjectId -RefObjectId $objectId + } + catch + { + $allOwnerAdded = $allOwnerAdded -and $false + Write-Host "Error occurred while adding owner [ObjectId: $($objectId)] to application . ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) + } + } + } + + return $allOwnerAdded; + +} + +function Get-AADUserDetails() +{ + Param( + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of owners to be added to an application.")] + $UserPrincipalNames = @() + ) + + $aadUsers = @(); + if (($UserPrincipalNames| Measure-Object).Count -gt 0) + { + $UserPrincipalNames | ForEach-Object{ + $aadUser = "" | Select-Object "UPN", "ObjectId" + $aadUser.UPN = $_; + $aadUser.ObjectId = [Constants]::AADUserNotFound + try + { + $user = Get-AzureADUser -ObjectId $_ + $aadUser.ObjectId = $user.ObjectId + } + catch + { + Write-Host "Error occurred while fetching AAD user [UPN: $($aadUser.UPN)]. ErrorMessage [$($_.Exception.Message)]" -ForegroundColor $([Constants]::MessageType.Error) + } + $aadUsers += $aadUser + } + } + + return $aadUsers; + +} + +function Add-OwnersToAADApplication() +{ + Param( + [string] + [ValidateNotNullOrEmpty()] + [Parameter(Mandatory = $true, HelpMessage="Object id of the Azure AD Application.")] + $AppObjectId, + + [string[]] + [Parameter(Mandatory = $false, HelpMessage="List of owners to be added to an application.")] + $UserPrincipalNames = @() + ) + + Write-Host "Adding additional owners to AAD Application [App ObjectId: $($AppObjectId)]" + + if (($UserPrincipalNames|Measure-Object).Count -gt 0) + { + $aadUsers = Get-AADUserDetails -UserPrincipalNames $UserPrincipalNames + $validAADUsers = $aadUsers | where-Object { $_.ObjectId -ne $([Constants]::UserNotFound)} + + if (($validAADUsers | Measure-Object).Count -gt 0) + { + $userObjectIds = $validAADUsers.ObjectId + $allOwnersAdded = Add-AADApplicationOwners -AppObjectId $AppObjectId -OwnerObjectIds $userObjectIds + + if ($allOwnersAdded) + { + Write-Host "Owners for application added successfully." -ForegroundColor $([Constants]::MessageType.Update) + } + else{ + Write-Host "One or more owners not added to application." -ForegroundColor $([Constants]::MessageType.Warning) + } + } + else{ + Write-Host "No valid users found." + } + } + else + { + Write-Host "UserPrincipalNames is empty. No owners added to application." + } +} + +function Enable-ByDesignExceptionFeature +{ + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription id in which AzTS is installed.")] + $HostSubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where AzTS is installed.")] + $HostResourceGroupName, + + [string[]] + [Parameter(Mandatory = $true, HelpMessage="Array of location containing primary and secondary location for cosmos DB.")] + $CosmosDBLocationArray, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription id in which AzTS KV is available/is to be created.")] + $KVSubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where AzTS KV is available/is to be created.")] + $KVResourceGroupName, + + [string] + [Parameter(Mandatory = $false, HelpMessage="Location where the resource group and key vault will get created. Default location is EastUS2.")] + $KVLocation = 'EastUS2', + + [string] + [Parameter(Mandatory = $false, HelpMessage="Name of the Key Vault which is to be used/is to be created.")] + $KeyVaultName + + ) + + Begin + { + # Step 1: Set context to subscription where AzTS is hosted. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($HostSubscriptionId) + if(-not $currentContext) + { + return; + } + } + + Process + { + try + { + Write-Host $([Constants]::DoubleDashLine) + Write-Host "Enabling By Design exception feature in Azure Tenant Security scanner...`n" -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::EnableByDesignExpInstructionMsg ) -ForegroundColor $([Constants]::MessageType.Info) + Write-Host $([Constants]::SingleDashLine) + + $ResourceId='/subscriptions/{0}/resourceGroups/{1}' -f $HostSubscriptionId,$HostResourceGroupName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0,5).ToString().ToLower() + + $InternalMI = $null + $CosmosDbConnString = $null + $CosmosConnStringSecret = $null + $AzTSUIAppPwdCrdential = $null + $AzTSUIAppPwdCrdentialSecret = $null + $AzTSAPI = $null + $NewAPIAppSettings = @{} + + + # Step 2: Validate AzTS host RG. + try + { + Write-Verbose "Validating the AzTS host resource group..." #-ForegroundColor $([Constants]::MessageType.Info) + # Step 2a: Validate AzTS host RG exists. + $rg = Get-AzResourceGroup -Name $HostResourceGroupName -ErrorAction SilentlyContinue + if(-not $rg) + { + Write-Host "`n`rAzTS host resource group does not exists." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + else + { + # Step 2a: Validate AzTS host RG have AzTS components. + $InternalMIName = "AzSK-AzTS-InternalMI-" + $ResourceHash + $InternalMI = Get-AzUserAssignedIdentity -ResourceGroupName $HostResourceGroupName -Name $InternalMIName -ErrorAction SilentlyContinue + if(-not $InternalMI) + { + Write-Host "`n`rAzTS host resource group does not contain AzTS components." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + else + { + Write-Verbose "AzTS host resource group validated successfully." #-ForegroundColor $([Constants]::MessageType.Info) + } + } + + } + catch + { + Write-Host "`n`rFailed to validate AzTS host resource group." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 3: Create cosmos db account and table for exception management. + try + { + Write-Verbose "Creating cosmos DB account and table for exception management if not already present..." + # Step 3a: Creating cosmos DB account if not already present. + $cosmosaccname = "aztscosmosdb" + $ResourceHash + Write-Verbose "Checking if cosmos DB account is already present..." + $cosmosacc = Get-AzCosmosDBAccount -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname -ErrorAction SilentlyContinue + if(-not $cosmosacc) + { + Write-Verbose "Cosmos DB account is not present..." + Write-Verbose "Creating Cosmos DB account..." + $cosmosacc = New-AzCosmosDBAccount -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname -ApiKind Table -EnableAutomaticFailover -Location $CosmosDBLocationArray -ErrorAction SilentlyContinue + } + + if(-not $cosmosacc) + { + throw [System.Exception] + } + Write-Verbose "Created Cosmos DB account successfully..." + + # Step 3b: Creating cosmos DB table for exception management if not already present. + Write-Verbose "Checking if cosmos DB table is already present..." + $exceptiontable = Get-AzCosmosDBTable -ResourceGroupName $HostResourceGroupName -AccountName $cosmosaccname -Name "Exceptions" -ErrorAction SilentlyContinue + if(-not $exceptiontable) + { + Write-Verbose "Cosmos DB table is not present..." + Write-Verbose "Creating Cosmos DB table..." + $exceptiontable = New-AzCosmosDBTable -ResourceGroupName $HostResourceGroupName -AccountName $cosmosaccname -Name "Exceptions" -AutoscaleMaxThroughput 1000 -ErrorAction SilentlyContinue + } + + if(-not $exceptiontable) + { + throw [System.Exception] + } + + Write-Verbose "Cosmos DB account and table created successfully." + + # Step 3c: Getting cosmos DB connection string. + Write-Verbose "Getting cosmos DB connection string..." + $connectionStrings = Get-AzCosmosDBAccountKey -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname -Type "ConnectionStrings" -ErrorAction SilentlyContinue + if(-not $connectionStrings) + { + Write-Host "`n`rFailed to get cosmos DB connection string." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + $CosmosDbConnString = $connectionStrings["Primary Table Connection String"] + Write-Verbose "Fetched cosmos DB connection string successfully." + } + catch + { + # Step 3c: Remove cosmos DB account if the creation fails. + $cosmosaccname = "aztscosmosdb" + $ResourceHash + Remove-AzCosmosDBAccount -ResourceGroupName $HostResourceGroupName -Name $cosmosaccname + Write-Host "`n`rFailed to create cosmos DB for exception management." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 4: Create key vault if not already exists. + try + { + # Step 4a: Set context to subscription where KV is present/is to be created. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($KVSubscriptionId) + if(-not $currentContext) + { + throw [System.Exception] + } + + Write-Verbose "Checking if key vault's the resource group exists or not..." #-ForegroundColor $([Constants]::MessageType.Info) + # Step 4b: Check if KV's RG exists or not. + $rg = Get-AzResourceGroup -Name $KVResourceGroupName -ErrorAction SilentlyContinue + if(-not $rg) + { + Write-Verbose "Creating resource group for key vault..." #-ForegroundColor $([Constants]::MessageType.Info) + # Step 4c: Create RG for KV.. + $rg = New-AzResourceGroup -Name $KVResourceGroupName -Location $KVLocation -ErrorAction SilentlyContinue + if(-not $rg) + { + throw [System.Exception] + } + Write-Verbose "Key vault's resource group created successfully." + } + else + { + Write-Verbose "Key vault's resource group already exists..." + # Step 4d: Check if the KV already exists. + $kv = Get-AzKeyVault -VaultName $KeyVaultName -ResourceGroupName $KVResourceGroupName -ErrorAction SilentlyContinue + if(-not $kv) + { + Write-Verbose "Creating key vault..." + # Step 4e: Create KV. + $kv = New-AzKeyVault -Name $KeyVaultName -ResourceGroupName $KVResourceGroupName -Location $KVLocation -ErrorAction SilentlyContinue + if(-not $kv) + { + throw [System.Exception] + } + Write-Verbose "Key vault created successfully." + } + else + { + Write-Verbose "Key vault already exists..." + } + } + } + catch + { + # Step 4f: Failed to validate/create KV. + Write-Host "`n`rFailed to validate/create key vault." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 5: Update KV's access policy and store cosmos connection string as secret. + try + { + # Step 5a: Update KV's access policy so that current user is able to set and get secrets. + Write-Verbose "Updating key vault's access policy so that current user is able to set and get secrets..." + $context = Get-AzContext + Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -UserPrincipalName $context.Account.Id -PermissionsToSecrets get,set,list + + # Step 5b: Update KV's access policy so that internal MI is able to get secrets. + Write-Verbose "Updating key vault's access policy so that internal MI is able to get secrets..." + Set-AzKeyVaultAccessPolicy -VaultName $KeyVaultName -PermissionsToSecrets get -ObjectId $InternalMI.PrincipalId + + # Step 5c: Storing cosmos DB connection string in KV as secret. + Write-Verbose "Storing cosmos DB connection string in Key vault as secret..." + $CosmosConnStringSecure = ConvertTo-SecureString $CosmosDbConnString -AsPlainText -Force + $CosmosConnStringSecret = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name "CosmosDBConnString" -SecretValue $CosmosConnStringSecure + + Write-Verbose "Successfully stored cosmos DB connection string in Key vault." + + } + catch + { + # Step 5d: Failed to store cosmos DB connection string. + Write-Host "`n`rFailed to store cosmos DB connection string in Key vault." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 6: Configure AzTS API to enable By Design exception feature. + try + { + # Step 6a: Set context to subscription where AzTS is hosted. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($HostSubscriptionId) + if(-not $currentContext) + { + throw [System.Exception] + } + + # Step 6b: Get AzTS API. + Write-Verbose "Getting AzTS API..." + $AzTSAPIName = "AzSK-AzTS-WebApi-" + $ResourceHash + $AzTSAPI = Get-AzWebApp -Name $AzTSAPIName -ResourceGroupName $HostResourceGroupName -ErrorAction SilentlyContinue + if(-not $AzTSAPI) + { + Write-Host "`n`rFailed to get AzTS API." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 6c: Update AzTS API's KV reference identity to user assigned identity. + Write-Verbose "Updating AzTS API's KV reference identity to user assigned identity..." + $userAssignedIdentityResourceId = $InternalMI.Id + $Path = "{0}?api-version=2021-01-01" -f $AzTSAPI.Id + Invoke-AzRestMethod -Method PATCH -Path $Path -Payload "{'properties':{'keyVaultReferenceIdentity':'$userAssignedIdentityResourceId'}}" + Write-Verbose "Successfully updated AzTS API's KV reference identity to user assigned identity." + + + # Step 6d: Update AzTS API's app settings. + Write-Verbose "Updating AzTS API's app settings..." + $cosmosConnStringAppSetting = "@Microsoft.KeyVault(SecretUri=" + $CosmosConnStringSecret.Id + ")" + $currentAPIAppSettings = $AzTSAPI.SiteConfig.AppSettings + + ForEach ($appSetting in $currentAPIAppSettings) { + $NewAPIAppSettings[$appSetting.Name] = $appSetting.Value + } + + $NewAPIAppSettings["AzureCosmosDBSettings__IsFeatureEnabled"] = "true" + $NewAPIAppSettings["AzureCosmosDBSettings__ConnectionString"] = $cosmosConnStringAppSetting + $NewAPIAppSettings["FeatureManagement__ExceptionLoggingFeature"] = "true" + $NewAPIAppSettings["UIConfigurations__ExceptionFeatureConfiguration__IsEnabled"] = "true" + $NewAPIAppSettings["UIConfigurations__ExceptionFeatureConfiguration__HelpPageUrl"] = "https://aka.ms/AzTS-Docs/Exception" + $NewAPIAppSettings["UIConfigurations__ExceptionFeatureConfiguration__WarningText"] = "When attesting controls, the highest level of discretion is required. Justification for each attested control is mandatory in order to document the rationale for bypassing the security control." + $NewAPIAppSettings.Remove("WEBSITE_DNS_SERVER") + + Set-AzWebApp -Name $AzTSAPIName -ResourceGroupName $HostResourceGroupName -AppSettings $NewAPIAppSettings + + Write-Verbose "Successfully updated AzTS API's app settings." + } + catch + { + # Step 6e: Failed to Configure AzTS API to enable By Design exception feature. + Write-Host "`n`rFailed to Configure AzTS API to enable By Design exception feature." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 7: Create password credential for AzTS UI AAD App. + try + { + # Step 7a: Get AzTS UI AAD App. + Write-Verbose "Getting AzTS UI AAD App..." + $aztsUIAADAppName = "AzSK-AzTS-UI-" + $ResourceHash + $aztsUIAADApp = Get-AzureADApplication -SearchString $aztsUIAADAppName -ErrorAction SilentlyContinue + if(-not $aztsUIAADApp) + { + Write-Host "`n`rFailed to get AzTS UI AAD App." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 7b: Create password credential for AzTS UI AAD App. + Write-Verbose "Creating password credential for AzTS UI AAD App..." + $startDateTime = Get-Date + $endDateTime = $startDateTime.AddMonths(6) + $pwdCredentials = New-AzureADApplicationPasswordCredential -ObjectId $aztsUIAADApp.ObjectId -StartDate $startDateTime -EndDate $endDateTime -ErrorAction SilentlyContinue + if(-not $pwdCredentials) + { + Write-Host "`n`rFailed to create password credential for AzTS UI AAD App." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + Write-Verbose "Successfully created password credential for AzTS UI AAD App." + + $AzTSUIAppPwdCrdential = $pwdCredentials.Value + } + catch + { + # Step 6e: Failed to Create password credential for AzTS UI AAD App. + Write-Host "`n`rFailed to Create password credential for AzTS UI AAD App." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 8: Store AzTS UI AAD App password credential in Key vault as secret. + try + { + # Step 8a: Set context to subscription where KV is present. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($KVSubscriptionId) + if(-not $currentContext) + { + throw [System.Exception] + } + + # Step 8b: Storing AzTS UI AAD App password credential in KV as secret. + Write-Verbose "Storing AzTS UI AAD App password credential in Key vault as secret..." + $AzTSUIAppPwdCrdentialSecure = ConvertTo-SecureString $AzTSUIAppPwdCrdential -AsPlainText -Force + $AzTSUIAppPwdCrdentialSecret = Set-AzKeyVaultSecret -VaultName $KeyVaultName -Name "APIClientSecret" -SecretValue $AzTSUIAppPwdCrdentialSecure + Write-Verbose "Successfully stored AzTS UI AAD App password credential in Key vault." + + } + catch + { + # Step 5d: Failed to store cosmos DB connection string. + Write-Host "`n`rFailed to store AzTS UI AAD App password credential in Key vault." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 9: Configure AzTS Scanner to enable By Design exception feature. + try + { + # Step 9a: Set context to subscription where AzTS is hosted. + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($HostSubscriptionId) + if(-not $currentContext) + { + throw [System.Exception] + } + + # Step 9b: Get AzTS Scanner. + Write-Verbose "Getting AzTS Scanner..." + $aztswipname = "AzSK-AzTS-WorkItemProcessor-" + $ResourceHash + $aztswip = Get-AzWebApp -Name $aztswipname -ResourceGroupName $HostResourceGroupName -ErrorAction SilentlyContinue + if(-not $aztswip) + { + Write-Host "`n`rFailed to get AzTS Scanner." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + # Step 9c: Update AzTS Scanner's KV reference identity to user assigned identity. + Write-Verbose "Updating AzTS Scanner's KV reference identity to user assigned identity..." + $userAssignedIdentityResourceId = $InternalMI.Id + $Path = "{0}?api-version=2021-01-01" -f $aztswip.Id + Invoke-AzRestMethod -Method PATCH -Path $Path -Payload "{'properties':{'keyVaultReferenceIdentity':'$userAssignedIdentityResourceId'}}" + Write-Verbose "Successfully updated AzTS Scanner's KV reference identity to user assigned identity." + + + # Step 9d: Update AzTS Scanner's app settings. + Write-Verbose "Updating AzTS API's app settings..." + $apiClientSecretAppSetting = "@Microsoft.KeyVault(SecretUri=" + $AzTSUIAppPwdCrdentialSecret.Id + ")" + $currentWIPAppSettings = $aztswip.SiteConfig.AppSettings + $newWIPAppSettings = @{} + ForEach ($appSetting in $currentWIPAppSettings) { + $newWIPAppSettings[$appSetting.Name] = $appSetting.Value + } + + $newWIPAppSettings["AzureCosmosDBSettings__IsFeatureEnabled"] = "true" + $newWIPAppSettings["APIClientConfiguration__ClientId"] = $NewAPIAppSettings["AADClientAppDetails__ApplicationId"] + $newWIPAppSettings["APIClientConfiguration__ClientSecret"] = $apiClientSecretAppSetting + $newWIPAppSettings["APIClientConfiguration__Scope"] = "api://" + $NewAPIAppSettings["AADClientAppDetails__ClientId"] + "/.default" + $newWIPAppSettings["APIClientConfiguration__ApiBaseUrl"] = "https://" + $AzTSAPI.DefaultHostName + + $context = Get-AzContext + $TenantId = $context.Tenant.Id + $newWIPAppSettings["AuthNSettings__TenantId"] = $TenantId + + $newWIPAppSettings.Remove("WEBSITE_DNS_SERVER") + + Set-AzWebApp -Name $aztswipname -ResourceGroupName $HostResourceGroupName -AppSettings $newWIPAppSettings + + Write-Verbose "Successfully updated AzTS Scanner's app settings." + } + catch + { + # Step 9e: Failed to Configure AzTS Scanner to enable By Design exception feature. + Write-Host "`n`rFailed to Configure AzTS Scanner to enable By Design exception feature." -ForegroundColor $([Constants]::MessageType.Error) + return; + } + + } + catch + { + Write-Host "Error occurred while enabling By Design Exception. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + return; + } + } } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/ConfigureAztsFeatureTemplate.json b/TemplateFiles/DeploymentFiles/ConfigureAztsFeatureTemplate.json index 8a6ffcae..fe5ff367 100644 --- a/TemplateFiles/DeploymentFiles/ConfigureAztsFeatureTemplate.json +++ b/TemplateFiles/DeploymentFiles/ConfigureAztsFeatureTemplate.json @@ -1,250 +1,250 @@ -[ - { - "FeatureName": "CMET", - "DependentFeaturesForEnabling": [], - "DependentFeaturesForDisabling": ["CMET Bulk Edit","MG Compliance Initiate Editor"], - - "ConfigurationDependenciesForEnabling": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__OrgPolicy", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "FeatureManagement__AddNewControl", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "FeatureManagement__PolicyStates", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsAddNewControlEnabled", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsEnabled", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "WorkItemProcessorSettings__AppName", - "ConfigurationValue": "##AppName##" - }, - { - "ConfigurationName": "WorkItemProcessorSettings__HostResourceGroupName", - "ConfigurationValue": "##HostResourceGroupName##" - }, - { - "ConfigurationName": "WorkItemProcessorSettings__HostSubscriptionId", - "ConfigurationValue": "##HostSubscriptionId##" - } - ] - }, - { - "ComponentName": "AzSK-AzTS-MetaDataAggregator-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__OrgPolicy", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "FeatureManagement__PolicyStates", - "ConfigurationValue": "true" - } - ] - }, - { - "ComponentName": "AzSK-AzTS-WorkItemProcessor-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__OrgPolicy", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "FeatureManagement__PolicyStates", - "ConfigurationValue": "true" - } - ] - } - ], - "ConfigurationDependenciesForDisabling": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__OrgPolicy", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "FeatureManagement__AddNewControl", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "FeatureManagement__PolicyStates", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsAddNewControlEnabled", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsEnabled", - "ConfigurationValue": "false" - } - ] - }, - { - "ComponentName": "AzSK-AzTS-MetaDataAggregator-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__OrgPolicy", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "FeatureManagement__PolicyStates", - "ConfigurationValue": "false" - } - ] - }, - { - "ComponentName": "AzSK-AzTS-WorkItemProcessor-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__OrgPolicy", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "FeatureManagement__PolicyStates", - "ConfigurationValue": "false" - } - ] - } - ] - }, - { - "FeatureName": "CMET Bulk Edit", - "DependentFeaturesForEnabling": ["CMET"], - "DependentFeaturesForDisabling": [], - "ConfigurationDependenciesForEnabling": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__BulkEdit", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsBulkEditEnabled", - "ConfigurationValue": "true" - } - ] - } - ], - "ConfigurationDependenciesForDisabling": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__BulkEdit", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsBulkEditEnabled", - "ConfigurationValue": "false" - } - ] - } - ] - }, - { - "FeatureName": "MG Processor", - "DependentFeaturesForEnabling": ["CMET"], - "DependentFeaturesForDisabling": ["MG Compliance Initiate Editor"], - "ConfigurationDependenciesForEnabling": [ - { - "ComponentName": "AzSK-AzTS-MetaDataAggregator-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__ManagementGroups", - "ConfigurationValue": "false" - } - ] - } - ], - "ConfigurationDependenciesForDisabling": [ - { - "ComponentName": "AzSK-AzTS-MetaDataAggregator-", - "Configuration": [ - { - "ConfigurationName": "FeatureManagement__ManagementGroups", - "ConfigurationValue": "false" - } - ] - } - ] - }, - { - "FeatureName": "PIM API", - "DependentFeaturesForEnabling": [], - "DependentFeaturesForDisabling": ["MG Compliance Initiate Editor"], - "ConfigurationDependenciesForEnabling": [ - { - "ComponentName": "AzSK-AzTS-MetaDataAggregator-", - "Configuration": [ - { - "ConfigurationName": "AuthzSettings__IsPIMEnabled", - "ConfigurationValue": "false" - } - ] - } - ], - "ConfigurationDependenciesForDisabling": [ - { - "ComponentName": "AzSK-AzTS-MetaDataAggregator-", - "Configuration": [ - { - "ConfigurationName": "AuthzSettings__IsPIMEnabled", - "ConfigurationValue": "false" - } - ] - } - ] - }, - { - "FeatureName": "MG Compliance Initiate Editor", - "DependentFeaturesForEnabling": ["CMET","MG Processor","PIM API"], - "DependentFeaturesForDisabling": [], - "ConfigurationDependenciesForEnabling": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": [ - { - "ConfigurationName": "UIConfigurations__complianceInitiativeFeatureConfiguration__IsEnabled", - "ConfigurationValue": "true" - }, - { - "ConfigurationName": "UIConfigurations__IsManagementGroupFilterEnabled", - "ConfigurationValue": "true" - } - ] - } - ], - "ConfigurationDependenciesForDisabling": [ - { - "ComponentName": "AzSK-AzTS-WebApi-", - "Configuration": [ - { - "ConfigurationName": "UIConfigurations__complianceInitiativeFeatureConfiguration__IsEnabled", - "ConfigurationValue": "false" - }, - { - "ConfigurationName": "UIConfigurations__IsManagementGroupFilterEnabled", - "ConfigurationValue": "false" - } - ] - } - ] - } +[ + { + "FeatureName": "CMET", + "DependentFeaturesForEnabling": [], + "DependentFeaturesForDisabling": ["CMET Bulk Edit","MG Compliance Initiate Editor"], + + "ConfigurationDependenciesForEnabling": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__OrgPolicy", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "FeatureManagement__AddNewControl", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "FeatureManagement__PolicyStates", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsAddNewControlEnabled", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsEnabled", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "WorkItemProcessorSettings__AppName", + "ConfigurationValue": "##AppName##" + }, + { + "ConfigurationName": "WorkItemProcessorSettings__HostResourceGroupName", + "ConfigurationValue": "##HostResourceGroupName##" + }, + { + "ConfigurationName": "WorkItemProcessorSettings__HostSubscriptionId", + "ConfigurationValue": "##HostSubscriptionId##" + } + ] + }, + { + "ComponentName": "AzSK-AzTS-MetaDataAggregator-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__OrgPolicy", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "FeatureManagement__PolicyStates", + "ConfigurationValue": "true" + } + ] + }, + { + "ComponentName": "AzSK-AzTS-WorkItemProcessor-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__OrgPolicy", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "FeatureManagement__PolicyStates", + "ConfigurationValue": "true" + } + ] + } + ], + "ConfigurationDependenciesForDisabling": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__OrgPolicy", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "FeatureManagement__AddNewControl", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "FeatureManagement__PolicyStates", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsAddNewControlEnabled", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsEnabled", + "ConfigurationValue": "false" + } + ] + }, + { + "ComponentName": "AzSK-AzTS-MetaDataAggregator-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__OrgPolicy", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "FeatureManagement__PolicyStates", + "ConfigurationValue": "false" + } + ] + }, + { + "ComponentName": "AzSK-AzTS-WorkItemProcessor-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__OrgPolicy", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "FeatureManagement__PolicyStates", + "ConfigurationValue": "false" + } + ] + } + ] + }, + { + "FeatureName": "CMET Bulk Edit", + "DependentFeaturesForEnabling": ["CMET"], + "DependentFeaturesForDisabling": [], + "ConfigurationDependenciesForEnabling": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__BulkEdit", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsBulkEditEnabled", + "ConfigurationValue": "true" + } + ] + } + ], + "ConfigurationDependenciesForDisabling": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__BulkEdit", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "UIConfigurations__ControlEditorFeatureConfiguration__IsBulkEditEnabled", + "ConfigurationValue": "false" + } + ] + } + ] + }, + { + "FeatureName": "MG Processor", + "DependentFeaturesForEnabling": ["CMET"], + "DependentFeaturesForDisabling": ["MG Compliance Initiate Editor"], + "ConfigurationDependenciesForEnabling": [ + { + "ComponentName": "AzSK-AzTS-MetaDataAggregator-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__ManagementGroups", + "ConfigurationValue": "false" + } + ] + } + ], + "ConfigurationDependenciesForDisabling": [ + { + "ComponentName": "AzSK-AzTS-MetaDataAggregator-", + "Configuration": [ + { + "ConfigurationName": "FeatureManagement__ManagementGroups", + "ConfigurationValue": "false" + } + ] + } + ] + }, + { + "FeatureName": "PIM API", + "DependentFeaturesForEnabling": [], + "DependentFeaturesForDisabling": ["MG Compliance Initiate Editor"], + "ConfigurationDependenciesForEnabling": [ + { + "ComponentName": "AzSK-AzTS-MetaDataAggregator-", + "Configuration": [ + { + "ConfigurationName": "AuthzSettings__IsPIMEnabled", + "ConfigurationValue": "false" + } + ] + } + ], + "ConfigurationDependenciesForDisabling": [ + { + "ComponentName": "AzSK-AzTS-MetaDataAggregator-", + "Configuration": [ + { + "ConfigurationName": "AuthzSettings__IsPIMEnabled", + "ConfigurationValue": "false" + } + ] + } + ] + }, + { + "FeatureName": "MG Compliance Initiate Editor", + "DependentFeaturesForEnabling": ["CMET","MG Processor","PIM API"], + "DependentFeaturesForDisabling": [], + "ConfigurationDependenciesForEnabling": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": [ + { + "ConfigurationName": "UIConfigurations__complianceInitiativeFeatureConfiguration__IsEnabled", + "ConfigurationValue": "true" + }, + { + "ConfigurationName": "UIConfigurations__IsManagementGroupFilterEnabled", + "ConfigurationValue": "true" + } + ] + } + ], + "ConfigurationDependenciesForDisabling": [ + { + "ComponentName": "AzSK-AzTS-WebApi-", + "Configuration": [ + { + "ConfigurationName": "UIConfigurations__complianceInitiativeFeatureConfiguration__IsEnabled", + "ConfigurationValue": "false" + }, + { + "ConfigurationName": "UIConfigurations__IsManagementGroupFilterEnabled", + "ConfigurationValue": "false" + } + ] + } + ] + } ] \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/ConfigureWebUI.ps1 b/TemplateFiles/DeploymentFiles/ConfigureWebUI.ps1 index c379ce68..0dad0275 100644 --- a/TemplateFiles/DeploymentFiles/ConfigureWebUI.ps1 +++ b/TemplateFiles/DeploymentFiles/ConfigureWebUI.ps1 @@ -1,146 +1,146 @@ -# Standard configurations - -$AzureEnvironmentToADAuthUrlMap = @{ - "AzureCloud" = "https://login.microsoftonline.com"; - "AzureGovernmentCloud" = "https://login.microsoftonline.us"; - "AzureChinaCloud" = "https://login.microsoftonline.cn"; -} - -$AzureEnvironmentToKuduConsoleUrlMap = @{ - "AzureCloud" = "https://{0}.scm.azurewebsites.net"; - "AzureGovernmentCloud" = "https://{0}.scm.azurewebsites.us"; - "AzureChinaCloud" = "https://{0}.scm.chinacloudsites.cn"; -} - -function GetAuthHeader { - [psobject] $headers = $null - try - { - - $resourceAppIdUri = (Get-AzContext).Environment.ResourceManagerUrl - $rmContext = Get-AzContext - $authResult = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate( - $rmContext.Account, - $rmContext.Environment, - $rmContext.Tenant, - [System.Security.SecureString] $null, - "Never", - $null, - $resourceAppIdUri); - - $header = "Bearer " + $authResult.AccessToken - $headers = @{"Authorization"=$header; 'If-Match'='*';} - } - catch - { - Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - } - - return($headers) -} - -function Configure-WebUI -{ - Param( - [string] - [Parameter(Mandatory = $true, HelpMessage="TenantID of the subscription where Azure Tenant Security Solution is to be installed.")] - $TenantId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of the Resource Group where setup resources will be created.")] - $ScanHostRGName, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of the web app deployed in azure for Azure Tenant Security Solution.")] - $UIAppName, - - [string] - [Parameter(Mandatory = $true, HelpMessage="URL of the Web API")] - $ApiUrl, - - [string] - [Parameter(Mandatory = $true, HelpMessage="ClientId of the web app deployed in azure for Azure Tenant Security Solution.")] - $UIClientId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="ClientId of the web api deployed in azure for Azure Tenant Security Solution.")] - $WebApiClientId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] - [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] - $AzureEnvironmentName, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Application Insights instrumentation key for Azure Tenant Security Solution.")] - $InstrumentationKey - - ) - - $AzureADAuthUrl = $AzureEnvironmentToADAuthUrlMap.$AzureEnvironmentName - $KuduConsoleUrl = $AzureEnvironmentToKuduConsoleUrlMap.$AzureEnvironmentName - - Write-Host "Configuring AzTS UI for $AzureEnvironmentName..." - - $baseDir = Get-Location - $configFile = "runtime-configuration-initial.js" - $configFilePath = "${baseDir}\${configFile}" - $configJs = @" -window.__UI_CONFIGURATION_INITIAL__ = { - "tenantId": "$TenantId", - "webAPI": "$ApiUrl", - "clientId": "$UIClientId", - "apiClientId": "$WebApiClientId", - "azureADAuthURL": "$AzureADAuthUrl", - "appInsights": { - "instrumentationKey": "$InstrumentationKey", - "loggingLevelConsole": 2, - "loggingLevelTelemetry": 2, - "maxBatchInterval": 15000, - "enableRequestHeaderTracking": true, - "enableResponseHeaderTracking": true, - "autoTrackPageVisitTime": true, - "disableDataLossAnalysis": false, - "enableCorsCorrelation": true, - "enableAutoRouteTracking": true, - "enableAjaxPerfTracking": true, - "enableAjaxErrorStatusText": true, - "enableUnhandledPromiseRejectionTracking": true, - "disableInstrumentationKeyValidation": true, - "ComponentName": "AzTS UI" - } -}; - -window.__UI_CONFIGURATION_EXTENDED__ = {}; -"@ - - $configJs | Set-Content $configFilePath - $userAgent = "powershell/1.0" - - $uploadUrl = $([string]::Join("/", $([string]::Format($KuduConsoleUrl, $UIAppName)), "api/vfs/site/wwwroot", ${configFile})) - [psobject] $headers = GetAuthHeader - Invoke-RestMethod -Uri $uploadUrl -Headers $headers -UserAgent $userAgent -Method PUT -InFile $configFilePath -ContentType "multipart/form-data" - - Reset-AzWebAppPublishingProfile -Name $UIAppName -ResourceGroupName $ScanHostRGName - - # Configure Staging slot - $webAppSlot = Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $UIAppName - if ($webAppSlot -ne $null) - { - # Only one staging slot is deployed as part of deployment - $webAppSlot = $webAppSlot | Select -First 1 - $slotName = $webAppSlot.Name.Split("/")[1] - $slotFullName = $UIAppName + "-" + $slotName - - $slotUploadUrl = $([string]::Join("/", $([string]::Format($KuduConsoleUrl, $slotFullName)), "api/vfs/site/wwwroot", ${configFile})) - [psobject] $headersSlot = GetAuthHeader - Invoke-RestMethod -Uri $slotUploadUrl -Headers $headersSlot -UserAgent $userAgent -Method PUT -InFile $configFilePath -ContentType "multipart/form-data" - - Reset-AzWebAppSlotPublishingProfile -ResourceGroupName $ScanHostRGName -Name $UIAppName -Slot $slotName - } - - If((test-path $configFilePath)) - { - Remove-Item -Force -Path $configFilePath - } +# Standard configurations + +$AzureEnvironmentToADAuthUrlMap = @{ + "AzureCloud" = "https://login.microsoftonline.com"; + "AzureGovernmentCloud" = "https://login.microsoftonline.us"; + "AzureChinaCloud" = "https://login.microsoftonline.cn"; +} + +$AzureEnvironmentToKuduConsoleUrlMap = @{ + "AzureCloud" = "https://{0}.scm.azurewebsites.net"; + "AzureGovernmentCloud" = "https://{0}.scm.azurewebsites.us"; + "AzureChinaCloud" = "https://{0}.scm.chinacloudsites.cn"; +} + +function GetAuthHeader { + [psobject] $headers = $null + try + { + + $resourceAppIdUri = (Get-AzContext).Environment.ResourceManagerUrl + $rmContext = Get-AzContext + $authResult = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate( + $rmContext.Account, + $rmContext.Environment, + $rmContext.Tenant, + [System.Security.SecureString] $null, + "Never", + $null, + $resourceAppIdUri); + + $header = "Bearer " + $authResult.AccessToken + $headers = @{"Authorization"=$header; 'If-Match'='*';} + } + catch + { + Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + } + + return($headers) +} + +function Configure-WebUI +{ + Param( + [string] + [Parameter(Mandatory = $true, HelpMessage="TenantID of the subscription where Azure Tenant Security Solution is to be installed.")] + $TenantId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of the Resource Group where setup resources will be created.")] + $ScanHostRGName, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of the web app deployed in azure for Azure Tenant Security Solution.")] + $UIAppName, + + [string] + [Parameter(Mandatory = $true, HelpMessage="URL of the Web API")] + $ApiUrl, + + [string] + [Parameter(Mandatory = $true, HelpMessage="ClientId of the web app deployed in azure for Azure Tenant Security Solution.")] + $UIClientId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="ClientId of the web api deployed in azure for Azure Tenant Security Solution.")] + $WebApiClientId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud")] + [ValidateSet("AzureCloud", "AzureGovernmentCloud","AzureChinaCloud")] + $AzureEnvironmentName, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Application Insights instrumentation key for Azure Tenant Security Solution.")] + $InstrumentationKey + + ) + + $AzureADAuthUrl = $AzureEnvironmentToADAuthUrlMap.$AzureEnvironmentName + $KuduConsoleUrl = $AzureEnvironmentToKuduConsoleUrlMap.$AzureEnvironmentName + + Write-Host "Configuring AzTS UI for $AzureEnvironmentName..." + + $baseDir = Get-Location + $configFile = "runtime-configuration-initial.js" + $configFilePath = "${baseDir}\${configFile}" + $configJs = @" +window.__UI_CONFIGURATION_INITIAL__ = { + "tenantId": "$TenantId", + "webAPI": "$ApiUrl", + "clientId": "$UIClientId", + "apiClientId": "$WebApiClientId", + "azureADAuthURL": "$AzureADAuthUrl", + "appInsights": { + "instrumentationKey": "$InstrumentationKey", + "loggingLevelConsole": 2, + "loggingLevelTelemetry": 2, + "maxBatchInterval": 15000, + "enableRequestHeaderTracking": true, + "enableResponseHeaderTracking": true, + "autoTrackPageVisitTime": true, + "disableDataLossAnalysis": false, + "enableCorsCorrelation": true, + "enableAutoRouteTracking": true, + "enableAjaxPerfTracking": true, + "enableAjaxErrorStatusText": true, + "enableUnhandledPromiseRejectionTracking": true, + "disableInstrumentationKeyValidation": true, + "ComponentName": "AzTS UI" + } +}; + +window.__UI_CONFIGURATION_EXTENDED__ = {}; +"@ + + $configJs | Set-Content $configFilePath + $userAgent = "powershell/1.0" + + $uploadUrl = $([string]::Join("/", $([string]::Format($KuduConsoleUrl, $UIAppName)), "api/vfs/site/wwwroot", ${configFile})) + [psobject] $headers = GetAuthHeader + Invoke-RestMethod -Uri $uploadUrl -Headers $headers -UserAgent $userAgent -Method PUT -InFile $configFilePath -ContentType "multipart/form-data" + + Reset-AzWebAppPublishingProfile -Name $UIAppName -ResourceGroupName $ScanHostRGName + + # Configure Staging slot + $webAppSlot = Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $UIAppName + if ($webAppSlot -ne $null) + { + # Only one staging slot is deployed as part of deployment + $webAppSlot = $webAppSlot | Select -First 1 + $slotName = $webAppSlot.Name.Split("/")[1] + $slotFullName = $UIAppName + "-" + $slotName + + $slotUploadUrl = $([string]::Join("/", $([string]::Format($KuduConsoleUrl, $slotFullName)), "api/vfs/site/wwwroot", ${configFile})) + [psobject] $headersSlot = GetAuthHeader + Invoke-RestMethod -Uri $slotUploadUrl -Headers $headersSlot -UserAgent $userAgent -Method PUT -InFile $configFilePath -ContentType "multipart/form-data" + + Reset-AzWebAppSlotPublishingProfile -ResourceGroupName $ScanHostRGName -Name $UIAppName -Slot $slotName + } + + If((test-path $configFilePath)) + { + Remove-Item -Force -Path $configFilePath + } } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/ExecutionScript.ps1 b/TemplateFiles/DeploymentFiles/ExecutionScript.ps1 index 945589a2..07e0232e 100644 --- a/TemplateFiles/DeploymentFiles/ExecutionScript.ps1 +++ b/TemplateFiles/DeploymentFiles/ExecutionScript.ps1 @@ -1,255 +1,255 @@ -<# ********************************************************************* - - Script execution guidance - - ********************************************************************* #> - - # To run this script, select a command and press F8. - - -<# ********************************************************************* - - Installation Prerequisite - - ********************************************************************* #> - -# *** 1 of 6. Validate prerequisites on machine - #Ensure that you are using Windows OS and have PowerShell version 5.0 or higher - - $PSVersionTable - -# *** 2 of 6. Installing required Az modules - # Install required Az modules - # Required versions: - # Az.Accounts >= 2.9.0 - # Az.Resources >= 1.10.0 - # Az.Storage >= 2.0.0 - # Az.ManagedServiceIdentity >= 0.7.3 - # Az.Monitor >= 1.5.0 - # Az.OperationalInsights >= 1.3.4 - # Az.ApplicationInsights >= 1.0.3 - # Az.Websites >= 2.8.1 - # Az.Network >= 2.5.0 - # Az.FrontDoor >= 1.8.0 - # Az.CosmosDB >= 1.8.2 - - Install-Module -Name Az.Accounts -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.Resources -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.Storage -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.ManagedServiceIdentity -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.Monitor -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.OperationalInsights -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.ApplicationInsights -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.Websites -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.Network -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.FrontDoor -AllowClobber -Scope CurrentUser -repository PSGallery - Install-Module -Name Az.CosmosDB -AllowClobber -Scope CurrentUser -repository PSGallery - - # Install AzureAd - # Required version: - # AzureAD >= 2.0.2.130 - Install-Module -Name AzureAD -AllowClobber -Scope CurrentUser -repository PSGallery - -# **** 3 of 6. Download and extract deployment template - - # i) If not already done, unblock the content. Below command will help to unblock files. - - # Set extracted folder path - $DeploymentTemplateFolderPath = "" - - # Unblock files - Get-ChildItem -Path $DeploymentTemplateFolderPath -Recurse | Unblock-File - - # ii) Point current path to deployment folder and load AzTS setup script - - # Point current path to extracted folder location and load setup script from deployment folder - - CD "$DeploymentTemplateFolderPath" - - # Load AzTS Setup script in session - - . ".\AzTSSetup.ps1" - - -<# ******************************************************************** - - Log in to Azure Portal and Azure Active Directory (AD) - - ********************************************************************* #> - -# Tenant id where the AzTS solution needs to be installed. -$TenantId = "" - -# Connect to AzureAD and AzAccount with the tenant Id where you want to use AzTS solution. - -# NOTE: Tenant Id *must* be specified when connecting to Azure AD and AzAccount. - -# Clear existing login, if any -# If you are not already connected to Azure, Disconnect command will return an error. In this case, please ignore the error and continue to next step. -Disconnect-AzAccount -Disconnect-AzureAD - -# Connect to AzureAD and AzAccount - -Connect-AzAccount -Tenant $TenantId -Connect-AzureAD -TenantId $TenantId - - -<# ******************************************************************** - - Setting up central scan managed identity (scanner MI) - - ********************************************************************* - - .Summary - - The AzTS setup provisions your subscriptions with the ability to do daily scans for security controls. - To do the scanning, it requires a User-assigned Managed Identity (central scanning identity owned by you) which has 'Reader' access - on target subscriptions on which scan needs to be performed. - -#> - -# *** 4 of 6. Setting up scanning identity - - # ***** Initialize required parameters ****** - - # Subscription id in which scanner MI needs to be created. - $MIHostingSubId = "" - - # Resource group name in which scanner MI needs to be created. - $MIHostingRGName = "" - - # Location in which scanner MI needs to be created. - $Location = "" - - # Name of the scanner MI. - $MIName = "" - - # List of target subscription(s) that needs to be scanned by AzTS. - # This command assigns 'Reader' access to user-assigned managed identity on target subscriptions. Add target subscription id(s) in place of - # Alternatively, you can grant scanner MI 'Reader' access at management group scope instead of individual subscriptions. - $TargetSubscriptionIds = @("","","") - - - # i) You can create central scanning user-assigned managed identity (MI) with below PowerShell command - - # Step 1: Create scanner MI and grant 'Reader' permission on target subscriptions. - $UserAssignedIdentity = Set-AzSKTenantSecuritySolutionScannerIdentity -SubscriptionId $MIHostingSubId ` - -ResourceGroupName $MIHostingRGName ` - -Location $Location ` - -UserAssignedIdentityName $MIName ` - -TargetSubscriptionIds $TargetSubscriptionIds - - # Step 2: Save resource id and principal Id generated for user identity using below command. This will be used in AzTS Soln installation. - - # Resource id of the user-assigned managed identity - $UserAssignedIdentity.Id - - # Object id of the user-assigned managed identity - $UserAssignedIdentity.PrincipalId - - # ii) Grant MS Graph read permission to central scanner MI. - - # NOTE: This step requires admin consent. Therefore, the signed-in user must be a member of one of the following administrator roles: - # Required Permission: Global Administrator or Privileged Role Administrator. - # If you do not have the permission required to complete this step, please contact your administrator. - # To proceed without this step, set the value of "-ScanIdentityHasGraphPermission" parameter to false in AzTS installation command (Install-AzSKTenantSecuritySolution) below in 'step 6 of 6'. - # By setting '-ScanIdentityHasGraphPermission' to $false, you are choosing to disable features dependent on Graph API. - - Grant-AzSKGraphPermissionToUserAssignedIdentity -UserAssignedIdentityObjectId $UserAssignedIdentity.PrincipalId -MSGraphPermissionsRequired @("PrivilegedAccess.Read.AzureResources", "Directory.Read.All") -ADGraphPermissionsRequired @("Directory.Read.All") - -<# ******************************************************************** - - Setup AzTS solution - - ********************************************************************* #> - - -# ***** Initialize required parameters ****** - -# Subscription id in which Azure Tenant Security Solution needs to be installed. -$HostSubscriptionId = "" - -# Resource group name in which Azure Tenant Security Solution needs to be installed. -$HostResourceGroupName = "" - -# Location in which resources needs to be created. -# Note: For better performance, we recommend hosting the Central Scanner MI and resources setup using AzTS Soln installation command in one location. -$Location = "" # e.g. EastUS2 - -# Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud -$AzureEnvironmentName = "" - -# Specify if scanner MI has Graph permission. This is to exclude controls dependent on Graph API reponse from the scan result, if scanner identity does not have graph permission. -$ScanIdentityHasGraphPermission = $false - -# The installation creates alert for monitoring health of the AzTS Soln. -# Comma-separated list of user email ids who should be sent the monitoring email. -$SendAlertNotificationToEmailIds = @('', '', '') - - -# *** 5 of 6. Create Azure AD application for secure authentication - - - # Step 1: Setup AD application for AzTS UI and API - $ADApplicationDetails = Set-AzSKTenantSecurityADApplication -SubscriptionId $HostSubscriptionId -ScanHostRGName $HostResourceGroupName - - # Step 2: Save WebAPIAzureADAppId and UIAzureADAppId generated for Azure AD application using below command. This will be used in AzTS Soln installation. - - # Azure AD application client (application) ids - $ADApplicationDetails.WebAPIAzureADAppId - $ADApplicationDetails.UIAzureADAppId - - -# *** 6 of 6. Set context and validate you have 'Owner' access on subscription where solution needs to be installed **** - - # Run Setup Command - # i) Set the context to hosting subscription - Set-AzContext -SubscriptionId $HostSubscriptionId - - # ii) Run install solution command - # Note : To install AzTS setup with vnet integration, uncomment switch '-EnableVnetIntegration' and then run the installation command - # Note : To install AzTS setup with WAF enabled, uncomment switch '-EnableWAF' and then run the installation command - $DeploymentResult = Install-AzSKTenantSecuritySolution ` - -SubscriptionId $HostSubscriptionId ` - -ScanHostRGName $HostResourceGroupName ` - -ScanIdentityId $UserAssignedIdentity.Id ` - -Location $Location ` - -WebAPIAzureADAppId $ADApplicationDetails.WebAPIAzureADAppId ` - -UIAzureADAppId $ADApplicationDetails.UIAzureADAppId ` - -AzureEnvironmentName $AzureEnvironmentName ` - -ScanIdentityHasGraphPermission:$ScanIdentityHasGraphPermission ` - -SendAlertNotificationToEmailIds $SendAlertNotificationToEmailIds ` - -EnableAutoUpdater ` - -EnableAzTSUI ` - -Verbose - - # OTHER SUPPORTED PARAMETERS (read more about its usage in AzTS github doc): - # 1. -EnableVnetIntegration - # 2. -EnableWAF - # 3. -CentralStorageAccountConnectionString "" - - - # iii) Save internal user-assigned managed identity name generated using below command. This will be used to grant Graph permission to internal MI. - $InternalIdentityObjectId = $DeploymentResult.Outputs.internalMIObjectId.Value - - # iv) Grant internal MI 'User.Read.All' permission. - - # **Note:** To complete this step, signed-in user must be a member of one of the following administrator roles: - # Required Permission: Global Administrator or Privileged Role Administrator. - # If you do not have the required permission, please contact your administrator. - # Read more about this under the section "Step 6 of 6. Run Setup Command" in GitHub doc. - - Grant-AzSKGraphPermissionToUserAssignedIdentity ` - -UserAssignedIdentityObjectId $InternalIdentityObjectId ` - -MSGraphPermissionsRequired @('User.Read.All') - - -<# ******************************************************************** - - Next step: Trigger AzTS Scan using On-Demand scan command - - ********************************************************************* #> - -# Note : If your AzTS solution is integrated to vnet, in that case uncomment switch '-EnableVnetIntegration' and then run below command to trigger AzTs scan +<# ********************************************************************* + + Script execution guidance + + ********************************************************************* #> + + # To run this script, select a command and press F8. + + +<# ********************************************************************* + + Installation Prerequisite + + ********************************************************************* #> + +# *** 1 of 6. Validate prerequisites on machine + #Ensure that you are using Windows OS and have PowerShell version 5.0 or higher + + $PSVersionTable + +# *** 2 of 6. Installing required Az modules + # Install required Az modules + # Required versions: + # Az.Accounts >= 2.9.0 + # Az.Resources >= 1.10.0 + # Az.Storage >= 2.0.0 + # Az.ManagedServiceIdentity >= 0.7.3 + # Az.Monitor >= 1.5.0 + # Az.OperationalInsights >= 1.3.4 + # Az.ApplicationInsights >= 1.0.3 + # Az.Websites >= 2.8.1 + # Az.Network >= 2.5.0 + # Az.FrontDoor >= 1.8.0 + # Az.CosmosDB >= 1.8.2 + + Install-Module -Name Az.Accounts -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.Resources -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.Storage -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.ManagedServiceIdentity -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.Monitor -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.OperationalInsights -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.ApplicationInsights -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.Websites -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.Network -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.FrontDoor -AllowClobber -Scope CurrentUser -repository PSGallery + Install-Module -Name Az.CosmosDB -AllowClobber -Scope CurrentUser -repository PSGallery + + # Install AzureAd + # Required version: + # AzureAD >= 2.0.2.130 + Install-Module -Name AzureAD -AllowClobber -Scope CurrentUser -repository PSGallery + +# **** 3 of 6. Download and extract deployment template + + # i) If not already done, unblock the content. Below command will help to unblock files. + + # Set extracted folder path + $DeploymentTemplateFolderPath = "" + + # Unblock files + Get-ChildItem -Path $DeploymentTemplateFolderPath -Recurse | Unblock-File + + # ii) Point current path to deployment folder and load AzTS setup script + + # Point current path to extracted folder location and load setup script from deployment folder + + CD "$DeploymentTemplateFolderPath" + + # Load AzTS Setup script in session + + . ".\AzTSSetup.ps1" + + +<# ******************************************************************** + + Log in to Azure Portal and Azure Active Directory (AD) + + ********************************************************************* #> + +# Tenant id where the AzTS solution needs to be installed. +$TenantId = "" + +# Connect to AzureAD and AzAccount with the tenant Id where you want to use AzTS solution. + +# NOTE: Tenant Id *must* be specified when connecting to Azure AD and AzAccount. + +# Clear existing login, if any +# If you are not already connected to Azure, Disconnect command will return an error. In this case, please ignore the error and continue to next step. +Disconnect-AzAccount +Disconnect-AzureAD + +# Connect to AzureAD and AzAccount + +Connect-AzAccount -Tenant $TenantId +Connect-AzureAD -TenantId $TenantId + + +<# ******************************************************************** + + Setting up central scan managed identity (scanner MI) + + ********************************************************************* + + .Summary + + The AzTS setup provisions your subscriptions with the ability to do daily scans for security controls. + To do the scanning, it requires a User-assigned Managed Identity (central scanning identity owned by you) which has 'Reader' access + on target subscriptions on which scan needs to be performed. + +#> + +# *** 4 of 6. Setting up scanning identity + + # ***** Initialize required parameters ****** + + # Subscription id in which scanner MI needs to be created. + $MIHostingSubId = "" + + # Resource group name in which scanner MI needs to be created. + $MIHostingRGName = "" + + # Location in which scanner MI needs to be created. + $Location = "" + + # Name of the scanner MI. + $MIName = "" + + # List of target subscription(s) that needs to be scanned by AzTS. + # This command assigns 'Reader' access to user-assigned managed identity on target subscriptions. Add target subscription id(s) in place of + # Alternatively, you can grant scanner MI 'Reader' access at management group scope instead of individual subscriptions. + $TargetSubscriptionIds = @("","","") + + + # i) You can create central scanning user-assigned managed identity (MI) with below PowerShell command + + # Step 1: Create scanner MI and grant 'Reader' permission on target subscriptions. + $UserAssignedIdentity = Set-AzSKTenantSecuritySolutionScannerIdentity -SubscriptionId $MIHostingSubId ` + -ResourceGroupName $MIHostingRGName ` + -Location $Location ` + -UserAssignedIdentityName $MIName ` + -TargetSubscriptionIds $TargetSubscriptionIds + + # Step 2: Save resource id and principal Id generated for user identity using below command. This will be used in AzTS Soln installation. + + # Resource id of the user-assigned managed identity + $UserAssignedIdentity.Id + + # Object id of the user-assigned managed identity + $UserAssignedIdentity.PrincipalId + + # ii) Grant MS Graph read permission to central scanner MI. + + # NOTE: This step requires admin consent. Therefore, the signed-in user must be a member of one of the following administrator roles: + # Required Permission: Global Administrator or Privileged Role Administrator. + # If you do not have the permission required to complete this step, please contact your administrator. + # To proceed without this step, set the value of "-ScanIdentityHasGraphPermission" parameter to false in AzTS installation command (Install-AzSKTenantSecuritySolution) below in 'step 6 of 6'. + # By setting '-ScanIdentityHasGraphPermission' to $false, you are choosing to disable features dependent on Graph API. + + Grant-AzSKGraphPermissionToUserAssignedIdentity -UserAssignedIdentityObjectId $UserAssignedIdentity.PrincipalId -MSGraphPermissionsRequired @("PrivilegedAccess.Read.AzureResources", "Directory.Read.All") -ADGraphPermissionsRequired @("Directory.Read.All") + +<# ******************************************************************** + + Setup AzTS solution + + ********************************************************************* #> + + +# ***** Initialize required parameters ****** + +# Subscription id in which Azure Tenant Security Solution needs to be installed. +$HostSubscriptionId = "" + +# Resource group name in which Azure Tenant Security Solution needs to be installed. +$HostResourceGroupName = "" + +# Location in which resources needs to be created. +# Note: For better performance, we recommend hosting the Central Scanner MI and resources setup using AzTS Soln installation command in one location. +$Location = "" # e.g. EastUS2 + +# Azure environment in which Azure Tenant Security Solution needs to be installed. The acceptable values for this parameter are: AzureCloud, AzureGovernmentCloud, AzureChinaCloud +$AzureEnvironmentName = "" + +# Specify if scanner MI has Graph permission. This is to exclude controls dependent on Graph API reponse from the scan result, if scanner identity does not have graph permission. +$ScanIdentityHasGraphPermission = $false + +# The installation creates alert for monitoring health of the AzTS Soln. +# Comma-separated list of user email ids who should be sent the monitoring email. +$SendAlertNotificationToEmailIds = @('', '', '') + + +# *** 5 of 6. Create Azure AD application for secure authentication + + + # Step 1: Setup AD application for AzTS UI and API + $ADApplicationDetails = Set-AzSKTenantSecurityADApplication -SubscriptionId $HostSubscriptionId -ScanHostRGName $HostResourceGroupName + + # Step 2: Save WebAPIAzureADAppId and UIAzureADAppId generated for Azure AD application using below command. This will be used in AzTS Soln installation. + + # Azure AD application client (application) ids + $ADApplicationDetails.WebAPIAzureADAppId + $ADApplicationDetails.UIAzureADAppId + + +# *** 6 of 6. Set context and validate you have 'Owner' access on subscription where solution needs to be installed **** + + # Run Setup Command + # i) Set the context to hosting subscription + Set-AzContext -SubscriptionId $HostSubscriptionId + + # ii) Run install solution command + # Note : To install AzTS setup with vnet integration, uncomment switch '-EnableVnetIntegration' and then run the installation command + # Note : To install AzTS setup with WAF enabled, uncomment switch '-EnableWAF' and then run the installation command + $DeploymentResult = Install-AzSKTenantSecuritySolution ` + -SubscriptionId $HostSubscriptionId ` + -ScanHostRGName $HostResourceGroupName ` + -ScanIdentityId $UserAssignedIdentity.Id ` + -Location $Location ` + -WebAPIAzureADAppId $ADApplicationDetails.WebAPIAzureADAppId ` + -UIAzureADAppId $ADApplicationDetails.UIAzureADAppId ` + -AzureEnvironmentName $AzureEnvironmentName ` + -ScanIdentityHasGraphPermission:$ScanIdentityHasGraphPermission ` + -SendAlertNotificationToEmailIds $SendAlertNotificationToEmailIds ` + -EnableAutoUpdater ` + -EnableAzTSUI ` + -Verbose + + # OTHER SUPPORTED PARAMETERS (read more about its usage in AzTS github doc): + # 1. -EnableVnetIntegration + # 2. -EnableWAF + # 3. -CentralStorageAccountConnectionString "" + + + # iii) Save internal user-assigned managed identity name generated using below command. This will be used to grant Graph permission to internal MI. + $InternalIdentityObjectId = $DeploymentResult.Outputs.internalMIObjectId.Value + + # iv) Grant internal MI 'User.Read.All' permission. + + # **Note:** To complete this step, signed-in user must be a member of one of the following administrator roles: + # Required Permission: Global Administrator or Privileged Role Administrator. + # If you do not have the required permission, please contact your administrator. + # Read more about this under the section "Step 6 of 6. Run Setup Command" in GitHub doc. + + Grant-AzSKGraphPermissionToUserAssignedIdentity ` + -UserAssignedIdentityObjectId $InternalIdentityObjectId ` + -MSGraphPermissionsRequired @('User.Read.All') + + +<# ******************************************************************** + + Next step: Trigger AzTS Scan using On-Demand scan command + + ********************************************************************* #> + +# Note : If your AzTS solution is integrated to vnet, in that case uncomment switch '-EnableVnetIntegration' and then run below command to trigger AzTs scan Start-AzSKTenantSecuritySolutionOnDemandScan -SubscriptionId $HostSubscriptionId -ScanHostRGName $HostResourceGroupName #-EnableVnetIntegration \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/KeyVaultMonitoringAlertTemplate.json b/TemplateFiles/DeploymentFiles/KeyVaultMonitoringAlertTemplate.json index bee5e81c..8ceab738 100644 --- a/TemplateFiles/DeploymentFiles/KeyVaultMonitoringAlertTemplate.json +++ b/TemplateFiles/DeploymentFiles/KeyVaultMonitoringAlertTemplate.json @@ -1,62 +1,62 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "UnintendedSecretAccessAlertQuery" : { - "type" : "string" - }, - "ActionGroupId" : { - "type" : "string" - }, - "LAResourceId" : { - "type" : "string" - }, - "Location" : { - "type" : "string" - } - }, - "variables": { - "laAlertSource":{ - "SourceId": "[parameters('LAResourceId')]", - "Type":"ResultCount" - }, - "actionGrp":{ - "ActionGroup": "[parameters('ActionGroupId')]" - }, - "unintendedSecretAccessAlert": "AzTS Secret access Alert", - "unintendedSecretAccessAlertDescription": "This is to notify you that AzTS scanning identity credentials, stored as secret in Key Vault has been accessed by some identity other than AzTS solution identity.`r`n**Next steps**`r`n Review the Key Vault activity logs, audit logs and access policy to identify the identity details and take appropriate action." - }, - "resources":[ - { - "name":"[variables('unintendedSecretAccessAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[parameters('Location')]", - "properties":{ - "description": "[variables('unintendedSecretAccessAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('UnintendedSecretAccessAlertQuery')]", - "dataSourceId": "[variables('laAlertSource').SourceId]", - "queryType":"[variables('laAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "60", - "timeWindowInMinutes": "60" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"1", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('unintendedSecretAccessAlert'))]" - }, - "trigger":{ - "thresholdOperator":"GreaterThan", - "threshold":"0" - } - } - } - } - ] +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "UnintendedSecretAccessAlertQuery" : { + "type" : "string" + }, + "ActionGroupId" : { + "type" : "string" + }, + "LAResourceId" : { + "type" : "string" + }, + "Location" : { + "type" : "string" + } + }, + "variables": { + "laAlertSource":{ + "SourceId": "[parameters('LAResourceId')]", + "Type":"ResultCount" + }, + "actionGrp":{ + "ActionGroup": "[parameters('ActionGroupId')]" + }, + "unintendedSecretAccessAlert": "AzTS Secret access Alert", + "unintendedSecretAccessAlertDescription": "This is to notify you that AzTS scanning identity credentials, stored as secret in Key Vault has been accessed by some identity other than AzTS solution identity.`r`n**Next steps**`r`n Review the Key Vault activity logs, audit logs and access policy to identify the identity details and take appropriate action." + }, + "resources":[ + { + "name":"[variables('unintendedSecretAccessAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[parameters('Location')]", + "properties":{ + "description": "[variables('unintendedSecretAccessAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('UnintendedSecretAccessAlertQuery')]", + "dataSourceId": "[variables('laAlertSource').SourceId]", + "queryType":"[variables('laAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "60", + "timeWindowInMinutes": "60" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"1", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('unintendedSecretAccessAlert'))]" + }, + "trigger":{ + "thresholdOperator":"GreaterThan", + "threshold":"0" + } + } + } + } + ] } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/MonitoringAlertTemplate.json b/TemplateFiles/DeploymentFiles/MonitoringAlertTemplate.json index 897794de..216388a6 100644 --- a/TemplateFiles/DeploymentFiles/MonitoringAlertTemplate.json +++ b/TemplateFiles/DeploymentFiles/MonitoringAlertTemplate.json @@ -1,288 +1,288 @@ -{ - "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", - "contentVersion": "1.0.0.0", - "parameters": { - "AutoUpdaterFailureAlertQuery" : { - "type" : "string" - }, - "AutoUpdaterNewReleaseAlertQuery": { - "type" : "string" - }, - "SubscriptionInvRefreshFailureAlertQuery": { - "type" : "string" - }, - "BaselineControlsInvRefreshFailureAlertQuery":{ - "type" : "string" - }, - "RBACInvRefreshFailureAlertQuery":{ - "type" : "string" - }, - "ControlResultsRefreshFailureAlertQuery":{ - "type" : "string" - }, - "ScanProgressSummaryQuery":{ - "type" : "string" - }, - "ActionGroupId" : { - "type" : "string" - }, - "AIResourceId" : { - "type" : "string" - }, - "LAResourceId" : { - "type" : "string" - }, - "IsAutoUpdaterEnabled": { - "type" : "bool", - "defaultValue": true - } - }, - "variables": { - "aiAlertSource":{ - "SourceId": "[parameters('AIResourceId')]", - "Type":"ResultCount" - }, - "laAlertSource":{ - "SourceId": "[parameters('LAResourceId')]", - "Type":"ResultCount" - }, - "actionGrp":{ - "ActionGroup": "[parameters('ActionGroupId')]" - }, - "autoUpdaterFailureAlert": "AzTS Auto-Updater Failure Alert", - "autoUpdaterFailureAlertDescription": "This is to notify you that AzTS Auto-Updater has not completed as expected in the last 24 hours.`r`n**Next steps**`r`n Run 'Install-AzSKTenantSecuritySolution' cmdlet. If this does not fix the issue, contact us at: azsksup@microsoft.com.", - "autoUpdaterNewReleaseAlert": "AzTS Service Updated to a New Version", - "autoUpdaterNewReleaseAlertDescription": "This is to notify you that AzTS service has been recently upgraded.", - "subscriptionInvRefreshFailureAlert" : "AzTS Subscription Inv Refresh Failure", - "subscriptionInvRefreshFailureAlertDescription" : "This is to notify you that subscription inventory has not refreshed in the last 24 hours. Try running the 'ATS_01_SubscriptionInvProcessor' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert. ", - "baselineControlsInvRefreshFailureAlert" : "AzTS Baseline Controls Inv Refresh Failure" , - "baselineControlsInvRefreshFailureAlertDescription" : "This is to notify you that baseline control inventory has not refreshed in the last 24 hours. Try running the 'ATS_02_BaselineControlsInvProcessor' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert. ", - "rbacInvRefreshFailureAlert" : "AzTS Subscription RBAC Inv Refresh Failure", - "rbacInvRefreshFailureAlertDescription" : "This is to notify you that subscription RBAC inventory has not refreshed in the last 24 hours. Try running the 'ATS_03_SubscriptionRBACProcessor' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert.", - "controlResultsRefreshFailureAlert" : "AzTS Control Results Refresh Failure", - "controlResultsRefreshFailureAlertDescription": "This is to notify you that control results have not refreshed in the last 24 hours. Try running the 'ATS_04_WorkItemScheduler' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert. ", - "scanProgressSummaryAlert" : "AzTS Scan Progress Summary", - "scanProgressSummaryAlertDescription": "This is to inform you of today's AzTS scan progress. Please use the following result to verify that the scan has completed for all subscriptions configured for the AzTS scan." - }, - "resources":[ - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "name":"[variables('autoUpdaterFailureAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('autoUpdaterFailureAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('AutoUpdaterFailureAlertQuery')]", - "dataSourceId": "[variables('aiAlertSource').SourceId]", - "queryType":"[variables('aiAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "2880" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"1", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('autoUpdaterFailureAlert'))]" - }, - "trigger":{ - "thresholdOperator":"Equal", - "threshold":"0" - } - } - } - }, - { - "condition": "[parameters('IsAutoUpdaterEnabled')]", - "name":"[variables('autoUpdaterNewReleaseAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('autoUpdaterNewReleaseAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('AutoUpdaterNewReleaseAlertQuery')]", - "dataSourceId": "[variables('aiAlertSource').SourceId]", - "queryType":"[variables('aiAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "1440" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"3", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('autoUpdaterNewReleaseAlert'))]" - }, - "trigger":{ - "thresholdOperator":"GreaterThan", - "threshold":"0" - } - } - } - }, - { - "name":"[variables('subscriptionInvRefreshFailureAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('subscriptionInvRefreshFailureAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('SubscriptionInvRefreshFailureAlertQuery')]", - "dataSourceId": "[variables('laAlertSource').SourceId]", - "queryType":"[variables('laAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "1440" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"1", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('subscriptionInvRefreshFailureAlert'))]" - }, - "trigger":{ - "thresholdOperator":"Equal", - "threshold":"0" - } - } - } - }, - { - "name":"[variables('baselineControlsInvRefreshFailureAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('baselineControlsInvRefreshFailureAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('BaselineControlsInvRefreshFailureAlertQuery')]", - "dataSourceId": "[variables('laAlertSource').SourceId]", - "queryType":"[variables('laAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "1440" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"1", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('baselineControlsInvRefreshFailureAlert'))]" - }, - "trigger":{ - "thresholdOperator":"Equal", - "threshold":"0" - } - } - } - }, - { - "name":"[variables('rbacInvRefreshFailureAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('rbacInvRefreshFailureAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('RBACInvRefreshFailureAlertQuery')]", - "dataSourceId": "[variables('laAlertSource').SourceId]", - "queryType":"[variables('laAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "1440" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"1", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('rbacInvRefreshFailureAlert'))]" - }, - "trigger":{ - "thresholdOperator":"Equal", - "threshold":"0" - } - } - } - }, - { - "name":"[variables('controlResultsRefreshFailureAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('controlResultsRefreshFailureAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('ControlResultsRefreshFailureAlertQuery')]", - "dataSourceId": "[variables('laAlertSource').SourceId]", - "queryType":"[variables('laAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "1440" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"1", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('controlResultsRefreshFailureAlert'))]" - }, - "trigger":{ - "thresholdOperator":"Equal", - "threshold":"0" - } - } - } - }, - { - "name":"[variables('scanProgressSummaryAlert')]", - "type":"Microsoft.Insights/scheduledQueryRules", - "apiVersion": "2018-04-16", - "location": "[resourceGroup().location]", - "properties":{ - "description": "[variables('scanProgressSummaryAlertDescription')]", - "enabled": true, - "source": { - "query": "[parameters('ScanProgressSummaryQuery')]", - "dataSourceId": "[variables('laAlertSource').SourceId]", - "queryType":"[variables('laAlertSource').Type]" - }, - "schedule":{ - "frequencyInMinutes": "1440", - "timeWindowInMinutes": "2880" - }, - "action":{ - "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", - "severity":"3", - "aznsAction":{ - "actionGroup":"[array(variables('actionGrp').ActionGroup)]", - "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('scanProgressSummaryAlert'))]" - }, - "trigger":{ - "thresholdOperator": "GreaterThanOrEqual", - "threshold":"0" - } - } - } - } - ] +{ + "$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentTemplate.json#", + "contentVersion": "1.0.0.0", + "parameters": { + "AutoUpdaterFailureAlertQuery" : { + "type" : "string" + }, + "AutoUpdaterNewReleaseAlertQuery": { + "type" : "string" + }, + "SubscriptionInvRefreshFailureAlertQuery": { + "type" : "string" + }, + "BaselineControlsInvRefreshFailureAlertQuery":{ + "type" : "string" + }, + "RBACInvRefreshFailureAlertQuery":{ + "type" : "string" + }, + "ControlResultsRefreshFailureAlertQuery":{ + "type" : "string" + }, + "ScanProgressSummaryQuery":{ + "type" : "string" + }, + "ActionGroupId" : { + "type" : "string" + }, + "AIResourceId" : { + "type" : "string" + }, + "LAResourceId" : { + "type" : "string" + }, + "IsAutoUpdaterEnabled": { + "type" : "bool", + "defaultValue": true + } + }, + "variables": { + "aiAlertSource":{ + "SourceId": "[parameters('AIResourceId')]", + "Type":"ResultCount" + }, + "laAlertSource":{ + "SourceId": "[parameters('LAResourceId')]", + "Type":"ResultCount" + }, + "actionGrp":{ + "ActionGroup": "[parameters('ActionGroupId')]" + }, + "autoUpdaterFailureAlert": "AzTS Auto-Updater Failure Alert", + "autoUpdaterFailureAlertDescription": "This is to notify you that AzTS Auto-Updater has not completed as expected in the last 24 hours.`r`n**Next steps**`r`n Run 'Install-AzSKTenantSecuritySolution' cmdlet. If this does not fix the issue, contact us at: azsksup@microsoft.com.", + "autoUpdaterNewReleaseAlert": "AzTS Service Updated to a New Version", + "autoUpdaterNewReleaseAlertDescription": "This is to notify you that AzTS service has been recently upgraded.", + "subscriptionInvRefreshFailureAlert" : "AzTS Subscription Inv Refresh Failure", + "subscriptionInvRefreshFailureAlertDescription" : "This is to notify you that subscription inventory has not refreshed in the last 24 hours. Try running the 'ATS_01_SubscriptionInvProcessor' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert. ", + "baselineControlsInvRefreshFailureAlert" : "AzTS Baseline Controls Inv Refresh Failure" , + "baselineControlsInvRefreshFailureAlertDescription" : "This is to notify you that baseline control inventory has not refreshed in the last 24 hours. Try running the 'ATS_02_BaselineControlsInvProcessor' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert. ", + "rbacInvRefreshFailureAlert" : "AzTS Subscription RBAC Inv Refresh Failure", + "rbacInvRefreshFailureAlertDescription" : "This is to notify you that subscription RBAC inventory has not refreshed in the last 24 hours. Try running the 'ATS_03_SubscriptionRBACProcessor' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert.", + "controlResultsRefreshFailureAlert" : "AzTS Control Results Refresh Failure", + "controlResultsRefreshFailureAlertDescription": "This is to notify you that control results have not refreshed in the last 24 hours. Try running the 'ATS_04_WorkItemScheduler' function manually. If this does not fix the issue, contact us at: azsksup@microsoft.com. Note: If you have recently install the setup and first scan is yet to be completed then you can ignore this alert. ", + "scanProgressSummaryAlert" : "AzTS Scan Progress Summary", + "scanProgressSummaryAlertDescription": "This is to inform you of today's AzTS scan progress. Please use the following result to verify that the scan has completed for all subscriptions configured for the AzTS scan." + }, + "resources":[ + { + "condition": "[parameters('IsAutoUpdaterEnabled')]", + "name":"[variables('autoUpdaterFailureAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('autoUpdaterFailureAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('AutoUpdaterFailureAlertQuery')]", + "dataSourceId": "[variables('aiAlertSource').SourceId]", + "queryType":"[variables('aiAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "2880" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"1", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('autoUpdaterFailureAlert'))]" + }, + "trigger":{ + "thresholdOperator":"Equal", + "threshold":"0" + } + } + } + }, + { + "condition": "[parameters('IsAutoUpdaterEnabled')]", + "name":"[variables('autoUpdaterNewReleaseAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('autoUpdaterNewReleaseAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('AutoUpdaterNewReleaseAlertQuery')]", + "dataSourceId": "[variables('aiAlertSource').SourceId]", + "queryType":"[variables('aiAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "1440" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"3", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('autoUpdaterNewReleaseAlert'))]" + }, + "trigger":{ + "thresholdOperator":"GreaterThan", + "threshold":"0" + } + } + } + }, + { + "name":"[variables('subscriptionInvRefreshFailureAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('subscriptionInvRefreshFailureAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('SubscriptionInvRefreshFailureAlertQuery')]", + "dataSourceId": "[variables('laAlertSource').SourceId]", + "queryType":"[variables('laAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "1440" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"1", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('subscriptionInvRefreshFailureAlert'))]" + }, + "trigger":{ + "thresholdOperator":"Equal", + "threshold":"0" + } + } + } + }, + { + "name":"[variables('baselineControlsInvRefreshFailureAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('baselineControlsInvRefreshFailureAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('BaselineControlsInvRefreshFailureAlertQuery')]", + "dataSourceId": "[variables('laAlertSource').SourceId]", + "queryType":"[variables('laAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "1440" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"1", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('baselineControlsInvRefreshFailureAlert'))]" + }, + "trigger":{ + "thresholdOperator":"Equal", + "threshold":"0" + } + } + } + }, + { + "name":"[variables('rbacInvRefreshFailureAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('rbacInvRefreshFailureAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('RBACInvRefreshFailureAlertQuery')]", + "dataSourceId": "[variables('laAlertSource').SourceId]", + "queryType":"[variables('laAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "1440" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"1", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('rbacInvRefreshFailureAlert'))]" + }, + "trigger":{ + "thresholdOperator":"Equal", + "threshold":"0" + } + } + } + }, + { + "name":"[variables('controlResultsRefreshFailureAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('controlResultsRefreshFailureAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('ControlResultsRefreshFailureAlertQuery')]", + "dataSourceId": "[variables('laAlertSource').SourceId]", + "queryType":"[variables('laAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "1440" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"1", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('controlResultsRefreshFailureAlert'))]" + }, + "trigger":{ + "thresholdOperator":"Equal", + "threshold":"0" + } + } + } + }, + { + "name":"[variables('scanProgressSummaryAlert')]", + "type":"Microsoft.Insights/scheduledQueryRules", + "apiVersion": "2018-04-16", + "location": "[resourceGroup().location]", + "properties":{ + "description": "[variables('scanProgressSummaryAlertDescription')]", + "enabled": true, + "source": { + "query": "[parameters('ScanProgressSummaryQuery')]", + "dataSourceId": "[variables('laAlertSource').SourceId]", + "queryType":"[variables('laAlertSource').Type]" + }, + "schedule":{ + "frequencyInMinutes": "1440", + "timeWindowInMinutes": "2880" + }, + "action":{ + "odata.type": "Microsoft.WindowsAzure.Management.Monitoring.Alerts.Models.Microsoft.AppInsights.Nexus.DataContracts.Resources.ScheduledQueryRules.AlertingAction", + "severity":"3", + "aznsAction":{ + "actionGroup":"[array(variables('actionGrp').ActionGroup)]", + "emailSubject": "[concat('AzTS MONITORING ALERT: ',variables('scanProgressSummaryAlert'))]" + }, + "trigger":{ + "thresholdOperator": "GreaterThanOrEqual", + "threshold":"0" + } + } + } + } + ] } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/OnDemandScan.ps1 b/TemplateFiles/DeploymentFiles/OnDemandScan.ps1 index 1a4e8cef..5857014e 100644 --- a/TemplateFiles/DeploymentFiles/OnDemandScan.ps1 +++ b/TemplateFiles/DeploymentFiles/OnDemandScan.ps1 @@ -1,539 +1,539 @@ -########### Load Common Functions And Classes ############### - -function Start-AzSKTenantSecuritySolutionOnDemandScan -{ - <# - .SYNOPSIS - This command would help in installing Azure Tenant Security Solution in your subscription. - .DESCRIPTION - This command will install an Azure Tenant Security Solution which runs security scan on subscription in a Tenant. - Security scan results will be populated in Log Analytics workspace and Azure Storage account which is configured during installation. - - .PARAMETER SubscriptionId - Subscription id in which Azure Tenant Security Solution needs to be installed. - .PARAMETER ScanHostRGName - Name of ResourceGroup where setup resources will be created. - - #> - param( - [string] - [Parameter(Mandatory = $true, HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where setup resources will be created.")] - $ScanHostRGName = "AzSK-AzTS-RG", - - [switch] - $ForceFetch, - - [switch] - [Parameter(Mandatory = $false, HelpMessage="Switch to specify if AzTS setup is integrated to vnet or not.")] - $EnableVnetIntegration - - ) - Begin - { - $currentContext = $null - $contextHelper = [ContextHelper]::new() - $currentContext = $contextHelper.SetContext($SubscriptionId) - $resourceManagerUrl = $currentContext.Environment.ResourceManagerUrl - if(-not ($currentContext -and $resourceManagerUrl)) - { - return; - } - . "$PSScriptRoot\TokenProvider.ps1" - } - Process - { - if(-not $EnableVnetIntegration) - { - $maFunctionApp = $null - try - { - Write-Host $([ScannerConstants]::DoubleDashLine) - Write-Host "Running Azure Tenant Security Solution...`n" -ForegroundColor Cyan - Write-Host $([ScannerConstants]::OnDemandScanInstructionMsg ) -ForegroundColor Cyan - Write-Host $([ScannerConstants]::OnDemandScanWarningMsg ) -ForegroundColor Yellow - Write-Host $([ScannerConstants]::SingleDashLine) - - $StartTimeAsString = [Datetime]::UtcNow.ToString("MM/dd/yyyy") - - $maFunctionApp = Get-AzWebApp -ResourceGroupName $ScanHostRGName | Where-Object { $_.Name -match "MetadataAggregator"} | Select -First 1 - $applicationInsight = Get-AzApplicationInsights -ResourceGroupName $ScanHostRGName | Where-Object { $_.Name -match "AzSK-AzTS-AppInsights"} | Select -First 1 - $laWorkspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $ScanHostRGName | Where-Object { $_.Name -match "AzSK-AzTS-LAWorkspace"} | Select -First 1 - - if(($maFunctionApp -ne $null) -and ($applicationInsight -ne $null) -and ($laWorkspace -ne $null)) - { - if($ForceFetch) - { - Write-Host "[WARNING] Enabling forceFetch for [$($maFunctionApp.Name)] function app." -ForegroundColor Yellow - $StartTimeAsString = [Datetime]::UtcNow.ToString("MM/dd/yyyy, HH:mm:ss") - $maFunctionAppSlot = Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' - $appSettings = $maFunctionAppSlot.SiteConfig.AppSettings - #setup the current app settings - $settings = @{} - ForEach ($isetting in $appSettings) { - $settings[$isetting.Name] = $isetting.Value - } - - $settings['WebJobConfigurations__ForceFetch'] = $true.ToString().Tolower() - $updatedSlotDetails = Set-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' -AppSettings $settings; - } - - $functionAppHostName = "https://" + $maFunctionApp.DefaultHostName; - $functionAppKeys = GetFunctionAppKey -AppServiceResourceId $maFunctionApp.Id - $functionAppMaterKey = $functionAppKeys.masterKey; - $laWorkspaceId = $laWorkspace.CustomerId.Guid - TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionInvProcessor) -FunctionAppMaterKey $functionAppMaterKey - TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.BaselineControlsInvProcessor) -FunctionAppMaterKey $functionAppMaterKey - WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionInvProcessor) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId - WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.BaselineControlsInvProcessor) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId - - TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionRBACProcessor) -FunctionAppMaterKey $functionAppMaterKey - WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionRBACProcessor) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId - - TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.WorkItemScheduler) -FunctionAppMaterKey $functionAppMaterKey - WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.WorkItemScheduler) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId - - Write-Host "$([Constants]::DoubleDashLine)" #-ForegroundColor $([Constants]::MessageType.Info) - Write-Host "$([ScannerConstants]::NextStepsMsg)" -ForegroundColor Cyan - Write-Host "$([Constants]::DoubleDashLine)" - - } - else - { - Write-Host "Error occurred while triggering on-demand scan. ErrorMessage [MetadataAggregator function app, Application Insight or Log Analytics workspace not found.]" -ForegroundColor $([Constants]::MessageType.Error) - } - } - catch - { - Write-Host "Error occurred while triggering AzTS scan. ExceptionMessage [$($_)]" - } - finally - { - if($ForceFetch -and $maFunctionApp -ne $null) - { - Write-Host "[WARNING] Disabling forceFetch for [$($maFunctionApp.Name)] function app." -ForegroundColor Yellow - $maFunctionAppSlot = Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' - $appSettings = $maFunctionAppSlot.SiteConfig.AppSettings - #setup the current app settings - $settings = @{} - ForEach ($isetting in $appSettings) { - $settings[$isetting.Name] = $isetting.Value - } - - $settings['WebJobConfigurations__ForceFetch'] = $false.ToString().Tolower() - $updatedSlotDetails = Set-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' -AppSettings $settings; - } - } - } - else - { - - $StorageAccount = $null - $queueName = "ondemandprocessingqueue" - try - { - Write-Host $([ScannerConstants]::DoubleDashLine) - Write-Host "Running Azure Tenant Security Solution...`n" -ForegroundColor Cyan - Write-Host $([ScannerConstants]::QueueInstructionMsg ) -ForegroundColor Cyan - Write-Host $([ScannerConstants]::ClientIpInstructionMsg ) -ForegroundColor Yellow - - $ClientIpAdditionFlag = Read-Host -Prompt "Allow addition of client IP to AzTS storage account (Y/N)" - if($ClientIpAdditionFlag -eq 'Y') - { - while([string]::IsNullOrWhiteSpace($clientIPAddress)) - { - $clientIPAddress = Read-Host -Prompt "`nPlease provide client IP address " - } - } - else - { - Write-Host "`n Terminated command execution for Client IP addition." -ForegroundColor Cyan - Write-Host $([ScannerConstants]::DoubleDashLine) - break; - } - - Write-Host $([ScannerConstants]::SingleDashLine) - - if($SubscriptionId -ne $null -and $ScanHostRGName -ne $null) - { - $StorageAccount = Get-AzStorageAccount -ResourceGroupName $ScanHostRGName | Where-Object { $_.StorageAccountName -match "azskaztsstorage"} | Select -First 1 - } - else - { - throw [System.ArgumentException] ("Unable to fetch storage account details. Please check if you have access to host RG"); - } - - - #Adding client ip to storage - Write-Host "Adding client IP to AzTS storage account [$($StorageAccount.StorageAccountName)], this may take 1-2 mins. `n" -ForegroundColor Yellow - - $IpAddedToStorage = Add-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName -IPAddressOrRange $clientIPAddress - - if(-not $IpAddedToStorage) - { - throw [System.ArgumentException] ("Error occurred while adding client IP to storage account."); - } - - Start-Sleep -Seconds 30 # waiting for 30 sec for IP to be successfully added in storage account - Write-Host "Successfully added client IP [$($clientIPAddress)] to AzTS storage account [$($StorageAccount.StorageAccountName)]. `n" -ForegroundColor Cyan - - #fetching storage account access keys - $storageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $ScanHostRGName -Name $storageAccount.StorageAccountName -ErrorAction Stop - - if(-not $storageAccountKey) - { - throw [System.ArgumentException] ("Unable to fetch 'storageAccountKey'. Please check if you have the access to read storage key."); - } - else - { - $storageAccountKey = $storageAccountKey.Value[0] - } - - $storageContext = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -StorageAccountKey $storageAccountKey -ErrorAction Stop - - #check if queue exists, else create new - if($queueName -ne $null) - { - $queue = Get-AzStorageQueue –Name $queueName –Context $storageContext -ErrorAction SilentlyContinue - if(-not $queue) - { - $queue = New-AzStorageQueue -Name $queueName -Context $storageContext -ErrorAction Stop - } - } - - #add message to the queue - $queueMessage = [Microsoft.Azure.Storage.Queue.CloudQueueMessage]::new("{""AzTSHostsubscriptionId"":""$($SubscriptionId)"", ""AzTSHostResourceGroup"":""$($ScanHostRGName)""}"); - - if($queueMessage -ne $null) - { - $message = $queue.CloudQueue.AddMessageAsync($queueMessage) - Write-Host "Successfully added message to AzTS storage account [$($StorageAccount.StorageAccountName)] for initiating on demand processing. `n" -ForegroundColor Cyan - } - else - { - throw [System.ArgumentException] ("Unable to add message to queue."); - } - - #removing client IP from storage - $removedIp = Remove-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName -IPAddressOrRange $clientIPAddress - Write-Host "Removed client IP [$($clientIPAddress)] from AzTS storage account [$($StorageAccount.StorageAccountName)]." -ForegroundColor Cyan - - Write-Host "$([Constants]::DoubleDashLine)" - Write-Host "$([ScannerConstants]::NextStepsMsg)" -ForegroundColor Cyan - Write-Host "$([Constants]::DoubleDashLine)" - - } - catch - { - Write-Host "Error occurred while adding client IP to storage account. Please make sure you have provided correct IP address and you have proper permissions (at least Contributor) on host RG and subscription. ExceptionMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) - } - finally - { - if($ClientIpAdditionFlag -eq 'Y') - { - $IpAddList = (Get-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName).IPRules - if($clientIPAddress -in $IpAddList.IPAddressOrRange) - { - $removedIp = Remove-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName -IPAddressOrRange $clientIPAddress - } - } - } - } - } -} - -function WaitForFunctionToComplete -{ - param ( - [ValidateNotNullOrEmpty()] - [string] $FunctionName, - - [ValidateNotNullOrEmpty()] - [string] $StartTimeAsString, - - [ValidateNotNullOrEmpty()] - [string] $ApplicationInsightId, - - [ValidateNotNullOrEmpty()] - [string] $LAWorkspaceId - ) - - $FunctionAppStatus = [EventStatus]::NotCompleted - $LAStatus = [EventStatus]::NotCompleted - Write-Host "Waiting for [$($FunctionName)] function to complete its job." -ForegroundColor Yellow - Write-Host "This operation can take up to 15 minutes (approx)." -NoNewline -ForegroundColor Yellow - @(1..15) | ForEach-Object { - $FunctionAppStatus = EventProcessor -StartTimeAsString $StartTimeAsString -FunctionName $FunctionName -ApplicationInsightId $ApplicationInsightId - $LAStatus = LogAnalyticsEventProcessor -StartTimeAsString $StartTimeAsString -FunctionName $FunctionName -WorkspaceId $LAWorkspaceId - if($FunctionAppStatus -ne [EventStatus]::Completed) - { - Write-Host ..$($_) -NoNewline -ForegroundColor Yellow; - Start-Sleep -Seconds 60 - } - elseif($LAStatus -ne [EventStatus]::Completed) - { - Write-Host ..$($_) -NoNewline -ForegroundColor Yellow; - Start-Sleep -Seconds 60 - } - else - { - # No Action - } - } - - Write-Host "" - if($FunctionAppStatus -eq [EventStatus]::Completed -and $LAStatus -eq [EventStatus]::Completed) - { - Write-Host "[$($FunctionName)] completed proccessing." -ForegroundColor Cyan - } - else - { - Write-Host "Exceeded max wait time. [$($FunctionName)] is taking longer than expected to process. Continue to the next step." -ForegroundColor Cyan - } -} - -function GetFunctionAppKey -{ - - param ( - [ValidateNotNullOrEmpty()] - [string] $AppServiceResourceId - ) - - try - { - if ($resourceManagerUrl) - { - $functionAppListKeyURL = $resourceManagerUrl + $AppServiceResourceId + "/host/default/listkeys?api-version=2018-11-01" - - $headers = [TokenProvider]::new().GetAuthHeader($resourceManagerUrl); - } - $functionAppKeysResponse = Invoke-WebRequest -UseBasicParsing -Uri $functionAppListKeyURL -Method Post -Headers $headers -Body '{}' - - $functionAppKeys = $functionAppKeysResponse.Content | ConvertFrom-Json - return $functionAppKeys - } - catch - { - throw $_ - } -} - - -function TriggerFunction -{ - - param ( - [ValidateNotNullOrEmpty()] - [string] $FunctionName, - - [ValidateNotNullOrEmpty()] - [string] $FunctionAppHostName, - - [ValidateNotNullOrEmpty()] - [string] $FunctionAppMaterKey - ) - - $maxRetryCount = 3 - $retryCount = 0 - - try - { - while($retryCount -lt $maxRetryCount) - { - try - { - $baseFunctionAppTriggerURL = $FunctionAppHostName + "/admin/functions/{0}"; - $functionAppTriggerURL = ([string]::Format($baseFunctionAppTriggerURL, $FunctionName)); - - Write-Host "Starting [$($FunctionName)] function." -ForegroundColor Cyan - $response = Invoke-WebRequest -UseBasicParsing -Uri $functionAppTriggerURL -Method Post -Headers @{ "x-functions-key" = "$($FunctionAppMaterKey)";"Content-Type"="application/json" } -Body '{}' - Write-Host "Successfully triggered [$($FunctionName)] function." -ForegroundColor Cyan - $retryCount = $maxRetryCount - } - catch - { - $retryCount += 1; - if ($retryCount -ge $maxRetryCount) - { - throw $($_); - } - else - { - Start-Sleep -Seconds (30 * $retryCount) - } - } - }# WhileEnd - } - catch - { - Write-Host "Error occurred while triggering function app [$($FunctionName)] ExceptionMessage [$($_)]. Please validate that the function is in running state and run this command again." -ForegroundColor Red - } -} - -function EventProcessor -{ - param( - [ValidateNotNullOrEmpty()] - [string] $StartTimeAsString, - - [ValidateNotNullOrEmpty()] - [string] $FunctionName, - - [ValidateNotNullOrEmpty()] - [string] $ApplicationInsightId - ) - - $Status = [EventStatus]::NotCompleted - - try - { - if ($resourceManagerUrl) - { - $aiQueryAPI = $resourceManagerUrl + $ApplicationInsightId + "/query?api-version=2018-04-20&query=traces - | where customDimensions.LogLevel contains 'Information' - | where timestamp > todatetime('{0}') - | where customDimensions.Category contains '{1}' and customDimensions.EventId == 2001 - | project StatusId = customDimensions.EventId" -f $StartTimeAsString, $FunctionName - - $headers = [TokenProvider]::new().GetAuthHeader($resourceManagerUrl); - } - $response = Invoke-WebRequest -UseBasicParsing -Uri $aiQueryAPI -Method Get -Headers $headers - - if($response -ne $null) - { - $customObject = $response.Content | ConvertFrom-Json - - if(($customObject | GM tables) -and ($customObject.tables -ne $null) -and ($customObject.tables[0] | GM rows) -and ($customObject.tables[0].rows -ne $null)) - { - $Status = [EventStatus]::Completed - } - } - } - catch - { - throw $_ - } - - return $Status; -} - - -function LogAnalyticsEventProcessor -{ - param( - [ValidateNotNullOrEmpty()] - [string] $StartTimeAsString, - - [ValidateNotNullOrEmpty()] - [string] $FunctionName, - - [ValidateNotNullOrEmpty()] - [string] $WorkspaceId - ) - - $Status = [EventStatus]::NotCompleted - - try - { - $LAQuery = [string]::Empty - - switch($FunctionName) - { - $([ScannerConstants]::FunctionApp.SubscriptionInvProcessor) { $LAQuery = $([ScannerConstants]::SubscriptionInvLAQuery -f $StartTimeAsString) } - $([ScannerConstants]::FunctionApp.BaselineControlsInvProcessor) { $LAQuery = $([ScannerConstants]::BaselineControlsInvLAQuery -f $StartTimeAsString) } - $([ScannerConstants]::FunctionApp.SubscriptionRBACProcessor) { $LAQuery = $([ScannerConstants]::RBACInvLAQuery -f $StartTimeAsString) } - $([ScannerConstants]::FunctionApp.WorkItemScheduler) { $LAQuery = $([ScannerConstants]::ControlResultsLAQuery -f $StartTimeAsString) } - } - - if(![string]::IsNullOrWhiteSpace($LAQuery)) - { - - $Result = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceId -Query $LAQuery - if(($Result.Results | Measure-Object).Count -gt 0) - { - $Status = [EventStatus]::Completed - } - - } - } - catch - { - Write-Host "Error occurred while validating result in Log Analytics. ExceptionMessage [$($_.Exception.Message)]". - } - - return $Status; -} - -enum EventStatus -{ - NotCompleted - Completed -} - -class ScannerConstants -{ - static [string] $OnDemandScanInstructionMsg = "This command will perform 4 important operations. It will:`r`n`n" + - " [1] Trigger subscription inventory processor `r`n" + - " [2] Trigger baseline controls inventory processor `r`n" + - " [3] Trigger Role-Based Access Control (RBAC) processor `r`n" + - " [4] Trigger work item scheduler `r`n"; - static [string] $OnDemandScanWarningMsg = "Please note that if the AzTS Soln has been setup recently, this command can take up to 30-45 minutes as it has to create tables in Log Analytics workspace for each inventory that is processed as part of this command."; - static [string] $NextStepsMsg = "Subscriptions have been queued for scan. The scan result will be available in the next 2 hours."; - - static [string] $QueueInstructionMsg = "This command will perform below activities. It will:`r`n`n" + - " [1] Send on-demand processing request using storage queue. `r`n" + - " [2] Using on-demand processing request, it will trigger below AzTS processing functions in sequence: `r`n" + - " [a] Subscription inventory processor: Collects subscription list to be scanned `r`n" + - " [b] Controls inventory processor: Collects control inventory `r`n" + - " [c] Role-Based Access Control (RBAC) processor: Collects RBAC data `r`n" + - " [d] WorkItem scheduler: Send subscription list from inventory to scanning queue `r`n" + - " [3] WorkItemProccessor will get auto triggered based on scanning queue messages and will scan the controls. `r`n"; - - static [string] $ClientIpInstructionMsg = "As your AzTS solution is integrated to VNet, i.e. resources like storage have access restrictions and firewall applied, therefore to trigger on-demand processing from your machine, we will need to add your client IP to AzTS storage account firewall (present inside host RG where your AzTS solution is hosted) on temporary basis. Once the message is added to the scanning queue, your IP will be removed from storage account. `r`n" ; - - static [string] $DoubleDashLine = "================================================================================" - static [string] $SingleDashLine = "--------------------------------------------------------------------------------" - - static [string] $SubscriptionInvLAQuery = "let TablePlaceholder = view () {{print SubscriptionId = 'SubscriptionIdNotFound'}}; - let SubInventory_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_SubInventory_CL | where TimeGenerated > todatetime('{0}') - | distinct SubscriptionId - )) - | where SubscriptionId !~ 'SubscriptionIdNotFound'; - SubInventory_CL"; - static [string] $BaselineControlsInvLAQuery = "let TablePlaceholder = view () {{print ControlId_s = 'NA'}}; - let BaselineControlsInv_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_BaselineControlsInv_CL | where TimeGenerated > todatetime('{0}') - | distinct ControlId_s - )) - | where ControlId_s !~ 'NA'; - BaselineControlsInv_CL"; - static [string] $RBACInvLAQuery = "let TablePlaceholder = view () {{print NameId = 'NA', RoleId = 'NA'}}; - let RBAC_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_RBAC_CL | where TimeGenerated > todatetime('{0}') - | take 10 - | project RoleId = coalesce(RoleId_g, RoleId_s), NameId = NameId_g - )) - | where NameId !~ 'NA'; - RBAC_CL"; - static [string] $ControlResultsLAQuery = "let TablePlaceholder = view () {{print SubscriptionId = 'SubscriptionIdNotFound'}}; - let ControlResults_CL = union isfuzzy=true TablePlaceholder, (union ( - AzSK_ControlResults_CL | where TimeGenerated > todatetime('{0}') - | distinct SubscriptionId - )) - | where SubscriptionId !~ 'SubscriptionIdNotFound'; - ControlResults_CL - | take 10"; - - static [Hashtable] $FunctionApp = @{ - SubscriptionInvProcessor = 'ATS_01_SubscriptionInvProcessor' - BaselineControlsInvProcessor = 'ATS_02_BaselineControlsInvProcessor' - SubscriptionRBACProcessor = 'ATS_03_SubscriptionRBACProcessor' - WorkItemScheduler = 'ATS_04_WorkItemScheduler' - } - +########### Load Common Functions And Classes ############### + +function Start-AzSKTenantSecuritySolutionOnDemandScan +{ + <# + .SYNOPSIS + This command would help in installing Azure Tenant Security Solution in your subscription. + .DESCRIPTION + This command will install an Azure Tenant Security Solution which runs security scan on subscription in a Tenant. + Security scan results will be populated in Log Analytics workspace and Azure Storage account which is configured during installation. + + .PARAMETER SubscriptionId + Subscription id in which Azure Tenant Security Solution needs to be installed. + .PARAMETER ScanHostRGName + Name of ResourceGroup where setup resources will be created. + + #> + param( + [string] + [Parameter(Mandatory = $true, HelpMessage="Subscription id in which Azure Tenant Security Solution needs to be installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage="Name of ResourceGroup where setup resources will be created.")] + $ScanHostRGName = "AzSK-AzTS-RG", + + [switch] + $ForceFetch, + + [switch] + [Parameter(Mandatory = $false, HelpMessage="Switch to specify if AzTS setup is integrated to vnet or not.")] + $EnableVnetIntegration + + ) + Begin + { + $currentContext = $null + $contextHelper = [ContextHelper]::new() + $currentContext = $contextHelper.SetContext($SubscriptionId) + $resourceManagerUrl = $currentContext.Environment.ResourceManagerUrl + if(-not ($currentContext -and $resourceManagerUrl)) + { + return; + } + . "$PSScriptRoot\TokenProvider.ps1" + } + Process + { + if(-not $EnableVnetIntegration) + { + $maFunctionApp = $null + try + { + Write-Host $([ScannerConstants]::DoubleDashLine) + Write-Host "Running Azure Tenant Security Solution...`n" -ForegroundColor Cyan + Write-Host $([ScannerConstants]::OnDemandScanInstructionMsg ) -ForegroundColor Cyan + Write-Host $([ScannerConstants]::OnDemandScanWarningMsg ) -ForegroundColor Yellow + Write-Host $([ScannerConstants]::SingleDashLine) + + $StartTimeAsString = [Datetime]::UtcNow.ToString("MM/dd/yyyy") + + $maFunctionApp = Get-AzWebApp -ResourceGroupName $ScanHostRGName | Where-Object { $_.Name -match "MetadataAggregator"} | Select -First 1 + $applicationInsight = Get-AzApplicationInsights -ResourceGroupName $ScanHostRGName | Where-Object { $_.Name -match "AzSK-AzTS-AppInsights"} | Select -First 1 + $laWorkspace = Get-AzOperationalInsightsWorkspace -ResourceGroupName $ScanHostRGName | Where-Object { $_.Name -match "AzSK-AzTS-LAWorkspace"} | Select -First 1 + + if(($maFunctionApp -ne $null) -and ($applicationInsight -ne $null) -and ($laWorkspace -ne $null)) + { + if($ForceFetch) + { + Write-Host "[WARNING] Enabling forceFetch for [$($maFunctionApp.Name)] function app." -ForegroundColor Yellow + $StartTimeAsString = [Datetime]::UtcNow.ToString("MM/dd/yyyy, HH:mm:ss") + $maFunctionAppSlot = Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' + $appSettings = $maFunctionAppSlot.SiteConfig.AppSettings + #setup the current app settings + $settings = @{} + ForEach ($isetting in $appSettings) { + $settings[$isetting.Name] = $isetting.Value + } + + $settings['WebJobConfigurations__ForceFetch'] = $true.ToString().Tolower() + $updatedSlotDetails = Set-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' -AppSettings $settings; + } + + $functionAppHostName = "https://" + $maFunctionApp.DefaultHostName; + $functionAppKeys = GetFunctionAppKey -AppServiceResourceId $maFunctionApp.Id + $functionAppMaterKey = $functionAppKeys.masterKey; + $laWorkspaceId = $laWorkspace.CustomerId.Guid + TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionInvProcessor) -FunctionAppMaterKey $functionAppMaterKey + TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.BaselineControlsInvProcessor) -FunctionAppMaterKey $functionAppMaterKey + WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionInvProcessor) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId + WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.BaselineControlsInvProcessor) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId + + TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionRBACProcessor) -FunctionAppMaterKey $functionAppMaterKey + WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.SubscriptionRBACProcessor) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId + + TriggerFunction -FunctionAppHostName $functionAppHostName -FunctionName $([ScannerConstants]::FunctionApp.WorkItemScheduler) -FunctionAppMaterKey $functionAppMaterKey + WaitForFunctionToComplete -StartTimeAsString $StartTimeAsString -FunctionName $([ScannerConstants]::FunctionApp.WorkItemScheduler) -ApplicationInsightId $applicationInsight.Id -LAWorkspaceId $laWorkspaceId + + Write-Host "$([Constants]::DoubleDashLine)" #-ForegroundColor $([Constants]::MessageType.Info) + Write-Host "$([ScannerConstants]::NextStepsMsg)" -ForegroundColor Cyan + Write-Host "$([Constants]::DoubleDashLine)" + + } + else + { + Write-Host "Error occurred while triggering on-demand scan. ErrorMessage [MetadataAggregator function app, Application Insight or Log Analytics workspace not found.]" -ForegroundColor $([Constants]::MessageType.Error) + } + } + catch + { + Write-Host "Error occurred while triggering AzTS scan. ExceptionMessage [$($_)]" + } + finally + { + if($ForceFetch -and $maFunctionApp -ne $null) + { + Write-Host "[WARNING] Disabling forceFetch for [$($maFunctionApp.Name)] function app." -ForegroundColor Yellow + $maFunctionAppSlot = Get-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' + $appSettings = $maFunctionAppSlot.SiteConfig.AppSettings + #setup the current app settings + $settings = @{} + ForEach ($isetting in $appSettings) { + $settings[$isetting.Name] = $isetting.Value + } + + $settings['WebJobConfigurations__ForceFetch'] = $false.ToString().Tolower() + $updatedSlotDetails = Set-AzWebAppSlot -ResourceGroupName $ScanHostRGName -Name $maFunctionApp.Name -Slot 'production' -AppSettings $settings; + } + } + } + else + { + + $StorageAccount = $null + $queueName = "ondemandprocessingqueue" + try + { + Write-Host $([ScannerConstants]::DoubleDashLine) + Write-Host "Running Azure Tenant Security Solution...`n" -ForegroundColor Cyan + Write-Host $([ScannerConstants]::QueueInstructionMsg ) -ForegroundColor Cyan + Write-Host $([ScannerConstants]::ClientIpInstructionMsg ) -ForegroundColor Yellow + + $ClientIpAdditionFlag = Read-Host -Prompt "Allow addition of client IP to AzTS storage account (Y/N)" + if($ClientIpAdditionFlag -eq 'Y') + { + while([string]::IsNullOrWhiteSpace($clientIPAddress)) + { + $clientIPAddress = Read-Host -Prompt "`nPlease provide client IP address " + } + } + else + { + Write-Host "`n Terminated command execution for Client IP addition." -ForegroundColor Cyan + Write-Host $([ScannerConstants]::DoubleDashLine) + break; + } + + Write-Host $([ScannerConstants]::SingleDashLine) + + if($SubscriptionId -ne $null -and $ScanHostRGName -ne $null) + { + $StorageAccount = Get-AzStorageAccount -ResourceGroupName $ScanHostRGName | Where-Object { $_.StorageAccountName -match "azskaztsstorage"} | Select -First 1 + } + else + { + throw [System.ArgumentException] ("Unable to fetch storage account details. Please check if you have access to host RG"); + } + + + #Adding client ip to storage + Write-Host "Adding client IP to AzTS storage account [$($StorageAccount.StorageAccountName)], this may take 1-2 mins. `n" -ForegroundColor Yellow + + $IpAddedToStorage = Add-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName -IPAddressOrRange $clientIPAddress + + if(-not $IpAddedToStorage) + { + throw [System.ArgumentException] ("Error occurred while adding client IP to storage account."); + } + + Start-Sleep -Seconds 30 # waiting for 30 sec for IP to be successfully added in storage account + Write-Host "Successfully added client IP [$($clientIPAddress)] to AzTS storage account [$($StorageAccount.StorageAccountName)]. `n" -ForegroundColor Cyan + + #fetching storage account access keys + $storageAccountKey = Get-AzStorageAccountKey -ResourceGroupName $ScanHostRGName -Name $storageAccount.StorageAccountName -ErrorAction Stop + + if(-not $storageAccountKey) + { + throw [System.ArgumentException] ("Unable to fetch 'storageAccountKey'. Please check if you have the access to read storage key."); + } + else + { + $storageAccountKey = $storageAccountKey.Value[0] + } + + $storageContext = New-AzStorageContext -StorageAccountName $storageAccount.StorageAccountName -StorageAccountKey $storageAccountKey -ErrorAction Stop + + #check if queue exists, else create new + if($queueName -ne $null) + { + $queue = Get-AzStorageQueue –Name $queueName –Context $storageContext -ErrorAction SilentlyContinue + if(-not $queue) + { + $queue = New-AzStorageQueue -Name $queueName -Context $storageContext -ErrorAction Stop + } + } + + #add message to the queue + $queueMessage = [Microsoft.Azure.Storage.Queue.CloudQueueMessage]::new("{""AzTSHostsubscriptionId"":""$($SubscriptionId)"", ""AzTSHostResourceGroup"":""$($ScanHostRGName)""}"); + + if($queueMessage -ne $null) + { + $message = $queue.CloudQueue.AddMessageAsync($queueMessage) + Write-Host "Successfully added message to AzTS storage account [$($StorageAccount.StorageAccountName)] for initiating on demand processing. `n" -ForegroundColor Cyan + } + else + { + throw [System.ArgumentException] ("Unable to add message to queue."); + } + + #removing client IP from storage + $removedIp = Remove-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName -IPAddressOrRange $clientIPAddress + Write-Host "Removed client IP [$($clientIPAddress)] from AzTS storage account [$($StorageAccount.StorageAccountName)]." -ForegroundColor Cyan + + Write-Host "$([Constants]::DoubleDashLine)" + Write-Host "$([ScannerConstants]::NextStepsMsg)" -ForegroundColor Cyan + Write-Host "$([Constants]::DoubleDashLine)" + + } + catch + { + Write-Host "Error occurred while adding client IP to storage account. Please make sure you have provided correct IP address and you have proper permissions (at least Contributor) on host RG and subscription. ExceptionMessage [$($_)]" -ForegroundColor $([Constants]::MessageType.Error) + } + finally + { + if($ClientIpAdditionFlag -eq 'Y') + { + $IpAddList = (Get-AzStorageAccountNetworkRuleSet -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName).IPRules + if($clientIPAddress -in $IpAddList.IPAddressOrRange) + { + $removedIp = Remove-AzStorageAccountNetworkRule -ResourceGroupName $ScanHostRGName -AccountName $storageAccount.StorageAccountName -IPAddressOrRange $clientIPAddress + } + } + } + } + } +} + +function WaitForFunctionToComplete +{ + param ( + [ValidateNotNullOrEmpty()] + [string] $FunctionName, + + [ValidateNotNullOrEmpty()] + [string] $StartTimeAsString, + + [ValidateNotNullOrEmpty()] + [string] $ApplicationInsightId, + + [ValidateNotNullOrEmpty()] + [string] $LAWorkspaceId + ) + + $FunctionAppStatus = [EventStatus]::NotCompleted + $LAStatus = [EventStatus]::NotCompleted + Write-Host "Waiting for [$($FunctionName)] function to complete its job." -ForegroundColor Yellow + Write-Host "This operation can take up to 15 minutes (approx)." -NoNewline -ForegroundColor Yellow + @(1..15) | ForEach-Object { + $FunctionAppStatus = EventProcessor -StartTimeAsString $StartTimeAsString -FunctionName $FunctionName -ApplicationInsightId $ApplicationInsightId + $LAStatus = LogAnalyticsEventProcessor -StartTimeAsString $StartTimeAsString -FunctionName $FunctionName -WorkspaceId $LAWorkspaceId + if($FunctionAppStatus -ne [EventStatus]::Completed) + { + Write-Host ..$($_) -NoNewline -ForegroundColor Yellow; + Start-Sleep -Seconds 60 + } + elseif($LAStatus -ne [EventStatus]::Completed) + { + Write-Host ..$($_) -NoNewline -ForegroundColor Yellow; + Start-Sleep -Seconds 60 + } + else + { + # No Action + } + } + + Write-Host "" + if($FunctionAppStatus -eq [EventStatus]::Completed -and $LAStatus -eq [EventStatus]::Completed) + { + Write-Host "[$($FunctionName)] completed proccessing." -ForegroundColor Cyan + } + else + { + Write-Host "Exceeded max wait time. [$($FunctionName)] is taking longer than expected to process. Continue to the next step." -ForegroundColor Cyan + } +} + +function GetFunctionAppKey +{ + + param ( + [ValidateNotNullOrEmpty()] + [string] $AppServiceResourceId + ) + + try + { + if ($resourceManagerUrl) + { + $functionAppListKeyURL = $resourceManagerUrl + $AppServiceResourceId + "/host/default/listkeys?api-version=2018-11-01" + + $headers = [TokenProvider]::new().GetAuthHeader($resourceManagerUrl); + } + $functionAppKeysResponse = Invoke-WebRequest -UseBasicParsing -Uri $functionAppListKeyURL -Method Post -Headers $headers -Body '{}' + + $functionAppKeys = $functionAppKeysResponse.Content | ConvertFrom-Json + return $functionAppKeys + } + catch + { + throw $_ + } +} + + +function TriggerFunction +{ + + param ( + [ValidateNotNullOrEmpty()] + [string] $FunctionName, + + [ValidateNotNullOrEmpty()] + [string] $FunctionAppHostName, + + [ValidateNotNullOrEmpty()] + [string] $FunctionAppMaterKey + ) + + $maxRetryCount = 3 + $retryCount = 0 + + try + { + while($retryCount -lt $maxRetryCount) + { + try + { + $baseFunctionAppTriggerURL = $FunctionAppHostName + "/admin/functions/{0}"; + $functionAppTriggerURL = ([string]::Format($baseFunctionAppTriggerURL, $FunctionName)); + + Write-Host "Starting [$($FunctionName)] function." -ForegroundColor Cyan + $response = Invoke-WebRequest -UseBasicParsing -Uri $functionAppTriggerURL -Method Post -Headers @{ "x-functions-key" = "$($FunctionAppMaterKey)";"Content-Type"="application/json" } -Body '{}' + Write-Host "Successfully triggered [$($FunctionName)] function." -ForegroundColor Cyan + $retryCount = $maxRetryCount + } + catch + { + $retryCount += 1; + if ($retryCount -ge $maxRetryCount) + { + throw $($_); + } + else + { + Start-Sleep -Seconds (30 * $retryCount) + } + } + }# WhileEnd + } + catch + { + Write-Host "Error occurred while triggering function app [$($FunctionName)] ExceptionMessage [$($_)]. Please validate that the function is in running state and run this command again." -ForegroundColor Red + } +} + +function EventProcessor +{ + param( + [ValidateNotNullOrEmpty()] + [string] $StartTimeAsString, + + [ValidateNotNullOrEmpty()] + [string] $FunctionName, + + [ValidateNotNullOrEmpty()] + [string] $ApplicationInsightId + ) + + $Status = [EventStatus]::NotCompleted + + try + { + if ($resourceManagerUrl) + { + $aiQueryAPI = $resourceManagerUrl + $ApplicationInsightId + "/query?api-version=2018-04-20&query=traces + | where customDimensions.LogLevel contains 'Information' + | where timestamp > todatetime('{0}') + | where customDimensions.Category contains '{1}' and customDimensions.EventId == 2001 + | project StatusId = customDimensions.EventId" -f $StartTimeAsString, $FunctionName + + $headers = [TokenProvider]::new().GetAuthHeader($resourceManagerUrl); + } + $response = Invoke-WebRequest -UseBasicParsing -Uri $aiQueryAPI -Method Get -Headers $headers + + if($response -ne $null) + { + $customObject = $response.Content | ConvertFrom-Json + + if(($customObject | GM tables) -and ($customObject.tables -ne $null) -and ($customObject.tables[0] | GM rows) -and ($customObject.tables[0].rows -ne $null)) + { + $Status = [EventStatus]::Completed + } + } + } + catch + { + throw $_ + } + + return $Status; +} + + +function LogAnalyticsEventProcessor +{ + param( + [ValidateNotNullOrEmpty()] + [string] $StartTimeAsString, + + [ValidateNotNullOrEmpty()] + [string] $FunctionName, + + [ValidateNotNullOrEmpty()] + [string] $WorkspaceId + ) + + $Status = [EventStatus]::NotCompleted + + try + { + $LAQuery = [string]::Empty + + switch($FunctionName) + { + $([ScannerConstants]::FunctionApp.SubscriptionInvProcessor) { $LAQuery = $([ScannerConstants]::SubscriptionInvLAQuery -f $StartTimeAsString) } + $([ScannerConstants]::FunctionApp.BaselineControlsInvProcessor) { $LAQuery = $([ScannerConstants]::BaselineControlsInvLAQuery -f $StartTimeAsString) } + $([ScannerConstants]::FunctionApp.SubscriptionRBACProcessor) { $LAQuery = $([ScannerConstants]::RBACInvLAQuery -f $StartTimeAsString) } + $([ScannerConstants]::FunctionApp.WorkItemScheduler) { $LAQuery = $([ScannerConstants]::ControlResultsLAQuery -f $StartTimeAsString) } + } + + if(![string]::IsNullOrWhiteSpace($LAQuery)) + { + + $Result = Invoke-AzOperationalInsightsQuery -WorkspaceId $WorkspaceId -Query $LAQuery + if(($Result.Results | Measure-Object).Count -gt 0) + { + $Status = [EventStatus]::Completed + } + + } + } + catch + { + Write-Host "Error occurred while validating result in Log Analytics. ExceptionMessage [$($_.Exception.Message)]". + } + + return $Status; +} + +enum EventStatus +{ + NotCompleted + Completed +} + +class ScannerConstants +{ + static [string] $OnDemandScanInstructionMsg = "This command will perform 4 important operations. It will:`r`n`n" + + " [1] Trigger subscription inventory processor `r`n" + + " [2] Trigger baseline controls inventory processor `r`n" + + " [3] Trigger Role-Based Access Control (RBAC) processor `r`n" + + " [4] Trigger work item scheduler `r`n"; + static [string] $OnDemandScanWarningMsg = "Please note that if the AzTS Soln has been setup recently, this command can take up to 30-45 minutes as it has to create tables in Log Analytics workspace for each inventory that is processed as part of this command."; + static [string] $NextStepsMsg = "Subscriptions have been queued for scan. The scan result will be available in the next 2 hours."; + + static [string] $QueueInstructionMsg = "This command will perform below activities. It will:`r`n`n" + + " [1] Send on-demand processing request using storage queue. `r`n" + + " [2] Using on-demand processing request, it will trigger below AzTS processing functions in sequence: `r`n" + + " [a] Subscription inventory processor: Collects subscription list to be scanned `r`n" + + " [b] Controls inventory processor: Collects control inventory `r`n" + + " [c] Role-Based Access Control (RBAC) processor: Collects RBAC data `r`n" + + " [d] WorkItem scheduler: Send subscription list from inventory to scanning queue `r`n" + + " [3] WorkItemProccessor will get auto triggered based on scanning queue messages and will scan the controls. `r`n"; + + static [string] $ClientIpInstructionMsg = "As your AzTS solution is integrated to VNet, i.e. resources like storage have access restrictions and firewall applied, therefore to trigger on-demand processing from your machine, we will need to add your client IP to AzTS storage account firewall (present inside host RG where your AzTS solution is hosted) on temporary basis. Once the message is added to the scanning queue, your IP will be removed from storage account. `r`n" ; + + static [string] $DoubleDashLine = "================================================================================" + static [string] $SingleDashLine = "--------------------------------------------------------------------------------" + + static [string] $SubscriptionInvLAQuery = "let TablePlaceholder = view () {{print SubscriptionId = 'SubscriptionIdNotFound'}}; + let SubInventory_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_SubInventory_CL | where TimeGenerated > todatetime('{0}') + | distinct SubscriptionId + )) + | where SubscriptionId !~ 'SubscriptionIdNotFound'; + SubInventory_CL"; + static [string] $BaselineControlsInvLAQuery = "let TablePlaceholder = view () {{print ControlId_s = 'NA'}}; + let BaselineControlsInv_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_BaselineControlsInv_CL | where TimeGenerated > todatetime('{0}') + | distinct ControlId_s + )) + | where ControlId_s !~ 'NA'; + BaselineControlsInv_CL"; + static [string] $RBACInvLAQuery = "let TablePlaceholder = view () {{print NameId = 'NA', RoleId = 'NA'}}; + let RBAC_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_RBAC_CL | where TimeGenerated > todatetime('{0}') + | take 10 + | project RoleId = coalesce(RoleId_g, RoleId_s), NameId = NameId_g + )) + | where NameId !~ 'NA'; + RBAC_CL"; + static [string] $ControlResultsLAQuery = "let TablePlaceholder = view () {{print SubscriptionId = 'SubscriptionIdNotFound'}}; + let ControlResults_CL = union isfuzzy=true TablePlaceholder, (union ( + AzSK_ControlResults_CL | where TimeGenerated > todatetime('{0}') + | distinct SubscriptionId + )) + | where SubscriptionId !~ 'SubscriptionIdNotFound'; + ControlResults_CL + | take 10"; + + static [Hashtable] $FunctionApp = @{ + SubscriptionInvProcessor = 'ATS_01_SubscriptionInvProcessor' + BaselineControlsInvProcessor = 'ATS_02_BaselineControlsInvProcessor' + SubscriptionRBACProcessor = 'ATS_03_SubscriptionRBACProcessor' + WorkItemScheduler = 'ATS_04_WorkItemScheduler' + } + } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/TokenProvider.ps1 b/TemplateFiles/DeploymentFiles/TokenProvider.ps1 index 7230aa31..1482eb6d 100644 --- a/TemplateFiles/DeploymentFiles/TokenProvider.ps1 +++ b/TemplateFiles/DeploymentFiles/TokenProvider.ps1 @@ -1,31 +1,31 @@ -########### Load Common Functions And Classes ############### - -class TokenProvider -{ - - [PSObject] GetAuthHeader([string] $resourceAppIdUri) - { - [psobject] $headers = $null - try - { - $rmContext = Get-AzContext - - $authResult = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate( - $rmContext.Account, - $rmContext.Environment, - $rmContext.Tenant, - [System.Security.SecureString] $null, - "Never", - $null, - $resourceAppIdUri); - - $header = "Bearer " + $authResult.AccessToken - $headers = @{"Authorization"=$header;"Content-Type"="application/json";} - } - catch - { - Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor Red - } - return($headers) - } +########### Load Common Functions And Classes ############### + +class TokenProvider +{ + + [PSObject] GetAuthHeader([string] $resourceAppIdUri) + { + [psobject] $headers = $null + try + { + $rmContext = Get-AzContext + + $authResult = [Microsoft.Azure.Commands.Common.Authentication.AzureSession]::Instance.AuthenticationFactory.Authenticate( + $rmContext.Account, + $rmContext.Environment, + $rmContext.Tenant, + [System.Security.SecureString] $null, + "Never", + $null, + $resourceAppIdUri); + + $header = "Bearer " + $authResult.AccessToken + $headers = @{"Authorization"=$header;"Content-Type"="application/json";} + } + catch + { + Write-Host "Error occurred while fetching auth header. ErrorMessage [$($_)]" -ForegroundColor Red + } + return($headers) + } } \ No newline at end of file diff --git a/TemplateFiles/DeploymentFiles/UpdateAzTSFeatures.ps1 b/TemplateFiles/DeploymentFiles/UpdateAzTSFeatures.ps1 index e32c9416..d610ba5e 100644 --- a/TemplateFiles/DeploymentFiles/UpdateAzTSFeatures.ps1 +++ b/TemplateFiles/DeploymentFiles/UpdateAzTSFeatures.ps1 @@ -1,673 +1,673 @@ -<### -# Overview: - This script is used to enable/disable features of AzTS in a Subscription. - -# Instructions to execute the script: - 1. Download the script and AztsControlConfigurationForFeatureExtension.json file.. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to enable/disable features of AzTS in the Subscription. Refer `Examples`, below. - -# Examples: - To Enable features of AzTS: - Configure-AzTSFeature -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FilePath "D:\Working\AztsScript\AztsControlConfigurationForFeatureExtension.json" -FeatureName "CMET" -FeatureActionType "Enable" - - To Disable features of AzTS: - Configure-AzTSFeature -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FilePath "D:\Working\AztsScript\AztsControlConfigurationForFeatureExtension.json" -FeatureName "CMET" -FeatureActionType "Disable" - -###> - -function Configure-AzTSFeature { - Param( - [string] - [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which AzTS is installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where AzTS is installed.")] - $ScanHostRGName, - - [string] - [Parameter(Mandatory = $false, HelpMessage = "File path for AzTS Control Configuration JSON file AztsControlConfigurationForFeatureExtension.json.")] - $FilePath = ".\ConfigureAzTSFeatureTemplate.json", - - [string] - [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled. Values for this parameter are 'CMET', 'CMET Bulk Edit', 'MG Processor', 'PIM API','MG Compliance Initiate Editor'")] - [ValidateSet("CMET", "CMET Bulk Edit", "MG Processor", "PIM API", "MG Compliance Initiate Editor")] - $FeatureName, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] - [ValidateSet("Enable", "Disable")] - $FeatureActionType - ) - - $inputParams = $PSBoundParameters - $FeatureName = $FeatureName.Trim() - - $logger = [Logger]::new($SubscriptionId) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Configure-AzTSFeature `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Starting process to $($FeatureActionType.ToLower()) $FeatureName feature. This may take 2-3 mins...", $([Constants]::MessageType.Info)) - - - $webAppConfigurationList = [hashtable]@{} - $dependentFeaturesForEnabling = @{} - $dependentFeaturesForDisabling = @{} - - # Set the context to host subscription - Set-AzContext -SubscriptionId $SubscriptionId | Out-null - - #To set context for default subs - #Update-AzConfig -DefaultSubscriptionForLogin $SubscriptionId | Out-null - - # AzTS resources name preparation - $ResourceId = '/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId, $ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0, 5).ToString().ToLower() - - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage("Loading File: [$($FilePath)]...") - - # Getting Control Configuration from file path - if (-not (Test-Path -Path $FilePath)) { - $logger.PublishCustomMessage("ERROR: File - $($FilePath) not found. Exiting...", $([Constants]::MessageType.Error)) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - break - } - - $JsonContent = Get-content -path $FilePath | ConvertFrom-Json - - $logger.PublishLogMessage("Loading File: [$($FilePath)] completed.") - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - - $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } - if ($null -ne $FilteredfeatureSetting) { - - #Checking if feature needs to be enabled - if ($FeatureActionType -ieq "Enable") { - if (($null -ne $FilteredfeatureSetting.DependentFeaturesForEnabling) -and ($FilteredfeatureSetting.DependentFeaturesForEnabling -ne "")) { - #Getting the list of dependent features - $dependentFeaturesForEnabling = Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DepandentFeatureName $FeatureName -FeatureActionType $FeatureActionType - - #Getting Unique Values - $dependentFeaturesForEnabling = $dependentFeaturesForEnabling | Sort-Object | Get-Unique - $dependentFeaturesForEnablingText = $dependentFeaturesForEnabling -join ", " - - #Enable below line for comma seperated dependent feature - #$logger.PublishCustomMessage("For enabling $FeatureName following dependent feature needs to be enabled: $dependentFeaturesForEnablingText" , $([Constants]::MessageType.Warning)) - - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Enabling [$FeatureName] will also enable dependent feature(s): " , $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage( $(( $dependentFeaturesForEnabling | Out-String).TrimEnd()) , $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage("`r`nDo you want to Continue? ", $([Constants]::MessageType.Warning)) - - $userInput = Read-Host -Prompt "(Y|N)" - - if ($userInput -ne "Y") { - $logger.PublishCustomMessage( "Azts Feature $FeatureName will not be enabled in the Subscription. Exiting..." , $([Constants]::MessageType.Error)) - break - } - - #Adding configuration for dependent features - foreach ($dependentFeature in $dependentFeaturesForEnabling) { - $webAppConfigurationList = Get-Configuration -FeatureName $dependentFeature -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType - - } - } - - #Adding Configuration for Feature - $webAppConfigurationList = Get-Configuration -FeatureName $FeatureName -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType - - - } - elseif ($FeatureActionType -ieq "Disable") { - - if (($null -ne $FilteredfeatureSetting.DependentFeaturesForDisabling) -and ($FilteredfeatureSetting.DependentFeaturesForDisabling -ne "")) { - #Getting the list of dependent features - $dependentFeaturesForDisabling = Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DepandentFeatureName $FeatureName -FeatureActionType $FeatureActionType - - #Getting Unique Values - $dependentFeaturesForDisabling = $dependentFeaturesForDisabling | Sort-Object | Get-Unique - $dependentFeaturesForDisablingText = $dependentFeaturesForDisabling -join ", " - - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Disabling [$FeatureName] will also disable dependent feature(s): " , $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage( $(( $dependentFeaturesForDisabling | Out-String).TrimEnd()) , $([Constants]::MessageType.Warning)) - $logger.PublishCustomMessage("`r`nDo you want to Continue? " , $([Constants]::MessageType.Warning)) - - $userInput = Read-Host -Prompt "(Y|N)" - - if ($userInput -ne "Y") { - $logger.PublishCustomMessage( "Azts Feature $FeatureName will not be disabled in the Subscription. Exiting..." , $([Constants]::MessageType.Error)) - break - } - - #Adding configuration for dependent features - foreach ($dependentFeature in $dependentFeaturesForDisabling) { - $webAppConfigurationList = Get-Configuration -FeatureName $dependentFeature -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType - } - } - - #Adding Configuration for Feature - $webAppConfigurationList = Get-Configuration -FeatureName $FeatureName -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType - } - - #Enabling/Disabling the feature - $webAppConfigurationList.GetEnumerator() | ForEach-Object { - try { - - # calling function to update the values - Configure-ModifyAppSetting -SubscriptionId $SubscriptionId -ScanHostRGName $ScanHostRGName -WebAppName $_.Name -AppSettings $_.Value -FeatureActionType $FeatureActionType - } - catch { - $logger.PublishCustomMessage("Error occurred while updating Configuration. Error: $($_)" , $([Constants]::MessageType.Error)) - $logger.PublishLogFilePath() - break - } - } - } - else { - $availableFeatureName = $JsonContent.FeatureName -join "," - $logger.PublishCustomMessage("The value entered for FeatureName: $FeatureName is invalid. Valid values are [$availableFeatureName]. Exiting..." , $([Constants]::MessageType.Error)) - $logger.PublishLogFilePath() - break - } - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - - - $dependentFeatures = $dependentFeaturesForDisabling - if (!$dependentFeaturesForEnabling.Count -eq 0) { - $dependentFeatures = $dependentFeaturesForEnabling - } - - if ($dependentFeatures.Count -eq 0 ) { - $logger.PublishCustomMessage( "Successfully $($FeatureActionType.ToLower()+"d") [$FeatureName] feature.", $([Constants]::MessageType.Info)) - } - else { - $logger.PublishCustomMessage( "The following feature(s) are $($FeatureActionType.ToLower()+"d") successfully:`r`n$FeatureName`r`n" + $(( $dependentFeatures | Out-String).TrimEnd()) , $([Constants]::MessageType.Info)) - } - - $logger.PublishLogMessage($([Constants]::DoubleDashLine)) - $logger.PublishLogFilePath() -} - - -function Configure-ModifyAppSetting { - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which AzTS is installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where AzTS is installed.")] - $ScanHostRGName, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] - $WebAppName, - - [hashtable] - [Parameter(Mandatory = $true, HelpMessage = "App Settings Keys which needs to be modified.")] - $AppSettings, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] - $FeatureActionType - - ) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Updating configuration for: [$($WebAppName)]...", $([Constants]::MessageType.Info)) - - #Enable below line to see the config values in console output window - #$logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Configure-AzTSTenantSecurityAdditionalFeature `r`nInput Parameters: $(( $_.Value | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) - $logger.PublishLogMessage($_.Value ) - - $AzTSAppSettings = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $WebAppName -ErrorAction Stop - - if ($null -ne $AzTSAppSettings) { - # Existing app settings - $ExistingAppSettings = $AzTSAppSettings.SiteConfig.AppSettings - - # Moving existing app settings in new app settings list to avoid being overridden - $NewAppSettings = @{} - $NewAppSettingsKeyOnly = @() - - if ($FeatureActionType -ieq "Enable") { - ForEach ($appSetting in $ExistingAppSettings) { - $NewAppSettings[$appSetting.Name] = $appSetting.Value - } - } - elseif ($FeatureActionType -ieq "Disable") { - ForEach ($appSetting in $ExistingAppSettings) { - $NewAppSettings[$appSetting.Name] = $appSetting.Value - } - } - - # Adding new settings to new app settings list - $AppSettings.GetEnumerator() | ForEach-Object { - $NewAppSettings[$_.Key] = $_.Value - $NewAppSettingsKeyOnly += $_.Value - } - - # Configuring new app settings - $AzTSAppSettings = Set-AzWebApp -ResourceGroupName $ScanHostRGName -Name $WebAppName -AppSettings $NewAppSettings -ErrorAction Stop - $logger.PublishCustomMessage("Updated configuration for: [$($WebAppName)]." , $([Constants]::MessageType.Update)) - } -} - -function Get-DependentFeature { - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] - $FeatureName, - - [string] - [Parameter(Mandatory = $false, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] - $DepandentFeatureName, - - [System.Object] - [Parameter(Mandatory = $true)] - $JsonContent, - - [System.Array] - [Parameter(Mandatory = $false)] - $DependentFeatures, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] - $FeatureActionType - - ) - - $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $DepandentFeatureName) } - - $DependentFeatures = @() - if ($FeatureActionType -ieq "Enable") { - $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForEnabling - } - else { $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForDisabling } - - if (($null -ne $DependentFeatures) -and ($DependentFeatures -ne "")) { - foreach ($DependentFeature in $DependentFeatures) { - if ( $FeatureName -ine $DependentFeature) { - - if ($null -eq $DependentFeatures) { - $DependentFeatures += $DependentFeature - $IsDependentFeature = Validate-DependentFeature -FeatureName $DependentFeature -JsonContent $JsonContent -FeatureActionType $FeatureActionType - if ($IsDependentFeature) { - Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DependentFeatures $DependentFeatures -DepandentFeatureName $DependentFeature -FeatureActionType $FeatureActionType - } - } - - if (!$DependentFeatures.Contains($DependentFeature)) { - $DependentFeatures += $DependentFeature - $IsDependentFeature = Validate-DependentFeature -FeatureName $DependentFeature -JsonContent $JsonContent -FeatureActionType $FeatureActionType - if ($IsDependentFeature) { - Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DependentFeatures $DependentFeatures -DepandentFeatureName $DependentFeature -FeatureActionType $FeatureActionType - } - } - } - } - } - return $DependentFeatures -} - -function Validate-DependentFeature { - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] - $FeatureName, - - [System.Object] - [Parameter(Mandatory = $true)] - $JsonContent, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] - $FeatureActionType - ) - - $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } - - $DependentFeatures = @() - if ($FeatureActionType -ieq "Enable") { - $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForEnabling - } - else { $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForDisabling } - - if (($null -ne $DependentFeatures) -and ($DependentFeatures -ne "")) { - return $true - } - else { - return $false - } -} - - -function Get-Configuration { - Param( - - [string] - [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] - $FeatureName, - - [System.Object] - [Parameter(Mandatory = $true)] - $JsonContent, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Hash value of resource.")] - $ResourceHash, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] - $FeatureActionType - ) - - #Filtering Dependent feature configuration - $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } - - $ConfigurationDependencies = @() - if ($FeatureActionType -ieq "Enable") { - $ConfigurationDependencies = $FilteredfeatureSetting.ConfigurationDependenciesForEnabling - } - else { $ConfigurationDependencies = $FilteredfeatureSetting.ConfigurationDependenciesForDisabling } - - - foreach ($ConfigurationDependency in $ConfigurationDependencies) { - - #Creating a hashtable for storing the configuration - $ConfigurationHashtable = [hashtable]@{}; - $featureName = $ConfigurationDependency.ComponentName + $ResourceHash; - foreach ($Configuration in $ConfigurationDependency.Configuration) { - - #replace value for configuration - if ( $Configuration.ConfigurationValue -ieq "##HostSubscriptionId##") { - $Configuration.ConfigurationValue = $SubscriptionId; - } - - if ( $Configuration.ConfigurationValue -ieq "##HostResourceGroupName##") { - $Configuration.ConfigurationValue = $ScanHostRGName; - } - - if ( $Configuration.ConfigurationValue -ieq "##AppName##") { - $Configuration.ConfigurationValue = $featureName; - } - - $ConfigurationHashtable[$Configuration.ConfigurationName] = $Configuration.ConfigurationValue - } - #null check - if ($null -ne $webAppConfigurationList) { - #if key contains - if (!$webAppConfigurationList.ContainsKey($featureName)) { - $webAppConfigurationList[$featureName] = $ConfigurationHashtable; - } - else { - #key found - $TempConfiguration = $webAppConfigurationList[$featureName] - $MergedConfiguration = $TempConfiguration + $ConfigurationHashtable - $webAppConfigurationList[$featureName] = $MergedConfiguration; - } - } - else - { $webAppConfigurationList[$featureName] = $ConfigurationHashtable; } - } - return $webAppConfigurationList; -} - -<### -# Overview: - This script is used to add feature configuration value for AzTS in a Subscription. - -# Instructions to execute the script: - 1. Download the script. - 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. - 3. Execute the script to add feature configuration value for AzTS in a Subscription. Refer `Examples`, below. - -# Examples: - To add user's object id into configuration of AzTS: - Add-AztsFeatureConfigurationValues -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FeatureName "CMET" -FeatureConfigValues "00000000-xxxx-0000-xxxx-000000000001,00000000-xxxx-0000-xxxx-000000000002,00000000-xxxx-0000-xxxx-000000000003" - -###> - -function Add-AztsFeatureConfigurationValues { - Param( - [string] - [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which Azure Tenant Security Solution is installed.")] - $SubscriptionId, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where Azure Tenant Security Solution is installed.")] - $ScanHostRGName, - - [string] - [Parameter(Mandatory = $false, HelpMessage = "File path for Azts Control Configuration file AztsControlConfiguration.json.")] - $FilePath = "./AddAzTSFeatureConfigurationValuesTemplate.json", - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Azts Feature Name to add Configuration values. Values for this parameter are 'CMET', 'MG Compliance Initiate Editor'")] - [ValidateSet("CMET", "MG Compliance Initiate Editor")] - $FeatureName, - - [string] - [Parameter(Mandatory = $true, HelpMessage = "Pass multiple Configuration Value as comma seperated")] - $FeatureConfigValues - ) - - $inputParams = $PSBoundParameters - $logger = [Logger]::new($SubscriptionId) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Add-AztsFeatureConfigurationValues `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Starting process to add Configuration Values for $FeatureName feature. This may take 2-3 mins...", $([Constants]::MessageType.Info)) - - - #checking if ConfigurationValues enetered is having single or multiple values - if ( !$FeatureConfigValues.Contains(",")) { - $FeatureConfigValues += ","; - } - - #Splitting the UserObjectIds from (,) - [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); - - # Set the context to host subscription - Set-AzContext -SubscriptionId $SubscriptionId | Out-null - - # AzTS resources name preparation - $ResourceId = '/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId, $ScanHostRGName; - $ResourceIdHash = get-hash($ResourceId) - $ResourceHash = $ResourceIdHash.Substring(0, 5).ToString().ToLower() - - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage("Loading File: [$($FilePath)]...") - - # Getting Control Configuration from file path - if (-not (Test-Path -Path $FilePath)) { - $logger.PublishCustomMessage("ERROR: File - $($FilePath) not found. Exiting...", $([Constants]::MessageType.Error)) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - break - } - - $JsonContent = Get-content -path $FilePath | ConvertFrom-Json - - $logger.PublishLogMessage("Loading File: [$($FilePath)] completed.") - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - - $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } - if ($null -ne $FilteredfeatureSetting) { - - - #Checking if any Dependent Features needs to be enabled - foreach ($dependentConfiguration in $FilteredfeatureSetting.ConfigurationDependencies) { - $IntPrivilegedEditorIds = @() - $NewAppSettings = @{} - $NewConfigurationList = @{} - $ExistingConfigurationList = @{} - - $ComponentName = $dependentConfiguration.ComponentName + $ResourceHash; - - #Getting Existing configuration value - $AzTSAppConfigurationSettings = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -ErrorAction Stop - - foreach ($Configuration in $dependentConfiguration.Configuration) { - if ($null -ne $AzTSAppConfigurationSettings) { - - if ($FeatureName -ieq "CMET" -or $FeatureName -ieq "MG Compliance Initiate Editor") { - - #Splitting the UserObjectIds from (,) - [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); - $IntPrivilegedEditorIds = @() - # Existing app settings - $AppSettings = $AzTSAppConfigurationSettings.SiteConfig.AppSettings - - # Moving existing app settings in new app settings list to avoid being overridden - ForEach ($appSetting in $AppSettings) { - $NewAppSettings[$appSetting.Name] = $appSetting.Value - - #Checking if Configuration Key exist to get the exisiting array value - if ($appSetting.Name.Contains($Configuration)) { - - $appSettingNameArray = $appSetting.Name.Split('_'); - $IntPrivilegedEditorIds += $appSettingNameArray[6]; - - #checking if the Configuration value exist (Key and Value) to avoid duplication - if ($FeatureConfigValueArray.Contains($appSetting.Value)) { - $ExistingConfigurationList["$($appSetting.Name)"] =$appSetting.Value - $FeatureConfigValueArray.Remove($appSetting.Value); - } - } - } - - #If exisitng configuration values does not exist, then setting it to 0 - if ($IntPrivilegedEditorIds.Count -eq 0) { - $IntPrivilegedEditorIds = 0 - } - - #Fetching max value - $IntPrivilegedEditorIdsMaxValue = ($IntPrivilegedEditorIds | Measure-Object -Maximum).Maximum - - #Adding configuration - foreach ($FeatureConfig in $FeatureConfigValueArray) { - if ($FeatureConfig -ne "") { - $NewAppSettings["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig - $NewConfigurationList["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig - $IntPrivilegedEditorIdsMaxValue++ - } - } - - } - } - } - - try { - if ($FeatureConfigValueArray.Count -gt 0) { - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Updating configuration for [$($ComponentName)]...", $([Constants]::MessageType.Info)) - $logger.PublishLogMessage($NewConfigurationList) - - #uncomment below line to see data in output console window - #$(( $NewConfigurationList | Out-String).TrimEnd()) | Write-Host -ForegroundColor $([Constants]::MessageType.Info) - - #Updating the new configuration values - $AzTSAppConfigurationSettings = Set-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -AppSettings $NewAppSettings -ErrorAction Stop - - $logger.PublishCustomMessage("Updated configuration for [$($ComponentName)]." , $([Constants]::MessageType.Update)) - - if ($ExistingConfigurationList.Count -gt 0) - { - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage("Existing Configuration found:") - $logger.PublishLogMessage($ExistingConfigurationList) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - } - } - else { - $logger.PublishCustomMessage("Entered configuration values are already present in $ComponentName.", $([Constants]::MessageType.Error)) - $logger.PublishLogMessage("Existing Configuration found:") - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - $logger.PublishLogMessage($ExistingConfigurationList) - $logger.PublishLogMessage($([Constants]::SingleDashLine)) - break - } - } - catch { - $logger.PublishCustomMessage("Error occurred while updating Configuration. Error: $($_)", $([Constants]::MessageType.Error)) - break - } - } - if ($FeatureConfigValueArray.Count -gt 0) { - $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) - $logger.PublishCustomMessage("Successfully added configuration(s) for [$FeatureName] feature." , $([Constants]::MessageType.Update)) - } - $logger.PublishLogFilePath() - } - else { - $availableFeatureName = $JsonContent.FeatureName -join ", " - $logger.PublishCustomMessage("The value entered for FeatureName: $FeatureName is invalid. Valid values are [$availableFeatureName]. Exiting..." , $([Constants]::MessageType.Error)) - $logger.PublishLogFilePath() - } -} - -function get-hash([string]$textToHash) { - $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider - $toHash = [System.Text.Encoding]::UTF8.GetBytes($textToHash) - $hashByteArray = $hasher.ComputeHash($toHash) - $result = [string]::Empty; - foreach ($byte in $hashByteArray) { - $result += "{0:X2}" -f $byte - } - return $result; -} - -class Constants { - static [Hashtable] $MessageType = @{ - Error = [System.ConsoleColor]::Red - Warning = [System.ConsoleColor]::Yellow - Info = [System.ConsoleColor]::Cyan - Update = [System.ConsoleColor]::Green - Default = [System.ConsoleColor]::White - } - - static [string] $DoubleDashLine = "================================================================================" - static [string] $SingleDashLine = "--------------------------------------------------------------------------------" -} - -class Logger { - [string] $logFilePath = ""; - - Logger([string] $HostSubscriptionId) { - $logFolerPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\FeatureUpdate\Subscriptions\$($HostSubscriptionId.replace('-','_'))"; - $logFileName = "\$('FeatureUpdateLogs_' + $(Get-Date).ToString('yyyyMMddhhmm') + '.txt')"; - $this.logFilePath = $logFolerPath + $logFileName - # Create folder if not exist - if (-not (Test-Path -Path $logFolerPath)) { - New-Item -ItemType Directory -Path $logFolerPath | Out-Null - } - # Create log file - - New-Item -Path $this.logFilePath -ItemType File | Out-Null - - } - - PublishCustomMessage ([string] $message, [string] $foregroundColor) { - $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor $foregroundColor - } - - PublishCustomMessage ([string] $message) { - $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor White - } - - PublishLogMessage ([string] $message) { - $($message) | Add-Content $this.logFilePath - } - - PublishLogMessage ([hashtable] $message) { - $($message) | Format-Table -Wrap -AutoSize | Out-File $this.logFilePath -Append utf8 -Width 100 - } - - PublishLogFilePath() { - Write-Host $([Constants]::DoubleDashLine)"`r`nLogs have been exported to: $($this.logFilePath)`n"$([Constants]::DoubleDashLine) -ForegroundColor Cyan - } +<### +# Overview: + This script is used to enable/disable features of AzTS in a Subscription. + +# Instructions to execute the script: + 1. Download the script and AztsControlConfigurationForFeatureExtension.json file.. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to enable/disable features of AzTS in the Subscription. Refer `Examples`, below. + +# Examples: + To Enable features of AzTS: + Configure-AzTSFeature -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FilePath "D:\Working\AztsScript\AztsControlConfigurationForFeatureExtension.json" -FeatureName "CMET" -FeatureActionType "Enable" + + To Disable features of AzTS: + Configure-AzTSFeature -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FilePath "D:\Working\AztsScript\AztsControlConfigurationForFeatureExtension.json" -FeatureName "CMET" -FeatureActionType "Disable" + +###> + +function Configure-AzTSFeature { + Param( + [string] + [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which AzTS is installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where AzTS is installed.")] + $ScanHostRGName, + + [string] + [Parameter(Mandatory = $false, HelpMessage = "File path for AzTS Control Configuration JSON file AztsControlConfigurationForFeatureExtension.json.")] + $FilePath = ".\ConfigureAzTSFeatureTemplate.json", + + [string] + [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled. Values for this parameter are 'CMET', 'CMET Bulk Edit', 'MG Processor', 'PIM API','MG Compliance Initiate Editor'")] + [ValidateSet("CMET", "CMET Bulk Edit", "MG Processor", "PIM API", "MG Compliance Initiate Editor")] + $FeatureName, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] + [ValidateSet("Enable", "Disable")] + $FeatureActionType + ) + + $inputParams = $PSBoundParameters + $FeatureName = $FeatureName.Trim() + + $logger = [Logger]::new($SubscriptionId) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Configure-AzTSFeature `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Starting process to $($FeatureActionType.ToLower()) $FeatureName feature. This may take 2-3 mins...", $([Constants]::MessageType.Info)) + + + $webAppConfigurationList = [hashtable]@{} + $dependentFeaturesForEnabling = @{} + $dependentFeaturesForDisabling = @{} + + # Set the context to host subscription + Set-AzContext -SubscriptionId $SubscriptionId | Out-null + + #To set context for default subs + #Update-AzConfig -DefaultSubscriptionForLogin $SubscriptionId | Out-null + + # AzTS resources name preparation + $ResourceId = '/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId, $ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0, 5).ToString().ToLower() + + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage("Loading File: [$($FilePath)]...") + + # Getting Control Configuration from file path + if (-not (Test-Path -Path $FilePath)) { + $logger.PublishCustomMessage("ERROR: File - $($FilePath) not found. Exiting...", $([Constants]::MessageType.Error)) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + break + } + + $JsonContent = Get-content -path $FilePath | ConvertFrom-Json + + $logger.PublishLogMessage("Loading File: [$($FilePath)] completed.") + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + + $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } + if ($null -ne $FilteredfeatureSetting) { + + #Checking if feature needs to be enabled + if ($FeatureActionType -ieq "Enable") { + if (($null -ne $FilteredfeatureSetting.DependentFeaturesForEnabling) -and ($FilteredfeatureSetting.DependentFeaturesForEnabling -ne "")) { + #Getting the list of dependent features + $dependentFeaturesForEnabling = Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DepandentFeatureName $FeatureName -FeatureActionType $FeatureActionType + + #Getting Unique Values + $dependentFeaturesForEnabling = $dependentFeaturesForEnabling | Sort-Object | Get-Unique + $dependentFeaturesForEnablingText = $dependentFeaturesForEnabling -join ", " + + #Enable below line for comma seperated dependent feature + #$logger.PublishCustomMessage("For enabling $FeatureName following dependent feature needs to be enabled: $dependentFeaturesForEnablingText" , $([Constants]::MessageType.Warning)) + + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Enabling [$FeatureName] will also enable dependent feature(s): " , $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage( $(( $dependentFeaturesForEnabling | Out-String).TrimEnd()) , $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage("`r`nDo you want to Continue? ", $([Constants]::MessageType.Warning)) + + $userInput = Read-Host -Prompt "(Y|N)" + + if ($userInput -ne "Y") { + $logger.PublishCustomMessage( "Azts Feature $FeatureName will not be enabled in the Subscription. Exiting..." , $([Constants]::MessageType.Error)) + break + } + + #Adding configuration for dependent features + foreach ($dependentFeature in $dependentFeaturesForEnabling) { + $webAppConfigurationList = Get-Configuration -FeatureName $dependentFeature -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType + + } + } + + #Adding Configuration for Feature + $webAppConfigurationList = Get-Configuration -FeatureName $FeatureName -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType + + + } + elseif ($FeatureActionType -ieq "Disable") { + + if (($null -ne $FilteredfeatureSetting.DependentFeaturesForDisabling) -and ($FilteredfeatureSetting.DependentFeaturesForDisabling -ne "")) { + #Getting the list of dependent features + $dependentFeaturesForDisabling = Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DepandentFeatureName $FeatureName -FeatureActionType $FeatureActionType + + #Getting Unique Values + $dependentFeaturesForDisabling = $dependentFeaturesForDisabling | Sort-Object | Get-Unique + $dependentFeaturesForDisablingText = $dependentFeaturesForDisabling -join ", " + + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Disabling [$FeatureName] will also disable dependent feature(s): " , $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage( $(( $dependentFeaturesForDisabling | Out-String).TrimEnd()) , $([Constants]::MessageType.Warning)) + $logger.PublishCustomMessage("`r`nDo you want to Continue? " , $([Constants]::MessageType.Warning)) + + $userInput = Read-Host -Prompt "(Y|N)" + + if ($userInput -ne "Y") { + $logger.PublishCustomMessage( "Azts Feature $FeatureName will not be disabled in the Subscription. Exiting..." , $([Constants]::MessageType.Error)) + break + } + + #Adding configuration for dependent features + foreach ($dependentFeature in $dependentFeaturesForDisabling) { + $webAppConfigurationList = Get-Configuration -FeatureName $dependentFeature -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType + } + } + + #Adding Configuration for Feature + $webAppConfigurationList = Get-Configuration -FeatureName $FeatureName -JsonContent $JsonContent -ResourceHash $ResourceHash -FeatureActionType $FeatureActionType + } + + #Enabling/Disabling the feature + $webAppConfigurationList.GetEnumerator() | ForEach-Object { + try { + + # calling function to update the values + Configure-ModifyAppSetting -SubscriptionId $SubscriptionId -ScanHostRGName $ScanHostRGName -WebAppName $_.Name -AppSettings $_.Value -FeatureActionType $FeatureActionType + } + catch { + $logger.PublishCustomMessage("Error occurred while updating Configuration. Error: $($_)" , $([Constants]::MessageType.Error)) + $logger.PublishLogFilePath() + break + } + } + } + else { + $availableFeatureName = $JsonContent.FeatureName -join "," + $logger.PublishCustomMessage("The value entered for FeatureName: $FeatureName is invalid. Valid values are [$availableFeatureName]. Exiting..." , $([Constants]::MessageType.Error)) + $logger.PublishLogFilePath() + break + } + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + + + $dependentFeatures = $dependentFeaturesForDisabling + if (!$dependentFeaturesForEnabling.Count -eq 0) { + $dependentFeatures = $dependentFeaturesForEnabling + } + + if ($dependentFeatures.Count -eq 0 ) { + $logger.PublishCustomMessage( "Successfully $($FeatureActionType.ToLower()+"d") [$FeatureName] feature.", $([Constants]::MessageType.Info)) + } + else { + $logger.PublishCustomMessage( "The following feature(s) are $($FeatureActionType.ToLower()+"d") successfully:`r`n$FeatureName`r`n" + $(( $dependentFeatures | Out-String).TrimEnd()) , $([Constants]::MessageType.Info)) + } + + $logger.PublishLogMessage($([Constants]::DoubleDashLine)) + $logger.PublishLogFilePath() +} + + +function Configure-ModifyAppSetting { + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which AzTS is installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where AzTS is installed.")] + $ScanHostRGName, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] + $WebAppName, + + [hashtable] + [Parameter(Mandatory = $true, HelpMessage = "App Settings Keys which needs to be modified.")] + $AppSettings, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] + $FeatureActionType + + ) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Updating configuration for: [$($WebAppName)]...", $([Constants]::MessageType.Info)) + + #Enable below line to see the config values in console output window + #$logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Configure-AzTSTenantSecurityAdditionalFeature `r`nInput Parameters: $(( $_.Value | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) + $logger.PublishLogMessage($_.Value ) + + $AzTSAppSettings = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $WebAppName -ErrorAction Stop + + if ($null -ne $AzTSAppSettings) { + # Existing app settings + $ExistingAppSettings = $AzTSAppSettings.SiteConfig.AppSettings + + # Moving existing app settings in new app settings list to avoid being overridden + $NewAppSettings = @{} + $NewAppSettingsKeyOnly = @() + + if ($FeatureActionType -ieq "Enable") { + ForEach ($appSetting in $ExistingAppSettings) { + $NewAppSettings[$appSetting.Name] = $appSetting.Value + } + } + elseif ($FeatureActionType -ieq "Disable") { + ForEach ($appSetting in $ExistingAppSettings) { + $NewAppSettings[$appSetting.Name] = $appSetting.Value + } + } + + # Adding new settings to new app settings list + $AppSettings.GetEnumerator() | ForEach-Object { + $NewAppSettings[$_.Key] = $_.Value + $NewAppSettingsKeyOnly += $_.Value + } + + # Configuring new app settings + $AzTSAppSettings = Set-AzWebApp -ResourceGroupName $ScanHostRGName -Name $WebAppName -AppSettings $NewAppSettings -ErrorAction Stop + $logger.PublishCustomMessage("Updated configuration for: [$($WebAppName)]." , $([Constants]::MessageType.Update)) + } +} + +function Get-DependentFeature { + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] + $FeatureName, + + [string] + [Parameter(Mandatory = $false, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] + $DepandentFeatureName, + + [System.Object] + [Parameter(Mandatory = $true)] + $JsonContent, + + [System.Array] + [Parameter(Mandatory = $false)] + $DependentFeatures, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] + $FeatureActionType + + ) + + $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $DepandentFeatureName) } + + $DependentFeatures = @() + if ($FeatureActionType -ieq "Enable") { + $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForEnabling + } + else { $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForDisabling } + + if (($null -ne $DependentFeatures) -and ($DependentFeatures -ne "")) { + foreach ($DependentFeature in $DependentFeatures) { + if ( $FeatureName -ine $DependentFeature) { + + if ($null -eq $DependentFeatures) { + $DependentFeatures += $DependentFeature + $IsDependentFeature = Validate-DependentFeature -FeatureName $DependentFeature -JsonContent $JsonContent -FeatureActionType $FeatureActionType + if ($IsDependentFeature) { + Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DependentFeatures $DependentFeatures -DepandentFeatureName $DependentFeature -FeatureActionType $FeatureActionType + } + } + + if (!$DependentFeatures.Contains($DependentFeature)) { + $DependentFeatures += $DependentFeature + $IsDependentFeature = Validate-DependentFeature -FeatureName $DependentFeature -JsonContent $JsonContent -FeatureActionType $FeatureActionType + if ($IsDependentFeature) { + Get-DependentFeature -FeatureName $FeatureName -JsonContent $JsonContent -DependentFeatures $DependentFeatures -DepandentFeatureName $DependentFeature -FeatureActionType $FeatureActionType + } + } + } + } + } + return $DependentFeatures +} + +function Validate-DependentFeature { + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] + $FeatureName, + + [System.Object] + [Parameter(Mandatory = $true)] + $JsonContent, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] + $FeatureActionType + ) + + $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } + + $DependentFeatures = @() + if ($FeatureActionType -ieq "Enable") { + $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForEnabling + } + else { $DependentFeatures = $FilteredfeatureSetting.DependentFeaturesForDisabling } + + if (($null -ne $DependentFeatures) -and ($DependentFeatures -ne "")) { + return $true + } + else { + return $false + } +} + + +function Get-Configuration { + Param( + + [string] + [Parameter(Mandatory = $true, HelpMessage = "AzTS Feature Name to be Enabled/Disabled.")] + $FeatureName, + + [System.Object] + [Parameter(Mandatory = $true)] + $JsonContent, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Hash value of resource.")] + $ResourceHash, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Action to be taken on AzTS Feature, Pass Enabled for enabling the feature and Disable for disabling the feature.")] + $FeatureActionType + ) + + #Filtering Dependent feature configuration + $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } + + $ConfigurationDependencies = @() + if ($FeatureActionType -ieq "Enable") { + $ConfigurationDependencies = $FilteredfeatureSetting.ConfigurationDependenciesForEnabling + } + else { $ConfigurationDependencies = $FilteredfeatureSetting.ConfigurationDependenciesForDisabling } + + + foreach ($ConfigurationDependency in $ConfigurationDependencies) { + + #Creating a hashtable for storing the configuration + $ConfigurationHashtable = [hashtable]@{}; + $featureName = $ConfigurationDependency.ComponentName + $ResourceHash; + foreach ($Configuration in $ConfigurationDependency.Configuration) { + + #replace value for configuration + if ( $Configuration.ConfigurationValue -ieq "##HostSubscriptionId##") { + $Configuration.ConfigurationValue = $SubscriptionId; + } + + if ( $Configuration.ConfigurationValue -ieq "##HostResourceGroupName##") { + $Configuration.ConfigurationValue = $ScanHostRGName; + } + + if ( $Configuration.ConfigurationValue -ieq "##AppName##") { + $Configuration.ConfigurationValue = $featureName; + } + + $ConfigurationHashtable[$Configuration.ConfigurationName] = $Configuration.ConfigurationValue + } + #null check + if ($null -ne $webAppConfigurationList) { + #if key contains + if (!$webAppConfigurationList.ContainsKey($featureName)) { + $webAppConfigurationList[$featureName] = $ConfigurationHashtable; + } + else { + #key found + $TempConfiguration = $webAppConfigurationList[$featureName] + $MergedConfiguration = $TempConfiguration + $ConfigurationHashtable + $webAppConfigurationList[$featureName] = $MergedConfiguration; + } + } + else + { $webAppConfigurationList[$featureName] = $ConfigurationHashtable; } + } + return $webAppConfigurationList; +} + +<### +# Overview: + This script is used to add feature configuration value for AzTS in a Subscription. + +# Instructions to execute the script: + 1. Download the script. + 2. Load the script in a PowerShell session. Refer https://aka.ms/AzTS-docs/RemediationscriptExcSteps to know more about loading the script. + 3. Execute the script to add feature configuration value for AzTS in a Subscription. Refer `Examples`, below. + +# Examples: + To add user's object id into configuration of AzTS: + Add-AztsFeatureConfigurationValues -SubscriptionId 00000000-xxxx-0000-xxxx-000000000000 -ScanHostRGName AzTS-Solution-RG -FeatureName "CMET" -FeatureConfigValues "00000000-xxxx-0000-xxxx-000000000001,00000000-xxxx-0000-xxxx-000000000002,00000000-xxxx-0000-xxxx-000000000003" + +###> + +function Add-AztsFeatureConfigurationValues { + Param( + [string] + [Parameter(Mandatory = $true, HelpMessage = "Subscription id in which Azure Tenant Security Solution is installed.")] + $SubscriptionId, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Name of ResourceGroup where Azure Tenant Security Solution is installed.")] + $ScanHostRGName, + + [string] + [Parameter(Mandatory = $false, HelpMessage = "File path for Azts Control Configuration file AztsControlConfiguration.json.")] + $FilePath = "./AddAzTSFeatureConfigurationValuesTemplate.json", + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Azts Feature Name to add Configuration values. Values for this parameter are 'CMET', 'MG Compliance Initiate Editor'")] + [ValidateSet("CMET", "MG Compliance Initiate Editor")] + $FeatureName, + + [string] + [Parameter(Mandatory = $true, HelpMessage = "Pass multiple Configuration Value as comma seperated")] + $FeatureConfigValues + ) + + $inputParams = $PSBoundParameters + $logger = [Logger]::new($SubscriptionId) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine + "`r`nMethod Name: Add-AztsFeatureConfigurationValues `r`nInput Parameters: $(($inputParams | Out-String).TrimEnd()) `r`n"), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Starting process to add Configuration Values for $FeatureName feature. This may take 2-3 mins...", $([Constants]::MessageType.Info)) + + + #checking if ConfigurationValues enetered is having single or multiple values + if ( !$FeatureConfigValues.Contains(",")) { + $FeatureConfigValues += ","; + } + + #Splitting the UserObjectIds from (,) + [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); + + # Set the context to host subscription + Set-AzContext -SubscriptionId $SubscriptionId | Out-null + + # AzTS resources name preparation + $ResourceId = '/subscriptions/{0}/resourceGroups/{1}' -f $SubscriptionId, $ScanHostRGName; + $ResourceIdHash = get-hash($ResourceId) + $ResourceHash = $ResourceIdHash.Substring(0, 5).ToString().ToLower() + + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage("Loading File: [$($FilePath)]...") + + # Getting Control Configuration from file path + if (-not (Test-Path -Path $FilePath)) { + $logger.PublishCustomMessage("ERROR: File - $($FilePath) not found. Exiting...", $([Constants]::MessageType.Error)) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + break + } + + $JsonContent = Get-content -path $FilePath | ConvertFrom-Json + + $logger.PublishLogMessage("Loading File: [$($FilePath)] completed.") + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + + $FilteredfeatureSetting = $JsonContent | Where-Object { ($_.FeatureName -ieq $FeatureName) } + if ($null -ne $FilteredfeatureSetting) { + + + #Checking if any Dependent Features needs to be enabled + foreach ($dependentConfiguration in $FilteredfeatureSetting.ConfigurationDependencies) { + $IntPrivilegedEditorIds = @() + $NewAppSettings = @{} + $NewConfigurationList = @{} + $ExistingConfigurationList = @{} + + $ComponentName = $dependentConfiguration.ComponentName + $ResourceHash; + + #Getting Existing configuration value + $AzTSAppConfigurationSettings = Get-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -ErrorAction Stop + + foreach ($Configuration in $dependentConfiguration.Configuration) { + if ($null -ne $AzTSAppConfigurationSettings) { + + if ($FeatureName -ieq "CMET" -or $FeatureName -ieq "MG Compliance Initiate Editor") { + + #Splitting the UserObjectIds from (,) + [System.Collections.ArrayList] $FeatureConfigValueArray = $FeatureConfigValues.Split(',').Trim(); + $IntPrivilegedEditorIds = @() + # Existing app settings + $AppSettings = $AzTSAppConfigurationSettings.SiteConfig.AppSettings + + # Moving existing app settings in new app settings list to avoid being overridden + ForEach ($appSetting in $AppSettings) { + $NewAppSettings[$appSetting.Name] = $appSetting.Value + + #Checking if Configuration Key exist to get the exisiting array value + if ($appSetting.Name.Contains($Configuration)) { + + $appSettingNameArray = $appSetting.Name.Split('_'); + $IntPrivilegedEditorIds += $appSettingNameArray[6]; + + #checking if the Configuration value exist (Key and Value) to avoid duplication + if ($FeatureConfigValueArray.Contains($appSetting.Value)) { + $ExistingConfigurationList["$($appSetting.Name)"] =$appSetting.Value + $FeatureConfigValueArray.Remove($appSetting.Value); + } + } + } + + #If exisitng configuration values does not exist, then setting it to 0 + if ($IntPrivilegedEditorIds.Count -eq 0) { + $IntPrivilegedEditorIds = 0 + } + + #Fetching max value + $IntPrivilegedEditorIdsMaxValue = ($IntPrivilegedEditorIds | Measure-Object -Maximum).Maximum + + #Adding configuration + foreach ($FeatureConfig in $FeatureConfigValueArray) { + if ($FeatureConfig -ne "") { + $NewAppSettings["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig + $NewConfigurationList["$Configuration$IntPrivilegedEditorIdsMaxValue"] = $FeatureConfig + $IntPrivilegedEditorIdsMaxValue++ + } + } + + } + } + } + + try { + if ($FeatureConfigValueArray.Count -gt 0) { + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Updating configuration for [$($ComponentName)]...", $([Constants]::MessageType.Info)) + $logger.PublishLogMessage($NewConfigurationList) + + #uncomment below line to see data in output console window + #$(( $NewConfigurationList | Out-String).TrimEnd()) | Write-Host -ForegroundColor $([Constants]::MessageType.Info) + + #Updating the new configuration values + $AzTSAppConfigurationSettings = Set-AzWebApp -ResourceGroupName $ScanHostRGName -Name $ComponentName -AppSettings $NewAppSettings -ErrorAction Stop + + $logger.PublishCustomMessage("Updated configuration for [$($ComponentName)]." , $([Constants]::MessageType.Update)) + + if ($ExistingConfigurationList.Count -gt 0) + { + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage("Existing Configuration found:") + $logger.PublishLogMessage($ExistingConfigurationList) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + } + } + else { + $logger.PublishCustomMessage("Entered configuration values are already present in $ComponentName.", $([Constants]::MessageType.Error)) + $logger.PublishLogMessage("Existing Configuration found:") + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + $logger.PublishLogMessage($ExistingConfigurationList) + $logger.PublishLogMessage($([Constants]::SingleDashLine)) + break + } + } + catch { + $logger.PublishCustomMessage("Error occurred while updating Configuration. Error: $($_)", $([Constants]::MessageType.Error)) + break + } + } + if ($FeatureConfigValueArray.Count -gt 0) { + $logger.PublishCustomMessage($([Constants]::DoubleDashLine), $([Constants]::MessageType.Info)) + $logger.PublishCustomMessage("Successfully added configuration(s) for [$FeatureName] feature." , $([Constants]::MessageType.Update)) + } + $logger.PublishLogFilePath() + } + else { + $availableFeatureName = $JsonContent.FeatureName -join ", " + $logger.PublishCustomMessage("The value entered for FeatureName: $FeatureName is invalid. Valid values are [$availableFeatureName]. Exiting..." , $([Constants]::MessageType.Error)) + $logger.PublishLogFilePath() + } +} + +function get-hash([string]$textToHash) { + $hasher = new-object System.Security.Cryptography.MD5CryptoServiceProvider + $toHash = [System.Text.Encoding]::UTF8.GetBytes($textToHash) + $hashByteArray = $hasher.ComputeHash($toHash) + $result = [string]::Empty; + foreach ($byte in $hashByteArray) { + $result += "{0:X2}" -f $byte + } + return $result; +} + +class Constants { + static [Hashtable] $MessageType = @{ + Error = [System.ConsoleColor]::Red + Warning = [System.ConsoleColor]::Yellow + Info = [System.ConsoleColor]::Cyan + Update = [System.ConsoleColor]::Green + Default = [System.ConsoleColor]::White + } + + static [string] $DoubleDashLine = "================================================================================" + static [string] $SingleDashLine = "--------------------------------------------------------------------------------" +} + +class Logger { + [string] $logFilePath = ""; + + Logger([string] $HostSubscriptionId) { + $logFolerPath = "$([Environment]::GetFolderPath('LocalApplicationData'))\AzTS\FeatureUpdate\Subscriptions\$($HostSubscriptionId.replace('-','_'))"; + $logFileName = "\$('FeatureUpdateLogs_' + $(Get-Date).ToString('yyyyMMddhhmm') + '.txt')"; + $this.logFilePath = $logFolerPath + $logFileName + # Create folder if not exist + if (-not (Test-Path -Path $logFolerPath)) { + New-Item -ItemType Directory -Path $logFolerPath | Out-Null + } + # Create log file + + New-Item -Path $this.logFilePath -ItemType File | Out-Null + + } + + PublishCustomMessage ([string] $message, [string] $foregroundColor) { + $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor $foregroundColor + } + + PublishCustomMessage ([string] $message) { + $($message) | Add-Content $this.logFilePath -PassThru | Write-Host -ForegroundColor White + } + + PublishLogMessage ([string] $message) { + $($message) | Add-Content $this.logFilePath + } + + PublishLogMessage ([hashtable] $message) { + $($message) | Format-Table -Wrap -AutoSize | Out-File $this.logFilePath -Append utf8 -Width 100 + } + + PublishLogFilePath() { + Write-Host $([Constants]::DoubleDashLine)"`r`nLogs have been exported to: $($this.logFilePath)`n"$([Constants]::DoubleDashLine) -ForegroundColor Cyan + } } \ No newline at end of file