SELinux in Kubernetes
Required knowledge for the CKS certification.
SELinux (Security-Enhanced Linux) is a Linux kernel security module that restricts the access that a specific subject — such as a process — has to files and other system resources. You define security policies that apply to subjects with specific SELinux labels. When a process that has an SELinux label attempts to access a file, the SELinux server checks whether the process' security policy allows the access and makes an authorization decision independently of standard Unix file permissions.
In Kubernetes, you configure SELinux labels through securityContext.seLinuxOptions, which instructs the container runtime to apply the corresponding label to the container process and its volumes. SELinux is the default mandatory access control (MAC) framework on Red Hat-based distributions — RHEL, CentOS Stream, Fedora, Bottlerocket, and RHEL CoreOS — all of which run SELinux in enforcing mode by default. On these nodes, seLinuxOptions actively constrains what a compromised container process can access on the host.
Kubernetes 1.36 promoted the SELinuxChangePolicy feature gate to GA, enabling efficient mount-time volume labeling for ReadWriteOncePod volumes and adding the seLinuxChangePolicy field to control labeling behavior per Pod.
1. How SELinux Differs from AppArmor
Both AppArmor and SELinux are Linux Security Modules that enforce mandatory access control. They differ in how they identify and confine subjects:
| Aspect | AppArmor | SELinux |
|---|---|---|
| Policy unit | Profile per binary or path | Type Enforcement labels per subject/object |
| Policy application | Defines resources using file paths | Uses the index node (inode) of a resource to identify it |
| Default on | Ubuntu, Debian | RHEL, CentOS Stream, Fedora, Bottlerocket, RHEL CoreOS |
| Kubernetes field | securityContext.appArmorProfile (GA 1.31) | securityContext.seLinuxOptions |
The two frameworks are not alternatives — they address the same problem at different levels of granularity. On distributions that support both, running them together provides deeper defense. In practice, a cluster will use whichever MAC framework its node OS ships with.
2. Configuring SELinux Labels
Set seLinuxOptions in spec.securityContext to apply a label to all containers and volumes in the Pod, or in spec.containers[*].securityContext to override per container.
apiVersion: v1
kind: Pod
metadata:
name: nginx-selinux
spec:
securityContext:
seLinuxOptions:
level: "s0:c123,c456"
containers:
- name: nginx
image: nginx:1.27
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
The seLinuxOptions field accepts four subfields:
| Field | Purpose |
|---|---|
level | Multi-Category Security (MCS) label — the most commonly set field |
type | SELinux type enforcement type |
user | SELinux user identity |
role | SELinux role |
For most Kubernetes workloads, only level needs to be set. The MCS label is given to all containers in the Pod as well as the volumes they mount. Volumes that support SELinux labeling are relabeled to be accessible by the label specified in seLinuxOptions.
Volume sharing with SELinux labels. After you specify an MCS label for a Pod, all Pods with the same label can access the same volume. If you need to enforce isolation between groups of Pods sharing a node, assign each group a unique MCS label:
# Pod A — isolated MCS domain
spec:
securityContext:
seLinuxOptions:
level: "s0:c10,c20"
# Pod B — different MCS domain, cannot access Pod A's volumes
spec:
securityContext:
seLinuxOptions:
level: "s0:c30,c40"
Without an explicit label. When a Pod does not specify seLinuxOptions, the container runtime assigns a unique random MCS label. This preserves isolation between Pods by default without requiring explicit label management — and is the recommended approach when volume sharing is not needed.
3. SELinux and Pod Security Standards
The Baseline Pod Security Standard restricts three of the four seLinuxOptions subfields. Setting restricted values causes Baseline (and Restricted) PSA admission to reject the Pod at create time.
| Restricted Field | Allowed Values |
|---|---|
seLinuxOptions.type (pod and all container levels) | "", container_t, container_init_t, container_kvm_t, container_engine_t (added in Kubernetes 1.31) |
seLinuxOptions.user (pod and all container levels) | "" (must be empty or unset) |
seLinuxOptions.role (pod and all container levels) | "" (must be empty or unset) |
seLinuxOptions.level | Any value — not restricted |
Issue: Specifying a custom type, a non-empty user, or a non-empty role breaks Baseline compliance.
Fix: Leave type, user, and role unset. Set only level for MCS-based workload isolation. The container runtime fills in the appropriate type automatically from the allowed set.
apiVersion: v1
kind: Pod
metadata:
name: baseline-compliant
namespace: psa-baseline
spec:
securityContext:
seLinuxOptions:
# Only level is set — type/user/role left empty for PSS Baseline compliance
level: "s0:c200,c300"
containers:
- name: app
image: myapp:1.0
securityContext:
runAsNonRoot: true
allowPrivilegeEscalation: false
4. Volume Labeling: seLinuxChangePolicy
When Kubernetes applies a SELinux label to a Pod's volumes, it historically performed a recursive relabeling pass that changed the SELinux label on every inode in the volume tree. For large volumes or remote filesystems, this traversal can delay Pod startup by minutes.
Kubernetes 1.36 added the seLinuxChangePolicy field (GA) to spec.securityContext, controlling how the label is applied to volumes:
| Value | Behavior |
|---|---|
MountOption (or unset) | Mounts the volume with -o context=<label>, a constant-time kernel-level operation with no recursive traversal |
Recursive | Forces the original recursive inode relabeling — use when applications depend on recursive relabeling semantics |
Feature gate status in Kubernetes 1.36
| Feature Gate | Status in v1.36 | Default |
|---|---|---|
SELinuxMountReadWriteOncePod | GA | Enabled |
SELinuxChangePolicy | GA | Enabled |
SELinuxMount | Beta | Disabled |
SELinuxMountReadWriteOncePod (GA, enabled) applies fast mount-time labeling to ReadWriteOncePod volumes automatically. SELinuxMount (Beta, disabled by default in 1.36) extends this to all volume types and is anticipated to be enabled by default in a future release. When it is enabled by default, seLinuxChangePolicy: Recursive becomes the opt-out for workloads that depend on the legacy relabeling behavior.
Before (default before SELinuxMountReadWriteOncePod was GA): The kubelet recursively relabeled every inode in the volume tree at Pod startup — O(n) in the number of files.
After (GA in 1.36 for ReadWriteOncePod volumes): The kubelet mounts the volume with -o context=<label>, applying the label at mount time in constant time regardless of file count.
When to keep seLinuxChangePolicy: Recursive
Opt out of fast mount labeling when your cluster has either of the following patterns:
- Pods on the same node sharing a PersistentVolumeClaim with different SELinux labels
- Privileged and unprivileged Pods reading from the same PersistentVolumeClaim
apiVersion: v1
kind: Pod
metadata:
name: shared-volume-legacy
spec:
securityContext:
seLinuxChangePolicy: Recursive # preserve recursive relabeling
seLinuxOptions:
level: "s0:c100,c200"
containers:
- name: app
image: app:1.0
volumes:
- name: shared-data
persistentVolumeClaim:
claimName: shared-pvc
CSI volumes: opt-in required
For CSI-provisioned volumes to use fast mount labeling, the CSIDriver object must explicitly opt in:
apiVersion: storage.k8s.io/v1
kind: CSIDriver
metadata:
name: csi.example.com
spec:
seLinuxMount: true
Volumes backed by CSI drivers that do not set spec.seLinuxMount: true continue to use recursive relabeling regardless of the Pod's seLinuxChangePolicy.
5. Audit: Finding Non-Compliant SELinux Configuration
Check all Pods in the cluster for seLinuxOptions fields that violate PSS Baseline (non-empty user or role, or type outside the allowlist):
kubectl get pods -A -o json | jq -r '
.items[] | select(
(.spec.securityContext.seLinuxOptions.user // "") != "" or
(.spec.securityContext.seLinuxOptions.role // "") != "" or
((.spec.securityContext.seLinuxOptions.type // "") |
test("^(container_t|container_init_t|container_kvm_t|container_engine_t|)$") | not) or
([.spec.containers[].securityContext.seLinuxOptions.user // ""] | any(. != "")) or
([.spec.containers[].securityContext.seLinuxOptions.role // ""] | any(. != ""))
) |
[.metadata.namespace, .metadata.name] | join("/")
'
Identify Pods that set seLinuxOptions at all (useful for auditing label consistency across a namespace):
kubectl get pods -A -o json | jq -r '
.items[] |
select(.spec.securityContext.seLinuxOptions != null) |
[.metadata.namespace, .metadata.name,
(.spec.securityContext.seLinuxOptions.level // "no-level")] |
join("\t")
'
6. Verification
After applying seLinuxOptions to a Pod, confirm the label is reflected in the running spec:
kubectl get pod <pod-name> -n <namespace> \
-o jsonpath='{.spec.securityContext.seLinuxOptions}{"\n"}'
Confirm the Pod's containers started without a PSA rejection by checking events:
kubectl get events -n <namespace> --field-selector reason=Failed \
--sort-by='.metadata.creationTimestamp'
A PSA Baseline violation due to a forbidden seLinuxOptions value produces an event with a message similar to pods "<name>" is forbidden: violates PodSecurity "baseline:latest".
Verify which nodes on the cluster run a SELinux-default operating system:
kubectl get nodes -o custom-columns=\
'NAME:.metadata.name,OS:.status.nodeInfo.osImage'
Nodes reporting RHEL, CentOS Stream, Fedora, Bottlerocket, or RHEL CoreOS in the OS column run SELinux in enforcing mode by default. Nodes running Ubuntu use AppArmor instead. On nodes where SELinux is not enforcing, seLinuxOptions values are written to the container spec but are not enforced by the kernel.

References
This article is based on information from the following official sources:
- SELinux Volume Label Changes goes GA (and likely implications in v1.37) - Kubernetes Blog
- Linux kernel security constraints for Pods and containers - Kubernetes Documentation
- Configure a Security Context for a Pod or Container - Kubernetes Documentation
- Pod Security Standards - Kubernetes Documentation