What a CIS Benchmark is
The Center for Internet Security (CIS) is a non-profit organization that publishes Benchmarks, hardening guidelines for each platform: RHEL 9, Ubuntu 22.04, Kubernetes, AWS, Docker, and so on.
A Benchmark is an 800 to 1000 page PDF with numbered recommendations:
1.1.1.1 Ensure mounting of cramfs filesystems is disabled (Automated)
Profile: L1 Server, L1 Workstation
Description: ...
Rationale: ...
Audit: modprobe -n -v cramfs | grep "^install /bin/false"
Remediation: echo "install cramfs /bin/false" >> /etc/modprobe.d/cramfs.conf
Each recommendation says what to check, why, how to audit it, and how to remediate. They split into:
- L1 (Level 1) is the baseline, with no loss of functionality. It targets most production servers.
- L2 (Level 2) is paranoid and can break legacy. It targets high-security environments.
- Server vs Workstation profiles have different priorities (for example, you do not need X11 on a server).
- Automated vs Manual says whether a scanner can check it automatically.
You download it after registering on cisecurity.org. It is free, and the license covers reading and use, not rebranding.
Why it matters
- Compliance: PCI-DSS, HIPAA, and SOC 2 all require some baseline. CIS is the most understandable and the most widely accepted.
- Reduce attack surface: strip SUID bits from binaries that do not need them, disable unneeded services, restrict kernel modules.
- Defense in depth: it sits between AppArmor/SELinux at the application level and the firewall on the network.
Alternative standards
| Standard | Who | Where it applies |
|---|---|---|
| CIS Benchmark | CIS (industry) | universal, the basis of compliance |
| STIG | DISA (US DoD) | US government sector |
| DISA SRG | DISA | containers, cloud |
| NIST SP 800-53 | NIST | high-level controls, not a recipe |
| PCI-DSS | Card industry | wherever cards are processed |
They often interlock: CIS maps to a PCI control. An auditor wants to see the Benchmark plus the scan result.
lynis, quick audit
A Bash script from CISOfy that runs locally, rates hardening against about a hundred heuristics, and produces a hardening index (0 to 100):
sudo lynis audit system
# Hardening index : 67
# Tests performed : 263
# Plugins enabled : 0
It checks the PAM config, sudo settings, [[ssh-hardening|sshd]], the firewall, mount options, kernel parameters, world-writable files, and outdated software.
The output gives Suggestions and Warnings linked to docs. It is more for the developer-admin (see quickly what is wrong) than for a compliance report.
# Only a specific section
sudo lynis audit system --tests-from-group "kernel,filesystems"
# Cron for regular auditing
0 2 * * * /usr/sbin/lynis audit system --cronjob > /var/log/lynis.log
Downsides: the score is subjective, it is not tied to a specific standard, and it is bash-only (no remediation).
OpenSCAP, formal compliance
The SCAP (Security Content Automation Protocol) standard from NIST is an XML format for describing checks, configs, and vulns. OpenSCAP is the open-source implementation.
It uses:
- XCCDF (eXtensible Configuration Checklist Description Format) for the profile description (the recommendations)
- OVAL (Open Vulnerability Assessment Language) for how to check
- CPE (Common Platform Enumeration) for which platforms apply
Ready-made profiles come from scap-security-guide (SSG), with RHEL/Ubuntu/Debian and CIS / STIG / PCI / HIPAA mappings.
# install
dnf install scap-security-guide openscap-scanner
# list of profiles
oscap info /usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
# audit against CIS L1 Server
oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis_server_l1 \
--report /tmp/cis-report.html \
--results /tmp/cis-results.xml \
/usr/share/xml/scap/ssg/content/ssg-rhel9-ds.xml
The artifact is an HTML report with pass/fail for each rule, plus XML with machine-readable results for a compliance system.
Auto-remediate (with caution)
OpenSCAP can generate a remediation script:
oscap xccdf generate fix \
--profile xccdf_org.ssgproject.content_profile_cis_server_l1 \
--output remediate.sh \
/tmp/cis-results.xml
Never run it blindly in production. Read the script with your own eyes:
- It may regenerate SSH keys and break your current access
- It may tighten sudo and stop automation accounts from working
- It may enable SELinux, and applications that are not ready will fall over
The approach: stage it (dev to staging to prod), exclude inappropriate rules, document the exceptions.
ansible-lockdown, Ansible playbooks
Ready-made Ansible roles for CIS/STIG across different distros:
ansible-lockdown/RHEL9-CIS
ansible-lockdown/UBUNTU22-CIS
ansible-lockdown/RHEL9-STIG
Each rule is a separate task with a tag (rule_1.1.1.1). You can
enable or disable them through pre-tasks:
vars:
rhel9cis_rule_1_1_1_1: true # cramfs disable
rhel9cis_rule_1_1_1_2: false # squashfs, keep it
rhel9cis_firewall: nftables # firewall choice
This is a production-ready way to apply CIS: versioned, idempotent, testable. Combine it with GitOps.
What you must check (short list)
If you do not have time for the full CIS, the minimum is:
- SSH: keys only, no root login (see ssh-hardening)
- firewall: default-deny, allowlist (see firewalld-vs-nftables)
- sudo: not NOPASSWD by default, audited through [[auditd|auditd]]
- PAM:
pam_faillock(which replaced the deprecatedpam_tally2),pam_pwquality(see pam) - Mount options:
nodev/nosuid/noexecon/tmpand/dev/shm - Sysctl:
kernel.kptr_restrict=1,kernel.dmesg_restrict=1,net.ipv4.conf.all.rp_filter=1 - Kernel modules: disable unused ones (
cramfs,freevxfs,jffs2,udf) - Auditd rules: execve, identity changes, file changes
in
/etc - Updates: unattended-upgrades at least for security
- Logging: centralized syslog, retention of 90 or more days
Container / Kubernetes hardening
CIS publishes separate Benchmarks for:
- Docker: daemon config, image build, runtime
- Kubernetes: control plane, node, RBAC, network
- EKS / GKE / AKS: cloud-specific
Tools:
docker-bench-security, a bash script for CIS Dockerkube-bench(Aqua), a CIS Kubernetes audit. It runs as a Job in the cluster and produces a reportkube-hunter, a penetration-style scanner for k8s
kubectl apply -f https://raw.githubusercontent.com/aquasecurity/kube-bench/main/job.yaml
kubectl logs -l job-name=kube-bench
Drift and continuous compliance
A one-time hardening does not work. Within a month someone will disable a rule, add a SUID binary, or weaken sudoers. The fix is continuous compliance:
- Cron
lynis --cronjoboroscapdaily, alert when the score drops below a threshold - A compliance platform (Wazuh, Tenable, Rapid7) centralizes it
- GitOps infrastructure, changes only through a PR
When things go wrong
oscapfails with a timeout: a slow disk, or the OVAL content includes a full-fs scan. You can exclude sections with--remediate.- Lynis reports a Hardening Index of 30: this is subjective. Do not
panic, read through the specific
Suggestionentries. - SSH does not work after applying a CIS role: it is typically disabled by ip-list. Use an out-of-band console (IPMI/serial) and roll back through ansible.
- Auto-remediate broke
cron: a common case wherenodev,noexecon/tmpbreaks scripts that write there. Exclude withansible skip_tags. - A compliance scan does not see a rule as fixed: the change happened in
runtime, but the
/etc/...config was not updated. The scan reads the file, not runtime state. - CIS requires
Ensure /tmp is on separate partition: on a cloud instance with a single disk this is painful. The alternative is a bind-mount in the systemdtmp.mount. - STIG contradicts the application docs: a common case with proprietary software. Document the exception and put in a compensating control.
What not to do
- Do not run remediation blindly in production: always read the script first
- Do not chase 100% CIS: some rules do not apply, and a documented exception beats forced compliance
- Do not harden just once: it degrades over months without monitoring
- Do not confuse audit and remediate: they are two different operations, and you can audit without modifying