Azure functions is an incredible cloud solution to deploy a variety of applications, ranging from a simple script to a full-scale application. FaaS is especially interesting from a security point of view, since the cloud provider manages most technology layers.
In this article, we will suggest two ways to improve the security of a database used by an Azure function. The first is based on Identity and Access Management (IAM), the second is based on virtual networks. The azure function used in this article is written in Python and the database is PostgreSQL.
Managing and protecting credentials is a complicated task. A good solution would be to avoid manipulating secrets. Thanks to IAM, we will use ephemeral credentials that are generated on demand. Fortunately, such practice is getting popular.
First set the azure Active Directory admin for the database and get an access token as described here : https://docs.microsoft.com/azure/postgresql/howto-configure-sign-in-aad-authentication. We will need the token later.
Then, enable managed identities in your function app, by clicking on identity in the sidebar of your function dashboard.
To get the application ID, find Enterprise applications in the search bar. Make sure to select all applications and search for your function.
After clicking on your application, you should get something that looks like this. Make sure to save the application ID.
SET aad_validate_oids_in_tenant = off;
CREATE ROLE myuser WITH LOGIN PASSWORD ‘<APPLICATION_ID>’ IN ROLE azure_ad_user;
From now on, the username you will be using in your application to connect is myuser. Where ever you need the database password in your code, call the following function that will fetch a token.
from azure.identity import DefaultAzureCredentialdef getAzToken():
credential = DefaultAzureCredential()
token = credential.get_token(“https://ossrdbms-aad.database.windows.net")
token = getAzToken()conn = psycopg2.connect(
password=token)cursor = conn.cursor()
cursor.execute(“SELECT * FROM clients”)
record = cursor.fetchall()
print(“Result “, record)
As shown in the example above, we never manipulate secrets and the generated token is only valid for a given period of time.
NB: You will probably need to specifically grant myuser permissions over databases, tables …
Virtual network integration
Now that we don’t need to store secrets anymore, there is another aspect of the communication that we can improve. We will integrate the Azure function and the database to the same vnet. Then we will not allow database connections from outside the vnet. Such measure will drastically reduce the attack surface.
PostgreSQL private endpoint
To connect the database to the vnet we will need to use a private endpoint. This functionality is only available in the general purpose plan of PostgreSQL and above, it’s not possible in the basic plan.
If you don’t have a vnet, create a new one.
To create a private endpoint, go to your database dashboard, on the sidebar, you will find Private endpoint connections. Click on + Private endpoint and follow the instructions. Be sure to enable the private DNS integration in the setup (it is enabled by default).
You can find the local ip address of the endpoint in the virtual network dashboard. We will be using that ip address for database connection.
Azure function vnet integration
To integrate an Azure function to a vnet, the function should run on a premium or a dedicated plan.
On the function dashboard, on the side bar, you will find Networking. The page contains various networking solutions, we need VNet Integration.
You will click on add and follow the instructions. Choose the vnet where you connected the database private endpoint. In the process you would probably need to create a new subnet in your virtual network so it will be delegated by Azure functions.
After the setup, if you want to be sure that the connection goes through the vnet, replace the database hostname in your application by the local ip of the database private endpoint, the connection should work.
Now that the connection goes through the vnet, we can go to the networking page in the database dashboard and enable the option deny public access, this will exclusively allow private endpoint connections.
From now on, to connect to the database from the function, you must use the ip address of the database’s endpoint as the host name.
Now that you don’t need to handle a password and the database is only exposed to your virtual network, the database attack surface is drastically reduced.
This story is part of Reda Souadi research during his internship at intuitem regarding Serverless applications security.