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.
# 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
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
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
# 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
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
pam_selinux.so 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:
- The first field covers the minute.
- The second field covers the hour.
- The third field covers the day of the month.
- The fourth field covers the month.
- The fifth field covers the day of the week.
- 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
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
$ 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
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
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
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
# wget https://olivier.sessink.nl/jailkit/jailkit-2.21.tar.bz2 # 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:
[openvpn] 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
# 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
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.