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

54
Pass
6
Warn
16
Fail
6
K8s N/A

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

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

bash
grep "^gpgcheck" /etc/dnf/dnf.conf
output
gpgcheck=1

GPG checking is enabled. PASS

1.3 Filesystem Integrity (AIDE)

bash
rpm -q aide
systemctl is-enabled aidecheck.timer
output
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

bash
sysctl kernel.randomize_va_space kernel.yama.ptrace_scope fs.suid_dumpable
cat /etc/systemd/coredump.conf | grep -E "^(Storage|ProcessSizeMax)"
output
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

bash
getenforce
grep "^SELINUX=" /etc/selinux/config
output
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

bash
update-crypto-policies --show
output
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:

bash
cat /etc/crypto-policies/back-ends/opensshserver.config
output
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

bash
cat /etc/motd
cat /etc/issue
cat /etc/issue.net

All three files contain:

output
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

bash
systemctl is-active chronyd
chronyc sources
output
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

bash
rpm -q gdm
systemctl get-default
output
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

bash
sysctl net.ipv4.ip_forward
sysctl net.ipv4.conf.all.send_redirects
sysctl net.ipv4.conf.default.send_redirects
output
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

bash
systemctl is-active firewalld
systemctl is-active nftables
output
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:

bash
sudo nft list ruleset | head -10
output
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:

bash
stat -c "%a %U %G" /etc/crontab
ls -ld /etc/cron.{hourly,daily,weekly,monthly,d}
output
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:

bash
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):

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

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

bash
authselect current
output
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:

bash
cat /etc/security/pwquality.conf | grep -v "^#" | grep -v "^$"
output
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):

bash
cat /etc/security/faillock.conf | grep -v "^#" | grep -v "^$"
output
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

bash
grep -E "^(PASS_MAX|PASS_MIN|PASS_WARN|UMASK|ENCRYPT)" /etc/login.defs
output
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):

bash
cat /etc/profile.d/tmout.sh
output
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

bash
sudo grep -E "^Defaults" /etc/sudoers
output
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:

bash
rpm -q systemd-journal-remote
grep "ForwardToSyslog" /etc/systemd/journald.conf
output
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

bash
systemctl is-active auditd
cat /proc/cmdline | grep -o "audit[^ ]*"
sudo auditctl -l | wc -l
output
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:

bash
sudo auditctl -s | grep enabled
sudo tail -3 /etc/audit/rules.d/99_auditd.rules
output
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:

bash
grep -E "^(max_log_file_action|space_left_action|admin_space_left_action|action_mail_acct|disk_full_action)" /etc/audit/auditd.conf
output
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

output
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

bash
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/containerd and /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:

output
/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

bash
sysctl kernel.dmesg_restrict kernel.kptr_restrict kernel.perf_event_paranoid kernel.unprivileged_bpf_disabled
fips-mode-setup --check
output
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:

bash
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=8192 in 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)

  1. SSH idle timeout: Set ClientAliveInterval 300 and ClientAliveCountMax 3 in sshd_config. Low risk, but verify CAPI SSH sessions are not affected by the timeout during long-running operations.
  2. SSH banner: Set Banner /etc/issue.net in sshd_config. Safe change, the banner file already exists.
  3. 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.
  4. suid_dumpable: Add fs.suid_dumpable = 0 to /etc/sysctl.d/99-hardening.conf. Low risk.
  5. dmesg_restrict: Add kernel.dmesg_restrict = 1 to /etc/sysctl.d/99-hardening.conf. Low risk.
  6. Filesystem module blacklisting: Add blacklist entries for cramfs, hfs, jffs2, udf, squashfs in /etc/modprobe.d/cis.conf. Low risk on VMs.
  7. 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.
  8. GRUB bootloader password: Can be set but adds operational complexity for VM console access. On Nutanix AHV, console is already protected by Prism authentication.
  9. SSH LogLevel: Change from INFO to VERBOSE for more detailed logging. Safe change.

CIS Level 2 Additional Gaps (higher risk)

  1. 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.
  2. SSH forwarding: Disabling AllowTcpForwarding and AllowAgentForwarding can break CAPI pivot operations and kubectl port-forwarding from the node. Test thoroughly.
  3. UsePAM: NKP sets UsePAM no. Changing to yes may affect how SSH authentication works for the konvoy user. Test that CAPI can still manage nodes after the change.
  4. PermitRootLogin: NKP requires without-password for key-based root access during cluster bootstrap and pivot. Setting to no will break CAPI/CAPX.
  5. 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.
  6. Kernel parameters: Set kptr_restrict=2, perf_event_paranoid=3. Low risk, but verify monitoring tools (node-exporter, etc.) still function.
  7. 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.
  8. USB storage blacklisting: Add to /etc/modprobe.d/cis.conf. Low risk on AHV VMs.
  9. sudo NOPASSWD: NKP requires NOPASSWD for the konvoy user for automated cluster lifecycle operations. Removing it will break CAPI.
  10. 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.