CIS Hardening of a Debian Linux Server Part 10: Level 2 Logging and Auditing

This is the eleventh entry in the series documenting the Debian 13 server CIS hardening project.

This pass covers Level 2 recommendations that were deferred in the Level 1 pass.

6.1.2 Configure rsyslog #

This section covers 6.1.2.9 through 6.1.2.11 of the benchmark. All three were flagged as Level 2 during the Level 1 pass and deferred.

6.1.2.9 Ensure rsyslog-gnutls Is Installed #

# dpkg-query -s rsyslog-gnutls &>/dev/null && echo "rsyslog-gnutls is installed"

No output, indicating the package is not present.

# apt install rsyslog-gnutls
# dpkg-query -s rsyslog-gnutls &>/dev/null && echo "rsyslog-gnutls is installed"
rsyslog-gnutls is installed

Recommendation passes.

Setting Up rsyslog Forwarding With a Log Server #

Satisfying 6.1.2.10 and 6.1.2.11 requires a certificate authority, a second machine to act as the log server, and a certificate pair for each host. A dedicated VPS was provisioned to act as the rsyslog server, built from a snapshot of the already-hardened server, and is referred to below as the server (i.e. the log server); the original machine is referred to as the client (i.e. the rsyslog client).

A private network (192.168.0.0/24) was created and both hosts attached to it, with the client and server each receiving a private address automatically (192.168.0.2 for the client and 192.168.0.3 for the server).

Hetzner Cloud “starter-net” private network overview

A certificate authority was generated on a separate, local machine:

$ certtool --generate-privkey --outfile ca-key.pem --bits 4096
** Note: You may use '--sec-param High' instead of '--bits 4096'
Generating a 4096 bit RSA private key...
$ ls
ca-key.pem
$ certtool --generate-self-signed --load-privkey ca-key.pem --outfile ca.pem
Generating a self signed certificate...
Please enter the details of the certificate's distinguished name. Just press enter to ignore a field.
Country name (2 chars): 
State or province name: 
Locality name: 
Organization name: 
Organizational unit name: 
Common name: rsyslogCA
UID: 
Enter the subject's domain component (DC): 
This field should not be used in new certificates.
E-mail: 
Enter the certificate's serial number in decimal (123) or hex (0xabcd)
(default is <REDACTED>)
value: 


Activation/Expiration time.
The certificate will expire in (days): 3650


Extensions.
Does the certificate belong to an authority? (y/N): y
Path length constraint (decimal, -1 for no constraint): 
Is this a TLS web client certificate? (y/N): 
Will the certificate be used for IPsec IKE operations? (y/N): 
Is this a TLS web server certificate? (y/N): 
Enter a dnsName of the subject of the certificate: 
Enter a URI of the subject of the certificate: 
Enter the IP address of the subject of the certificate: 
Enter the e-mail of the subject of the certificate: 
Will the certificate be used for signing (required for TLS)? (Y/n): y
Will the certificate be used for data encryption? (y/N): 
Will the certificate be used to sign OCSP requests? (y/N): 
Will the certificate be used to sign code? (y/N): 
Will the certificate be used for time stamping? (y/N): 
Will the certificate be used for email protection? (y/N): 
Will the certificate be used to sign other certificates? (Y/n): y
Will the certificate be used to sign CRLs? (y/N): 
Enter the URI of the CRL distribution point: 
X.509 Certificate Information:
        Version: 3
        Serial Number (hex): <REDACTED>
        Validity:
                Not Before: Sat Aug 15 09:53:58 UTC 2026
                Not After: Tue Aug 12 09:54:11 UTC 2036
        Subject: CN=rsyslogCA
        Subject Public Key Algorithm: RSA
        Algorithm Security Level: High (4096 bits)
                Modulus (bits 4096):
                        <REDACTED>
                Exponent (bits 24):
                        01:00:01
        Extensions:
                Basic Constraints (critical):
                        Certificate Authority (CA): TRUE
                Key Usage (critical):
                        Digital signature.
                        Certificate signing.
                Subject Key Identifier (not critical):
                        <REDACTED>
Other Information:
        Public Key ID:
                <REDACTED>
        Public Key PIN:
                <REDACTED>

Is the above information ok? (y/N): y


Signing certificate...

$ ls
ca-key.pem  ca.pem
$ chmod 400 ca-key.pem
$ chmod 600 ca.pem
$ ls -l               
total 16
-r-------- 1 sharaf sharaf 10722 Aug 15 10:52 ca-key.pem
-rw------- 1 sharaf sharaf  1785 Aug 15 10:56 ca.pem

A machine certificate is generated for each host, signed by this CA. The client’s certificate is generated as a TLS web client certificate; the server’s as a TLS web server certificate. The full interactive session is shown once, for the client:

$ certtool --generate-privkey --outfile starter-key.pem --bits 4096
** Note: You may use '--sec-param High' instead of '--bits 4096'
Generating a 4096 bit RSA private key...
$ certtool --generate-certificate --load-privkey starter-key.pem --outfile starter-cert.pem --load-ca-certificate ca.pem --load-ca-privkey ca-key.pem
Generating a signed certificate...
Please enter the details of the certificate's distinguished name. Just press enter to ignore a field.
Country name (2 chars): 
State or province name: 
Locality name: 
Organization name: 
Organizational unit name: 
Common name: starter
UID: 
Enter the subject's domain component (DC): 
This field should not be used in new certificates.
E-mail: 
Enter the certificate's serial number in decimal (123) or hex (0xabcd)
(default is <REDACTED>)
value: 


Activation/Expiration time.
The certificate will expire in (days): 1000


Extensions.
Does the certificate belong to an authority? (y/N): n
Is this a TLS web client certificate? (y/N): y
Will the certificate be used for IPsec IKE operations? (y/N): 
Is this a TLS web server certificate? (y/N): n
Enter a dnsName of the subject of the certificate: 
Enter a URI of the subject of the certificate: 
Enter the IP address of the subject of the certificate: 
Enter the e-mail of the subject of the certificate: 
Will the certificate be used for signing (required for TLS)? (Y/n): 
Will the certificate be used for encryption (not required for TLS)? (Y/n): 
Will the certificate be used for data encryption? (y/N): 
Will the certificate be used to sign OCSP requests? (y/N): 
Will the certificate be used to sign code? (y/N): 
Will the certificate be used for time stamping? (y/N): 
Will the certificate be used for email protection? (y/N): 
Enter the URI of the CRL distribution point: 
X.509 Certificate Information:
        Version: 3
        Serial Number (hex): <REDACTED>
        Validity:
                Not Before: Sat Aug 15 10:18:05 UTC 2026
                Not After: Fri May 11 10:18:13 UTC 2029
        Subject: CN=starter
        Subject Public Key Algorithm: RSA
        Algorithm Security Level: High (4096 bits)
                Modulus (bits 4096):
                        <REDACTED>
                Exponent (bits 24):
                        01:00:01
        Extensions:
                Basic Constraints (critical):
                        Certificate Authority (CA): FALSE
                Key Purpose (not critical):
                        TLS WWW Client.
                Key Usage (critical):
                        Digital signature.
                        Key encipherment.
                Subject Key Identifier (not critical):
                        <REDACTED>
                Authority Key Identifier (not critical):
                        <REDACTED>
Other Information:
        Public Key ID:
                <REDACTED>
        Public Key PIN:
                <REDACTED>

Is the above information ok? (y/N): y


Signing certificate...
$ ls 
ca-key.pem  ca.pem  starter-cert.pem  starter-key.pem
$ chmod 600 starter-cert.pem starter-key.pem 

The same procedure is repeated for the server, substituting the common name and the certificate’s purpose: heyday in place of starter, and the TLS web server certificate prompt answered y in place of the TLS web client certificate prompt. Both key and certificate files end up with 600 permissions.

ca.pem plus the certificate and key for each host are transferred to their respective machines with scp:

$ scp -p ca.pem starter-cert.pem starter-key.pem starter:~/
Authorized users only. All activity may be monitored and reported.
Enter passphrase for key '<REDACTED>': 
ca.pem                                                                                                        100% 1785    23.1KB/s   00:00    
starter-cert.pem                                                                                              100% 1850    24.0KB/s   00:00    
starter-key.pem                                                                                               100%   10KB  66.8KB/s   00:00

The files are then moved into place, with the key restricted to the private directory:

# mv /home/sharaf/ca.pem /etc/ssl/certs/
# mv /home/sharaf/starter-cert.pem /etc/ssl/certs/
# mv /home/sharaf/starter-key.pem /etc/ssl/private/
# chown root:root /etc/ssl/certs/ca.pem /etc/ssl/certs/starter-cert.pem /etc/ssl/private/starter-key.pem
# chmod 600 /etc/ssl/certs/ca.pem /etc/ssl/certs/starter-cert.pem /etc/ssl/private/starter-key.pem
# ls -l /etc/ssl/certs/ca.pem /etc/ssl/certs/starter-cert.pem /etc/ssl/private/starter-key.pem
-rw------- 1 root root  1785 Aug 15 09:56 /etc/ssl/certs/ca.pem
-rw------- 1 root root  1850 Aug 15 10:20 /etc/ssl/certs/starter-cert.pem
-rw------- 1 root root 10725 Aug 15 10:16 /etc/ssl/private/starter-key.pem

The same is done on the server, with heyday-cert.pem and heyday-key.pem in place of the client’s files.

With the certificates in place, rsyslog is configured on both ends. On the server, a dedicated file avoids editing /etc/rsyslog.conf directly:

# cat /etc/rsyslog.d/60-log-server.conf
module(load="imtcp")

global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="/etc/ssl/certs/ca.pem"
    DefaultNetstreamDriverCertFile="/etc/ssl/certs/heyday-cert.pem"
    DefaultNetstreamDriverKeyFile="/etc/ssl/private/heyday-key.pem"
    )

input(
    type="imtcp"
    port="6514"
    StreamDriver.Name="gtls"
    StreamDriver.Mode="1"
    StreamDriver.AuthMode="x509/name"
    PermittedPeer=["starter"]
    )

An initial attempt at validation fails, since imuxsock is already loaded in /etc/rsyslog.conf:

# rsyslogd -N1 -f /etc/rsyslog.conf
rsyslogd: version 8.2504.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: module 'imuxsock' already in this config, cannot be added  [v8.2504.0 try https://www.rsyslog.com/e/2221 ]

The imuxsock load line is removed from the new file, after which validation succeeds and the service is restarted:

# rsyslogd -N1 -f /etc/rsyslog.conf
rsyslogd: version 8.2504.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: End of config validation run. Bye.
# systemctl restart rsyslog
# ss -tlnp | grep 6514
LISTEN 0      25           0.0.0.0:6514      0.0.0.0:*    users:(("rsyslogd",pid=59362,fd=6))
LISTEN 0      25              [::]:6514         [::]:*    users:(("rsyslogd",pid=59362,fd=7))

On the client, a similarly dedicated file configures forwarding:

# cat /etc/rsyslog.d/60-log-client.conf
global(
    DefaultNetstreamDriver="gtls"
    DefaultNetstreamDriverCAFile="/etc/ssl/certs/ca.pem"
    DefaultNetstreamDriverCertFile="/etc/ssl/certs/starter-cert.pem"
    DefaultNetstreamDriverKeyFile="/etc/ssl/private/starter-key.pem"
)

action(
    type="omfwd"
    target="192.168.0.3"
    protocol="tcp"
    port="6514"
    StreamDriver="gtls"
    StreamDriverMode="1"
    StreamDriverAuthMode="x509/name"
    StreamDriverPermittedPeers="heyday"
    queue.type="linkedList"
)

# rsyslogd -N1 -f /etc/rsyslog.conf
rsyslogd: version 8.2504.0, config validation run (level 1), master config /etc/rsyslog.conf
rsyslogd: End of config validation run. Bye.
# systemctl restart rsyslog

UFW rules on the private interface complete the setup. On the server:

# ufw allow in on enp7s0 from 192.168.0.2 to any port 6514 proto tcp
Rule added
# ufw reload
Firewall reloaded
# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), deny (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere                  
6514/tcp on enp7s0         ALLOW IN    192.168.0.2               
22/tcp (v6)                ALLOW IN    Anywhere (v6)             

80/tcp                     ALLOW OUT   Anywhere                  
443                        ALLOW OUT   Anywhere                  
123/udp                    ALLOW OUT   Anywhere                  
53                         ALLOW OUT   Anywhere                  
853                        ALLOW OUT   Anywhere                  
80/tcp (v6)                ALLOW OUT   Anywhere (v6)             
443 (v6)                   ALLOW OUT   Anywhere (v6)             
123/udp (v6)               ALLOW OUT   Anywhere (v6)             
53 (v6)                    ALLOW OUT   Anywhere (v6)             
853 (v6)                   ALLOW OUT   Anywhere (v6) 

On the client:

# ufw allow out on enp7s0 to 192.168.0.3 port 6514 proto tcp
Rule added
# ufw reload
Firewall reloaded
# ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), deny (outgoing), disabled (routed)
New profiles: skip

To                         Action      From
--                         ------      ----
22/tcp                     ALLOW IN    Anywhere                  
22/tcp (v6)                ALLOW IN    Anywhere (v6)             

80/tcp                     ALLOW OUT   Anywhere                  
443                        ALLOW OUT   Anywhere                  
123/udp                    ALLOW OUT   Anywhere                  
53                         ALLOW OUT   Anywhere                  
853                        ALLOW OUT   Anywhere                  
192.168.0.3 6514/tcp     ALLOW OUT   Anywhere on enp7s0        
80/tcp (v6)                ALLOW OUT   Anywhere (v6)             
443 (v6)                   ALLOW OUT   Anywhere (v6)             
123/udp (v6)               ALLOW OUT   Anywhere (v6)             
53 (v6)                    ALLOW OUT   Anywhere (v6)             
853 (v6)                   ALLOW OUT   Anywhere (v6)   

Hetzner’s own documentation on cloud firewalls notes that they do not secure traffic within a private network, so no further adjustment is made on that front.

A test message confirms end-to-end delivery. On the client:

# hostname
starter
# logger -p local0.info "Test message from starter"

On the server:

# hostname
heyday
# tail -n 1 /var/log/syslog
2026-08-15T12:27:32+00:00 starter root: Test message from starter

The message arrives as expected.

6.1.2.10 Ensure rsyslog Forwarding Uses gtls #

Satisfying this recommendation requires the certificate infrastructure described above, so the audit is expected to fail until that setup is complete.

# grep -Psi -- '^\h*StreamDriver=\"gtls\"' /etc/rsyslog.conf /etc/rsyslog.d/*.conf

No output.

With that setup complete, the audit is run again:

# grep -Psi -- '^\h*StreamDriver=\"gtls\"' /etc/rsyslog.conf /etc/rsyslog.d/*.conf
/etc/rsyslog.d/60-log-client.conf:    StreamDriver="gtls"

Recommendation passes.

6.1.2.11 Ensure rsyslog CA Certificates Are Configured #

This recommendation was likewise skipped until the global TLS configuration was in place. Only the audit is confirmed here.

# grep -Psi -- 'DefaultNetstreamDriverCAFile' /etc/rsyslog.conf /etc/rsyslog.d/*
/etc/rsyslog.d/60-log-client.conf:    DefaultNetstreamDriverCAFile="/etc/ssl/certs/ca.pem"

Recommendation passes.

6.2 System Auditing #

This section covers 6.2.1 through 6.2.4 of the benchmark. All four are Level 2 and were deferred in the Level 1 pass.

6.2.1 Configure auditd Service #

6.2.1.1 Ensure auditd Packages Are Installed #

# dpkg-query -s auditd &>/dev/null && echo auditd is installed
# dpkg-query -s audispd-plugins &>/dev/null && echo audispd-plugins is installed

No output for either check. Neither package is installed.

# apt install auditd audispd-plugins
# dpkg-query -s auditd &>/dev/null && echo auditd is installed
auditd is installed
# dpkg-query -s audispd-plugins &>/dev/null && echo audispd-plugins is installed
audispd-plugins is installed

Recommendation passes.

6.2.1.2 Ensure auditd Service Is Enabled and Active #

# systemctl is-enabled auditd | grep '^enabled'
enabled
# systemctl is-active auditd | grep '^active'
active

Recommendation passes.

6.2.1.3 Ensure Auditing for Processes That Start Prior to auditd Is Enabled #

# find /boot -type f -name 'grub.cfg' -exec grep -Ph -- '^\h*linux' {} + | grep -v 'audit=1'
        linux   /boot/vmlinuz-6.12.96+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro  consoleblank=0 systemd.show_status=true console=tty1 console=ttyS0
                linux   /boot/vmlinuz-6.12.96+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro  consoleblank=0 systemd.show_status=true console=tty1 console=ttyS0
                linux   /boot/vmlinuz-6.12.96+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro single dis_ucode_ldr 
                linux   /boot/vmlinuz-6.12.95+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro  consoleblank=0 systemd.show_status=true console=tty1 console=ttyS0
                linux   /boot/vmlinuz-6.12.95+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro single dis_ucode_ldr 

Every boot entry is missing audit=1. Nothing should be returned.

GRUB_CMDLINE_LINUX in /etc/default/grub is set to "audit=1".

update-grub is run to regenerate the boot configuration:

# update-grub
Generating grub configuration file ...
Found linux image: /boot/vmlinuz-6.12.96+deb13-cloud-amd64
Found initrd image: /boot/initrd.img-6.12.96+deb13-cloud-amd64
Found linux image: /boot/vmlinuz-6.12.95+deb13-cloud-amd64
Found initrd image: /boot/initrd.img-6.12.95+deb13-cloud-amd64
Warning: os-prober will not be executed to detect other bootable partitions.
Systems on them will not be added to the GRUB boot configuration.
Check GRUB_DISABLE_OS_PROBER documentation entry.
Adding boot menu entry for UEFI Firmware Settings ...
done
# find /boot -type f -name 'grub.cfg' -exec grep -Ph -- '^\h*linux' {} + | grep -v 'audit=1'

No output. Recommendation passes.

6.2.1.4 Ensure audit_backlog_limit Is Configured #

# find /boot -type f -name 'grub.cfg' -exec grep -Ph -- '^\h*linux' {} + | grep -Pv 'audit_backlog_limit=\d+\b'
        linux   /boot/vmlinuz-6.12.96+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro audit=1 consoleblank=0 systemd.show_status=true console=tty1 console=ttyS0
                linux   /boot/vmlinuz-6.12.96+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro audit=1 consoleblank=0 systemd.show_status=true console=tty1 console=ttyS0
                linux   /boot/vmlinuz-6.12.96+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro single dis_ucode_ldr audit=1
                linux   /boot/vmlinuz-6.12.95+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro audit=1 consoleblank=0 systemd.show_status=true console=tty1 console=ttyS0
                linux   /boot/vmlinuz-6.12.95+deb13-cloud-amd64 root=UUID=8ba91a8e-417d-4e0c-96a7-1f1468d56d79 ro single dis_ucode_ldr audit=1

Nothing should be returned.

audit_backlog_limit=8192 is appended to GRUB_CMDLINE_LINUX in /etc/default/grub, immediately after audit=1, following the same space separated format already used on GRUB_CMDLINE_LINUX_DEFAULT. The line becomes:

GRUB_CMDLINE_LINUX="audit=1 audit_backlog_limit=8192"

The rest of the file is unchanged. update-grub is run again, and the audit is repeated:

# find /boot -type f -name 'grub.cfg' -exec grep -Ph -- '^\h*linux' {} + | grep -Pv 'audit_backlog_limit=\d+\b'

No output. The 6.2.1.3 audit is also re-run to confirm the new change did not regress it:

# find /boot -type f -name 'grub.cfg' -exec grep -Ph -- '^\h*linux' {} + | grep -v 'audit=1'

No output. Both recommendations pass.

6.2.2 Configure Data Retention #

This section covers 6.2.2.1 through 6.2.2.4 of the benchmark.

6.2.2.1 Ensure Audit Log Storage Size Is Configured #

# grep -Po -- '^\h*max_log_file\h*=\h*\d+\b' /etc/audit/auditd.conf
max_log_file = 8

Recommendation passes. The benchmark does not mandate a specific size, so a decision is made to leave the existing value unchanged. It can be revisited later if needed.

6.2.2.2 Ensure Audit Logs Are Not Automatically Deleted #

# grep max_log_file_action /etc/audit/auditd.conf
max_log_file_action = ROTATE

Not compliant. The expected value is keep_logs.

max_log_file_action is changed from ROTATE to keep_logs in /etc/audit/auditd.conf.

# grep max_log_file_action /etc/audit/auditd.conf
max_log_file_action = keep_logs

Recommendation passes.

6.2.2.3 Ensure System Is Disabled When Audit Logs Are Full #

disk_full_action controls the daemon’s behavior once the partition holding the audit logs fills up, and accepts values ranging from a plain syslog warning to rotating logs, executing a script, suspending logging, or halting the system. disk_error_action controls behavior when a write or rotation error occurs, with a similar range of options short of rotate.

# grep -Pi -- '^\h*disk_full_action\h*=\h*(halt|single)\b' /etc/audit/auditd.conf
# grep -Pi -- '^\h*disk_error_action\h*=\h*(syslog|single|halt)\b' /etc/audit/auditd.conf

No output for either. Neither is compliant.

The benchmark recommends halt or single for disk_full_action, but both carry a risk of losing access to the VPS if triggered unexpectedly. rotate is used instead for disk_full_action, and syslog for disk_error_action, both previously set to SUSPEND:

# grep -Pi -- '^\h*disk_full_action\h*=\h*(halt|single)\b' /etc/audit/auditd.conf
# grep -Pi -- '^\h*disk_error_action\h*=\h*(syslog|single|halt)\b' /etc/audit/auditd.conf
disk_error_action = syslog

disk_error_action now passes. disk_full_action remains non-compliant by design, on account of the lockout risk described above.

That decision is revisited, and both parameters are instead set to halt, with the Hetzner rescue system available as a fallback should either trigger:

# grep -Pi -- '^\h*disk_full_action\h*=\h*(halt|single)\b' /etc/audit/auditd.conf
disk_full_action = halt
# grep -Pi -- '^\h*disk_error_action\h*=\h*(syslog|single|halt)\b' /etc/audit/auditd.conf
disk_error_action = halt

Both recommendations pass.

6.2.2.4 Ensure System Warns When Audit Logs Are Low on Space #

space_left_action is the first of three escalating thresholds and fires as disk space starts to run low. Valid values include a syslog warning, log rotation, an email notice, a custom script, or suspending the daemon. According to the auditd.conf(5) man page, its halt option is deprecated and should no longer be used. admin_space_left_action is the second, emergency-level threshold, with a similar set of options including halt. The third and final threshold is disk_full_action, covered above.

# grep -P -- '^\h*space_left_action\h*=\h*(email|exec|single|halt)\b' /etc/audit/auditd.conf
# grep -P -- '^\h*admin_space_left_action\h*=\h*(single|halt)\b' /etc/audit/auditd.conf

No output for either. space_left_action is set to execute a custom script, and admin_space_left_action to halt.

A script is created at /usr/local/sbin/auditd-space-left-action:

/usr/bin/logger -p authpriv.warning -t auditd-space-left "Audit disk space running low on $(/usr/bin/hostname)"
/usr/sbin/service auditd resume

Testing the two lines individually, the logger call works as expected:

# /usr/bin/logger -p authpriv.warning -t auditd-space-left "Audit disk space running low on $(/usr/bin/hostname)"
# tail -1 /var/log/warnings.log
2026-07-24T22:34:10.823129+00:00 starter auditd-space-left: Audit disk space running low on starter

The service call does not:

# /usr/sbin/service auditd resume
Usage: /etc/init.d/auditd {start|stop|restart|reload|force-reload|rotate|status}

Debian’s auditd init script has no resume verb. auditd(8) documents a SIGUSR2 signal instead, which causes the daemon to resume logging and passing events to plugins after a suspension. This is sent using systemctl kill:

# /usr/bin/systemctl kill --signal=SIGUSR2 auditd

No errors. The script is updated accordingly:

/usr/bin/logger -p authpriv.warning -t auditd-space-left "Audit disk space running low on $(/usr/bin/hostname)"
/usr/bin/systemctl kill --signal=SIGUSR2 auditd

The file is created with permissions restricted to 700:

# vim /usr/local/sbin/auditd-space-left-action
# cat /usr/local/sbin/auditd-space-left-action
/usr/bin/logger -p authpriv.warning -t auditd-space-left "Audit disk space running low on $(/usr/bin/hostname)"
/usr/bin/systemctl kill --signal=SIGUSR2 auditd
# ls -l /usr/local/sbin/auditd-space-left-action
-rw-r----- 1 root root 160 Jul 25 00:54 /usr/local/sbin/auditd-space-left-action
# chmod 700 /usr/local/sbin/auditd-space-left-action
# ls -l /usr/local/sbin/auditd-space-left-action
-rwx------ 1 root root 160 Jul 25 00:54 /usr/local/sbin/auditd-space-left-action

space_left_action is set to exec /usr/local/sbin/auditd-space-left-action, and admin_space_left_action to halt:

# grep -P -- '^\h*space_left_action\h*=\h*(email|exec|single|halt)\b' /etc/audit/auditd.conf
space_left_action = exec /usr/local/sbin/auditd-space-left-action
# grep -P -- '^\h*admin_space_left_action\h*=\h*(single|halt)\b' /etc/audit/auditd.conf
admin_space_left_action = halt

Both recommendations pass.

6.2.3 Configure auditd Rules #

This section covers 6.2.3.1 through 6.2.3.37 of the benchmark.

The benchmark provides a script to check whether existing audit rules use the deprecated -w watch syntax or are missing an -F arch= field.

#!/usr/bin/env bash
f_audit_rules_format_chk()
{
rules_dir="/etc/audit/rules.d"
deprecated_flag="-w"
a_output=()
for file in "$rules_dir"/*rules; do
  [[ -e "$file" ]] || continue
  while IFS= read -r line; do
    # Skip empty or commented lines
    [[ "$line" =~ ^\s*# ]] || [[ -z "$line" ]] && continue
    # Check for deprecated -w
    if [[ "$line" == *"$deprecated_flag"* ]]; then
      a_output+=("Deprecated '-w' found in $file Rule: $(wrap_text "$line") Flag: $deprecated_flag")
    fi
    # Check for missing -F arch
    if [[ "$line" =~ ^-a[[:space:]]+always,exit ]] && [[ "$line" != *"-F arch="* ]] \
    ; then
      a_output+=("Missing '-F arch=' in $file Rule: $(wrap_text "$line") Flag: Missing arch")
    fi
  done < "$file"
done
if [ ${#a_output[@]} -eq 0 ]; then
  echo -e "No issues found in audit rules files"
else
  echo -e "\nAudit rules issue detected:"
  for output in "${a_output[@]}"; do
    echo -e "\n$output"
  done
fi
}
# wrap_text function for formatting
wrap_text() {
echo "$1"
}
f_audit_rules_format_chk
No issues found in audit rules files

Good.

Per man auditctl, on a bi-arch system such as x86_64, a rule with no explicit arch= directive applies to both 32 and 64 bit syscalls, which is not guaranteed to behave correctly since a given syscall is not guaranteed to share the same number across both interfaces. Two rules are therefore needed for every syscall based recommendation in this section: one with arch=b64, one with arch=b32. Confirming the system is 64 bit is enough to justify adding both:

# uname -m
x86_64

All rules for this section are collected in a single custom file, /etc/audit/rules.d/50-cis-hardening.rules. Per man augenrules, files under /etc/audit/rules.d are concatenated in natural sort order and merged into the running configuration with augenrules --load. The general workflow for each recommendation below is: confirm the rule is missing, append it to the custom file, merge and load the rules, confirm no reboot is required, then confirm the rule is now present.

6.2.3.1 Ensure Modification of the /etc/sudoers File Is Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/sudoers'
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/sudoers' /etc/audit/rules.d/*.rules

No output for either. Not compliant.

The following rules are added to /etc/audit/rules.d/50-cis-hardening.rules:

# /etc/sudoers
-a always,exit -F arch=b64 -S all -F path=/etc/sudoers -F perm=wa -k scope
-a always,exit -F arch=b64 -S all -F dir=/etc/sudoers.d -F perm=wa -k scope
-a always,exit -F arch=b32 -S all -F path=/etc/sudoers -F perm=wa -k scope
-a always,exit -F arch=b32 -S all -F dir=/etc/sudoers.d -F perm=wa -k scope

The file is created with permissions restricted to the owner:

# vim /etc/audit/rules.d/50-cis-hardening.rules
# ls -l /etc/audit/rules.d/50-cis-hardening.rules
-rw-r----- 1 root root 318 Jul 25 02:51 /etc/audit/rules.d/50-cis-hardening.rules
# chmod g-r /etc/audit/rules.d/50-cis-hardening.rules
# ls -l /etc/audit/rules.d/50-cis-hardening.rules
-rw------- 1 root root 318 Jul 25 02:51 /etc/audit/rules.d/50-cis-hardening.rules

The rules are merged and loaded:

# augenrules --load
No rules
enabled 1
failure 1
pid 75996
rate_limit 0
backlog_limit 8192
lost 0
backlog 4
backlog_wait_time 60000
backlog_wait_time_actual 0
enabled 1
failure 1
pid 75996
rate_limit 0
backlog_limit 8192
lost 0
backlog 4
backlog_wait_time 60000
backlog_wait_time_actual 0
enabled 1
failure 1
pid 75996
rate_limit 0
backlog_limit 8192
lost 0
backlog 0
backlog_wait_time 60000
backlog_wait_time_actual 0
# if [[ $(auditctl -s | grep "enabled") =~ "2" ]]; then printf "Reboot required to load rules\n"; fi

No output, so no reboot is required. This same three step sequence, loading the rules and confirming no reboot is needed, is repeated for every recommendation in this section without being shown again in full.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/sudoers'
-a always,exit -F arch=b64 -S all -F path=/etc/sudoers -F perm=wa -F key=scope
-a always,exit -F arch=b64 -S all -F dir=/etc/sudoers.d -F perm=wa -F key=scope
-a always,exit -F arch=b32 -S all -F path=/etc/sudoers -F perm=wa -F key=scope
-a always,exit -F arch=b32 -S all -F dir=/etc/sudoers.d -F perm=wa -F key=scope
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/sudoers' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/sudoers -F perm=wa -k scope
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F dir=/etc/sudoers.d -F perm=wa -k scope
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/sudoers -F perm=wa -k scope
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F dir=/etc/sudoers.d -F perm=wa -k scope

Recommendation passes.

6.2.3.2 Ensure Actions as Another User Are Always Logged #

# auditctl -l | grep execve
# grep -Ps -- 'execve' /etc/audit/rules.d/*.rules

No output for either. Not compliant.

# actions as another user
-a always,exit -F arch=b64 -C euid!=uid -F auid!=unset -S execve -k user_emulation
-a always,exit -F arch=b32 -C euid!=uid -F auid!=unset -S execve -k user_emulation

Merged and loaded, no reboot required.

# auditctl -l | grep execve
-a always,exit -F arch=b64 -S execve -C uid!=euid -F auid!=-1 -F key=user_emulation
-a always,exit -F arch=b32 -S execve -C uid!=euid -F auid!=-1 -F key=user_emulation
# grep -Ps -- 'execve' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -C euid!=uid -F auid!=unset -S execve -k user_emulation
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -C euid!=uid -F auid!=unset -S execve -k user_emulation

Recommendation passes.

6.2.3.3 Ensure Events That Modify the sudo Log File Are Collected #

/etc/sudoers.d/99-cis-hardening already sets a custom sudo log file location, confirmed separately from the auditd check:

# grep -rPsi "^\h*Defaults\h+([^#]+,\h*)?logfile\h*=\h*(\"|\')?\H+(\"|\')?(,\h*\H+\h*)*\h*(#.*)?$" /etc/sudoers*
/etc/sudoers.d/99-cis-hardening:Defaults logfile="/var/log/sudo.log"
# auditctl -l | grep -Ps -- 'sudo.log'
# grep -Ps -- 'sudo.log' /etc/audit/rules.d/*.rules

No output for either. Not compliant.

# /var/log/sudo.log
-a always,exit -F arch=b64 -S all -F path=/var/log/sudo.log -F perm=wa -k sudo_log_file
-a always,exit -F arch=b32 -S all -F path=/var/log/sudo.log -F perm=wa -k sudo_log_file

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'sudo.log'
-a always,exit -F arch=b64 -S all -F path=/var/log/sudo.log -F perm=wa -F key=sudo_log_file
-a always,exit -F arch=b32 -S all -F path=/var/log/sudo.log -F perm=wa -F key=sudo_log_file
# grep -Ps -- 'sudo.log' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# /var/log/sudo.log
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/var/log/sudo.log -F perm=wa -k sudo_log_file
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/var/log/sudo.log -F perm=wa -k sudo_log_file

Recommendation passes.

6.2.3.4 Ensure Events That Modify Date and Time Information Are Collected #

At this point, the only other file under /etc/audit/rules.d/ besides the custom one is audit.rules, which only sets buffer and failure mode options and contains none of the paths or syscalls being audited here. From this recommendation onward, the pre-remediation audit checks that grep the rules files under /etc/audit/rules.d/ are skipped, since the rule is already known to be absent; only the live auditctl -l check is shown before remediation, with the rules file check restored afterward to confirm the change took effect.

# auditctl -l | grep -Ps -- '(adjtimex|settimeofday|clock_settime)'
# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/localtime'

No output for either. Not compliant.

# adjtimex, settimeofday, clock_settime
-a always,exit -F arch=b64 -S adjtimex,settimeofday -k time-change
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -k time-change
-a always,exit -F arch=b32 -S adjtimex,settimeofday -k time-change
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -k time-change

# /etc/localtime
-a always,exit -F arch=b64 -S all -F path=/etc/localtime -F perm=wa -k localtime-change
-a always,exit -F arch=b32 -S all -F path=/etc/localtime -F perm=wa -k localtime-change

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '(adjtimex|settimeofday|clock_settime)'
-a always,exit -F arch=b64 -S adjtimex,settimeofday -F key=time-change
-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -F key=time-change
-a always,exit -F arch=b32 -S settimeofday,adjtimex -F key=time-change
-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -F key=time-change
# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/localtime'
-a always,exit -F arch=b64 -S all -F path=/etc/localtime -F perm=wa -F key=localtime-change
-a always,exit -F arch=b32 -S all -F path=/etc/localtime -F perm=wa -F key=localtime-change
# grep -Ps -- '(adjtimex|settimeofday|clock_settime)' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# adjtimex, settimeofday, clock_settime
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S adjtimex,settimeofday -k time-change
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S clock_settime -F a0=0x0 -k time-change
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S adjtimex,settimeofday -k time-change
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S clock_settime -F a0=0x0 -k time-change
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/localtime' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/localtime -F perm=wa -k localtime-change
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/localtime -F perm=wa -k localtime-change

Both recommendations pass.

6.2.3.5 Ensure Events That Modify sethostname and setdomainname Are Collected #

# auditctl -l | grep -Ps -- 'sethostname|setdomainname'

No output. Not compliant.

# sethostname and setdomainname
-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale
-a always,exit -F arch=b32 -S sethostname,setdomainname -k system-locale

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'sethostname|setdomainname'
-a always,exit -F arch=b64 -S sethostname,setdomainname -F key=system-locale
-a always,exit -F arch=b32 -S sethostname,setdomainname -F key=system-locale
# grep -Ps -- 'sethostname|setdomainname' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# sethostname and setdomainname
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S sethostname,setdomainname -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S sethostname,setdomainname -k system-locale

Recommendation passes.

6.2.3.6 Ensure Events That Modify /etc/issue and /etc/issue.net Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/issue'

No output. Not compliant.

# /etc/issue and /etc/issue.net
-a always,exit -F arch=b64 -S all -F path=/etc/issue -F perm=wa -k system-locale
-a always,exit -F arch=b64 -S all -F path=/etc/issue.net -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/issue -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/issue.net -F perm=wa -k system-locale

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/issue'
-a always,exit -F arch=b64 -S all -F path=/etc/issue -F perm=wa -F key=system-locale
-a always,exit -F arch=b64 -S all -F path=/etc/issue.net -F perm=wa -F key=system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/issue -F perm=wa -F key=system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/issue.net -F perm=wa -F key=system-locale
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/issue' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/issue -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/issue.net -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/issue -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/issue.net -F perm=wa -k system-locale

Recommendation passes.

6.2.3.7 Ensure Events That Modify /etc/hosts and /etc/hostname Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/host'

No output. Not compliant.

# /etc/hosts and /etc/hostname
-a always,exit -F arch=b64 -S all -F path=/etc/hosts -F perm=wa -k system-locale
-a always,exit -F arch=b64 -S all -F path=/etc/hostname -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/hosts -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/hostname -F perm=wa -k system-locale

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/host'
-a always,exit -F arch=b64 -S all -F path=/etc/hosts -F perm=wa -F key=system-locale
-a always,exit -F arch=b64 -S all -F path=/etc/hostname -F perm=wa -F key=system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/hosts -F perm=wa -F key=system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/hostname -F perm=wa -F key=system-locale
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/host' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/hosts -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/hostname -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/hosts -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/hostname -F perm=wa -k system-locale

Recommendation passes.

6.2.3.8 Ensure Events That Modify the System’s Network Environment Are Collected #

# auditctl -l | grep -Ps -- '\/etc/net'

No output. Not compliant. The following rules are added, covering /etc/network/interfaces, /etc/network/interfaces.d, and /etc/netplan:

# network environment
-a always,exit -F arch=b64 -S all -F path=/etc/network/interfaces -F perm=wa -k system-locale
-a always,exit -F arch=b64 -S all -F dir=/etc/network/interfaces.d -F perm=wa -k system-locale
-a always,exit -F arch=b64 -S all -F dir=/etc/netplan/ -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/network/interfaces -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F dir=/etc/network/interfaces.d -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F dir=/etc/netplan/ -F perm=wa -k system-locale

Loading the rules fails:

# augenrules --load
No rules
enabled 1
failure 1
pid 75996
rate_limit 0
backlog_limit 8192
lost 0
backlog 0
backlog_wait_time 60000
backlog_wait_time_actual 0
enabled 1
failure 1
pid 75996
rate_limit 0
backlog_limit 8192
lost 0
backlog 4
backlog_wait_time 60000
backlog_wait_time_actual 0
Error sending add rule data request (No such file or directory)
There was an error in line 31 of /etc/audit/audit.rules

Line 31 is the /etc/netplan/ rule. /etc/netplan does not exist on this system:

# ls -la /etc/network/interfaces /etc/network/interfaces.d /etc/netplan/
ls: cannot access '/etc/netplan/': No such file or directory
-rw-r--r-- 1 root root  289 May 12 13:26 /etc/network/interfaces

/etc/network/interfaces.d:
total 12
drwxr-xr-x 2 root root 4096 Jun 27 11:38 .
drwxr-xr-x 7 root root 4096 May 12 13:26 ..
-rw-r--r-- 1 root root  540 Jun 27 11:38 50-cloud-init

The arch=b64/arch=b32 pair targeting /etc/netplan is removed, leaving only the /etc/network/interfaces and /etc/network/interfaces.d rules. The rules are merged and loaded again without error, and no reboot is required.

# auditctl -l | grep -Ps -- '\/etc/net'
-a always,exit -F arch=b64 -S all -F path=/etc/network/interfaces -F perm=wa -F key=system-locale
-a always,exit -F arch=b64 -S all -F dir=/etc/network/interfaces.d -F perm=wa -F key=system-locale
-a always,exit -F arch=b32 -S all -F path=/etc/network/interfaces -F perm=wa -F key=system-locale
-a always,exit -F arch=b32 -S all -F dir=/etc/network/interfaces.d -F perm=wa -F key=system-locale
# grep -Ps -- '\/etc/net' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/network/interfaces -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F dir=/etc/network/interfaces.d -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/network/interfaces -F perm=wa -k system-locale
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F dir=/etc/network/interfaces.d -F perm=wa -k system-locale

Recommendation passes.

6.2.3.9 Ensure Events That Modify /etc/NetworkManager Directory Are Collected #

# auditctl -l | grep -Ps -- '\/etc/NetworkManager'

No output. Not compliant.

# /etc/NetworkManager/
-a always,exit -F arch=b64 -S all -F dir=/etc/NetworkManager/ -F perm=wa -k system-locale
-a always,exit -F arch=b32 -S all -F dir=/etc/NetworkManager/ -F perm=wa -k system-locale

Loading the rules fails again, with the same class of error as 6.2.3.8:

Error sending add rule data request (No such file or directory)
There was an error in line 33 of /etc/audit/audit.rules
# ls /etc/NetworkManager/
ls: cannot access '/etc/NetworkManager/': No such file or directory
# dpkg -l | grep network-manager

/etc/NetworkManager does not exist, and the network-manager package is not installed. The two rules are commented out rather than removed, so they can be enabled later if the package is installed:

# /etc/NetworkManager/ (uncomment if network-manager is installed)
# -a always,exit -F arch=b64 -S all -F dir=/etc/NetworkManager/ -F perm=wa -k system-locale
# -a always,exit -F arch=b32 -S all -F dir=/etc/NetworkManager/ -F perm=wa -k system-locale

Merged and loaded without error, no reboot required. Since the rules remain commented out, this recommendation is not applicable on this system and is left in place for future reference rather than marked as passing.

6.2.3.10 Ensure Use of Privileged Commands Are Collected #

# findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,) | grep -Pv "noexec|nosuid"
/              /dev/sda1  ext4 rw,relatime,errors=remount-ro
/boot/efi      /dev/sda15 vfat rw,relatime,fmask=0077,dmask=0077,codepage=437,iocharset=ascii,shortname=mixed,utf8,errors=remount-ro

These are the two filesystems that will be traversed for setuid and setgid binaries.

#!/usr/bin/env bash
{
  for PARTITION in $(findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,) | grep -Pv "noexec|nosuid" | awk '{print $1}'); do
    for PRIVILEGED in $(find "${PARTITION}" -xdev -perm /6000 -type f); do
      grep -qr "${PRIVILEGED}" /etc/audit/rules.d && printf "OK: '${PRIVILEGED}' found in auditing rules.\n" || printf "Warning: '${PRIVILEGED}' not found in on disk configuration.\n"
    done
  done
}

This finds eighteen privileged binaries, none yet covered by an audit rule: ssh-agent, expiry, dotlockfile, crontab, gpasswd, sudo, newgrp, chage, chfn, passwd, chsh, mount, su, at, umount, dbus-daemon-launch-helper, ssh-keysign, and unix_chkpwd. A second, similar script checks the same binaries against the live loaded ruleset instead of the on-disk files, with the same eighteen reported as missing.

A third script generates the corresponding -a always,exit rules automatically, using the system’s actual UID_MIN rather than a hardcoded value:

#!/usr/bin/env bash
{
  UID_MIN=$(awk '/^\s*UID_MIN/{print $2}' /etc/login.defs)
  AUDIT_RULE_FILE="/etc/audit/rules.d/50-privileged.rules"
  NEW_DATA=()
  for PARTITION in $(findmnt -n -l -k -it $(awk '/nodev/ { print $2 }' /proc/filesystems | paste -sd,) | grep -Pv "noexec|nosuid" | awk '{print $1}'); do
    readarray -t DATA < <(find "${PARTITION}" -xdev -perm /6000 -type f | awk -v UID_MIN=${UID_MIN} '{print "-a always,exit -F arch=b64 -S all -F path=" $1 " -F perm=x -F auid>="UID_MIN" -F auid!=unset -k privileged" }')
    for ENTRY in "${DATA[@]}"; do
      NEW_DATA+=("${ENTRY}")
    done
  done
  readarray &> /dev/null -t OLD_DATA < "${AUDIT_RULE_FILE}"
  COMBINED_DATA=( "${OLD_DATA[@]}" "${NEW_DATA[@]}" )
  printf '%s\n' "${COMBINED_DATA[@]}" | sort -u > "${AUDIT_RULE_FILE}"
}

Running it once generates the arch=b64 rules into a separate file, /etc/audit/rules.d/50-privileged.rules. Rather than editing the script to also emit arch=b32 rules, the b32 variants are authored directly by duplicating the b64 block and swapping the architecture field. Both sets are appended to the custom 50-cis-hardening.rules file:

# privileged programs (setuid and setgid)
# b64
-a always,exit -F arch=b64 -S all -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/dotlockfile -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/expiry -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/lib/dbus-1.0/dbus-daemon-launch-helper -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b64 -S all -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
# b32
-a always,exit -F arch=b32 -S all -F path=/usr/bin/at -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chage -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chfn -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chsh -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/crontab -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/dotlockfile -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/expiry -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/gpasswd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/mount -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/newgrp -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/passwd -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/ssh-agent -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/sudo -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/su -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/bin/umount -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/lib/dbus-1.0/dbus-daemon-launch-helper -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/lib/openssh/ssh-keysign -F perm=x -F auid>=1000 -F auid!=unset -k privileged
-a always,exit -F arch=b32 -S all -F path=/usr/sbin/unix_chkpwd -F perm=x -F auid>=1000 -F auid!=unset -k privileged

Loading the rules fails with a duplicate rule error, since the generated 50-privileged.rules file was left in place alongside the copy already added to the custom file:

Error sending add rule data request (Rule exists)
There was an error in line 69 of /etc/audit/audit.rules
# rm /etc/audit/rules.d/50-privileged.rules

The rules are merged and loaded again without error, and no reboot is required. Both privileged-binary check scripts from above are run again, and every entry now reports OK, against both the on-disk rule files and the live ruleset.

6.2.3.11 Ensure Unsuccessful File Access Attempts Are Collected #

# awk '/^\s*UID_MIN/{print $2}' /etc/login.defs
1000
# auditctl -l | grep -Ps -- '(EACCES|EPERM)'

No output. Not compliant.

# unsuccessful file access attempts
-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access
-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '(EACCES|EPERM)'
-a always,exit -F arch=b64 -S open,truncate,ftruncate,creat,openat -F exit=-EACCES -F auid>=1000 -F auid!=-1 -F key=access
-a always,exit -F arch=b64 -S open,truncate,ftruncate,creat,openat -F exit=-EPERM -F auid>=1000 -F auid!=-1 -F key=access
-a always,exit -F arch=b32 -S open,creat,truncate,ftruncate,openat -F exit=-EACCES -F auid>=1000 -F auid!=-1 -F key=access
-a always,exit -F arch=b32 -S open,creat,truncate,ftruncate,openat -F exit=-EPERM -F auid>=1000 -F auid!=-1 -F key=access
# grep -Ps -- '(EACCES|EPERM)' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EACCES -F auid>=1000 -F auid!=unset -k access
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S creat,open,openat,truncate,ftruncate -F exit=-EPERM -F auid>=1000 -F auid!=unset -k access

Recommendation passes.

6.2.3.12 Ensure Events That Modify /etc/group Information Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/group'

No output. Not compliant.

# /etc/group
-a always,exit -F arch=b64 -S all -F path=/etc/group -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/group -F perm=wa -k identity

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/group'
-a always,exit -F arch=b64 -S all -F path=/etc/group -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/group -F perm=wa -F key=identity
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/group' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/group -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/group -F perm=wa -k identity

Recommendation passes.

6.2.3.13 Ensure Events That Modify /etc/passwd Information Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/passwd'

No output. Not compliant.

# /etc/passwd
-a always,exit -F arch=b64 -S all -F path=/etc/passwd -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/passwd -F perm=wa -k identity

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/passwd'
-a always,exit -F arch=b64 -S all -F path=/etc/passwd -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/passwd -F perm=wa -F key=identity
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/passwd' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/passwd -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/passwd -F perm=wa -k identity

Recommendation passes.

6.2.3.14 Ensure Events That Modify /etc/shadow and /etc/gshadow Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*shadow'

No output. Not compliant.

# /etc/gshadow and /etc/shadow
-a always,exit -F arch=b64 -S all -F path=/etc/gshadow -F perm=wa -k identity
-a always,exit -F arch=b64 -S all -F path=/etc/shadow -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/gshadow -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/shadow -F perm=wa -k identity

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*shadow'
-a always,exit -F arch=b64 -S all -F path=/etc/gshadow -F perm=wa -F key=identity
-a always,exit -F arch=b64 -S all -F path=/etc/shadow -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/gshadow -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/shadow -F perm=wa -F key=identity
# grep -Ps -- 'shadow' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# /etc/gshadow and /etc/shadow
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/gshadow -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/shadow -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/gshadow -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/shadow -F perm=wa -k identity

Recommendation passes.

6.2.3.15 Ensure Events That Modify /etc/security/opasswd Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/security\/opasswd'

No output. Not compliant.

# /etc/security/opasswd
-a always,exit -F arch=b64 -S all -F path=/etc/security/opasswd -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/security/opasswd -F perm=wa -k identity

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/security\/opasswd'
-a always,exit -F arch=b64 -S all -F path=/etc/security/opasswd -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/security/opasswd -F perm=wa -F key=identity
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/security\/opasswd' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/security/opasswd -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/security/opasswd -F perm=wa -k identity

Recommendation passes.

6.2.3.16 Ensure Events That Modify /etc/nsswitch.conf File Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/nsswitch.conf'

No output. Not compliant.

# /etc/nsswitch.conf
-a always,exit -F arch=b64 -S all -F path=/etc/nsswitch.conf -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/nsswitch.conf -F perm=wa -k identity

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/nsswitch.conf'
-a always,exit -F arch=b64 -S all -F path=/etc/nsswitch.conf -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/nsswitch.conf -F perm=wa -F key=identity
# grep -Ps -- 'nsswitch.conf' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# /etc/nsswitch.conf
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/nsswitch.conf -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/nsswitch.conf -F perm=wa -k identity

Recommendation passes.

6.2.3.17 Ensure Events That Modify /etc/pam.conf and /etc/pam.d/ Information Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/pam'

No output. Not compliant.

# /etc/pam.conf and /etc/pam.d 
-a always,exit -F arch=b64 -S all -F path=/etc/pam.conf -F perm=wa -k identity
-a always,exit -F arch=b64 -S all -F dir=/etc/pam.d -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F path=/etc/pam.conf -F perm=wa -k identity
-a always,exit -F arch=b32 -S all -F dir=/etc/pam.d -F perm=wa -k identity

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/pam'
-a always,exit -F arch=b64 -S all -F path=/etc/pam.conf -F perm=wa -F key=identity
-a always,exit -F arch=b64 -S all -F dir=/etc/pam.d -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F path=/etc/pam.conf -F perm=wa -F key=identity
-a always,exit -F arch=b32 -S all -F dir=/etc/pam.d -F perm=wa -F key=identity
# grep -Ps -- '^\h*[^#\n\r]+\h*\/etc\/pam' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/pam.conf -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F dir=/etc/pam.d -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/pam.conf -F perm=wa -k identity
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F dir=/etc/pam.d -F perm=wa -k identity

Recommendation passes.

6.2.3.18 Ensure Discretionary Access Control Permission Modification Events chmod,fchmod,fchmodat,fchmodat2 Are Collected #

# auditctl -l | grep -Ps -- 'chmod'

No output. Not compliant.

# modification events chmod,fchmod,fchmodat,fchmodat2
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,fchmodat2 -F auid>=1000 -F auid!=unset -k perm_mod
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,fchmodat2 -F auid>=1000 -F auid!=unset -k perm_mod

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'chmod'
-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,fchmodat2 -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,fchmodat2 -F auid>=1000 -F auid!=-1 -F key=perm_mod
# grep -Ps -- 'chmod' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# modification events chmod,fchmod,fchmodat,fchmodat2
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S chmod,fchmod,fchmodat,fchmodat2 -F auid>=1000 -F auid!=unset -k perm_mod
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S chmod,fchmod,fchmodat,fchmodat2 -F auid>=1000 -F auid!=unset -k perm_mod

Recommendation passes.

6.2.3.19 Ensure Discretionary Access Control Permission Modification Events chown,fchown,lchown,fchownat Are Collected #

# auditctl -l | grep -Ps -- 'chown'

No output. Not compliant.

# modification events chown,fchown,lchown,fchownat
-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod
-a always,exit -F arch=b32 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'chown'
-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S lchown,fchown,chown,fchownat -F auid>=1000 -F auid!=-1 -F key=perm_mod
# grep -Ps -- 'chown' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# modification events chown,fchown,lchown,fchownat
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S chown,fchown,lchown,fchownat -F auid>=1000 -F auid!=unset -k perm_mod

Recommendation passes.

6.2.3.20 Ensure Discretionary Access Control Permission Modification Events setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr Collected #

# auditctl -l | grep -Ps -- 'xattr'

No output. Not compliant.

# modification events setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'xattr'
-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=-1 -F key=perm_mod
# grep -Ps -- 'xattr' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# modification events setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr -F auid>=1000 -F auid!=unset -k perm_mod

Recommendation passes.

6.2.3.21 Ensure Successful File System Mounts Are Collected #

# auditctl -l | grep -Ps -- '\-S mount'

No output. Not compliant.

# file system mounts
-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=unset -k mounts
-a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=unset -k mounts

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '\-S mount'
-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=-1 -F key=mounts
-a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=-1 -F key=mounts
# grep -Ps -- '\-S mount' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S mount -F auid>=1000 -F auid!=unset -k mounts
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S mount -F auid>=1000 -F auid!=unset -k mounts

Recommendation passes.

6.2.3.22 Ensure Session Initiation Information Is Collected #

/var/run/utmp does not exist on this system:

# ls -la /var/run/utmp /var/log/wtmp /var/log/btmp
ls: cannot access '/var/run/utmp': No such file or directory
-rw-rw---- 1 root utmp     0 Jul  1 00:28 /var/log/btmp
-rw-rw-r-- 1 root utmp 60672 Jul 26 02:03 /var/log/wtmp

Debian presumably removed utmp for not being Y2038 safe, so the remediation for this recommendation covers only /var/log/wtmp and /var/log/btmp.

# auditctl -l | grep -Ps -- '(\/var\/run\/utmp|\/var\/log\/wtmp|\/var\/log\/btmp)'

No output. Not compliant.

# session initiation information (/var/run/utmp is not there)
-a always,exit -F arch=b64 -S all -F path=/var/log/wtmp -F perm=wa -k session
-a always,exit -F arch=b64 -S all -F path=/var/log/btmp -F perm=wa -k session
-a always,exit -F arch=b32 -S all -F path=/var/log/wtmp -F perm=wa -k session
-a always,exit -F arch=b32 -S all -F path=/var/log/btmp -F perm=wa -k session

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '(\/var\/run\/utmp|\/var\/log\/wtmp|\/var\/log\/btmp)'
-a always,exit -F arch=b64 -S all -F path=/var/log/wtmp -F perm=wa -F key=session
-a always,exit -F arch=b64 -S all -F path=/var/log/btmp -F perm=wa -F key=session
-a always,exit -F arch=b32 -S all -F path=/var/log/wtmp -F perm=wa -F key=session
-a always,exit -F arch=b32 -S all -F path=/var/log/btmp -F perm=wa -F key=session
# grep -Ps -- '(\/var\/run\/utmp|\/var\/log\/wtmp|\/var\/log\/btmp)' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# session initiation information (/var/run/utmp is not there)
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/var/log/wtmp -F perm=wa -k session
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/var/log/btmp -F perm=wa -k session
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/var/log/wtmp -F perm=wa -k session
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/var/log/btmp -F perm=wa -k session

Recommendation passes.

6.2.3.23 Ensure Login and Logout Events Are Collected #

# ls -la /var/log/lastlog /var/run/faillock
-rw-rw-r-- 1 root utmp 292292 Jul 26 02:03 /var/log/lastlog

/var/run/faillock:
total 0
drwxr-xr-x  2 root   root  80 Jul 19 22:23 .
drwxr-xr-x 27 root   root 880 Jul 26 02:03 ..
-rw-rw----  1 root   root   0 Jul 26 02:17 root
-rw-rw----  1 sharaf root   0 Jul 26 02:04 sharaf
# auditctl -l | grep -Ps -- '(lastlog|faillock)'

No output. Not compliant.

# login and logout events
-a always,exit -F arch=b64 -S all -F path=/var/log/lastlog -F perm=wa -k logins
-a always,exit -F arch=b64 -S all -F path=/var/run/faillock -F perm=wa -k logins
-a always,exit -F arch=b32 -S all -F path=/var/log/lastlog -F perm=wa -k logins
-a always,exit -F arch=b32 -S all -F path=/var/run/faillock -F perm=wa -k logins

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '(lastlog|faillock)'
-a always,exit -F arch=b64 -S all -F path=/var/log/lastlog -F perm=wa -F key=logins
-a always,exit -F arch=b64 -S all -F path=/var/run/faillock -F perm=wa -F key=logins
-a always,exit -F arch=b32 -S all -F path=/var/log/lastlog -F perm=wa -F key=logins
-a always,exit -F arch=b32 -S all -F path=/var/run/faillock -F perm=wa -F key=logins
# grep -Ps -- '(lastlog|faillock)' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/var/log/lastlog -F perm=wa -k logins
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/var/run/faillock -F perm=wa -k logins
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/var/log/lastlog -F perm=wa -k logins
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/var/run/faillock -F perm=wa -k logins

Recommendation passes.

# auditctl -l | grep -Ps -- 'unlink'

No output. Not compliant.

# unlink file deletion events
-a always,exit -F arch=b64 -S unlink,unlinkat -F auid>=1000 -F auid!=unset -k delete
-a always,exit -F arch=b32 -S unlink,unlinkat -F auid>=1000 -F auid!=unset -k delete

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'unlink'
-a always,exit -F arch=b64 -S unlink,unlinkat -F auid>=1000 -F auid!=-1 -F key=delete
-a always,exit -F arch=b32 -S unlink,unlinkat -F auid>=1000 -F auid!=-1 -F key=delete
# grep -Ps -- 'unlink' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# unlink file deletion events
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S unlink,unlinkat -F auid>=1000 -F auid!=unset -k delete
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S unlink,unlinkat -F auid>=1000 -F auid!=unset -k delete

Recommendation passes.

6.2.3.25 Ensure rename File Deletion Events by Users Are Collected #

# auditctl -l | grep -Ps -- 'rename'

No output. Not compliant.

# rename file deletion events
-a always,exit -F arch=b64 -S rename,renameat,renameat2 -F auid>=1000 -F auid!=unset -k delete
-a always,exit -F arch=b32 -S rename,renameat,renameat2 -F auid>=1000 -F auid!=unset -k delete

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'rename'
-a always,exit -F arch=b64 -S rename,renameat,renameat2 -F auid>=1000 -F auid!=-1 -F key=delete
-a always,exit -F arch=b32 -S rename,renameat,renameat2 -F auid>=1000 -F auid!=-1 -F key=delete
# grep -Ps -- 'rename' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# rename file deletion events
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S rename,renameat,renameat2 -F auid>=1000 -F auid!=unset -k delete
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S rename,renameat,renameat2 -F auid>=1000 -F auid!=unset -k delete

Recommendation passes.

6.2.3.26 Ensure Events That Modify the System’s Mandatory Access Controls Are Collected #

# auditctl -l | grep -Ps -- 'apparmor'

No output. Not compliant.

# Mandatory Access Controls
-a always,exit -F arch=b64 -S all -F path=/etc/apparmor -F perm=wa -k MAC-policy
-a always,exit -F arch=b64 -S all -F dir=/etc/apparmor.d -F perm=wa -k MAC-policy
-a always,exit -F arch=b32 -S all -F path=/etc/apparmor -F perm=wa -k MAC-policy
-a always,exit -F arch=b32 -S all -F dir=/etc/apparmor.d -F perm=wa -k MAC-policy

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'apparmor'
-a always,exit -F arch=b64 -S all -F path=/etc/apparmor -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b64 -S all -F dir=/etc/apparmor.d -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b32 -S all -F path=/etc/apparmor -F perm=wa -F key=MAC-policy
-a always,exit -F arch=b32 -S all -F dir=/etc/apparmor.d -F perm=wa -F key=MAC-policy
# grep -Ps -- 'apparmor' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/etc/apparmor -F perm=wa -k MAC-policy
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F dir=/etc/apparmor.d -F perm=wa -k MAC-policy
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/etc/apparmor -F perm=wa -k MAC-policy
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F dir=/etc/apparmor.d -F perm=wa -k MAC-policy

Recommendation passes.

6.2.3.27 Ensure Successful and Unsuccessful Attempts to Use the chcon Command Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/chcon'

No output. Not compliant.

# attempts to use the chcon command
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/chcon'
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
# grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/chcon' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/usr/bin/chcon -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng

Recommendation passes.

6.2.3.28 Ensure Successful and Unsuccessful Attempts to Use the setfacl Command Are Collected #

# auditctl -l | grep -Ps '^\h*[^#\n\r]+\h*\/usr\/bin\/setfacl'

No output. Not compliant.

# attempts to use the setfacl command
-a always,exit -F arch=b64 -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng
-a always,exit -F arch=b32 -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps '^\h*[^#\n\r]+\h*\/usr\/bin\/setfacl'
-a always,exit -F arch=b64 -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -F arch=b32 -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
# grep -Ps '^\h*[^#\n\r]+\h*\/usr\/bin\/setfacl' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/usr/bin/setfacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng

Recommendation passes.

6.2.3.29 Ensure Successful and Unsuccessful Attempts to Use the chacl Command Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/chacl'

No output. Not compliant.

# attempts to use the chacl command
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/chacl'
-a always,exit -F arch=b64 -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
-a always,exit -F arch=b32 -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=-1 -F key=perm_chng
# grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/chacl' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/usr/bin/chacl -F perm=x -F auid>=1000 -F auid!=unset -k perm_chng

Recommendation passes.

6.2.3.30 Ensure Successful and Unsuccessful Attempts to Use the usermod Command Are Collected #

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/sbin\/usermod'

No output. Not compliant.

# attempts to use the usermod command
-a always,exit -F arch=b64 -S all -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -k usermod
-a always,exit -F arch=b32 -S all -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -k usermod

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/sbin\/usermod'
-a always,exit -F arch=b64 -S all -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=-1 -F key=usermod
-a always,exit -F arch=b32 -S all -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=-1 -F key=usermod
# grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/sbin\/usermod' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -k usermod
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/usr/sbin/usermod -F perm=x -F auid>=1000 -F auid!=unset -k usermod

Recommendation passes.

6.2.3.31 Ensure Kernel Module Loading Unloading and Modification Is Collected #

#!/usr/bin/env bash
{
  a_files=("/usr/sbin/lsmod" "/usr/sbin/rmmod" "/usr/sbin/insmod" "/usr/sbin/modinfo" "/usr/sbin/modprobe" "/usr/sbin/depmod")
  for l_file in "${a_files[@]}"; do
    if [ "$(readlink -f "$l_file")" = "$(readlink -f /bin/kmod)" ]; then
      printf "OK: \"$l_file\"\n"
    else
      printf "Issue with symlink for file: \"$l_file\"\n"
    fi
  done
}
OK: "/usr/sbin/lsmod"
OK: "/usr/sbin/rmmod"
OK: "/usr/sbin/insmod"
OK: "/usr/sbin/modinfo"
OK: "/usr/sbin/modprobe"
OK: "/usr/sbin/depmod"

Good.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/kmod'

No output. Not compliant.

# kernel module loading unloading and modification
-a always,exit -F arch=b64 -S all -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -k kernel_modules
-a always,exit -F arch=b32 -S all -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -k kernel_modules

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/kmod'
-a always,exit -F arch=b64 -S all -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=-1 -F key=kernel_modules
-a always,exit -F arch=b32 -S all -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=-1 -F key=kernel_modules
# grep -Ps -- '^\h*[^#\n\r]+\h*\/usr\/bin\/kmod' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S all -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -k kernel_modules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S all -F path=/usr/bin/kmod -F perm=x -F auid>=1000 -F auid!=unset -k kernel_modules

Recommendation passes.

6.2.3.32 Ensure Kernel “init_module” and “finit_module” Loading Unloading and Modification Is Collected #

# auditctl -l | grep -Ps -- 'init_module'

No output. Not compliant.

# "init_module" and "finit_module" loading unloading and modification
-a always,exit -F arch=b64 -S init_module,finit_module -F auid>=1000 -F auid!=unset -k kernel_modules
-a always,exit -F arch=b32 -S init_module,finit_module -F auid>=1000 -F auid!=unset -k kernel_modules

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'init_module'
-a always,exit -F arch=b64 -S init_module,finit_module -F auid>=1000 -F auid!=-1 -F key=kernel_modules
-a always,exit -F arch=b32 -S init_module,finit_module -F auid>=1000 -F auid!=-1 -F key=kernel_modules
# grep -Ps -- 'init_module' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# "init_module" and "finit_module" loading unloading and modification
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S init_module,finit_module -F auid>=1000 -F auid!=unset -k kernel_modules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S init_module,finit_module -F auid>=1000 -F auid!=unset -k kernel_modules

Recommendation passes.

6.2.3.33 Ensure Kernel “delete_module” Loading Unloading and Modification Is Collected #

The benchmark notes that grouping syscalls onto a single -S line, rather than writing a separate rule per syscall, is preferable for performance. delete_module is therefore added to the existing init_module,finit_module rule rather than given its own line:

# auditctl -l | grep -Ps -- 'delete_module'

No output. Not compliant.

# "init_module", "finit_module", "delete_module" loading unloading and modification
-a always,exit -F arch=b64 -S init_module,finit_module,delete_module -F auid>=1000 -F auid!=unset -k kernel_modules
-a always,exit -F arch=b32 -S init_module,finit_module,delete_module -F auid>=1000 -F auid!=unset -k kernel_modules

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'delete_module'
-a always,exit -F arch=b64 -S init_module,delete_module,finit_module -F auid>=1000 -F auid!=-1 -F key=kernel_modules
-a always,exit -F arch=b32 -S init_module,delete_module,finit_module -F auid>=1000 -F auid!=-1 -F key=kernel_modules
# grep -Ps -- 'delete_module' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:# "init_module", "finit_module", "delete_module" loading unloading and modification
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S init_module,finit_module,delete_module -F auid>=1000 -F auid!=unset -k kernel_modules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S init_module,finit_module,delete_module -F auid>=1000 -F auid!=unset -k kernel_modules

Recommendation passes.

6.2.3.34 Ensure Kernel query_module Loading Unloading and Modification Is Collected #

create_module and query_module are both deprecated according to their man pages, to the point that this system has no man page for either. Neither syscall number has actually been removed, though: auditctl accepts test rules against both without error.

# auditctl -a always,exit -F arch=b64 -S create_module -k test 2>&1
# auditctl -a always,exit -F arch=b64 -S query_module -k test 2>&1

The benchmark itself is inconsistent here: create_module is mentioned alongside query_module in the surrounding text, but only query_module corresponds to an actual numbered recommendation with its own audit and remediation. Only query_module is therefore added, appended to the same merged kernel_modules rule used above:

# "init_module", "finit_module", "delete_module" loading unloading and modification
-a always,exit -F arch=b64 -S init_module,finit_module,delete_module,query_module -F auid>=1000 -F auid!=unset -k kernel_modules
-a always,exit -F arch=b32 -S init_module,finit_module,delete_module,query_module -F auid>=1000 -F auid!=unset -k kernel_modules

Merged and loaded, no reboot required.

# auditctl -l | grep -Ps -- 'query_module'
-a always,exit -F arch=b64 -S init_module,delete_module,query_module,finit_module -F auid>=1000 -F auid!=-1 -F key=kernel_modules
-a always,exit -F arch=b32 -S init_module,delete_module,query_module,finit_module -F auid>=1000 -F auid!=-1 -F key=kernel_modules
# grep -Ps -- 'query_module' /etc/audit/rules.d/*.rules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b64 -S init_module,finit_module,delete_module,query_module -F auid>=1000 -F auid!=unset -k kernel_modules
/etc/audit/rules.d/50-cis-hardening.rules:-a always,exit -F arch=b32 -S init_module,finit_module,delete_module,query_module -F auid>=1000 -F auid!=unset -k kernel_modules

Recommendation passes.

6.2.3.35 Ensure the Audit Configuration Is Loaded Regardless of Errors #

# grep -Ph -- '^\h*-c\b' /etc/audit/rules.d/*.rules | tail -1

No output. Not compliant. Since this directive belongs at the start of the merged configuration rather than in the custom rules file, it is placed in a new, separately numbered file, 01-initialize.rules:

# printf '%s\n' "" "-c" >> /etc/audit/rules.d/01-initialize.rules
# cat /etc/audit/rules.d/01-initialize.rules

-c

Merged and loaded, no reboot required.

# grep -Ph -- '^\h*-c\b' /etc/audit/rules.d/*.rules | tail -1
-c

Recommendation passes.

6.2.3.36 Ensure the Audit Configuration Is Immutable #

# grep -Ph -- '^\h*-e\h+2\b' /etc/audit/rules.d/*.rules | tail -1

No output. Not compliant. Following the same pattern as above, this directive needs to load last, so it is placed in a new file, 99-finalize.rules:

# printf '\n%s' "-e 2" >> /etc/audit/rules.d/99-finalize.rules
# augenrules --load
...
Enable must be 0, 1, or 2 was 2##
There was an error in line 127 of /etc/audit/audit.rules

Line 127 of the generated configuration reads -e 2## First rule - delete all. The printf command used to create the file left no trailing newline, so the next rule file’s content ran directly onto the same line as -e 2. Appending a trailing newline resolves it:

# cat /etc/audit/rules.d/99-finalize.rules

-e 2

# augenrules --load
...
enabled 2
failure 1
pid 75996
rate_limit 0
backlog_limit 8192
lost 0
backlog 698
backlog_wait_time 60000
backlog_wait_time_actual 0

Unlike every other recommendation in this section, this one requires a reboot to take effect:

# if [[ $(auditctl -s | grep "enabled") =~ "2" ]]; then printf "Reboot required to load rules\n"; fi
Reboot required to load rules

The system is rebooted, after which the audit is repeated:

# grep -Ph -- '^\h*-e\h+2\b' /etc/audit/rules.d/*.rules | tail -1
-e 2

Recommendation passes.

6.2.3.37 Ensure the Running and On Disk Configuration Is the Same #

# augenrules --check
/usr/sbin/augenrules: No change
/usr/sbin/augenrules: Audit system is in immutable mode - exiting with no changes

Recommendation passes.

6.2.4 Configure auditd File Access #

This section covers 6.2.4.1 through 6.2.4.10 of the benchmark.

6.2.4.1 Ensure Audit Log Files Mode Is Configured #

#!/usr/bin/env bash
{
  l_perm_mask="0137"
  if [ -e "/etc/audit/auditd.conf" ]; then
    l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")"
    if [ -d "$l_audit_log_directory" ]; then
      l_maxperm="$(printf '%o' $(( 0777 & ~$l_perm_mask )) )"
      a_files=()
      while IFS= read -r -d $'\0' l_file; do
        [ -e "$l_file" ] && a_files+=("$l_file")
      done < <(find "$l_audit_log_directory" -maxdepth 1 -type f -perm /"$l_perm_mask" -print0)
      if (( "${#a_files[@]}" > 0 )); then
        for l_file in "${a_files[@]}"; do
          l_file_mode="$(stat -Lc '%#a' "$l_file")"
          echo -e "\n- Audit Result:\n ** FAIL **\n - File: \"$l_file\" is mode: \"$l_file_mode\"\n (should be mode: \"$l_maxperm\" or more restrictive)\n"
        done
      else
        echo -e "\n- Audit Result:\n ** PASS **\n - All files in \"$l_audit_log_directory\" are mode: \"$l_maxperm\" or more restrictive"
      fi
    else
        echo -e "\n- Audit Result:\n ** FAIL **\n - Log file directory not set in \"/etc/audit/auditd.conf\" please set log file directory"
    fi
  else
    echo -e "\n- Audit Result:\n ** FAIL **\n - File: \"/etc/audit/auditd.conf\" not found.\n - ** Verify auditd is installed **"
  fi
}
- Audit Result:
 ** PASS **
 - All files in "/var/log/audit" are mode: "640" or more restrictive

Recommendation passes.

6.2.4.2 Ensure Audit Log Files Owner Is Configured #

#!/usr/bin/env bash
{
  l_output="" l_output2=""
  if [ -e "/etc/audit/auditd.conf" ]; then
    l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")"
    if [ -d "$l_audit_log_directory" ]; then
      while IFS= read -r -d $'\0' l_file; do
        l_output2="$l_output2\n - File: \"$l_file\" is owned by user: \"$(stat -Lc '%U' "$l_file")\"\n (should be owned by user: \"root\")\n"
      done < <(find "$l_audit_log_directory" -maxdepth 1 -type f ! -user root -print0)
    else
      l_output2="$l_output2\n - Log file directory not set in \"/etc/audit/auditd.conf\" please set log file directory"
    fi
  else
    l_output2="$l_output2\n - File: \"/etc/audit/auditd.conf\" not found.\n - ** Verify auditd is installed **"
  fi
  if [ -z "$l_output2" ]; then
    l_output="$l_output\n - All files in \"$l_audit_log_directory\" are owned by user: \"root\"\n"
    echo -e "\n- Audit Result:\n ** PASS **\n - * Correctly configured * :$l_output"
  else
    echo -e "\n- Audit Result:\n ** FAIL **\n - * Reasons for audit failure * :$l_output2\n"
  fi
}
- Audit Result:
 ** PASS **
 - * Correctly configured * :
 - All files in "/var/log/audit" are owned by user: "root"

Recommendation passes.

6.2.4.3 Ensure Audit Log Files Group Owner Is Configured #

# grep -Piws -- '^\h*log_group\h*=\h*\H+\b' /etc/audit/auditd.conf | grep -Pvi -- '(adm)'

No output.

#!/usr/bin/env bash
{
  if [ -e /etc/audit/auditd.conf ]; then
    l_fpath="$(dirname "$(awk -F "=" '/^\s*log_file/ {print $2}' /etc/audit/auditd.conf | xargs)")"
    find -L "$l_fpath" -not -path "$l_fpath"/lost+found -type f \( ! -group root -a ! -group adm \) -exec ls -l {} +
  fi
}

No output. Every audit log file belongs to either root or adm.

Recommendation passes.

6.2.4.4 Ensure the Audit Log File Directory Mode Is Configured #

#!/usr/bin/env bash
{
l_perm_mask="0027"
if [ -e "/etc/audit/auditd.conf" ]; then
  l_audit_log_directory="$(dirname "$(awk -F= '/^\s*log_file\s*/{print $2}' /etc/audit/auditd.conf | xargs)")"
  if [ -d "$l_audit_log_directory" ]; then
    l_maxperm="$(printf '%o' $(( 0777 & ~$l_perm_mask )) )"
    l_directory_mode="$(stat -Lc '%#a' "$l_audit_log_directory")"
    if [ $(( $l_directory_mode & $l_perm_mask )) -gt 0 ]; then
      echo -e "\n- Audit Result:\n ** FAIL **\n - Directory: \"$l_audit_log_directory\" is mode: \"$l_directory_mode\"\n (should be mode: \"$l_maxperm\" or more restrictive)\n"
    else
      echo -e "\n- Audit Result:\n ** PASS **\n - Directory: \"$l_audit_log_directory\" is mode: \"$l_directory_mode\"\n (should be mode: \"$l_maxperm\" or more restrictive)\n"
    fi
  else
    echo -e "\n- Audit Result:\n ** FAIL **\n - Log file directory not set in \"/etc/audit/auditd.conf\" please set log file directory"
  fi
else
  echo -e "\n- Audit Result:\n ** FAIL **\n - File: \"/etc/audit/auditd.conf\" not found\n - ** Verify auditd is installed **"
fi
}
- Audit Result:
 ** PASS **
 - Directory: "/var/log/audit" is mode: "0750"
 (should be mode: "750" or more restrictive)

Recommendation passes.

6.2.4.5 Ensure Audit Configuration Files Mode Is Configured #

#!/usr/bin/env bash
{
  l_output="" l_output2="" l_perm_mask="0137"
  l_maxperm="$( printf '%o' $(( 0777 & ~$l_perm_mask )) )"
  while IFS= read -r -d $'\0' l_fname; do
    l_mode=$(stat -Lc '%#a' "$l_fname")
    if [ $(( "$l_mode" & "$l_perm_mask" )) -gt 0 ]; then
      l_output2="$l_output2\n - file: \"$l_fname\" is mode: \"$l_mode\" (should be mode: \"$l_maxperm\" or more restrictive)"
    fi
  done < <(find /etc/audit/ -type f \( -name "*.conf" -o -name '*.rules' \) -print0)
  if [ -z "$l_output2" ]; then
    echo -e "\n- Audit Result:\n ** PASS **\n - All audit configuration files are mode: \"$l_maxperm\" or more restrictive"
  else
    echo -e "\n- Audit Result:\n ** FAIL **\n$l_output2"
  fi
}
- Audit Result:
 ** PASS **
 - All audit configuration files are mode: "640" or more restrictive

Recommendation passes.

6.2.4.6 Ensure Audit Configuration Files Owner Is Configured #

# find /etc/audit/ -type f \( -name '*.conf' -o -name '*.rules' \) ! -user root

No output. Recommendation passes.

6.2.4.7 Ensure Audit Configuration Files Group Owner Is Configured #

# find /etc/audit/ -type f \( -name '*.conf' -o -name '*.rules' \) ! -group root

No output. Recommendation passes.

6.2.4.8 Ensure Audit Tools Mode Is Configured #

#!/usr/bin/env bash
{
  l_output="" l_output2="" l_perm_mask="0022"
  l_maxperm="$( printf '%o' $(( 0777 & ~$l_perm_mask )) )"
  a_audit_tools=("/sbin/auditctl" "/sbin/aureport" "/sbin/ausearch" "/sbin/auditd" "/sbin/augenrules")
    for l_audit_tool in "${a_audit_tools[@]}"; do
      l_mode="$(stat -Lc '%#a' "$l_audit_tool")"
      if [ $(( "$l_mode" & "$l_perm_mask" )) -gt 0 ]; then
        l_output2="$l_output2\n - Audit tool \"$l_audit_tool\" is mode: \"$l_mode\" and should be mode: \"$l_maxperm\" or more restrictive"
      else
        l_output="$l_output\n - Audit tool \"$l_audit_tool\" is correctly configured to mode: \"$l_mode\""
      fi
    done
  if [ -z "$l_output2" ]; then
    echo -e "\n- Audit Result:\n ** PASS **\n - * Correctly configured * :$l_output"
  else
    echo -e "\n- Audit Result:\n ** FAIL **\n - * Reasons for audit failure * :$l_output2\n"
    [ -n "$l_output" ] && echo -e "\n - * Correctly configured * :\n$l_output\n"
  fi
  unset a_audit_tools
}
- Audit Result:
 ** PASS **
 - * Correctly configured * :
 - Audit tool "/sbin/auditctl" is correctly configured to mode: "0755"
 - Audit tool "/sbin/aureport" is correctly configured to mode: "0755"
 - Audit tool "/sbin/ausearch" is correctly configured to mode: "0755"
 - Audit tool "/sbin/auditd" is correctly configured to mode: "0755"
 - Audit tool "/sbin/augenrules" is correctly configured to mode: "0755"

Recommendation passes.

6.2.4.9 Ensure Audit Tools Owner Is Configured #

# stat -Lc "%n %U" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/auditd /sbin/augenrules | awk '$2 != "root" {print}'

No output. Recommendation passes.

6.2.4.10 Ensure Audit Tools Group Owner Is Configured #

# stat -Lc "%n %G" /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/auditd /sbin/augenrules | awk '$2 != "root" {print}'

No output. Recommendation passes.

6.3 Configure Integrity Checking #

6.3.3 Ensure Cryptographic Mechanisms Are Used to Protect the Integrity of Audit Tools #

#!/usr/bin/env bash
{
  a_output=() a_output2=() l_tool_dir="$(readlink -f /sbin)"
  a_items=("p" "i" "n" "u" "g" "s" "b" "acl" "xattrs" "sha512")
  l_aide_cmd="$(whereis aide | awk '{print $2}')"
  a_audit_files=("auditctl" "auditd" "ausearch" "aureport" "augenrules")
  if [ -f "$l_aide_cmd" ] && command -v "$l_aide_cmd" &>/dev/null; then
    a_aide_conf_files=("$(find -L /etc -type f -name 'aide.conf')")
    f_file_par_chk()
    {
      a_out2=()
      for l_item in "${a_items[@]}"; do
        ! grep -Psiq -- '(\h+|\+)'"$l_item"'(\h+|\+)' <<< "$l_out" && \
        a_out2+=(" - Missing the \"$l_item\" option")
      done
      if [ "${#a_out2[@]}" -gt "0" ]; then
        a_output2+=(" - Audit tool file: \"$l_file\"" "${a_out2[@]}")
      else
        a_output+=(" - Audit tool file: \"$l_file\" includes:" " \"${a_items[*]}\"")
      fi
    }
    for l_file in "${a_audit_files[@]}"; do
      if [ -f "$l_tool_dir/$l_file" ]; then
        l_out="$("$l_aide_cmd" --config "${a_aide_conf_files[@]}" -p f:"$l_tool_dir/$l_file")"
        f_file_par_chk
      else
        a_output+=(" - Audit tool file \"$l_file\" doesn't exist")
      fi
    done
  else
    a_output2+=(" - The command \"aide\" was not found" " Please install AIDE")
  fi
  if [ "${#a_output2[@]}" -le 0 ]; then
    printf '%s\n' "" "- Audit Result:" " ** PASS **" "${a_output[@]}" ""
  else
    printf '%s\n' "" "- Audit Result:" " ** FAIL **" " - Reason(s) for audit failure:" "${a_output2[@]}"
    [ "${#a_output[@]}" -gt 0 ] && printf '%s\n' "" "- Correctly set:" "${a_output[@]}" ""
  fi
}

A handful of warnings are printed first, about a selective rule in /etc/aide/aide.conf.d/98_aide_vfat not matching since the root and /usr filesystems are not vfat, which do not affect the result:

- Audit Result:
 ** PASS **
 - Audit tool file: "auditctl" includes:
 "p i n u g s b acl xattrs sha512"
 - Audit tool file: "auditd" includes:
 "p i n u g s b acl xattrs sha512"
 - Audit tool file: "ausearch" includes:
 "p i n u g s b acl xattrs sha512"
 - Audit tool file: "aureport" includes:
 "p i n u g s b acl xattrs sha512"
 - Audit tool file: "augenrules" includes:
 "p i n u g s b acl xattrs sha512"

Each audit tool is already covered by AIDE’s file integrity monitoring, with all expected check parameters enabled. Recommendation passes.

Automatic Security Updates #

Last, as stated in 1.2, the adopted package management policy mandates a transition to unattended-upgrades on a security-only scope once hardening is complete. That transition is now made.

The origins pattern in 50unattended-upgrades is restricted to:

        `origin=Debian,codename=trixie,label=Debian-Security`
        `origin=Debian,codename=trixie-security,label=Debian-Security`

excluding the general stable archive. The periodic switches in 20auto-upgrades are confirmed active, and both apt-daily.timer and apt-daily-upgrade.timer are enabled and running on schedule. A dry run against the current package set confirms the origin restriction is applied correctly, with no packages outside the security archive eligible for automatic installation.

The Lynis hardening index stayed at 78 immediately after the auditd work above, the same score as before this pass began. It moved to 77 once the rsyslog TLS forwarding setup from earlier in this entry was also in place, then to 79 after a reboot:

================================================================================

  Lynis security scan details:

  Scan mode:
  Normal [▆]  Forensics [ ]  Integration [ ]  Pentest [ ]

  Lynis modules:
  - Compliance status      [?]
  - Security audit         [V]
  - Vulnerability scan     [V]

  Details:
  Hardening index : 79 [###############     ]
  Tests performed : 278
  Plugins enabled : 2

  Software components:
  - Firewall               [V]
  - Intrusion software     [X]
  - Malware scanner        [X]

  Files:
  - Test and debug information      : /var/log/lynis.log
  - Report data                     : /var/log/lynis-report.dat

================================================================================