Azure Key Vault: Secrets, Keys, and Certificate Rotation Patterns

Key Vault looks trivial: put secret in, read secret out. The design decisions that separate a clean deployment from a mess are about topology, access model, and rotation. Get those three right and Key Vault quietly does its job for years. Get them wrong and you have a single shared vault with two hundred secrets, access policies nobody can audit, and certificates that expire in production on a Saturday.

Topology: Vault per Workload per Environment

One vault per application per environment. The vault becomes a security boundary that matches your RBAC boundary: the payments team touches the payments vaults and nothing else, production access differs from dev, and a compromise blast radius stays small. Shared vaults always drift toward everyone having access to everything.

resource "azurerm_key_vault" "payments_prod" {
  name                          = "kv-payments-prod-weu"
  location                      = "westeurope"
  resource_group_name           = azurerm_resource_group.sec.name
  tenant_id                     = data.azurerm_client_config.current.tenant_id
  sku_name                      = "standard"
  rbac_authorization_enabled    = true
  purge_protection_enabled      = true
  soft_delete_retention_days    = 90
  public_network_access_enabled = false
}

resource "azurerm_role_assignment" "app_read" {
  scope                = azurerm_key_vault.payments_prod.id
  role_definition_name = "Key Vault Secrets User"
  principal_id         = azurerm_user_assigned_identity.payments.principal_id
}

Non negotiables in that block: RBAC authorization instead of legacy access policies, purge protection on (required for CMK scenarios and prevents an attacker permanently destroying secrets), and public access off with a private endpoint in front. Humans get access through PIM eligible roles, never standing assignments, and applications authenticate with managed identities, never client secrets, because using a secret to fetch secrets defeats the point.

Certificate Rotation That Actually Rotates

Key Vault certificates carry a lifetime policy, and for public certs an integrated CA (DigiCert or GlobalSign) lets Key Vault renew automatically. The consumers that matter, App Gateway, App Service, and AKS via the Secrets Store CSI driver, all poll the vault and pick up the new version without redeployment. That combination is the difference between certificate expiry being an incident category and being a non event.

resource "azurerm_key_vault_certificate" "api" {
  name         = "api-contoso-com"
  key_vault_id = azurerm_key_vault.payments_prod.id

  certificate_policy {
    issuer_parameters {
      name = "DigiCert"
    }
    key_properties {
      key_type   = "RSA"
      key_size   = 2048
      exportable = true
      reuse_key  = false
    }
    lifetime_action {
      action {
        action_type = "AutoRenew"
      }
      trigger {
        days_before_expiry = 30
      }
    }
    x509_certificate_properties {
      subject            = "CN=api.contoso.com"
      validity_in_months = 12
      key_usage          = ["digitalSignature", "keyEncipherment"]
    }
  }
}

Key Rotation and Customer Managed Keys

For encryption keys backing storage accounts, disks, and databases, set a rotation policy on the key itself. Azure services using CMK reference the key without a version, so when Key Vault rotates it the service starts using the new version automatically.

resource "azurerm_key_vault_key" "storage_cmk" {
  name         = "cmk-storage"
  key_vault_id = azurerm_key_vault.payments_prod.id
  key_type     = "RSA"
  key_size     = 3072
  key_opts     = ["unwrapKey", "wrapKey"]

  rotation_policy {
    automatic {
      time_before_expiry = "P30D"
    }
    expire_after         = "P90D"
    notify_before_expiry = "P29D"
  }
}

Secrets Are the Weak Link

Certificates and keys rotate natively. Plain secrets do not, because rotation means coordinating with the system that validates the credential. The pattern that works: an Event Grid subscription on the vault fires SecretNearExpiry, a Function generates a new credential at the source (database, API provider), writes it as a new secret version, and applications always read the latest version at startup or on a short cache. Better still, eliminate the secret entirely, and every managed identity you adopt is one less thing to rotate. Finally, enable diagnostic logging to Log Analytics and alert on denied access attempts and purge operations, because a vault that is being probed is telling you something.

Cheers
Osama

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.