Security audit workflow - vulnerability scan β verification
npx skills add netsapiensis/claude-code-skills --skill "rocky-security-hardening"
Install specific skill from multi-skill repository
# Description
Rocky Linux 8/9 security hardening including CIS benchmarks with OpenSCAP, SSH hardening, fail2ban, auditd rules, PAM configuration with authselect, and system-wide crypto policies. Use when hardening servers, configuring SSH security, setting up intrusion prevention, or compliance scanning.
# SKILL.md
name: rocky-security-hardening
description: Rocky Linux 8/9 security hardening including CIS benchmarks with OpenSCAP, SSH hardening, fail2ban, auditd rules, PAM configuration with authselect, and system-wide crypto policies. Use when hardening servers, configuring SSH security, setting up intrusion prevention, or compliance scanning.
Rocky Linux Security Hardening
CIS benchmarks, SSH hardening, fail2ban, auditd, PAM/authselect, and crypto policies for Rocky Linux 8/9.
Prerequisite: See rocky-foundation for OS detection and safety tier definitions.
CIS Benchmarks and OpenSCAP
Install OpenSCAP
# Install scanner and security guide # [CONFIRM]
dnf install -y openscap-scanner scap-security-guide
# List available profiles # [READ-ONLY]
oscap info /usr/share/xml/scap/ssg/content/ssg-rl8-ds.xml # Rocky 8
oscap info /usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml # Rocky 9
Common Profiles
| Profile ID | Description |
|---|---|
xccdf_org.ssgproject.content_profile_cis |
CIS Level 1 Server |
xccdf_org.ssgproject.content_profile_cis_server_l1 |
CIS Level 1 Server |
xccdf_org.ssgproject.content_profile_cis_workstation_l1 |
CIS Level 1 Workstation |
xccdf_org.ssgproject.content_profile_stig |
DISA STIG |
xccdf_org.ssgproject.content_profile_pci-dss |
PCI-DSS |
Run Scans
# Determine correct datastream file # [READ-ONLY]
ROCKY_VERSION=$(source /etc/os-release && echo "${VERSION_ID%%.*}")
DS_FILE="/usr/share/xml/scap/ssg/content/ssg-rl${ROCKY_VERSION}-ds.xml"
# Run CIS scan and generate HTML report # [READ-ONLY]
oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis \
--report /tmp/cis-report.html \
--results /tmp/cis-results.xml \
"$DS_FILE"
# Generate remediation script (review before running!) # [READ-ONLY]
oscap xccdf generate fix \
--profile xccdf_org.ssgproject.content_profile_cis \
--fix-type bash \
--output /tmp/cis-remediation.sh \
--result-id "" \
/tmp/cis-results.xml
# Review remediation script before executing # [READ-ONLY]
less /tmp/cis-remediation.sh
WARNING: Never run auto-generated remediation scripts without reviewing them. They can break services.
SSH Hardening
Version Differences: SSH Config
| Feature | Rocky 8 | Rocky 9 |
|---|---|---|
| Main config | /etc/ssh/sshd_config |
/etc/ssh/sshd_config |
| Drop-in configs | Not default | /etc/ssh/sshd_config.d/*.conf |
| Default crypto | Wider compatibility | Stricter (crypto policies) |
| Include directive | Manual | Pre-configured |
SSH Configuration
CORRECT -- use drop-in configs on Rocky 9, main config on Rocky 8:
ROCKY_VERSION=$(source /etc/os-release && echo "${VERSION_ID%%.*}")
if [[ "$ROCKY_VERSION" == "9" ]]; then
# Rocky 9: Use drop-in config # [CONFIRM]
cat > /etc/ssh/sshd_config.d/50-hardening.conf << 'EOF'
# Key-only authentication
PasswordAuthentication no
PubkeyAuthentication yes
AuthenticationMethods publickey
# Disable root login
PermitRootLogin no
# Protocol hardening
X11Forwarding no
MaxAuthTries 3
MaxSessions 5
ClientAliveInterval 300
ClientAliveCountMax 2
LoginGraceTime 30
# Restrict users (uncomment and customize)
# AllowUsers deploy admin
# AllowGroups sshusers
# Logging
LogLevel VERBOSE
EOF
else
# Rocky 8: Edit main config (back up first!) # [CONFIRM]
cp /etc/ssh/sshd_config /etc/ssh/sshd_config.bak.$(date +%Y%m%d%H%M%S)
# Then edit /etc/ssh/sshd_config with the same directives
fi
ALWAYS validate before restarting SSH:
# Validate SSH config # [READ-ONLY]
sshd -t
# If validation passes, restart # [CONFIRM]
systemctl restart sshd
WRONG -- restarting SSH without validation:
# WRONG: If config has errors, you lose SSH access
systemctl restart sshd
# CORRECT: Always validate first
sshd -t && systemctl restart sshd
SSH Key Management
# Generate key pair (on client) # [READ-ONLY]
ssh-keygen -t ed25519 -C "user@hostname"
# Copy public key to server # [CONFIRM]
ssh-copy-id -i ~/.ssh/id_ed25519.pub user@server
# Verify key permissions on server # [READ-ONLY]
ls -la ~/.ssh/
# Should be: ~/.ssh (700), ~/.ssh/authorized_keys (600)
# Fix permissions # [CONFIRM]
chmod 700 ~/.ssh
chmod 600 ~/.ssh/authorized_keys
Non-Standard SSH Port
# 1. Update SELinux # [CONFIRM]
semanage port -a -t ssh_port_t -p tcp 2222
# 2. Update firewall # [CONFIRM]
firewall-cmd --add-port=2222/tcp --permanent
firewall-cmd --reload
# 3. Update SSH config # [CONFIRM]
# Rocky 9: /etc/ssh/sshd_config.d/50-port.conf
# Port 2222
# Rocky 8: /etc/ssh/sshd_config
# Port 2222
# 4. Validate and restart # [CONFIRM]
sshd -t && systemctl restart sshd
# 5. Test BEFORE closing current session
# Open new terminal: ssh -p 2222 user@server
fail2ban
Installation and Setup
# Install from EPEL # [CONFIRM]
dnf install -y epel-release
dnf install -y fail2ban fail2ban-firewalld
# Enable service # [CONFIRM]
systemctl enable --now fail2ban
Configuration
CORRECT -- use local overrides, never edit main config:
# WRONG: Editing /etc/fail2ban/jail.conf (overwritten on update)
# CORRECT: Create /etc/fail2ban/jail.local # [CONFIRM]
cat > /etc/fail2ban/jail.local << 'EOF'
[DEFAULT]
bantime = 3600
findtime = 600
maxretry = 5
banaction = firewallcmd-rich-rules[actiontype=<multiport>]
banaction_allports = firewallcmd-rich-rules[actiontype=<allports>]
[sshd]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/secure
maxretry = 3
bantime = 86400
[sshd-ddos]
enabled = true
port = ssh
filter = sshd-ddos
logpath = /var/log/secure
maxretry = 5
bantime = 3600
EOF
fail2ban Operations
# Status # [READ-ONLY]
fail2ban-client status
fail2ban-client status sshd
# Banned IPs # [READ-ONLY]
fail2ban-client get sshd banned
# Manually ban/unban # [CONFIRM]
fail2ban-client set sshd banip 192.168.1.100
fail2ban-client set sshd unbanip 192.168.1.100
# Reload config # [CONFIRM]
fail2ban-client reload
# Test regex against log # [READ-ONLY]
fail2ban-regex /var/log/secure /etc/fail2ban/filter.d/sshd.conf
Custom Jail Example (Nginx)
# /etc/fail2ban/jail.d/nginx.conf # [CONFIRM]
[nginx-http-auth]
enabled = true
port = http,https
filter = nginx-http-auth
logpath = /var/log/nginx/error.log
maxretry = 3
bantime = 3600
[nginx-botsearch]
enabled = true
port = http,https
filter = nginx-botsearch
logpath = /var/log/nginx/access.log
maxretry = 5
bantime = 86400
Audit Framework (auditd)
Basic Configuration
# Check audit status # [READ-ONLY]
auditctl -s
auditctl -l # List current rules
# View audit logs # [READ-ONLY]
ausearch -m USER_AUTH --start today
ausearch -m AVC --start today
aureport --summary
aureport --login
aureport --failed
Audit Rules
# Rule file: /etc/audit/rules.d/99-custom.rules # [CONFIRM]
# Watch sensitive files for changes
-w /etc/passwd -p wa -k identity
-w /etc/group -p wa -k identity
-w /etc/shadow -p wa -k identity
-w /etc/sudoers -p wa -k sudoers
-w /etc/sudoers.d/ -p wa -k sudoers
-w /etc/ssh/sshd_config -p wa -k sshd_config
# Monitor user commands
-a always,exit -F arch=b64 -S execve -F uid=0 -k root_commands
# Monitor file deletions
-a always,exit -F arch=b64 -S unlink,unlinkat,rename,renameat -F uid>=1000 -k file_deletion
# Monitor mount operations
-a always,exit -F arch=b64 -S mount -k mounts
# Monitor kernel module loading
-a always,exit -F arch=b64 -S init_module,finit_module -k module_load
-a always,exit -F arch=b64 -S delete_module -k module_unload
# Monitor network configuration changes
-w /etc/sysconfig/network -p wa -k network_config
-w /etc/NetworkManager/ -p wa -k network_config
# Make rules immutable (requires reboot to change)
# WARNING: Only add this when rules are finalized
# -e 2
# Load rules # [CONFIRM]
augenrules --load
# Verify rules loaded # [READ-ONLY]
auditctl -l
# Search audit events # [READ-ONLY]
ausearch -k identity --start today
ausearch -k sudoers --start today
ausearch -k root_commands -ts recent
PAM Configuration
authselect (Rocky 8/9)
CRITICAL: On Rocky 8/9, NEVER edit PAM files directly. Use authselect.
# View current profile # [READ-ONLY]
authselect current
authselect list-features
# List available profiles # [READ-ONLY]
authselect list
# Select profile with features # [CONFIRM]
authselect select sssd with-faillock with-mkhomedir --force
# Enable/disable features # [CONFIRM]
authselect enable-feature with-faillock
authselect disable-feature with-mkhomedir
# Available features # [READ-ONLY]
authselect list-features sssd
WRONG -- direct PAM editing:
# WRONG: Editing PAM files directly (authselect will overwrite)
vim /etc/pam.d/system-auth
vim /etc/pam.d/password-auth
# CORRECT: Use authselect
authselect select sssd with-faillock --force
Account Lockout (pam_faillock)
# Enable faillock via authselect # [CONFIRM]
authselect enable-feature with-faillock
# Configure faillock # [CONFIRM]
# /etc/security/faillock.conf
cat > /etc/security/faillock.conf << 'EOF'
deny = 5
unlock_time = 900
fail_interval = 900
audit
even_deny_root
root_unlock_time = 60
EOF
# Check locked accounts # [READ-ONLY]
faillock --user username
# Unlock a locked account # [CONFIRM]
faillock --user username --reset
Password Quality (pam_pwquality)
# Configure password requirements # [CONFIRM]
# /etc/security/pwquality.conf
cat > /etc/security/pwquality.conf << 'EOF'
minlen = 14
dcredit = -1
ucredit = -1
lcredit = -1
ocredit = -1
minclass = 3
maxrepeat = 3
maxclassrepeat = 4
retry = 3
enforce_for_root
EOF
Password Aging
# Set defaults for new users # [CONFIRM]
# /etc/login.defs
# PASS_MAX_DAYS 90
# PASS_MIN_DAYS 7
# PASS_WARN_AGE 14
# PASS_MIN_LEN 14
# Set for existing user # [CONFIRM]
chage -M 90 -m 7 -W 14 username
# Check password aging # [READ-ONLY]
chage -l username
Cryptographic Policies
System-wide crypto policies control TLS, SSH, IPSec, and other crypto defaults.
# Check current policy # [READ-ONLY]
update-crypto-policies --show
# Available policies # [READ-ONLY]
update-crypto-policies --show
# DEFAULT -- reasonable defaults
# FUTURE -- conservative, may break older clients
# FIPS -- FIPS 140-2 compliance
# LEGACY -- allows old protocols (NOT recommended)
# NEXT -- Rocky 9 only, between DEFAULT and FUTURE
Set Crypto Policy
# Set policy # [CONFIRM]
update-crypto-policies --set FUTURE
# Rocky 9 only: policy submodules # [CONFIRM]
update-crypto-policies --set DEFAULT:NO-SHA1
# Verify # [READ-ONLY]
update-crypto-policies --show
# View what the policy affects # [READ-ONLY]
update-crypto-policies --check
Version Differences: Crypto Policies
| Feature | Rocky 8 | Rocky 9 |
|---|---|---|
| Policies | DEFAULT, LEGACY, FUTURE, FIPS | Same + NEXT |
| Submodules | Not supported | Supported (e.g., :NO-SHA1) |
| Default OpenSSL | 1.1.1 | 3.0 (different defaults) |
| SHA-1 | Allowed in DEFAULT | Restricted in some contexts |
Additional Hardening
Kernel Hardening (sysctl)
# /etc/sysctl.d/99-security.conf # [CONFIRM]
cat > /etc/sysctl.d/99-security.conf << 'EOF'
# Network hardening
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
net.ipv4.conf.all.accept_redirects = 0
net.ipv4.conf.default.accept_redirects = 0
net.ipv4.conf.all.accept_source_route = 0
net.ipv4.conf.default.accept_source_route = 0
net.ipv4.conf.all.log_martians = 1
net.ipv4.conf.default.log_martians = 1
net.ipv4.icmp_echo_ignore_broadcasts = 1
net.ipv4.icmp_ignore_bogus_error_responses = 1
net.ipv4.conf.all.rp_filter = 1
net.ipv4.conf.default.rp_filter = 1
net.ipv4.tcp_syncookies = 1
# Disable IPv6 if not needed (uncomment)
# net.ipv6.conf.all.disable_ipv6 = 1
# net.ipv6.conf.default.disable_ipv6 = 1
# Kernel hardening
kernel.randomize_va_space = 2
kernel.dmesg_restrict = 1
kernel.kptr_restrict = 2
fs.suid_dumpable = 0
EOF
# Apply # [CONFIRM]
sysctl -p /etc/sysctl.d/99-security.conf
Disable Unnecessary Services
# Find and disable unnecessary services # [READ-ONLY]
systemctl list-units --type=service --state=running
# Common services to disable if not needed # [CONFIRM]
systemctl disable --now postfix # If no local mail
systemctl disable --now rpcbind # If no NFS/NIS
systemctl disable --now avahi-daemon # If no mDNS
systemctl mask ctrl-alt-del.target # Prevent accidental reboot
File Permission Hardening
# Check world-writable files # [READ-ONLY]
find / -xdev -type f -perm -0002 -ls 2>/dev/null
# Check unowned files # [READ-ONLY]
find / -xdev -nouser -o -nogroup 2>/dev/null
# Check SUID/SGID files # [READ-ONLY]
find / -xdev -type f \( -perm -4000 -o -perm -2000 \) -ls 2>/dev/null
# Secure boot loader # [CONFIRM]
chmod 600 /boot/grub2/grub.cfg
USB Storage Disable
# Disable USB storage # [CONFIRM]
echo "install usb-storage /bin/true" > /etc/modprobe.d/disable-usb-storage.conf
echo "blacklist usb-storage" >> /etc/modprobe.d/disable-usb-storage.conf
Checklist: Server Hardening
- [ ] Set crypto policy (
update-crypto-policies --set FUTURE) - [ ] SSH: Key-only auth, no root login, validate config
- [ ] Configure fail2ban for SSH (and web services if applicable)
- [ ] Set password policies (pwquality, faillock, aging)
- [ ] Configure auditd rules for critical files
- [ ] Apply kernel hardening sysctl parameters
- [ ] Disable unnecessary services
- [ ] Check file permissions (world-writable, SUID/SGID)
- [ ] Run OpenSCAP CIS scan and review results
- [ ] SELinux in Enforcing mode (
getenforce) - [ ] Firewall configured with minimal open ports
When to Use This Skill
- Initial server hardening after OS installation
- Preparing for compliance audits (CIS, STIG, PCI-DSS)
- Configuring SSH security
- Setting up intrusion prevention (fail2ban)
- Configuring audit logging
- Managing PAM authentication settings
- Setting system-wide cryptographic policies
Related Skills
- rocky-foundation -- OS detection, safety tiers
- rocky-selinux -- SELinux is part of security posture
- rocky-core-system -- Service management, user admin
- rocky-networking -- Firewall hardening
- rocky-webstack -- Web server security, TLS
# Supported AI Coding Agents
This skill is compatible with the SKILL.md standard and works with all major AI coding agents:
Learn more about the SKILL.md standard and how to use these skills with your preferred AI coding agent.