Configuring PAM services with SELinux

June 25, 2021

systemd and D-Bus are SELinux-aware applications, with explicit SELinux support built in. Several other services exist on a Linux system that play nicely together with SELinux yet are not SELinux-aware themselves. Many of these services have an affinity with SELinux through their PAM integration.


Cockpit is a simple, browser-based management application that allows administrators to easily see system resources (monitoring) as well as to interact with the system. It also allows users to log into the system through the browser.

It is this browser-based terminal that we want to configure: by tuning the target SELinux roles for the SELinux users, we can selectively put users in a specific role. This effectively defines what the users can accomplish through this browser-based session.

Installing Cockpit

The Cockpit application is readily available in the CentOS repository, so installing it is a breeze:

# yum install cockpit

While the application does not need additional configuration, if you do need tweaks, you will need to create the configuration file, /etc/cockpit/cockpit.conf, yourself as the application does not create a default configuration file. Within this configuration file, you can configure the TLS settings, or disable encrypted communication generally.

Let’s disable the encrypted communication for this demonstration run (but if you intend to use Cockpit in production, you should not only keep encryption on but also ensure that only trusted hosts are connecting, possibly even requiring client certificate authentication using the ClientCertAuthentication directive):


With this set, we can continue with configuring SELinux for Cockpit.

Restricting user logins

Through these instructions, we will add the more restricted user_r role to the staff_u SELinux user, and then ensure that all logins mapped to the staff_u SELinux user are logged in using the user_r role when they log in through Cockpit. If they log in through other services, they will continue using the default staff_r role.


The use of the user_r role rather than the (even more restricted) guest_r role is to allow the Cockpit application to function properly. The application will run a service under the user’s privileges, which are not sufficient for Cockpit if we use the guest_t user domain.

Let’s first add the user_r role so that we can put the users in the correct context later:

# semanage user -m -R "staff_r sysadm_r system_r user_r" staff_u

Next, we want to update the SELinux configuration so that any Cockpit login by staff_u mapped users is going to use the user_r role. The Cockpit application has logins done through a service running in the cockpit_session_t context, which we find out by checking the context of the process first, and then logging in on Cockpit and checking the context of the processes again. There, we notice that a new process (cockpit-session) runs with the cockpit_session_t context:

# ps -eZ | grep cockpit
system_u:system_r:cockpit_ws_t:s0	 ... cockpit-ws
system_u:system_r:cockpit_session_t:s0 ... cockpit-session localhost

With this information now available, we can edit the /etc/selinux/targeted/contexts/users/staff_u file as follows:

system_r:local_login_t:s0   staff_r:staff_t:s0 sysadm_r:sysadm_t:s0
system_r:remote_login_t:s0  staff_r:staff_t:s0
system_r:sshd_t:s0          staff_r:staff_t:s0 sysadm_r:sysadm_t:s0
system_r:cockpit_session_t:s0   user_r:user_t:s0
system_r:crond_t:s0         staff_r:staff_t:s0 staff_r:cronjob_t:s0

By adjusting the order of the roles listed for the cockpit_session_t context (or limiting them to only the user_r role), we ensure that users allowed to run with the user_r role (like the staff_u user we configured earlier on) do so through the user_r role. As this role is more restricted than the default staff_t user domain, logins through Cockpit are thus more isolated.

This approach can be used for all PAM-enabled services, as this solely relies on the call in the service PAM configuration. For some services, the SELinux policy administrators add in a few more tweaks to use, such as with cron and SSH, which we’ll discuss next.


Cron services on a system allow you to run tasks or commands on predefined schedules. Some cron applications are explicitly made SELinux-aware (such as fcron), allowing them to compute the target context a job should run in. Even cron systems that do not have any specific SELinux logic built in can be fine-tuned.

Switching between user-specific and generic contexts

A common setup supported through the SELinux policy is to toggle whether user tasks run in the user’s default context (such as staff_t for staff users) or in a default, restricted cron context (cronjob_t). Both approaches have their pros and cons.

When we configure the system to have user jobs run in the user’s default context, then users know what the privileges are of their jobs. A guest user has guest privileges, a staff user has staff privileges, and so forth. This is the most common configuration, and the default cron system on CentOS uses the context of the file containing the user’s tasks (located in /var/spool/cron) to deduce the target runtime context.

By running user jobs in a more restricted context such as cronjob_t, all users’ cron jobs run with the same privileges, and the administrator can easily fine-tune the privileges for all user jobs. This also allows the administrator to grant specific privileges for cron jobs while keeping the user contexts free of these rights.

Let’s have a simple task executed every minute, namely a 59-second sleep. As a regular user, create a file (let’s say lisa.cron) with the following content:

* * * * * sleep 59

This file uses the common cron syntax, where the following applies:

  1. The first field covers the minute.
  2. The second field covers the hour.
  3. The third field covers the day of the month.
  4. The fourth field covers the month.
  5. The fifth field covers the day of the week.
  6. The rest of the line is the command to execute.

The fields can use expressions to facilitate time definitions. For instance, to run every 15 minutes, you can use */15 in the first field. If you want to run only at 8 o’clock and 18 o’clock, you can use the 8,18 value in the second field. Another example is if you only want to run on workdays, for which you can use 1-5 in the fifth field (in cron, Sunday holds both 0 and 7 as valid values).

By loading it with the crontab command, the file is checked for errors and, if error-free, is securely placed inside /var/spool/cron (the crontab command is a setuid command that is able to modify /var/spool/cron even though this location is inaccessible by regular users):

$ crontab ./lisa.cron

From here, the cron daemon will pick up this file, and 1 minute later we will see the command active in the background:

$ ps -efZ | grep sleep
staff_u:staff_r:staff_t:s0 ...  sleep 59

As seen from the output, the command is running in the staff_t context. To change this to the cronjob_t type, rather than editing the SELinux context definition file as we did with the Cockpit application, use the cron_userdomain_transition SELinux boolean:

# setsebool cron_userdomain_transition off

This boolean changes the active SELinux policy behavior so that any user task executed from the cron system executes within the cronjob_t domain. You might need to reset the crontab definition (this depends on the cron system used), but afterward, we will see the job running in the cronjob_t domain:

$ ps -efZ | grep sleep
staff_u:staff_r:cronjob_t:s0 ...  sleep 59

The use of SELinux booleans to allow administrators to differentiate system behavior as needed is commonly used. For the SSH daemon, SELinux policy administrators have defined something similar.


The OpenSSH daemon is the most common secure shell daemon around. It allows users to remotely access systems through a terminal, as well as to securely transfer files, tunnel application communications, and more.

When logging in through SSH, the PAM controls apply, but the SELinux policy also has specific SSH controls embedded and controllable through SELinux booleans.

Directly logging in as sysadm_t

The first change to assess is to allow directly logging in using the sysadm_r role. Users mapped to the staff_u SELinux user by default log in using the (more restricted) staff_r role, and then need to explicitly switch roles to obtain the more privileged sysadm_r role.

The first change we need to make is to edit the /etc/selinux/targeted/contexts/users/staff_u file and adjust the order of the roles listed for the sshd_t context:

system_r:local_login_t:s0   staff_r:staff_t:s0 sysadm_r:sysadm_t:s0
system_r:remote_login_t:s0  staff_r:staff_t:s0
system_r:sshd_t:s0          sysadm_r:sysadm_t:s0 staff_r:staff_t:s0 
system_r:cockpit_session_t:s0   user_r:user_t:s0
system_r:crond_t:s0         staff_r:staff_t:s0 staff_r:cronjob_t:s0

However, this is not enough. The SELinux policy administrators have disabled direct logins through SSH to the sysadm_r role, forcing users to explicitly change roles (and thus reauthenticate). This approach is because SSH is often a publicly reachable and not otherwise easily controllable service (unlike services such as web servers, which can have reverse proxies and web application firewalls in front).

Change the SELinux ssh_sysadm_login boolean to true to enable the wanted behavior:

# setsebool ssh_sysadm_login true

This boolean changes the SELinux policy behavior to allow logins to the sysadm_r role from the SSH daemon.

Chrooting Linux users

Another feature that SSH supports is forcing logins from selected users to be chrooted. A chroot (which is a portmanteau of change root) is an isolation method for processes, where the process no longer sees the entire filesystem but only a part of it.

Before we configure SSH to chroot some users, we need to create a properly functioning environment: once we change the root for a process, all commands and libraries that the process wants to read or execute need to be available within this chroot environment.

Let’s first create a chroot environment. A nice utility that assists in creating the right folder structure and files is Jailkit. Jailkit is not available by default through the regular repositories but can be easily installed and only requires a working compiler and Python environment.

We start off by installing the necessary dependencies:

# yum install gcc python36-devel

Next, we download the Jailkit source code and build it. As CentOS does not have a linked Python binary by default (as it requires the use of python3 as the runtime), we need to tell the build scripts how to address Python. We do this by declaring the PYTHONINTERPRETER environment variable:

# wget
# tar xvf jailkit-2.21.tar.bz2
# cd jailkit-2.21
# export PYTHONINTERPRETER=/usr/bin/python3
# ./configure
# make
# make install

Once the installation is complete, you might need to remove a duplicate includesections call within the Jailkit configuration file (the jk_init command, which we will use next, will inform you about it if you don’t). The openvpn section in /etc/jailkit/jk_init.ini should look like this:

comment = jail for the openvpn daemon
paths = /usr/sbin/openvpn
users = root,nobody
groups = root,nobody
devices = /dev/urandom, /dev/random, /dev/net/tun
includesections = netbasics, uidbasics
need_logsocket = 1

With the configuration updated, we can now create the chroot environment. Let’s create the /srv/chroot directory and then populate it with the necessary files, directories, device nodes, and more with the jk_init command:

# mkdir /srv/chroot
# jk_init -v -j /srv/chroot extshellplusnet

We want to make sure that the SELinux contexts for the resources inside this location are equivalent to the root location, so let’s create a file context equivalency definition:

# semanage fcontext -a -e / /srv/chroot
# restorecon -RvF /srv/chroot

With the chroot environment set, we can now update the SSH configuration to chroot a user:

Match User lisa
  X11Forwarding no
  AllowTcpForwarding no
  ChrootDirectory /srv/chroot

While not applicable to all systems (as it depends on the distribution), we might need to tell the SELinux policy that the user domains for the users can chroot. This privilege (sys_chroot) is often not enabled by default for user domains:

# setsebool selinuxuser_use_ssh_chroot true

With this set, restart the SSH daemon and see whether the chroot is successful:

# systemctl restart ssh

Chroot environments are not only sensible for SSH access; other daemons might support chroot environments to further protect the resources on the system. In the past, chroot support was a common way to further harden the system. Namespace and resource isolation support has, however, largely surpassed the need for chroot jails. These new features have also jumpstarted the containerized ecosystem.

The SELinux support for applications such as Cockpit, cron, and OpenSSH is generally provided through the SELinux policy and uses PAM integration to link SELinux controls within the application. It is, however, also possible to explicitly build in SELinux support in applications not intentionally SELinux-aware, but who support dynamic additions of logic through a modular design. As an example of this, we will look at Apache and the mod_selinux Apache module next.

Related Articles

No Results Found

The page you requested could not be found. Try refining your search, or use the navigation above to locate the post.

Lorem ipsum dolor sit amet consectetur


Submit a Comment

Your email address will not be published. Required fields are marked *

fourteen − one =