Skip to main content

SELinux Volume Labels in Kubernetes 1.36: Audit Your Multi-Tenant Storage Before 1.37

9 min readDora NodaDora Noda
Share
On this page

Kubernetes 1.37 is not a release to discover that two Pods have been sharing a volume in a way your node security model cannot represent. It shipped on August 26 with a change that makes a good optimization the default: eligible volumes can be mounted with the Pod's SELinux context instead of having a runtime walk and relabel every file. That turns a slow startup path into an immediate mount—but it also makes one old shared-volume pattern fail visibly instead of continuing by accident.

For a self-hosted PaaS, that is useful pressure. A platform should know exactly which workload owns a volume, which context sees it, and whether a privileged operational Pod shares it with a tenant workload. The upgrade is not a blanket security win by itself; its value is that it makes incompatible label sharing a deterministic condition that an operator can find and handle before it becomes a deployment outage.

The answer first: what changes between 1.36 and 1.37

Here is the operator-facing difference. Kubernetes 1.36 is the audit release; 1.37 is the default-behavior release.

ReleaseWhat is stable or enabledWhat happens to an eligible volumeAction for a PaaS operator
1.36SELinuxMountReadWriteOncePod and SELinuxChangePolicy are GA. The broader SELinuxMount gate remains Beta and off by default.A ReadWriteOncePod volume can use the mount option. Other eligible volumes retain recursive relabeling unless the broader gate is deliberately enabled.Turn on the warning controller, inventory CSI support, and find label conflicts while the safe fallback is still the default.
1.37SELinuxMount is GA and enabled by default for eligible volumes.Kubelet supplies -o context=<label>; two different contexts cannot occupy the same mount on one node. A conflicting Pod can remain ContainerCreating.Fix the storage topology or apply the narrow Recursive opt-out before upgrading node pools.

This is not a change for every cluster. Kubernetes skips this logic when SELinux is absent or disabled on the Linux node. It also does not apply just because a cluster has a securityContext: the Pod, its PVC, and the storage driver must meet the eligibility rules. The upstream 1.36 guidance and 1.37 release page are worth reading together because they explain the staging and the final default separately.

Why a mount label changes both startup time and compatibility

SELinux is a mandatory access-control system: processes and objects carry labels, and policy decides whether one label may access another. Historically, Kubernetes passed a Pod's context to the container runtime, which then recursively relabeled the files visible in the Pod's volumes. On a large or remote filesystem, walking every directory and inode can delay startup substantially.

The newer path is different. Kubelet asks the volume implementation to mount with -o context=<label>, so the kernel presents the needed context through that mount without a recursive traversal. The upstream project describes that as constant-time setup for qualifying mounts—not a universal speed claim for every storage class or filesystem. Kubernetes' feature-gate reference calls out the same distinction: the new path widens the optimization from ReadWriteOncePod volumes to eligible volumes more generally.

Before assuming a workload takes that path, confirm all of these conditions:

  • The node runs Linux with SELinux available and enforcing.
  • The workload uses a PVC. Secret, ConfigMap, and projected volumes do not use this mount optimization.
  • The access mode and feature-gate state make the PVC eligible. ReadWriteOncePod is the low-risk case; the broader path is the 1.37 default.
  • Kubernetes knows an SELinux label—at minimum, the Pod or its relevant containers specify seLinuxOptions.level.
  • The CSI driver explicitly opts in with spec.seLinuxMount: true. A driver that represents a directory inside a larger shared filesystem normally should not claim that capability.

That last condition belongs in the platform's storage acceptance criteria. The CSIDriver API says a driver that advertises seLinuxMount: true must be able to mount its volumes separately with different context options. Kubelet verifies the result; a driver that advertises support but cannot honor it leaves affected Pods unable to start. “Set the field everywhere” is therefore not a migration plan.

The compatibility trap: one shared volume, two labels

The important break is not “a Pod has SELinux.” It is “two Pods need one eligible mounted volume on the same node, but need different SELinux contexts.” Recursive relabeling historically allowed two patterns that the mount-option model deliberately cannot preserve:

  • Two Pods use different subPath values from the same volume and have different labels.
  • A privileged operations Pod and an unprivileged application Pod share a volume.

Consider a platform-managed application that writes import files to a shared PVC while a privileged repair or migration job mounts the same claim. The app has a container label; the repair job has the privileged spc_t type. With the old recursive path, that arrangement could run. With an SELinux context mount, one mount has one context. If both Pods land on the same node, kubelet must refuse one rather than present the shared mount as both labels. The blocked Pod stays in ContainerCreating until the conflict is removed.

That is the multi-tenant hardening step in practical terms: the platform no longer quietly proceeds with a shared mount arrangement whose access contexts conflict. It is not a substitute for per-tenant volume ownership, namespace isolation, admission control, or a careful privileged-access design. It is a fail-closed boundary that makes a bad sharing shape visible.

When an application genuinely requires the old pattern, make the exception explicit and local to that Pod instead of disabling the new behavior fleet-wide:

yaml
apiVersion: v1
kind: Pod
metadata:
  name: legacy-volume-maintenance
spec:
  securityContext:
    seLinuxChangePolicy: Recursive
    seLinuxOptions:
      level: "s0:c42,c77"
  containers:
    - name: maintenance
      image: example/maintenance:1.0
      securityContext:
        privileged: true
      volumeMounts:
        - name: shared-data
          mountPath: /data
  volumes:
    - name: shared-data
      persistentVolumeClaim:
        claimName: tenant-imports

Recursive is a compatibility escape hatch, not a default policy to stamp into every generated manifest. It gives an operator a reviewable exception with an owner and an expiry path. A platform should also ask whether a privileged repair job needs to share a tenant volume at all; a one-shot job scheduled after the app releases the claim is often a cleaner design.

Run this audit before a node-pool upgrade

The audit has four parts. Do it per node pool and storage class, not once for a whole fleet, because mixed operating systems and CSI drivers produce different answers.

FindingMeaningNext move
SELinux disabled or unavailable on the node imageThis feature has no effect for that pool.Record the exemption; do not report it as a completed SELinux audit.
CSI driver does not advertise seLinuxMount: trueKubernetes retains recursive relabeling for that driver.Validate driver roadmap before expecting the startup optimization.
Warning controller sees no conflictsEligible workloads are ready for the default mount path.Stage 1.37 in a canary pool and watch kubelet events.
Controller finds a conflictA scheduler placement can create a blocked Pod after the default changes.Split the volume use, align labels, serialize access, or add a narrowly scoped Recursive policy.

First, inventory node operating-system policy. Kubernetes does not infer “SELinux enforcing” from a namespace label; inspect the immutable image and node-pool configuration, then confirm it on a representative node with your approved host-debug procedure. Pools based on a non-SELinux image should be documented as unaffected, while enforcing pools go through the rest of the checklist.

Second, list the storage drivers and their declaration:

sh
kubectl get csidriver -o jsonpath='{range .items[*]}{.metadata.name}{"\t"}{.spec.seLinuxMount}{"\n"}{end}'

An empty or false value is meaningful: it says Kubernetes must not pass the context mount option to that driver. Match the true entries to the StorageClasses and PVCs tenants actually use; a capability on an unused driver does not make an application eligible.

Third, enable the opt-in selinux-warning-controller in kube-controller-manager on clusters that use SELinux. The upstream configuration is --controllers=*,selinux-warning-controller. It emits Events for potentially incompatible Pods and exposes the selinux_warning_controller_selinux_volume_conflict metric. Scrape the controller-manager metrics through the same authenticated monitoring path used for your control plane, alert on a nonzero value, and inspect the associated Pods and claims. The Kubernetes security-context documentation specifically recommends reaching zero conflicts before moving to 1.37 or later.

Finally, test the remediation on a non-production copy of each identified topology. An event is deliberately conservative: it can warn about Pods scheduled on different nodes because a later reschedule could co-locate them. Treat that as an upgrade test case, not noise to suppress.

Make the PaaS contract clearer, not broader

For a git-push platform, the safest default is usually simple: each deployed service gets its own claim, and platform maintenance never shares a writable tenant mount while the service is live. That design makes SELinux context mounting a startup improvement and a useful guardrail, not a compatibility gamble.

The tricky cases deserve an explicit contract: which system jobs may mount tenant data, whether they are privileged, whether the mount is exclusive, and which StorageClasses permit multi-Pod sharing. Put seLinuxChangePolicy: Recursive behind the same review path as any other privileged storage exception. Do not use it to hide a controller alert and assume the security story is complete.

A staged rollout also keeps the operational lesson small. Enable the warning controller on 1.36-capable control planes; resolve or document each conflict; upgrade a canary node pool; then watch for ContainerCreating Pods and volume-mount errors before advancing the fleet. Kubernetes' KEP recorded warning-controller conflicts in only a small minority of observed OpenShift clusters, but “rare” is not the same as “safe to skip”—a single shared volume can block a production deploy. The SELinux relabeling KEP is especially helpful for the precise phases, telemetry, and rollback behavior.

Kubernetes 1.37 makes the fast, context-mounted path the normal one for eligible storage. Audit it as a storage-design change, use Recursive only as a named compatibility decision, and let the warning controller find the ambiguous mounts while you still have time to fix them.

Bex.co is the open-source, AI-native Render alternative: push a git repo and get a running HTTPS service on machines you own. Its Cluster-API foundation makes node-pool and storage behavior something operators can inspect and control, rather than an opaque platform assumption. Star the project on GitHub or explore the deployment model at Bex.co.

Sources

Related articles

Run this on infrastructure you own

bex is the open-source, AI-native Render alternative — push a git repo and get a running HTTPS service on your own machines.

Get started with bex