Skip to main content

Azure Key Vault module

Imports the certificate + key into an Azure Key Vault as a Key Vault certificate. Every renewal becomes a new version under the same certificate name, so Azure services that reference the certificate by its unversioned identifier (App Service, Application Gateway, API Management, AKS Secrets Store CSI) pick up the renewal on their own.

Overview

  • Type: azurekeyvault, credential type: azure_service_principal (JSON {"tenant_id":"...","client_id":"...","client_secret":"..."}), slot default.
  • Per target: acquire an AAD token (client credentials) → GET the latest version of the KV certificate → compare the leaf's SHA-256 (over the DER) locally → identical and enabled → skip (idempotent); otherwise import an AES-256-encrypted PKCS#12 (leaf + chain + key, under a random per-import password sent in the request pwd field) as a new version under the same name.
  • The import is always PKCS#12 — Azure Key Vault does not accept EC private keys in PEM, and CertAutoPilot defaults to ECDSA P-256.
  • A certificate policy is set only on the first import (secret content type PKCS#12). Renewals do not send a policy, so lifetime actions or exportability you configure on the KV certificate afterwards are preserved.
  • Every imported version is tagged certautopilot:certificate-id so ownership is tracked across renewals.
  • Rollback: supported — a rollback imports a previous retained certificate version into the Key Vault certificate object (as a new KV version under the same name, which unversioned consumer references pick up) (Rollback).
  • Sovereign clouds are supported: the vault URL suffix (.vault.azure.net, .vault.azure.cn, .vault.usgovcloudapi.net) selects the matching AAD authority automatically.

Requirements

  • A service principal with access to the vault. Create one and grant it the RBAC role Key Vault Certificates Officer (or, on access-policy vaults, the certificate permissions Get + Import + List):
# 1. Create the service principal
az ad sp create-for-rbac --name certautopilot-kv

# 2. Grant it Key Vault Certificates Officer on the vault (RBAC vaults)
az role assignment create \
--role "Key Vault Certificates Officer" \
--assignee <appId-from-step-1> \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault-name>

# Access-policy vaults instead:
az keyvault set-policy --name <vault-name> \
--spn <appId-from-step-1> \
--certificate-permissions get import list
  • Key Vault firewall: if the vault restricts network access, allowlist CertAutoPilot's egress IP — a firewalled vault surfaces as AZUREKV_AUTH/AZUREKV_CONNECT even with a correct credential.
  • Outbound HTTPS (443) to both {vault}.vault.azure.net and login.microsoftonline.com (or the sovereign-cloud equivalents).

Target configuration

FieldRequiredDescription
vault_urlyesThe vault URI, e.g. https://myvault.vault.azure.net. Sovereign clouds (.vault.azure.cn, .vault.usgovcloudapi.net) are supported. No port or path.
cert_namenoKV certificate name ([0-9a-zA-Z-]{1,127}). Empty → auto-generated cap-<token>-<slug>, unique per certificate.
tagsnoExtra tags on the imported certificate (max 10). The certautopilot: prefix is reserved for the ownership tag.
http_timeoutnoPer-request timeout in seconds. 0 = default (30); values below 5 are raised to 5; max 120.
{
"vault_url": "https://myvault.vault.azure.net",
"cert_name": "",
"tags": { "env": "prod" }
}

Identity and naming

The deployment identity is the KV certificate name:

  • Empty cert_name (recommended) → an auto-generated, per-certificate name cap-<token>-<slug> — stable across renewals and unique per certificate, so one vault target serves many CertAutoPilot certificates without clobbering. Each certificate is tracked independently in per-cert remote state.
  • Explicit cert_name (target field or a per-distribution override) → the module adopts the existing KV certificate of that name and imports new versions under it.
  • If the name is already owned by a different CertAutoPilot certificate (its certautopilot:certificate-id ownership tag doesn't match), the run logs a warning but proceeds — deliberate adoption of a name is legitimate; the tag is then rewritten to the new owner.

Renewals never create a second KV certificate: they add a version under the same name, which is exactly what unversioned Azure consumer references (e.g. an Application Gateway KV reference, polled roughly every 4 hours) expect.

Rollback

Supported via previous-version re-deploy: a rollback imports a previous retained certificate version into the same KV certificate name — the module's normal deploy path, fed older material. Eligibility, the version picker, and auto-rollback: Rollback.

Key Vault's own versioning shapes what a rollback actually achieves here:

  • The rolled-back-to material lands as a new KV version, which becomes the current version of the certificate. The version that was current before the rollback is neither deleted nor disabled — Key Vault keeps it, and it remains directly addressable by its version id.
  • Consumers that reference the certificate by an unversioned URI (App Service, Application Gateway, APIM, the AKS CSI driver) pick the rolled-back material up on their own refresh schedule — Application Gateway polls roughly every 4 hours, for example. The rollback job reports success as soon as the import lands; the edge moves later.
  • Consumers pinned to a versioned URI never move at all. Repoint them manually — CertAutoPilot has no way to rewrite a pinned reference.

Plan rollback windows around the slowest consumer refresh, not around the job's completion time.

Soft delete and purge protection

Azure Key Vault keeps deleted certificates in a soft-deleted state (typically 90 days). Importing over a soft-deleted name fails with a 409 ("...deleted but recoverable state... can only be recovered or purged"), surfaced as AZUREKV_CONFLICT. CertAutoPilot does not auto-recover. Either:

az keyvault certificate recover --vault-name <vault-name> --name <cert-name>
# or, if you truly want the name freed (blocked when purge protection is on):
az keyvault certificate purge --vault-name <vault-name> --name <cert-name>

…or pick a different cert_name (or leave it empty for the auto name).

Credential

Create an Azure Service Principal credential (Settings → Distribution → Credentials):

{
"tenant_id": "00000000-0000-0000-0000-000000000000",
"client_id": "00000000-0000-0000-0000-000000000000",
"client_secret": "your-client-secret"
}

The principal needs Key Vault Certificates Officer (RBAC) or certificate permissions Get + Import + List (access policy) on the vault — see Requirements.

Verification

The health check acquires an AAD token and lists one certificate page (GET /certificates?maxresults=1) — proving reachability, credential validity, and List permission. To verify a deployment:

az keyvault certificate show --vault-name <vault-name> --name <cert-name> \
--query "{thumbprint:x509ThumbprintHex, expires:attributes.expires, tags:tags}"

The expiry should match the renewed certificate, and tags should carry certautopilot:certificate-id.

Error codes

CodeMeaningResolution
AZUREKV_CONNECTVault or AAD endpoint unreachable (also raised on an unexpected redirect)Check outbound egress and the vault_url.
AZUREKV_AUTHToken rejected or missing permissionFix tenant/client/secret; grant Key Vault Certificates Officer (or access-policy Get+Import+List); allowlist CertAutoPilot's egress IP in the Key Vault firewall.
AZUREKV_NOT_FOUNDVault or certificate not foundVerify the vault URL and certificate name.
AZUREKV_CONFLICTA soft-deleted certificate holds the name, or a certificate operation is pendingaz keyvault certificate recover (or purge — blocked by purge protection), or pick a different cert_name.
AZUREKV_VALIDATIONBad vault_url/cert_name, or Azure rejected the PFXCheck the field formats and the certificate material.
AZUREKV_QUOTAKey Vault throttling (429)Retried automatically with backoff.