Jacob Garber

Does Kubernetes Work With SELinux?

Yes.

The official instructions for kubeadm say to disable SELinux before doing the installation and give a big fat warning that:

  • Setting SELinux in permissive mode by running setenforce 0 and sed ... effectively disables it. This is required to allow containers to access the host filesystem; for example, some cluster network plugins require that. You have to do this until SELinux support is improved in the kubelet.
  • You can leave SELinux enabled if you know how to configure it but it may require settings that are not supported by kubeadm.

The second sentence essentially tells the story: there are a few steps you need to do to make Kubernetes and SELinux interact happily, but it’s not something that kubeadm can do by itself, so the devs are preemptively disabling SELinux to avoid floods of GitHub issues of why people’s clusters aren’t working. With a little bit of effort (and I mean only a little), it is perfectly possible to make Kubernetes work with SELinux. The existing documentation about it is not great, so it me quite a while to realize that. For simplicity here I will assume you are using a RHEL-based system.

A quick recap: in SELinux, every process, file, and port have a type, and policies define which process types are allowed to access which file and port types. There are many different policies that come pre-installed on a RHEL system, and fortunately there is the container-selinux package that comes with policies for containers. There are two container types relevant to us:

  • container_t: This is the default type that most containers run with, and it lets them access container file types. An SELinux compatible CRI will automatically launch most containers in the cluster with this type, and except for a few exceptions it will “just work”.
  • spc_t: This is a “super privileged container”—it is unconfined by SELinux and can do whatever it wants. Clearly we want to minimize the number of containers that run with this type, so it should only be used by administrative containers that interact with the host to manage the cluster itself.

Let’s install a cluster then. Here we will be using CRI-O, which is a CRI developed by RedHat that is of course very compatible with SELinux (though I think containerd works too).

  1. Install container-selinux, cri-o, kubelet, kubeadm, and kubectl using these instructions.
  2. Bootstrap a cluster with kubeadm like normal. CRI-O will launch the control pods as spc_t since their manifests set hostNetwork: true.
  3. Install a CNI like Cilium. The Cilium pods are set to run as spc_t since they need to interact with the host network.1

And that’s it? Almost. For security reasons, containers with the container_t type are not allowed to access the underlying host, but on a bare-metal cluster we usually need to install extra administrative pods that do need access (e.g. a PersistentVolume provider). Some of these containers will run as spc_t automatically, such as ones that set privileged: true like TopoLVM, but some won’t. For example, Alloy attempts to mount hostPath volumes to scrape your host logs, and a quick look at the audit logs using ausearch -m AVC,USER_AVC -ts recent -i shows a denial.

----
type=PROCTITLE msg=audit(2025-07-03 03:34:58.695:2086) : proctitle=/bin/alloy run /etc/alloy/config.alloy --storage.path=/tmp/alloy --server.http.listen-addr=0.0.0.0:12345 --server.http.ui-path-p
type=SYSCALL msg=audit(2025-07-03 03:34:58.695:2086) : arch=x86_64 syscall=bpf success=no exit=EACCES(Permission denied) a0=BPF_MAP_CREATE a1=0xc000b1fd90 a2=0x50 a3=0x0 items=0 ppid=7511 pid=7513 auid=unset uid=root gid=root euid=root suid=root fsuid=root egid=root sgid=root fsgid=root tty=(none) ses=unset comm=alloy exe=/usr/bin/alloy subj=system_u:system_r:container_t:s0:c269,c920 key=(null)
type=AVC msg=audit(2025-07-03 03:34:58.695:2086) : avc:  denied  { map_create } for  pid=7513 comm=alloy scontext=system_u:system_r:container_t:s0:c269,c920 tcontext=system_u:system_r:container_t:s0:c269,c920 tclass=bpf permissive=0

The simplest way to fix this is to manually override its security context to run as spc_t in the Helm chart.

alloy:
  securityContext:
    seLinuxOptions:
      type: spc_t

Of course this is somewhat of a blunt instrument, since spc_t removes all SELinux restrictions, not just the ones that Alloy needs relaxed. Ideally you’d create a custom policy using something like udica that is tailored for your specific application.2 Using spc_t though works in a pinch and is much better than disabling SELinux entirely.

For curiosity’s sake, you can run ps -eZ | grep -e spc_t -e container_t on the host see the SELinux types of the container processes. Here we can see the control plane pods, cilium, and alloy running as spc_t as expected, and core-dns and a utility pod running as container_t since they don’t need host access.

system_u:system_r:spc_t:s0         3642 ?        00:02:08 kube-controller
system_u:system_r:spc_t:s0         3653 ?        00:01:30 kube-scheduler
system_u:system_r:spc_t:s0         3668 ?        00:02:58 etcd
system_u:system_r:spc_t:s0         3684 ?        00:05:36 kube-apiserver
system_u:system_r:spc_t:s0         3777 ?        00:00:01 kube-proxy
system_u:system_r:spc_t:s0         4977 ?        00:00:12 cilium-operator
system_u:system_r:spc_t:s0         5026 ?        00:01:30 cilium-agent
system_u:system_r:spc_t:s0         5384 ?        00:00:00 cilium-health-r
system_u:system_r:spc_t:s0         5467 ?        00:00:00 cilium-envoy-st
system_u:system_r:spc_t:s0         5471 ?        00:00:12 cilium-envoy
system_u:system_r:container_t:s0:c120,c730 5523 ? 00:00:07 coredns
system_u:system_r:container_t:s0:c321,c358 5536 ? 00:00:07 coredns
system_u:system_r:spc_t:s0:c51,c687 8739 ?       00:00:00 alloy
system_u:system_r:container_t:s0:c51,c687 8751 ? 00:00:00 prometheus-conf

And there you have it, now you can run a Kubernetes cluster with one less tear from Dan Walsh.

1

You may need to restart the cri-o and kubelet systemd services to pick up the new CNI.

2

For example, RKE2 runs its control plane pods with a custom type that is more restricted than spc_t.