Skip to main content
emnode
Compliance

Enforce AKS pod security

One capability across every workload in an AKS cluster: make sure no pod runs more privileged than it needs, no container pulls from a registry you do not trust, and no deployment can quietly grant itself root, host access or a writable filesystem, unless you genuinely intend it.

15 min·10 sections·AZURE

Last reviewed

Enforcing pod security: the basics

What does an over-privileged AKS workload actually look like?

An Azure Kubernetes Service cluster runs whatever pod specifications your teams hand it, and a pod can ask for far more than it needs in a handful of distinct ways. A container can run privileged, which gives it every root capability of the underlying node. It can run as the root user, so a compromise inside the container is root on the host. It can allow privilege escalation, share the host's process or IPC namespace, keep a writable root filesystem, or hold Linux capabilities it never uses. It can pull an image from any registry on the internet rather than a trusted one. It can sit in the default namespace, automount the cluster's API credentials, or run with no CPU or memory limit at all. Each of these is its own Microsoft Defender for Cloud recommendation, but the underlying risk is the same: a workload that can reach further into the node, the cluster or the supply chain than you meant it to.

Defender for Cloud turns each of these into its own data plane hardening recommendation, which is why one busy cluster can fail eight or ten container checks at once. 'Privileged containers should be avoided', 'Running containers as root user should be avoided' and 'Container with privilege escalation should be avoided' close the widest doors into the node. They pair with 'Immutable (read-only) root filesystem should be enforced for containers', 'Least privileged Linux capabilities should be enforced for containers' and 'Containers sharing sensitive host namespaces should be avoided' to shrink what a compromised container can do, with 'Container images should be deployed from trusted registries only' to control what runs in the first place, and with 'Kubernetes clusters should disable automounting API credentials', 'Kubernetes clusters should not use the default namespace' and 'Container CPU and memory limits should be enforced' to tidy up the blast radius. They read as ten separate problems on the report, but they are one job: stop workloads being more privileged than they need to be.

Most of this is drift, not intent. A base image that defaults to root, a Helm chart copied from a sample that left privileged set to true, a deployment in the default namespace because nobody created one, a container with no resource limits because the author never added them. All of it is enforced through a single mechanism: the Azure Policy add-on for AKS, which installs OPA Gatekeeper as an admission controller and evaluates every pod against the policies you assign. The work is to turn that add-on on, see which workloads are over-privileged, fix the specifications, and then set the policies to deny so the cluster refuses the next over-privileged pod at admission.

In this lesson you will learn how an AKS workload expresses privilege and trust, how the Azure Policy add-on and Gatekeeper evaluate every pod, how to find every over-privileged workload in a cluster, and how to lock the cluster down without blocking the deployments you depend on. The Controls this lesson covers section lists every Defender for Cloud data plane recommendation in this capability, each linking to a deep page with the exact check and a copy-and-paste fix.

Fun fact

The container that was the whole host

A privileged container is not really sandboxed at all. With the privileged flag set, the container shares the node's kernel with almost no restrictions, so escaping it to the host can be as simple as mounting the host's filesystem from inside the pod. Security researchers have demonstrated full node takeover from a single privileged pod in seconds, which is exactly why the Kubernetes Pod Security Standards put 'no privileged containers', 'no running as root' and 'read-only root filesystem' at the centre of the restricted profile. AKS removed the old PodSecurityPolicy feature entirely, so on a modern cluster the Azure Policy add-on with Gatekeeper is the supported way to enforce these standards.

Finding over-privileged workloads across a cluster

Priya is the platform lead at a scale-up preparing for its first SOC 2 audit. Defender for Cloud shows container findings spread across several deployments in a cluster that started as a quick proof of concept and grew into production.

Rather than work the findings one by one, Priya first confirms the Azure Policy add-on is installed so Gatekeeper is actually evaluating pods, then lists which workloads still run privileged or as root, so the genuine exceptions can be separated from drift before any policy is set to deny.

First confirm the Azure Policy add-on is enabled, then ask the cluster which running pods are privileged or root. These are the widest-open doors into the node.

$ az aks show -g rg-prod -n aks-prod --query addonProfiles.azurepolicy.enabled -o tsv && kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"/"}{.metadata.name}{" priv="}{.spec.containers[*].securityContext.privileged}{" user="}{.spec.containers[*].securityContext.runAsUser}{"\n"}{end}'
true
default/legacy-agent priv=true user=0
payments/api-7d9c priv=<none> user=1000
# legacy-agent runs privileged, as root, in the default namespace. Fix it first.

The add-on is on (true), so Gatekeeper is auditing. A pod that is privileged AND root AND in the default namespace trips several recommendations at once and is the highest-value target in this group.

How Defender for Cloud decides an AKS workload is over-privilegeddeep dive

These recommendations are not evaluated by reading the AKS resource in Azure Resource Manager. They are evaluated inside the cluster by the Azure Policy add-on, which extends OPA Gatekeeper v3, an admission controller webhook. When you assign a Kubernetes policy, Azure Policy translates it into a Gatekeeper constraint template and constraint, and Gatekeeper checks every pod admission request against it in real time, as well as scanning the whole cluster for existing violations every 15 minutes. Almost all of these checks resolve to a field in the pod's securityContext: 'privileged' true, 'runAsUser' 0 or 'runAsNonRoot' false, 'allowPrivilegeEscalation' true, 'readOnlyRootFilesystem' false, an over-broad capabilities list, or hostPID and hostIPC set. The trusted-registries and resource-limits checks read the container image reference and the resources block instead.

Because this runs in-cluster, two things follow. First, the add-on must be installed for any of these recommendations to report at all: a cluster without it shows the parent recommendation 'Azure Kubernetes Service clusters should have the Azure Policy add-on for Kubernetes installed' and none of the data plane findings underneath. Second, after you remediate, the posture catches up on the next assessment cycle, which can take up to 30 minutes, rather than flipping to Healthy the instant you redeploy a pod.

Every Kubernetes policy supports the effects audit, deny and disabled. By default these policies run in audit: Gatekeeper records violations and reports them to Defender for Cloud but admits the pod anyway. Setting the effect to deny is what turns the report into enforcement, so Gatekeeper rejects a non-compliant admission request outright. Two details matter when you switch to deny. A pod that already exists keeps running, but the moment it is rescheduled onto another node Gatekeeper blocks its recreation, so enforcement bites on the next rollout. And system namespaces such as kube-system, gatekeeper-system and azure-arc should be excluded from evaluation, which is why a couple of policies require a namespace-exclusion parameter and others, like trusted registries and resource limits, require you to supply the list of allowed registries or the limit values before they can report anything but unhealthy.

What is the impact of leaving AKS workloads over-privileged?

The direct impact is container escape. A privileged or root container shares the node's kernel with almost no isolation, so a single remote-code-execution bug in that workload becomes root on the host, and from the host an attacker can read the secrets, identities and traffic of every other pod on that node. Allowing privilege escalation, sharing the host process or IPC namespace, or keeping a writable root filesystem each widen that path. The overwhelming majority of these are accidents: a base image that runs as root, a sample chart that set privileged, a capability nobody dropped.

The second-order impact is supply chain and blast radius. 'Container images should be deployed from trusted registries only' is the control that decides what is allowed to run at all, so without it a compromised or typosquatted public image can land straight into production. 'Kubernetes clusters should disable automounting API credentials' stops a compromised pod from using the cluster's own API to spread, the default-namespace control keeps workloads from colliding in a space with no boundaries, and resource limits stop one workload starving the rest of a node in a denial-of-service. Together they shrink what a single compromise can reach.

On the compliance side, every modern framework, ISO 27001, SOC 2, the CIS Azure Kubernetes Service Benchmark, PCI DSS and the UK and EU data-protection regimes, expects evidence that container workloads run with least privilege and from controlled sources. A passing set of data plane hardening recommendations across every cluster, backed by policies in deny mode, is the cheapest and most defensible artefact you can hand an auditor.

How do you lock an AKS cluster down safely?

Work the capability as one loop rather than chasing individual findings. The order matters: get the add-on auditing and see every violation before you set anything to deny, so you do not block a deployment your business depends on.

1. Install the add-on so Gatekeeper can audit

Register the Microsoft.PolicyInsights resource provider and enable the azure-policy add-on on the cluster. Until it is installed, Gatekeeper is not running and none of the data plane recommendations report. Confirm the azure-policy and gatekeeper pods are running before you go further.

2. Assign the pod security standards in audit mode

Assign the built-in initiative 'Kubernetes cluster pod security baseline standards for Linux-based workloads', or the stricter restricted standards if your workloads can meet them, with effect audit. Exclude kube-system, gatekeeper-system and azure-arc from evaluation. Audit mode reports every violation without blocking anything, so you get a true picture before enforcing.

3. Fix the workloads, highest blast radius first

In each pod specification, set privileged false, runAsNonRoot true with a non-root runAsUser, allowPrivilegeEscalation false and readOnlyRootFilesystem true, drop all Linux capabilities and add back only what is needed, and remove hostPID and hostIPC. Add CPU and memory limits, move workloads out of the default namespace, and point image references at your trusted registry. Prioritise the workloads that are internet-facing or hold data over internal batch jobs.

4. Switch to deny so the cluster refuses the next bad pod

Once audit shows only the exceptions you have decided to keep, set the policy effect to deny. Configure 'Container images should be deployed from trusted registries only' with your allowed registry list and the resource-limit policy with your maximum values. Now Gatekeeper rejects a privileged, root or untrusted-image pod at admission, and each documented exception is an explicit, owned decision rather than drift.

# 1. Turn on the Azure Policy add-on so Gatekeeper starts auditing the cluster.
az provider register --namespace Microsoft.PolicyInsights
az aks enable-addons --addons azure-policy \
  --resource-group rg-prod --name aks-prod
# Confirm the policy and gatekeeper pods are running.
kubectl get pods -n kube-system -l app=azure-policy
kubectl get pods -n gatekeeper-system

# 2. Assign the built-in pod security baseline initiative in AUDIT first.
# Look the initiative up by its exact display name, never paste a GUID.
SCOPE=$(az aks show -g rg-prod -n aks-prod --query id -o tsv)
SETID=$(az policy set-definition list \
  --query "[?displayName=='Kubernetes cluster pod security baseline standards for Linux-based workloads'].name" -o tsv)
az policy assignment create \
  --name aks-pod-baseline \
  --policy-set-definition "$SETID" \
  --scope "$SCOPE" \
  --params '{"effect":{"value":"audit"},"excludedNamespaces":{"value":["kube-system","gatekeeper-system","azure-arc"]}}'

# 3. Fix the workloads (in each pod spec, securityContext):
#   privileged: false | runAsNonRoot: true | runAsUser: 1000
#   allowPrivilegeEscalation: false | readOnlyRootFilesystem: true
#   capabilities: { drop: ["ALL"] }  and add back only what is needed.

# 4. Once audit is clean except for named exceptions, ENFORCE.
az policy assignment update --name aks-pod-baseline \
  --set parameters.effect.value=deny

Quick quiz

Question 1 of 5

Defender for Cloud shows findings across privileged containers, running as root, trusted registries and resource limits on one AKS cluster. What is the most efficient way to think about them?

You can now treat AKS pod security as one capability rather than a scatter of findings: install the Azure Policy add-on so Gatekeeper audits every pod, assign the pod security standards, fix the over-privileged workloads highest blast radius first, then switch the policies to deny so the cluster refuses the next privileged, root or untrusted-image pod at admission. The Controls this lesson covers section below links every recommendation in this group to its deep page and fix.

Back to the library

Controls this lesson covers

One capability, many Microsoft Defender for Cloud recommendations. This lesson is the shared playbook; each control below keeps its own deep page with the exact check, severity and a copy-and-paste fix.

Azure Kubernetes Service