Skip to content

Security Migration: RBAC, Pod Security, Identity, and Compliance

Status: Authored 2026-04-30 Audience: Security engineers and platform architects migrating Kubernetes security configurations to AKS. Scope: RBAC mapping, Pod Security Standards, Entra Workload Identity, Azure Key Vault Secrets Provider, Defender for Containers, image scanning, and policy enforcement.


1. Authentication migration

From kubeconfig certificates to Entra ID

Self-managed Kubernetes clusters typically use client certificates or static tokens for authentication. AKS integrates with Entra ID natively.

# Enable Entra ID integration (at cluster creation)
az aks create \
  --enable-aad \
  --aad-admin-group-object-ids "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
  --enable-azure-rbac \
  --disable-local-accounts \
  ...

# Or enable on existing cluster
az aks update \
  --resource-group rg-aks-prod \
  --name aks-prod-eastus2 \
  --enable-aad \
  --aad-admin-group-object-ids "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"

Authentication method mapping

Source method AKS equivalent Notes
Client certificates (kubeconfig) Entra ID authentication (kubelogin) az aks get-credentials generates Entra ID-based kubeconfig
Static tokens (token file) Entra ID tokens Static tokens should never be used in production
OIDC provider (Dex, Keycloak) Entra ID (native) Remove OIDC proxy. Entra ID is the native OIDC provider
LDAP/AD integration (via OIDC) Entra ID (direct) Entra ID replaces the LDAP/AD → OIDC bridge
Service account tokens (in-cluster) Entra Workload Identity Service accounts remain for in-cluster auth; Workload Identity for Azure service access

Conditional Access for kubectl

Apply Entra Conditional Access policies to Kubernetes API access:

  • Require MFA for cluster-admin operations
  • Require compliant device for production cluster access
  • Block access from untrusted networks (IP-based conditions)
  • Require specific authentication methods (passwordless, FIDO2)

2. RBAC migration

Kubernetes RBAC with Entra ID groups

Map on-prem Kubernetes RBAC bindings to Entra ID groups:

# Before (on-prem): RBAC binding to local user
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
    name: cluster-admin-binding
subjects:
    - kind: User
      name: admin@internal.gov
      apiGroup: rbac.authorization.k8s.io
roleRef:
    kind: ClusterRole
    name: cluster-admin
    apiGroup: rbac.authorization.k8s.io


# After (AKS): RBAC binding to Entra ID group
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
    name: cluster-admin-binding
subjects:
    - kind: Group
      name: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Entra ID group Object ID
      apiGroup: rbac.authorization.k8s.io
roleRef:
    kind: ClusterRole
    name: cluster-admin
    apiGroup: rbac.authorization.k8s.io

Azure RBAC for Kubernetes (alternative)

Azure RBAC for Kubernetes maps Azure roles to Kubernetes permissions, managed entirely in Azure:

Azure built-in role Kubernetes equivalent Use case
Azure Kubernetes Service RBAC Cluster Admin cluster-admin Full cluster administration
Azure Kubernetes Service RBAC Admin admin (namespaced) Namespace-level administration
Azure Kubernetes Service RBAC Writer edit (namespaced) Deploy and manage workloads
Azure Kubernetes Service RBAC Reader view (namespaced) Read-only access to resources
# Assign Azure RBAC role for K8s access
az role assignment create \
  --role "Azure Kubernetes Service RBAC Writer" \
  --assignee-object-id "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" \
  --scope "/subscriptions/.../resourceGroups/rg-aks-prod/providers/Microsoft.ContainerService/managedClusters/aks-prod-eastus2/namespaces/production"

Namespace-level RBAC migration pattern

# Namespace-scoped role for application team
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
    name: app-team-role
    namespace: production
rules:
    - apiGroups: ["", "apps", "batch", "networking.k8s.io"]
      resources:
          [
              "deployments",
              "services",
              "pods",
              "configmaps",
              "jobs",
              "cronjobs",
              "ingresses",
          ]
      verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
    - apiGroups: [""]
      resources: ["secrets"]
      verbs: ["get", "list", "watch"] # No create/update for secrets (managed in Key Vault)
    - apiGroups: [""]
      resources: ["pods/log", "pods/exec"]
      verbs: ["get", "create"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
    name: app-team-binding
    namespace: production
subjects:
    - kind: Group
      name: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Entra ID group: App Team Production
      apiGroup: rbac.authorization.k8s.io
roleRef:
    kind: Role
    name: app-team-role
    apiGroup: rbac.authorization.k8s.io

3. Pod Security Standards (replacing PSP and SCC)

Pod Security Policy to Pod Security Standards migration

Pod Security Policies (PSP) were deprecated in K8s 1.21 and removed in 1.25. AKS uses Pod Security Standards (PSS) with Pod Security Admission (PSA).

PSP / SCC level PSS level Description
privileged / SCC privileged privileged Unrestricted; system workloads only
restricted with some relaxation / SCC anyuid baseline Prevents known privilege escalations; allows most workloads
restricted / SCC restricted restricted Heavily restricted; best security posture

Applying PSS to namespaces

apiVersion: v1
kind: Namespace
metadata:
    name: production
    labels:
        # Enforce restricted PSS (block non-compliant pods)
        pod-security.kubernetes.io/enforce: restricted
        pod-security.kubernetes.io/enforce-version: latest
        # Audit and warn on restricted violations
        pod-security.kubernetes.io/audit: restricted
        pod-security.kubernetes.io/warn: restricted

OpenShift SCC to PSS mapping

OpenShift SCC PSS level Migration notes
privileged privileged System workloads; apply to kube-system only
anyuid baseline Most common. Remove runAsUser: RunAsAny; set explicit runAsUser in pod spec
restricted restricted Direct mapping. May need seccompProfile and runAsNonRoot in pod spec
hostaccess privileged (restricted to specific pods) Limit to DaemonSets that need host access; use Gatekeeper exceptions
hostmount-anyuid baseline + volume exceptions Use Gatekeeper to allow specific hostPath mounts
nonroot restricted Direct mapping
hostnetwork privileged (restricted to specific pods) Limit to Ingress controllers and monitoring DaemonSets

Common pod security adjustments

# Before: Pod relying on SCC anyuid (running as root)
spec:
  containers:
    - name: app
      image: legacy-app:latest  # Runs as root by default

# After: Pod compliant with PSS restricted
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    runAsGroup: 1000
    fsGroup: 1000
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: csainaboxacr.azurecr.io/legacy-app:latest
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop:
            - ALL

4. Entra Workload Identity

Entra Workload Identity replaces pod-managed identity (aad-pod-identity) and service account token-based authentication to Azure services.

Setup

# 1. Create managed identity
az identity create \
  --name umi-app-prod \
  --resource-group rg-aks-prod

# 2. Create federated credential
az identity federated-credential create \
  --name fc-app-prod \
  --identity-name umi-app-prod \
  --resource-group rg-aks-prod \
  --issuer $(az aks show -g rg-aks-prod -n aks-prod-eastus2 --query oidcIssuerProfile.issuerUrl -o tsv) \
  --subject system:serviceaccount:production:app-sa \
  --audience api://AzureADTokenExchange

# 3. Grant Azure permissions to managed identity
az role assignment create \
  --role "Storage Blob Data Contributor" \
  --assignee $(az identity show -g rg-aks-prod -n umi-app-prod --query principalId -o tsv) \
  --scope /subscriptions/.../resourceGroups/rg-storage/providers/Microsoft.Storage/storageAccounts/stcsainbox

# 4. Create Kubernetes service account with Workload Identity annotation
kubectl apply -f - << 'EOF'
apiVersion: v1
kind: ServiceAccount
metadata:
  name: app-sa
  namespace: production
  annotations:
    azure.workload.identity/client-id: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
EOF

# 5. Deploy pod with Workload Identity
kubectl apply -f - << 'EOF'
apiVersion: apps/v1
kind: Deployment
metadata:
  name: app
  namespace: production
spec:
  template:
    metadata:
      labels:
        azure.workload.identity/use: "true"
    spec:
      serviceAccountName: app-sa
      containers:
        - name: app
          image: csainaboxacr.azurecr.io/app:latest
          env:
            - name: AZURE_STORAGE_ACCOUNT
              value: stcsainbox
EOF

Identity migration mapping

Source identity method AKS equivalent Migration path
K8s Secret with Azure credentials Workload Identity Remove Secret; configure federated credential
aad-pod-identity (NMI) Workload Identity Remove NMI DaemonSet; use federated credentials
Service account token + custom auth Workload Identity Replace custom auth code with DefaultAzureCredential SDK
On-prem Vault (HashiCorp) Azure Key Vault + Workload Identity Migrate secrets to Key Vault; authenticate with Workload Identity
AWS IRSA (if multi-cloud) Workload Identity Azure equivalent of AWS IRSA

5. Azure Key Vault Secrets Provider

Installation and configuration

# Enable Key Vault Secrets Provider addon
az aks enable-addons \
  --resource-group rg-aks-prod \
  --name aks-prod-eastus2 \
  --addons azure-keyvault-secrets-provider \
  --enable-secret-rotation \
  --rotation-poll-interval 2m

Mount secrets from Key Vault

# SecretProviderClass
apiVersion: secrets-store.csi.x-k8s.io/v1
kind: SecretProviderClass
metadata:
    name: kv-secrets
    namespace: production
spec:
    provider: azure
    parameters:
        usePodIdentity: "false"
        useVMManagedIdentity: "false"
        clientID: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" # Workload Identity client ID
        keyvaultName: kv-csa-prod
        tenantId: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
        objects: |
            array:
              - |
                objectName: db-password
                objectType: secret
              - |
                objectName: api-key
                objectType: secret
              - |
                objectName: tls-cert
                objectType: secret
    secretObjects:
        - secretName: app-secrets
          type: Opaque
          data:
              - objectName: db-password
                key: DB_PASSWORD
              - objectName: api-key
                key: API_KEY
---
# Pod mounting Key Vault secrets
apiVersion: apps/v1
kind: Deployment
metadata:
    name: app
    namespace: production
spec:
    template:
        metadata:
            labels:
                azure.workload.identity/use: "true"
        spec:
            serviceAccountName: app-sa
            containers:
                - name: app
                  image: csainaboxacr.azurecr.io/app:latest
                  envFrom:
                      - secretRef:
                            name: app-secrets
                  volumeMounts:
                      - name: secrets-store
                        mountPath: "/mnt/secrets"
                        readOnly: true
            volumes:
                - name: secrets-store
                  csi:
                      driver: secrets-store.csi.k8s.io
                      readOnly: true
                      volumeAttributes:
                          secretProviderClass: kv-secrets

6. Defender for Containers

Enable Defender for Containers

# Enable at subscription level
az security pricing create \
  --name Containers \
  --tier standard

# Enable on AKS cluster
az aks update \
  --resource-group rg-aks-prod \
  --name aks-prod-eastus2 \
  --enable-defender

Defender capabilities

Capability Description Replaces
Vulnerability assessment Scans ACR images for CVEs; powered by Microsoft Defender Vulnerability Management Trivy, Clair, Anchore
Runtime threat detection Detects suspicious container behavior (crypto mining, reverse shells, privilege escalation) Falco, Sysdig
Admission control Blocks deployment of images with critical vulnerabilities OPA Gatekeeper admission webhooks
Binary drift detection Detects executables running in containers that are not part of the original image Custom runtime security
Network analytics Maps container network flows and detects anomalies Network monitoring tools

Image scanning workflow

flowchart LR
    A[Developer pushes to ACR] --> B[Defender scans image]
    B --> C{Vulnerabilities?}
    C -->|Critical/High| D[Block deployment via Azure Policy]
    C -->|Medium/Low| E[Deploy with warning]
    C -->|None| F[Deploy clean]
    D --> G[Developer remediates]
    G --> A

7. Azure Policy for Kubernetes (Gatekeeper)

Built-in policy initiatives

Initiative Description Policies
Kubernetes cluster pod security baseline Enforces PSS baseline level 8 policies
Kubernetes cluster pod security restricted Enforces PSS restricted level 12 policies
CIS Microsoft Azure Foundations Benchmark CIS benchmark for AKS 20+ policies
NIST SP 800-53 Rev 5 NIST controls for containers 30+ policies

Assign policy initiative

# Assign pod security restricted initiative
az policy assignment create \
  --name "aks-pod-security-restricted" \
  --display-name "AKS Pod Security Restricted" \
  --policy-set-definition "42b8ef37-b724-4e24-bbc8-7a7708edfe00" \
  --scope "/subscriptions/.../resourceGroups/rg-aks-prod/providers/Microsoft.ContainerService/managedClusters/aks-prod-eastus2" \
  --params '{"effect": {"value": "deny"}}'

Custom policy examples

# Require approved container registries only
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
    name: k8sallowedregistries
spec:
    crd:
        spec:
            names:
                kind: K8sAllowedRegistries
            validation:
                openAPIV3Schema:
                    type: object
                    properties:
                        registries:
                            type: array
                            items:
                                type: string
    targets:
        - target: admission.k8s.gatekeeper.sh
          rego: |
              package k8sallowedregistries
              violation[{"msg": msg}] {
                container := input.review.object.spec.containers[_]
                not startswith(container.image, input.parameters.registries[_])
                msg := sprintf("Container image '%v' is not from an approved registry", [container.image])
              }
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRegistries
metadata:
    name: require-approved-registries
spec:
    match:
        kinds:
            - apiGroups: [""]
              kinds: ["Pod"]
        excludedNamespaces:
            - kube-system
            - gatekeeper-system
    parameters:
        registries:
            - "csainaboxacr.azurecr.io/"
            - "mcr.microsoft.com/"

8. Image provenance and signing

Notation (Notary v2) for image signing

# Install Notation
az acr notation install

# Sign an image
notation sign \
  --signature-format cose \
  csainaboxacr.azurecr.io/app:v2.3.1

# Verify an image
notation verify \
  csainaboxacr.azurecr.io/app:v2.3.1

# Enforce signed images via Ratify on AKS
helm install ratify ratify/ratify \
  --namespace gatekeeper-system \
  --set featureFlags.RATIFY_CERT_ROTATION=true

9. Security migration checklist

  • Entra ID integration enabled on AKS cluster
  • Local accounts disabled
  • RBAC bindings mapped to Entra ID groups
  • Pod Security Standards enforced per namespace
  • Workload Identity configured for all pods accessing Azure services
  • Secrets migrated to Azure Key Vault
  • Key Vault Secrets Provider CSI driver installed and configured
  • Defender for Containers enabled (vulnerability scanning + runtime protection)
  • Azure Policy initiatives assigned (CIS, PSS, NIST)
  • Container images pushed to ACR (no external registries in production)
  • Image signing configured (Notation)
  • Network policies enforced (default-deny + explicit allow)
  • Private cluster enabled (no public API server endpoint)
  • Audit logs flowing to Log Analytics
  • Conditional Access policies applied to kubectl access

Maintainers: CSA-in-a-Box core team Last updated: 2026-04-30 Related: Cluster Migration | Networking Migration | Federal Migration Guide