| Tested on | Rocky Linux 10.2 (Red Quartz) workstation |
|---|---|
| Package | kubectl 1.36.3 |
| Node runtime | containerd://2.2.5; Linux kernel 6.12.0-211.34.1.el10_2.0.1.x86_64 (worker node) |
| Applies to | Any host with kubectl configured; any Kubernetes cluster |
| Cert prep | CKA · CKAD · CKS |
| Lab environment | Multi-node kubeadm cluster with containerd — install Kubernetes with kubeadm |
| Privilege | Normal user (no sudo required on the workstation) |
| Scope | Container-level capabilities.add and capabilities.drop, runtime capability inspection, drop ALL, safe CHOWN add-back, NET_BIND_SERVICE and runtime sysctl behaviour, Pod Security Admission restrictions, SYS_ADMIN implications, and privileged comparison. Does not cover full SecurityContext configuration, custom seccomp profiles, admission-policy setup, or Windows containers. |
| Related guides | Authentication and admission control Kubernetes RBAC Kubernetes Pods and lifecycle |
Traditional root privilege on Linux is split into individual capabilities. Kubernetes lets you grant or remove those privileges per container through securityContext.capabilities, without making the entire container privileged. This walkthrough inspects what containerd grants on this cluster, drops every default capability, then adds back only what a specific operation needs.
Create namespace cap-lab for the examples:
kubectl create namespace cap-labSample output:
namespace/cap-lab createdUnderstand Linux Capabilities
Container-Level Scope
On Linux, root is not a single on/off switch. Capabilities such as CHOWN and NET_BIND_SERVICE gate relatively focused operations, while SYS_ADMIN covers a broad collection of kernel administration functions.
In Kubernetes:
- Capabilities are configured under the container-level
securityContext. - They are Linux-specific; there is no Pod-level
capabilitiesfield. - Each container in the Pod, including sidecars and init containers, carries its own
addanddroplists.
For runAsUser, fsGroup, readOnlyRootFilesystem, and other non-capability fields, see SecurityContext with practical examples.
Runtime Defaults Can Differ
Kubernetes does not ship one fixed starting capability list for every cluster.
The container runtime (containerd on this lab) applies its own defaults when the Pod spec omits capability rules. Those defaults can change when you upgrade the runtime, switch CRI implementations, or adjust node configuration.
Any capability mask in this article is observed output from containerd://2.2.5 on this cluster, not a guarantee for your environment. Always inspect the running container on your own nodes before you harden production manifests.
Understand the Capability Sets
When you read /proc/1/status or capsh --print, several capability sets appear:
- Inheritable (
CapInh) — capabilities that can participate in capability inheritance acrossexecve().- Permitted (
CapPrm) — the maximum set the process can place in its effective set.- Effective (
CapEff) — capabilities the kernel checks when performing permission checks.- Bounding (
CapBnd) — limits capabilities that can be gained when another executable is run.- Ambient (
CapAmb) — capabilities preserved across execution of a non-privileged program when Linux inheritance rules permit it.
Inspect Current Container Capabilities
Read Capability Masks
Start with a diagnostic container and read the capability masks from /proc/1/status:
kubectl run cap-busybox-default -n cap-lab --image=busybox:1.36 --restart=Never --command -- sh -c "grep ^Cap /proc/1/status; sleep 3600"Sample output:
pod/cap-busybox-default createdWait for the Pod to become Ready, then read the logs:
kubectl wait --for=condition=Ready pod/cap-busybox-default -n cap-lab --timeout=60sSample output:
pod/cap-busybox-default condition metkubectl logs -n cap-lab cap-busybox-defaultSample output (observed on containerd://2.2.5):
CapInh: 0000000000000000
CapPrm: 00000000a80425fb
CapEff: 00000000a80425fb
CapBnd: 00000000a80425fb
CapAmb: 0000000000000000Display Human-Readable Capability Names
When the image includes capsh, a human-readable summary is easier to read. The Fedora Minimal image below installs libcap at startup, then prints the Current line:
apiVersion: v1
kind: Pod
metadata:
name: cap-capsh-default
namespace: cap-lab
spec:
restartPolicy: Never
containers:
- name: app
image: quay.io/fedora/fedora-minimal:44
command:
- sh
- -c
- |
microdnf install -y libcap >/dev/null &&
capsh --print | grep '^Current:'This example requires outbound access to the Fedora package repositories while the container starts.
kubectl apply -f cap-capsh-default.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cap-capsh-default -n cap-lab --timeout=120sSample output:
pod/cap-capsh-default created
pod/cap-capsh-default condition metkubectl logs -n cap-lab cap-capsh-defaultSample output (trimmed, observed on containerd://2.2.5):
Current: cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap=epThe Current line shows the capability set observed by this process after Kubernetes and the container runtime started it.
Configure Capabilities in Kubernetes
Add and Drop YAML
Capability changes belong on a specific container:
securityContext:
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEKeep these naming rules in mind:
- Linux documentation uses
CAP_NET_BIND_SERVICE. - Kubernetes YAML omits the
CAP_prefix — writeNET_BIND_SERVICE. addanddropapply to one container only; copy the block to every container that needs it.
Drop All Runtime Defaults
The primary least-privilege baseline is drop: ["ALL"], which removes every capability the runtime would otherwise grant.
apiVersion: v1
kind: Pod
metadata:
name: cap-drop-all
namespace: cap-lab
spec:
restartPolicy: Never
containers:
- name: app
image: busybox:1.36
command: ["sh", "-c", "grep ^Cap /proc/1/status"]
securityContext:
capabilities:
drop: ["ALL"]kubectl apply -f cap-drop-all.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cap-drop-all -n cap-lab --timeout=60sSample output:
pod/cap-drop-all created
pod/cap-drop-all condition metkubectl logs -n cap-lab cap-drop-allSample output:
CapInh: 0000000000000000
CapPrm: 0000000000000000
CapEff: 0000000000000000
CapBnd: 0000000000000000
CapAmb: 0000000000000000With drop: ["ALL"], chown is denied. Apply a Pod that attempts to change ownership:
apiVersion: v1
kind: Pod
metadata:
name: cap-chown-fail
namespace: cap-lab
spec:
restartPolicy: Never
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- |
touch /tmp/cap-test
chown 1000 /tmp/cap-test 2>&1
securityContext:
capabilities:
drop: ["ALL"]kubectl apply -f cap-chown-fail.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Failed pod/cap-chown-fail -n cap-lab --timeout=60sSample output:
pod/cap-chown-fail created
pod/cap-chown-fail condition metkubectl logs -n cap-lab cap-chown-failSample output:
chown: /tmp/cap-test: Operation not permittedOrdinary application behavior that does not need elevated kernel privileges continues to work.
Add Back CHOWN
Add CHOWN back when the workload must change file ownership:
apiVersion: v1
kind: Pod
metadata:
name: cap-chown-yes
namespace: cap-lab
spec:
restartPolicy: Never
containers:
- name: app
image: busybox:1.36
command:
- sh
- -c
- |
touch /tmp/cap-test
chown 1000 /tmp/cap-test
stat -c '%u' /tmp/cap-test
securityContext:
capabilities:
drop: ["ALL"]
add: ["CHOWN"]kubectl apply -f cap-chown-yes.yaml
kubectl wait --for=jsonpath='{.status.phase}'=Succeeded pod/cap-chown-yes -n cap-lab --timeout=60s
kubectl logs -n cap-lab cap-chown-yesSample output:
pod/cap-chown-yes created
pod/cap-chown-yes condition met
1000CHOWN restores only the permission needed to change file ownership. Do not test SYS_TIME by changing the clock on a Kubernetes node; inspect its capability mask instead.
NET_RAW permits raw and packet sockets, but ping is not a reliable capability test on current runtimes. Containerd can configure net.ipv4.ping_group_range so ordinary processes can send ICMP echo requests without NET_RAW. Test an actual raw-socket operation if your workload requires this capability.
The safe CHOWN before-and-after lab above proves the drop ALL and selective add-back workflow without relying on runtime networking defaults.
NET_BIND_SERVICE and Runtime Sysctls
NET_BIND_SERVICE allows binding to TCP/UDP ports below 1024. It appears in the observed default set on this cluster (cap_net_bind_service in the Current line above).
After drop: ["ALL"], add it back only when the workload truly needs a low port:
securityContext:
runAsUser: 1000
runAsNonRoot: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICEOn current containerd CRI configurations, net.ipv4.ip_unprivileged_port_start is commonly set to 0 inside the Pod network namespace. In that case, a non-root process can bind to port 80 even when every capability mask is zero, so a successful low-port bind does not prove that NET_BIND_SERVICE is effective.
Inspect the actual value before testing:
kubectl exec cap-busybox-default -n cap-lab -- cat /proc/sys/net/ipv4/ip_unprivileged_port_startObserved output on the tested containerd://2.2.5 cluster:
0Containerd 2.x enables this value by default for eligible CRI containers unless the runtime configuration overrides it.
A value of 0 allows unprivileged binding across the complete port range. Keep the NET_BIND_SERVICE YAML pattern for workloads and policy profiles that require it, but do not present an uncontrolled port-80 bind as a portable before-and-after capability test.
Pod Security Admission Restrictions
These examples focus on capability behavior and are not complete Restricted-profile manifests. A namespace enforcing Restricted also requires allowPrivilegeEscalation: false, runAsNonRoot: true, an approved seccomp profile, and drop: ["ALL"]; only NET_BIND_SERVICE may be added back. Run this lab in a namespace that does not enforce Restricted, or adapt every Pod to the complete Restricted requirements.
SYS_ADMIN and Privileged Containers
allowPrivilegeEscalation and Other Security Controls
SYS_ADMIN is a broad capability tied to many kernel administration paths. It is not a generic fix for file permission errors or volume mount problems.
Relationships worth remembering:
- Prefer a narrower capability or a design change before you add
SYS_ADMIN. - Kubernetes treats
allowPrivilegeEscalationas always true when the container is privileged or hasCAP_SYS_ADMIN. - Seccomp, AppArmor, SELinux, Pod Security Admission, and
runAsUserall interact with the effective security posture. A capability grants a Linux privilege; it does not bypass every isolation layer.
This article does not include exploit or host-escape demonstrations. Treat any workload that requests SYS_ADMIN as a security review item.
Capabilities Compared with Privileged Mode
| Capability configuration | Privileged container |
|---|---|
| Grants selected privileges | Receives all Linux capabilities |
| Normal seccomp and other controls can still apply | Overrides several isolation controls |
| Narrower blast radius | Broad host-level access |
| Preferred when one capability is sufficient | Reserved for exceptional workloads |
privileged: true is not equivalent to adding one capability. The SecurityContext guide covers privileged, runAsNonRoot, and read-only root filesystem settings separately.
Verify and Troubleshoot Capability Changes
Repeatable Test Sequence
Use this repeatable sequence on your cluster:
- Run a container with no capability rules and record the observed
Currentline orCapEffmask. - Apply
drop: ["ALL"]and confirm the effective set changed. - Trigger an operation that needs a removed capability and confirm it fails.
- Add one capability with
add. - Re-run the same operation and confirm it succeeds.
- Confirm unrelated capabilities remain absent.
On this cluster, dropping all capabilities changed CapEff from 00000000a80425fb to all zeros, chown failed, and adding CHOWN restored only file-ownership changes.
Common Capability Mistakes
| Mistake | What goes wrong |
|---|---|
Writing CAP_NET_BIND_SERVICE in YAML |
API expects NET_BIND_SERVICE without the CAP_ prefix |
Placing capabilities under Pod-level spec.securityContext |
capabilities is not a valid PodSecurityContext field; normal strict kubectl validation rejects it. Configure securityContext.capabilities separately on each container |
| Assuming root means every capability | Effective caps still follow runtime defaults unless privileged |
| Publishing one universal default list | Runtime and version differences make static lists inaccurate |
Using SYS_ADMIN as a broad workaround |
Grants far more than most apps need |
| Adding a capability while seccomp blocks the syscall | Operation still fails; check multiple layers |
| Hardening only the main container | Sidecars and init containers keep runtime defaults |
Testing SYS_TIME with date -s on a cluster node |
Can change the worker node clock and disrupt kubelet, certificates, and logs |
What's Next
- Kubernetes Pod Security Standards and Admission
- Kubernetes Secrets with Examples
- Kubernetes Multi-Tenancy and Workload Isolation
References
- Configure a Security Context for a Pod or Container — capability fields and allowPrivilegeEscalation
- Pod Security Standards — allowed capability additions under Baseline and Restricted profiles
- Using sysctls in a Kubernetes Cluster — safe network sysctls
- Linux capabilities — capability semantics and process capability sets
- Linux time namespaces — clocks that are and are not virtualized
- containerd 2.0 changes — unprivileged port and ICMP defaults
- kubectl wait — readiness and phase waits
Summary
Linux capabilities let you grant narrow kernel privileges without handing a container full root access. Kubernetes configures them per container through securityContext.capabilities.add and drop, not at the Pod level.
You inspected the runtime-provided set on a containerd://2.2.5 cluster, dropped every default with drop: ["ALL"], and confirmed that chown failed until you added back CHOWN. The NET_BIND_SERVICE YAML pattern is the same; verify low-port binding on your nodes because sysctl settings can change the outcome.
SYS_ADMIN and privileged: true sit at the opposite end of the spectrum from drop: ["ALL"] plus a single add. For runAsNonRoot, read-only root filesystem, and seccomp profiles, continue with the SecurityContext walkthrough.

