RHEL Advanced #5: Security Hardening — auditd, OpenSCAP, FIPS

10 min read

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:

Placing the Three Tools #

ToolRoleWhen to use
auditdRecord changes that happened on the systemPost-incident investigation, compliance evidence
OpenSCAPAutomated check + remediation against standardsCertification prep, periodic audits
FIPS modeOnly certified crypto modules permittedWhen 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.

check status
$ sudo systemctl status auditd
$ sudo auditctl -s
enabled 1
failure 1
pid 1234
rate_limit 0
backlog_limit 8192
lost 0
backlog 0

enabled 1 means it is on. SELinux denials (#4) eventually drop into auditd as well.

Default log location #

/var/log/audit/audit.log
$ 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.

/etc/audit/rules.d/00-defaults.rules
# common opening entries
-D
-b 8192
-f 1
OptionMeaning
-DClear existing rules (clean state on restart)
-b 8192Backlog limit
-f 1Failure action (0=silent, 1=printk, 2=panic)
/etc/audit/rules.d/10-watch.rules
# 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
OptionMeaning
-w <path>watch — track changes to this path
-p waPermissions 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.

/etc/audit/rules.d/20-actions.rules
# 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
PartMeaning
-a always,exitalways (record whenever filter passes), exit (at syscall exit)
-F arch=b64architecture filter (64-bit)
-S <syscall>syscall to track
-F path=...path filter
-k <key>tag

Loading rules #

apply
# compile rule files into /etc/audit/audit.rules
$ sudo augenrules --load

# or restart
$ sudo systemctl restart auditd

# inspect loaded rules
$ sudo auditctl -l

augenrules --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.

/etc/audit/rules.d/99-finalize.rules
-e 2

Once -e 2 is applied, any further rule addition is rejected. Place it as the last rule file.

Querying — ausearch #

common patterns
# 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 #

summary reports
$ 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 only

These make a useful bundle for periodic reporting.

Retention and rotation #

Key fields in /etc/audit/auditd.conf:

/etc/audit/auditd.conf essentials
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 full

In 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.

install
$ sudo dnf install -y openscap-scanner scap-security-guide

Available profiles #

content location
$ 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
...
ProfileWho requires it
cis / cis_server_l1CIS Benchmark — most common
pci-dssCard payments (PCI-DSS)
stigUS DoD — DISA STIG
hipaaUS healthcare
cuiUS Controlled Unclassified Information
anssi_bp28_highFrance ANSSI

Running a scan #

scan with the CIS profile
$ 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.xml

Open 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 #

ResultMeaning
passPassed
failFailed — not compliant
notapplicableDoes not apply to this system
notcheckedCannot be checked automatically (manual verification needed)

Auto-remediation #

Passing --remediate makes the scan auto-fix failing rules.

auto-remediate — carefully
$ sudo oscap xccdf eval \
    --profile xccdf_org.ssgproject.content_profile_cis \
    --remediate \
    /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml

Running --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:

  1. dry-run--results-arf arf.xml --report report.html only (no fix)
  2. Review the report, assess impact
  3. Extract a remediation scriptoscap xccdf generate fix --result-id ... arf.xml > fix.sh
  4. Review the script and apply in stages

Extract as an Ansible playbook #

ansible format
$ 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.yml

Running 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.

Kickstart snippet
%addon org_fedora_oscap
    content-type = scap-security-guide
    profile = xccdf_org.ssgproject.content_profile_cis
%end

OpenSCAP 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 #

enable FIPS mode
$ sudo fips-mode-setup --enable
$ sudo reboot

# check
$ sudo fips-mode-setup --check
FIPS mode is enabled.

$ cat /proc/sys/crypto/fips_enabled
1

fips-mode-setup --enable internally does:

  1. Include the fips module in dracut configuration
  2. Regenerate initramfs
  3. Add the fips=1 GRUB argument
  4. 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 #

disable
$ sudo fips-mode-setup --disable
$ sudo reboot

Toggling 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 #

StageToolOutcome
1. Install machineKickstart + OpenSCAP CIS profileInitial state aligned to standard
2. Start operatingauditd rules applied + -e 2 lockEvery change recorded
3. Periodic auditOpenSCAP scheduled weekly → reportAlert on drift
4. Track changesausearch / aureportWho did what
5. Certification requirementsFIPS mode enabledWeak 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 2 in auditd rules — rules can be tampered with at runtime. A perennial finding in compliance environments.
  • auditd filling the disk and freezing the systemdisk_full_action = HALT may be the default. Pair with SIEM forwarding or a separate disk/partition.
  • Running OpenSCAP --remediate directly 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 notchecked rules 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 #

TaskCommand
auditd statussudo auditctl -s
Load rulessudo augenrules --load
Inspect rulessudo auditctl -l
Query by keysudo ausearch -k <key> -ts today -i
Summary reportsudo aureport --summary
OpenSCAP scansudo oscap xccdf eval --profile <id> --report report.html <ds.xml>
Auto-remediatesudo oscap xccdf eval --profile <id> --remediate <ds.xml>
Extract Ansiblesudo oscap xccdf generate fix --fix-type ansible --profile <id> <ds.xml>
Enable FIPSsudo fips-mode-setup --enable && sudo reboot
Check FIPSsudo 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/ and augenrules --load. End with -e 2 to lock. Query with ausearch -k, summarize with aureport.
  • OpenSCAP — auto-check + auto-fix against standards (CIS, STIG, PCI-DSS, HIPAA, …). Run oscap xccdf eval for the HTML report and prefer extracting an Ansible playbook and applying in stages.
  • FIPS mode — only certified crypto modules. fips-mode-setup --enable and 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 2 lock, disk-full action, never run OpenSCAP --remediate straight 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.

X