auditd¶
auditd is the Linux audit system: a kernel component that records system calls against rules, and a daemon that writes them to /var/log/audit/audit.log. It's the closest thing Linux has to the Windows Security log, and on most images it isn't installed. The whole section is Level 2 in the benchmark (CIS 6.2.x); it's still worth doing, because it's what makes the Reading the Logs page answer questions.
Install and enable¶
sudo apt install -y auditd audispd-plugins
sudo systemctl enable --now auditd
sudo auditctl -s
auditctl -s shows enabled 1 when it's working (CIS 6.2.1.1, 6.2.1.2). The Bootloader page adds audit=1 and audit_backlog_limit=8192 to the kernel line so events before the daemon starts are kept (6.2.1.3, 6.2.1.4).
auditd.conf¶
/etc/audit/auditd.conf controls the log files and what happens when they fill. The dangerous values are the ones that quietly stop logging.
| Setting | Value | Why |
|---|---|---|
max_log_file |
32 (MB) or more (CIS 6.2.2.1) | Room per file |
num_logs |
5 or more | How many files are kept |
max_log_file_action |
keep_logs (CIS 6.2.2.2) or ROTATE; never IGNORE |
When a file fills, start a new one and keep the old ones. keep_logs never deletes; ROTATE keeps num_logs. |
space_left_action |
email or syslog (CIS 6.2.2.4; never ignore) |
Warn when the disk is getting full |
admin_space_left_action |
single or halt (CIS 6.2.2.4) |
Drop to single-user mode when it's nearly full; a hardened machine would rather stop than run unlogged |
disk_full_action |
halt or single (CIS 6.2.2.3; never ignore) |
Same when it is full |
disk_error_action |
syslog, single, or halt (CIS 6.2.2.3) |
|
action_mail_acct |
root, or an administrator named in the README (never nobody) |
Who gets the email |
log_format |
ENRICHED |
Usernames are resolved in the log |
F=/etc/audit/auditd.conf
sudo sed -i -E 's/^max_log_file_action.*/max_log_file_action = keep_logs/; s/^space_left_action.*/space_left_action = email/; s/^admin_space_left_action.*/admin_space_left_action = single/; s/^disk_full_action.*/disk_full_action = halt/; s/^disk_error_action.*/disk_error_action = halt/; s/^action_mail_acct.*/action_mail_acct = root/; s/^num_logs.*/num_logs = 5/; s/^max_log_file .*/max_log_file = 32/' $F
Rules¶
Rules live in /etc/audit/rules.d/*.rules and are compiled into /etc/audit/audit.rules by augenrules --load. There are two shapes, and once you can read them you can write any rule the benchmark asks for.
A file watch logs writes and attribute changes to one path:
-w /etc/sudoers -p wa -k scope
-w the path, -p wa the permissions to watch (w write, a attribute change, r read, x execute), -k a key you choose so you can search for it later with ausearch -k scope.
A syscall rule logs a system call when its conditions match:
-a always,exit -F arch=b64 -S execve -C euid!=uid -F auid!=unset -k user_emulation
-a always,exit means log it after the call returns. -F arch=b64 is the CPU architecture (on a 64-bit kernel each rule wants a matching arch=b32 twin, because a 32-bit program uses a different syscall table). -S the syscall names. -F and -C are filters: auid>=1000 restricts to real users, auid!=unset skips system processes, -C euid!=uid compares two fields (this one means "running as someone other than who logged in", which is sudo or su). -F path=… -F perm=x on a syscall rule watches execution of one binary.
Start a file, sudo nano /etc/audit/rules.d/50-hardening.rules, and write the rules for the table below. Do the file watches first; they're the shape you'll use most. For the syscall rules, write the b64 line, then copy it and change b64 to b32.
| What to log | CIS | Type | Targets | Key |
|---|---|---|---|---|
| Account and group changes | 6.2.3.8 | watch, -p wa |
/etc/passwd, /etc/shadow, /etc/group, /etc/gshadow, /etc/security/opasswd, /etc/nsswitch.conf, /etc/pam.conf, /etc/pam.d |
identity |
| Changes to who can sudo | 6.2.3.1 | watch | /etc/sudoers, /etc/sudoers.d |
scope |
| Commands run as another user | 6.2.3.2 | syscall execve with -C euid!=uid -F auid!=unset |
user_emulation |
|
| Changes to the sudo log | 6.2.3.3 | watch | /var/log/sudo.log |
sudo_log_file |
| Sessions and logins | 6.2.3.11, 6.2.3.12 | watch | /var/run/utmp, /var/log/wtmp, /var/log/btmp, /var/log/lastlog, /var/run/faillock, /var/log/faillog |
session for the first three, logins for the rest |
| Permission and owner changes | 6.2.3.9 | syscall chmod,fchmod,fchmodat,chown,fchown,fchownat,lchown,setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr with -F auid>=1000 -F auid!=unset |
perm_mod |
|
| Failed file access | 6.2.3.7 | syscall creat,open,openat,truncate,ftruncate with -F exit=-EACCES (and a second pair with -F exit=-EPERM), auid>=1000, auid!=unset |
access |
|
| Deletions | 6.2.3.13 | syscall rename,unlink,unlinkat,renameat, auid>=1000, auid!=unset |
delete |
|
| Mounts | 6.2.3.10 | syscall mount, auid>=1000, auid!=unset |
mounts |
|
| Privileged commands | 6.2.3.6, 6.2.3.15 to 6.2.3.18; STIG UBTU-22-654010 to 654125 | -a always,exit -F path=<binary> -F perm=x -F auid>=1000 -F auid!=unset |
/usr/bin/chcon, /usr/bin/setfacl, /usr/bin/chacl, /usr/sbin/apparmor_parser, /usr/sbin/usermod, /usr/bin/chage, /usr/bin/chfn, /usr/bin/chsh, /usr/bin/crontab, /usr/sbin/fdisk, /usr/bin/gpasswd, /usr/bin/mount, /usr/bin/umount, /usr/bin/newgrp, /usr/sbin/pam_timestamp_check, /usr/bin/passwd, /usr/bin/ssh-agent, /usr/lib/openssh/ssh-keysign, /usr/bin/su, /usr/bin/sudo, /usr/bin/sudoedit, /usr/sbin/unix_update |
privileged (or one key per command, as the STIG does) |
| AppArmor policy | 6.2.3.14 | watch | /etc/apparmor, /etc/apparmor.d |
MAC-policy |
| The files you hardened | watch | /etc/ssh/sshd_config, /etc/ssh/sshd_config.d, /etc/pam.d, /etc/security, /etc/login.defs, /etc/sysctl.conf, /etc/sysctl.d, /etc/cron.d, /etc/crontab, /var/spool/cron, /etc/systemd/system |
your choice | |
| Journal | STIG UBTU-22-654190 | watch | /var/log/journal |
systemd_journal |
| Kernel modules | 6.2.3.19; STIG UBTU-22-654055, 654060 | syscall init_module,finit_module,delete_module,create_module,query_module, auid>=1000, auid!=unset; plus a path=/usr/bin/kmod -F perm=x rule |
kernel_modules |
|
| Time changes | 6.2.3.4 | syscall adjtimex,settimeofday,clock_settime; watch /etc/localtime |
time-change |
|
| Network identity | 6.2.3.5 | syscall sethostname,setdomainname; watch /etc/issue, /etc/issue.net, /etc/hosts, /etc/hostname, /etc/networks, /etc/network, /etc/netplan |
system-locale |
|
| The audit config itself | watch | /etc/audit, /var/log/audit |
auditconfig, auditlog |
The full benchmark rule for privileged commands generates a line for every setuid and setgid binary on the system: sudo find / -perm /6000 -type f lists them, and a short shell loop that prints a -a always,exit -F path= line for each is a good first script to write.
Load and check:
sudo augenrules --load
sudo auditctl -l | wc -l
sudo auditctl -l | grep identity
The last one prints your file watches back in the kernel's own spelling:
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
A rule the kernel rejected is reported by augenrules --load with the file and line number; a typo in a syscall name is the usual cause.
Making the rules immutable (-e 2 as the last line of the last rules file, CIS 6.2.3.20) means nothing can change them until reboot; do that at the end if the README doesn't need auditing changed later. augenrules --check confirms the loaded rules match the files on disk (6.2.3.21).
Protect the audit files¶
The log, the config, and the tools themselves have permissions the benchmark checks (CIS 6.2.4.1 to 6.2.4.10):
sudo chmod 750 /var/log/audit; sudo chown root:root /var/log/audit
sudo find /var/log/audit -type f -exec chmod 640 {} +; sudo chown root:root /var/log/audit/*
sudo chmod 640 /etc/audit/auditd.conf /etc/audit/audit.rules /etc/audit/rules.d/*.rules; sudo chown root:root /etc/audit/*.conf /etc/audit/*.rules /etc/audit/rules.d/*
sudo chmod 755 /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/augenrules; sudo chown root:root /sbin/auditctl /sbin/aureport /sbin/ausearch /sbin/autrace /sbin/auditd /sbin/augenrules
Verify¶
systemctl is-active auditd
sudo auditctl -s | grep enabled
sudo auditctl -l | grep -c user_emulation # 2
grep -E '^(max_log_file_action|disk_full_action|action_mail_acct)' /etc/audit/auditd.conf
sudo ausearch -k user_emulation --start today | tail -n 5
Example¶
systemctl is-active auditd says inactive: not installed. Install it. auditd.conf (from a previous install) has max_log_file_action = IGNORE, disk_full_action = ignore, action_mail_acct = nobody. Fix all three, write the rules file, augenrules --load. ausearch -k user_emulation a minute later shows your own sudo commands, which is the proof it works.
Try it¶
- Write the five
identityfile watches by hand, load them, edit/etc/passwdwithnano(change nothing), and find the event withausearch -k identity. - Write the
user_emulationsyscall rule, runsudo true, and find it.
Build it¶
Your rules file, written from the table, is the tool. Add a rules-check.sh that runs augenrules --check, auditctl -l | wc -l, and ausearch -k user_emulation --start today | tail.