Intro
When you enable Access Restrictions onto your Azure App Services (such as an Azure Function or Web App) you cut off any inbound traffic from the internet. This can pose an extra challenge when needing to deploy code via pipelines as you now have no inbound public access to the SCM (source control manager aka Kudo service).
This guide will go through how to deploy with these restrictions in place via Microsoft hosted Azure DevOps agents.
Azure DevOps Agents
Firstly comes the challenge of continuous deployment with these restrictions in place. Azure DevOps Agents hosted by Microsoft state:
Meaning if we try to deploy to the app service now we have these restrictions we’ll likely get a 401 unauthorized error, as we’ve disabled public access to the app service (both the frontend and backend-SCM where you’d typically deploy code to).
There’s typically two main options to get around this:
- Deploy a VM and install an ADO self-hosted agent on the vNet of the app so it’s coming from an ‘internal’ address rather than public (via Private Endpoint vNet integration) or;
- Create tasks in your pipeline to allow the public IP of the MS agent on the app firewall dynamically during runtime
The blog will focus on the latter as Microsoft have blogged on the first solution here if you’re interested.
Note2: There is a weekly JSON file that posts Azure IP ranges, of which you could extrapolate the ADO IP space for your region in an automated solution instead. This isn’t covered in this blog post however.
The solution
As part of my deployment tasks I dynamically grab the ADO agent IP during runtime, store this into a variable, add it onto the apps access rules, let deployment run through & then remove the rule after. All via the pipeline deployment so it’s fully automated whilst maintaining our security posture.
Prerequisites:
- Public network access: Enabled from selected networks and IP addresses
- Unmatched rule action to Deny (super important)
This keeps the app service unavailable from public access due to the Deny rule match. Additionally we will have a ‘deny all’ rule to block all traffic.
(Note: the Main site & Advanced tool site can have different rule matches, to avoid this you can enable ‘Use main site rules’ to consolidate under one site rule set. Below is just an example screenshot for reference):
This will allow the CLI to dynamically add the ADO agent IP to the rule as a higher priority allowing the agent access for deployment.
ADO Tasks
Now for the tasks themselves.
Grab the agent IP & add it to a pipeline task variable:
Using the website http://ipinfo.io/ip we are able to get the public IP address at the runtime and set it into a pipeline variable called ‘address’ for use later in the pipeline tasks.
- task: Bash@3
name: ADOAgentIP
inputs:
targetType: 'inline'
script: |
ipaddress=$(curl -s http://ipinfo.io/ip)
echo Azure DevOps Agent IP: $ipaddress
echo "##vso[task.setvariable variable=address;isOutput=true;]$ipaddress"
YAMLAdd the rule to the app service:
Using az webapp CLI we’re able to add the IP to the sites access rule restrictions at a priority level higher than the ‘Deny all’ rule seen from earlier in the post. Using the task name from earlier ‘ADOAgentIP’ and the pipeline variable ‘address’ we can create a dynamic variable to add the IP address $(ADOAgentIP.address) in the CLI.
- task: AzureCLI@2
displayName: Add Azure DevOps Agent IP to allow list
inputs:
azureSubscription: $(azureServiceConnection)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az account set --subscription $(prodSub)
az webapp config access-restriction add -g example-rg -n example-func-001 --rule-name ado-agent --action Allow --ip-address "$(ADOAgentIP.address)" --priority 100
YAMLRemoving the rule after your deployment steps:
Lastly, using the CLI once more, we can remove our rule using the rule name. In my example ‘ado-agent’. I’m also including the condition always() so that if the pipeline fails in any of the prior steps, we are removing the IP from the rule list to keep things tidy.
- task: AzureCLI@2
displayName: Remove Azure DevOps Agent IP
inputs:
azureSubscription: $(azureServiceConnection)
scriptType: 'bash'
scriptLocation: 'inlineScript'
inlineScript: |
az webapp config access-restriction remove -g example-rg -n example-func-001 --rule-name ado-agent
condition: always()
YAMLConclusion
That’s it! Quite a simple solution which works really nicely as a starting point. The tasks above are mostly an example, and could be improved on in your own workflow – such as making sure you always remove the IP even if the app service deployment fails. However, this is a good starting point to build out from.
I hope others find this useful.
I found this article when I was looking for a solution to this issue and I must say it is well written. The explanations are excellent, they’re references to other materials and the scripts work! Thank you for this post.
That’s great to hear, I’m glad it helped you 🙂 Thank you!
This did set me on the path, so thank you very much. It appears that the configuration has changed since this article was written – I could only set the deny rule if public access was set to select virtual networks and IP addresses. If enable all networks was allowed, the rules were disabled.
Hey Joe. Glad it helped. You may be right there. I still use this method often so I will look to review my steps and adjust them accordingly if they have slightly changed. The concept, like you say, is still very much the same however.
Shout if you need a hand with anything.
I appreciate that, thanks – I may take you up on that. Glad I could give a little back in the meantime 🙂
Sure Joe, just reach out on socials if you ever do (LinkedIn is a good bet).
I’ve now updated the steps, Joe. Thank you!