Cosmos DB Security: RBAC, Keys, and Network Isolation
Cosmos DB has two authentication paths — account keys and Entra ID data plane RBAC — and two network access paths — public endpoint and private endpoint. Securing Cosmos DB means choosing the right authentication method, applying least-privilege role assignments, and isolating network access to prevent unauthorized connections.
Account keys vs data plane RBAC
Every Cosmos DB account has two primary keys and two secondary keys. These keys are shared secrets — any application with a key can read and write everything in the account. Keys do not support per-user audit trails, and rotating them requires updating every connection string that uses them.
Data plane RBAC assigns specific roles to Entra ID identities at the account, database, or container scope. It supports managed identities, service principals, users, and groups. Entra ID issues short-lived tokens; there are no secrets to rotate.
The built-in data plane roles are:
- Cosmos DB Built-in Data Reader — read access to all items and containers. Role ID:
00000000-0000-0000-0000-000000000001. - Cosmos DB Built-in Data Contributor — read and write access to all items and containers. Role ID:
00000000-0000-0000-0000-000000000002.
You can also create custom role definitions that restrict access to specific operations or specific container paths.
Assigning data plane RBAC roles
Data plane RBAC for Cosmos DB uses its own role assignment system, separate from Azure RBAC. You assign roles using the az cosmosdb sql role assignment create command, not az role assignment create.
# Get the object ID of a managed identity or service principal
PRINCIPAL_ID=$(az identity show \
--resource-group myResourceGroup \
--name myAppIdentity \
--query principalId -o tsv)
# Get the Cosmos DB account resource ID
COSMOS_ID=$(az cosmosdb show \
--resource-group myResourceGroup \
--name mycosmosaccount \
--query id -o tsv)
# Assign the built-in Data Reader role at the account scope
az cosmosdb sql role assignment create \
--resource-group myResourceGroup \
--account-name mycosmosaccount \
--role-definition-id 00000000-0000-0000-0000-000000000001 \
--principal-id $PRINCIPAL_ID \
--scope $COSMOS_ID
# Assign Data Contributor role scoped to a specific database
az cosmosdb sql role assignment create \
--resource-group myResourceGroup \
--account-name mycosmosaccount \
--role-definition-id 00000000-0000-0000-0000-000000000002 \
--principal-id $PRINCIPAL_ID \
--scope "${COSMOS_ID}/dbs/myDatabase"Scope the role as narrowly as possible. If an application only needs to read from one container, scope the assignment to that container rather than the whole account:
# Scope to a specific container
az cosmosdb sql role assignment create \
--resource-group myResourceGroup \
--account-name mycosmosaccount \
--role-definition-id 00000000-0000-0000-0000-000000000001 \
--principal-id $PRINCIPAL_ID \
--scope "${COSMOS_ID}/dbs/myDatabase/colls/orders"Disabling key-based authentication
Once all applications are migrated to Entra ID authentication, disable the local key auth on the account to enforce that no key-based access is possible:
# Disable account keys (requires all apps to use Entra ID first)
az cosmosdb update \
--resource-group myResourceGroup \
--name mycosmosaccount \
--disable-local-auth true
# Verify the setting
az cosmosdb show \
--resource-group myResourceGroup \
--name mycosmosaccount \
--query disableLocalAuthDisabling local auth is immediate and affects all connections using account keys, including the Data Explorer in the Azure Portal. The portal uses key-based access by default — you will lose portal Data Explorer access unless you have an Entra ID role assignment for your user principal. Assign your own identity the Data Reader or Data Contributor role before disabling keys.
To allow specific keys for a defined rotation period while moving to RBAC, use the key rotation workflow:
# List current keys
az cosmosdb keys list \
--resource-group myResourceGroup \
--name mycosmosaccount \
--type keys
# Regenerate the secondary key (update apps using secondary first)
az cosmosdb keys regenerate \
--resource-group myResourceGroup \
--name mycosmosaccount \
--key-kind secondary
# Then switch apps to the new secondary and regenerate primary
az cosmosdb keys regenerate \
--resource-group myResourceGroup \
--name mycosmosaccount \
--key-kind primaryPrivate endpoints for Cosmos DB
A private endpoint creates a private IP address inside your VNet that routes to the Cosmos DB service through the Azure backbone — bypassing the public internet entirely. Applications in the VNet connect to Cosmos DB on the private IP, and DNS is configured to resolve the Cosmos DB hostname to that private IP.
# Create a private endpoint for Cosmos DB
COSMOS_ID=$(az cosmosdb show \
--resource-group myResourceGroup \
--name mycosmosaccount \
--query id -o tsv)
az network private-endpoint create \
--resource-group myResourceGroup \
--name myCosmosPrivateEndpoint \
--vnet-name myVNet \
--subnet mySubnet \
--private-connection-resource-id $COSMOS_ID \
--group-id Sql \
--connection-name myCosmosConnection
# Create a private DNS zone for Cosmos DB
az network private-dns zone create \
--resource-group myResourceGroup \
--name privatelink.documents.azure.com
# Link the DNS zone to the VNet
az network private-dns link vnet create \
--resource-group myResourceGroup \
--zone-name privatelink.documents.azure.com \
--name myDnsLink \
--virtual-network myVNet \
--registration-enabled false
# Create DNS records for the private endpoint
az network private-endpoint dns-zone-group create \
--resource-group myResourceGroup \
--endpoint-name myCosmosPrivateEndpoint \
--name myZoneGroup \
--private-dns-zone privatelink.documents.azure.com \
--zone-name documentsAfter creating the private endpoint, disable public network access to complete the isolation:
az cosmosdb update \
--resource-group myResourceGroup \
--name mycosmosaccount \
--public-network-access DisabledIP firewall rules
For scenarios where a private endpoint is not practical — for example, a developer’s local machine or a third-party service — you can restrict public access to specific IP ranges. IP firewall rules allow only listed IP addresses to reach the public endpoint.
# Allow a specific IP (e.g., your office NAT IP)
az cosmosdb update \
--resource-group myResourceGroup \
--name mycosmosaccount \
--ip-range-filter "203.0.113.0/24,198.51.100.42"
# Allow Azure services (Portal, Azure Functions, etc.)
# Add 0.0.0.0 to allow all Azure datacenter IPs
az cosmosdb update \
--resource-group myResourceGroup \
--name mycosmosaccount \
--ip-range-filter "203.0.113.0/24,0.0.0.0"The 0.0.0.0 special range allows all Azure services’ outbound IPs, which is required for Azure Functions, App Service, and other managed services that use dynamic outbound IPs. This is not “allow all internet” — it restricts access to Azure datacenter IP ranges only.
Managed Identity access from AKS and App Service
For workloads running on AKS or App Service, use managed identities to authenticate to Cosmos DB without storing credentials. The pod or app gets an Entra ID token automatically from the Azure IMDS endpoint.
# For App Service: get the system-assigned managed identity principal ID
APP_IDENTITY=$(az webapp identity show \
--resource-group myResourceGroup \
--name myWebApp \
--query principalId -o tsv)
# Assign Data Contributor role
az cosmosdb sql role assignment create \
--resource-group myResourceGroup \
--account-name mycosmosaccount \
--role-definition-id 00000000-0000-0000-0000-000000000002 \
--principal-id $APP_IDENTITY \
--scope $COSMOS_IDIn your application code, use the DefaultAzureCredential which automatically uses the managed identity when running on Azure:
// .NET SDK with managed identity
var credential = new DefaultAzureCredential();
var client = new CosmosClient(
"https://mycosmosaccount.documents.azure.com:443/",
credential
);Auditing with Diagnostic Settings
Enable Diagnostic Settings to ship Cosmos DB control plane and data plane audit logs to a Log Analytics workspace or Storage Account. This captures key usage, RBAC changes, and optional query logs.
WORKSPACE_ID=$(az monitor log-analytics workspace show \
--resource-group myResourceGroup \
--workspace-name myLogWorkspace \
--query id -o tsv)
az monitor diagnostic-settings create \
--resource $COSMOS_ID \
--resource-group myResourceGroup \
--name cosmosdb-audit \
--workspace $WORKSPACE_ID \
--logs '[
{"category": "DataPlaneRequests", "enabled": true},
{"category": "ControlPlaneRequests", "enabled": true},
{"category": "QueryRuntimeStatistics", "enabled": false}
]' \
--metrics '[
{"category": "Requests", "enabled": true}
]'The QueryRuntimeStatistics category logs every query executed, including the query text. This is useful for performance analysis but generates very high log volume and may capture sensitive data in query predicates. Enable it selectively and for limited time periods during investigation rather than permanently.
Common mistakes
- Storing the account primary key in application settings or environment variables. The primary key is equivalent to a root credential. If it is exposed through a leaked config file, a misconfigured environment variable, or a compromised deployment pipeline, the attacker has full access to all data. Use managed identities with data plane RBAC instead — the identity token is never stored as a secret and cannot be extracted from the application.
- Creating a private endpoint but leaving public network access enabled. A private endpoint adds a private network path but does not remove the public one. If you intend private-only access, you must explicitly set
publicNetworkAccesstoDisabled. Many teams create private endpoints and assume the public endpoint is disabled automatically — check this explicitly after setup. - Assigning the Data Contributor role at the account scope when only one container needs write access. A Contributor role at the account scope gives write access to every database and container in the account, including ones that should be read-only. Scope RBAC assignments to the specific database or container the application actually needs to write to, and use Data Reader for everything else.
Summary
- Prefer data plane RBAC with managed identities over account keys — RBAC supports least privilege, requires no secret rotation, and integrates with Entra ID audit logging.
- Assign roles at the narrowest possible scope (container > database > account) and use the built-in Data Reader role for read-only workloads.
- Private endpoints combined with disabling public network access provide the strongest network isolation; IP firewall rules are a lighter alternative for scenarios where private endpoints are not practical.
- Enable Diagnostic Settings to capture data plane and control plane audit logs; avoid permanent QueryRuntimeStatistics logging due to high volume and potential data sensitivity.
Frequently asked questions
What is the difference between the Cosmos DB account key and data plane RBAC?
The account key is a shared secret that grants full access to all databases, containers, and items in the account — equivalent to a root credential. Data plane RBAC uses Microsoft Entra ID identities (users, groups, service principals, managed identities) with scoped role assignments that grant only the specific operations needed. RBAC is more secure because it supports least privilege, requires no secret rotation, and integrates with Microsoft Entra audit logs.
Can I disable account keys entirely?
Yes. Set the disableLocalAuth property to true on the Cosmos DB account. This forces all data plane access through Entra ID authentication. Existing key-based connections will stop working immediately, so ensure all applications are migrated to managed identities or service principal authentication before disabling keys.
Does a private endpoint for Cosmos DB prevent all public access?
Not by default. Creating a private endpoint does not automatically disable the public endpoint. You must also set publicNetworkAccess to Disabled on the account. Until you do that, the account is accessible both via the private endpoint (from your VNet) and via the public internet.