EX415 Exam Prep

Original author: nazunalika

Last modified: Mon Aug 1 17:02

This page contains the necessary resources to help you prepare for the Red Hat Certified Specialist in Security exam, EX415. This follows loosely the youtube playlist as much as possible with various examples and ideas.

The list of objectives can be found here. Note that the exam objectives can change at any time. It is the responsibility of the reader to always review the objectives prior to studying and taking the exam to ensure success.


Affiliation and Exam Information

Please note that we are not affiliated with Red Hat. The materials and examples used are our own and do not reflect the training programs provided by Red Hat and are educational only. We do not disclose any of the tasks, questions, or material on the exam as it would violate the NDA. Any questions sent to us about anything directly related to the exam will not be answered. We also do not provide any one-on-one tutoring or online teaching courses.

If exam objectives have changed to where the videos and this material are missing information, we can add on at any time upon request. If there are things about the various components that you’d like to see in the videos that may fit into the objectives, we can add it also upon request.


The video series goes over setting up your VM’s to study for the exam as well as going over the objectives, following the objectives as outlined by Red Hat. The list of objectives can be found here.

Exam Information

The EX415 exam tests your knowledge in a real world format style test - Meaning just like any Red Hat exam, it is performance-based and you perform tasks as if you were on the job. You are evaluated on the tasks you perform and if they meet the objective criteria. The EX415 is related to various security components in the Red Hat ecosystem and counts toward the RHCA (Red Hat Certified Architect).

To take the exam, you must have at least an RHCSA. If you are attempting to become a Red Hat Certified Architect, you must have an RHCE.


The resources will be listed in their various sections where needed.

Use Red Hat Ansible Engine

Install Red Hat Ansible Engine on a control node

# If studying on CentOS, you will need EPEL.
% yum install epel-release -y

# Install ansible
% yum install ansible -y

Configure managed nodes

# On all nodes, create the ansible user with full SUDO permissions
% useradd ansible

% visudo -f /etc/sudoers.d/ansible

# Create a temporary password on all nodes to pass out the keys without
# performing a copy and paste
% passwd ansible

# On the control node, generate SSH keys
% su - ansible
ansible% ssh-keygen
enerating public/private rsa key pair.
Enter file in which to save the key (/home/ansible/.ssh/id_rsa):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ansible/.ssh/id_rsa.
Your public key has been saved in /home/ansible/.ssh/id_rsa.pub.
The key fingerprint is:
SHA256:. . . ansible@control.example.com
The key's randomart image is:
+---[RSA 2048]----+
. . .

# Pass the keys to all systems
ansible% ssh-copy-id control.example.com
ansible% ssh-copy-id node1.example.com
ansible% ssh-copy-id node2.example.com
ansible% exit

# On all systems, lock the ansible user
% passwd -l ansible

Configure a simple inventory

% vi /etc/ansible/inventory
control ansible_host=
node1 ansible_host=
node2 ansible_host=

control ansible_host=

node1 ansible_host=
node2 ansible_host=

Perform basic management of systems

# Example running a command
% ansible all -a 'uptime'

# Example running a module
% ansible all -m yum -a 'name=* state=latest'

Run a provided playbook against specified nodes

# Generic example
% ansible-playbook /etc/ansible/playbook.yaml

# If it has variables you can change
% ansible-playbook /etc/ansible/playbook.yaml --extra-vars="host=node1"

Configure Intrusion Detection

Intrusion detection is a way of identifying the suspicious or malicious network, file system, or policy violations. In the case of a simple file system monitoring, we will use AIDE.

% yum install aide -y

# This will create the initial baseline for the system
% aide --init
% mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz

# You can then run a check to see if anything has changed. (There won't be any)
% aide --check

# Let's add some directories to monitor
% mkdir /opt/static
% vi /etc/aide.conf
/opt/static DIR

% aide --update
% mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
% cd /opt/static
% touch example
% chown operator:root .

# When you run the check, there should be a change
% aide --check

# Configure a cron job to check once a night
% crontab -e
0 0 * * * /usr/sbin/aide --check

When configuring AIDE, there are many options. In fact, a lot of them are commented throughout the file.

So for example, you can use ! to negate checking a particular directory. For example !/var/log/httpd/ would prevent checking the httpd log directory as it would always be changing. Here are some other various examples.

# Do not monitor any logs or spool

# Log only access to a directory or file
/opt/static APP_ACCESS

Configure Encrypted Storage

When you install a CentOS 7, CentOS 8, or even Fedora, an option is given to the user if they want to encrypt their disk. Typically, the /home directory is encrypted in this scenario. The /home directory, upon being mounted at boot, requests a password to be entered. This is LUKS in action.

The LUKS binaries and support should be available usually by default. But, in the package isn’t available, you can install cryptsetup. That will provide the minimal required to setup a luks encrypted file system.

To setup a basic encrypted partition, let’s do it on node1:

% cryptsetup luksFormat /dev/sdb1

# You can then verify the headers.
% cryptsetup luksDump /dev/sdb1
LUKS header information for /dev/sdb1

Version:        1
Cipher name:    aes
Cipher mode:    xts-plain64
Hash spec:      sha256
Payload offset: 4096
MK bits:        512
MK digest:      10 dc 1c c8 5c 4f c5 30 30 58 f8 90 3d ed 61 97 dc 0b d6 4b
MK salt:        e3 61 e8 c9 6b 59 a3 29 55 6c c5 4c dd 63 2f 66
                fd e4 a1 72 29 48 57 a4 0b e7 f2 c9 dd 12 0f bc
MK iterations:  119809
UUID:           b2a181b0-4078-415e-94c2-18c51a886a3b

Key Slot 0: ENABLED
        Iterations:             1855886
        Salt:                   c1 6f fe 4e 66 ce 90 e5 68 19 ca fb 29 35 b1 a0
                                0f d1 66 0a dc 0a cc 6e 69 4e 6c 07 d0 51 6d b8
        Key material offset:    8
        AF stripes:             4000
Key Slot 1: DISABLED
Key Slot 2: DISABLED
Key Slot 3: DISABLED
Key Slot 4: DISABLED
Key Slot 5: DISABLED
Key Slot 6: DISABLED
Key Slot 7: DISABLED

% cryptsetup isLuks -v /dev/sdb1
Command successful.

# Open the partition and create a file system
% cryptsetup luksOpen /dev/sdb1 mnt
% mkfs.xfs /dev/mapper/mnt
% mount /dev/mapper/mnt /mnt
% touch /mnt/test
% umount /mnt
% cryptsetup luksClose mnt

# If you want it to mount at boot time before setting up tang/clevis
% vi /etc/crypttab
mnt /dev/sdb1 none none

% vi /etc/fstab
. . .
/dev/mapper/mnt /mnt xfs defaults 1 2

When you reboot, it should ask for a passphrase. The passphrase you entered during the setup should work.

Let’s setup NBDE, which is Network-Bound Disk Encryption. NBDE is essentially a way to allow the user to encrypt volumes of disks, whether physical or virtual, without requiring manual intervention to enter a password at boot time. There are a few components of NBDE:

  • Tang: The server for binding data to network presence. In essence, it provides available data for when a system is bound to a secure network. It is stateless and does not store keys, nor identifiable information of a client.

  • Clevis: A pluggable framework for automating decryption. Clevis unlocks LUKS volumes and acts as a client.

    • Pin: A plugin to the clevis framework. This pin is used to interact with the tang NBDE server.

Clevis and Tang are both the client and server components for NBDE. More information can be found here.

Setup tang on the control node.

% yum install tang -y

# Enable tang. Notice it's a socket unit, not a service. It is because
# tang is stateless.
% systemctl enable tangd.socket --now

# Open the http port
% firewall-cmd --add-service=http
% firewall-cmd --runtime-to-permanent

Remember the luks volume we created on one of the nodes? Let’s set it up with NBDE.

% yum install clevis clevis-dracut clevis-luks clevis-systemd -y
% CFG='{"url":""}'
% clevis bind luks -d /dev/sdb1 tang "$CFG"
. . .
Do you wish to trust these keys? [ynYN] Y
Do you wish to initialize /dev/sdb1? [yn] y
Enter existing LUKS password:

% luksmeta show -d /dev/sdb1
0   active empty
1   active STRING
2 inactive empty
3 inactive empty
4 inactive empty
5 inactive empty
6 inactive empty
7 inactive empty

# Modify /etc/fstab and /etc/cryptab like so...
% vi /etc/crypttab
mnt /dev/sdb1 none _netdev

% vi /etc/fstab
. . .
/dev/mapper/mnt /mnt xfs defaults,_netdev 1 2

% dracut -f
% systemctl enable clevis-luks-askpass.path

# Reboot the system and test.
% init 6

There may be a case where you want high availability with your tang servers. If this is the case and you have two tang servers up and running, you would configure your nodes to use both servers.

% CFG='{"t":2,"pins":{"tang":[{"url":""},{"url":""}]}}'
% clevis luks bind -d /dev/sdb1 sss "$CFG"

Rotating keys is fairly simple.

% DB=/var/db/tang
% jose jwk gen -i '{"alg":"ES512"}' -o $DB/new_sig.jwk
% jose jwk gen -i '{"alg":"ECMR"}' -o $DB/new_exc.jwk
% mv $DB/old_sig.jwk $DB/.old_sig.jwk
% mv $DB/old_exc.jwk $DB/.old_exc.jwk

The old clients will still continue to use the old keys. The old keys can be removed once the clients are using the new keys. Removing the old keys before this can easily result in data loss.

Optionally, you can configure clevis to unlock removable media that has been encrypted with luks. The following procedure allows removable disks to be automatically unlocked when plugged in, in the case of being the GNOME environment. Otherwise, clevis luks unlock works just as well.

% yum install clevis-udisks2 -y
% init 6
% CFG='{"url":""}'
% clevis bind luks -d /dev/sdc1 tang "$CFG"

Restrict USB Devices

USB Guard is a software that can allow or block specific USB devices. This is done to prevent malicious devices from being plugged into a system or even whitelist very specific devices (such as a yubikey, but not another USB device that acts as a keyboard).

% yum install usbguard -y

# Generate a base policy of what's currently plugged in
% usbguard generate-policy > /etc/usbguard/rules.conf

The policy that is generated from above would look something like this.

% cat /etc/usbguard/rules.conf
allow id 1d6b:0002 serial "0000:00:14.0" name "xHCI Host Controller" hash "jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=" parent-hash "G1ehGQdrl3dJ9HvW9w2HdC//pk87pKzFE1WY25bq8k4=" with-interface 09:00:00
allow id 1d6b:0003 serial "0000:00:14.0" name "xHCI Host Controller" hash "3Wo3XWDgen1hD5xM3PSNl3P98kLp1RUTgGQ5HSxtf8k=" parent-hash "G1ehGQdrl3dJ9HvW9w2HdC//pk87pKzFE1WY25bq8k4=" with-interface 09:00:00
allow id 0853:0111 serial "" name "Realforce 87" hash "tFZkrWQsnTe7xB6rnXvrskg3d1fbZ8azPVGpQoMsiNo=" parent-hash "jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=" via-port "1-1" with-interface 03:01:01
allow id 046d:c52b serial "" name "USB Receiver" hash "5zeNOFQHsaZg43M4KgvCUwvU8C+GNCY8Rgdlwxc+Vpk=" parent-hash "jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=" via-port "1-2" with-interface { 03:01:01 03:01:02 03:00:00 }
allow id 8087:0a2b serial "" name "" hash "TtRMrWxJil9GOY/JzidUEOz0yUiwwzbLm8D7DJvGxdg=" parent-hash "jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=" via-port "1-7" with-interface { e0:01:01 e0:01:01 e0:01:01 e0:01:01 e0:01:01 e0:01:01 e0:01:01 }
allow id 5986:2113 serial "" name "Integrated Camera" hash "8WIUHlRXRajhb9Tp+q4NUjsyob4CQFAPUUTwCr+amic=" parent-hash "jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=" via-port "1-8" with-interface { 0e:01:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 0e:02:00 }
allow id 0bda:0316 serial "20120501030900000" name "USB3.0-CRW" hash "WG1MSC3YZsmCslTNGpjTTjT2lUvhNfU4gEVvD3gIuV4=" parent-hash "3Wo3XWDgen1hD5xM3PSNl3P98kLp1RUTgGQ5HSxtf8k=" with-interface 08:06:50

So now, if we enable and start usbguard, any new usb devices plugged in will be denied.

% systemctl enable usbguard --now

# At this point, I've plugged in my yubikey.
% usbguard list-devices | grep block
15: block id 1d6b:0002 serial "0000:3c:00.0" name "xHCI Host Controller" hash "+k9gUUE6Cnbob2WB/I//KMZ1hZ1UgvI6RrqNkIDvdmQ=" parent-hash "iu6QpiQUdPs2m89ViiXPDZXOJ69o6tB9kpJnYaWdvME=" via-port "usb3" with-interface 09:00:00
16: block id 1d6b:0003 serial "0000:3c:00.0" name "xHCI Host Controller" hash "f/j0P3jeotLSPQLacl0JEBDT/k4mgTo84SKV39leYSc=" parent-hash "iu6QpiQUdPs2m89ViiXPDZXOJ69o6tB9kpJnYaWdvME=" via-port "usb4" with-interface 09:00:00

# Let's allow the devices
% usbguard allow-device --permanent 15
% usbguard allow-device --permanent 16
% usbguard list-devices | grep block
18: block id 1050:0407 serial "" name "YubiKey OTP+FIDO+CCID" hash "UP/fS/jaI4Elg4Fej+gf1QXLWPleJ54MqMtO16eSmr8=" parent-hash "+k9gUUE6Cnbob2WB/I//KMZ1hZ1UgvI6RrqNkIDvdmQ=" via-port "3-1" with-interface { 03:01:01 03:00:00 0b:00:00 }

# In my case, it was my controller that was blocked initially. Now that it's unblocked, the device is blocked.
% usbguard allow-device --permanent 18

# If I wanted to block a device even after allowing it
% usbguard block-device 15
% usbguard block-device 16

In the event I want to allow another user/group to allow or block devices, you can allow it in the poolicy.

# This allows anyone in the staff group to be able to modify USB device authorization states,
# list USB devices, listen to exception events, and list USB authorization policies.
% usbguard add-user -g staff --devices=modify,list,listen --policy=list --exceptions=listen

It is also possible to be dynamic with the rules configuration, using the rule language to create your own rules. So for example. I only want to allow my keyboard access to port 1-1.

% vi /tmp/rules.conf
allow id 0853:0111 serial "" name "Realforce 87" hash "tFZkrWQsnTe7xB6rnXvrskg3d1fbZ8azPVGpQoMsiNo=" parent-hash "jEP/6WzviqdJ5VSeTUY8PatCNBKeaREvo2OqdplND/o=" via-port "1-1" with-interface 03:01:01
reject via-port "1-1"

# Now we install it. It is also possible to just modify the file directly, but that
# is generally not recommended.
% install -m 0600 -o root -g root /tmp/rules.conf /etc/usbguard/rules.conf

More information on usbguard and can found here

Manage System Login Security using Pluggable Authentication Modules (PAM)

PAM has been an essential part of Linux (and Unix, where it started) for years. PAM first appeared in the Linux world on Red Hat Linux 3.0.4 in 1996. PAM serves as a way to provide dynamic authentication support for services on a Linux system as well as applications. There are four types of management groups in PAM:

  • Auth: Validates/verifies a user’s identity. It does this in multiple ways. The most common way is requesting and checking a password.

  • Account: The account modules check that an account is a valid target under the current conditions, such as expiration, time of day, or even access to the requested service.

  • Password: These modules are responsible for updating passwords. They are generally used tightly with the auth modules. These modules can also be used to enforce strong passwords.

  • Session: These modules are responsible for the service environment. They define actions that are performed at the beginning and at the end of a session. A session starts when the user has successfully logged into the system.

To configure password policies and faillock, you have two options: Manually modifying /etc/pam.d/{system-auth,password-auth} and /etc/security/pwquality.conf or using authconfig. We’ll stick with authconfig for now and verify the pam files.

# Let's try to set some CIS benchmarks settings. Below we are:
#  * Require at least 1 lowercase letter
#  * Require at least 1 uppercase letter
#  * Require at least 1 number
#  * Require at least 1 other character
#  * Minimum of 14 characters
#  * Turning on faillock, locking a user after 5 failures, 15 minutes before the account is unlocked.
% authconfig --passminlen=14 --enablereqlower --enablerequpper --enablereqdigit --enablereqother --enablefaillock --faillockargs="audit deny=5 unlock_time=900" --updateall

# There is an odd thing that happens to the password-auth file. This is to make it consistent.
% cp /etc/pam.d/system-auth /etc/pam.d/password-auth

# Verify the pwquality.conf file
% grep -v '^#' /etc/security/pwquality.conf
minlen = 14
lcredit = -1
ucredit = -1
dcredit = -1
ocredit = -1

# Here's a system-auth example. As you can see, the faillock modules are surrounding the pam_unix in
# the auth stack, but comes before pam_unix in account. This ensures the lockout is successful. The
# pwquality module is set in the password module. The settings in /etc/security/pwquality.conf apply
# here.
% cat /etc/pam.d/system-auth

# This file is auto-generated.
# User changes will be destroyed the next time authconfig is run.
auth        required      pam_env.so
auth        required      pam_faildelay.so delay=2000000
auth        required      pam_faillock.so preauth silent audit deny=5 unlock_time=900
auth        [default=1 ignore=ignore success=ok] pam_succeed_if.so uid >= 1000 quiet
auth        [default=1 ignore=ignore success=ok] pam_localuser.so
auth        sufficient    pam_unix.so nullok try_first_pass
auth        requisite     pam_succeed_if.so uid >= 1000 quiet_success
auth        sufficient    pam_sss.so forward_pass
auth        required      pam_faillock.so authfail audit deny=5 unlock_time=900
auth        required      pam_deny.so

account     required      pam_faillock.so
account     required      pam_unix.so
account     sufficient    pam_localuser.so
account     sufficient    pam_succeed_if.so uid < 1000 quiet
account     [default=bad success=ok user_unknown=ignore] pam_sss.so
account     required      pam_permit.so

password    requisite     pam_pwquality.so try_first_pass local_users_only retry=3 authtok_type=
password    sufficient    pam_unix.so sha512 shadow nullok try_first_pass use_authtok
password    sufficient    pam_sss.so use_authtok
password    required      pam_deny.so

session     optional      pam_keyinit.so revoke
session     required      pam_limits.so
-session     optional      pam_systemd.so
session     optional      pam_oddjob_mkhomedir.so umask=0077
session     [success=1 default=ignore] pam_succeed_if.so service in crond quiet use_uid
session     required      pam_unix.so
session     optional      pam_sss.so

# Optional: If you want to turn on password history, you can either add remember=5 to pam_unix.so in the password
#           stack or add pam_pwhistory.so. This is done in /etc/pam.d/system-auth and /etc/pam.d/password-auth

# Example 1
password    sufficient    pam_unix.so sha512 shadow nullok try_first_pass use_authtok remember=5

# Example 2 (recommended)
password    requisite     pam_pwhistory.so use_authtok remember=5
password    sufficient    pam_unix.so sha512 shadow nullok try_first_pass use_authtok remember=5

Configure System Auditing

Auditd is a subsystem that deals in access monitoring and accounting for Linux. It was built and designed to be integrated deep into the kernel and watch for system calls, whether normal or malicious, as such to create an audit trail. It does not provide any additional security. Instead, it acts as a logger of violations and actions performed on the system. Because of the deep integration, auditd is used as the logger for SELinux.

There’s a couple of ways to enable auditd rules.

  • You can either modify /etc/audit/rules.d/audit.rules or drop a file appended with .rules and then run augenrules

  • You can run auditctl - Though the next time auditd or the system is restarted, the rule you have added is lost. This should only be used as a test.

What you’ll find when you view the initial rules is that you’ll see a -D (which deletes all current rules) and then a -b, which is a backlog buffer. As for the syntax of an audit rule where we are attempting to monitor something, it generally works like this:

# Action can be always/never, filter can be task, exit, user
-a action,filter \
# field=value specifies additional options that modify the rule to match events.
# It can match based on arch, group ID, process ID, and many others. See man auditctl(8)
-F field=value \
# This is an interfield comparison. It checks whether something equals or doesn't equal another value.
# Example, -C uid!=euid means that the UID of the event should NOT equal/match the EFFECTIVE UID
-C comparison \
# Specify the system call by name. Examples would be setuid, execve. See /usr/include/asm/unistd_64.h
-S system call \
# This is a key that can be used to match against when running ausearch or aureport. This can also be
# specified using -F key=name instead of using -k name
-k key

# Example ruleset:
# Delete all rules

# Set backlog to 8192, way above the RHEL 7 default
-b 8192

# Let's monitor calling su for uid's equal to or greater than 1000
-a always,exit -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=4294967295 -k privileged
-a always,exit -F arch=b64 -S setuid -Fa0=0 -F exe=/usr/bin/su -k privileged
-a always,exit -F arch=b64 -S setresuid -F a0=0 -F exe=/usr/bin/sudo -k privileged
-a always,exit -F arch=b64 -S execve -C uid!=euid -F euid=0 -k privileged

# Any time the su command is called (which is a setuid binary), it is logged under the key privileged.
# Here's an example of me calling sudo su -
% ausearch -k privileged -ui 1000 -x /usr/bin/su
time->Sun Nov 17 14:59:00 2019
type=PROCTITLE msg=audit(1574027940.430:155902): proctitle=7375646F007375002D
type=PATH msg=audit(1574027940.430:155902): item=1 name="/lib64/ld-linux-x86-64.so.2" inode=8413547 dev=fd:00 mode=0100755 ouid=0 ogid=0 rdev=00:00 obj=system_u:object_r:ld_so_t:s0 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=PATH msg=audit(1574027940.430:155902): item=0 name="/usr/bin/sudo" inode=4414497 dev=fd:00 mode=0104111 ouid=0 ogid=0 rdev=00:00 obj=system_u:object_r:sudo_exec_t:s0 nametype=NORMAL cap_fp=0000000000000000 cap_fi=0000000000000000 cap_fe=0 cap_fver=0
type=EXECVE msg=audit(1574027940.430:155902): argc=3 a0="sudo" a1="su" a2="-"
type=SYSCALL msg=audit(1574027940.430:155902): arch=c000003e syscall=59 success=yes exit=0 a0=5558e5c69c60 a1=5558e5b54700 a2=5558e5b52c80 a3=8 items=2 ppid=13115 pid=13146 auid=1000 uid=1000 gid=1000 euid=0 suid=0 fsuid=0 egid=1000 sgid=1000 fsgid=1000 tty=pts5 ses=1 comm="sudo" exe="/usr/bin/sudo" subj=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 key="privileged"

What if though, you don’t want to configure your own rules? Or come up with your own? What if you just want some form of compliance (like PCI DSS)? Well thankfully there is some predefined rules provided to you.

% ls /usr/share/doc/audit-2.8.5/rules | sort

You can easily take these rules and copy them, run augenrules, and you’re golden. Here’s an example.

% cp /usr/share/doc/audit-2.8.5/rules/30-pci-dss-v31.rules /etc/audit/rules.d/pci.rules
% augenrules
# Let's grep for a part...
% grep 'clock_settime' /etc/audit/audit.rules
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=10.4.2b-time-change
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=10.4.2b-time-change

# Though, you could grep for just 'always,exit' and get a much bigger result.

Looks like our rules took and they’re active.

For giggles, let’s produce some audit reports on my system.

% aureport --start yesterday 00:00:00 --end today 00:00:00

Summary Report
Range of time in logs: 10/22/2019 15:56:25.579 - 11/17/2019 15:41:51.453
Selected time for report: 11/16/2019 00:00:00 - 11/17/2019 15:41:51
Number of changes in configuration: 0
Number of changes to accounts, groups, or roles: 0
Number of logins: 2
Number of failed logins: 0
Number of authentications: 11
Number of failed authentications: 0
Number of users: 3
Number of terminals: 13
Number of host names: 4
Number of executables: 14
Number of commands: 13
Number of files: 84
Number of AVC's: 0
Number of MAC events: 3
Number of failed syscalls: 8
Number of anomaly events: 0
Number of responses to anomaly events: 0
Number of crypto events: 191
Number of integrity events: 0
Number of virt events: 0
Number of keys: 8
Number of process IDs: 77
Number of events: 1110

aureport -x --summary | head

Executable Summary Report
total  file
14879  /usr/sbin/timedatex
14515  /usr/sbin/sshd
8491  /usr/libexec/platform-python3.6
6357  /usr/sbin/chronyd
2974  /usr/bin/sudo
# ... many more!

# Failed events only just for users (the -i translates UID into name)
% aureport -u --failed --summary -i

Failed User Summary Report
total  auid
822  ansible
451  nazu
28  unset

# Check against our TTY consoles (physical console)
# I haven't logged into my machine physically in some time.
% aureport --tty

TTY Report
# date time event auid term sess comm data
<no events of interest were found>

# I can also try to search for events though.
% ausearch --start boot -m LOGIN | head -3
time->Tue Oct 22 20:01:38 2019
type=LOGIN msg=audit(1571799698.833:107001): pid=26065 uid=0 subj=system_u:system_r:sshd_t:s0-s0:c0.c1023 old-auid=4294967295 auid=1000 tty=(none) old-ses=4294967295 ses=64 res=1

# Check it out, in CSV format!
% ausearch --start boot -m LOGIN --format csv | head -2

A nice thing to know how to configure too is the auditd settings themselves in /etc/audit/auditd.conf. This configuration can be modified to control how big logs can be, what to do if the filesystem is filled up, or what to do when the logs have been maxed out. See man auditd.conf 5.

Configure SELinux

If you have used RHEL or CentOS (or even Fedora) and looked up guides on how to setup or configure something, you may notice that some tend to start out the same exact way: “Disable SELinux”. This is an errorneous and quite frankly, a bad set of advice to ever give to a user. Disabling the security integrity of your system to install an application should always be considered to be poor advice. There is no reason or case where you must disable the security layers of your system. From a support standpoint in fact, if you are a CentOS user and go to the IRC channel on Freenode, you will be denied support for having it disabled.

“What about anti virus though? My company dictates we use $x and we have to disable it to use it.” Again, you are disabling a core security component of the kernel and operating system for no gain. In fact, you could just use ClamAV and have no SELinux policy violations. But that is beyond the scope of this section.

To ensure SELinux is enforcing, you can run the getenforce command. If it is set to “disabled”, you will need to enable it and relabel your filesystem.

% getenforce
% vi /etc/selinux/config

% touch /.autorelabel
% init 6

In most general use cases, there are a lot of booleans that can be set that enable or disable a specific feature. Here are some common examples using httpd (apache).

% getsebool -a | grep httpd
. . .
( lots of settings! )

# A common one to enable is httpd_unified
# This boolean allows apache/nginx to perform writes under httpd_sys_content_rw_t
% setsebool httpd_unified 1

# Another odd one is httpd_enable_homedir
# This allows users to have a ~/public_html directory to be accessible
% setsebool httpd_enable_homedirs 1

All files and directories have an SELinux context. They usually have, in the general case, stock/default contexts that do fine in most cases. For example. /var/www/html and /srv/www get httpd_sys_content_t by default.

% matchpathcon /srv/www
/srv/www        system_u:object_r:httpd_sys_content_t:s0
% matchpathcon /var/www/html
/var/www/html   system_u:object_r:httpd_sys_content_t:s0

You can find all the contexts using the semanage command. You’ll be surprised at the number of contexts there are.

% semanage fcontext -l

Let’s say you want to make some directory outside of the norm allowable by SELinux for httpd.

% mkdir /opt/www
% semanage fcontext -a -t httpd_sys_content_t "/opt/www(/.*)?"
% restorecon -Rv /opt/www

# There is also the chcon command, but it is not permanent. A restorecon or
# an autorelabel will wipe out the contexts.

You will find if you run cp the contexts will change based on the directory the file lands. If you run mv, the context moves with it. If you find if a context is set incorrectly, restorecon will come to the rescue.

% ls -lZ /tmp/id_rsa*
-rw-------. 1 root root unconfined_u:object_r:user_tmp_t:s0 1843 Nov 19 22:27 /tmp/id_rsa
-rw-r--r--. 1 root root unconfined_u:object_r:user_tmp_t:s0  415 Nov 19 22:27 /tmp/id_rsa.pub
% mv /tmp/id_rsa* ~/.ssh/
% ls -lZ ~/.ssh/
total 12
-rw-------. 1 root root unconfined_u:object_r:user_tmp_t:s0 1843 Nov 19 22:27 id_rsa
-rw-r--r--. 1 root root unconfined_u:object_r:user_tmp_t:s0  415 Nov 19 22:27 id_rsa.pub
% restorecon -v ~/.ssh/*
Relabeled /root/.ssh/id_rsa from unconfined_u:object_r:user_tmp_t:s0 to unconfined_u:object_r:ssh_home_t:s0
Relabeled /root/.ssh/id_rsa.pub from unconfined_u:object_r:user_tmp_t:s0 to unconfined_u:object_r:ssh_home_t:s0

# Not the best example, but you get the idea.

Some interesting tidbits is how the ps command can show the context in which a process is running under.

% ps -Z -C systemd
LABEL                             PID TTY          TIME CMD
system_u:system_r:init_t:s0         1 ?        00:02:18 systemd
unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023 2709 ? 00:00:01 systemd

# This would show you all running processes, including their contexts.
# Both formats support the option.
% ps -efZ
% ps auxZ

Since processes run under some form of context, they do sometimes have the ability to transition. For example.

# Search for allow rules with the source type of ftpd_t
# and a target type of public_content_t, and the class name of 'file'
% sesearch -A -s ftpd_t -t public_content_t -c file
Found 2 semantic av rules:
   allow ftpd_t public_content_t : file { ioctl read getattr lock open } ;
   allow ftpd_t non_security_file_type : file { ioctl read write create getattr setattr lock append unlink link rename open } ;

One sort of uncommon, but clever piece of SELinux is the idea of SELinux users. An SELinux User has roles. Some users have more than one role. What the role means is defined by policy. Roles dictates what domains (contexts) are possible, including transitioning between roles. Let’s take a look at some defaults.

% semanage login -l

Login Name           SELinux User         MLS/MCS Range        Service

__default__          unconfined_u         s0-s0:c0.c1023       *
admin                unconfined_u         s0-s0:c0.c1023       *
label                unconfined_u         s0-s0:c0.c1023       *
root                 unconfined_u         s0-s0:c0.c1023       *
tester               unconfined_u         s0-s0:c0.c1023       *

# The default is unconfined_u. Here's all the available maps.
% semanage user -l

                Labeling   MLS/       MLS/
SELinux User    Prefix     MCS Level  MCS Range                      SELinux Roles

guest_u         user       s0         s0                             guest_r
root            user       s0         s0-s0:c0.c1023                 staff_r sysadm_r system_r unconfined_r
staff_u         user       s0         s0-s0:c0.c1023                 staff_r sysadm_r system_r unconfined_r
sysadm_u        user       s0         s0-s0:c0.c1023                 sysadm_r
system_u        user       s0         s0-s0:c0.c1023                 system_r unconfined_r
unconfined_u    user       s0         s0-s0:c0.c1023                 system_r unconfined_r
user_u          user       s0         s0                             user_r
xguest_u        user       s0         s0                             xguest_r

# Here is what the user maps do
unconfined_u - do not have additional user-based SELinux restrictions.
user_u       - Non-admin users. "su" or "sudo" cannot be used.
             -> user_r role applied
staff_u      - Regular users. "sudo" is allowed, "su" is not
             -> staff_r and sysadm_r roles applied, user can switch between both roles using "newrole -r"
sysadm_u     - Direct system administrator role. "su" and "sudo" is allowed.
             -> sysadm_r role applied
system_u     - User meant for system services
             -> system_r role applied. "newrole -r" will not allow a user to switch to this.

# To set the __default__ login, you can use semanage.
# We'll make sure the default is user_u
% semanage login -m -s user_u -r s0 __default__

# I want to set my label user as sysadm_u
% semanage login -m -s sysadm_u label

# Maybe I want to remove it?
% semanage login -d -s sysadm_u label

# Actually, I want a new user in that map
% useradd -Z sysadm_u sysadmin

# If a user has sysadm_u, they can only logged into locally. This means ssh to that user will fail
# even if you know the password. If you want to allow them to SSH:
% setsebool -P ssh_sysadm_login on

# Well, what if I want to prevent users and guests to not be allowed to execute in their homes?
% getsebool -a | grep exec_content
auditadm_exec_content --> on
dbadm_exec_content --> on
guest_exec_content --> on
logadm_exec_content --> on
secadm_exec_content --> on
staff_exec_content --> on
sysadm_exec_content --> on
user_exec_content --> on
xguest_exec_content --> on

% setsebool user_exec_content 0
% setsebool guest_exec_content 0

More info on users and roles can be found here.

Package & Binary Glossary

  • setroubleshoot-server

    • sealert

  • policycoreutils-devel

    • sepolicy

  • policycoreutils-python

    • semanage

    • audit2why

    • audit2allow

  • policycoreutils-newrole

    • newrole

  • setools-console

    • seinfo

Command Glossary

# semanage -> SELinux Policy Management Tool
# Append an -l to "list"
semanage fcontext -> Manages file contexts
semanage port     -> Manages network port types
semanage login    -> Manages login mappings between linux and selinux confined users
semanage user     -> Manages confined users

# setsebool / getsebool -> SELinux boolean values
setsebool [-PNV] -> Sets a boolean value
getsebool [-a] -> Gets a or all booleans and their values

Enforce Security Compliance