SELinux Policy Management
Managing SELinux Booleans and Creating Custom Policy Modules
Download Oracle Linux
Sometimes you have a problem with a Linux system
that makes you say:
"This is so strange,
it should
be working!
The ownerships and permissions are correct.
No helpful error or warning messages appear
on the command line.
What could be wrong?"
The answer in such a situation is often that
Security-Enhanced Linux,
or simply SELinux,
is stopping something.
It isn't silent interference.
All this is being logged.
But the log is in a very verbose and hard to understand format,
stored in a file that only
root
can read.
You may be able to fix the problem easily by toggling
one or more SELinux boolean settings.
Or, you may need to create and load
a custom SELinux policy module.
Or, as in this case, a little of each.
It isn't difficult to diagnose the problem and fix
what's needed, let's see how.
This example fixes a problem you encounter when trying to
use the pam_yubico.so
PAM module
to authenticate with a YubiKey on a hardened RHEL 8 server.
The Problem Scenario
Configuring YubiKey Authentication withpam_yubico.so
Let's say you want to use Yubikey to do
multi-factor authentication.
However, Red Hat no longer includes the
pam_pkcs11.so
PAM module that was in earlier major releases.
They also didn't include the
pam_u2f.so
module which you might have on other distributions.
The RHEL media contains no
YubiKey-specific PAM module.
However, the EPEL collection,
Extra Packages for Enterprise Linux,
has packages you can install on RHEL
and compatible derivatives including
Oracle Linux and Fedora Linux.
EPEL includes a pam_yubico
package
that includes the pam_yubico.so
PAM module.
That can work, but you have to solve a two-part SELinux problem. You want to use a PAM module that is no longer a standard component of Red Hat Enterprise Linux, while Red Hat wants you to instead use Red Hat Identity Management or IdM.
If Things Are Strange, Suspect SELinux
I have found that if you run into a strange situation where it seems that something should work but it mysteriously doesn't, it's very likely the fault of SELinux.
Run a simple test.
Have one terminal in which you are trying but failing
to run something, probably as an unprivileged user.
Or perhaps not, SELinux can interfere with root
doing things, which is even more mysterious.
As root
in another terminal,
use the
setenforce
command to switch SELinux to Permissive mode.
It will analyze every action,
and then if it normally would block the action,
it will allow it but log it.
All you really need of the following is the second command.
The first and last commands show us what happens.
# getenforce Enforcing # setenforce Permissive # getenforce Permissive
Now return to the test window and try the command again. If it still fails, then the problem isn't SELinux. But if it works with SELinux in Permissive mode, we're on the track to solve this quickly.
Switch back to the root
window and
truncate the audit log:
# > /var/log/audit/audit.log
Re-run the problematic commands in the testing window.
If there are multiple ones, run them all.
Then switch back to the
root
window and save the small audit log
that will contain just the events that were flagged
for denial but permitted.
# cp /var/log/audit/audit.log .
Let's use the
audit2allow
command to see what changes would be required to
allow those commands to run
even when SELinux is in Enforcing mode.
"AVC" stands for Access Vector Cache,
it's where SELinux stores information about its decisions
to allow and deny access.
Notice that three blocked operations can be fixed
by toggling the state of one SELinux boolean.
# audit2allow < audit.log #============= staff_sudo_t ============== #!!!! This avc can be allowed using the boolean 'authlogin_yubikey' allow staff_sudo_t auth_home_t:dir { add_name read remove_name write }; #!!!! This avc can be allowed using the boolean 'authlogin_yubikey' allow staff_sudo_t auth_home_t:file { create rename setattr unlink write }; allow staff_sudo_t udev_var_run_t:file { getattr open read }; #============= sysadm_su_t ============== allow sysadm_su_t auth_home_t:dir { add_name read remove_name write }; allow sysadm_su_t auth_home_t:file { create getattr open read rename setattr unlink write }; allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt }; allow sysadm_su_t udev_var_run_t:file { getattr open read }; allow sysadm_su_t usb_device_t:chr_file { ioctl open read write }; allow sysadm_su_t wtmp_t:file write; #============= sysadm_sudo_t ============== #!!!! This avc can be allowed using the boolean 'authlogin_yubikey' allow sysadm_sudo_t auth_home_t:dir { add_name read remove_name write }; #!!!! This avc can be allowed using the boolean 'authlogin_yubikey' allow sysadm_sudo_t auth_home_t:file { create rename setattr unlink write }; allow sysadm_sudo_t udev_var_run_t:file { getattr open read };
Advanced Topic — What If It Didn't Find Everything?
In an extreme, but fortunately fairly clear, situation,
you may get nothing more than "Nothing to do" from
the audit2allow
program.
Your problem might be caused by
SELinux dontaudit
rules.
If you think that might be the case, disable all
dontaudit
rules:
# semodule -DB
Then re-run what you would like enabled within a
permissive
session,
and see if audit2allow
can generate some policy code now.
Fix What We Can With Existing Booleans
The SELinux policy booleans
are pre-defined rules we can switch off and on.
We will use those rather than partially reinventing wheels
with an overly broad custom policy.
Our report told us that just one boolean,
authlogin_yubikey
,
was needed.
We can see that it was off
before,
and the below sequence changed it to on
.
The -P
option
permanently changes the system's policy,
so our modification will persist after reboots.
# getsebool authlogin_yubikey authlogin_yubikey --> off # setsebool -P authlogin_yubikey on # getsebool authlogin_yubikey authlogin_yubikey --> on
That Should Have Improved Things
Now truncate the audit log file again and repeat the experiment.
# > /var/log/audit/audit.log
[... Re-run all the troublesome commands in the user window ...]
# cp /var/log/audit/audit.log .
# audit2allow < audit.log
#============= staff_sudo_t ==============
allow staff_sudo_t udev_var_run_t:file { getattr open read };
#============= sysadm_su_t ==============
allow sysadm_su_t auth_home_t:dir { add_name read remove_name write };
allow sysadm_su_t auth_home_t:file { create getattr open read rename setattr unlink write };
allow sysadm_su_t self:capability dac_override;
allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt };
allow sysadm_su_t udev_var_run_t:file { getattr open read };
allow sysadm_su_t usb_device_t:chr_file { ioctl open read write };
allow sysadm_su_t wtmp_t:file write;
#============= sysadm_sudo_t ==============
allow sysadm_sudo_t udev_var_run_t:file { getattr open read };
That looks good, nothing is left that could be handled by a boolean. Nine things remain to be given special permission with our custom policy module.
Let's Start Buiding A Custom Policy Module
Rerun the audit2allow
command with some
added parameters:
-
The
-r
option calculates and includes the required definitions. -
The
-m
option lets us specify a module name on the command line. You can give it whatever unique name you want,mfa_yubikey
made sense to me. -
We will redirect the result into a file.
Its name should end with
.te
so the following compilation step understands what it is.
You don't have to give the same name to the file and the module which it contains, as I do here. However, I would find it confusing if I didn't! The name doesn't matter, as long as it's unique. But again, choose a name that makes sense to yourself.
# audit2allow -r -m mfa_yubikey < audit.log > mfa_yubikey.te # cat mfa_yubikey.te module mfa_yubikey 1.0; require { type staff_sudo_t; type wtmp_t; type usb_device_t; type sysadm_sudo_t; type auth_home_t; type udev_var_run_t; type sysadm_su_t; class file { create getattr open read rename setattr unlink write }; class capability dac_override; class netlink_kobject_uevent_socket { bind create getattr setopt }; class dir { add_name read remove_name write }; class chr_file { ioctl open read write }; } #============= staff_sudo_t ============== allow staff_sudo_t udev_var_run_t:file { getattr open read }; #============= sysadm_su_t ============== allow sysadm_su_t auth_home_t:dir { add_name read remove_name write }; allow sysadm_su_t auth_home_t:file { create getattr open read rename setattr unlink write }; allow sysadm_su_t self:capability dac_override; allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt }; allow sysadm_su_t udev_var_run_t:file { getattr open read }; allow sysadm_su_t usb_device_t:chr_file { ioctl open read write }; allow sysadm_su_t wtmp_t:file write; #============= sysadm_sudo_t ============== allow sysadm_sudo_t udev_var_run_t:file { getattr open read };
That's The Custom Policy Module Source Code, Let's Compile It And Clean Up
Compile the policy module and see what results.
$ make -f /usr/share/selinux/devel/Makefile mfa_yubikey.pp $ ls -l mfa_yubikey* -rw-rw-r--. 1 cromwell cromwell 0 Aug 26 14:22 mfa_yubikey.fc -rw-rw-r--. 1 cromwell cromwell 23 Aug 26 14:22 mfa_yubikey.if -rw-rw-r--. 1 cromwell cromwell 8833 Aug 26 14:22 mfa_yubikey.pp -rw-------. 1 cromwell cromwell 1155 Aug 26 14:21 mfa_yubikey.te $ file mfa_yubikey* mfa_yubikey.fc: empty mfa_yubikey.if: SE Linux policy interface source mfa_yubikey.pp: , created: Thu Jan 1 00:00:15 1970, modified: Tue June 22 05:08:35 2010 mfa_yubikey.te: C++ source, ASCII text $ cat mfa_yubikey.if ## <summary></summary>
Clean up, we don't need the two trivial files:
$ rm mfa_yubikey.fc mfa_yubikey.if
However, of course you want to keep the source code somewhere safe.
Insert The Custom Policy Module And Test It
Insert the policy module:
$ sudo semodule -v -i mfa_yubikey.pp Attempting to install module 'mfa_yubikey.pp': Ok: return value of 0. Committing changes: Ok: transaction number 27.
Reboot the system and verify that the changes persist:
# getsebool -a | grep -i yubikey authlogin_yubikey --> on # semodule -l | grep -i yubikey mfa_yubikey
And, of course, test whatever it was that used to be mysteriously broken!
Going Deeper
The audit2allow
command will give me
policy module source code that solves my problem.
However, its solution might be overly generalized.
It might allow some events that aren't needed
to run the commands of interest,
at least not in the ways in which I ran them during the tests.
For example, I didn't expect to see this line:
allow sysadm_su_t self:netlink_kobject_uevent_socket { bind create getattr setopt };
I did not expect network socket creation and use
to appear.
I could have tried commenting out that line,
compiling the module without it,
and testing to see if the resulting module was permissive
enough for what I wanted to do.
Or maybe I need that over all line but I could delete
some of the actions in the {...}
list.
Some people who worked with SELinux at the NSA, where it was initially developed, have told me that NSA staff with more academic interest in SELinux love it because of its auditable fine-grained control.
However, developing a
(nearly) complete and (nearly) minimal policy
is an enormous job.
You may run into weird situations like the
ping
command checking the attributes
of some file under /var/lock
.
Would it break functionality if SELinux blocked that test?
There's an enormous number of these apparently pointless tests. And so there would be an enormous number of functionality tests to perform. What if now allowing the attribute check did break functionality, but only when attempting esoteric option combinations in infrequently encountered situations?
If so, now there's a judgement call to make — is that obscure and seldom-used functionality critical to the mission?