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:

The following drawing presents in a simple manner the resources architecture:

The architecture of a scenario using a service endpoint

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:

CosmosDB authentication error

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:

CosmosDB authentication error

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.

comments powered by Disqus