Composing hardening based on least privilege: reducing the unconfined domain, adopting confined domains, disabling unnecessary booleans, analyzing the policy with sesearch, reviewing neverallow rules, and the OpenSCAP baseline and CIS benchmark.

In episode 13 your network was consistently labeled from ports to packets. But there's a more fundamental and often uncomfortable question: how many processes on your host are truly confined by SELinux, and how many still run freely? On the default targeted policy, most daemons are indeed confined — but regular users, cron scripts, interactive SSH sessions, and custom applications often run as unconfined_t: a domain that's practically unrestricted.
This episode is the turning point from understanding SELinux to composing a defense strategy. We discuss least privilege concretely: mapping unlocked domains, confining them, trimming booleans, analyzing the policy with sesearch, understanding the neverallow safety net, and locking everything down with an OpenSCAP/CIS baseline that's repeatable across many machines.
An unconfined_t process essentially bypasses almost all SELinux checks. It's not "safe" — it's unmitigated risk. The analogy: SELinux is a door-bolt system, and unconfined_t is a resident given a master key. All the bolts are still in place, but for them, the doors are unlocked.
Start hardening by taking a picture of the real condition:
ps -eZ | grep unconfinedunconfined_u:system_r:unconfined_t:s0 3101 ? 00:00:00 sshd
unconfined_u:unconfined_r:unconfined_t:s0 5201 pts/0 00:00:00 bashTwo patterns to distinguish: system_r:unconfined_t is a system service that doesn't yet have its own domain (a red flag: check whether it should be confined), while unconfined_r:unconfined_t is an interactive user session (the default condition, confinable). The hardening goal doesn't have to be eliminating both — the goal is reducing deliberately, not letting everything just pass through.
First step for services: make sure it runs as a daemon with a confined domain. Under the targeted policy, registered daemons (sshd, httpd, named, ntpd, mysql) are automatically confined. The problem is usually with daemons that aren't registered. Two options for them:
dnf install -y selinux-policy-targeted on RHEL, which carries modules for hundreds of services.Verify the current condition with sestatus -v for important daemons, and sesearch to see how wide a domain's rules are:
sesearch --allow -s sshd_t -c tcp_socketFor interactive users, the biggest lever is the SELinux user, not the Linux account. Look at the mapping:
semanage login -l
semanage user -lLogin Name SELinux User MLS/MCS Range
__default__ unconfined_u s0-s0:c0.c1023
root unconfined_u s0-s0:c0.c1023
alice user_u s0__default__ is the mapping for all accounts not listed. Changing a user from unconfined_u to user_u (or staff_u for those who need wider sudo) makes their login session run confined, and all their child processes follow:
semanage login -m -s user_u aliceFrom SELinux's perspective, user_u is a "regular resident": allowed to run their own user applications (user_t), but unable to write files owned by system domains. To make sure nothing "escapes" into an unwanted domain, audit2why on appearing denials will give hints about why a user process was denied.
Tip
Change SELinux users one at a time, not all at once. Start with non-interactive accounts (for example application accounts), observe the logs for a few days, then confine admin accounts. user_u also enables passwd-style protection: confined users can't just execute files from their own directories labeled user_tmp_t at will.
Booleans (episode 7) are the knobs that open and close policy behavior without writing rules. Unfortunately, the "turn on booleans until the app works" habit leaves knobs on that aren't needed. Audit by showing booleans along with their difference from default:
getsebool -a
semanage boolean -l -Chttpd_can_network_connect -> onEvery unneeded on boolean is an attack surface. Turn off what's clearly unnecessary:
setsebool -P httpd_can_network_connect off-P makes the change persistent in the policy store. Without -P, the boolean returns to its default value at reboot — that's often the "magical change" that makes your configuration non-reproducible. For service-specific booleans, also check network-related ones (for example httpd_can_connect_ftp), virtualization (virt_use_nfs), and NFS/Samba (nfs_export_all_rw) — ask yourself: what feature on this machine genuinely needs this?
Hardening without analysis is guessing. sesearch (part of setools) reads the active policy and answers questions like "what can domain X do?". Three most useful variations:
sesearch --allow -s httpd_t -t http_port_t -c tcp_socket
sesearch --allow -s httpd_t -c file
sesearch --allow -s httpd_t -c tcp_socket -p name_bindThe general form: sesearch --allow -s <source> -t <target> -c <class> -p <permission>. Combining them lets you reconstruct a domain's "surface": which ports it may bind, what kinds of files it can read, and which classes it can reach. Running sesearch --allow -s httpd_t -c file and finding rules to etc_t is a finding worth discussing in a security review — not just trivia.
The neverallow rule is the opposite of allow: a statement of "this will never be allowed" verified when the policy is compiled. It's not a runtime decision — it's a structural guarantee. Refpolicy carries thousands of neverallow rules that prevent certain domains from touching sensitive classes (for example, user domains must not access the security class). See them with sesearch:
sesearch --neverallow -s httpd_tIf your module accidentally adds an allow httpd_t ... that violates a neverallow, the module compilation fails with a message showing the conflict — that's what makes neverallow a safety net for everyone writing policies, including you in episode 12. When writing custom modules, always run sesearch --neverallow on the new domain as a final check before loading.
The final principle: hardening must be measurable and repeatable, not memory. OpenSCAP is a compliance scanner that carries official hardening profiles, including CIS and STIG. On RHEL 9:
dnf install -y openscap-scanner scap-security-guideoscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis \
--report /tmp/cis-report.html \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xmlThe generated HTML report carries the status of each CIS control — and some of those controls are exactly about SELinux: ensuring SELinux is enforcing, the policy is targeted, and no dangerous booleans are on. To close detected gaps automatically, OpenSCAP can generate remediation scripts:
oscap xccdf generate fix --fix-type bash \
--profile xccdf_org.ssgproject.content_profile_cis \
--output /tmp/cis-fix.sh \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xmlWarning
Don't blindly run the generated cis-fix.sh in production. That script changes system configuration directly — including disabling services and changing policies. Run it in staging first, compare the config delta, and review every change before applying. A baseline is only useful if the team knows exactly what was changed and why.
In this episode 14, you've composed a measurable least privilege strategy: auditing unconfined_t processes with ps -eZ, reducing the unconfined domain with custom policies or distro policy packages, confining users via semanage login and semanage user, trimming booleans with getsebool -a and setsebool -P, reading a domain's surface with sesearch --allow, understanding the structural guarantee of neverallow, and locking everything down with the OpenSCAP baseline and CIS profile that produce reports and remediation scripts.
The essentials to take with you:
unconfined_t.on boolean is an attack surface — audit and turn off what's unnecessary.sesearch turns guesses into facts; neverallow keeps those facts consistent.Now your system is locked down and measurable. But defense without observation is empty hope: a denial never seen is the same as a permission never checked. In the next episode 15, we enter Audit, Monitoring & Incident Response: configuring auditd, reading traces with ausearch and aureport, building real-time denial alerts, and the complete incident response workflow — denial chain analysis, forensics of changed contexts, and fast mitigation that doesn't break production. See you in episode 15!