Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/SKILLS.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ why it matters → incorrect example → correct example → references.
| [`full-text-search/`](../skills/full-text-search/) | `fts-` | `createSearchIndexes` + `$search` for BM25 keyword / phrase / fuzzy; custom analyzers (keyword + edgeGram) for prefix matching on IDs; `pathHierarchy` for hierarchical identifiers; multi-field search indexes; hybrid (BM25 + vector) with RRF |
| [`high-availability/`](../skills/high-availability/) | `ha-` | Enabling HA + zone redundancy, cross-region replica, automatic backup retention, documented SLAs |
| [`storage/`](../skills/storage/) | `storage-` | Premium SSD v2 high-performance storage: compute-tier-gated IOPS/bandwidth caps, v1 vs v2 selection, limitations (no CMK, migration paths), disk-hydration sequencing |
| [`security/`](../skills/security/) | `security-` | TLS, Private Endpoint, Microsoft Entra RBAC, CMK |
| [`security/`](../skills/security/) | `security-` | TLS, Private Endpoint, IP firewall rules (CIDR + propagation), Azure RBAC actions for `mongoClusters/*`, Microsoft Entra ID + OIDC authentication, MongoDB database roles for data-plane access (incl. `readWriteAnyDatabase`+`clusterAdmin` pairing), token-lifetime / revocation pattern, CMK |
| [`monitoring/`](../skills/monitoring/) | `monitoring-` | Slow query logs, metrics & alerts |
| [`local-deployment/`](../skills/local-deployment/) | `local-` | Docker image choice, Compose, TLS, env-driven config, dev/prod parity |

Expand Down
39 changes: 36 additions & 3 deletions skills/security/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,49 @@
---
name: documentdb-security
description: Security best practices for Azure DocumentDB — TLS enforcement, Private Endpoint / firewall configuration, Microsoft Entra ID + RBAC for authentication, and customer-managed keys (CMK) for encryption at rest. Use when reviewing production security posture, configuring networking, setting up authentication / authorization, or preparing for compliance audits.
description: Security best practices for Azure DocumentDB — TLS enforcement, Private Endpoint / firewall configuration, two-level access control (Azure RBAC on the `mongoClusters` resource + Microsoft Entra ID OIDC authentication with MongoDB database roles for data-plane access), token-lifetime / revocation handling, and customer-managed keys (CMK) for encryption at rest. Use when reviewing production security posture, configuring networking, setting up authentication / authorization, granting per-app least-privilege access, revoking compromised tokens, or preparing for compliance audits.
license: MIT
---

# Security — Azure DocumentDB

Core controls: TLS on the wire, network isolation with Private Endpoint, Microsoft Entra ID for identity, and CMK for data-at-rest encryption on regulated workloads.
Core controls: TLS on the wire, network isolation with Private Endpoint, **two-level access control** (Azure RBAC for the cluster resource + Entra ID + MongoDB database roles for data), and CMK for data-at-rest encryption on regulated workloads.

## Defense-in-depth checklist

A production cluster should have all eight layers in place:

| Layer | Default | Production recommendation | Rule |
|---|---|---|---|
| **Network** | Public access + firewall rules | Private Endpoint; public access disabled; firewall ≠ `0.0.0.0/0` | [`security-private-endpoint`](security-private-endpoint.md), [`security-firewall-rules`](security-firewall-rules.md) |
| **Transport** | TLS up to 1.3 (always on) | TLS verified at client; `tlsAllowInvalidCertificates` never set | [`security-tls-required`](security-tls-required.md) |
| **Identity** | One built-in native admin | Entra ID enabled; managed identities per workload; admin password strong + rotated | [`security-entra-rbac`](security-entra-rbac.md), [`security-admin-password-and-identity-separation`](security-admin-password-and-identity-separation.md) |
| **Control-plane authorization** | Subscription-level Azure RBAC | Custom role scoped to `Microsoft.DocumentDB/mongoClusters/*` at resource-group scope | [`security-azure-rbac-actions`](security-azure-rbac-actions.md) |
| **Data-plane authorization** | One admin user | Per-database least-privilege roles; admin identity ≠ runtime identity | [`security-database-roles`](security-database-roles.md), [`security-admin-password-and-identity-separation`](security-admin-password-and-identity-separation.md) |
| **Encryption at rest** | Service-managed AES-256 | CMK for regulated workloads (Premium SSD v1 only — see `storage/`) | [`security-cmk-encryption`](security-cmk-encryption.md) |
| **Backups** | Automated, 35-day retention | Restore drills; understand 7-day post-deletion window | [Reliability in Azure DocumentDB](https://learn.microsoft.com/azure/reliability/reliability-documentdb) |
| **Incident response** | Audit + activity logs available | Token revocation playbook ready; monitoring alerts wired up | [`security-token-lifetime-revocation`](security-token-lifetime-revocation.md), [`monitoring/`](../monitoring/) |

## Two-level access model

Azure DocumentDB separates **who can manage the cluster as an Azure resource** from **who can read/write data inside it**:

| Layer | What it controls | Granted via |
|---|---|---|
| **Azure RBAC** (control-plane) | Read cluster metadata, list connection strings, manage firewall rules, manage private endpoints, register/remove Entra users | Role assignments on `Microsoft.DocumentDB/mongoClusters/*` actions |
| **Database roles** (data-plane) | Read/write documents, run queries, create collections | MongoDB roles (`readWriteAnyDatabase`, `clusterAdmin`, `readAnyDatabase`, `root`) mapped to a registered Entra principal or native user |

A principal needs both layers for end-to-end access, and they are managed independently. **Use different principals for the two layers** wherever practical — see [`security-admin-password-and-identity-separation`](security-admin-password-and-identity-separation.md).

## Rules

- [security-tls-required](security-tls-required.md) — Always connect with TLS; never disable certificate validation in production.
- [security-private-endpoint](security-private-endpoint.md) — Use Private Endpoint / firewall rules; disable public network access where possible.
- [security-entra-rbac](security-entra-rbac.md) — Prefer Microsoft Entra ID + RBAC over long-lived passwords; create per-app secondary users with least privilege.
- [security-firewall-rules](security-firewall-rules.md) — IP firewall rules in CIDR form; "Allow Azure services" toggle; ~15-minute propagation delay; avoid the `0.0.0.0-255.255.255.255` shortcut.
- [security-entra-rbac](security-entra-rbac.md) — Enable Microsoft Entra ID authentication, register principals as `mongoClusters/users`, connect with `MONGODB-OIDC`; prefer managed identities over passwords.
- [security-azure-rbac-actions](security-azure-rbac-actions.md) — Azure resource-level RBAC: actions exposed by `Microsoft.DocumentDB/mongoClusters/*`, custom-role pattern, control-plane least-privilege.
- [security-database-roles](security-database-roles.md) — MongoDB database roles for data-plane access: `readWriteAnyDatabase` + `clusterAdmin` must be granted together for read-write; `readAnyDatabase` for read-only; secondary-user management via mongo shell.
- [security-admin-password-and-identity-separation](security-admin-password-and-identity-separation.md) — Strong admin password policy (≥8 chars + complexity); use distinct Azure identities for control-plane vs data-plane to bound blast radius.
- [security-token-lifetime-revocation](security-token-lifetime-revocation.md) — Entra access tokens are valid up to ~90 minutes from issuance even after the principal is disabled; revoke data-plane access immediately by deleting the `mongoClusters/users/<principal-id>` resource.
- [security-cmk-encryption](security-cmk-encryption.md) — Use customer-managed keys (CMK) for data-at-rest encryption on regulated workloads.
- [security-cmk-troubleshooting](security-cmk-troubleshooting.md) — CMK operational runbook: causes of `Inaccessible` cluster state, ~60-minute revalidation window, managed-identity / key / vault recovery procedures, and provisioning-failure triage.

166 changes: 166 additions & 0 deletions skills/security/security-admin-password-and-identity-separation.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
# security-admin-password-and-identity-separation

**Category:** Security · **Priority:** MEDIUM

## Why it matters

Two related habits move an Azure DocumentDB cluster from "default-secure" to "production-hardened":

1. The cluster's built-in administrative account uses a **password**. That password is the fallback path that bypasses Entra ID, so its strength and rotation hygiene matter — even when most workloads use managed identities.
2. The Azure identity that **manages** the cluster (creates, scales, deletes, lists connection strings) and the identity that **uses** the cluster's data (reads, writes, queries) should be **different principals**. Sharing one identity across both planes is a classic privilege-escalation path — a data-plane bug that yields code execution now also yields cluster-management rights.

This rule captures both habits because the Learn security overview groups them as identity-management best practices.

## Admin password policy

Azure DocumentDB enforces a minimum password policy on administrative accounts: **at least 8 characters, with all four of upper-case, lower-case, digits, and non-alphanumeric characters.** Treat the floor as the floor, not the target — generate longer passwords from a password manager and store them in Key Vault.

## Identity separation: control plane vs data plane

Recall the two-level access model (see `SKILL.md`):

- **Control plane** — Azure RBAC on `Microsoft.DocumentDB/mongoClusters/*` (resize, firewall, list connection strings, register users).
- **Data plane** — Entra ID + MongoDB database roles (read/write documents).

Use **distinct Azure identities** for these two layers wherever practical. The principle is the same as separating "deploy" identities from "runtime" identities elsewhere in Azure: a single compromised identity should not be able to both modify infrastructure and access data.

## Incorrect

Weak admin password:

```bicep
// Fails policy if too short, but even meeting the minimum (`Pa55!ab`) is too weak.
administrator: {
userName: 'clusteradmin'
password: 'Pa55word!' // ← 9 chars, dictionary-derived, easily guessed
}
```

Hard-coding admin credentials in a connection string for runtime use:

```javascript
// Anti-pattern — the admin account is now exposed to every host that runs this code.
const uri = `mongodb+srv://clusteradmin:${PROD_ADMIN_PASSWORD}@<cluster>.global.mongocluster.cosmos.azure.com/?tls=true`;
```

Using the **same** managed identity for IaC (control plane) and for the application (data plane):

```bicep
// Anti-pattern — the app's managed identity has both:
// 1. Contributor on the cluster (control plane)
// 2. mongoClusters/users registration as readWrite (data plane)
// A code-execution bug in the app can now resize, delete, or exfiltrate keys.
resource appIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
name: 'app-identity'
}

resource controlPlaneRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
// …
properties: {
roleDefinitionId: contributorRoleId
principalId: appIdentity.properties.principalId // same identity
}
}

resource dataPlaneUser 'Microsoft.DocumentDB/mongoClusters/users@2025-09-01' = {
// …
properties: {
identityProvider: { /* … */ }
roles: [ { db: 'orders', role: 'readWrite' } ] // same identity
}
}
```

## Correct

### Generate strong admin passwords from Key Vault

Sample workflow:

```bash
# Generate a 32-char password and store in Key Vault.
NEW_PWD=$(openssl rand -base64 24 | tr -d '/+=' | head -c 32)
az keyvault secret set \
--vault-name "<kv>" \
--name "docdb-admin-password" \
--value "$NEW_PWD"
```

Reference it from Bicep instead of inlining a literal:

```bicep
@secure()
param adminPassword string // sourced from Key Vault at deploy time

resource cluster 'Microsoft.DocumentDB/mongoClusters@2025-09-01' = {
name: clusterName
location: location
properties: {
administrator: {
userName: 'clusteradmin'
password: adminPassword
}
// …
}
}
```

```bash
az deployment group create \
--resource-group "<rg>" \
--template-file cluster.bicep \
--parameters adminPassword="$(az keyvault secret show --vault-name <kv> --name docdb-admin-password --query value -o tsv)"
```

Rotate the admin password on a schedule (e.g. quarterly) and after any incident. Use managed identities for everyday workload access so admin-password rotation is not on the critical path.

### Use two separate identities

Pattern: one identity for IaC / SRE, a different identity for each workload that consumes data.

```bicep
// Control-plane identity — used by your deploy pipeline.
resource sreIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
name: 'sre-deploy-identity'
}

resource controlPlaneRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(resourceGroup().id, sreIdentity.id, 'control-plane')
scope: resourceGroup()
properties: {
roleDefinitionId: docdbRbacOwnerRoleId // see security-azure-rbac-actions
principalId: sreIdentity.properties.principalId
}
}

// Data-plane identity — used by the application at runtime.
resource appIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2023-01-31' existing = {
name: 'orders-api-identity'
}

resource dataPlaneUser 'Microsoft.DocumentDB/mongoClusters/users@2025-09-01' = {
name: '${clusterName}/users/${appIdentity.properties.principalId}'
properties: {
identityProvider: {
type: 'Microsoft.EntraID'
properties: { principalType: 'ManagedIdentity' }
}
roles: [
{ db: 'orders', role: 'readWrite' }
]
}
}
```

The SRE identity can resize / configure / register users but has **no** database role and cannot read data. The app identity can read and write `orders` but has **no** Azure RBAC role and cannot scale, list connection strings, or delete the cluster. A compromise of either is bounded.

### Why this matters in practice

- A leaked CI/CD identity that holds Contributor on the cluster but no database role can still cause damage (delete the cluster, change firewall, list connection strings, register a malicious user) — but it cannot directly exfiltrate data, buying the responder time.
- A leaked app identity that holds `readWrite` on one database cannot resize, delete, or reconfigure the cluster — the blast radius is the database it owns.

## References

- [Secure your cluster — Azure DocumentDB](https://learn.microsoft.com/azure/documentdb/security)
- [Create secondary users](https://learn.microsoft.com/azure/documentdb/secondary-users)
- Related: [security-entra-rbac](security-entra-rbac.md), [security-azure-rbac-actions](security-azure-rbac-actions.md), [security-database-roles](security-database-roles.md)
Loading