Version note
This article was written for NKP 2.17. The current release is 2.18. See the Upgrade to NKP 2.18 series for the latest guidance.
NKP Node Security: CIS Benchmark Audit on Rocky Linux 9.7
Note
This article is a living document. The initial audit is complete, but we will continue adding checks, deeper analysis, and remediation guides as we test them in our lab environment.
A complete CIS Rocky Linux 9 benchmark audit (Level 1 and Level 2) of a live NKP 2.17 management cluster node. We ran every check against the actual node, show the real commands and output, and explain what Nutanix pre-hardens out of the box versus what requires Kubernetes exceptions.
Executive Summary
Audit performed on a default NKP 2.17.1 management cluster worker node running Rocky Linux 9.7. No post-deployment hardening was applied. All results reflect the out-of-the-box NKP node image.
We ran two parallel assessments: a manual deep-dive covering every CIS section with real commands, and an automated OpenSCAP scan using the official scap-security-guide CIS profiles.
OpenSCAP Automated Scan Results
| Profile | Pass | Fail | N/A | Total | Score |
|---|---|---|---|---|---|
| CIS Level 1 Server | 218 | 42 | 30 | 290 | 84% |
| CIS Level 2 Server | 284 | 80 | 31 | 395 | 78% |
Note
OpenSCAP evaluates each sub-control individually (e.g., 6 separate xattr audit rules, 5 separate kernel module rules). This is stricter than a grouped manual assessment. Many "fails" are SSH settings where NKP deliberately uses different values for CAPI/CAPX cluster management, and audit rules that use combined syscall notation instead of individual rule-per-syscall format that oscap expects.
Manual Audit Summary
CIS Level 1 pass rate: 46/58 (79%) | CIS Level 2 pass rate: 54/76 (71%) | Adjusted (excluding K8s exceptions): 54/70 (77%)
Master Checklist
| # | Control | Level | Status |
|---|---|---|---|
| 1.1.1 | Disable unused filesystems (cramfs, hfs, jffs2, udf, squashfs) | L1 | WARN |
| 1.1.1 | Disable USB storage | L2 | WARN |
| 1.1.2 | /tmp separate mount + nodev, nosuid, noexec | L1 | PASS |
| 1.1.3 | /dev/shm nodev, nosuid, noexec | L1 | PASS |
| 1.1.4 | /var separate partition | L2 | FAIL |
| 1.1.5 | /var/tmp separate partition | L2 | FAIL |
| 1.1.6 | /var/log separate partition | L2 | FAIL |
| 1.1.7 | /var/log/audit separate partition | L2 | FAIL |
| 1.1.8 | /home separate partition | L2 | FAIL |
| 1.2 | GPG check enabled | L1 | PASS |
| 1.3 | AIDE installed | L1 | PASS |
| 1.3 | AIDE periodic check enabled | L1 | FAIL |
| 1.4 | GRUB bootloader password | L1 | FAIL |
| 1.5.1 | ASLR enabled | L1 | PASS |
| 1.5.2 | Ptrace scope restricted | L1 | PASS |
| 1.5.3 | Core dumps disabled | L1 | PASS |
| 1.5.4 | suid_dumpable = 0 | L1 | FAIL |
| 1.6 | SELinux enforcing | L1 | PASS |
| 1.7 | Crypto policy hardened | L1 | PASS |
| 1.8 | Warning banners (motd, issue, issue.net) | L1 | PASS |
| 2.1 | Time synchronization (chronyd) | L1 | PASS |
| 2.2 | Unnecessary services removed | L1 | WARN |
| 2.3 | Unnecessary clients removed | L1 | PASS |
| 2.4 | GDM/GUI not installed | L1 | PASS |
| 2.5 | Autofs disabled | L1 | PASS |
| 3.1.1 | IP forwarding | L1 | K8s N/A |
| 3.1.2 | Packet redirects disabled | L1 | PASS |
| 3.1.3 | Wireless disabled | L1 | PASS |
| 3.1.4 | Bluetooth disabled | L1 | PASS |
| 3.2.1 | Uncommon protocols disabled (dccp, tipc, rds, sctp) | L2 | WARN |
| 3.3.1 | Source routed packets rejected | L1 | PASS |
| 3.3.2 | ICMP redirects rejected | L1 | PASS |
| 3.3.3 | Secure ICMP redirects disabled | L1 | PASS |
| 3.3.4 | Suspicious packets logged | L1 | PASS |
| 3.3.5 | ICMP broadcasts ignored | L1 | PASS |
| 3.3.6 | Bogus ICMP responses ignored | L1 | PASS |
| 3.3.7 | Reverse path filtering | L1 | K8s N/A |
| 3.3.8 | TCP SYN cookies enabled | L1 | PASS |
| 3.3.9 | IPv6 router advertisements disabled | L1 | PASS |
| 3.3 | Firewall active | L1 | K8s N/A |
| 4.1 | Cron restricted | L1 | PASS |
| 4.2.1 | SSH PermitRootLogin | L1 | WARN |
| 4.2.2 | SSH MaxAuthTries | L1 | WARN |
| 4.2.3 | SSH PermitEmptyPasswords | L1 | PASS |
| 4.2.4 | SSH PermitUserEnvironment | L1 | PASS |
| 4.2.5 | SSH IgnoreRhosts | L1 | PASS |
| 4.2.6 | SSH X11Forwarding disabled | L1 | PASS |
| 4.2.7 | SSH AllowTcpForwarding disabled | L2 | FAIL |
| 4.2.8 | SSH AllowAgentForwarding disabled | L2 | FAIL |
| 4.2.9 | SSH ClientAliveInterval set | L1 | FAIL |
| 4.2.10 | SSH Banner configured | L1 | FAIL |
| 4.2.11 | SSH UsePAM enabled | L1 | FAIL |
| 4.2.12 | SSH strong ciphers only | L1 | PASS |
| 4.2.13 | SSH strong MACs only | L1 | WARN |
| 4.2.14 | SSH strong KEX algorithms | L1 | PASS |
| 4.3.1 | Password quality (pwquality) | L1 | PASS |
| 4.3.2 | Account lockout (faillock) | L1 | PASS |
| 4.3.3 | Password hashing SHA512 | L1 | PASS |
| 4.3.4 | Password history enforced | L1 | PASS |
| 4.4.1 | Password aging configured | L1 | PASS |
| 4.4.2 | Default umask 027 | L1 | PASS |
| 4.4.3 | Root account locked | L1 | PASS |
| 4.4.4 | Shell idle timeout (TMOUT) | L1 | PASS |
| 4.5.1 | Sudo use_pty | L1 | PASS |
| 4.5.2 | Sudo logfile | L1 | PASS |
| 5.1.1 | rsyslog active | L1 | PASS |
| 5.1.2 | journald persistent + compressed | L1 | PASS |
| 5.1.3 | Remote log forwarding | L2 | FAIL |
| 5.1.4 | rsyslog FileCreateMode 0640 | L1 | PASS |
| 5.2.1 | Auditd active + enabled | L2 | PASS |
| 5.2.2 | Audit enabled at boot (GRUB) | L2 | PASS |
| 5.2.3 | Audit rules comprehensive (64+ rules) | L2 | PASS |
| 5.2.4 | Audit rules immutable (-e 2) | L2 | PASS |
| 5.2.5 | Audit log storage + alerting | L2 | PASS |
| 6.1 | Critical file permissions | L1 | PASS |
| 6.2 | No empty passwords, no duplicate IDs | L1 | PASS |
| 6.3 | No world-writable files (host FS) | L1 | K8s N/A |
| 6.4 | No unowned/ungrouped files (host FS) | L1 | K8s N/A |
| 6.5 | SUID/SGID binaries audited | L1 | PASS |
| 6.6 | Kernel dmesg_restrict | L1 | FAIL |
| 6.7 | Kernel kptr_restrict = 2 | L2 | FAIL |
| 6.8 | Kernel perf_event_paranoid = 3 | L2 | FAIL |
| 6.9 | Kernel unprivileged_bpf_disabled | L1 | PASS |
| 6.10 | FIPS mode enabled | L2 | FAIL |
Test Environment
| Component | Version |
|---|---|
| NKP | 2.17.1 |
| OS | Rocky Linux 9.7 (Blue Onyx) |
| Kernel | 5.14.0-611.36.1.el9_7.x86_64 |
| Platform | Nutanix AHV (KVM) |
| SELinux | Enforcing (targeted) |
| Node Role | Management cluster worker |
The node was deployed using the standard NKP pre-built Rocky 9.7 OS image from the Nutanix Portal. No additional hardening was applied post-deployment. Everything in this article reflects the default NKP node image.
Section 1: Initial Setup
1.1 Filesystem Configuration
Unused kernel modules (cramfs, hfs, jffs2, udf, squashfs, freevxfs) are not loaded but are not explicitly blacklisted via /etc/modprobe.d/. The fat module is loaded (required by the EFI boot partition).
# Check if a module is loaded and disabled
for fs in cramfs freevxfs hfs hfsplus jffs2 squashfs udf; do
lsmod | grep "^$fs " && echo "LOADED" || echo "not loaded"
modprobe -n -v $fs 2>/dev/null
done
USB storage L2: The usb-storage module is not loaded but is not blacklisted in /etc/modprobe.d/. On a VM running on Nutanix AHV, USB passthrough is not enabled by default, making this a low risk finding.
Temporary filesystem mounts are properly configured:
| Mount Point | Separate | nodev | nosuid | noexec | Status |
|---|---|---|---|---|---|
| /tmp | Yes | Yes | Yes | Yes | PASS |
| /dev/shm | Yes | Yes | Yes | Yes | PASS |
| /var | No | N/A | N/A | N/A | FAIL L2 |
| /var/tmp | No | N/A | N/A | N/A | FAIL L2 |
| /var/log | No | N/A | N/A | N/A | FAIL L2 |
| /var/log/audit | No | N/A | N/A | N/A | FAIL L2 |
| /home | No | N/A | N/A | N/A | FAIL L2 |
Note
The NKP node image uses a single root disk. Separate partitions for /var, /var/log, /var/log/audit, and /home are not created. This is standard for cloud/VM images where the disk is provisioned as a single volume. For environments requiring strict CIS L2 compliance, custom images built with Nutanix Image Builder (NIB) can include separate partitions.
1.2 Package Manager
grep "^gpgcheck" /etc/dnf/dnf.conf
gpgcheck=1
GPG checking is enabled. PASS
1.3 Filesystem Integrity (AIDE)
rpm -q aide
systemctl is-enabled aidecheck.timer
aide-0.16-105.el9.x86_64
disabled
AIDE is installed PASS but the periodic check timer is not enabled FAIL.
Tip
To enable AIDE periodic checks: sudo systemctl enable --now aidecheck.timer. On NKP nodes, be aware that Kubernetes workloads create and remove files constantly under /var/lib/containerd/ and /var/lib/kubelet/, so your AIDE config should exclude those paths to avoid alert fatigue.
1.4 Bootloader Security
GRUB bootloader password is not set FAIL. This is typical for VM-based deployments where physical console access is already controlled at the hypervisor level (Nutanix AHV console access requires Prism authentication).
1.5 Process Hardening
sysctl kernel.randomize_va_space kernel.yama.ptrace_scope fs.suid_dumpable
cat /etc/systemd/coredump.conf | grep -E "^(Storage|ProcessSizeMax)"
kernel.randomize_va_space = 2
kernel.yama.ptrace_scope = 1
fs.suid_dumpable = 2
Storage=none
ProcessSizeMax=0
| Control | Level | Value | Expected | Status |
|---|---|---|---|---|
| ASLR (randomize_va_space) | L1 | 2 | 2 | PASS |
| Ptrace scope | L1 | 1 | 1 | PASS |
| Core dumps (systemd) | L1 | Storage=none | none | PASS |
| suid_dumpable | L1 | 2 | 0 | FAIL |
1.6 SELinux
getenforce
grep "^SELINUX=" /etc/selinux/config
Enforcing
SELINUX=enforcing
SELINUXTYPE=targeted
SELinux is in enforcing mode with the targeted policy PASS. This is significant: many Kubernetes distributions disable SELinux entirely because of compatibility issues. NKP keeps it enforcing.
The troubleshooting packages setroubleshoot and mcstrans are both absent, which is correct.
1.7 Crypto Policy
update-crypto-policies --show
DEFAULT:NO-SSHWEAKCIPHERS:NO-SSHWEAKMACS
The system-wide crypto policy is hardened beyond the default PASS. The NO-SSHWEAKCIPHERS and NO-SSHWEAKMACS sub-policies remove weak SSH algorithms system-wide.
CIS v2.0.0 control 1.6.2 requires that sshd does not override the crypto policy with explicit directives. We verified no crypto overrides exist in /etc/ssh/sshd_config or /etc/ssh/sshd_config.d/ PASS. The SSH backend config is managed entirely through the crypto policy:
cat /etc/crypto-policies/back-ends/opensshserver.config
Ciphers aes256-gcm@openssh.com,aes256-ctr,aes128-gcm@openssh.com,aes128-ctr
MACs hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha1,hmac-sha2-512
KexAlgorithms curve25519-sha256,curve25519-sha256@libssh.org,...
RequiredRSASize 2048
Note
The crypto policy still includes hmac-sha1 in the MAC list. CIS v2.0.0 controls 1.6.4-1.6.7 recommend stricter sub-policies that also disable CBC ciphers, chacha20-poly1305, and EtM MAC variants for SSH. Implementing the full CIS crypto policy module would address these, but requires testing SSH connectivity after the change.
1.8 Warning Banners
cat /etc/motd
cat /etc/issue
cat /etc/issue.net
All three files contain:
Authorized uses only. All activity may be monitored and reported.
Login banners are properly configured with a legal warning PASS. Note that the SSH Banner directive is not set in sshd_config (covered in Section 4.2).
Section 2: Services
2.1 Time Synchronization
systemctl is-active chronyd
chronyc sources
active
^* vps-rtm1.orleans.ddnss.de 2 10 377 332 -950us[-966us] +/- 8485us
Chronyd is active and synchronized PASS. Four NTP pool sources are configured in /etc/chrony.conf. System clock shows synchronized via timedatectl.
2.2 Special Purpose Services
We checked for 14 unnecessary services (avahi, cups, dhcpd, slapd, nfs-server, named, vsftpd, httpd, dovecot, smb, squid, net-snmp, ypserv, telnet-server, tftp-server). Only one issue:
| Service | Status |
|---|---|
| rpcbind | WARN Installed (rpcbind-1.2.6-7.el9) |
| All others | PASS Not installed |
rpcbind is present but is a dependency of some NFS-related packages. It is not actively running as a service.
2.3 Service Clients
All unnecessary client packages are absent: telnet, openldap-clients, ftp, ypbind. PASS
2.4 GDM / GUI
rpm -q gdm
systemctl get-default
package gdm is not installed
multi-user.target
No graphical environment installed. Default target is multi-user.target. PASS
2.5 Autofs
Autofs is not installed and not enabled. PASS
Section 3: Network Configuration
3.1 Host Network Parameters
sysctl net.ipv4.ip_forward
sysctl net.ipv4.conf.all.send_redirects
sysctl net.ipv4.conf.default.send_redirects
net.ipv4.ip_forward = 1
net.ipv4.conf.all.send_redirects = 0
net.ipv4.conf.default.send_redirects = 0
| Control | Level | Value | Status |
|---|---|---|---|
| IP forwarding enabled | L1 | 1 (on) | K8s N/A Required for pod networking |
| IPv6 forwarding enabled | L1 | 1 (on) | K8s N/A Required for dual-stack |
| Packet redirects disabled | L1 | 0 (off) | PASS |
| Wireless interfaces | L1 | None present | PASS |
3.2 Network Hardening Parameters
Every network hardening sysctl is properly configured:
| Parameter | Level | Value | Status |
|---|---|---|---|
| accept_source_route (all, IPv4+IPv6) | L1 | 0 | PASS |
| accept_redirects (all, IPv4+IPv6) | L1 | 0 | PASS |
| secure_redirects (all) | L1 | 0 | PASS |
| log_martians (all) | L1 | 1 | PASS |
| icmp_echo_ignore_broadcasts | L1 | 1 | PASS |
| icmp_ignore_bogus_error_responses | L1 | 1 | PASS |
| tcp_syncookies | L1 | 1 | PASS |
| accept_ra IPv6 (all) | L1 | 0 | PASS |
| rp_filter (default) | L1 | 1 | PASS |
| rp_filter (all) | L1 | 0 | K8s N/A |
Note
net.ipv4.conf.all.rp_filter = 0 is intentionally set for Cilium CNI. Cilium uses eBPF for packet forwarding and requires asymmetric routing to work correctly. The NKP sysctl overrides in /etc/sysctl.d/ also disable rp_filter for cilium_* and lxc* interfaces.
3.3 Firewall
systemctl is-active firewalld
systemctl is-active nftables
inactive
inactive
Neither firewalld nor nftables services are active as standalone K8s N/A. This is expected: Cilium manages all network policy enforcement via eBPF and nftables rules. The nftables ruleset shows Cilium and kube-proxy managed chains:
sudo nft list ruleset | head -10
table ip mangle {
chain KUBE-IPTABLES-HINT {
}
chain POSTROUTING {
type filter hook postrouting priority mangle; policy accept;
counter packets 74222 bytes 88691775 jump CILIUM_POST_mangle
}
Section 4: Access, Authentication & Authorization
4.1 Cron
All cron controls pass PASS:
stat -c "%a %U %G" /etc/crontab
ls -ld /etc/cron.{hourly,daily,weekly,monthly,d}
600 root root
drwx------. 2 root root /etc/cron.hourly
drwx------. 2 root root /etc/cron.daily
drwx------. 2 root root /etc/cron.weekly
drwx------. 2 root root /etc/cron.monthly
drwx------. 2 root root /etc/cron.d
/etc/cron.allow and /etc/at.allow exist. /etc/cron.deny and /etc/at.deny are absent.
4.2 SSH Server Configuration
This is where we find the most gaps. Here is the full sshd configuration audit:
sudo sshd -T | grep -iE "^(permit|max|ignore|hostbased|x11|allowtcp|login|client|banner|log|usepam|allowagent|cipher|macs|kex)"
| Parameter | Value | CIS Expected | Level | Status |
|---|---|---|---|---|
| PermitRootLogin | without-password | no | L1 | WARN |
| MaxAuthTries | 6 | 4 | L1 | WARN |
| MaxSessions | 10 | 10 | L1 | PASS |
| PermitEmptyPasswords | no | no | L1 | PASS |
| PermitUserEnvironment | no | no | L1 | PASS |
| IgnoreRhosts | yes | yes | L1 | PASS |
| HostbasedAuthentication | no | no | L1 | PASS |
| X11Forwarding | no | no | L1 | PASS |
| AllowTcpForwarding | yes | no | L2 | FAIL |
| AllowAgentForwarding | yes | no | L2 | FAIL |
| LoginGraceTime | 120 | 60 | L1 | WARN |
| ClientAliveInterval | 0 | 300 | L1 | FAIL |
| ClientAliveCountMax | 3 | 3 | L1 | PASS |
| Banner | none | /etc/issue.net | L1 | FAIL |
| LogLevel | INFO | INFO | L1 | PASS |
| UsePAM | no | yes | L1 | FAIL |
SSH Ciphers PASS (all strong):
chacha20-poly1305@openssh.com
aes128-ctr, aes192-ctr, aes256-ctr
aes128-gcm@openssh.com, aes256-gcm@openssh.com
SSH MACs WARN include some weak entries:
hmac-sha1-etm@openssh.com ← weak
hmac-sha1 ← weak
umac-64-etm@openssh.com ← weak (short tag)
umac-64@openssh.com ← weak (short tag)
Important
Despite the system-wide crypto policy NO-SSHWEAKMACS, these MACs still appear in the sshd runtime config. This happens when sshd_config explicitly overrides the crypto policy with a MACs directive. Check /etc/ssh/sshd_config and /etc/ssh/sshd_config.d/ for explicit MAC settings that bypass the policy.
SSH Key Exchange Algorithms PASS: curve25519-sha256, ECDH (P-256/P-384/P-521), and DH group16/18.
Why PermitRootLogin is without-password: NKP needs root-level access via SSH keys during cluster lifecycle operations (CAPI/CAPX). The without-password setting allows key-based root login while blocking password authentication. This is a deliberate NKP design choice.
4.3 PAM and Password Quality
NKP uses a custom authselect profile:
authselect current
Profile ID: custom/nkp-sssd-profile
Enabled features:
- with-faillock
- with-pwhistory
- without-nullok
This profile enables faillock, password history, and disables null password authentication. Password quality enforcement is excellent:
cat /etc/security/pwquality.conf | grep -v "^#" | grep -v "^$"
difok = 2
maxrepeat = 3
enforce_for_root
minlen = 14
minclass = 4
maxsequence = 3
| Control | Value | CIS | Status |
|---|---|---|---|
| Minimum length | 14 | 14 | PASS |
| Character classes required | 4 | 4 | PASS |
| Max repeated chars | 3 | 3 | PASS |
| Max sequential chars | 3 | 3 | PASS |
| Characters different from old | 2 | 2 | PASS |
| Enforce for root | yes | yes | PASS |
Account lockout (faillock):
cat /etc/security/faillock.conf | grep -v "^#" | grep -v "^$"
deny = 5
unlock_time = 900
5 failed attempts triggers a 15-minute lockout PASS. PAM is configured with pam_faillock.so in both password-auth and system-auth using preauth + authfail flow.
Password hashing: SHA512 in both PAM (pam_unix.so sha512) and /etc/login.defs (ENCRYPT_METHOD SHA512) PASS.
Password history: pam_pwhistory.so is configured with use_authtok, preventing password reuse PASS.
4.4 User Accounts and Environment
grep -E "^(PASS_MAX|PASS_MIN|PASS_WARN|UMASK|ENCRYPT)" /etc/login.defs
UMASK 027
PASS_MAX_DAYS 365
PASS_MIN_DAYS 7
PASS_WARN_AGE 7
ENCRYPT_METHOD SHA512
| Control | Value | Status |
|---|---|---|
| Max password age | 365 days | PASS |
| Min password age | 7 days | PASS |
| Warning before expiry | 7 days | PASS |
| Default umask | 027 | PASS |
| Root account | Locked (LK) | PASS |
| UID 0 accounts | root only | PASS |
Shell idle timeout (TMOUT):
cat /etc/profile.d/tmout.sh
TMOUT=600
export TMOUT
readonly TMOUT
The shell timeout is set to 600 seconds (10 minutes), exported, and marked readonly so users cannot override it PASS.
Three system accounts have interactive shells (sync, shutdown, halt). These are standard RHEL defaults with restricted functionality.
4.5 Sudo Configuration
sudo grep -E "^Defaults" /etc/sudoers
Defaults !visiblepw
Defaults always_set_home
Defaults env_reset
Defaults secure_path = /sbin:/bin:/usr/sbin:/usr/bin
Defaults use_pty
Defaults logfile="/var/log/sudo.log"
Defaults timestamp_timeout=15
| Control | Status | Detail |
|---|---|---|
| use_pty | PASS | Sudo commands run in pseudo-terminal |
| Sudo logging | PASS | Logs to /var/log/sudo.log |
| secure_path | PASS | Restricted PATH for sudo |
| env_reset | PASS | Environment sanitized |
The konvoy user has NOPASSWD: ALL sudo access. This is required by NKP for cluster lifecycle operations (scaling, upgrades, node management via CAPI).
Section 5: Logging & Auditing
5.1 System Logging
| Service | Status |
|---|---|
| rsyslog | PASS Active, enabled |
| systemd-journald | PASS Storage=persistent, Compress=yes |
Both rsyslog and journald are running. Journal is configured for persistent storage with compression.
Remote log forwarding L2:
rpm -q systemd-journal-remote
grep "ForwardToSyslog" /etc/systemd/journald.conf
package systemd-journal-remote is not installed
ForwardToSyslog=no
No remote log forwarding is configured FAIL. For CIS Level 2, logs should be forwarded to a central log server. In production NKP environments, this is typically handled by deploying a log collector (Fluentd, Vector, or Promtail) as a DaemonSet.
5.2 Auditd
systemctl is-active auditd
cat /proc/cmdline | grep -o "audit[^ ]*"
sudo auditctl -l | wc -l
active
audit=1
audit_backlog_limit=8192
64
Auditd is active and enabled at boot via GRUB kernel parameters PASS. The system has 64 audit rules PASS covering:
Full audit rules breakdown (click to expand)
| Category | Rules | What's Monitored |
|---|---|---|
| Sudo/Privilege | 3 | /etc/sudoers, sudoers.d, sudo.log |
| User Emulation | 2 | execve with uid!=euid (privilege escalation) |
| Time Changes | 3 | adjtimex, settimeofday, clock_settime, /etc/localtime |
| System Locale | 5 | hostname, domainname, /etc/issue, /etc/hosts, network config |
| Privileged Commands | 17 | chage, crontab, gpasswd, mount, passwd, pkexec, su, sudo, unix_chkpwd, etc. |
| File Access | 4 | open/truncate/creat with EACCES/EPERM (unauthorized access attempts) |
| Identity | 5 | /etc/passwd, shadow, group, gshadow, opasswd |
| Permission Changes | 6 | chmod, chown, setxattr, removexattr |
| Mounts | 2 | mount syscall tracking |
| Session | 3 | utmp, wtmp, btmp |
| Logins | 2 | lastlog, faillock |
| File Deletion | 2 | rename, unlink, unlinkat |
| SELinux/MAC | 2 | /etc/selinux, /usr/share/selinux |
| Permission Tools | 2 | chcon, setfacl |
Audit rules immutability L2:
sudo auditctl -s | grep enabled
sudo tail -3 /etc/audit/rules.d/99_auditd.rules
enabled 2
-e 2
The -e 2 flag IS set PASS, making audit rules immutable after loading. Any changes to rules require a system reboot.
Auditd.conf storage and alerting settings:
grep -E "^(max_log_file_action|space_left_action|admin_space_left_action|action_mail_acct|disk_full_action)" /etc/audit/auditd.conf
max_log_file_action = keep_logs
space_left_action = email
action_mail_acct = root
admin_space_left_action = halt
disk_full_action = SUSPEND
Audit logs are kept (not automatically deleted) PASS. Low disk space triggers email to root, critically low space halts the system, and full disk suspends auditing. This is a strong configuration.
5.3 Log Rotation
daily
rotate 4
Logs are rotated daily with 4 days of retention. This is minimal but acceptable for nodes where centralized log shipping is expected.
Section 6: System Maintenance
6.1 Critical File Permissions
| File | Permissions | Owner | Status |
|---|---|---|---|
| /etc/passwd | 644 | root:root | PASS |
| /etc/shadow | 000 | root:root | PASS |
| /etc/group | 644 | root:root | PASS |
| /etc/gshadow | 000 | root:root | PASS |
| /etc/passwd- | 644 | root:root | PASS |
| /etc/shadow- | 000 | root:root | PASS |
Shadow files with permission 000 is stricter than the CIS requirement of 640.
6.2 User and Group Integrity
All checks pass PASS: no empty passwords, no duplicate UIDs/GIDs, no duplicate user/group names. Only root has UID 0.
6.3 World-Writable Files
sudo find / -xdev -type f -perm -0002 -not -path "/proc/*" -not -path "/sys/*" 2>/dev/null
Found world-writable files in two locations:
/usr/local/bin/containerdand/usr/local/bin/ctr(containerd binaries)- Various files under
/var/lib/kubelet/pods/(ephemeral pod containers)
These are all Kubernetes runtime artifacts K8s N/A. No world-writable files exist on the base OS filesystem.
6.4 Unowned / Ungrouped Files
All unowned and ungrouped files are located within /var/lib/containerd/ snapshots (container image layers with non-host UIDs). No issues on the base OS filesystem K8s N/A.
6.5 SUID/SGID Binaries
The host filesystem has the standard minimal set of SUID binaries PASS:
/usr/bin/chage, crontab, gpasswd, mount, newgrp, passwd, pkexec, su, sudo, umount
/usr/lib/polkit-1/polkit-agent-helper-1
/usr/sbin/grub2-set-bootflag, pam_timestamp_check, unix_chkpwd, mount.nfs
All of these are expected for a RHEL-based system. Additional SUID/SGID binaries found inside containerd snapshots are isolated to container image layers.
Kernel Hardening
sysctl kernel.dmesg_restrict kernel.kptr_restrict kernel.perf_event_paranoid kernel.unprivileged_bpf_disabled
fips-mode-setup --check
kernel.dmesg_restrict = 0
kernel.kptr_restrict = 1
kernel.perf_event_paranoid = 2
kernel.unprivileged_bpf_disabled = 1
FIPS mode is disabled.
| Control | Value | CIS Expected | Level | Status |
|---|---|---|---|---|
| dmesg_restrict | 0 | 1 | L1 | FAIL |
| kptr_restrict | 1 | 2 | L2 | FAIL |
| perf_event_paranoid | 2 | 3 | L2 | FAIL |
| unprivileged_bpf_disabled | 1 | 1 | L1 | PASS |
| FIPS mode | disabled | enabled | L2 | FAIL |
Note
kptr_restrict = 1 hides kernel pointers from unprivileged users (passes L1). CIS L2 recommends value 2 which hides them from all users including root. Similarly, perf_event_paranoid = 2 restricts perf to root (passes L1), while L2 wants 3 to fully disable it. FIPS mode requires configuration during image build and cannot be enabled post-install without potential system instability.
OpenSCAP Automated Scan
We also ran the official OpenSCAP scanner with scap-security-guide CIS profiles to validate our manual findings:
sudo dnf install -y openscap-scanner scap-security-guide
sudo oscap xccdf eval \
--profile xccdf_org.ssgproject.content_profile_cis_server_l1 \
--report /tmp/cis-l1-report.html \
/usr/share/xml/scap/ssg/content/ssg-rl9-ds.xml
OpenSCAP Results
| Profile | Total Rules | Pass | Fail | N/A | Pass Rate |
|---|---|---|---|---|---|
| CIS L1 Server | 290 | 218 | 42 | 30 | 84% |
| CIS L2 Server | 395 | 284 | 80 | 31 | 78% |
Where OpenSCAP Differs from Our Manual Audit
OpenSCAP evaluates each sub-control as an individual rule. For example, our single "audit DAC modifications" check becomes 12 separate rules (chmod, chown, fchmod, fchmodat, fchown, fchownat, setxattr, lsetxattr, fsetxattr, removexattr, lremovexattr, fremovexattr). Our audit rules use combined syscall notation (-S chmod,fchmod,fchmodat) which is functionally equivalent but does not match oscap's per-syscall regex.
Notable OpenSCAP "fails" that are actually working correctly on the node:
| OpenSCAP Rule | Why It "Fails" | Actual Status |
|---|---|---|
| sshd_disable_empty_passwords, disable_host_auth, disable_rhosts, do_not_permit_user_env | sshd uses system crypto policy (1.6.2 PASS), not explicit config lines. OpenSCAP greps for literal directives. | Working as intended |
| sshd_set_keepalive (ClientAliveCountMax) | OpenSCAP wants the explicit directive in config, but the default value (3) already matches | Functionally compliant |
| audit_rules_kernel_module_loading_* (5 rules) | Rules exist using combined -S init_module,finit_module,delete_module,create_module,query_module but oscap wants individual rules |
Functionally equivalent |
| audit_rules_dac_modification_*xattr (6 rules) | Rules use combined -S setxattr,lsetxattr,fsetxattr,removexattr,lremovexattr,fremovexattr |
Functionally equivalent |
| grub2_audit_argument, grub2_audit_backlog_limit | OpenSCAP checks /etc/default/grub, but audit params are in /proc/cmdline and working |
audit=1 confirmed active |
Tip
Some OpenSCAP failures are "format mismatches" rather than security gaps. The scanner checks for specific config file entries, while NKP configures the same settings through different mechanisms (crypto policies, combined audit rules, cloud-init). Review the HTML report and cross-reference with the manual findings above to distinguish real gaps from false positives.
Overall Score
Manual Audit by CIS Level
| CIS Level | Total Controls | Pass | Warn | Fail | K8s N/A |
|---|---|---|---|---|---|
| Level 1 | 58 | 46 | 5 | 7 | 4 |
| Level 2 (additional) | 19 | 9 | 1 | 7 | 2 |
| Combined | 77 | 55 | 6 | 14 | 6 |
Manual Audit by Section
| CIS Section | Pass | Warn | Fail | K8s N/A |
|---|---|---|---|---|
| 1. Initial Setup | 9 | 1 | 7 | 0 |
| 2. Services | 4 | 1 | 0 | 0 |
| 3. Network | 9 | 0 | 0 | 3 |
| 4. Access/Auth | 20 | 3 | 5 | 0 |
| 5. Logging & Auditing | 6 | 0 | 1 | 0 |
| 6. Maintenance + Kernel | 7 | 1 | 1 | 3 |
CIS Level 1 manual: 46/54 (85%) | CIS Level 2 manual: 55/71 (77%) (excluding K8s exceptions)
OpenSCAP CIS L1: 218/260 (84%) | OpenSCAP CIS L2: 284/364 (78%) (excluding N/A)
What NKP Hardens Out of the Box
The NKP Rocky 9.7 node image ships with substantial security hardening that many Kubernetes distributions lack:
- SELinux enforcing with targeted policy (most K8s distros disable this)
- Hardened crypto policy (DEFAULT:NO-SSHWEAKCIPHERS:NO-SSHWEAKMACS)
- 64 audit rules covering privileged commands, file access, identity changes, and MAC policy
- Audit at boot with
audit=1 audit_backlog_limit=8192in kernel parameters - AIDE installed for filesystem integrity monitoring
- Strong password policy: 14 chars, 4 character classes, enforce_for_root
- Account lockout: 5 attempts, 15-minute unlock via pam_faillock
- Login banners on all entry points (motd, issue, issue.net)
- Root account locked, password authentication disabled
- Core dumps disabled via systemd (Storage=none, ProcessSizeMax=0)
- Shell idle timeout (TMOUT=600, readonly, exported)
- Full network hardening: martian logging, ICMP filtering, SYN cookies, redirect blocking
- Sudo logging with use_pty enabled, logfile configured
- SHA512 password hashing with password history (pam_pwhistory)
- Cron/at access restricted via allow files
- /tmp isolated with nodev, nosuid, noexec
What Needs Attention
The items below can theoretically be remediated, but every change must be tested on a non-production cluster first. NKP relies on SSH, sudo, and specific kernel settings for CAPI/CAPX lifecycle operations (scaling, upgrades, node replacement). Blindly applying CIS remediations can break cluster management and leave you with an unrecoverable cluster.
Warning
Do NOT apply these changes directly on production NKP clusters. Some settings (PermitRootLogin, AllowTcpForwarding, UsePAM, sudo NOPASSWD) are deliberate NKP requirements. Changing them will break cluster lifecycle operations. Always build a test cluster, apply changes, then validate that nkp scale, nkp upgrade, and node replacement still work before rolling out to production.
CIS Level 1 Gaps (fixable with testing)
- SSH idle timeout: Set
ClientAliveInterval 300andClientAliveCountMax 3in sshd_config. Low risk, but verify CAPI SSH sessions are not affected by the timeout during long-running operations. - SSH banner: Set
Banner /etc/issue.netin sshd_config. Safe change, the banner file already exists. - AIDE timer:
sudo systemctl enable --now aidecheck.timer. Safe, but configure AIDE exclusions for/var/lib/containerd/and/var/lib/kubelet/to prevent thousands of false positives. - suid_dumpable: Add
fs.suid_dumpable = 0to/etc/sysctl.d/99-hardening.conf. Low risk. - dmesg_restrict: Add
kernel.dmesg_restrict = 1to/etc/sysctl.d/99-hardening.conf. Low risk. - Filesystem module blacklisting: Add blacklist entries for cramfs, hfs, jffs2, udf, squashfs in
/etc/modprobe.d/cis.conf. Low risk on VMs. - Uncommon protocols: Blacklist dccp, tipc, rds, sctp in
/etc/modprobe.d/cis.conf. Low risk, but verify Cilium does not use SCTP if you have SCTP services. - GRUB bootloader password: Can be set but adds operational complexity for VM console access. On Nutanix AHV, console is already protected by Prism authentication.
- SSH LogLevel: Change from INFO to VERBOSE for more detailed logging. Safe change.
CIS Level 2 Additional Gaps (higher risk)
- Separate partitions (/var, /var/tmp, /var/log, /var/log/audit, /home): Requires building a custom OS image with Nutanix Image Builder (NIB). Cannot be changed post-deployment.
- SSH forwarding: Disabling
AllowTcpForwardingandAllowAgentForwardingcan break CAPI pivot operations and kubectl port-forwarding from the node. Test thoroughly. - UsePAM: NKP sets
UsePAM no. Changing toyesmay affect how SSH authentication works for the konvoy user. Test that CAPI can still manage nodes after the change. - PermitRootLogin: NKP requires
without-passwordfor key-based root access during cluster bootstrap and pivot. Setting tonowill break CAPI/CAPX. - Remote log forwarding: Deploy a log collector (Fluentd, Vector, or Promtail) as a DaemonSet rather than modifying node-level rsyslog. This is the Kubernetes-native approach.
- Kernel parameters: Set
kptr_restrict=2,perf_event_paranoid=3. Low risk, but verify monitoring tools (node-exporter, etc.) still function. - FIPS mode: Must be configured at image build time. Cannot be enabled post-install without risk of system instability. Requires FIPS-validated kernel and crypto modules.
- USB storage blacklisting: Add to
/etc/modprobe.d/cis.conf. Low risk on AHV VMs. - sudo NOPASSWD: NKP requires
NOPASSWDfor the konvoy user for automated cluster lifecycle operations. Removing it will break CAPI. - even_deny_root in faillock: Can be enabled but ensure the root account lockout does not interfere with emergency recovery procedures.
Summary
The NKP 2.17 Rocky Linux 9.7 node image arrives well hardened. Both our manual audit and OpenSCAP confirm consistent results: 84-85% on CIS Level 1 and 77-78% on CIS Level 2. SELinux enforcing, 64+ immutable audit rules, hardened crypto policies, and comprehensive PAM configuration are all configured by default. The main Level 1 gaps are SSH settings (idle timeout, banner) and a couple of kernel parameters. Level 2 gaps are primarily separate disk partitions (inherent to cloud images), remote log forwarding, and stricter kernel parameters. Every remediation must be tested on a non-production cluster first, as NKP relies on SSH and sudo for CAPI/CAPX lifecycle management. For a Kubernetes platform, this is a strong security baseline that exceeds what most distributions provide out of the box.


