3
votes

I have an application hosted in Azure, and I use Azure DevOps to manage my build and release pipelines. As part of the release, I warm up the application by making a request to the root url (e.g. https://myapp.azurewebsites.net). In order to make this request I must first make sure the hosted build agent running the deployment has access to that url (or I will get a 403). I have written a short powershell script to achieve this, and put it in an Azure Powershell task. It adds the IP of the build agent to the IpSecurityConfiguration of the app service. So far so good. It works perfectly for apps that are just apps. Where it falls down is when I try to use it against a staging environment. When we release to production we first push the code to a staging slot, then flip it over to live when we've run our tests and made sure everything is good. The powershell script that correctly handles the IpSecurityConfiguration for the app services does not work on the staging slot. To access a staging slot, we use myappname/slots/staging for the variable $(WebApiName), normally it would just be the name of the app service itself. Again, this works perfectly if I run the script from my local environment, it only fails in the pipeline. The code is below:

# Whitelist Azure Agent IPs

$agentIP = Invoke-RestMethod http://ipinfo.io/json | Select -exp ip

Write-Host "Connecting to Azure"

$APIVersion = ((Get-AzureRmResourceProvider -ProviderNamespace Microsoft.Web).ResourceTypes | Where-Object ResourceTypeName -eq sites).ApiVersions[0]
Write-Host "API Version is $APIVersion. Getting web app config for $(WebApiName) in $(ResourceGroupName)"

$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $(WebApiName) -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)


Write-Host "Got web app config: $WebApiConfig" 

$webIP = [PSCustomObject]@{
                          ipAddress = "$agentIP/32";
                          action = "Allow";
                          tag = 'Default';
                          priority = 300;
                          name = $agentIP.ToString();
                          description = $agentIP.ToString() 
                                }
Write-Host "Adding $agentIP to security restrictions"

$WebApiConfig.Properties.ipSecurityRestrictions += $webIP
Write-Host "Updating security restrictions"
# update app restrictions, do not prompt for confirmation
$result = Set-AzureRmResource -ResourceId $WebApiConfig.ResourceId -Properties $WebApiConfig.Properties -ApiVersion $APIVersion -Force

To muddy the water somewhat, I can get the exact same code to work perfectly with the staging slot locally by changing

$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/config -ResourceName $(WebApiName) -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)

to

$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites -ResourceName $(WebApiName)/config -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)

but this doesn't work in the Azure Powershell task. Instead I can't deploy to any environment because the task fails while trying to access IpSecurityRestrictions on the $WebApiConfig object. The exception is "Exception setting "ipSecurityRestrictions": "The property 'ipSecurityRestrictions' cannot be found on this object. Verify that the property exists and can be set."

As I said earlier, if I run the script in exactly this form locally, it works perfectly. Obviously I have to manually replace the variables that come from the build pipeline, but otherwise there is no difference between code that works exactly as I want it to on my local machine and code that fails in the release. You can verify this by swapping out $(WebApiName) for a valid app service name and $(ResourceGroupName) for the resource group that app service is in. I put a line in about halfway down that outputs $WebApiConfig so that I can see what it is, and on my local machine I see a valid object, while in the output of the task I get nothing. The line just says "Got web app config:"

Anyone got any ideas?

  • I've tried changing the version of powershell used by the task to match the version I've got.
  • I've tried using the preview version of the task (v4, otherwise I've been using v3).
  • I've tried every permutation of /sites/config everywhere I can think of in the call to Get-AzureRmResource (since that was what allowed it to work locally on the slot).

Just one final thing in case anyone wonders. I'm doing it this way instead of whitelisting all the IPs in Microsoft's list (https://www.microsoft.com/en-us/download/confirmation.aspx?id=41653) for two reasons, firstly it's a lot easier to maintain a short list of our own IPs, and secondly there seems to be a bug somewhere in the way Azure handles those CIDR definitions because IPs that are categorically in those ranges are frequently blocked during our deployments even when we have the entire file whitelisted. This way I just add whichever IP is currently being used dynamically to the whitelist, and remove it after we're done. Assuming I can get it to work...

1

1 Answers

5
votes

Finally figured out the solution to this. In order to work with slots the resource type has to be subtly different. This line works in an Azure Powershell task:

$WebApiConfig = (Get-AzureRmResource -ResourceType Microsoft.Web/sites/slots/config -ResourceName $(WebApiName) -ResourceGroupName $(ResourceGroupName) -ApiVersion $APIVersion)

Posting in case it helps anyone else with the same issue. I can confirm that the approach I've taken works great in managing access to Azure sites by build agents, and saves a lot of messing around with Microsoft's build agent xml file.