01What Is Azure Key Vault?
Azure Key Vault is a cloud-based, managed hardware security module (HSM) service for storing and accessing secrets, cryptographic keys, and certificates. It centralizes application secrets, tightly controls access with Entra ID, logs all access activity, and helps achieve a zero-secret architecture — where no credentials ever appear in code, config files, or environment variables.
02Core Concepts & Terminology
Before diving into Key Vault operations, it helps to understand the foundational terminology that underpins the service. These concepts define how secrets are organized, accessed, versioned, and protected throughout their lifecycle. Mastering this vocabulary ensures you can design secure vault architectures, configure access correctly, and communicate clearly with your team about secrets management strategies.
| Term | Definition |
|---|---|
| Vault | A logical container for secrets, keys, and certificates. Unique DNS: <vault-name>.vault.azure.net |
| Secret | Any text value — connection strings, API keys, passwords, tokens, JSON blobs |
| Key | A cryptographic key (RSA 2048/3072/4096, EC P-256/P-384/P-521) — used for encrypt/decrypt/sign/verify |
| Certificate | An X.509 certificate with its private key managed together — for TLS, code signing, etc. |
| Version | Every secret/key/certificate update creates a new immutable version. Old versions still accessible. |
| Secret Identifier | Unique URI: https://<vault>.vault.azure.net/secrets/<name>/<version> |
| Access Policy | Legacy model — grants a principal Get/List/Set/Delete on vault objects |
| RBAC | Recommended model — fine-grained role assignments using Entra ID |
| Soft Delete | Deleted objects retained for 7–90 days before permanent removal |
| Purge Protection | Prevents permanent deletion of a vault or its objects — compliance requirement |
| Managed HSM | Single-tenant, FIPS 140-2 Level 3 HSM pool — highest security tier |
03Secrets, Keys & Certificates
Key Vault manages three distinct object types, each designed for a specific security use case. Secrets hold arbitrary text values like connection strings and API keys, Keys provide server-side cryptographic operations without ever exposing the key material, and Certificates manage the full lifecycle of X.509 certificates including auto-renewal. Choosing the right object type ensures you get the appropriate security guarantees and operational capabilities for each credential in your system.
Opaque string values. Any text up to 25 KB.
- Database connection strings
- API keys & tokens
- Passwords & credentials
- Storage account keys
- OAuth client secrets
- Arbitrary JSON config blobs
Cryptographic keys. Operations performed server-side.
- RSA 2048 / 3072 / 4096
- EC P-256 / P-384 / P-521
- AES (Managed HSM only)
- Encrypt / Decrypt
- Sign / Verify
- Wrap Key / Unwrap Key
X.509 certs with lifecycle management.
- TLS / SSL certificates
- Code signing certificates
- Auto-renew with DigiCert / GlobalSign
- Self-signed certificates
- Import existing PFX/PEM
- Push to App Service / APIM
04Tiers — Standard vs Premium
Azure Key Vault offers two pricing tiers that differ primarily in how cryptographic keys are protected. Standard tier uses software-backed keys suitable for most workloads, while Premium tier provides HSM-backed keys validated to FIPS 140-2 Level 2 — a requirement for regulated industries handling payment card data or health records. Choose your tier based on compliance requirements early, as migrating keys between tiers requires re-creation. Both tiers support secrets and certificates identically.
| Feature | Standard | Premium |
|---|---|---|
| Secrets | ✓ Yes | ✓ Yes |
| Software-backed Keys | ✓ Yes | ✓ Yes |
| HSM-backed Keys | ✗ No | ✓ Yes |
| FIPS 140-2 Level | Level 1 (software) | Level 2 (HSM) |
| Key types | RSA, EC (software) | RSA, EC (HSM-backed) |
| AES keys | ✗ No | Managed HSM only |
| Pricing | Per operation | Per operation (higher for HSM keys) |
| Compliance | General workloads | Regulated industries (PCI-DSS, HIPAA) |
05Access Model — RBAC vs Access Policies
Key Vault supports two authorization models for controlling who can read, write, or manage vault objects. Azure RBAC (the recommended model) provides fine-grained, per-secret role assignments that integrate with Entra ID Conditional Access and Privileged Identity Management. Legacy Access Policies are vault-scoped and limited to 1,024 entries, making them harder to audit and manage at scale. New vaults should always use RBAC mode — it offers better security posture, inheritance from management groups, and supports just-in-time access elevation.
| Dimension | Azure RBAC (Recommended) | Vault Access Policies (Legacy) |
|---|---|---|
| Control plane | ✓ Yes | ✓ Yes |
| Data plane | ✓ Yes | ✓ Yes |
| Granularity | Per-secret, per-key, per-cert | Vault-wide only |
| Audit | Azure Activity Log + RBAC logs | Vault diagnostic logs |
| Inheritance | Management group → subscription → RG | Vault-scoped only |
| Max policies per vault | N/A | 1,024 |
| Conditional Access support | ✓ Yes | ✗ No |
| PIM (Just-in-time access) | ✓ Yes | ✗ No |
| Migration | Switch vault to RBAC mode (one-way) | Default on older vaults |
Key Vault RBAC Roles
| Role | Scope | Permissions |
|---|---|---|
Key Vault Administrator | Vault | Full data plane + manage access policies |
Key Vault Secrets Officer | Vault / Secret | CRUD secrets, manage secret properties |
Key Vault Secrets User | Vault / Secret | Read secret value only — most applications use this |
Key Vault Crypto Officer | Vault / Key | CRUD keys, perform all crypto operations |
Key Vault Crypto User | Vault / Key | Use keys for crypto operations — no management |
Key Vault Certificate Officer | Vault / Cert | CRUD certificates |
Key Vault Reader | Vault | Read metadata only — no secret values |
Key Vault Purge Operator | Vault | Can purge soft-deleted objects |
Assign RBAC Role via CLI
# Grant a Managed Identity read access to secrets only (recommended)
az role assignment create \
--assignee <principal-id-or-object-id> \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.KeyVault/vaults/<vault-name>
# Grant access to a specific secret only (fine-grained)
az role assignment create \
--assignee <principal-id> \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/\
Microsoft.KeyVault/vaults/<vault-name>/secrets/<secret-name>06Working with Secrets
Secrets are the most commonly used Key Vault object type — they store any text value up to 25 KB, including database connection strings, API keys, passwords, and OAuth tokens. The SDK provides a simple get/set interface that works identically across .NET, Python, Java, and JavaScript, with DefaultAzureCredential handling authentication transparently. In production, always set expiration dates and content types on secrets to enable automated rotation workflows and make audit logs more meaningful.
Set & Get Secrets — .NET SDK
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
var vaultUri = new Uri("https://<vault-name>.vault.azure.net/");
var credential = new DefaultAzureCredential();
var client = new SecretClient(vaultUri, credential);
// Set a secret
await client.SetSecretAsync("db-connection-string",
"Server=myserver.database.windows.net;Database=mydb;...");
// Get latest version
KeyVaultSecret secret = await client.GetSecretAsync("db-connection-string");
Console.WriteLine(secret.Value);
// Get specific version
KeyVaultSecret versionedSecret = await client.GetSecretAsync(
"db-connection-string",
"a1b2c3d4e5f6...");
// Set with properties (expiry, content type, tags)
var newSecret = new KeyVaultSecret("api-key", "sk-abcdef123456");
newSecret.Properties.ExpiresOn = DateTimeOffset.UtcNow.AddDays(90);
newSecret.Properties.ContentType = "text/plain";
newSecret.Properties.Tags["Environment"] = "Production";
newSecret.Properties.Tags["ManagedBy"] = "Platform-Team";
await client.SetSecretAsync(newSecret);
// List all secret names
await foreach (SecretProperties props in client.GetPropertiesOfSecretsAsync())
{
Console.WriteLine($"{props.Name} — Enabled: {props.Enabled}, Expires: {props.ExpiresOn}");
}Set & Get Secrets — Python SDK
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
from datetime import datetime, timezone, timedelta
credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://<vault>.vault.azure.net/", credential=credential)
# Set a secret
client.set_secret("db-password", "super-secret-password",
expires_on=datetime.now(timezone.utc) + timedelta(days=90),
content_type="text/plain",
tags={"Environment": "Prod"})
# Get a secret
secret = client.get_secret("db-password")
print(secret.value)
# List all secrets
for secret_props in client.list_properties_of_secrets():
print(f"{secret_props.name}: expires {secret_props.expires_on}")CLI Operations
# Create a secret
az keyvault secret set \
--vault-name myKeyVault \
--name "db-password" \
--value "P@ssw0rd!Secure" \
--expires "2026-12-31T00:00:00Z"
# Show a secret value
az keyvault secret show \
--vault-name myKeyVault \
--name "db-password" \
--query value -o tsv
# List all secrets
az keyvault secret list --vault-name myKeyVault --query "[].name" -o table
# Delete (soft delete — restorable)
az keyvault secret delete --vault-name myKeyVault --name "db-password"
# Restore soft-deleted secret
az keyvault secret recover --vault-name myKeyVault --name "db-password"
# Permanently purge (requires purge protection to be off)
az keyvault secret purge --vault-name myKeyVault --name "db-password"07Working with Keys (Cryptography)
Key Vault keys enable server-side cryptographic operations — encryption, decryption, signing, and verification — without ever exposing the raw key material to your application. This is critical for compliance because the key never leaves the vault boundary (or HSM boundary on Premium tier), eliminating the risk of key exfiltration. Use RSA keys for encryption and general-purpose signing, and EC keys for high-performance digital signatures. For large data encryption, always use the envelope encryption pattern rather than sending data directly to Key Vault.
Create & Use Keys — .NET SDK
using Azure.Security.KeyVault.Keys;
using Azure.Security.KeyVault.Keys.Cryptography;
var keyClient = new KeyVaultClient(vaultUri, credential);
// Create RSA 4096-bit key (HSM-protected on Premium)
var rsaKey = new CreateRsaKeyOptions("my-rsa-key")
{
KeySize = 4096,
KeyOperations = { KeyOperation.Encrypt, KeyOperation.Decrypt, KeyOperation.Sign, KeyOperation.Verify },
ExpiresOn = DateTimeOffset.UtcNow.AddYears(1),
HardwareProtected = true // HSM-backed (Premium tier required)
};
KeyVaultKey key = await keyClient.CreateRsaKeyAsync(rsaKey);
// Create EC key for signing
var ecKey = new CreateEcKeyOptions("my-ec-key")
{
CurveName = KeyCurveName.P256,
KeyOperations = { KeyOperation.Sign, KeyOperation.Verify }
};
await keyClient.CreateEcKeyAsync(ecKey);
// Encrypt data
var cryptoClient = new CryptographyClient(key.Id, credential);
byte[] plaintext = Encoding.UTF8.GetBytes("sensitive data");
EncryptResult encryptResult = await cryptoClient.EncryptAsync(
EncryptionAlgorithm.RsaOaep256, plaintext);
// Decrypt data
DecryptResult decryptResult = await cryptoClient.DecryptAsync(
EncryptionAlgorithm.RsaOaep256, encryptResult.Ciphertext);
string decrypted = Encoding.UTF8.GetString(decryptResult.Plaintext);
// Sign data
SignResult signResult = await cryptoClient.SignDataAsync(
SignatureAlgorithm.RS256, Encoding.UTF8.GetBytes("data to sign"));
// Verify signature
VerifyResult verifyResult = await cryptoClient.VerifyDataAsync(
SignatureAlgorithm.RS256,
Encoding.UTF8.GetBytes("data to sign"),
signResult.Signature);
Console.WriteLine($"Valid: {verifyResult.IsValid}");Key Operations Reference
| Operation | Description | Common Use |
|---|---|---|
| Encrypt / Decrypt | Asymmetric encryption with RSA | Protect small payloads, key exchange |
| Sign / Verify | Digital signatures with RSA or EC | Document signing, JWT signing, code signing |
| Wrap Key / Unwrap Key | Encrypt another key (envelope encryption) | Protect symmetric keys (e.g. AES data encryption keys) |
| Import | Bring your own key (BYOK) into Key Vault | Regulatory key custody requirements |
| Rotate | Create new key version; old version still available | Cryptographic agility |
08Working with Certificates
Key Vault certificate management handles the entire lifecycle of X.509 certificates — from creation or import through renewal and deployment. You can generate self-signed certificates for development, or integrate with DigiCert and GlobalSign for production CA-issued certificates that auto-renew before expiry. This eliminates the operational burden of tracking certificate expirations manually, which is one of the most common causes of production outages. Always configure lifetime actions to auto-renew at least 30 days before expiry to allow time for propagation.
| Operation | Description |
|---|---|
| Create (self-signed) | Generate a new self-signed X.509 certificate |
| Create (CA-issued) | Generate CSR and auto-fulfill via DigiCert or GlobalSign |
| Import (PFX/PEM) | Import an existing certificate with private key |
| Renew | Trigger renewal before expiry — automated with integrated CAs |
| Export | Download certificate (PFX, PEM) for use in other services |
| Set issuers | Configure DigiCert / GlobalSign as trusted CAs for auto-issuance |
| Contact notifications | Alert on cert expiry (30, 60, 90 days before) |
Create Certificate Policy (Auto-Renew)
using Azure.Security.KeyVault.Certificates;
var certClient = new CertificateClient(vaultUri, credential);
var certPolicy = new CertificatePolicy("Self", "CN=myapp.contoso.com")
{
ValidityInMonths = 12,
ReuseKey = false,
CertificateTransparency = false,
ContentType = CertificateContentType.Pkcs12,
KeyType = CertificateKeyType.Rsa,
KeySize = 2048,
KeyUsage = { CertificateKeyUsage.DigitalSignature, CertificateKeyUsage.KeyEncipherment },
LifetimeActions =
{
// Auto-renew 30 days before expiry
new LifetimeAction(CertificatePolicyAction.AutoRenew)
{
DaysBeforeExpiry = 30
}
}
};
// Begin certificate creation (async operation)
CertificateOperation operation = await certClient.StartCreateCertificateAsync(
"my-tls-cert", certPolicy);
// Wait for completion
KeyVaultCertificateWithPolicy cert = await operation.WaitForCompletionAsync();
Console.WriteLine($"Cert thumbprint: {Convert.ToHexString(cert.Properties.X509Thumbprint)}");09Managed Identity Integration
Managed Identity is the gold standard for authenticating to Key Vault. No connection strings, no certificates, no secrets in config — the Azure platform handles credential issuance and rotation entirely.
- 1Enable Managed Identity on your resourceSystem-assigned: auto-created and deleted with the resource. User-assigned: independently managed identity shared across resources.
- 2Assign Key Vault RBAC roleGrant Key Vault Secrets User (or narrower scope) to the managed identity via IAM on the vault.
- 3Use DefaultAzureCredential in codeSDK automatically uses Managed Identity when running in Azure. Falls back to Azure CLI / VS credentials in local dev.
- 4No credential rotation neededMicrosoft handles token issuance. No secrets stored anywhere. Access can be revoked instantly by removing the role assignment.
Enable System-Assigned Identity via CLI
# Enable on App Service
az webapp identity assign \
--resource-group myRG \
--name myAppService
# Enable on Function App
az functionapp identity assign \
--resource-group myRG \
--name myFunctionApp
# Enable on AKS node pool (for pod identity)
az aks update \
--resource-group myRG \
--name myAKS \
--enable-managed-identity
# Assign Key Vault Secrets User to the identity
az role assignment create \
--assignee $(az webapp identity show --resource-group myRG --name myApp --query principalId -o tsv) \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>DefaultAzureCredential Chain
| Environment | Credential Used |
|---|---|
| Azure VM / App Service / Functions | Managed Identity (IMDS endpoint) |
| Azure Kubernetes Service | Workload Identity / Pod Identity |
| Local development (VS Code) | Azure CLI credential or Visual Studio credential |
| Local development (VS) | Visual Studio credential |
| CI/CD pipeline | Service Principal via env vars AZURE_CLIENT_ID, AZURE_CLIENT_SECRET |
| GitHub Actions | OIDC federated identity — no secrets stored |
10Network Security & Private Endpoints
By default, Key Vault is accessible over the public internet — which is fine for development but unacceptable for production secrets stores. Network security features let you restrict vault access to specific IP ranges, VNet subnets, or eliminate public exposure entirely with Private Endpoints. In production, the recommended configuration is to disable public network access completely and use Private Endpoints combined with the Trusted Microsoft Services bypass, ensuring only your VNet resources and Azure platform services can reach the vault.
| Feature | Description |
|---|---|
| IP Firewall Rules | Allow access from specific IP ranges only — blocks all other public access |
| VNet Service Endpoints | Allow access from specific VNet subnets — traffic stays on Azure backbone |
| Private Endpoints | Key Vault accessible only via private IP within your VNet — no public exposure at all |
| Trusted Services Bypass | Allow Azure services (App Service, Functions) to bypass firewall when using Managed Identity |
| Disable Public Network Access | Block all public internet access — only private endpoints work |
Create Private Endpoint via CLI
# Create private endpoint for Key Vault
az network private-endpoint create \
--name myKVPrivateEndpoint \
--resource-group myRG \
--vnet-name myVNet \
--subnet mySubnet \
--private-connection-resource-id \
/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault> \
--group-ids vault \
--connection-name myKVConnection
# Create Private DNS Zone for Key Vault
az network private-dns zone create \
--resource-group myRG \
--name "privatelink.vaultcore.azure.net"
# Link DNS zone to VNet
az network private-dns link vnet create \
--resource-group myRG \
--zone-name "privatelink.vaultcore.azure.net" \
--name myDNSLink \
--virtual-network myVNet \
--registration-enabled false11Soft Delete & Purge Protection
Soft delete and purge protection are Key Vault's safety net against accidental or malicious deletion of critical secrets and keys. When soft delete is enabled (mandatory on all new vaults), deleted objects are retained in a recoverable state for 7–90 days before permanent removal. Purge protection goes further by preventing anyone — including administrators — from permanently destroying objects during the retention window. Enable purge protection on all production vaults, especially those holding encryption keys, as losing a key means losing access to all data encrypted with it.
| Feature | Description | Default |
|---|---|---|
| Soft Delete | Deleted secrets/keys/certs/vaults retained for 7–90 days | Enabled (cannot disable on new vaults) |
| Soft Delete Retention | How long deleted items are kept before permanent deletion | 90 days (configurable 7–90) |
| Purge Protection | Prevents permanent deletion during soft-delete window — even by admins | Off (strongly recommended on) |
| Recover | Restore a soft-deleted object back to active state | Available during retention window |
| Purge | Permanently destroy an object (only after soft-delete window, or if purge protection off) | Requires explicit permission |
# Create vault with purge protection (recommended for production)
az keyvault create \
--resource-group myRG \
--name myKeyVault \
--location eastus \
--enable-purge-protection true \
--retention-days 90 \
--enable-rbac-authorization true
# Enable purge protection on existing vault
az keyvault update \
--resource-group myRG \
--name myKeyVault \
--enable-purge-protection true12Secret Versioning & Rotation
Every time you update a secret, Key Vault creates a new immutable version. The previous version remains accessible via its version-specific URI — enabling rollback. Automated rotation uses Event Grid to notify Function Apps or Logic Apps when rotation is needed.
Secret Version URIs
# Latest version (always resolves to current active version)
https://<vault>.vault.azure.net/secrets/<name>
# Specific version (immutable — always returns that version)
https://<vault>.vault.azure.net/secrets/<name>/<version-id>
# Example
https://myvault.vault.azure.net/secrets/db-password
https://myvault.vault.azure.net/secrets/db-password/a1b2c3d4e5f6789012345678901234abAutomated Rotation with Event Grid
// Event Grid fires this event when a secret is near expiry
{
"id": "...",
"topic": "/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault>",
"subject": "db-password",
"eventType": "Microsoft.KeyVault.SecretNearExpiry",
"data": {
"Id": "https://<vault>.vault.azure.net/secrets/db-password/<version>",
"VaultName": "<vault>",
"ObjectType": "Secret",
"ObjectName": "db-password",
"Version": "<version>",
"NBF": null,
"EXP": 1893456000
}
}Rotation Function (Event Grid Handler)
[FunctionName("RotateSecret")]
public static async Task Run(
[EventGridTrigger] EventGridEvent eventGridEvent,
ILogger log)
{
var secretName = eventGridEvent.Subject;
log.LogInformation($"Rotating secret: {secretName}");
// 1. Generate new credential at the source system (DB, API, etc.)
var newPassword = await GenerateNewDbPasswordAsync();
// 2. Update the secret in Key Vault with new version
var secretClient = new SecretClient(
new Uri("https://<vault>.vault.azure.net/"),
new DefaultAzureCredential());
var newSecret = new KeyVaultSecret(secretName, newPassword);
newSecret.Properties.ExpiresOn = DateTimeOffset.UtcNow.AddDays(90);
await secretClient.SetSecretAsync(newSecret);
log.LogInformation($"Secret {secretName} rotated successfully");
// Apps using the latest-version URI will pick up new value on next read
}13Logic Apps Integration
Logic Apps can retrieve secrets from Key Vault as workflow actions — injecting credentials at runtime without ever storing them in workflow definitions or connection parameters.
| Action | Description |
|---|---|
| Get secret | Retrieve a secret value by name — inject into downstream actions |
| Set secret | Create or update a secret value |
| List secrets | Enumerate secret names in a vault |
| Delete secret | Soft-delete a secret |
Get Secret Action (JSON)
{
"Get_DB_password": {
"type": "ApiConnection",
"inputs": {
"host": {
"connection": {
"name": "@parameters('$connections')['keyvault']['connectionId']"
}
},
"method": "get",
"path": "/secrets/@{encodeURIComponent('db-password')}/value"
}
},
"Call_Database": {
"type": "Http",
"inputs": {
"method": "POST",
"uri": "https://myapi.contoso.com/data",
"headers": {
"X-DB-Password": "@body('Get_DB_password')?['value']"
}
},
"runAfter": { "Get_DB_password": ["Succeeded"] }
}
}14Function Apps Integration
Key Vault References in App Settings (Recommended)
The cleanest pattern: store a Key Vault reference in App Settings rather than the actual value. Azure resolves the reference at runtime — the Function App never sees the raw secret in config.
# App Setting value format for Key Vault reference
@Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/<name>/<version>)
# Or always latest version:
@Microsoft.KeyVault(VaultName=<vault>;SecretName=<name>)
# Example in Azure Portal → Function App → Configuration:
# Name: DB_CONNECTION_STRING
# Value: @Microsoft.KeyVault(VaultName=myVault;SecretName=db-connection-string)# Set Key Vault reference via CLI
az functionapp config appsettings set \
--resource-group myRG \
--name myFunctionApp \
--settings "DB_CONNECTION_STRING=@Microsoft.KeyVault(VaultName=myVault;SecretName=db-connection-string)"Read Secret via SDK in Function Code
[FunctionName("ProcessData")]
public static async Task<IActionResult> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequest req,
ILogger log)
{
// Option 1: Read from App Settings (Key Vault reference resolved by platform)
string connectionString = Environment.GetEnvironmentVariable("DB_CONNECTION_STRING");
// Option 2: Read directly from Key Vault via SDK
var kvClient = new SecretClient(
new Uri("https://myvault.vault.azure.net/"),
new DefaultAzureCredential()); // Uses Function App's Managed Identity
KeyVaultSecret secret = await kvClient.GetSecretAsync("api-key");
string apiKey = secret.Value;
return new OkObjectResult("Processed");
}15App Service & Container Apps
Key Vault References in App Service
App Service and Azure Container Apps support the same Key Vault Reference syntax as Function Apps. Enable system-assigned managed identity, grant Key Vault Secrets User role, then use the reference syntax.
# Enable identity on App Service
az webapp identity assign --resource-group myRG --name myWebApp
PRINCIPAL_ID=$(az webapp identity show --resource-group myRG --name myWebApp --query principalId -o tsv)
# Grant access to Key Vault
az role assignment create \
--assignee $PRINCIPAL_ID \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/myVault
# Set Key Vault reference as app setting
az webapp config appsettings set \
--resource-group myRG \
--name myWebApp \
--settings "STRIPE_API_KEY=@Microsoft.KeyVault(VaultName=myVault;SecretName=stripe-api-key)"Container Apps — Secrets from Key Vault
resource containerApp 'Microsoft.App/containerApps@2023-05-01' = {
name: 'myapp'
identity: { type: 'SystemAssigned' }
properties: {
configuration: {
secrets: [
{
name: 'db-password'
keyVaultUrl: 'https://myvault.vault.azure.net/secrets/db-password'
identity: 'system' // Use system-assigned identity
}
]
}
template: {
containers: [{
env: [
{ name: 'DB_PASSWORD', secretRef: 'db-password' }
]
}]
}
}
}16AKS & Kubernetes Integration
Two main approaches for AKS: Workload Identity (Entra ID pod-level identity) combined with the Secrets Store CSI Driver to mount Key Vault secrets as Kubernetes secrets or volume files.
Secrets Store CSI Driver Setup
# Enable CSI driver add-on on AKS
az aks enable-addons \
--resource-group myRG \
--name myAKS \
--addons azure-keyvault-secrets-provider
# Enable Workload Identity
az aks update \
--resource-group myRG \
--name myAKS \
--enable-workload-identity \
--enable-oidc-issuerSecretProviderClass Manifest
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: azure-keyvault-secrets
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "false"
clientID: "<workload-identity-client-id>"
keyvaultName: "myVault"
cloudName: ""
objects: |
array:
- |
objectName: db-password
objectType: secret
objectVersion: ""
- |
objectName: my-tls-cert
objectType: cert
objectVersion: ""
tenantId: "<tenant-id>"
secretObjects:
- secretName: app-secrets
type: Opaque
data:
- objectName: db-password
key: DB_PASSWORD17CI/CD & DevOps Integration
CI/CD pipelines often need secrets for deployment — database credentials, API keys, signing certificates — but storing them as pipeline variables or repository secrets creates sprawl and audit gaps. Key Vault integration lets your pipelines fetch secrets at runtime using OIDC federated credentials (GitHub Actions) or service connections (Azure DevOps), keeping all secrets centralized with full audit logging. The best practice is to use OIDC authentication where possible, eliminating long-lived service principal secrets entirely from your CI/CD configuration.
GitHub Actions — Read Key Vault Secrets
name: Deploy with Key Vault Secrets
on:
push:
branches: [main]
permissions:
id-token: write # Required for OIDC
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Login to Azure (OIDC — no secrets stored in GitHub)
uses: azure/login@v1
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: Get secrets from Key Vault
uses: azure/get-keyvault-secrets@v1
with:
keyvault: myKeyVault
secrets: 'db-connection-string, stripe-api-key, jwt-signing-key'
id: kvSecrets
- name: Deploy
run: |
# Secrets available as env vars: DB-CONNECTION-STRING, STRIPE-API-KEY, etc.
echo "Deploying with retrieved secrets..."
env:
DB_CONN: ${{ steps.kvSecrets.outputs.db-connection-string }}Azure DevOps — Key Vault Task
- task: AzureKeyVault@2
displayName: 'Get Key Vault Secrets'
inputs:
azureSubscription: 'myServiceConnection'
KeyVaultName: 'myKeyVault'
SecretsFilter: 'db-password,api-key,jwt-secret'
RunAsPreJob: true
# Secrets now available as pipeline variables: $(db-password), $(api-key)
- script: echo "Deploying..."
env:
DB_PASS: $(db-password)18Monitoring, Audit & Alerts
Key Vault logs every single operation — every secret read, every failed access, every key use. This audit trail is critical for compliance and breach detection. Enable diagnostic settings to ship logs to Log Analytics.
| Log Category | Description | Alert On |
|---|---|---|
| AuditEvent | All data plane operations: GET, SET, DELETE, SIGN, etc. | Unauthorized access attempts |
| AzurePolicyEvaluationDetails | Policy compliance evaluations | Non-compliant resources |
| Metrics | API latency, availability, total requests | High error rate, latency spikes |
KQL — Unauthorized Access Attempts
AzureDiagnostics
| where ResourceType == "VAULTS"
| where ResultType != "Success"
| where OperationName in ("SecretGet", "KeySign", "CertificateGet")
| project TimeGenerated, OperationName, ResultType, CallerIpAddress,
identity_claim_appid_g, requestUri_s
| order by TimeGenerated descKQL — Who Accessed a Specific Secret
AzureDiagnostics
| where ResourceType == "VAULTS"
| where OperationName == "SecretGet"
| where id_s contains "db-password"
| project TimeGenerated, CallerIpAddress,
identity_claim_appid_g,
identity_claim_oid_g,
requestUri_s, ResultType
| order by TimeGenerated descEnable Diagnostic Settings via CLI
az monitor diagnostic-settings create \
--resource /subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.KeyVault/vaults/<vault> \
--name "kv-audit-logs" \
--workspace <log-analytics-workspace-id> \
--logs '[{"category":"AuditEvent","enabled":true,"retentionPolicy":{"enabled":true,"days":365}}]' \
--metrics '[{"category":"AllMetrics","enabled":true}]'19Managed HSM
Azure Managed HSM is a fully managed, single-tenant, FIPS 140-2 Level 3 HSM service. Unlike Key Vault Premium (Level 2, shared HSM), Managed HSM gives you a dedicated HSM pool where Microsoft cannot access your keys — the security domain is entirely customer-controlled.
| Feature | Key Vault Premium | Managed HSM |
|---|---|---|
| FIPS Level | 140-2 Level 2 | 140-2 Level 3 |
| Tenant model | Multi-tenant HSM | Single-tenant dedicated |
| Microsoft key access | Possible (shared) | Not possible (customer security domain) |
| AES-256 keys | ✗ No | ✓ Yes |
| RSA / EC keys | ✓ Yes | ✓ Yes |
| BYOK | ✓ Yes | ✓ Yes |
| Role management | Azure RBAC | Local RBAC (Managed HSM-specific roles) |
| Availability | Zone-redundant | 99.9% SLA, zone-redundant |
| Use case | Most regulated workloads | Highest assurance, payment schemes, govt |
| Price | Per operation | ~$3.20/hr per HSM unit |
# Create Managed HSM
az keyvault create \
--hsm-name myManagedHSM \
--resource-group myRG \
--location eastus \
--sku Standard_B1 \
--administrators <admin-object-id>
# Activate (download security domain — required first time)
# This is a ceremony requiring 3+ security domain officers
az keyvault security-domain download \
--hsm-name myManagedHSM \
--sd-wrapping-keys cert1.cer cert2.cer cert3.cer \
--sd-quorum 2 \
--security-domain-file sd.json20Security Patterns & Best Practices
These battle-tested patterns represent the collective wisdom of securing secrets at scale across enterprise Azure environments. Each pattern addresses a specific threat vector — from credential leakage in source code to blast radius containment when a single application is compromised. Implementing these patterns together creates a defense-in-depth strategy where no single failure can expose your entire secrets estate. Start with zero-secret architecture and least privilege access as your foundation, then layer on rotation and vault isolation as your organization matures.
21Pricing Overview
Key Vault pricing is operation-based — you pay per 10,000 transactions rather than a flat monthly fee, making it extremely cost-effective for most workloads. A typical application reading secrets a few hundred times per day will cost pennies per month. HSM-backed key operations on Premium tier cost more due to the dedicated hardware, and Managed HSM has a significant hourly charge reflecting its single-tenant nature. The most important cost optimization is caching secrets in memory rather than fetching on every request.
Key Vault Standard & Premium
| Operation Type | Standard | Premium |
|---|---|---|
| Secrets operations | ~$0.03 per 10,000 | ~$0.03 per 10,000 |
| Certificate operations | ~$3.00 per certificate renewal | ~$3.00 per certificate renewal |
| RSA 2048 key operations | ~$0.03 per 10,000 | ~$0.03 per 10,000 (SW) |
| RSA 2048 HSM key operations | N/A | ~$0.15 per 10,000 |
| RSA 4096 HSM key operations | N/A | ~$0.30 per 10,000 |
| Advanced threat protection | ~$0.02 per vault/month | ~$0.02 per vault/month |
Managed HSM
| SKU | Price |
|---|---|
| Standard B1 (3 HSM partitions) | ~$2.30/hr (~$1,660/month) |
| Key operations (all types) | ~$0.30 per 10,000 |
22Quick Reference Cheat Sheet
This cheat sheet consolidates the most frequently needed Key Vault URIs, CLI commands, and service limits into a single quick-reference section. Bookmark this for daily operations — it covers everything from constructing secret URIs and App Setting references to the essential CLI commands for vault management. Keep the transaction rate limits in mind when designing high-throughput applications, and remember that secret names are limited to 127 characters with only alphanumeric characters and dashes allowed.
# Vault base URL
https://<vault-name>.vault.azure.net/
# Secret URI (latest version)
https://<vault>.vault.azure.net/secrets/<name>
# Secret URI (specific version)
https://<vault>.vault.azure.net/secrets/<name>/<version>
# Key URI
https://<vault>.vault.azure.net/keys/<name>/<version>
# Certificate URI
https://<vault>.vault.azure.net/certificates/<name>/<version>
# App Setting Key Vault Reference (latest)
@Microsoft.KeyVault(VaultName=<vault>;SecretName=<name>)
# App Setting Key Vault Reference (specific version)
@Microsoft.KeyVault(SecretUri=https://<vault>.vault.azure.net/secrets/<name>/<version>)# Create vault (RBAC mode, purge protection on)
az keyvault create -g myRG -n myVault \
--enable-rbac-authorization true \
--enable-purge-protection true
# Set / Get / Delete a secret
az keyvault secret set --vault-name myVault -n mySecret --value "value"
az keyvault secret show --vault-name myVault -n mySecret --query value -o tsv
az keyvault secret delete --vault-name myVault -n mySecret
az keyvault secret recover --vault-name myVault -n mySecret
# List all secrets
az keyvault secret list --vault-name myVault --query "[].name" -o table
# List secret versions
az keyvault secret list-versions --vault-name myVault -n mySecret -o table
# Assign role
az role assignment create --assignee <oid> \
--role "Key Vault Secrets User" \
--scope /subscriptions/<sub>/.../vaults/<vault>
# Show Key Vault access policies / identity
az keyvault show --name myVault --query "properties.accessPolicies"
az webapp identity show -g myRG -n myApp --query principalId -o tsv| Limit | Value |
|---|---|
| Max vaults per subscription | Unlimited (soft limit: 500/region) |
| Max secrets per vault | Unlimited |
| Max keys per vault | Unlimited |
| Max secret size | 25 KB |
| Max secret name length | 127 characters |
| Max versions per secret/key | Unlimited |
| Max transaction rate | 2,000 combined secret/key/cert operations per 10 seconds |
| Soft delete retention | 7–90 days (default 90) |
| Managed HSM key operations/sec | 2,000 (RSA 2048) |