RHEL Advanced #5: Security Hardening — auditd, OpenSCAP, FIPS
In #4 SELinux Advanced we looked at how to pin down a single domain. This post climbs higher: hardening the whole machine through a compliance and audit lens. auditd captures every change to the system, OpenSCAP auto-checks and auto-remediates against industry standards, and FIPS mode is required by government and financial certifications — all in one cycle.
Position of this post in the RHEL Advanced series:
- #1 Boot Process — GRUB2, dracut, Recovery Mode
- #2 Kernel Tuning — sysctl, tuned, kdump
- #3 Performance Analysis — sar, top/htop, iostat, vmstat, perf
- #4 SELinux Advanced — Writing Policy and audit2allow
- #5 Security Hardening — auditd, OpenSCAP, FIPS ← this post
- #6 Subscription / Satellite / Insights
- #7 Cockpit for GUI Management and Web Console
Placing the Three Tools #
| Tool | Role | When to use |
|---|---|---|
auditd | Record changes that happened on the system | Post-incident investigation, compliance evidence |
OpenSCAP | Automated check + remediation against standards | Certification prep, periodic audits |
FIPS mode | Only certified crypto modules permitted | When required by gov / financial contracts |
auditd answers “who did what” after the fact, OpenSCAP asks “is the system configured to standard”, and FIPS constrains which cryptographic algorithms are available system-wide. Together they cover compliance demands in one cycle.
auditd — The Record of Every Change #
A user-space daemon that sits on top of the Linux kernel’s audit subsystem. RHEL 9 has it installed and starts it automatically at boot.
$ sudo systemctl status auditd
$ sudo auditctl -s
enabled 1
failure 1
pid 1234
rate_limit 0
backlog_limit 8192
lost 0
backlog 0enabled 1 means it is on. SELinux denials (#4) eventually drop into auditd as well.
Default log location #
$ sudo tail /var/log/audit/audit.log
type=USER_LOGIN msg=audit(1714435200.123:1234): pid=2345 uid=0 ...
type=SYSCALL msg=audit(1714435201.456:1235): arch=c000003e syscall=2 success=yes ...Hard to read directly. ausearch and aureport are the standard query tools.
Writing rules — /etc/audit/rules.d/ #
auditctl one-liners are runtime-only; persistence lives in /etc/audit/rules.d/*.rules.
# common opening entries
-D
-b 8192
-f 1| Option | Meaning |
|---|---|
-D | Clear existing rules (clean state on restart) |
-b 8192 | Backlog limit |
-f 1 | Failure action (0=silent, 1=printk, 2=panic) |
# watch /etc/passwd, /etc/shadow for changes
-w /etc/passwd -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/sudoers -p wa -k actions
-w /etc/sudoers.d/ -p wa -k actions
# watch SSH config changes
-w /etc/ssh/sshd_config -p wa -k sshd_config| Option | Meaning |
|---|---|
-w <path> | watch — track changes to this path |
-p wa | Permissions to track (w=write, a=attribute change, r=read, x=execute) |
-k <key> | Tag attached to events — query later with ausearch -k |
Syscall-level rules #
Record every time a particular syscall fires.
# every user/group change attempt
-a always,exit -F arch=b64 -S setuid -S setgid -S setresuid -S setresgid -k privilege
# track module loading via insmod / modprobe
-a always,exit -F path=/sbin/insmod -F perm=x -k modules
-a always,exit -F path=/sbin/modprobe -F perm=x -k modules| Part | Meaning |
|---|---|
-a always,exit | always (record whenever filter passes), exit (at syscall exit) |
-F arch=b64 | architecture filter (64-bit) |
-S <syscall> | syscall to track |
-F path=... | path filter |
-k <key> | tag |
Loading rules #
# compile rule files into /etc/audit/audit.rules
$ sudo augenrules --load
# or restart
$ sudo systemctl restart auditd
# inspect loaded rules
$ sudo auditctl -laugenrules --load concatenates files in /etc/audit/rules.d/ in alphabetical order and applies them.
Immutable mode — -e 2 #
The option that locks rules and forbids changes until reboot. Mandatory in compliance environments.
-e 2Once -e 2 is applied, any further rule addition is rejected. Place it as the last rule file.
Querying — ausearch #
# by key
$ sudo ausearch -k identity
# by time window
$ sudo ausearch -k identity -ts today
$ sudo ausearch -k identity -ts 04/30/2026 09:00:00
# by user
$ sudo ausearch -ua curtis -ts today
# failed syscalls only
$ sudo ausearch -sv no -ts today
# friendlier output
$ sudo ausearch -k identity -i-i (interpret) translates numeric UIDs and syscall numbers into human-readable names.
Summaries — aureport #
$ sudo aureport --summary
$ sudo aureport -au # authentication attempts
$ sudo aureport -l # login events
$ sudo aureport -f -i # file events (interpret mode)
$ sudo aureport --failed # failures onlyThese make a useful bundle for periodic reporting.
Retention and rotation #
Key fields in /etc/audit/auditd.conf:
log_file = /var/log/audit/audit.log
max_log_file = 100 # MB
num_logs = 10
max_log_file_action = ROTATE # KEEP_LOGS / ROTATE / SUSPEND
space_left = 200 # MB — alert below this
space_left_action = SYSLOG
admin_space_left = 50 # MB — further below
admin_space_left_action = SUSPEND
disk_full_action = HALT # when fullIn compliance environments, set max_log_file_action = KEEP_LOGS so logs are never lost, and ship to a separate collection system (SIEM). That is the standard pattern.
OpenSCAP — Automated Security Standard Checks #
SCAP (Security Content Automation Protocol) is a NIST-defined security automation standard. oscap is the tool that operates on top of it. RHEL ships standardized content via the SCAP Security Guide package.
$ sudo dnf install -y openscap-scanner scap-security-guideAvailable profiles #
$ ls /usr/share/xml/scap/ssg/content/
ssg-rhel9-ds.xml
...
$ sudo oscap info /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml | grep -E '^(Profile|Description)'
Profile: xccdf_org.ssgproject.content_profile_cis
Profile: xccdf_org.ssgproject.content_profile_cis_server_l1
Profile: xccdf_org.ssgproject.content_profile_pci-dss
Profile: xccdf_org.ssgproject.content_profile_stig
Profile: xccdf_org.ssgproject.content_profile_hipaa
Profile: xccdf_org.ssgproject.content_profile_cui
Profile: xccdf_org.ssgproject.content_profile_anssi_bp28_high
...| Profile | Who requires it |
|---|---|
cis / cis_server_l1 | CIS Benchmark — most common |
pci-dss | Card payments (PCI-DSS) |
stig | US DoD — DISA STIG |
hipaa | US healthcare |
cui | US Controlled Unclassified Information |
anssi_bp28_high | France ANSSI |
Running a scan #
$ sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis \
--results-arf arf.xml \
--report report.html \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xmlOpen report.html in a browser and you get a human-readable report. Passed / failed rules, each rule’s description, how to fix it, and even bash or ansible auto-remediation snippets.
Result categories #
| Result | Meaning |
|---|---|
pass | Passed |
fail | Failed — not compliant |
notapplicable | Does not apply to this system |
notchecked | Cannot be checked automatically (manual verification needed) |
Auto-remediation #
Passing --remediate makes the scan auto-fix failing rules.
$ sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis \
--remediate \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xmlRunning --remediate in one shot on a production machine is risky. Failing rules sometimes include things like sshd_config’s PermitRootLogin no that may immediately cut off the operator currently SSH’d in as root. Standard flow:
- dry-run —
--results-arf arf.xml --report report.htmlonly (no fix) - Review the report, assess impact
- Extract a remediation script —
oscap xccdf generate fix --result-id ... arf.xml > fix.sh - Review the script and apply in stages
Extract as an Ansible playbook #
$ sudo oscap xccdf generate fix \
--profile xccdf_org.ssgproject.content_profile_cis \
--fix-type ansible \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml \
> cis-remediate.ymlRunning the generated cis-remediate.yml with Ansible applies the same fix consistently across many machines — the same flow you’ll encounter again on the RHCE track.
Apply at install time — Kickstart #
To install a server to standard from the start, specify the profile in Kickstart’s %addon org_fedora_oscap section.
%addon org_fedora_oscap
content-type = scap-security-guide
profile = xccdf_org.ssgproject.content_profile_cis
%endOpenSCAP runs automatically at the end of installation. The same approach also works when building integrated images with RHEL Image Builder.
FIPS — Only Certified Crypto Modules #
FIPS (Federal Information Processing Standards) 140-2/140-3 is the US government’s set of requirements for certified cryptographic modules. Frequently required by government, financial, and healthcare contracts. RHEL 9 ships certified OpenSSL, NSS, and libgcrypt.
Enabling #
$ sudo fips-mode-setup --enable
$ sudo reboot
# check
$ sudo fips-mode-setup --check
FIPS mode is enabled.
$ cat /proc/sys/crypto/fips_enabled
1fips-mode-setup --enable internally does:
- Include the fips module in
dracutconfiguration - Regenerate
initramfs - Add the
fips=1GRUB argument - Back up and regenerate SSH host keys if they are FIPS-incompatible
Effective after reboot.
What changes #
- Weak algorithms (MD5, RC4, DES, 3DES, Blowfish, …) refused
- SHA-1 refused for digital signatures (plain hash comparison is partially allowed)
- SSH refuses weak key exchange and ciphers — some client compatibility hit
- Java runs on a BC-FIPS provider — affects some libraries
- TLS 1.0/1.1 refused, only TLS 1.2/1.3 permitted
- Bypass paths like self-compiled OpenSSL are blocked
The operational impact is significant — run compatibility tests before enabling.
FIPS and containers #
When the host is in FIPS mode, calls inside a container that go through the host kernel’s crypto API are still under FIPS constraints. Container OpenSSL using its own algorithms is a separate matter. The standard is the FIPS build of RHEL UBI (ubi9/ubi-minimal + FIPS module).
Disabling #
$ sudo fips-mode-setup --disable
$ sudo rebootToggling FIPS state risks breaking existing keys and certificates, so the standard practice is to decide once at provisioning time and leave it.
A Unified Operational Flow #
| Stage | Tool | Outcome |
|---|---|---|
| 1. Install machine | Kickstart + OpenSCAP CIS profile | Initial state aligned to standard |
| 2. Start operating | auditd rules applied + -e 2 lock | Every change recorded |
| 3. Periodic audit | OpenSCAP scheduled weekly → report | Alert on drift |
| 4. Track changes | ausearch / aureport | Who did what |
| 5. Certification requirements | FIPS mode enabled | Weak algorithms blocked |
Each stage reinforces the next. Track weaknesses surfaced by OpenSCAP with auditd, constrain the algorithms themselves with FIPS, and prove the changes via audit logs.
Common Pitfalls #
- Missing
-e 2in auditd rules — rules can be tampered with at runtime. A perennial finding in compliance environments. auditdfilling the disk and freezing the system —disk_full_action = HALTmay be the default. Pair with SIEM forwarding or a separate disk/partition.- Running OpenSCAP
--remediatedirectly in production — can sever sshd or lock out users. Dry-run → review → staged. - Build failures after enabling FIPS — some Java builds and Python
hashlib.md5(usedforsecurity=False)etc. require call-site fixes. Validate in CI first. - Host FIPS, container non-FIPS image — the container’s own OpenSSL may use weak algorithms. Use UBI FIPS builds.
- Ignoring
notcheckedrules in a SCAP report — they cannot be auto-checked but require manual verification. Provide separate evidence at certification time. - Too many auditd rules — tracking every syscall hurts performance and explodes disk. Stick to the minimum the compliance standard demands.
Commands Worth Remembering #
| Task | Command |
|---|---|
| auditd status | sudo auditctl -s |
| Load rules | sudo augenrules --load |
| Inspect rules | sudo auditctl -l |
| Query by key | sudo ausearch -k <key> -ts today -i |
| Summary report | sudo aureport --summary |
| OpenSCAP scan | sudo oscap xccdf eval --profile <id> --report report.html <ds.xml> |
| Auto-remediate | sudo oscap xccdf eval --profile <id> --remediate <ds.xml> |
| Extract Ansible | sudo oscap xccdf generate fix --fix-type ansible --profile <id> <ds.xml> |
| Enable FIPS | sudo fips-mode-setup --enable && sudo reboot |
| Check FIPS | sudo fips-mode-setup --check / cat /proc/sys/crypto/fips_enabled |
Wrap-up #
- auditd — the change recorder. Put watch (
-w) and syscall (-a) rules into/etc/audit/rules.d/andaugenrules --load. End with-e 2to lock. Query withausearch -k, summarize withaureport. - OpenSCAP — auto-check + auto-fix against standards (CIS, STIG, PCI-DSS, HIPAA, …). Run
oscap xccdf evalfor the HTML report and prefer extracting an Ansible playbook and applying in stages. - FIPS mode — only certified crypto modules.
fips-mode-setup --enableand reboot. Weak algorithms refused, TLS 1.2/1.3 only, mind SSH / Java compatibility. - The bundle — install (OpenSCAP) → record changes (auditd) → periodic audit (OpenSCAP) → certification (FIPS).
- Operational pitfalls — the
-e 2lock, disk-full action, never run OpenSCAP--remediatestraight in production.
The next post steps back from security to RHEL’s subscription and operations tooling: Subscription Manager, Satellite, and the cloud-managed SaaS Insights — all in one cycle.