Recently, Shannon & I got an interesting request from a customer who is using multi-tenant App Services (e.g., non-ASE), but wanted to keep communications restricted to their virtual network. If you’re familiar with Azure App Services, typically the way to achieve this is via an App Service Environment (ASE). ASEs are single-tenant, dedicated nodes running the App Service stack in your virtual network - this flips the ‘private’ bit as they have RFC 1918 IPs from within your VNet. This means you can do pretty much whatever you want with networking - use 17 firewalls in front of it, access vnet or on-prem resources, whatever. Problem is, they can get a bit pricey, at least relative to multi-tenant App Service. Internal ASEs can have a bit of deployment drama too, since you need to manage DNS and get wildcard certificates for your ASE (wildcard certs being particularly difficult to get from security & ops teams). Fortunately, with some (very) recent additions, this is possible using a combination of ‘New’ VNET Integration (gateway-less) and Service Endpoints/IP Restrictions in Azure. Let’s dig in.
Layout
We’ve got:
jpd-app-1
, a web app (API) with it’s own App Service Planjpd-app-2
, another web app (API) with it’s own App Service Plan- a vnet,
vnet1
, with three subnets,jpd-app-1-subnet
,jpd-app-2-subnet
anddefault
- a VM for testing, in the
default
subnet - The two apps & vnet live in the same region
We need jpd-app-1
and jpd-app-2
to talk to each other and also be accessible from the VNet, but not from the internet at all.
Integrating with the VNet
First we need to get integrated to the VNet from each of our app services. We need to keep the app service integrations in their own subnets, so we can delegate the subnet to the App Service. First we need to register the Microsoft.Web
service endpoint. If you’re creating a new VNet, you can do this at creation time. If not, it’s easy to enable afterward.
During creation:
After creation (from the VNet –> Service Endpoints pane). Enable it on all the subnets you want to access our App Services from.
Once you’ve enabled them, head to the Networking pane of the first App Service (jpd-app-1
in my example).
Configure VNet Integration, then choose Add VNet (Preview). Choose your vnet, then your subnet.
Do this for both of your App Services, integrating each into their respective subnets. At this point, our app services are now connected to vnet1
- if we had resources we needed to access in the VNet, like a VM, a SQL Managed Instance or even something on-prem via site-to-site VPN, we’d be able to by this point. The app services are exposed to the internet, however. We’ve enabled access from the App Services to the VNet, but we haven’t restricted access to the App Services to only the VNet just yet. For that we’ll use Access Restrictions.
Access Restrictions
Next we need to enforce the access restrictions. Back in the networking pane of your App Service, you’ll see ‘Access Restrictions.’ This is where we’ll add our vnets. By default, your App Service will allow all traffic from all sources. You can leave the default Allow All rule, since as soon as we add our own restrictions, the Allow All rule becomes a Deny All, with our rules taking priority.
You’ll also note there are two hosts listed - <yourapp>.azurewebsites.net
and <yourapp>.scm.azurewebsites.net
- the first one is your app, the second one (.scm.) is the backstage view of your web app, the Kudu console. You can restrict these independently. Make sure you add restrictions for both the main site and Kudu/scm site if you don’t want any access from the internet!
Now let’s add our restriction! Since this is jpd-app-1
and it’s integrated into the jpd-app-1-subnet
subnet, we need to add two rules:
- Access from
jpd-app-2-subnet
subnet - Access from
default
subnet - Deny everything else
First the jpd-app-2-subnet
rule. Make sure you choose ‘Virtual Network’ in the type. Then choose your vnet (vnet1
) and your subnet for the other app that needs access (jpd-app-2-subnet
). Repeat for (default
) to enable the rest of the vnet and any other vnets/subnets that may need access.
Repeat for the other app
In your other app (jpd-app-2
), do the same thing. Add access restrictions, only this time choose the subnet of the other app (jpd-app-1-subnet
).
Testing
If you go to your app in a browser from your local machine, you should get a 403, ‘web site stopped.’ This is the experience for app services that are restricted - it’s a 403 Forbidden, stopped is a bit misleading here.
From your web app’s kudu console (<yourapp>.scm.azurewebsites.net
), which is either accessible only from the VM on your vnet, or from the internet, if you didn’t add a restriction, we can do a curl to the other app to make sure our configuration is all square. tcpping
isn’t valid here, because the site does respond to ping - as it is available on the internet, but returning 403
.
curl -sI https://<yourapp>.azurewebsites.net
This should return some HTML/markup. If it’s a 403
, something is wrong. If you get a 200/OK
, you’re in business!
Note: you may notice that my URLs changed near the end - I added a bad rule so I had to trash the two app services and recreate them. Don’t put zero (0) as a priority for a rule!
I haven’t tried this with Functions on a consumption plan yet, but that’s coming next. Stay tuned!