If you deploy your web application to Azure AppServices a common problem to solve is to secure the database server it is using. Since the solution is deployed in a public cloud, you want to ensure that the database can only be accessed from the web application and not by the entire internet. The Premium SKU of AppServices, called AppService Environment (ASE), offers dedicated VMs for your AppServices that are deployed in your own private Virtual Network. Using ASE would solve the problem, since the db server could have no public ip address then, but using ASE comes with a higher price tag which sometimes is just too high. So what other options do exist?
My web application used in this blog post is simple. It’s a webapp in AppServices and a VM with MySQL database installed. I used a Classic VM here, but it could have been an ARM VM too.
Solution 1 – Endpoint ACLs or NSG
The first solution is to let the MySQL database server open port 3306 publically but limit who can access it to only the AppServices webapp. With a Classic VM you use ACLs on the Endpoint and on an ARM VM you would use NSG (Network Security Groups).
You find the public ip address that AppServices is using for the webapp by doing a simple nslookup of the fqdn. Using that ip address we set an ACL on the CIDR address of ip-addr/32 (32 meaning 1 single ip address)
In the configuration for the connection string in the webapp, you use the FQDN of the VM, like cljungmysqldb02.cloudapp.net.
Pros – Mission accomplished! You’ve successfully locked down the database server to only being used from your webapp. This is the cheapest solution since there are no extra resources provisioned.
Cons – The ip address of your webapp may change causing your app to break and forcing you to change the ACL. During this period someone else may have this ip address and have the possibility of attacking you. Unlikely, yes, but the probability exists and you have to be open about it in a security review.
Solution 2 – AppServices VNet Integration
The second solution is to deploy the MySQL database VM into an Azure Virtual Network that has Point-to-site (P2S) VPN gateway enabled. The db server opens port 3306 in its firewall, but it has no public ip address or endpoint and can only be accessed via its internal ip address. AppServices has a network feature called VNet Integration, which means it can act as a VPN Client accessing the VNet via P2S.
For setting up a P2S VNet, please see the references. It involves defining an ip address range that the clients will use when the connect to the network, then creating the VPN Dynamic Gateway and generating and uploading a self-signed certificate for the P2S connections.
In my test VNet I use 10.8.0.0/22 for the subnet that the VM will be in and 10.41.0.0/24 for the subnet that P2S clients will use. When you see the webapp screenshots below this will be important.
When the VNet is ready, you can go to the Networking section in the AppService configuration and add the VNET Integration. It can connect to both ARM and Classic VNets.
Pros – Mission accomplished again! You’ve successfully locked down the database server to only being used from your webapp. This solution do not expose the database to the public internet and does not face the risk of ip addresses to change.
Cons – The VPN Gateway needed for the P2S clients to connect will incure cost. The AppService also needs to be on a Standard plan in order to be able to connect to P2S. This combined makes this a more expensive solution than Solution 1, but it is still cheaper than an AppServicesEnvironment. Also, in my tests I see a performance penalty accessing the database via the VPN connection. Solution 2 will be a bit slower than Solution 1.
Sample WebApp setup
I created a tiny webapp where I can specify how to access the database via a QueryString parameter in the HTTP request, specifying if it should use Solution 1 or 2. I have three settings where the first one (mysqldb.vnet) is accessing the database via the internal ip address. The other two are the same and accesses the database using the public endpoint that is ACL’d.
My MySQL database is the employees sample database (found here) where I query the departments table.
The webapp also does this query where USER() part will tell us from where we access the db server
select @@hostname as host, DATABASE() as db, USER() as user
Sample test run – Solution 1
Querying the MySQL using Solution 1 shows that we access the db using the AppServices public ip address (email@example.com) via the public ip address of the MySQL server. When I hit refresh in the browser a few times, the query time quickly drops down to 1-2-3 milliseconds.
Sample test run – Solution 2
Querying the MySQL using Solution 2 shows that we access the db using the AppServices ip address (firstname.lastname@example.org) that it got via the point-to-site connection to the VPN network. We can also see that we access the MySQL server using its internal ip address 10.8.0.4. This time, hitting refresh in the browser, the query time quickly can drop down to 2-3-4 milliseconds, but it is more common that the query time is in range 5-10 ms. This is noticeably slower than Solution 1.
I’ve showed you that you don’t have to use AppServiceEnvironment to lock down the database your AppServices WebApp. There are two simpler solutions where Solution 1 is really simple to implement and is very performant. Solution 2 will pass a tough security review but will be a little less performant since the overhead of going through the Point-to-site VPN connection.
Configuring Point-to-site connection to a VNet
Integrating AppServices WebApps with VNET Integration