Back to blog
AzureBest PracticesCloud SecurityIdentity & AccessOperations & Compliance

Key Vault Key Has No Expiration: Why It Matters and How to Fix It

Azure Key Vault keys without an expiration date stay valid forever. Learn the risks, how to set expiry with CLI and Terraform, and how to enforce it in CI/CD.

TL;DR

This check flags Azure Key Vault keys that have no expiration date set, which means a compromised or stale key can stay valid forever. Set an expiry with az keyvault key set-attributes --expires and enforce rotation going forward.

Cryptographic keys are not the kind of thing you want to leave running indefinitely. A key that never expires is a key that nobody is forced to review, rotate, or retire. The Key Vault Key Has No Expiration check looks at the keys stored in your Azure Key Vaults and raises a finding whenever one of them lacks an expiration date.

It is a small configuration gap that tends to slip through because keys usually work fine without an expiry. They keep encrypting, decrypting, signing, and wrapping until something goes wrong. The problem is that "until something goes wrong" can mean years of exposure.


What this check detects

Azure Key Vault keys carry a set of attributes that control their lifecycle. One of them is expires (often shown as exp), a Unix timestamp marking when the key stops being valid for operations. When this attribute is unset, the key is valid forever.

This Lensix check (keyvault_keyexpiry, in the keyvault_checks module) inspects each key object in every Key Vault across your Azure subscriptions and flags any key where the expiration date is missing.

Note: Setting an expiration date does not rotate a key on its own. It marks the key as invalid after a point in time, which forces a deliberate action: rotate, renew, or retire. Azure Key Vault does support automated rotation policies, which pair nicely with expiration and are covered later in this post.


Why it matters

Keys protect data at rest, sign tokens, and wrap other secrets. An attacker who gets hold of key material, whether through a leaked access policy, an over-permissioned managed identity, or a compromised application, can keep using a non-expiring key for as long as it exists. There is no built-in deadline that cuts off the access.

Here are the concrete risks:

  • Indefinite blast radius. If a key is compromised and never expires, the window of exposure is open until a human notices and revokes it. With an expiry, the damage is time-boxed.
  • Stale cryptography. Algorithms and key sizes that were fine five years ago may be weak today. A key with no expiry has no natural prompt to move to stronger parameters.
  • Compliance failures. PCI DSS, SOC 2, ISO 27001, and NIST SP 800-57 all expect documented key lifecycles with defined cryptoperiods. A key with no expiration is hard to defend in an audit.
  • Operational drift. Keys accumulate. Without expiration, old keys linger long after the apps that used them are gone, expanding the attack surface for no benefit.

Warning: Expiring a key that is actively in use will break any operation that depends on it after the expiry date. Encryption with that key version stops, and decryption of data wrapped by it can fail if the key is disabled or deleted. Always confirm what consumes a key before setting a near-term expiry, and prefer rotation to a new version over abruptly killing the current one.


How to fix it

You can set an expiration date on an existing key through the Azure CLI, the portal, PowerShell, or infrastructure as code. The first step is always to figure out which keys are affected and what uses them.

Step 1: Find keys without an expiration

List the keys in a vault and inspect their attributes:

# List keys in a vault
az keyvault key list --vault-name my-keyvault \
  --query "[].{name:name, enabled:attributes.enabled, expires:attributes.expires}" \
  --output table

Any row where expires is empty is a candidate finding. To check a single key version in detail:

az keyvault key show --vault-name my-keyvault --name my-key \
  --query "attributes"

Step 2: Set an expiration date

Use set-attributes with an ISO 8601 timestamp. The example below sets the key to expire one year out:

Danger: The command below changes a live cryptographic key. If applications still rely on this key version after the expiry date, their encrypt and sign operations will fail. Validate consumers first and pick an expiry far enough out to allow a clean rotation.

# Set expiry one year from now (Linux/macOS date)
az keyvault key set-attributes \
  --vault-name my-keyvault \
  --name my-key \
  --expires "$(date -u -d '+1 year' '+%Y-%m-%dT%H:%M:%SZ')"

On a system without GNU date, pass an explicit timestamp:

az keyvault key set-attributes \
  --vault-name my-keyvault \
  --name my-key \
  --expires "2026-06-01T00:00:00Z"

Step 3: Verify the change

az keyvault key show --vault-name my-keyvault --name my-key \
  --query "attributes.expires"

Portal steps

  1. Open your Key Vault in the Azure portal.
  2. Under Objects, select Keys and choose the key.
  3. Click the current version.
  4. Set Set expiration date? to Yes and pick a date.
  5. Click Save.

Tip: Instead of slapping an expiry on the current version, create a fresh key version, point your apps at it, then expire the old version. Key Vault keeps prior versions, so consumers that still reference the old version ID keep working until you cut them over.


Fixing it in infrastructure as code

If your vaults are managed with Terraform, set the expiration at creation so new keys are never born without one. The expiration_date argument expects an RFC 3339 timestamp:

resource "azurerm_key_vault_key" "example" {
  name         = "app-encryption-key"
  key_vault_id = azurerm_key_vault.example.id
  key_type     = "RSA"
  key_size     = 2048

  key_opts = [
    "decrypt",
    "encrypt",
    "sign",
    "unwrapKey",
    "verify",
    "wrapKey",
  ]

  expiration_date = "2026-06-01T00:00:00Z"

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

The rotation_policy block does double duty here. It expires the key after 90 days and automatically rotates to a new version 30 days before expiry, so the lifecycle is enforced without manual intervention.

With Bicep, the equivalent looks like this:

resource key 'Microsoft.KeyVault/vaults/keys@2023-07-01' = {
  parent: keyVault
  name: 'app-encryption-key'
  properties: {
    kty: 'RSA'
    keySize: 2048
    attributes: {
      enabled: true
      exp: 1780272000  // Unix timestamp for expiry
    }
  }
}

How to prevent it from happening again

One-off fixes do not hold. The goal is to make a missing expiration impossible to ship in the first place.

Enforce with Azure Policy

Azure has a built-in policy definition, "Key Vault keys should have an expiration date", that audits or denies keys without an expiry. Assign it with deny effect to block creation:

az policy assignment create \
  --name "require-key-expiration" \
  --display-name "Key Vault keys must have an expiration date" \
  --policy "152b15f7-8e1f-4c1f-ab71-8c010ba5dbc0" \
  --params '{"effect": {"value": "Deny"}}' \
  --scope "/subscriptions/"

Warning: A Deny assignment will block legitimate key creation that omits an expiry, including pipelines you may not have updated yet. Roll it out as Audit first, fix the findings, then switch to Deny once your tooling consistently sets expiration dates.

Gate it in CI/CD

For Terraform users, scan plans before they apply. A Checkov run catches keys without expiration through rule CKV_AZURE_40:

checkov -d ./infra --check CKV_AZURE_40

Wire that into your pipeline so a merge request that introduces a non-expiring key fails the build. Pair it with Lensix scanning on the deployed resources to catch anything that bypasses the IaC path, such as keys created manually in the portal or by a script.

Tip: Define a rotation policy at the vault level as a default. Any key created in that vault inherits the policy, so expiration and rotation become the standard rather than something each team has to remember per key.


Best practices

  • Always set an expiration on every key. Treat a missing expiry as a bug, not a default. There is no production scenario where a key should live forever unreviewed.
  • Use rotation policies, not just expiration dates. An expiry without rotation just creates an outage on the expiry date. Automated rotation keeps a valid key in place while retiring the old version.
  • Match the cryptoperiod to the key's sensitivity. NIST SP 800-57 gives guidance on cryptoperiods. High-value keys deserve shorter lifecycles than low-risk ones.
  • Enable expiry notifications. Configure Event Grid events for KeyNearExpiry and KeyExpired so the right team gets alerted before a key lapses.
  • Restrict who can create keys. Tight RBAC on the vault reduces the number of paths that can produce an unmanaged, non-expiring key.
  • Audit regularly. Run continuous checks across all subscriptions. Keys multiply quietly, and a quarterly manual review is not enough at scale.

A key with no expiration is a decision to never revisit that key. In security, decisions like that age badly. Give every key a deadline and a rotation plan, and the audit, the incident response, and the next engineer who inherits the vault all get easier.