Integrate a Web App with a CosmosDB database using a Virtual Network Service Endpoint in Azure
Consider the following real-life application scenario. Your Web App (Azure App Service) accesses a CosmosDB instance to read data. Your users access the web application from its UI, and you want to ensure they cannot directly access the CosmosDB instance using its public URL.
In this article, you will learn:
- How to use a System Managed Identity and RBAC roles to access your CosmosDB instance from your Web App
- How to integrate your Web App and your CosmosDB with an Azure Virtual Network
- How to create a Service Endpoint to allow only the Virtual Network to access CosmosDB
- All the necessary
az
CLI commands to complete the tutorial, without using the Azure Portal UI :)
The following drawing presents in a simple manner the resources architecture:
You can find the code for the Web API that accesses the CosmosDB instance in this repository on my GitHub account. The API returns data from CosmosDB. We do not need additional functionality for this tutorial, as our focus is on the networking aspect.
Up to this point, both the Web API and CosmosDB are publicly accessible, since we can load data from the database from our local computer.
Create a new Resource Group
The first step in our tutorial is to create a new Resource Group to contain all the resources we will create. I used names for the created resources based on my preference; however, you can adapt the commands and define the names of your choice. Run the following command:
az group create \
--name dotNetCosmosDbVnetPrivateEndpoint \
--location switzerlandnorth
Create a new App Service Plan
Next, we create a plan to specify the payment tier of the App Service we are about to create. We pick a standard tier to activate all the features of the web application.
az appservice plan create \
--name dotNetCosmosDbVnetPrivateEndpointAppServicePlan \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--is-linux \
--sku S1
Create a new Web App
For our tutorial, we will use a .NET application to query our CosmosDB. We create a new web app to host this web application:
az webapp create \
--name dotNetCosmosDbVnetPrivateEndpointWebApp \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--plan dotNetCosmosDbVnetPrivateEndpointAppServicePlan \
--runtime "DOTNETCORE:8.0"
Create a CosmosDB account
The account-name
of the CosmosDB account cannot contain capital letters. Run the following command. The creation of this resource can take a few minutes to complete:
az cosmosdb create \
--name cosmosdbprivateendpoint \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--enable-virtual-network true
Create a CosmosDB database with SQL API
Next, create a new database for the account we just created:
az cosmosdb sql database create \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--account-name cosmosdbprivateendpoint \
--name cosmosDBPrivateEndpointDatabase
Create a CosmosDB container
The last step concerning CosmosDB is to create the container that will host our data. The defined partition key is only for use in the example code.
az cosmosdb sql container create \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--account-name cosmosdbprivateendpoint \
--database-name cosmosDBPrivateEndpointDatabase \
--name cosmosDBPrivateEndpointContainer \
--partition-key-path "/OrderId" \
--throughput "400"
Clone the code for the web application and publish it
Clone this repository from my GitHub account. It contains a Web API with one controller and one action that connects to the CosmosDB database we created earlier and queries for data.
Open the repository with a new Visual Studio instance, right-click on the API project, and select Publish. Define a new Publish profile and click on Publish. Your app will be uploaded to the Azure Web App.
After the publish is complete, open Postman and call the action of the API using the publicly accessible URL of your Web App. You will receive a 500 response (internally it is a 403 Forbidden response), which logs the following error in the background, as you have not defined an RBAC role for the Web App to access the CosmosDB:
We need to set authentication for the Web App to access the CosmosDB database.
Grant access rights to your Web App
First, we activate a new System Managed Identity for our Web App. Use the following command which creates the Identity and also stores its principalId
inside a variable. We will use this GUID in the subsequent command.
principalId=$(az webapp identity assign \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--name dotNetCosmosDbVnetPrivateEndpointWebApp \
--query principalId \
--output tsv)
Now, use the following command to give the Web App the right to read data from the CosmosDB database. The principal-id
contains the GUID we stored in the previous command.
az cosmosdb sql role assignment create \
--account-name cosmosdbprivateendpoint \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--scope "/" \
--principal-id $principalId \
--role-definition-id "00000000-0000-0000-0000-000000000001"
Create a new Virtual Network with a Subnet
We are now ready to focus on the networking part of this tutorial. First, we create a new Virtual Network to host our Web App and CosmosDB resources. Run the following command:
az network vnet create \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--name dotNetCosmosDbVnet \
--address-prefix 10.0.0.0/16 \
--subnet-name dotNetCosmosDbSubnet \
--subnet-prefix 10.0.1.0/24
Integrate our Resources into the new Virtual Network
We integrate the Web App into the virtual network we just created. This ensures that the Web App belongs to the same Virtual Network as the CosmosDB and can access the database. Run the following command:
az webapp vnet-integration add \
--name dotNetCosmosDbVnetPrivateEndpointWebApp \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--vnet dotNetCosmosDbVnet \
--subnet dotNetCosmosDbSubnet
For CosmosDB, we first need to enable support for Virtual Networks. We could have added the --enable-virtual-network true
option when initially creating the CosmosDB account. However, for the purposes of this tutorial and to demonstrate the default open-to-everyone option of CosmosDB, I did not include it. Run the following command. It will take several minutes to complete:
az cosmosdb update \
--name cosmosdbprivateendpoint \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--enable-virtual-network true
Now comes the integration of CosmosDB by creating a new network rule. Since we first perform the assignment and later add a new Service Endpoint into the Virtual Network, we set the --ignore-missing-vnet-service-endpoint
to true
. The following command can take several minutes to finish, so take a small break while it runs:
az cosmosdb network-rule add \
--name cosmosdbprivateendpoint \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--virtual-network dotNetCosmosDbVnet \
--subnet dotNetCosmosDbSubnet \
--ignore-missing-vnet-service-endpoint true
Test the Web App one more time
With the RBAC role in place and the integration of our resources into a common Virtual Network, we test our Web App via Postman one more time. We receive a 500 response (internally it is a 403 Forbidden response), which again looks like the following message:
This is because the last and most important part of this tutorial is missing: a Service Endpoint. Through this endpoint, our Web App can query the database and return data to the user.
Add the Service Endpoint into the Virtual Network
The final step of our tutorial is to create a Service Endpoint to allow our CosmosDB to be accessed only through its private IP address. You can read more about Service Endpoints in the official Microsoft documentation.
We create a new dedicated subnet for the Service Endpoint, which is wired to our CosmosDB account.
az network vnet subnet update \
--name dotNetCosmosDbSubnet \
--resource-group dotNetCosmosDbVnetPrivateEndpoint \
--vnet-name dotNetCosmosDbVnet \
--service-endpoints Microsoft.AzureCosmosDB
Test your Web App one last time
Fire one more GET request from Postman, and this time we receive a null
value back, which is the default value we defined in the action of our API. This means our Web App can now connect to CosmosDB and query for data.
Delete the Resource Group
To save money, we now delete the Resource Group we created for this tutorial. This action will delete all the resources we created, so no more costs will incur. Run the following command:
az group delete \
--name dotNetCosmosDbVnetPrivateEndpoint \
--no-wait \
--yes -y
Conclusion
n this tutorial, we saw how important it is to hide your Azure resources behind a Virtual Network. By using the Azure Service Endpoint feature, we can secure various resources by taking them off the internet and making them available only within the defined Virtual Network via a private IP. We also learned everything using CLI commands, so we can reuse and even translate the commands into a .bicep
file for creating infrastructure as code.