Module 4.3: SELinux Contexts
Linux Security | Complexity:
[COMPLEX]| Time: 35-40 min
Prerequisites
Section titled “Prerequisites”Before starting this module:
- Required: Module 2.3: Capabilities & LSMs
- Helpful: Module 4.2: AppArmor Profiles for comparison
- Helpful: Access to RHEL/CentOS/Fedora system
What You’ll Be Able to Do
Section titled “What You’ll Be Able to Do”After this module, you will be able to:
- Explain SELinux contexts (user:role:type:level) and how they control access
- Diagnose “Permission denied” errors caused by SELinux using audit2why and sesearch
- Configure SELinux for Kubernetes nodes (container_t, container_file_t contexts)
- Choose between enforcing, permissive, and disabled modes and explain the trade-offs
Why This Module Matters
Section titled “Why This Module Matters”SELinux (Security-Enhanced Linux) is the mandatory access control system used by RHEL, CentOS, Fedora, and their derivatives. It’s more complex than AppArmor but provides finer-grained control.
Understanding SELinux helps you:
- Manage RHEL-based Kubernetes nodes — SELinux is enabled by default
- Debug “permission denied” errors — When file permissions look correct
- Pass CKS exam — SELinux is tested alongside AppArmor
- Understand container isolation — SELinux labels separate containers
When something works on Ubuntu but fails on RHEL with no obvious cause, SELinux is often involved.
Stop and think: You migrate a perfectly functioning Nginx pod from an Ubuntu-based Kubernetes cluster to a new RHEL-based cluster. The pod starts, but Nginx returns 403 Forbidden for files mounted from a hostPath volume, even though the file permissions are
777. Based on the architectural differences between these distributions, why would DAC (Discretionary Access Control) allow access while the overall system denies it?
Did You Know?
Section titled “Did You Know?”-
SELinux was developed by the NSA — Released in 2000, it was contributed to the Linux kernel. Despite its origin, it’s open source and widely audited.
-
SELinux has over 300,000 rules in a typical targeted policy. Each rule defines what one type can do to another type.
-
“Just disable SELinux” is terrible advice — It’s a common but dangerous response to SELinux issues. Instead, learn to work with it or use permissive mode for debugging.
-
Multi-Level Security (MLS) is military-grade — SELinux can implement classified/secret/top-secret style mandatory access controls, though most systems use the simpler “targeted” policy.
SELinux vs AppArmor
Section titled “SELinux vs AppArmor”| Aspect | SELinux | AppArmor |
|---|---|---|
| Approach | Label-based | Path-based |
| Complexity | Higher | Lower |
| Granularity | Finer | Coarser |
| Distros | RHEL, CentOS, Fedora | Ubuntu, Debian, SUSE |
| Policy | Compiled | Text files |
| Learning curve | Steeper | Gentler |
SELinux Concepts
Section titled “SELinux Concepts”Security Labels
Section titled “Security Labels”Every file, process, and resource has a security context (label):
user:role:type:level
Example: system_u:object_r:httpd_sys_content_t:s0 │ │ │ │ │ │ │ └── MLS level │ │ └── Type (most important!) │ └── Role └── UserPause and predict: If a process with context
httpd_tattempts to read a file with contextuser_home_t, but the file’s standard Linux permission is777(world-readable), what will the SELinux enforcement engine decide and why?
Type Enforcement (TE)
Section titled “Type Enforcement (TE)”Most SELinux decisions use type enforcement:
┌─────────────────────────────────────────────────────────────────┐│ TYPE ENFORCEMENT ││ ││ Process Context: httpd_t ││ │ ││ │ wants to read ││ │ ││ ▼ ││ File Context: httpd_sys_content_t ││ │ ││ ▼ ││ ┌─────────────────────────────────────────┐ ││ │ Policy rule exists? │ ││ │ │ ││ │ allow httpd_t httpd_sys_content_t:file read; ││ │ │ ││ │ YES → ALLOW │ ││ │ NO → DENY │ ││ └─────────────────────────────────────────┘ ││ ││ Access requires: DAC allows AND SELinux policy allows │└─────────────────────────────────────────────────────────────────┘Common Types
Section titled “Common Types”| Type | Purpose |
|---|---|
httpd_t | Apache/nginx processes |
httpd_sys_content_t | Web content files |
container_t | Container processes |
container_file_t | Container files |
sshd_t | SSH daemon |
user_home_t | User home directories |
etc_t | /etc files |
var_log_t | Log files |
SELinux Modes
Section titled “SELinux Modes”Three Modes
Section titled “Three Modes”# Check current modegetenforce# Returns: Enforcing, Permissive, or Disabled
# Get detailed statussestatus| Mode | Behavior |
|---|---|
| Enforcing | Policies enforced, violations denied and logged |
| Permissive | Policies not enforced, violations only logged |
| Disabled | SELinux completely off |
Changing Modes
Section titled “Changing Modes”# Temporarily set to permissive (until reboot)sudo setenforce 0
# Temporarily set to enforcingsudo setenforce 1
# Cannot enable if disabled (requires reboot)
# Permanent change: edit /etc/selinux/config# SELINUX=enforcing|permissive|disabledViewing Contexts
Section titled “Viewing Contexts”File Contexts
Section titled “File Contexts”# Show file contextls -Z /var/www/html/# -rw-r--r--. root root system_u:object_r:httpd_sys_content_t:s0 index.html
# Just the contextstat -c %C /var/www/html/index.htmlProcess Contexts
Section titled “Process Contexts”# Show process contextsps -eZ | grep httpd# system_u:system_r:httpd_t:s0 1234 ? 00:00:01 httpd
# Current shell contextid -Z# unconfined_u:unconfined_r:unconfined_t:s0User Contexts
Section titled “User Contexts”# Show SELinux user mappingsemanage login -l
# Show SELinux userssemanage user -lManaging File Contexts
Section titled “Managing File Contexts”Setting Contexts
Section titled “Setting Contexts”# Change context temporarily (doesn't survive relabel)chcon -t httpd_sys_content_t /var/www/html/newfile.html
# Change context recursivelychcon -R -t httpd_sys_content_t /var/www/html/
# Restore default contextrestorecon -v /var/www/html/newfile.html
# Restore recursivelyrestorecon -Rv /var/www/html/Defining Default Contexts
Section titled “Defining Default Contexts”# View default file contextssemanage fcontext -l | grep httpd
# Add custom default contextsudo semanage fcontext -a -t httpd_sys_content_t "/srv/web(/.*)?"
# Apply the changesudo restorecon -Rv /srv/webSELinux Booleans
Section titled “SELinux Booleans”Booleans are on/off switches for policy features:
# List all booleansgetsebool -a
# List specific booleangetsebool httpd_can_network_connect# httpd_can_network_connect --> off
# Set temporarilysudo setsebool httpd_can_network_connect on
# Set permanentlysudo setsebool -P httpd_can_network_connect on
# Common booleansgetsebool -a | grep httpd# httpd_can_network_connect# httpd_can_network_connect_db# httpd_enable_cgi# httpd_read_user_contentCommon Booleans
Section titled “Common Booleans”| Boolean | Purpose |
|---|---|
httpd_can_network_connect | Allow httpd to make network connections |
httpd_can_network_connect_db | Allow httpd to connect to databases |
container_manage_cgroup | Allow containers to manage cgroups |
container_use_devices | Allow containers to use devices |
Troubleshooting SELinux
Section titled “Troubleshooting SELinux”Finding Denials
Section titled “Finding Denials”# Check audit logsudo ausearch -m AVC -ts recent
# Sample denial:# type=AVC msg=audit(...): avc: denied { read } for pid=1234# comm="httpd" name="secret.html" dev="sda1" ino=12345# scontext=system_u:system_r:httpd_t:s0# tcontext=system_u:object_r:admin_home_t:s0# tclass=file permissive=0
# Use audit2why to explainsudo ausearch -m AVC -ts recent | audit2why
# More readable with sealert (if installed)sudo sealert -a /var/log/audit/audit.logInterpreting Denials
Section titled “Interpreting Denials”avc: denied { read } for pid=1234 │ │ │ │ │ └── Process ID │ └── Operation attempted └── Denial
scontext=system_u:system_r:httpd_t:s0 ← Source (process)tcontext=system_u:object_r:admin_home_t:s0 ← Target (file)tclass=file ← Object classGenerate Policy from Denials
Section titled “Generate Policy from Denials”# Generate policy module from audit logsudo ausearch -m AVC -ts recent | audit2allow -M mypolicy
# Review the generated policycat mypolicy.te
# Install the policysudo semodule -i mypolicy.ppCommon Fixes
Section titled “Common Fixes”# Wrong file context → Fix with restoreconsudo restorecon -Rv /path/to/files
# Need network access → Enable booleansudo setsebool -P httpd_can_network_connect on
# Custom location for web content → Add fcontextsudo semanage fcontext -a -t httpd_sys_content_t "/custom/path(/.*)?"sudo restorecon -Rv /custom/path
# Last resort → Create custom policysudo ausearch -m AVC | audit2allow -M myfixsudo semodule -i myfix.ppContainer SELinux
Section titled “Container SELinux”Container Types
Section titled “Container Types”# Container processes run as container_tps -eZ | grep container# system_u:system_r:container_t:s0:c123,c456 12345 ? 00:00:00 nginx
# Container files have container_file_tls -Z /var/lib/containers/Multi-Category Security (MCS)
Section titled “Multi-Category Security (MCS)”Containers get unique MCS labels for isolation:
container_t:s0:c123,c456 │ └── Category pair (unique per container)
Container A: container_t:s0:c1,c2Container B: container_t:s0:c3,c4
Container A cannot access Container B's files (different categories)Podman/Docker SELinux
Section titled “Podman/Docker SELinux”# Podman with SELinuxpodman run --rm -it fedora cat /proc/1/attr/current# system_u:system_r:container_t:s0:c123,c456
# Volume mount with SELinuxpodman run -v /host/path:/container/path:Z fedora ls /container/path# :z = shared, :Z = private (relabels)Kubernetes SELinux
Section titled “Kubernetes SELinux”apiVersion: v1kind: Podspec: securityContext: seLinuxOptions: level: "s0:c123,c456" # MCS label type: "container_t" # Type (usually automatic) containers: - name: app image: nginxCommon Mistakes
Section titled “Common Mistakes”| Mistake | Problem | Solution |
|---|---|---|
| Disabling SELinux | No protection | Use permissive mode to debug |
| Using chcon only | Changes lost on relabel | Use semanage fcontext |
| Ignoring booleans | Creating unnecessary policy | Check booleans first |
| Wrong volume labels | Container can’t access files | Use :Z or :z mount option |
| Permissive forever | Never enforcing | Fix issues, return to enforcing |
| Not checking audit log | Missing root cause | Always check ausearch/audit2why |
Question 1
Section titled “Question 1”Scenario: A developer complains that their web application cannot read a newly created configuration file at /var/www/html/config.ini. You use chcon -t httpd_sys_content_t /var/www/html/config.ini and the application works. However, after a scheduled weekend system patching and reboot, the application fails with the exact same permission error. What caused the fix to revert, and how should it be resolved permanently?
Show Answer
The chcon command changes the SELinux context of a file immediately, but this change is strictly temporary and exists only in the filesystem metadata. When the system was patched or an administrator ran a filesystem relabel operation (like restorecon), the temporary context was overwritten by the default policy defined for that directory path. To permanently resolve the issue, you must define the default policy using semanage fcontext -a -t httpd_sys_content_t "/var/www/html/config.ini" and then apply it with restorecon -v /var/www/html/config.ini. This ensures the correct label survives system reboots and relabeling operations.
Question 2
Section titled “Question 2”Scenario: You deploy a PHP application on a CentOS server running Apache (httpd). The application needs to connect to an external PostgreSQL database to fetch user data. The database credentials are correct, and curl from the server can reach the database port, but the PHP application logs show “Permission denied” when attempting the connection. How do you diagnose and resolve this without altering file contexts?
Show Answer
The issue is likely caused by an SELinux Boolean that restricts the httpd process from initiating outbound network connections, which is disabled by default for security. You can diagnose this by checking the audit logs (ausearch -m AVC) or by checking the specific boolean status with getsebool httpd_can_network_connect. To resolve it, run setsebool -P httpd_can_network_connect on (or httpd_can_network_connect_db on). The -P flag ensures the change is written to the permanent policy on disk, meaning the web server will still be able to connect to the database even after the system reboots.
Question 3
Section titled “Question 3”Scenario: Two different pods, Pod A (a frontend web server) and Pod B (a backend API), are running on the same Fedora-based Kubernetes node. Both container processes run under the exact same SELinux type (container_t). A vulnerability in Pod A allows an attacker to execute arbitrary code. Explain the mechanism SELinux uses to ensure the attacker cannot read the mounted files belonging to Pod B, despite both processes having the same container_t type.
Show Answer
SELinux achieves this isolation through Multi-Category Security (MCS), which adds a unique category pair to the security context of each container. While both Pod A and Pod B run as container_t, the container runtime assigns them distinct MCS labels (e.g., Pod A gets s0:c1,c2 and Pod B gets s0:c3,c4). When Pod A attempts to access files mapped to Pod B, the SELinux policy checks the MCS labels. Because the categories do not match, access is denied, effectively isolating the containers from each other even though they share the same base process type.
Question 4
Section titled “Question 4”Scenario: You install a third-party monitoring agent on a production node. The agent’s systemd service fails to start, and standard file permissions look completely normal. You suspect SELinux is blocking it. Walk through the exact investigative steps you would take to identify the root cause before attempting any fixes.
Show Answer
The first step is to never disable SELinux or set it to permissive mode immediately on a production node. Instead, you should query the audit logs for access vector cache (AVC) denials using sudo ausearch -m AVC -ts recent. Piping this output to audit2why will translate the cryptic raw log into a human-readable explanation of exactly which process was denied access to which target, and often suggests the reason. Once you understand the specific denial (e.g., the agent process lacks a required network boolean or is trying to read a path with the wrong fcontext), you can apply a targeted fix rather than blindly altering security policies.
Question 5
Section titled “Question 5”Scenario: You are migrating a legacy container deployment script to a new RHEL 9 server. The script uses Podman to launch an analytics container that mounts /opt/analytics_data from the host. When the container starts, it immediately crashes with an inability to read the mounted directory. You run podman run -v /opt/analytics_data:/data:Z analytics-image. What exactly does the :Z flag do, and why might it be dangerous if /opt/analytics_data was a shared system directory like /etc?
Show Answer
The :Z (uppercase Z) flag instructs the container runtime to perform a private volume relabeling, meaning it changes the SELinux context of the host directory to match the specific, unique MCS label of that individual container. This allows the container exclusive access to the files. It would be highly dangerous to use :Z on a shared system directory like /etc because the relabeling process would change the context of all files within it, instantly breaking the host operating system’s ability to read its own configuration files and likely crashing the system. For directories that must be shared among multiple containers or the host, the :z (lowercase z) flag should be used to apply a shared label.
Hands-On Exercise
Section titled “Hands-On Exercise”Working with SELinux
Section titled “Working with SELinux”Objective: Understand SELinux contexts, booleans, and troubleshooting.
Environment: RHEL, CentOS, Fedora, or Rocky Linux
Part 1: Check SELinux Status
Section titled “Part 1: Check SELinux Status”# 1. Check modegetenforcesestatus
# 2. View your contextid -Z
# 3. View file contextsls -Z /etc/passwdls -Z /var/www/html/ 2>/dev/null || ls -Z /var/log/Part 2: File Contexts
Section titled “Part 2: File Contexts”# 1. Create test directorysudo mkdir /srv/testapp
# 2. Check default contextls -Zd /srv/testapp# Should show default_t or similar
# 3. Create a filesudo touch /srv/testapp/index.htmlls -Z /srv/testapp/
# 4. Change context temporarilysudo chcon -t httpd_sys_content_t /srv/testapp/index.htmlls -Z /srv/testapp/index.html
# 5. Restore default (undoes chcon)sudo restorecon -v /srv/testapp/index.htmlls -Z /srv/testapp/index.html
# 6. Set permanent contextsudo semanage fcontext -a -t httpd_sys_content_t "/srv/testapp(/.*)?"sudo restorecon -Rv /srv/testappls -Z /srv/testapp/Part 3: Booleans
Section titled “Part 3: Booleans”# 1. List all booleansgetsebool -a | wc -l
# 2. Find httpd booleansgetsebool -a | grep httpd
# 3. Check specific booleangetsebool httpd_can_network_connect
# 4. Change it (temporarily)sudo setsebool httpd_can_network_connect ongetsebool httpd_can_network_connect
# 5. Revertsudo setsebool httpd_can_network_connect offPart 4: Troubleshooting
Section titled “Part 4: Troubleshooting”# 1. Generate a denial (if httpd installed)# Try to serve file from wrong context
# 2. Check audit logsudo ausearch -m AVC -ts recent | tail -20
# 3. If denials exist, analyzesudo ausearch -m AVC -ts recent | audit2why
# 4. Alternative: use sealert if installedsudo sealert -a /var/log/audit/audit.log | head -50Part 5: Permissive Mode (Careful!)
Section titled “Part 5: Permissive Mode (Careful!)”# 1. Check current modegetenforce
# 2. Set permissive temporarilysudo setenforce 0getenforce
# 3. Generate would-be denials# ... run your application ...
# 4. Check what would have been deniedsudo ausearch -m AVC -ts recent
# 5. Return to enforcingsudo setenforce 1getenforceCleanup
Section titled “Cleanup”sudo semanage fcontext -d "/srv/testapp(/.*)?"sudo rm -rf /srv/testappSuccess Criteria
Section titled “Success Criteria”- Checked SELinux status and mode
- Viewed file and process contexts
- Changed file context with chcon and restorecon
- Set permanent context with semanage
- Listed and modified booleans
- Analyzed AVC denials
Key Takeaways
Section titled “Key Takeaways”-
Labels are everything — user:role:type:level controls access
-
Type enforcement is primary — Most decisions based on type
-
Booleans before custom policy — Check if a switch exists first
-
semanage for permanent changes — chcon is temporary
-
Don’t disable, debug — Permissive mode for troubleshooting
What’s Next?
Section titled “What’s Next?”In Module 4.4: seccomp Profiles, you’ll learn system call filtering—blocking dangerous kernel calls regardless of what LSM is in use.