The provider secures the control plane; you still own who can reach and call the API, node patching on standard node pools, workload privileges, network policy and admission. Make the API endpoint private or restrict it to known addresses, and authenticate people through the cloud identity provider. Remove static administrator paths: use EKS access entries instead of the aws-auth ConfigMap, and disable AKS local accounts. Give pods their own cloud identity and keep them away from the node's. Keep nodes on current images, enforce Pod Security restricted with seccomp RuntimeDefault, and add AppArmor and user namespaces (hostUsers: false, GA in 1.36) on nodes that support them. Then check the cluster with kube-bench against your service's CIS benchmark.
- EKS Auto Mode, GKE Autopilot and AKS Automatic shift node patching to the provider. On standard node pools you choose when nodes update: AKS publishes node images weekly and supports images up to 90 days old.
- Defaults favor convenience. An EKS API endpoint is public, its creator is an invisible cluster administrator, and a cluster created through the API starts in
CONFIG_MAPmode. AKS local accounts bypass Microsoft Entra ID until you disable them. - Pod Security restricted rejected a privileged,
hostPIDpod on Kubernetes 1.37 and listed all six violations; on GKE Autopilot,RuntimeDefaultseccomp applies to every pod. - User namespaces are GA in 1.36. In our 1.37 test, root in the pod mapped to host UID 2001731584. They need Linux 6.3 or later and containerd 2.0 or CRI-O 1.25 or later.
- AppArmor fails closed: on a node without it, the kubelet rejected our pod with
AppArmor is not enabled on the host. - kube-bench 0.16.0 bundles CIS EKS 1.8.0, AKS 1.8 and GKE 1.9.0 checks, but its sample job files still name older benchmarks and the
:latestimage.
| Setting | Value | Note | Source |
|---|---|---|---|
| API endpoint | Private, or public restricted to known CIDRs | New EKS clusters have a public endpoint and no private one. | https://docs.aws.amazon.com/eks/latest/userguide/cluster-endpoint.html |
| EKS authentication mode | API (access entries only) | The aws-auth ConfigMap is deprecated. Clusters created through the API, SDKs or CloudFormation default to CONFIG_MAP; the console defaults to API_AND_CONFIG_MAP. | https://docs.aws.amazon.com/eks/latest/userguide/grant-k8s-access.html |
| EKS cluster creator | bootstrapClusterCreatorAdminPermissions=false; grant administrators through access entries | Otherwise the creator holds cluster-admin with no visible entry. | https://docs.aws.amazon.com/cli/latest/reference/eks/create-cluster.html |
| AKS authentication | Microsoft Entra ID with Azure RBAC; --disable-local-accounts | Local accounts are on by default and give non-auditable --admin access. | https://learn.microsoft.com/en-us/azure/aks/local-accounts |
| Pod cloud identity | EKS Pod Identity or IRSA; Microsoft Entra Workload ID; Workload Identity Federation for GKE | On AKS every pod can reach IMDS by default; the restriction is in preview. | https://learn.microsoft.com/en-us/azure/aks/imds-restriction |
| Node updates | Auto-upgrade channel or a scheduled node image rollout at least monthly | AKS node OS channel NodeImage or SecurityPatch; GKE release channel; EKS managed node group or Karpenter drift. | https://learn.microsoft.com/en-us/azure/aks/support-policies |
| Pod Security Admission | enforce: restricted with enforce-version pinned, plus warn and audit | — | https://kubernetes.io/docs/concepts/security/pod-security-admission/ |
| seccomp | seccompProfile.type: RuntimeDefault in every pod | The kubelet's seccompDefault (GA in 1.27) applies it to pods that set nothing; GKE Autopilot applies it everywhere. | https://kubernetes.io/docs/tutorials/security/seccomp/ |
| User namespaces | hostUsers: false for workloads that can run in them | GA in 1.36; incompatible with hostNetwork, hostPID, hostIPC and NFS volumes. | https://kubernetes.io/docs/concepts/workloads/pods/user-namespaces/ |
| Admission policy | ValidatingAdmissionPolicy (GA 1.30) and MutatingAdmissionPolicy (GA 1.36), or Kyverno or Gatekeeper | Use failurePolicy: Fail for security rules. | https://kubernetes.io/docs/reference/access-authn-authz/validating-admission-policy/ |
- From outside your network,
curlthe API endpoint and confirm it is unreachable or refused; from an authorized network,kubectl get nsworks only with a cloud-identity login. - On EKS, run
aws eks describe-cluster --name prod --query cluster.accessConfigand confirmAPI; on AKS, confirmaz aks get-credentials --adminfails withGetting static credential isn't allowed. - Apply a privileged pod to a restricted namespace and confirm the API server refuses it and names each violation.
- In a pod with
hostUsers: false,cat /proc/self/uid_mapmust map 0 to a high host UID, not 0. - From a pod, request the metadata service and confirm it times out or returns no node credentials.
- Run kube-bench against your service's current CIS benchmark and record every FAIL as fixed, accepted with a reason, or not applicable because the provider owns it.
- Applies to
- Kubernetes 1.37 (tested with kind 0.33.0, containerd 2.3.4, Linux 6.12)
- Amazon EKS Standard and Auto Mode
- Azure Kubernetes Service Standard and Automatic
- Google Kubernetes Engine Standard and Autopilot
- kube-bench 0.16.0
Amazon EKS, Azure Kubernetes Service and Google Kubernetes Engine on Kubernetes 1.35 to 1.37. Feature status and defaults change with each release; check the current documentation for your cluster version.
- Part of
- Learning path: Runtime and cloud defense (step 4 of 13). Workflow: Review a design
Learning goals and prerequisites
What you will learn
- Split responsibility for a managed cluster between you and the provider, by node mode.
- Restrict API access and remove static administrator paths on EKS, AKS and GKE.
- Enforce Pod Security restricted with seccomp, and add AppArmor and user namespaces where nodes support them.
- Check a cluster against the CIS benchmark for its service and the NSA/CISA guidance.
Before you start
Platform engineers and cluster owners who run EKS, AKS or GKE, and the security engineers who review them
- Read Kubernetes pod specs and RBAC objects.
- Use your provider's CLI with a role that can view cluster configuration.
Refresh first: Run Kubernetes workloads with only the privileges they need · Review cloud permissions and role trust together
Go to the guided practiceThe CIS Kubernetes Benchmark is written for clusters where you run the control plane. For managed clusters, use the provider benchmarks (CIS Amazon EKS, CIS AKS and CIS GKE), which mark control-plane items as the provider's responsibility. GKE's documentation says customers cannot evaluate its managed control plane against the benchmark at all. The NSA and CISA Kubernetes Hardening Guide, version 1.2 of August 2022, covers the same ground by theme: pod security, network separation, authentication and authorization, audit logging, and upgrades.
kube-bench runs the node, policy and managed-service checks as a Job. Many items cannot be checked automatically and come back as WARN for manual review, so treat its output as a work list, not a score. Its generic checks stop at CIS Kubernetes Benchmark 1.12, which covers Kubernetes 1.32 to 1.34; on managed clusters run the provider benchmark instead. The node and container images are yours to benchmark in every mode: /domains/runtime/containers/image-hardening-benchmarks/ scans them with OpenSCAP against CIS and STIG profiles and fails the build on deviations nobody approved.
# kube-bench 0.16.0 bundles CIS Kubernetes 1.12, EKS 1.8.0, AKS 1.8 and GKE 1.9.0 checks.
# The job files in its repository default to older benchmarks and the :latest image, so
# download one, pin the image and set the benchmark you report against.
curl -sSL https://raw.githubusercontent.com/aquasecurity/kube-bench/v0.16.0/job-eks.yaml | sed -e 's|docker.io/aquasec/kube-bench:latest|docker.io/aquasec/kube-bench:v0.16.0@sha256:75506f222d1eb6ce2a751a5533bdc0a3b54c898e2e49e7751d0ee22cfb862679|' -e 's|"eks-1.5.0"|"eks-1.8.0"|' > job-eks.yaml
kubectl apply -f job-eks.yaml
kubectl wait --for=condition=complete job/kube-bench --timeout=300s
kubectl logs job/kube-bench > kube-bench-eks-1.8.0.txtTreat the API endpoint as an administrative interface: private where your deployment tooling allows it, otherwise limited to known egress addresses. Authenticate people through the cloud identity provider, so leavers and MFA are handled where the rest of their access is, and remove every credential that bypasses it.
On EKS, switch to access entries. The change from CONFIG_MAP is one-way, so migrate the entries first. Access policy permissions don't appear in kubectl auth can-i --list; review them with aws eks list-associated-access-policies. On AKS, disabling local accounts stops az aks get-credentials --admin from issuing a static admin certificate; on an existing cluster, rotate the cluster certificates afterwards. On GKE, enable private nodes and authorized networks, or use the DNS-based endpoint with IAM.
Turn on control plane audit logs and keep them. EKS sends no control plane logs to CloudWatch until you enable each type; the runtime detection and response guide at /domains/runtime/detection/runtime-detection-response/ shows an audit policy and detections that use them.
# --- Amazon EKS ---------------------------------------------------------------
# Private endpoint only (or keep public with publicAccessCidrs set to your egress IPs).
aws eks update-cluster-config --name prod \
--resources-vpc-config endpointPublicAccess=false,endpointPrivateAccess=true
# Access entries instead of the deprecated aws-auth ConfigMap (one-way change).
aws eks update-cluster-config --name prod --access-config authenticationMode=API
aws eks create-access-entry --cluster-name prod \
--principal-arn arn:aws:iam::111122223333:role/platform-oncall --type STANDARD
aws eks associate-access-policy --cluster-name prod \
--principal-arn arn:aws:iam::111122223333:role/platform-oncall \
--access-scope type=namespace,namespaces=payments \
--policy-arn arn:aws:eks::aws:cluster-access-policy/AmazonEKSEditPolicy
# Control plane logs are off until you enable them.
aws eks update-cluster-config --name prod \
--logging '{"clusterLogging":[{"types":["api","audit","authenticator","controllerManager","scheduler"],"enabled":true}]}'
# --- Azure Kubernetes Service --------------------------------------------------
az aks update --resource-group rg-prod --name prod \
--enable-aad --aad-admin-group-object-ids <entra-group-id> \
--enable-azure-rbac --disable-local-accounts \
--enable-oidc-issuer --enable-workload-identity \
--api-server-authorized-ip-ranges 203.0.113.0/24 \
--node-os-upgrade-channel NodeImage
# After disabling local accounts on an existing cluster, rotate its certificates.
az aks rotate-certs --resource-group rg-prod --name prod
# --- Google Kubernetes Engine (Standard) -----------------------------------------
gcloud container clusters create prod --location europe-west1 \
--release-channel regular \
--workload-pool PROJECT_ID.svc.id.goog \
--enable-private-nodes \
--enable-master-authorized-networks --master-authorized-networks 203.0.113.0/24 \
--shielded-secure-boot \
--enable-dataplane-v2A pod that can read the node's credentials from the instance metadata service can act as the node: on EKS that is the node IAM role, on AKS the kubelet's managed identity, on GKE the node service account. Give each workload its own identity (EKS Pod Identity or IRSA, Microsoft Entra Workload ID, Workload Identity Federation for GKE), then remove the path to the node's.
On EKS, require IMDSv2 with a hop limit of 1 on node launch templates, so a token request from a pod's network namespace never gets an answer. GKE's metadata server, enabled by Workload Identity Federation for GKE and always on in Autopilot, hides node credentials from pods. On AKS every pod can reach IMDS by default; the IMDS restriction was still a preview on 11 October 2026 and conflicts with several add-ons. Where no platform control fits, a NetworkPolicy that excludes the metadata address from pod egress works with any CNI that enforces egress policy. It does not cover pods with hostNetwork: true, which Pod Security restricted forbids anyway.
# Pods in payments may resolve DNS and reach anything except the node's metadata service.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: egress-without-metadata
namespace: payments
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- {protocol: UDP, port: 53}
- {protocol: TCP, port: 53}
- to:
- ipBlock:
cidr: 0.0.0.0/0
except: [169.254.169.254/32]Every container on a node shares its kernel, so a kernel bug or a container escape reaches every pod on the node. Keep node images current: AKS publishes node images weekly and doesn't support images older than 90 days, and EKS Auto Mode replaces nodes within 21 days at most. Prefer a minimal container OS such as Bottlerocket, Container-Optimized OS or Azure Linux.
Run untrusted or multi-tenant code on separate nodes with taints and tolerations, or in a sandbox with its own kernel. GKE Sandbox runs pods under gVisor with runtimeClassName: gvisor, which Autopilot supports by default; on GKE 1.37.0-gke.4713000 or later the microvm runtime class runs pods in lightweight VMs instead, on nodes with nested virtualization. AKS Pod Sandboxing runs each pod in a Kata Containers VM with runtimeClassName: kata-vm-isolation, on Azure Linux node pools only. On EKS, Fargate gives each pod its own kernel. Microsoft's own guidance is that only a hypervisor boundary is safe for hostile multi-tenant workloads.
Pod Security Admission is built into every supported Kubernetes version. Label each workload namespace to enforce the restricted level and pin enforce-version, so a cluster upgrade cannot change what is allowed without you noticing. On our Kubernetes 1.37 test cluster, the namespace below rejected a pod that set privileged: true and hostPID: true, and the error listed all six violations: host namespaces, privileged, allowPrivilegeEscalation != false, unrestricted capabilities, runAsNonRoot != true and seccompProfile.
The pod below passes restricted and adds a user namespace. Running it on that cluster, /proc/self/uid_map read 0 2001731584 65536: UID 0 in the container was UID 2001731584 on the node. /proc/self/status showed CapEff and CapBnd of zero, NoNewPrivs 1 and Seccomp 2. User namespaces need Linux 6.3 or later on the node, containerd 2.0 or CRI-O 1.25 or later with runc 1.2 or crun 1.9 or later, and filesystems with idmap mount support. They cannot be combined with hostNetwork, hostPID, hostIPC or NFS volumes. AKS documents them for Kubernetes 1.33 and later on Azure Linux 3.0 or Ubuntu 24.04 nodes.
AppArmor has been GA since Kubernetes 1.31 and is set with the appArmorProfile field rather than annotations. It fails closed. Our node had no AppArmor, so the kubelet rejected the pod with Cannot enforce AppArmor: AppArmor is not enabled on the host. Set it only on node pools whose OS runs AppArmor. AKS documents it for Ubuntu and for Azure Linux 3.0 from the 7 November 2025 node image; EKS Auto Mode nodes use SELinux instead.
apiVersion: v1
kind: Namespace
metadata:
name: payments
labels:
pod-security.kubernetes.io/enforce: restricted
pod-security.kubernetes.io/enforce-version: v1.37
pod-security.kubernetes.io/warn: restricted
pod-security.kubernetes.io/warn-version: v1.37
pod-security.kubernetes.io/audit: restricted
pod-security.kubernetes.io/audit-version: v1.37
---
apiVersion: v1
kind: Pod
metadata:
name: api
namespace: payments
spec:
hostUsers: false # user namespace: root in the pod is not root on the node
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 65532
runAsGroup: 65532
seccompProfile:
type: RuntimeDefault
# appArmorProfile: # only on node pools whose OS runs AppArmor (see below)
# type: RuntimeDefault
containers:
- name: api
image: ghcr.io/acme/api@sha256:<digest>
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
resources:
limits:
memory: 256Mi
cpu: 500mPod Security Standards have three fixed levels. They cannot require an approved registry, signed images, user namespaces or resource limits. ValidatingAdmissionPolicy (GA in 1.30) and MutatingAdmissionPolicy (GA in 1.36) run CEL in the API server with no webhook to keep alive. Kyverno and Gatekeeper add image verification and policy reports, and AKS and GKE offer managed versions: the Azure Policy add-on, which runs Gatekeeper, and GKE Policy Controller.
On our 1.37 cluster, the policy below rejected a pod without hostUsers: false in a labeled namespace, with the policy's message, and admitted the compliant pod. It matches pods, so a Deployment that violates it fails when its ReplicaSet creates pods. Add a matching rule for workload templates if you want the error at apply time. The container image hardening guide at /domains/runtime/containers/container-image-hardening/ shows Kyverno signature verification, and the container network and admission guide at /domains/runtime/containers/container-network-admission/ shows a policy for network boundaries.
Inventory the admission webhooks that add-ons install, and check who can reach them. The ingress-nginx case study (/domains/runtime/containers/case-study-ingress-nginx/) shows an unauthenticated webhook reachable from every pod, in a controller that could read every Secret, and the project's retirement in March 2026 means it will not be fixed again.
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicy
metadata:
name: require-user-namespaces
spec:
failurePolicy: Fail
matchConstraints:
resourceRules:
- apiGroups: [""]
apiVersions: ["v1"]
operations: ["CREATE", "UPDATE"]
resources: ["pods"]
validations:
- expression: "has(object.spec.hostUsers) && object.spec.hostUsers == false"
message: "Pods in this namespace must set spec.hostUsers: false"
---
apiVersion: admissionregistration.k8s.io/v1
kind: ValidatingAdmissionPolicyBinding
metadata:
name: require-user-namespaces
spec:
policyName: require-user-namespaces
validationActions: [Deny]
matchResources:
namespaceSelector:
matchLabels:
security.example.com/user-namespaces: required- Assuming 'managed' means hardened. The provider runs the control plane; the defaults for endpoint exposure, local accounts and node access are yours to change.
- Leaving the EKS cluster creator as an implicit administrator, or keeping
aws-authentries after moving to access entries. - Pinning Pod Security to
latest, so an upgrade silently changes what is enforced. - Setting AppArmor or user namespaces cluster-wide without checking every node pool's OS and kernel.
- Reading kube-bench output as a pass rate. WARN means you must check it by hand, and control-plane items on managed clusters are the provider's.
- Blocking metadata with a NetworkPolicy while allowing
hostNetworkpods, which bypass it.
Guided practice
You inherit the EKS cluster described below. Decide what the provider covers, what you must fix first and how you will prove each fix.
Supplied scenario material
aws eks describe-cluster --name prod (excerpt)
version: 1.36
resourcesVpcConfig: endpointPublicAccess=true, publicAccessCidrs=[0.0.0.0/0], endpointPrivateAccess=false
accessConfig: authenticationMode=CONFIG_MAP
logging: all types disabled
Node groups
ng-general: managed node group, AL2023 AMI from 4 months ago, launch template HttpTokens=optional, hop limit 2
ng-batch: self-managed, Ubuntu 24.04, runs customer-supplied scripts
Workloads
payments namespace: no pod-security labels; 3 pods set privileged: true "for performance"
All pods use the node role for S3 access; no Pod Identity or IRSA
kube-bench (eks-1.8.0): 9 FAIL, 31 WARN1. Split responsibility
Which items above does AWS own, and which are yours?
Expected result and reasoning
AWS owns the control plane and etcd. Everything listed is yours: endpoint exposure, authentication mode, logging, node AMI updates, IMDS settings, pod privileges and workload identity.
2. Order the fixes
Pick the three changes that cut the most risk first, and say why.
Expected result and reasoning
Restrict the endpoint (private or known CIDRs) and move to access entries, removing implicit creator access. Require IMDSv2 with hop limit 1 after giving pods their own identity through Pod Identity, because every pod can currently use the node role. Enforce Pod Security restricted on payments after removing
privileged: true. Then patch the AMIs and enable audit logs.3. Isolate ng-batch
How should customer-supplied scripts run?
Expected result and reasoning
On a dedicated, tainted node group with no access to other namespaces' secrets or identities, with user namespaces on Ubuntu 24.04, or in a separate VM boundary such as Fargate or a separate cluster if the code is hostile.
4. Prove it
Name the evidence that closes each fix.
Expected result and reasoning
describe-clustershows the private endpoint and API mode; a privileged pod is rejected with the restricted violations; a pod's metadata token request times out; the S3 calls in CloudTrail show the Pod Identity role; kube-bench FAILs are fixed or recorded with reasons.5. Plan the rollout
How do you avoid an outage when enforcing Pod Security on payments?
Expected result and reasoning
Apply warn and audit labels first, fix the pods the warnings name, then add enforce with a pinned version, using a canary namespace or a dry run with
kubectl label --dry-run=server.
The cluster description is supplied. The Pod Security, user namespace, AppArmor and admission behavior on this page was tested on a local Kubernetes 1.37 cluster; provider CLI commands were checked against current documentation, not run.
Save a draft on this device to resume later. Automatic saving begins after you choose Save. Drafts are never sent by this application; use synthetic data.
No draft saved on this device yet.
Sources & further reading
The standards, specifications and documentation behind the guidance above. Each is the evidence for a claim on this page; consult it for detail beyond the defaults and versions listed here.
- Amazon EKS best practices: security and the shared responsibility model
- Amazon EKS Auto Mode: shared responsibility
- Amazon EKS: grant IAM users and roles access to Kubernetes APIs
- AKS support policies and shared responsibility
- AKS: manage local accounts with Microsoft Entra integration
- AKS: secure container access (user namespaces, AppArmor, seccomp)
- GKE shared responsibility model
- GKE Autopilot security measures
- NSA and CISA Kubernetes Hardening Guide 1.2. Version 1.2 (August 2022).
- Kubernetes: user namespaces. Kubernetes v1.37.
- Kubernetes v1.36: user namespaces are GA. 23 April 2026.
- Kubernetes: Pod Security Admission. Kubernetes v1.37.
- Kubernetes: restrict a container's access to resources with AppArmor. Kubernetes v1.37.
- kube-bench v0.16.0: benchmark versions by platform (cfg/config.yaml)
Reference information
- SDLC phase
- DevelopmentReleaseMaintenance