Production Server Setup Notes, Ep. 4 - What Is SELinux?
Contents
Also available in Korean: 운영계 세팅 회고 ep4 - SELinux란?
Overview
The production server was set to run SELinux in Enforcing mode.
The development server runs in Permissive mode, so containers that worked fine there end up blocked with Permission denied in production.
With SELinux in force, access can be blocked even after opening file permissions with chmod.
That is because access is checked once more, separately from file permissions.
DAC and MAC
Linux’s basic permission model is DAC (Discretionary Access Control).
The file owner decides with rwx who may do what.
SELinux adds MAC (Mandatory Access Control) on top.
Here the system policy, not the file owner, decides what is allowed, and root cannot bypass the check.
With SELinux on, every access request must pass DAC and then the SELinux policy to be allowed.
Labels: what SELinux looks at
SELinux attaches a label (context) to every process and file.
A label has four fields, user:role:type:level, and you can see them with ls -Z (files) and ps -Z (processes).
Decisions are mostly made on the third field, the type.
The policy is an allow list of the form “processes of this type may do these actions on files of that type”.
This is called type enforcement.
Any action without an allow rule in the policy is denied.
The three modes
| mode | behavior |
|---|---|
| Enforcing | allow/deny by policy && log denials |
| Permissive | allow even policy violations && log violations |
| Disabled | no checks |
getenforce shows which mode a server is running in.
In Permissive mode policy violations are only logged, not actually blocked.
So if the development server is Permissive, actions that production (Enforcing) would block never show up during development.
A mode changed with setenforce reverts on reboot. The persistent setting lives in /etc/selinux/config.
Identifying denials
Actions SELinux blocks are recorded in the audit log as denial records of type AVC.
ausearch -m AVC -ts today shows today’s denials.
The AVC (Access Vector Cache) is where SELinux caches the answer to "may this type perform this action on that type". Denial records carry its name.
avc: denied { read } for pid=4121 comm="otelcol" name="pods"
scontext=system_u:system_r:container_t:s0:c12,c34
tcontext=system_u:object_r:container_log_t:s0 tclass=dir
scontext: who (the label of the requesting process)tcontext: what (the label of the target){ read },tclass=dir: which action, on what kind of target
This record shows a container (container_t) being denied a read on the pod log directory (container_log_t).
The policy also has deny rules that are deliberately not logged (dontaudit). If something is blocked but no AVC record appears, suspect this.
Fixing denials
Red Hat’s official documentation says to fix SELinux denials by checking for a labeling problem first, then booleans, and only then a policy module.
Each step allows more than the one before.
1. Fix labels
Most denials come from files carrying the wrong label.
Register a labeling rule for the path with semanage fcontext and apply it with restorecon.
semanage fcontext -a -t container_file_t "/srv/data(/.*)?"
restorecon -Rv /srv/data
chcon can change a label too, but it is not registered as a rule, so a later restorecon or full relabel puts the old label back.
2. Turn on a boolean
The policy includes prebuilt switches (booleans) for commonly needed permissions.
For example, if a web server has to make outbound connections, turn it on with setsebool -P httpd_can_network_connect on.
getsebool -a lists them.
3. Add a policy module
If labels and booleans are not enough, build a policy module from the denial records.
audit2allow reads the AVC records and generates allow rules, and semodule -i installs the module.
It allows everything that was logged, so read the generated rules and keep only what is needed.
Turning SELinux off or switching to Permissive is not a fix.
Even if you use it briefly while investigating, once you find the cause, fix it with one of the three steps above and go back to Enforcing.
Containers and SELinux
Processes running inside a container get the type container_t.
The policy lets container_t processes read and write files of type container_file_t.
So when a directory on the host is mounted into a container as a volume and its type is not container_file_t, access is denied even if rwx is wide open.
For example, a directory on a newly attached disk has the type unlabeled_t (no label), so containers can neither read nor write it.
Setting up the production server ran into three of these.
- Directories on the data disk had no label. Every directory on the separately attached disk was
unlabeled_t, so containers could not write to it. Using method 1,container_file_trules were registered for the DB and backup directories. - The log collector could not read pod logs. This is the AVC example from Identifying denials. Instead of
spc_t, which has no restrictions at all, the collector runs ascontainer_logreader_t, which only adds reading container logs. - A postStart hook could not write to the container log. The postStart hook, which runs when the pod starts, was blocked writing to
/proc/1/fd/1, so its output was moved to a file inside the pod.
With Docker and Podman, the volume options :z (shared by several containers) and :Z (private to one container) relabel the mounted directory for you.
k3s ships a separate SELinux policy package (k3s-selinux) and turns SELinux on with selinux: true in its config.
The official k3s documentation states that a custom --data-dir is not supported under SELinux.
So instead of moving the data directory, a directory on the data disk was bind-mounted onto k3s’s default data path (/var/lib/rancher).
Then semanage fcontext -e registered the data-disk directory to receive the same labels as the default path.
That way, running restorecon on the data-disk side does not break the k3s labels.
References
- Red Hat, Using SELinux (RHEL 9) https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html/using_selinux/index
- K3s, SELinux Support https://docs.k3s.io/advanced#selinux-support
- containers/container-selinux https://github.com/containers/container-selinux