Encryption
TSC mapping: CC6.1 (Logical access security — data at rest), CC6.7 (Data transmission and removal controls)
Auditors require evidence that customer data is encrypted at rest and in transit, that encryption keys are managed with proper access controls, and that secrets are not stored in plaintext.
1. Azure Key Vault — Key Management
Azure Key Vault is the central service for managing cryptographic keys, secrets, and certificates. Use customer-managed keys (CMKs) in Key Vault for any data requiring strong access segregation and audit trails.
Create a Key Vault with soft-delete and purge protection
# Create a Key Vault (soft-delete and purge protection are mandatory for SOC 2)
az keyvault create \
--resource-group rg-security \
--name kv-soc2-prod \
--location eastus \
--sku premium \
--enable-soft-delete true \
--soft-delete-retention-days 90 \
--enable-purge-protection true \
--enable-rbac-authorization true
# Create a customer-managed key for encryption
az keyvault key create \
--vault-name kv-soc2-prod \
--name cmk-storage-prod \
--kty RSA \
--size 4096 \
--ops encrypt decrypt wrapKey unwrapKey
Enable automatic key rotation
# Set automatic rotation policy (rotate every 12 months, notify 30 days before expiry)
az keyvault key rotation-policy update \
--vault-name kv-soc2-prod \
--name cmk-storage-prod \
--value '{
"lifetimeActions": [
{
"trigger": {"timeBeforeExpiry": "P30D"},
"action": {"type": "Notify"}
},
{
"trigger": {"timeAfterCreate": "P12M"},
"action": {"type": "Rotate"}
}
],
"attributes": {
"expiryTime": "P13M"
}
}'
Key Vault RBAC — least-privilege access
# Grant Key Vault Secrets User to an application managed identity
az role assignment create \
--role "Key Vault Secrets User" \
--assignee-object-id <managed-identity-principal-id> \
--scope /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.KeyVault/vaults/kv-soc2-prod
# Grant Key Vault Administrator to the security team only
az role assignment create \
--role "Key Vault Administrator" \
--assignee-object-id <security-group-id> \
--scope /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.KeyVault/vaults/kv-soc2-prod
Verify key rotation status:
az keyvault key show \
--vault-name kv-soc2-prod \
--name cmk-storage-prod \
--query "[attributes.created,attributes.updated,attributes.expires]" \
--output table
Reference: Azure Key Vault documentation → · Key Vault key rotation →
2. Azure Storage — Encryption at Rest
Azure Storage always encrypts data at rest with AES-256 (service-managed by default). For SOC 2, move to customer-managed keys for auditable key control.
Enable CMK encryption on a Storage Account
# Assign a managed identity to the storage account (needed to access Key Vault)
az storage account update \
--resource-group rg-prod \
--name stprod \
--assign-identity
STORAGE_PRINCIPAL=$(az storage account show \
--resource-group rg-prod \
--name stprod \
--query identity.principalId -o tsv)
# Grant the storage account identity access to the Key Vault key
az role assignment create \
--role "Key Vault Crypto Service Encryption User" \
--assignee-object-id $STORAGE_PRINCIPAL \
--scope /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.KeyVault/vaults/kv-soc2-prod
# Configure the storage account to use the CMK
az storage account update \
--resource-group rg-prod \
--name stprod \
--encryption-key-source Microsoft.Keyvault \
--encryption-key-vault https://kv-soc2-prod.vault.azure.net/ \
--encryption-key-name cmk-storage-prod \
--encryption-key-version "" # Empty string = use latest version automatically
Block public access on all Storage Accounts
# Disable public blob access at the storage account level
az storage account update \
--resource-group rg-prod \
--name stprod \
--allow-blob-public-access false
# Enforce HTTPS-only (secure transfer)
az storage account update \
--resource-group rg-prod \
--name stprod \
--https-only true
# Set minimum TLS version
az storage account update \
--resource-group rg-prod \
--name stprod \
--min-tls-version TLS1_2
Verify encryption configuration
az storage account show \
--resource-group rg-prod \
--name stprod \
--query "{Encryption:encryption.keySource, HTTPS:enableHttpsTrafficOnly, PublicAccess:allowBlobPublicAccess, MinTLS:minimumTlsVersion}"
Reference: Azure Storage encryption → · Customer-managed keys →
3. Azure Managed Disks — Encryption at Rest
Azure Managed Disks are encrypted by default (platform-managed key). For SOC 2, use a Disk Encryption Set with a CMK to bring key control into your Key Vault.
# Create a Disk Encryption Set backed by your CMK
KEY_ID=$(az keyvault key show \
--vault-name kv-soc2-prod \
--name cmk-storage-prod \
--query key.kid -o tsv)
az disk-encryption-set create \
--resource-group rg-prod \
--name des-prod \
--location eastus \
--key-url $KEY_ID \
--source-vault /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.KeyVault/vaults/kv-soc2-prod
# Grant the Disk Encryption Set identity access to the Key Vault
DES_PRINCIPAL=$(az disk-encryption-set show \
--resource-group rg-prod \
--name des-prod \
--query identity.principalId -o tsv)
az role assignment create \
--role "Key Vault Crypto Service Encryption User" \
--assignee-object-id $DES_PRINCIPAL \
--scope /subscriptions/<sub>/resourceGroups/rg-security/providers/Microsoft.KeyVault/vaults/kv-soc2-prod
# Create a VM with CMK-encrypted disk
az vm create \
--resource-group rg-prod \
--name vm-prod \
--image UbuntuLTS \
--size Standard_B2ms \
--os-disk-encryption-set /subscriptions/<sub>/resourceGroups/rg-prod/providers/Microsoft.Compute/diskEncryptionSets/des-prod \
--admin-username azureadmin \
--generate-ssh-keys
Audit unencrypted disks:
az disk list \
--query "[?encryption.type!='EncryptionAtRestWithCustomerKey'].[name,resourceGroup,diskState,encryption.type]" \
--output table
Reference: Disk Encryption Sets → · Server-side encryption for Azure Disks →
4. Azure SQL — Transparent Data Encryption with CMK
Azure SQL Database enables TDE (AES-256) by default with a service-managed key. Use a Customer-Managed Key (BYOK) for auditable key control.
# Configure TDE with a CMK on Azure SQL
az sql server tde-key set \
--resource-group rg-prod \
--server sql-prod \
--server-key-type AzureKeyVault \
--kid https://kv-soc2-prod.vault.azure.net/keys/cmk-storage-prod/<key-version>
# Enable TDE on the server
az sql server update \
--resource-group rg-prod \
--name sql-prod \
--set "properties.encryptionProtector.serverKeyName=<vault>_<key>_<version>"
# Verify TDE status on all databases
az sql db tde show \
--resource-group rg-prod \
--server sql-prod \
--database prod-db \
--query "{Status:status,DatabaseName:name}"
Enable Advanced Threat Protection:
az sql server advanced-threat-protection-setting update \
--resource-group rg-prod \
--server sql-prod \
--state Enabled
# Enable SQL Auditing to Log Analytics
az sql server audit-policy update \
--resource-group rg-prod \
--server sql-prod \
--state Enabled \
--log-analytics-workspace-resource-id $WORKSPACE_ID \
--log-analytics-target-state Enabled
Reference: Azure SQL TDE → · Azure SQL BYOK TDE →
5. Azure Key Vault — Secret and Certificate Management
Store all connection strings, API keys, and certificates in Key Vault. Never store secrets in environment variables, app config files, or source code.
# Store a database connection string as a Key Vault secret
az keyvault secret set \
--vault-name kv-soc2-prod \
--name prod-db-connection-string \
--value "Server=sql-prod.database.windows.net;Database=prod;Authentication=Active Directory Managed Identity"
# Import a TLS certificate into Key Vault
az keyvault certificate import \
--vault-name kv-soc2-prod \
--name app-tls-cert \
--file ./app-cert.pfx \
--password <pfx-password>
# Set auto-rotation policy for a certificate
az keyvault certificate get-default-policy | \
jq '.lifetimeActions = [{"trigger":{"daysBeforeExpiry":30},"action":{"actionType":"AutoRenew"}}]' > policy.json
az keyvault certificate create \
--vault-name kv-soc2-prod \
--name app-tls-cert-managed \
--policy @policy.json
Access secrets from application code via managed identity (no credentials stored):
from azure.identity import DefaultAzureCredential
from azure.keyvault.secrets import SecretClient
credential = DefaultAzureCredential()
client = SecretClient(vault_url="https://kv-soc2-prod.vault.azure.net/", credential=credential)
secret = client.get_secret("prod-db-connection-string")
# Never log secret.value
Reference: Azure Key Vault secrets → · Key Vault certificates →
6. Other Services — Encryption Checklist
| Service | Control | How to verify |
|---|---|---|
| Azure Cosmos DB | CMK encryption at rest | az cosmosdb show --query "keyVaultKeyUri" |
| Azure Cache for Redis | TLS 1.2+ enforced | az redis show --query "minimumTlsVersion" |
| Azure Service Bus | CMK encryption | Portal → Service Bus namespace → Encryption |
| Azure Event Hub | CMK encryption | az eventhubs namespace show --query "encryption" |
| Azure Kubernetes Service | etcd encrypted with CMK | az aks show --query "diskEncryptionSetID" |
| Azure Backup | Encryption with CMK | Backup vault → Properties → Encryption settings |
| App Service (env vars) | Use Key Vault references | Replace values with @Microsoft.KeyVault(...) references |
SOC 2 Evidence for Encryption
| Evidence item | How to collect |
|---|---|
| Key Vault key list with rotation policy | az keyvault key list --vault-name <name> + key rotation-policy show |
| Storage account encryption configuration | az storage account show --query encryption |
| VM disk encryption status | az disk list --query "[*].[name,encryption.type]" |
| Azure SQL TDE status per database | az sql db tde show |
| Key Vault access log (secret reads) | KQL: `AzureDiagnostics |
| Defender for Cloud encryption recommendations | Defender for Cloud → Recommendations → Encrypt data at rest |
| Azure Policy compliance (encryption policies) | az policy state list --query "[?complianceState=='NonCompliant']" |