5 min read
Zero Trust Security in Azure: Trust Nothing, Verify Everything
Zero Trust security became the dominant security paradigm in 2021. The principle is simple: never trust, always verify. Let’s explore how to implement Zero Trust on Azure.
Zero Trust Principles
- Verify Explicitly: Always authenticate and authorize based on all available data points
- Least Privilege Access: Limit user access with just-in-time and just-enough-access
- Assume Breach: Minimize blast radius and segment access
Identity-Based Perimeter
Moving from network-based to identity-based security:
// Managed Identity for workloads
resource managedIdentity 'Microsoft.ManagedIdentity/userAssignedIdentities@2021-09-30-preview' = {
name: 'app-identity'
location: resourceGroup().location
}
// Key Vault with RBAC
resource keyVault 'Microsoft.KeyVault/vaults@2021-10-01' = {
name: 'app-keyvault'
location: resourceGroup().location
properties: {
tenantId: subscription().tenantId
sku: {
family: 'A'
name: 'standard'
}
enableRbacAuthorization: true
enablePurgeProtection: true
enableSoftDelete: true
networkAcls: {
defaultAction: 'Deny'
bypass: 'AzureServices'
virtualNetworkRules: [
{
id: subnet.id
}
]
}
}
}
// Grant identity access to secrets
resource secretsOfficer 'Microsoft.Authorization/roleAssignments@2021-04-01-preview' = {
scope: keyVault
name: guid(keyVault.id, managedIdentity.id, 'secrets-officer')
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', '4633458b-17de-408a-b874-0445c86b69e6') // Key Vault Secrets Officer
principalId: managedIdentity.properties.principalId
principalType: 'ServicePrincipal'
}
}
Conditional Access Policies
# Create Conditional Access Policy via Microsoft Graph
$policy = @{
displayName = "Require MFA for Azure Management"
state = "enabled"
conditions = @{
users = @{
includeUsers = @("All")
excludeUsers = @()
includeGroups = @()
excludeGroups = @("$breakGlassGroupId")
}
applications = @{
includeApplications = @("797f4846-ba00-4fd7-ba43-dac1f8f63013") # Azure Management
}
locations = @{
includeLocations = @("All")
excludeLocations = @("$trustedLocationId")
}
clientAppTypes = @("all")
signInRiskLevels = @()
userRiskLevels = @()
}
grantControls = @{
operator = "AND"
builtInControls = @("mfa", "compliantDevice")
}
sessionControls = @{
signInFrequency = @{
value = 4
type = "hours"
isEnabled = $true
}
}
}
Invoke-MgGraphRequest -Method POST -Uri "https://graph.microsoft.com/v1.0/identity/conditionalAccess/policies" -Body $policy
Network Micro-Segmentation
// Private Endpoints for all PaaS services
resource storagePrivateEndpoint 'Microsoft.Network/privateEndpoints@2021-05-01' = {
name: 'storage-pe'
location: resourceGroup().location
properties: {
subnet: {
id: privateEndpointSubnet.id
}
privateLinkServiceConnections: [
{
name: 'storage-pls'
properties: {
privateLinkServiceId: storageAccount.id
groupIds: ['blob']
}
}
]
}
}
resource privateDnsZone 'Microsoft.Network/privateDnsZones@2020-06-01' = {
name: 'privatelink.blob.core.windows.net'
location: 'global'
}
resource privateDnsZoneLink 'Microsoft.Network/privateDnsZones/virtualNetworkLinks@2020-06-01' = {
parent: privateDnsZone
name: 'vnet-link'
location: 'global'
properties: {
registrationEnabled: false
virtualNetwork: {
id: vnet.id
}
}
}
// Network Security Group with deny-all default
resource nsg 'Microsoft.Network/networkSecurityGroups@2021-05-01' = {
name: 'app-nsg'
location: resourceGroup().location
properties: {
securityRules: [
{
name: 'DenyAllInbound'
properties: {
priority: 4096
direction: 'Inbound'
access: 'Deny'
protocol: '*'
sourceAddressPrefix: '*'
destinationAddressPrefix: '*'
sourcePortRange: '*'
destinationPortRange: '*'
}
}
{
name: 'AllowAppGatewayInbound'
properties: {
priority: 100
direction: 'Inbound'
access: 'Allow'
protocol: 'Tcp'
sourceAddressPrefix: 'GatewayManager'
destinationAddressPrefix: '*'
sourcePortRange: '*'
destinationPortRange: '443'
}
}
]
}
}
Workload Identity Federation
// Using workload identity in Kubernetes
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
public class SecretService
{
private readonly SecretClient _secretClient;
public SecretService()
{
// Uses workload identity when running in AKS
var credential = new DefaultAzureCredential(new DefaultAzureCredentialOptions
{
// Exclude unnecessary credential types for faster auth
ExcludeEnvironmentCredential = true,
ExcludeSharedTokenCacheCredential = true,
ExcludeVisualStudioCredential = true,
ExcludeVisualStudioCodeCredential = true,
ExcludeAzureCliCredential = true,
ExcludeInteractiveBrowserCredential = true,
// Keep these for local dev and AKS
ManagedIdentityClientId = Environment.GetEnvironmentVariable("AZURE_CLIENT_ID")
});
var vaultUri = new Uri(Environment.GetEnvironmentVariable("KEY_VAULT_URI"));
_secretClient = new SecretClient(vaultUri, credential);
}
public async Task<string> GetSecretAsync(string secretName)
{
var secret = await _secretClient.GetSecretAsync(secretName);
return secret.Value.Value;
}
}
# AKS workload identity configuration
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-identity
namespace: production
annotations:
azure.workload.identity/client-id: "<managed-identity-client-id>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: api-service
namespace: production
spec:
template:
metadata:
labels:
azure.workload.identity/use: "true"
spec:
serviceAccountName: app-identity
containers:
- name: api
image: myacr.azurecr.io/api:latest
env:
- name: KEY_VAULT_URI
value: "https://app-keyvault.vault.azure.net/"
- name: AZURE_CLIENT_ID
valueFrom:
fieldRef:
fieldPath: metadata.annotations['azure.workload.identity/client-id']
Just-In-Time Access
# Request JIT access to VM
from azure.identity import DefaultAzureCredential
from azure.mgmt.security import SecurityCenter
from datetime import datetime, timedelta
def request_jit_access(
subscription_id: str,
resource_group: str,
vm_name: str,
requestor_email: str
):
credential = DefaultAzureCredential()
client = SecurityCenter(credential, subscription_id, "")
# Define JIT request
jit_request = {
"virtualMachines": [
{
"id": f"/subscriptions/{subscription_id}/resourceGroups/{resource_group}/providers/Microsoft.Compute/virtualMachines/{vm_name}",
"ports": [
{
"number": 22,
"allowedSourceAddressPrefix": "Internet",
"endTimeUtc": (datetime.utcnow() + timedelta(hours=3)).isoformat()
}
]
}
],
"justification": "Troubleshooting production issue"
}
# Submit request
result = client.jit_network_access_policies.initiate(
resource_group_name=resource_group,
jit_network_access_policy_name="default",
jit_network_access_policy_initiate_type=jit_request
)
return result
# Azure Policy for JIT enforcement
jit_policy = {
"mode": "All",
"policyRule": {
"if": {
"allOf": [
{
"field": "type",
"equals": "Microsoft.Compute/virtualMachines"
},
{
"field": "Microsoft.Compute/virtualMachines/storageProfile.osDisk.osType",
"equals": "Linux"
}
]
},
"then": {
"effect": "deployIfNotExists",
"details": {
"type": "Microsoft.Security/jitNetworkAccessPolicies",
"existenceCondition": {
"field": "Microsoft.Security/jitNetworkAccessPolicies/virtualMachines[*].id",
"contains": "[field('id')]"
}
}
}
}
}
Continuous Verification
# Implement continuous access evaluation
from azure.identity import DefaultAzureCredential
from azure.monitor.query import LogsQueryClient
from datetime import datetime, timedelta
def monitor_anomalous_access():
credential = DefaultAzureCredential()
client = LogsQueryClient(credential)
query = """
SigninLogs
| where TimeGenerated > ago(1h)
| where ResultType != 0
| summarize FailedAttempts = count() by UserPrincipalName, IPAddress, Location
| where FailedAttempts > 5
| project UserPrincipalName, IPAddress, Location, FailedAttempts, RiskLevel = iff(FailedAttempts > 10, 'High', 'Medium')
"""
result = client.query_workspace(
workspace_id="your-workspace-id",
query=query,
timespan=timedelta(hours=1)
)
for row in result.tables[0].rows:
if row['RiskLevel'] == 'High':
# Trigger automated response
revoke_user_sessions(row['UserPrincipalName'])
send_security_alert(row)
def revoke_user_sessions(user_principal_name: str):
"""Revoke all active sessions for a user"""
# Use Microsoft Graph to revoke sessions
pass
Zero Trust Checklist
| Control | Status | Implementation |
|---|---|---|
| Strong Authentication | Required | Azure AD + MFA |
| Device Compliance | Required | Intune + Conditional Access |
| Least Privilege | Required | PIM + RBAC |
| Micro-Segmentation | Required | Private Endpoints + NSGs |
| Encryption | Required | TLS 1.2+ + At-rest encryption |
| Monitoring | Required | Azure Sentinel + Defender |
| Automated Response | Recommended | Logic Apps + Playbooks |
Zero Trust in 2021 transitioned from concept to implementation. Azure provides the tools; the challenge is configuring them cohesively.