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)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:
| Level | What it restricts | Use case |
|---|---|---|
| Privileged | Nothing — all permissions allowed | System namespaces only |
| Baseline | Prevents known privilege escalations | Most workloads |
| Restricted | Enforces 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 neededContainer 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 VaultMicrosoft 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 defenderDefender 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: 443Common mistakes
- 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.
- 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.
- 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.
- 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.
- 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.
Summary
- RBAC controls who can make API calls in Kubernetes. Integrate with Azure AD for authentication and use least-privilege roles for all users and service accounts.
- Pod Security Standards enforce security constraints at the namespace level. Apply at minimum the baseline policy to prevent privileged container escapes.
- Never store sensitive values as plain Kubernetes Secrets in production. Enable etcd encryption or use the Secrets Store CSI driver with Key Vault.
- Enable Microsoft Defender for Containers for runtime threat detection across all nodes and pods.
- Apply default-deny network policies per namespace and only open the communication paths your applications actually need.
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.