Key Vault — Virtual Network Integration and Private Endpoints
A comprehensive guide to securing Azure Key Vault with network isolation using firewall rules, VNet service endpoints, private endpoints, and trusted service bypass — following zero-trust principles.
1. Why Network Security for Key Vault Matters
Zero Trust Principles
Azure Key Vault stores your most sensitive assets: encryption keys, certificates, and secrets such as connection strings and API keys. Under a zero-trust model, no network location is inherently trusted. Even traffic originating from within your Azure subscription must be explicitly authorized.
Key principles applied to Key Vault:
- Verify explicitly — Authenticate every request with Managed Identity or service principal; authorize with RBAC.
- Least-privilege access — Grant only the specific Key Vault roles needed (e.g.,
Key Vault Secrets Userrather thanContributor). - Assume breach — Restrict the network blast radius so that a compromised VM in one subnet cannot reach vaults in another.
Compliance Requirements
Regulatory frameworks mandate network-level controls for cryptographic key stores:
| Framework | Requirement |
|---|---|
| PCI-DSS 4.0 | Requirement 1: Install and maintain network security controls |
| HIPAA | §164.312(e)(1): Transmission security — encrypt and restrict access |
| SOC 2 | CC6.1: Logical and physical access controls |
| ISO 27001 | A.13.1: Network security management |
Attack Surface Reduction
A publicly accessible Key Vault exposes a TLS endpoint on the internet. Even with strong authentication, this creates risk:
- Credential stuffing against service principals
- Token replay from compromised CI/CD pipelines
- Exfiltration via compromised developer workstations
By restricting network access, you add a defense-in-depth layer that blocks unauthorized traffic before authentication is even evaluated.
2. Architecture Overview
┌──────────────────── Azure Region ───────────────────────┐
│ │
│ VNet 10.0.0.0/16 │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ App Subnet │ │ AKS Subnet │ │ PE Subnet │ │
│ │ 10.0.1.0/24 │ │ 10.0.2.0/24 │ │ 10.0.3.0/24 │ │
│ │ │ │ │ │ │ │
│ │ App Service │ │ AKS Cluster │ │ Private EP │ │
│ │ (VNet Integ) │ │ (CSI Driver) │ │ 10.0.3.4 │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ └─────────────────┴─────────────────┘ │
│ │ │
│ Private DNS Zone: privatelink.vaultcore.azure.net │
│ myvault.privatelink.vaultcore.azure.net → 10.0.3.4 │
│ │ │
│ ┌─────────────────────────┴─────────────────────────┐ │
│ │ Azure Key Vault (Public Access: Disabled) │ │
│ │ Firewall: Deny All | Trusted Services: Bypass │ │
│ └───────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
▲
│ ExpressRoute / VPN
┌──────┴───────┐
│ On-Premises │
└──────────────┘
Traffic flow:
- App Service uses VNet integration to route outbound traffic through the App Subnet.
- DNS resolves
myvault.vault.azure.net→ CNAME →myvault.privatelink.vaultcore.azure.net→10.0.3.4. - Traffic reaches the private endpoint NIC in the PE Subnet.
- Key Vault processes the request over the Microsoft backbone — never traversing the public internet.
3. Understanding Key Vault Network Access Models
3.1 Public Access (Default)
By default, Key Vault accepts connections from any IP address on the internet. Authentication is still required, but the endpoint is reachable globally.
# Check current network rules
az keyvault show --name myvault --resource-group rg \
--query "properties.networkAcls" -o json
Default response:
{
"bypass": "AzureServices",
"defaultAction": "Allow",
"ipRules": [],
"virtualNetworkRules": []
}
3.2 Service Endpoints
VNet service endpoints extend your VNet identity to Key Vault over the Azure backbone. Traffic still hits the public endpoint, but Key Vault validates the source VNet/subnet.
Limitations:
- Traffic uses the public IP of Key Vault (no private IP assigned)
- Cannot be used from on-premises networks
- Limited to the same region (or paired region with global service endpoints)
3.3 Private Endpoints
Private endpoints assign a private IP from your VNet to the Key Vault instance. DNS is configured so that the vault's FQDN resolves to this private IP. Traffic never leaves the Microsoft network.
Advantages over service endpoints:
- Works from on-premises via VPN/ExpressRoute
- Works cross-region
- Fully eliminates public endpoint exposure
- Compatible with NSG and UDR on the subnet
3.4 Trusted Services Bypass
Certain first-party Azure services need access to Key Vault for platform operations (e.g., disk encryption, SQL TDE, App Service certificate binding). The "trusted services" bypass allows these services even when the firewall denies all other traffic.
Trusted services include:
- Azure Backup
- Azure Disk Encryption
- Azure SQL (TDE)
- Azure Storage (CMK encryption)
- Azure Data Factory
- Azure Event Hubs / Service Bus (CMK)
- Azure Resource Manager (for template deployments)
4. Step-by-Step Implementation
4.1 Configuring Firewall Rules
IP-Based Rules
Allow specific public IPs (e.g., corporate egress, CI/CD runners):
# Add IP rule for corporate office
az keyvault network-rule add \
--name myvault \
--resource-group rg \
--ip-address 203.0.113.0/24
# Add individual IP for CI/CD runner
az keyvault network-rule add \
--name myvault \
--resource-group rg \
--ip-address 198.51.100.42
# Set default action to Deny (blocks all traffic not explicitly allowed)
az keyvault update \
--name myvault \
--resource-group rg \
--default-action Deny
VNet Rules (Service Endpoints)
# Enable service endpoint on the subnet first
az network vnet subnet update \
--resource-group rg \
--vnet-name myvnet \
--name app-subnet \
--service-endpoints Microsoft.KeyVault
# Add VNet rule to Key Vault
SUBNET_ID=$(az network vnet subnet show \
--resource-group rg \
--vnet-name myvnet \
--name app-subnet \
--query id -o tsv)
az keyvault network-rule add \
--name myvault \
--resource-group rg \
--subnet $SUBNET_ID
Bicep — Firewall Configuration
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' = {
name: 'myvault'
location: resourceGroup().location
properties: {
sku: { family: 'A', name: 'standard' }
tenantId: subscription().tenantId
enableRbacAuthorization: true
publicNetworkAccess: 'Disabled'
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
ipRules: [
{ value: '203.0.113.0/24' }
]
virtualNetworkRules: [
{ id: appSubnet.id }
]
}
}
}
4.2 Creating Private Endpoints
Azure CLI
# Get Key Vault resource ID
KV_ID=$(az keyvault show --name myvault --resource-group rg --query id -o tsv)
# Create private endpoint
az network private-endpoint create \
--name kv-private-endpoint \
--resource-group rg \
--vnet-name myvnet \
--subnet pe-subnet \
--private-connection-resource-id $KV_ID \
--group-id vault \
--connection-name kv-pe-connection
# Verify the private endpoint connection
az keyvault private-endpoint-connection list \
--vault-name myvault \
--resource-group rg -o table
Bicep — Private Endpoint
param vnetName string
param peSubnetName string
param keyVaultName string
param location string = resourceGroup().location
resource vnet 'Microsoft.Network/virtualNetworks@2023-09-01' existing = { name: vnetName }
resource peSubnet 'Microsoft.Network/virtualNetworks/subnets@2023-09-01' existing = {
parent: vnet
name: peSubnetName
}
resource keyVault 'Microsoft.KeyVault/vaults@2023-07-01' existing = { name: keyVaultName }
resource privateEndpoint 'Microsoft.Network/privateEndpoints@2023-09-01' = {
name: 'kv-private-endpoint'
location: location
properties: {
subnet: { id: peSubnet.id }
privateLinkServiceConnections: [
{
name: 'kv-pe-connection'
properties: {
privateLinkServiceId: keyVault.id
groupIds: [ 'vault' ]
}
}
]
}
}
4.3 DNS Configuration for Private Endpoints
Private endpoints require DNS to resolve the vault FQDN to the private IP. Azure Private DNS Zones automate this.
Azure CLI — Private DNS Zone Setup
# Create private DNS zone
az network private-dns zone create \
--resource-group rg \
--name privatelink.vaultcore.azure.net
# Link DNS zone to VNet
az network private-dns link vnet create \
--resource-group rg \
--zone-name privatelink.vaultcore.azure.net \
--name kv-dns-link \
--virtual-network myvnet \
--registration-enabled false
# Create DNS zone group (auto-registers A record for the PE)
az network private-endpoint dns-zone-group create \
--resource-group rg \
--endpoint-name kv-private-endpoint \
--name kv-dns-zone-group \
--private-dns-zone privatelink.vaultcore.azure.net \
--zone-name keyvault
# Verify DNS resolution
az network private-endpoint dns-zone-group show \
--resource-group rg \
--endpoint-name kv-private-endpoint \
--name kv-dns-zone-group
Bicep — Private DNS Zone and Link
name: 'privatelink.vaultcore.azure.net'
location: 'global'
}
resource dnsVnetLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: 'kv-vnet-link'
location: 'global'
properties: {
virtualNetwork: { id: vnet.id }
registrationEnabled: false
}
}
resource dnsZoneGroup 'Microsoft.Network/privateEndpoints/privateDnsZoneGroups@2023-09-01' = {
parent: privateEndpoint
name: 'kv-dns-zone-group'
properties: {
privateDnsZoneConfigs: [
{
name: 'keyvault'
properties: {
privateDnsZoneId: privateDnsZone.id
}
}
]
}
}
DNS Resolution Chain
Client request: myvault.vault.azure.net
└─► CNAME: myvault.privatelink.vaultcore.azure.net
└─► A record (Private DNS Zone): 10.0.3.4
For on-premises clients, configure conditional forwarders to forward vaultcore.azure.net queries to Azure DNS (168.63.129.16) via a DNS forwarder VM or Azure DNS Private Resolver.
4.4 Configuring Trusted Microsoft Services Bypass
# Enable trusted services bypass
az keyvault update \
--name myvault \
--resource-group rg \
--bypass AzureServices
# Verify
az keyvault show --name myvault --resource-group rg \
--query "properties.networkAcls.bypass" -o tsv
# Output: AzureServices
To disable bypass (strict mode — not recommended unless all services use private endpoints):
az keyvault update \
--name myvault \
--resource-group rg \
--bypass None
5. Integration Patterns
5.1 App Service with VNet Integration
App Service requires regional VNet integration to route outbound traffic through your VNet and reach the private endpoint.
# Enable VNet integration on App Service
az webapp vnet-integration add \
--name mywebapp \
--resource-group rg \
--vnet myvnet \
--subnet app-subnet
# Route all traffic through VNet (required for private endpoint DNS)
az webapp config appsettings set \
--name mywebapp \
--resource-group rg \
--settings WEBSITE_VNET_ROUTE_ALL=1
# Use private DNS — set DNS to Azure DNS
az webapp config appsettings set \
--name mywebapp \
--resource-group rg \
--settings WEBSITE_DNS_SERVER=168.63.129.16
C# — Accessing Key Vault from App Service with Managed Identity:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
var client = new SecretClient(
new Uri("https://myvault.vault.azure.net"),
new DefaultAzureCredential());
KeyVaultSecret secret = await client.GetSecretAsync("my-secret");
string secretValue = secret.Value;
No code changes are needed when switching from public to private access — the SDK resolves DNS transparently.
5.2 Azure Functions with Private Endpoint
Azure Functions on the Elastic Premium or App Service plan support VNet integration.
resource functionApp 'Microsoft.Web/sites@2023-01-01' = {
name: 'myfuncapp'
location: location
kind: 'functionapp'
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: appServicePlan.id
virtualNetworkSubnetId: appSubnet.id
siteConfig: {
vnetRouteAllEnabled: true
appSettings: [
{ name: 'WEBSITE_DNS_SERVER', value: '168.63.129.16' }
{ name: 'KeyVaultUri', value: 'https://myvault.vault.azure.net' }
]
}
}
}
5.3 AKS Pods Accessing Key Vault via CSI Driver
The Azure Key Vault Provider for Secrets Store CSI Driver mounts secrets as volumes in pods. With private endpoints, the AKS VNet must resolve the vault's private DNS.
# Enable the addon
az aks enable-addons \
--addons azure-keyvault-secrets-provider \
--name myaks \
--resource-group rg
# Link the private DNS zone to the AKS VNet
az network private-dns link vnet create \
--resource-group rg \
--zone-name privatelink.vaultcore.azure.net \
--name aks-dns-link \
--virtual-network aks-vnet \
--registration-enabled false
SecretProviderClass manifest:
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
name: kv-secrets
spec:
provider: azure
parameters:
usePodIdentity: "false"
useVMManagedIdentity: "true"
userAssignedIdentityID: "<managed-identity-client-id>"
keyvaultName: "myvault"
objects: |
array:
- |
objectName: my-secret
objectType: secret
tenantId: "<tenant-id>"
Mount the SecretProviderClass as a CSI volume in your pod spec with driver: secrets-store.csi.k8s.io and secretProviderClass: "kv-secrets".
5.4 Logic Apps with VNet Connector
Standard Logic Apps (single-tenant) support VNet integration natively. Consumption Logic Apps require an Integration Service Environment (ISE) or the newer VNet connector.
# Standard Logic App — enable VNet integration
az logicapp vnet-integration add \
--name mylogicapp \
--resource-group rg \
--vnet myvnet \
--subnet logic-subnet
For the Key Vault connector action in the workflow, use managed identity authentication with audience https://vault.azure.net.
6. Managed Identity + Private Endpoint Combination
The strongest security posture combines network isolation (private endpoints) with passwordless authentication (managed identity) and fine-grained authorization (RBAC).
6.1 System-Assigned vs User-Assigned Identity
| Aspect | System-Assigned | User-Assigned |
|---|---|---|
| Lifecycle | Tied to the resource | Independent |
| Sharing | One per resource | Shared across resources |
| Use case | Single-purpose services | Shared identity for microservices |
| Deletion | Auto-deleted with resource | Must be manually deleted |
Recommendation: Use user-assigned identities for services that share the same Key Vault access pattern (e.g., multiple Function Apps reading the same secrets). Use system-assigned for isolated workloads.
6.2 RBAC Roles for Key Vault
Azure Key Vault supports two authorization models: access policies (legacy) and Azure RBAC (recommended).
Key built-in roles:
| Role | Scope | Permissions |
|---|---|---|
| Key Vault Administrator | Vault | Full management of all vault objects |
| Key Vault Secrets Officer | Vault | Manage secrets (CRUD) |
| Key Vault Secrets User | Vault | Read secret values |
| Key Vault Crypto Officer | Vault | Manage keys |
| Key Vault Crypto User | Vault | Use keys for crypto operations |
| Key Vault Certificates Officer | Vault | Manage certificates |
# Assign "Key Vault Secrets User" to a managed identity
IDENTITY_PRINCIPAL_ID=$(az identity show \
--name my-identity \
--resource-group rg \
--query principalId -o tsv)
az role assignment create \
--role "Key Vault Secrets User" \
--assignee-object-id $IDENTITY_PRINCIPAL_ID \
--assignee-principal-type ServicePrincipal \
--scope $(az keyvault show --name myvault --resource-group rg --query id -o tsv)
Bicep — RBAC Assignment
@description('Principal ID of the managed identity')
param principalId string
resource kvRoleAssignment 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(keyVault.id, principalId, 'Key Vault Secrets User')
scope: keyVault
properties: {
roleDefinitionId: subscriptionResourceId(
'Microsoft.Authorization/roleDefinitions',
'4633458b-17de-408a-b874-0445c86b69e6' // Key Vault Secrets User
)
principalId: principalId
principalType: 'ServicePrincipal'
}
}
6.3 Migrating from Access Policies to RBAC
# Step 1: Enable RBAC authorization (access policies will be ignored)
az keyvault update --name myvault --resource-group rg --enable-rbac-authorization true
# Step 2: Create equivalent RBAC role assignments for all existing access policies
# Step 3: Verify access from each application
# Step 4: Remove legacy access policies to avoid confusion
Important: When
enableRbacAuthorizationis set totrue, all access policies are ignored. Ensure RBAC assignments are in place before enabling this flag.
C# — Full example with User-Assigned Managed Identity:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
// Specify the client ID of the user-assigned managed identity
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
ManagedIdentityClientId = "<user-assigned-identity-client-id>"
});
var client = new SecretClient(
new Uri("https://myvault.vault.azure.net"),
credential);
KeyVaultSecret secret = await client.GetSecretAsync("database-connection-string");
7. Monitoring and Diagnostics
7.1 Diagnostic Settings for Key Vault
Enable diagnostic logs to capture all access attempts, including those blocked by the firewall.
# Create Log Analytics workspace (if not existing)
WORKSPACE_ID=$(az monitor log-analytics workspace show \
--resource-group rg \
--workspace-name kv-logs \
--query id -o tsv)
# Enable diagnostics on Key Vault
az monitor diagnostic-settings create \
--name kv-diagnostics \
--resource $(az keyvault show --name myvault --resource-group rg --query id -o tsv) \
--workspace $WORKSPACE_ID \
--logs '[
{"categoryGroup": "allLogs", "enabled": true, "retentionPolicy": {"enabled": true, "days": 90}}
]' \
--metrics '[
{"category": "AllMetrics", "enabled": true, "retentionPolicy": {"enabled": true, "days": 90}}
]'
7.2 KQL Queries for Access Denied Events
All failed requests (403 Forbidden)
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where ResultSignature == "Forbidden" or httpStatusCode_d == 403
| project TimeGenerated, CallerIPAddress, OperationName,
ResultDescription, Resource, identity_claim_appid_g
| order by TimeGenerated desc
| take 100
Network-denied requests (firewall blocks)
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where ResultDescription contains "request is not allowed"
| summarize Count=count() by CallerIPAddress, bin(TimeGenerated, 1h)
| order by Count desc
Detect access from unexpected IPs
let allowedIPs = dynamic(["10.0.1.0/24", "10.0.2.0/24", "10.0.3.4"]);
AzureDiagnostics
| where ResourceProvider == "MICROSOFT.KEYVAULT"
| where httpStatusCode_d == 200
| where not(ipv4_is_in_any_range(CallerIPAddress, allowedIPs))
| project TimeGenerated, CallerIPAddress, OperationName, identity_claim_appid_g
7.3 Azure Policy for Enforcing Private Endpoints
Use built-in policies to enforce network security at scale:
# Assign built-in policy: "Azure Key Vault should disable public network access"
az policy assignment create \
--name "kv-disable-public-access" \
--display-name "Key Vault must disable public network access" \
--policy "405c5871-3e91-4644-8a63-58e19d68ff5b" \
--scope "/subscriptions/<subscription-id>" \
--params '{"effect": {"value": "Deny"}}'
# Assign built-in policy: "Key vaults should use private link"
az policy assignment create \
--name "kv-require-private-link" \
--display-name "Key Vault must use private endpoints" \
--policy "a6abeaec-4d90-4a02-805f-6b26c4d3fbe9" \
--scope "/subscriptions/<subscription-id>" \
--params '{"effect": {"value": "Audit"}}'
8. Migration Strategy: Public to Private Access
8.1 Phased Migration Without Downtime
Moving from public to private access requires careful sequencing to avoid breaking existing applications.
Phase 1: Prepare (no impact)
# 1. Deploy private endpoint and DNS zone
# 2. Verify DNS resolution from within the VNet
nslookup myvault.vault.azure.net
# Expected: resolves to 10.0.3.4 (private IP)
# 3. Enable VNet integration on all consuming services
# 4. Test application connectivity (vault is still publicly accessible)
Phase 2: Add firewall rules (minimal impact)
# Add VNet rules for all subnets that need access
az keyvault network-rule add --name myvault --resource-group rg --subnet $APP_SUBNET_ID
az keyvault network-rule add --name myvault --resource-group rg --subnet $AKS_SUBNET_ID
# Add IP rules for any external access (CI/CD, developer machines)
az keyvault network-rule add --name myvault --resource-group rg --ip-address $CICD_IP
# Set default action to Deny
az keyvault update --name myvault --resource-group rg --default-action Deny
Phase 3: Disable public access (full lockdown)
# Only after confirming all services work via private endpoint
az keyvault update \
--name myvault \
--resource-group rg \
--public-network-access Disabled
8.2 Testing Connectivity
# From within a VM in the VNet
nslookup myvault.vault.azure.net
# Should return: 10.0.3.4
# Test secret retrieval
az keyvault secret show --vault-name myvault --name test-secret
# From App Service (Kudu console / SSH)
nameresolver myvault.vault.azure.net
# Should return the private IP
# From AKS pod
kubectl exec -it my-app -- nslookup myvault.vault.azure.net
C# connectivity test:
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
using System.Net;
// Verify DNS resolution
var addresses = await Dns.GetHostAddressesAsync("myvault.vault.azure.net");
Console.WriteLine($"Resolved to: {string.Join(", ", addresses.Select(a => a.ToString()))}");
// Verify secret access
var client = new SecretClient(new Uri("https://myvault.vault.azure.net"), new DefaultAzureCredential());
var secret = await client.GetSecretAsync("test-secret");
Console.WriteLine("✓ Secret retrieved successfully");
8.3 Rollback Plan
If issues arise after disabling public access:
# Immediate rollback — re-enable public access
az keyvault update \
--name myvault \
--resource-group rg \
--public-network-access Enabled \
--default-action Allow
# This takes effect within 1-2 minutes
# Private endpoint continues to work alongside public access
Tip: Keep the rollback command in a runbook. The private endpoint remains functional even when public access is re-enabled, so there is no regression for services already using the private path.
9. Multi-Region and Disaster Recovery
9.1 Key Vault is Regional
Azure Key Vault is a regional service — there is no built-in geo-replication. For multi-region architectures:
Option A: Vault per region with synchronized secrets
East US (kv-eastus, PE: 10.0.3.4) ◄──sync──► West US (kv-westus, PE: 10.1.3.4)
Synchronize secrets using Azure DevOps pipelines or a custom Function App that copies secrets between vaults on a schedule.
Option B: Single vault with cross-region private endpoints
Private endpoints can be created in VNets in different regions from the Key Vault. Traffic routes over the Microsoft backbone.
# Create PE in West US VNet pointing to East US Key Vault
az network private-endpoint create \
--name kv-pe-westus \
--resource-group rg-westus \
--vnet-name vnet-westus \
--subnet pe-subnet \
--private-connection-resource-id $KV_EASTUS_ID \
--group-id vault \
--connection-name kv-cross-region-connection
9.2 Private Endpoint Considerations for DR
| Scenario | Approach |
|---|---|
| Active-Active | Vault per region, each with local PE |
| Active-Passive | Primary vault with PE in both regions |
| Failover | DNS-based routing; update app config to point to DR vault |
9.3 Backup and Restore
# Backup a secret (downloads encrypted blob)
az keyvault secret backup --vault-name kv-eastus --name my-secret --file my-secret.bak
# Restore to DR vault (must be in same subscription and geography)
az keyvault secret restore --vault-name kv-westus --file my-secret.bak
Limitation: Key Vault backup/restore only works within the same Azure geography (e.g., US, Europe). Cross-geography restore is not supported.
10. Common Pitfalls, Troubleshooting, and Best Practices
10.1 DNS Resolution Issues
Symptom: Application gets 403 Forbidden or connection timeout after enabling private endpoint.
Root cause: DNS is still resolving to the public IP.
Diagnosis:
# From the failing resource
nslookup myvault.vault.azure.net
# Expected (correct):
# myvault.vault.azure.net → CNAME → myvault.privatelink.vaultcore.azure.net → 10.0.3.4
# Actual (broken):
# myvault.vault.azure.net → 52.x.x.x (public IP)
Fixes:
- Ensure the Private DNS Zone is linked to the VNet where the client resides.
- For App Service: set
WEBSITE_DNS_SERVER=168.63.129.16andWEBSITE_VNET_ROUTE_ALL=1. - For AKS: link the Private DNS Zone to the AKS VNet (not just the PE VNet).
- For on-premises: configure conditional forwarder for
vaultcore.azure.net→ Azure DNS Private Resolver.
10.2 403 Forbidden Errors
| Cause | Diagnostic | Fix |
|---|---|---|
| IP not in firewall allow list | Check CallerIPAddress in diagnostics | Add IP rule |
| VNet rule missing | Check subnet ID in network rules | Add VNet rule |
| Service endpoint not enabled on subnet | az network vnet subnet show | Enable Microsoft.KeyVault endpoint |
| RBAC role not assigned | Check role assignments on vault | Assign appropriate role |
| Access policy missing (legacy mode) | Check access policies | Add access policy or migrate to RBAC |
| Managed identity not propagated | Wait 5-10 minutes after creation | Retry; check principal ID |
10.3 Private Endpoint Connection States
az keyvault private-endpoint-connection list \
--vault-name myvault --resource-group rg -o table
| State | Meaning | Action |
|---|---|---|
| Pending | Awaiting approval | Approve the connection |
| Approved | Active and working | None |
| Rejected | Denied by vault owner | Re-create or approve |
| Disconnected | PE deleted or broken | Re-create PE |
10.4 Best Practices Checklist
- ✅ Disable public network access for all production vaults
- ✅ Use private endpoints instead of service endpoints for new deployments
- ✅ Enable trusted services bypass unless all Azure services use private endpoints
- ✅ Use RBAC instead of access policies (set
enableRbacAuthorization: true) - ✅ Use managed identities — eliminate stored credentials entirely
- ✅ Enable diagnostic logging to Log Analytics with 90+ day retention
- ✅ Apply Azure Policy to prevent creation of publicly accessible vaults
- ✅ Use separate vaults for dev/staging/production environments
- ✅ Enable soft-delete and purge protection (enabled by default since Feb 2025)
- ✅ Tag vaults with data classification and owning team
- ✅ Rotate secrets on a schedule; use Key Vault's expiry notifications
- ✅ Test DNS resolution from every consuming service after enabling private endpoints
- ✅ Document the rollback procedure before disabling public access
10.5 Performance Considerations
- Private endpoint adds negligible latency (< 1ms within the same region)
- Cross-region private endpoints add latency proportional to distance
- Key Vault throttles at 1000 operations per 10 seconds per vault — cache secrets in application code
- Reuse
SecretClientinstances (register as singleton in DI) — the SDK caches auth tokens internally
Summary
| Layer | Control | Purpose |
|---|---|---|
| Network | Private Endpoint + Firewall | Block unauthorized network paths |
| Identity | Managed Identity | Passwordless authentication |
| Authorization | Azure RBAC | Least-privilege access |
| Monitoring | Diagnostic Logs + KQL | Detect and alert on anomalies |
| Governance | Azure Policy | Enforce standards at scale |
By combining private endpoints, managed identities, RBAC, and monitoring, you achieve a defense-in-depth posture for your most sensitive assets — with no secrets stored in application code and no exposure to the public internet.