DevOps on Azure, part 4: VNet, managed identity, and Key Vault — the security skeleton
Part 4from the DevOps on Azure series · 6 parts in all
Part 4: the security skeleton. Azure's two signature strengths — managed identity and Key Vault — let you reach a state AWS reaches with more moving parts: zero credentials in code, zero public database endpoints, zero standing human access.
VNet and NSGs: same concepts, new names
The three-subnet pattern is identical to the AWS series: public subnet for Application Gateway/Front Door, app subnet for your services, isolated subnet for data. Network Security Groups are the security groups of this cloud:
# NSG chain: db <- app <- internet (via gateway only)
az network nsg create -g maplecart-rg -n nsg-app
az network nsg rule create -g maplecart-rg --nsg-name nsg-app -n allow-gateway \
--priority 100 --direction Inbound --access Allow \
--protocol Tcp --destination-port-ranges 8080 \
--source-address-prefixes 10.0.1.0/24 # the gateway subnet only
az network nsg create -g maplecart-rg -n nsg-db
az network nsg rule create -g maplecart-rg --nsg-name nsg-db -n allow-app \
--priority 100 --direction Inbound --access Allow \
--protocol Tcp --destination-port-ranges 1433 \
--source-address-prefixes 10.0.2.0/24 # the app subnet only
Managed identity: the death of connection strings with passwords
An App Service, Function, or AKS pod can carry a system-assigned managed identity — an Entra identity whose credentials Azure rotates automatically. Grant it database access, and the app connects with its identity:
# Enable identity on the web app and grant it SQL access
az webapp identity assign -g maplecart-rg -n maplecart-web # prints principalId
az sql server ad-admin create -g maplecart-rg -s maplecart-sql \
--display-name maplecart-web-identity --object-id <principalId>
# Grant it Key Vault access (RBAC, not access policies - the modern way)
az keyvault set-policy is deprecated: use role assignments
az role assignment create --assignee <principalId> \
--role "Key Vault Secrets User" \
--scope <keyvault-resource-id>
The app code from part 3 (ActiveDirectoryDefault) picks this up with zero
configuration. There are no secrets to leak because there are no secrets — the identity
*is* the credential, issued fresh per connection.
Key Vault: secrets, keys, certificates — with an audit trail
az keyvault create -g maplecart-rg -n maplecart-kv --enable-rbac-authorization
az keyvault secret set --vault-name maplecart-kv --name stripe-api-key --file ./stripe.key
# Rotation with an expiry policy; Event Grid can alert before expiry
az keyvault secret set-attributes --vault-name maplecart-kv --name stripe-api-key \
--expires "$(date -d '+90 days' -u +%Y-%m-%dT%H:%MZ)"
Every read is logged to Azure Monitor — who fetched which secret, when, from which service. In an incident, that log is the difference between "we think no one took anything" and "here is the complete access list."
The skeleton assembled
VNet segmentation (NSG chain), workload identity (managed identities), and secret management (Key Vault with RBAC + audit) — the same three layers as the AWS part 4, mapped 1:1: security groups ↔ NSGs, IAM roles ↔ managed identities, Secrets Manager ↔ Key Vault. Once you see the mapping, the cloud you know transfers directly. Next: pipelines and observability.