Azure Kubernetes Service (AKS) removes most of the undifferentiated heavy lifting of running Kubernetes, but it does not remove your responsibility for securing what runs on top of it. Every week, new clusters go into production with permissive RBAC, public API servers, root-privileged containers, and no plan to stay current with CVEs. None of that is an AKS problem; it is an operating-model problem.

This practical, opinionated guide walks through securing an AKS cluster from start to finish: identity, network, workload, supply chain, runtime security, and the often-overlooked process of maintaining security over time.

Importance of the shared responsibility line

AKS handles the control plane components, such as the API server, etcd, scheduler, and controller-manager, while Microsoft is responsible for patching and security. The rest of the node OS setup, network segmentation, RBAC, workload security context, secrets management, image provenance, and regular patching is managed by you. AKS now offers two operating modes that shift the location of this boundary.

  • AKS Automatic offers a secure baseline with numerous preconfigured controls, including auto-upgrade, Azure CNI Overlay with network policy, Workload Identity, Azure Policy, and Defender-ready defaults, all largely managed for you.
  • AKS Standard provides full control over every knob, placing the responsibility on you to set each one correctly.

Whichever mode you run, the hardening domains below are the same. What differs is how much of it AKS has already done for you (e.g., Microsoft Learn and Best practices for cluster security in AKS).

AKS shared responsibility model: Microsoft manages the control plane, you manage identity, network policy, workload security, image provenance and upgrade cadence

Rely on established external frameworks instead of creating your own

Before writing a single policy, identify the frameworks you will be assessed against. Reinventing a hardening checklist from memory is how gaps occur. Four references matter most for AKS:

  1. CIS Azure Kubernetes Service (AKS) Benchmark: a CIS benchmark designed specifically for AKS, created through a collaboration between the Center for Internet Security and the Azure team to ensure alignment with the Microsoft security baseline for AKS. (CIS blog).
  2. CIS Kubernetes Benchmark: the upstream, distribution-agnostic benchmark provides AKS with information on which of its node images meet the requirements of each CIS Kubernetes Benchmark version. (Microsoft Learn, CIS Kubernetes benchmark).
  3. NSA/CISA Kubernetes Hardening Guidance: a joint technical report by NSA and CISA targeting security teams, DevOps, and system administrators. It covers pod security, network separation, authentication, logging, and upgrade practices, and is currently at version 1.2. (CISA advisory, full PDF).
  4. Microsoft Cloud Security Benchmark for AKS: Microsoft’s control mapping for the AKS resource type covers identity, network, logging, and posture, supporting the compliance recommendations in Microsoft Defender for Cloud. However, the published baseline document is based on benchmark v1.0, which Microsoft indicates may be outdated. Therefore, consider it a control map and verify details against the current AKS security documentation (Microsoft Learn, Azure security baseline for AKS).

Use the CIS AKS Benchmark and the NSA/CISA guide as your reference controls, with Azure Policy and Defender for Cloud to ensure enforcement and provide evidence. This setup gives CISOs a verifiable connection from stating “we are hardened” to a recognized external standard, making it significantly more persuasive in compliance reviews than merely referencing an internal wiki page.

1. Identity and access: kill standing access first

The API server is the most valuable target within the cluster. Two modifications are more important than anything else in this context:

  • Integrate Kubernetes RBAC with Microsoft Entra ID so that cluster access is managed through your current identity governance, conditional access, and MFA policies, rather than using a separate kubeconfig credential that no one rotates.
  • Disable all local accounts completely. Even if Entra integration is active, the clusterAdmin local credential acts as non-auditable backdoor unless you explicitly deactivate it.
az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --location westeurope \
  --enable-aad \
  --enable-azure-rbac \
  --aad-admin-group-object-ids `<entra-group-object-id>` \
  --disable-local-accounts \
  --generate-ssh-keys

For workloads, avoid distributing long-lived Azure credentials as Kubernetes Secrets. Instead, use Microsoft Entra Workload ID, which federates a Kubernetes service account with an Entra app registration via OIDC. This setup allows pods to exchange a short-lived Kubernetes token for an Azure AD token, eliminating the need to store any secret in the cluster.

az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --enable-oidc-issuer \
  --enable-workload-identity \
  --generate-ssh-keys

References: Microsoft Entra Workload ID for AKS, Manage local accounts with Microsoft Entra integration

2. Network: shrink the attack surface before you filter it

Hardening the network starts with reducing exposure, then adding filtering on top.

Make the API server private. A private cluster eliminates the public endpoint entirely; the API server can only be accessed via your VNet, a peered network, or a private DNS zone.

az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --network-plugin azure \
  --network-policy azure \
  --enable-private-cluster \
  --private-dns-zone system \
  --generate-ssh-keys

Enforce NetworkPolicy. Using network policy in Azure (or Cilium on newer Azure CNI Overlay clusters) allows you to create standard Kubernetes NetworkPolicy objects that are actively enforced rather than ignored. For example, you can block pod egress to the Azure Instance Metadata Service (IMDS), which is often exploited as a pivot point for credential theft in cloud-hosted clusters.

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: restrict-instance-metadata
  namespace: default
spec:
  podSelector:
    matchLabels: {}
  policyTypes:
    - Egress
  egress:
    - to:
        - ipBlock:
            cidr: 10.10.0.0/16
            except:
              - 169.254.169.254/32

Use this pattern across all namespaces lacking an explicit, reviewed requirement to call IMDS. Reference: Best practices for cluster security in AKS: restrict access to the Instance Metadata API

3. Workloads: least privilege at the pod level

Cluster-level controls are ineffective if all pods run as root, use writable filesystems, and retain full capabilities. Establish a security baseline that every workload is required to adhere to:

apiVersion: v1
kind: Pod
metadata:
  name: hardened-example
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 10001
    seccompProfile:
      type: RuntimeDefault
  containers:
    - name: app
      image: myregistry.azurecr.io/app:1.4.2
      securityContext:
        allowPrivilegeEscalation: false
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      resources:
        limits:
          cpu: "500m"
          memory: "256Mi"

Don’t depend on developers remembering this. Use the Azure Policy add-on for AKS to enforce it centrally; it integrates Gatekeeper/OPA and provides preconfigured initiatives aligned with the Kubernetes Pod Security Standards.

az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --enable-addons azure-policy \
  --generate-ssh-keys

Set the “Kubernetes cluster pod security restricted standards for Linux-based workloads” initiative to deny mode for production namespaces, and audit mode during the cleanup of existing violations. Ensure enforcement is active:

kubectl get constrainttemplates

Then confirm a privileged pod is rejected:

cat <<EOF | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
  name: nginx-privileged
spec:
  containers:
    - name: nginx-privileged
      image: mcr.microsoft.com/oss/nginx/nginx:1.15.5-alpine
      securityContext:
        privileged: true
EOF

Expected result: the API server denies the pod with denied by azurepolicy-container-no-privilege…, and the pod is not scheduled. Reference: Use Azure Policy to secure your AKS clusters.

4. Secrets and the software supply chain

The primary failure modes here are secrets stored in environment variables or configuration maps, and unscanned, unpatched images running in production.

  • Mount secrets from Azure Key Vault via the Secrets Store CSI Driver instead of native Kubernetes Secrets, ensuring values are not stored unencrypted in etcd at the application layer and enabling centralized rotation.
  • Limit image pulls to your Azure Container Registry (ACR) using admission-controlled trusted registries, and activate ACR content trust/image signing for all production deployments.
  • Enable Image Cleaner to automatically detect and remove stale, vulnerable images (using Trivy), preventing them from being targets for lateral movement.
az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --enable-image-cleaner \
  --image-cleaner-interval-hours 24 \
  --generate-ssh-keys

Reference: Use Image Cleaner on AKS.

5. Runtime protection and visibility

Static hardening prevents an attacker from succeeding initially, while runtime protection alerts you to attempts later. Enable Microsoft Defender for Containers to assess image vulnerabilities in ACR, detect threats via Kubernetes audit logs, and receive runtime anomaly alerts on nodes.

az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --enable-defender \
  --generate-ssh-keys

Combine this with Container Insights (Azure Monitor) to collect logs and metrics, and direct Kubernetes audit logs to a Log Analytics workspace to create an audit trail, not just generate alerts. Reference: Introduction to Microsoft Defender for Containers.

The five AKS defense-in-depth layers: identity and access, network, workloads, supply chain, and runtime and monitoring, stacked from outer to inner

Putting it together: a hardened baseline in one command

Combine all the above into one cluster-create call (adjust names, IDs, and region for your environment):

az aks create \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --location westeurope \
  --enable-aad \
  --enable-azure-rbac \
  --aad-admin-group-object-ids `<entra-group-object-id>` \
  --disable-local-accounts \
  --enable-oidc-issuer \
  --enable-workload-identity \
  --network-plugin azure \
  --network-policy azure \
  --enable-private-cluster \
  --private-dns-zone system \
  --enable-defender \
  --enable-image-cleaner \
  --image-cleaner-interval-hours 24 \
  --enable-addons azure-policy \
  --auto-upgrade-channel stable \
  --node-os-upgrade-channel NodeImage \
  --generate-ssh-keys

This isn’t a copy-and-paste-to-production command; it’s a checklist encoded as CLI flags. Review each flag against your network topology (hub-spoke peering for the private DNS zone, existing Entra groups, and existing Log Analytics workspace) before running it.

Keeping the hardening current: hardening is not a one-time event

This aspect is often overlooked in blog posts and deployments, but it’s crucial because hardened clusters can slowly become vulnerable over time. A cluster that is CIS-compliant when deployed isn’t automatically compliant several months later unless it undergoes active maintenance. The support status for Kubernetes versions, node OS packages, and the policy and CVE landscape are all in constant flux.

A continuous five-step loop for keeping AKS hardening current: set upgrade channels, scan with kube-bench, watch Azure Policy state, track Defender secure score, reconcile via GitOps

Automate version and node currency

AKS supports only a limited range of Kubernetes minor versions, usually from N to N-2. If you don’t update, the cluster will eventually fall out of support, stopping security patches for the control plane. To avoid this, specify an upgrade channel instead of leaving it on none.

az aks update \
  --resource-group rg-aks-prod \
  --name aks-prod-weu \
  --auto-upgrade-channel stable \
  --node-os-upgrade-channel NodeImage

stable maintains the cluster on the most recent patch of minor version N-1, balancing up-to-date software with the risk of deploying a new minor release into production. Combine it with a Planned Maintenance window of at least four hours to ensure upgrades occur at a predictable time rather than during peak traffic. Reference: Automatically upgrade an AKS cluster.

Node OS patches are independent of Kubernetes version upgrades. Linux nodes automatically download security patches nightly, but the node image itself updates only during an upgrade. This means a newly scaled node might run on an outdated, unpatched image. The NodeImage auto-upgrade channel addresses this by refreshing node images automatically as Microsoft releases new ones, approximately weekly. Reference: Upgrade AKS node images.

Check for drift on a schedule, not by accident

Configuration drift happens when an engineer adds a hostNetwork: true pod, a NetworkPolicy gets silently removed during troubleshooting, or a namespace is left out of Azure Policy and not re-added. This is the leading cause of a hardened baseline weakening. To avoid this, three complementary checks are performed regularly:

1. CIS benchmark scans with kube-bench.

Run kube-bench as a scheduled Kubernetes Job on your worker nodes. Since managed control planes like AKS restrict access to master nodes, focus the runs on node-level and policy checks for which you are responsible. Consider the remaining checks as covered under Microsoft’s control-plane SLA.

apiVersion: batch/v1
kind: CronJob
metadata:
  name: kube-bench-node
  namespace: security
spec:
  schedule: "0 3 * * 1"
  jobTemplate:
    spec:
      template:
        spec:
          hostPID: true
          containers:
            - name: kube-bench
              image: docker.io/aquasec/kube-bench:v0.9.5
              command: ["kube-bench", "node", "--benchmark", "cis-1.9"]
              volumeMounts:
                - name: var-lib-kubelet
                  mountPath: /var/lib/kubelet
                  readOnly: true
                - name: etc-systemd
                  mountPath: /etc/systemd
                  readOnly: true
          restartPolicy: Never
          volumes:
            - name: var-lib-kubelet
              hostPath:
                path: /var/lib/kubelet
            - name: etc-systemd
              hostPath:
                path: /etc/systemd
      backoffLimit: 1

Send results to Log Analytics and trigger alerts for new failures; not only rely on the absolute score, but also on a drop from 96% to 91%, as that is more actionable than a static “91% compliant” tile on the dashboard. Reference: kube-bench, aquasecurity/kube-bench on GitHub.

2. Azure Policy compliance state as continuous evidence.

Since the Azure Policy add-on assesses each admission request and regularly rechecks existing objects, its compliance status acts as a real-time drift indicator rather than a snapshot in time. Query it regularly and provide the results to whatever the CISO’s team reviews.

az policy state list \
  --resource-group rg-aks-prod \
  --filter "complianceState eq 'NonCompliant'" \
  --query "[].{policy:policyDefinitionName, resource:resourceId}" \
  -o table

3. Microsoft Defender for Cloud secure score, tracked over time.

Defender regularly reviews the AKS security baseline, including coverage for identity, network, and image scanning, and generates severity-ranked recommendations. Monitor the secure score for the AKS resource group weekly; if the score remains flat or declines after a hardening effort, it indicates that drift has begun.

Close the loop with change management, not just tooling

Tools detect drift while the process prevents it. In practice, two habits make the difference:

  • Store all cluster-hardening configurations, such as Azure Policy assignments, NetworkPolicy manifests, and admission baselines, in Git via pull requests. Reconcile these with GitOps tools like Flux or Argo CD instead of applying changes manually with kubectl. Any manual kubectl edit to a hardened resource will be detected as drift during the next reconciliation.
  • Re-run the CIS AKS Benchmark and NSA/CISA guidance checklist with each minor Kubernetes upgrade instead of annually. New Kubernetes minor versions sometimes alter default behaviors, such as API deprecations and PodSecurity admission defaults, which can silently reopen previously closed controls.

A closing checklist

If there’s one key takeaway from this post, it’s that hardening AKS isn’t achieved with a single az aks create command. Instead, it involves five interconnected layers: identity, network, workload, supply chain, and runtime, each aligned with an external framework for auditability. Additionally, ongoing practices such as weekly kube-bench scans, continuous Azure Policy checks, maintaining Defender secure scores, and ensuring upgrade channels remain active are part of a continuous effort. Consider the first a project and the second a permanent operational commitment; doing so ensures the “hardened” status remains valid even after a year.

References