Identity-first security treats identity as the primary perimeter and uses strong policy, federated workload trust, and secretless runtime credentials to reduce attack surface. On Azure, this converges on three pillars: Conditional Access for policy enforcement, Workload Identity for federating non-human workloads, and Managed Identities for first-party resource access. The practical outcome is fewer long-lived secrets, consistent controls across humans and services, and measurable blast-radius reduction.

Conditional Access enforces contextual access decisions for human identities and, increasingly, for service principals. Effective programs start with a small set of guardrails that are easy to reason about: require phishing-resistant MFA for privileged roles, block legacy protocols, and enforce compliant or hybrid-joined devices for administration. Always maintain at least two emergency access accounts excluded from policy and stored offline to avoid tenant lockout; verify they work quarterly, then leave them untouched. Treat policy rollout as code, use report-only mode first, and avoid overlapping policies that produce ambiguous results.

Managed Identities eliminate secrets for Azure-to-Azure calls. System-assigned identities are lifecycle-coupled to a single resource and reduce drift; user-assigned identities are reusable, support key rotation independence, and are easier to target with role assignments and diagnostics. Default to system-assigned for single-resource scenarios such as a Function accessing one Key Vault, and prefer user-assigned when multiple resources share a trust boundary or you plan to rotate identities without redeploying compute. Keep role assignments at the narrowest possible scope and prefer data-plane roles like Storage Blob Data Reader over control-plane roles like Reader whenever the SDK allows it.

Workload Identity addresses the non-human gap for Kubernetes and CI systems by replacing secrets and node-level agents with OIDC federation between the workload issuer and Entra ID. On AKS, enable the cluster OIDC issuer, map a Kubernetes service account to a user-assigned managed identity using a federated credential, then let the pod acquire tokens with standard Azure SDKs. This produces short-lived, audience-bound tokens, eliminates secret distribution, and moves trust to configurable policy in Entra ID.

# Create Azure user-assigned managed identity and assign Storage Blob Data Reader role
RG="rg-sec-west"
LOC="westeurope"
UAMI="uami-app-storage"
SA="stappabcd1234"
az group create -n "$RG" -l "$LOC"
az identity create -g "$RG" -n "$UAMI"
UAMI_CLIENT_ID=$(az identity show -g "$RG" -n "$UAMI" --query clientId -o tsv)
UAMI_PRINCIPAL_ID=$(az identity show -g "$RG" -n "$UAMI" --query principalId -o tsv)
SCOPE="/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/$RG/providers/Microsoft.Storage/storageAccounts/$SA"
az role assignment create --assignee-object-id "$UAMI_PRINCIPAL_ID" --role "Storage Blob Data Reader" --scope "$SCOPE"
echo "UAMI clientId: $UAMI_CLIENT_ID"

For AKS, configure the cluster OIDC issuer and workload identity once, then bind the user-assigned identity to a service account subject. The audience must remain the default value used by Azure AD token exchange unless you have a clear reason to change it, because SDKs and tooling assume it.

# Configure AKS cluster for Workload Identity and create OIDC federated credential
RG="rg-sec-west"
AKS="aks-sec-cluster"
UAMI="uami-app-storage"
NS="payments"
SA="api"
az aks update -g "$RG" -n "$AKS" --enable-oidc-issuer --enable-workload-identity
OIDC_ISSUER=$(az aks show -g "$RG" -n "$AKS" --query "oidcIssuerProfile.issuerUrl" -o tsv)
UAMI_CLIENT_ID=$(az identity show -g "$RG" -n "$UAMI" --query clientId -o tsv)
az identity federated-credential create \
  --identity-name "$UAMI" \
  --resource-group "$RG" \
  --name "${NS}-${SA}" \
  --issuer "$OIDC_ISSUER" \
  --subject "system:serviceaccount:${NS}:${SA}" \
  --audiences "api://AzureADTokenExchange"
echo "Federation ready for subject system:serviceaccount:${NS}:${SA}"

On the cluster side, use a service account label and annotation so the pod uses federated identity rather than a secret. Keep the identity binding at namespace or workload granularity to limit lateral movement. Avoid sharing one identity across unrelated teams; rotate client IDs only as part of planned maintenance.

# Kubernetes ServiceAccount configured for Azure Workload Identity federation
apiVersion: v1
kind: ServiceAccount
metadata:
  name: api
  namespace: payments
  labels:
    azure.workload.identity/use: "true"
  annotations:
    azure.workload.identity/client-id: "<uami-client-id>"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: payments-api
  namespace: payments
spec:
  replicas: 2
  selector:
    matchLabels: { app: payments-api }
  template:
    metadata:
      labels: { app: payments-api }
    spec:
      serviceAccountName: api
      containers:
      - name: api
        image: ghcr.io/org/payments-api:1.4.2
        env:
        - name: AZURE_CLIENT_ID
          valueFrom:
            fieldRef:
              fieldPath: metadata.annotations['azure.workload.identity/client-id']

Conditional Access should be delivered through code and tested in report-only mode before enforcement. The following payload illustrates a policy that targets privileged roles, requires strong MFA, and excludes two emergency accounts. The key tactic is constraining scope to role-based groups, not “All users,” and moving to enforcement only after sign-in telemetry shows acceptable failure rates.

# Azure Conditional Access policy requiring phishing-resistant MFA for privileged roles
{
  "displayName": "Privileged roles require phishing-resistant MFA",
  "state": "enabledForReportingButNotEnforced",
  "conditions": {
    "users": {
      "includeGroups": ["<aad-group-id-privileged-roles>"],
      "excludeUsers": ["<breakglass-upn-1>", "<breakglass-upn-2>"]
    },
    "applications": { "includeApplications": ["All"] },
    "clientAppTypes": ["exchangeActiveSync", "browser", "mobileAppsAndDesktopClients"],
    "signInRiskLevels": ["medium", "high"],
    "locations": { "excludeLocations": ["<named-location-hq>"] }
  },
  "grantControls": {
    "operator": "OR",
    "builtInControls": ["mfa"],
    "authenticationStrength": { "id": "c19f0f0a-0000-0000-0000-000000000000" }
  },
  "sessionControls": { "signInFrequency": { "value": 12, "type": "hours", "isEnabled": true } }
}

For workloads, align Conditional Access policy with your trust boundaries rather than trying to reproduce network perimeters. If your cluster control plane and CI runners operate from known egress locations, define named locations and restrict service principal sign-ins to those ranges. If you use multiple tenants, create per-tenant policies and assign them to application registrations rather than broad service principal scopes; this reduces the chance of accidentally blocking third-party integrations that rely on the same app.

Observability closes the loop. Use sign-in logs for both humans and service principals to validate policy impact and catch misconfigurations early. Track conditional access status, failure reasons, and unincluded clients that still use legacy protocols. The query below helps identify workload identities that are being blocked or failing token acquisition after rollout.

# KQL query to identify service principal sign-in failures and conditional access issues
AADServicePrincipalSignInLogs
| where TimeGenerated > ago(24h)
| project TimeGenerated, AppId, AppDisplayName, IPAddress, ConditionalAccessStatus, Status, ResourceDisplayName
| summarize failures = countif(ConditionalAccessStatus != "notApplied" and Status.errorCode != 0),
           total = count()
         by AppId, AppDisplayName
| extend failureRate = todouble(failures) / todouble(total)
| where failureRate > 0.05
| order by failureRate desc, total desc

For least-privilege enforcement, treat role assignment creation as a policy-controlled operation. Deny overly broad roles to managed identities at subscription scope and require justification for Contributor. The following Azure Policy definition rejects Owner assignments to managed identities and keeps drift out of your platform.

# Azure Policy definition preventing Owner role assignment to managed identities
{
  "properties": {
    "displayName": "Deny Owner role to managed identities",
    "mode": "All",
    "policyRule": {
      "if": {
        "allOf": [
          { "field": "type", "equals": "Microsoft.Authorization/roleAssignments" },
          { "field": "Microsoft.Authorization/roleAssignments/roleDefinitionId",
            "like": "*providers/Microsoft.Authorization/roleDefinitions/8e3af657-a8ff-443c-a75c-2fe8c4bcb635" },
          { "field": "Microsoft.Authorization/roleAssignments/principalType", "equals": "ServicePrincipal" },
          { "field": "Microsoft.Authorization/roleAssignments/principalIdType", "equals": "ObjectId" }
        ]
      },
      "then": { "effect": "deny" }
    }
  }
}

Infrastructure as code keeps identity consistent across environments. Codify user-assigned identities, federated credentials, and role assignments with clear variable boundaries for tenant, issuer, and subject. Prefer immutable naming for federated credentials because downstream systems may cache subjects. The snippet below wires an AKS service account to a managed identity using Terraform.

# Terraform configuration for user-assigned managed identity with federated credentials
terraform {
  required_providers {
    azurerm = {
      source  = "hashicorp/azurerm"
      version = "~> 3.110"
    }
  }
}

provider "azurerm" {
  features {}
}

resource "azurerm_user_assigned_identity" "uami" {
  name                = "uami-app-storage"
  resource_group_name = var.resource_group
  location            = var.location
}

resource "azurerm_federated_identity_credential" "fic" {
  name                = "${var.k8s_namespace}-${var.k8s_service_account}"
  resource_group_name = var.resource_group
  parent_id           = azurerm_user_assigned_identity.uami.id
  audience            = ["api://AzureADTokenExchange"]
  issuer              = var.oidc_issuer_url
  subject             = "system:serviceaccount:${var.k8s_namespace}:${var.k8s_service_account}"
}

resource "azurerm_role_assignment" "blob_reader" {
  scope                = var.storage_account_id
  role_definition_name = "Storage Blob Data Reader"
  principal_id         = azurerm_user_assigned_identity.uami.principal_id
}

Teams migrating from AAD Pod Identity should plan a staged cutover. Enable OIDC and create federated credentials in parallel, deploy new service accounts per workload, and shift only those deployments that pass smoke tests. Avoid dual-writing to both identity systems within the same pod; instead, run two replica sets side by side, validate telemetry and access paths, then remove the old DaemonSet from the cluster. This cutover posture prevents node-level credential leakage and confirms your policy assumptions before full rollout.

For human access, keep Privileged Identity Management at the center of your story. Require elevation for high-risk activities, apply stronger authentication strength during elevation, and rely on Conditional Access to enforce session limits. Use continuous access evaluation to quickly revoke tokens in response to critical events, and measure drift by reviewing expired but still active refresh tokens alongside device compliance posture. When exceptions appear, capture them as code with explicit expiry dates rather than out-of-band approvals.

The combined pattern is straightforward: authenticate humans with strong, context-aware policies, authenticate workloads through federation rather than secrets, and authorize via least-privilege role assignments bound to managed identities. With policy defined as code, federation mapped to explicit subjects, and role scopes kept tight, identity becomes a predictable control surface. The result is fewer credentials to rotate, fewer places to misconfigure, and a platform that responds to risk signals without breaking delivery.

References

  1. Azure AD Conditional Access Documentation https://learn.microsoft.com/en-us/entra/identity/conditional-access/

  2. Azure Workload Identity for AKS https://learn.microsoft.com/en-us/azure/aks/workload-identity-overview

  3. Azure Managed Identities Documentation https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/

  4. Azure RBAC Best Practices https://learn.microsoft.com/en-us/azure/role-based-access-control/best-practices

  5. OIDC Federation in Azure https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation

  6. Zero Trust Security Model https://learn.microsoft.com/en-us/security/zero-trust/