13Apr, 2021
A Complete Guide to Restricting Access to Sitecore Managed Cloud
Sitecore Managed Cloud can be a lot of things for an organization, saving a great deal of time and expense having offloaded numerous tasks and resources. One important step that's still up to you, is restricting access to non-delivery resources.
You can restrict access to your resources by IP in Azure, but the challenge is knowing what ranges to whitelist. Having spoken with Sitecore about the documentation around this, they've confirmed the instructions are backwards in how to discover what resources should be allowed in. Well, we'll make it clear now.
You're going to have to whitelist the following ranges when applying IP restriction in Azure:
- Sitecore Managed Cloud instances such as Cortex Reporting, SOLR or Azure Search, etc.
- Integrations such as SiteImprove.
- Application Insights.
- Your organization's IP addresses.
You can get the ranges for your integrations and offices, and Application Insights at the Microsoft documentation page here, but the one that requires a bit of work are the Sitecore dependencies.
Discovering IP Addresses Required by Sitecore
The first step in getting these IPs will be understanding what dependencies there are between the Sitecore instances. We'll use a PowerShell script to do this. If you haven't installed the PowerShell AzureRM module on your PC before, you're going to have to do that first with the following commands.
set-executionpolicy unrestricted
then
Install-Module AzureRM -AllowClobber
Once installed, create the file GetConnectionStrings.ps1 with the following script:
param( [Parameter(Mandatory=$True)][string] $subscriptionId, [Parameter(Mandatory=$True)][string] $resourceGroupName ) Login-AzureRmAccount $env:ScriptLocation = Get-Location $rootPath = $env:ScriptLocation Set-AzureRmContext -SubscriptionId $subscriptionId $resourcesObject=New-Object -TypeName PSObject $resourcesObject=@{} $webApps=Get-AzureRmWebApp -ResourceGroupName $resourceGroupName foreach($webApp in $webApps) { $webAppName=$webApp.Name $fileName="ConnectionStrings.config" $kuduApiUrl = "https://"+$webAppName+".scm.azurewebsites.net/api/vfs/site/wwwroot/App_Config/$fileName" Write-Host "Getting Webapp $webAppName credentials" $resourceType = "Microsoft.Web/sites/config" $resourceName = "$webAppName/publishingcredentials" $publishingCredentials = Invoke-AzureRmResourceAction -ResourceGroupName $resourceGroupName -ResourceType $resourceType ` -ResourceName $resourceName -Action list -ApiVersion 2015-08-01 -Force $kuduApiAuthorisationToken = ("Basic {0}" -f [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes(("{0}:{1}" -f $publishingCredentials.Properties.PublishingUserName, $publishingCredentials.Properties.PublishingPassword)))) $filePath="$rootPath\$fileName" try{ Invoke-RestMethod -Uri $kuduApiUrl ` -Headers @{"Authorization"=$kuduApiAuthorisationToken;"If-Match"="*"} ` -Method GET ` -ContentType "xml" ` -OutFile $filePath ` -ErrorAction SilentlyContinue }catch{} [xml]$xmlResult=Get-Content $filePath $webAppObject = New-Object -TypeName PSObject $webAppObject | Add-Member -Name 'ConnectionStrings' -MemberType NoteProperty -Value (New-object System.Collections.Arraylist) $webAppObject | Add-Member -Name 'ReferencedWebAppsComponents' -MemberType NoteProperty -Value (New-Object System.Collections.Arraylist) $webAppObject | Add-Member -Name 'WebAppName' -MemberType Noteproperty -Value '' $webAppObject | Add-Member -Name 'OutboundIpAddresses' -MemberType Noteproperty -Value '' $webAppObject.WebAppName=$webAppName Write-Host "Getting web app $webAppName connection strings for databases and referenced components" -ForegroundColor DarkYellow foreach( $connectionStringNode in $xmlResult.connectionStrings.add) { if($connectionStringNode.connectionString.StartsWith("http")) { $referencedWebApp=[PSCustomObject]@{ 'ConnectionStringName'=$connectionStringNode.name 'WebAppUrl'=$connectionStringNode.connectionString 'WebAppName'=$connectionStringNode.connectionString.Replace("https://","").Replace(".azurewebsites.net","") } if($referencedWebApp.WebAppName -ne $null) { $j=$webAppObject.ReferencedWebAppsComponents.add($referencedWebApp) } } elseif($connectionStringNode.name.StartsWith("reporting.apikey") -and ($webApp.Name.EndsWith("rep") -ne $True)) { $reportingWebApp=$webApps | Where-Object { $_.Name.EndsWith("rep") -and ($_.Name.EndsWith("ma-rep") -eq $false)} $repotingWebAppName=$reportingWebApp.ResourceName $reportingWebAppURL="https://$repotingWebAppName.azurewebsites.net" $referencedWebApp=[PSCustomObject]@{ 'ConnectionStringName'=$connectionStringNode.name 'WebAppUrl'=$reportingWebAppURL 'WebAppName'=$repotingWebAppName } if($referencedWebApp.WebAppName -ne $null) { $j=$webAppObject.ReferencedWebAppsComponents.add($referencedWebApp) } } else { $sb = New-Object System.Data.Common.DbConnectionStringBuilder try { $sb.set_ConnectionString($connectionStringNode.connectionString) $connectionString=[PSCustomObject]@{ 'name'=$connectionStringNode.name 'initialcatalog'=$sb.'initial catalog' 'datasource'=$sb.'data source' } if($connectionString.initialcatalog -ne $null){ $i=$webAppObject.ConnectionStrings.Add($connectionString) } } Catch { } } } write-host "Getting web app $webAppName Outbound Ip Addresses" -ForegroundColor DarkYellow $webAppObject.OutboundIpAddresses=(Get-AzureRmWebApp -Name $webAppName -ResourceGroupName $resourceGroupName).OutboundIpAddresses $resourcesObject.Add($webAppName,$webAppObject) } $outputFile= $rootPath+"\WebAppConnectionStrings.json" $resourcesObject | ConvertTo-Json -Depth 4 | Out-File $outputFile Write-Host "Script completed successfuly" -ForegroundColor Green Write-Host "Find the results in $outputFile" -ForegroundColor Green
Execute this command (.\GetConnectionStrings.ps1), and you'll be asked for your Azure subscription ID, resource group name, and username/password. This will generate the file, WebAppConnectionStrings.json which we'll need.
In today's example I'm going to restrict access to the Content Authoring instance, so when I open WebAppConnectionStrings.json, I'm going to look for the entry named mc-xxxxxxxx-xxxx-xxxx-xxxx-xxxxxx-cm.
When looking at this entry, you'll see ReferencedWebAppsComponents, which has all the resources that must be whitelisted. Search for each item by its WebAppName in this file, get the OutboundIpAddresses, and add them to the list of IPs you've made already.
Applying Your IP Restrictions
Now that you have all of the ranges needed, open your Application Insights and filter to the last 30 minutes. Create the file AutoApplyIpRanges.ps1 from the following script:
Param ( [Parameter(Mandatory = $true,Position = 0, HelpMessage = 'Please provide subscription Name')] [ValidateNotNullOrEmpty()] [string]$SubscriptionId, [Parameter(Mandatory = $true,Position = 1, HelpMessage = 'Please provide resource group Name')] [ValidateNotNullOrEmpty()] [string]$ResourceGroup, # Name of your Web or API App. [Parameter(Mandatory = $true,Position = 2, HelpMessage='Provide Webapp Name')] [ValidateNotNullOrEmpty()] [string]$WebAppName, [Parameter(Mandatory = $true,Position = 3, HelpMessage = 'Please provide Azure User Id')] [ValidateNotNullOrEmpty()] [string]$AzureUserId, [Parameter(Mandatory = $true,Position = 4, HelpMessage = 'Please provide Azure Password')] [ValidateNotNullOrEmpty()] [string]$AzurePassword, # Priority value. [Parameter(Mandatory=$true,HelpMessage='Provide priority number')] [ValidateNotNullOrEmpty()] [int]$Priority, # WhitelistIp values. [Parameter(Mandatory=$true,HelpMessage='Provide list of IPs to whitelist')] [ValidateNotNullOrEmpty()] [string]$IPList, [ValidateNotNullOrEmpty()] [Parameter(HelpMessage='Provide Firewall Rule Name')] [string]$RuleName='WhiteList' ) $ErrorActionPreference='stop' try { function Add-AzureIpRestrictionRule { Write-Host "Whitelisting $($rule.IPAddress)" $ApiVersions = Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Web | Select-Object -ExpandProperty ResourceTypes | Where-Object ResourceTypeName -eq 'sites' | Select-Object -ExpandProperty ApiVersions $LatestApiVersion = $ApiVersions[0] $WebAppConfig = Get-AzureRmResource -ResourceType 'Microsoft.Web/sites/config' -ResourceName $WebAppName -ResourceGroup $ResourceGroup -ApiVersion $LatestApiVersion $WebAppConfig.Properties.ipSecurityRestrictions = $WebAppConfig.Properties.ipSecurityRestrictions + @($rule) $null=Set-AzureRmResource -ResourceId $WebAppConfig.ResourceId -Properties $WebAppConfig.Properties -ApiVersion $LatestApiVersion -Force Write-Host "Whitelisting $($rule.IPAddress) Success" } #Login to Azure Write-Host "`nLogging into Azure" $SecurePwd = ConvertTo-SecureString -String $AzurePassword -AsPlainText -Force $Creds = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList ($AzureUserId, $SecurePwd) Connect-AzureRmAccount -Credential $Creds Write-Host "Logging into Azure Success`n" #Set Required subscription Write-Host "`nSelecting subscription $SubscriptionId" Set-AzureRmContext -SubscriptionId $SubscriptionId Write-Host "Selecting subscription $SubscriptionId Success `n" $IncrementCounter = 1 $IPs=$IPList -split ';' foreach ($IPAddress in $IPs) { if($IPAddress -match '\\' -or $IPAddress -eq '' -OR $IPAddress -eq ' ') { Write-Host "Invalid IP address format $IPAddress, So ignored it" Continue } try { if($IPAddress -match '/') { $ip=$IPAddress.Substring(0,$IPAddress.IndexOf('/')) } else { $ip=$IPAddress } #Ip address validation $null=[IPAddress]$ip } catch { Write-Host "Invalid IP address format $IPAddress, So ignored it" Continue } if($IPAddress -match '/') { $IPAddress=$IPAddress.Trim() $rule = [PSCustomObject]@{ IPAddress = "$($IPAddress)" Action = 'Allow' Priority = "$Priority" Name = "$RuleName-$IncrementCounter" } $IncrementCounter++ Add-AzureIpRestrictionRule -ResourceGroup "$ResourceGroup" -AppServiceName "$WebAppName" -rule $rule } else { $IPAddress=$IPAddress.Trim() $rule = [PSCustomObject]@{ IPAddress = "$($IPAddress)/32" Action = 'Allow' Priority = "$Priority" Name = "$RuleName-$IncrementCounter" } $IncrementCounter++ Add-AzureIpRestrictionRule -ResourceGroup "$ResourceGroup" -AppServiceName "$WebAppName" -rule $rule } } } catch { # get error record [Management.Automation.ErrorRecord]$e = $_ # retrieve information about runtime error $info = [PSCustomObject]@{ Exception = $e.Exception.Message Reason = $e.CategoryInfo.Reason Target = $e.CategoryInfo.TargetName Script = $e.InvocationInfo.ScriptName Line = $e.InvocationInfo.ScriptLineNumber Column = $e.InvocationInfo.OffsetInLine } # output information. Post-process collected info, and log info (optional) $info Throw $_.exception.message }
Execute the following command to apply your whitelisted IPs.
.\AutoApplyIpRanges.ps1 ` -SubscriptionId '[your-subscription-id]' ` -ResourceGroup '[you-resource-group]' ` -WebAppName '[your-app's-name]' ` -AzureUserId '[your-email]' ` -AzurePassword '[your-password]' ` -Priority 100 -IPList '123.123.123.123/28;456.456.456.456/28' ` -RuleName '[name-for-your-rule]'
A couple of things about this. When you run this script it will automatically create Deny All entry. If you've whitelisted an IP, you obviously need to deny all others, right? Also, Application Insights will hiccup if you're entering a large number of IPs, so just monitor that everything comes back up to 100% availability when it's done. If not, just remove all entries and check what IPs are missing.
Now, this is how you address one instance, where you likely have many. The good news is the IP ranges are usually the same across all of them. Good luck!