Skip to main content
emnode
Compliance Low severity MCSB DP-5

Microsoft Defender for Cloud · Azure Database for PostgreSQL

(Enable if required) PostgreSQL servers should use customer-managed keys to encrypt data at rest

Written and reviewed by Emnode · Last reviewed

What does the recommendation "(Enable if required) PostgreSQL servers should use customer-managed keys to encrypt data at rest" check?

Audits each Azure Database for PostgreSQL Single Server (the 'Microsoft.DBforPostgreSQL/servers' resource type) to confirm data at rest is encrypted with a customer-managed key (CMK) you hold in Azure Key Vault, rather than the default service-managed key. The policy uses an AuditIfNotExists effect, so it reports non-compliant servers without blocking or changing them. It does not provision a key, configure the Key Vault access policy or assign the server identity for you, and it does not check Flexible Server, which is a separate resource type and a separate recommendation. It also does not verify your key rotation cadence, soft-delete or purge-protection settings on the vault: it only confirms that a customer-managed key is bound to the server.

Why does "(Enable if required) PostgreSQL servers should use customer-managed keys to encrypt data at rest" matter?

By default PostgreSQL data at rest is already encrypted with a Microsoft-managed key, so the cryptography is sound either way. The gap a customer-managed key closes is control and separation of duties: with a CMK in your own Key Vault you decide who can use the key, you hold the audit trail for every wrap and unwrap operation, and you can revoke access to render the database unreadable if a server is compromised or decommissioned under your terms. Many regulatory regimes (PCI DSS, HMG and several financial-services frameworks) require this key custody before they accept a managed database for sensitive workloads. The trade-off is operational risk you now own: if the server loses access to the key, it begins refusing all connections within roughly ten minutes, so a deleted key or a revoked vault permission becomes a self-inflicted outage. That is why the severity is Low and the recommendation is gated as 'enable if required'.

How do I fix "(Enable if required) PostgreSQL servers should use customer-managed keys to encrypt data at rest"?

  1. Give the server an identity and let it reach the key: run 'az postgres server update -g <rg> -n <server> --assign-identity' to create the system-assigned managed identity, then grant that principal access to the vault with 'az keyvault set-policy -n <vault> -g <rg> --object-id <server-principal-id> --key-permissions get wrapKey unwrapKey'. The vault must already have soft delete and purge protection enabled.
  2. Bind the key to the server: 'az postgres server key create -g <rg> -s <server> --kid https://<vault>.vault.azure.net/keys/<keyName>/<keyVersion>'. Use a key that has no expiry and is not disabled, and keep the vault and the server in the same region.
  3. Assign the audit policy so drift is caught: look the definition up by name rather than hardcoding a GUID, e.g. pid=$(az policy definition list --query "[?displayName=='PostgreSQL servers should use customer-managed keys to encrypt data at rest'].name" -o tsv) then 'az policy assignment create --name pg-cmk-audit --policy "$pid" --scope <subscription-or-rg-scope>'. Set new servers up the same way in Bicep or Terraform so the key binding is part of the deployment, not a manual afterthought.

Remediation script · bash

# 1. Lock the wire and the network on a production PostgreSQL flexible server.
RG=db-prod-rg
SRV=ordersprod-pg

# Enforce encrypted connections (dynamic, no restart needed).
az postgres flexible-server parameter set \
  --resource-group "$RG" --server-name "$SRV" \
  --source user-override \
  --name require_secure_transport --value ON

# Pin the minimum TLS version.
az postgres flexible-server parameter set \
  --resource-group "$RG" --server-name "$SRV" \
  --source user-override \
  --name ssl_min_protocol_version --value TLSv1.2

# Disable public network access.
az postgres flexible-server update \
  --resource-group "$RG" --name "$SRV" \
  --public-access Disabled

# 2. Turn on the pgaudit trail and connection throttling.
for p in "pgaudit.log=role,ddl,misc" \
         "pgaudit.log_client=ON" \
         "pgaudit.log_level=log" \
         "pgaudit.log_statement=on" \
         "pgaudit.log_statement_once=on" \
         "connection_throttle.enable=on"; do
  az postgres flexible-server parameter set \
    --resource-group "$RG" --server-name "$SRV" \
    --source user-override \
    --name "${p%%=*}" --value "${p#*=}"
done

# 3. Provision a Microsoft Entra administrator and disable password sign-in.
az postgres flexible-server ad-admin create \
  --resource-group "$RG" --server-name "$SRV" \
  --display-name "dba-team" \
  --object-id "$(az ad group show --group dba-team --query id -o tsv)" \
  --type Group
az postgres flexible-server update \
  --resource-group "$RG" --name "$SRV" \
  --microsoft-entra-auth Enabled --password-auth Disabled

# 4. Ratchet it shut: deny any future PostgreSQL flexible server with public access.
# Look the built-in policy up by its EXACT display name, never paste a guessed GUID.
PID=$(az policy definition list \
  --query "[?displayName=='Public network access should be disabled for PostgreSQL flexible servers'].name" \
  -o tsv)
az policy assignment create \
  --name deny-public-pg-flex \
  --policy "$PID" \
  --params '{"effect":{"value":"Deny"}}' \
  --scope "/subscriptions/00000000-0000-0000-0000-000000000000"

Full walkthrough (console steps, edge cases and verification) in the lesson Harden Azure MySQL and PostgreSQL.

Is "(Enable if required) PostgreSQL servers should use customer-managed keys to encrypt data at rest" a false positive?

A non-production or transient server holding only synthetic or already-public data, where you have made a deliberate decision to stay on the Microsoft-managed key to avoid the key-availability outage risk, will flag as non-compliant even though that is the correct choice for that workload. Because the policy is audit-only and customer-managed keys are explicitly optional here, record the exception (a policy exemption scoped to that server, or a tag your reporting recognises) rather than enabling a CMK you do not actually need.