Securing AKS Clusters: Best Practices

A Kubernetes cluster running insecurely is not just a risk to your applications — it is a risk to your entire Azure environment. A compromised pod with the right permissions or a misconfigured RBAC policy can give an attacker access to secrets, Azure resources, and other workloads. Security for AKS is a collection of overlapping controls, each reducing the blast radius of different failure modes.

Kubernetes RBAC — who can do what

Kubernetes RBAC controls access to the Kubernetes API. It is separate from Azure RBAC and operates entirely within the cluster. Three core objects:

  • Role / ClusterRole — defines a set of permissions (verbs on resources)
  • RoleBinding / ClusterRoleBinding — grants a role to a user, group, or service account
  • ServiceAccount — an identity for pods to call the Kubernetes API
# A role that allows reading pods and logs in one namespace
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  namespace: production
  name: pod-reader
rules:
- apiGroups: [""]
  resources: ["pods", "pods/log"]
  verbs: ["get", "list", "watch"]
---
# Bind the role to a user
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: read-pods-binding
  namespace: production
subjects:
- kind: User
  name: "dev-user@company.com"   # Azure AD user
  apiGroup: rbac.authorization.k8s.io
roleRef:
  kind: Role
  name: pod-reader
  apiGroup: rbac.authorization.k8s.io
# Check what permissions a user has
kubectl auth can-i list pods --namespace production \
  --as dev-user@company.com

# List all role bindings in a namespace
kubectl get rolebindings -n production -o wide

# View cluster-wide bindings
kubectl get clusterrolebindings -o wide | grep -v system:

Azure AD integration for authentication

Instead of managing Kubernetes-native users, integrate with Azure AD (Microsoft Entra ID) so your existing identity infrastructure handles authentication:

# Enable Azure AD integration during cluster creation
az aks create \
  --resource-group my-aks-rg \
  --name my-cluster \
  --enable-aad \
  --enable-azure-rbac \
  --generate-ssh-keys

# Grant a user Azure Kubernetes Service Cluster Admin Role
az role assignment create \
  --role "Azure Kubernetes Service Cluster Admin Role" \
  --assignee user@company.com \
  --scope $(az aks show --resource-group my-aks-rg \
    --name my-cluster --query id -o tsv)

# Grant a group read-only access
az role assignment create \
  --role "Azure Kubernetes Service Cluster User Role" \
  --assignee-object-id <group-object-id> \
  --assignee-principal-type Group \
  --scope $(az aks show --resource-group my-aks-rg \
    --name my-cluster --query id -o tsv)
Tip

With Azure RBAC enabled, you can manage cluster access entirely through Azure role assignments instead of kubectl RoleBindings. This gives you a single pane of glass for all Azure permissions, including who can access which clusters.

Pod Security Standards

Pod Security Standards (PSS) replaced the deprecated Pod Security Policy in Kubernetes 1.25. They define three policy levels enforced at the namespace level:

LevelWhat it restrictsUse case
PrivilegedNothing — all permissions allowedSystem namespaces only
BaselinePrevents known privilege escalationsMost workloads
RestrictedEnforces security best practices strictly (no root, read-only filesystem, etc.)Security-sensitive workloads
# Apply baseline policy to a namespace (enforce mode rejects violations)
kubectl label namespace production \
  pod-security.kubernetes.io/enforce=baseline \
  pod-security.kubernetes.io/warn=restricted \
  pod-security.kubernetes.io/audit=restricted

# Test: try to run a privileged pod in this namespace
kubectl run priv-test \
  --image=nginx \
  --privileged=true \
  --namespace production
# Error: privileged containers are not allowed (baseline policy)

Securing pod specifications

Even without namespace-level policies, individual pod security context settings matter. Add these to all production pod specs:

spec:
  securityContext:
    runAsNonRoot: true          # Container cannot run as root
    runAsUser: 1000             # Specific non-root UID
    runAsGroup: 3000
    fsGroup: 2000               # Volume ownership
    seccompProfile:
      type: RuntimeDefault      # Restrict system calls
  containers:
  - name: app
    image: myapp:1.0
    securityContext:
      allowPrivilegeEscalation: false   # Cannot gain more privileges
      readOnlyRootFilesystem: true      # Cannot write to container FS
      capabilities:
        drop:
        - ALL                           # Drop all Linux capabilities
        add:
        - NET_BIND_SERVICE              # Only re-add what's needed

Container image security

Your container images are as important as your cluster configuration. Insecure images introduce vulnerabilities even in a well-secured cluster.

# Use Azure Container Registry with vulnerability scanning
# Enable Defender for Containers on ACR
az security pricing create \
  --name Containers \
  --tier Standard

# Scan a specific image
az acr task run \
  --registry my-registry \
  --image my-app:1.0 \
  --cmd "mcr.microsoft.com/azure-cli:latest \
    az acr image show -n my-registry --image my-app:1.0"

# Use only images from trusted registries
# Enforce this with Azure Policy (built-in definition):
# "Kubernetes cluster containers should only use allowed images"

Best practices for container images:

  • Use minimal base images (Alpine, distroless) to reduce attack surface
  • Never embed secrets in images — they end up in image history and registries
  • Pin image digests (not just tags) for critical production images to prevent tag mutation attacks
  • Enable content trust or image signing if available in your registry

Securing secrets at rest

Kubernetes Secrets are base64-encoded by default, which is not encryption. For AKS, enable etcd encryption at rest using Azure Key Vault:

# Enable key management service (KMS) encryption
# This encrypts etcd secrets with a Key Vault key
az aks update \
  --resource-group my-aks-rg \
  --name my-cluster \
  --enable-azure-keyvault-kms \
  --azure-keyvault-kms-key-id https://my-vault.vault.azure.net/keys/my-key/version

# Alternatively, use the Secrets Store CSI driver to avoid storing
# sensitive values in etcd at all — mount directly from Key Vault

Microsoft Defender for Containers

Enable Defender for Containers to get runtime threat detection across your cluster:

# Enable Defender for Containers
az security pricing create \
  --name Containers \
  --tier Standard \
  --resource-group my-aks-rg

# The Defender sensor deploys as a DaemonSet to every node
kubectl get daemonset -n kube-system | grep defender

Defender detects:

  • Cryptocurrency mining processes inside containers
  • Privilege escalation attempts
  • Abnormal shell executions (e.g., bash running inside a web server container)
  • Container escapes and host filesystem access attempts
  • Connections to known malicious IPs

Alerts appear in Microsoft Defender for Cloud within minutes of detection.

Network hardening

# Default deny-all network policy for a namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: default-deny-all
  namespace: production
spec:
  podSelector: {}    # Applies to ALL pods in namespace
  policyTypes:
  - Ingress
  - Egress
# Allow specific egress to Azure services only
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: allow-azure-egress
  namespace: production
spec:
  podSelector:
    matchLabels:
      app: my-app
  policyTypes:
  - Egress
  egress:
  - to:
    - ipBlock:
        cidr: 0.0.0.0/0
        except:
        - 169.254.0.0/16    # Block IMDS endpoint for non-identity pods
    ports:
    - protocol: TCP
      port: 443

Common mistakes

  1. Granting cluster-admin to everyone. Cluster-admin can do anything in the cluster, including reading all Secrets and modifying system workloads. Use the principle of least privilege — developers usually need namespace-scoped read access, not cluster-admin.
  2. No pod security standards on any namespace. Without namespace-level policies, any deployment can run privileged containers as root. Apply at minimum the baseline policy to all namespaces except kube-system.
  3. Leaving the Kubernetes dashboard enabled with default settings. The Kubernetes dashboard with broad RBAC permissions and public access has been exploited in high-profile attacks. If you do not use it, delete it. If you do, restrict access carefully.
  4. Not separating workload namespaces. Running dev, staging, and production workloads in the same namespace (or cluster) means a mistake in dev could affect production. Use separate namespaces with network policies, or separate clusters for true isolation.
  5. Ignoring Defender for Containers recommendations. Microsoft Defender regularly publishes security recommendations specific to your cluster’s configuration. Treating these as optional misses actionable, high-priority issues like exposed secrets and over-privileged identities.

Frequently asked questions

What is the biggest security mistake teams make with AKS?

Running containers as root is the most common and impactful mistake. Containers running as root with a host-mount or privileged flag can escape the container and compromise the node. Use Pod Security Standards to prevent this at the cluster level.

Do I need both RBAC and network policies?

Yes. RBAC controls who can make changes to the Kubernetes API (create deployments, read secrets, etc.). Network policies control which pods can communicate over the network. They protect different attack vectors and are both necessary.

How do I know if my cluster is already compromised?

Enable Microsoft Defender for Containers on your cluster. It detects anomalous behavior like processes running inside containers that should not be there, privilege escalation attempts, and unusual network connections. Defender alerts in near real-time.

Last verified: 19 March 2026 Cloud services change frequently. Verify details against official documentation before making infrastructure decisions.