CKS — Scanning Worker Nodes
Exam Context
| Property | Value |
|---|---|
| Domain | Domain 1 — Cluster Setup |
| Weight | 10% |
| Objective | Use CIS benchmark to review the security configuration of Kubernetes components |
Worker nodes run your actual workloads. A misconfigured kubelet can expose container exec access, leak Pod metadata, or allow unauthorized reads of Secrets. This topic covers every major kube-bench check for worker nodes and the exact remediation steps.
Running the Worker Node Scan
SSH into a worker node and run:
# Full worker node scan
sudo kube-bench run --targets node
Or as a Kubernetes Job:
kubectl apply -f kube-bench-node-job.yaml
kubectl wait --for=condition=complete job/kube-bench-node --timeout=120s
kubectl logs job/kube-bench-node
The scan covers two main sections:
- Section 4.1 — Worker Node Configuration Files
- Section 4.2 — Kubelet
Section 4.1: Worker Node Configuration Files
These checks verify that kubelet-related files have restrictive permissions and proper ownership. File permission issues are among the most common FAIL results on worker nodes.
kubelet Service File
[FAIL] 4.1.1 Ensure that the kubelet service file permissions are set to 600 or more restrictive
[FAIL] 4.1.2 Ensure that the kubelet service file ownership is set to root:root
What it checks: The systemd service file for kubelet.
Audit:
# Find the kubelet service file
systemctl show -p FragmentPath kubelet
# Common location
stat -c '%a %U:%G' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
Expected output (secure):
600 root:root
Remediation:
# Fix permissions
sudo chmod 600 /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
# Fix ownership
sudo chown root:root /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
# Verify
stat -c '%a %U:%G' /etc/systemd/system/kubelet.service.d/10-kubeadm.conf
kubelet.conf (kubeconfig)
[FAIL] 4.1.5 Ensure that the --kubeconfig kubelet.conf file permissions are set to 600 or more restrictive
[FAIL] 4.1.6 Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root
Audit:
stat -c '%a %U:%G' /etc/kubernetes/kubelet.conf
Remediation:
sudo chmod 600 /etc/kubernetes/kubelet.conf
sudo chown root:root /etc/kubernetes/kubelet.conf
kubelet config.yaml
[FAIL] 4.1.9 If the kubelet config.yaml configuration file is being used, validate permissions set to 600 or more restrictive
[FAIL] 4.1.10 If the kubelet config.yaml configuration file is being used, ensure ownership is set to root:root
Audit:
stat -c '%a %U:%G' /var/lib/kubelet/config.yaml
Remediation:
sudo chmod 600 /var/lib/kubelet/config.yaml
sudo chown root:root /var/lib/kubelet/config.yaml
Complete File Permission Remediation Script
For convenience, here’s a script that fixes all common file permission issues on a worker node:
#!/bin/bash
# fix-worker-file-permissions.sh
# Fixes CIS benchmark Section 4.1 file permission issues
echo "Fixing kubelet service file permissions..."
KUBELET_SVC=$(systemctl show -p FragmentPath kubelet | cut -d= -f2)
if [ -f "$KUBELET_SVC" ]; then
sudo chmod 600 "$KUBELET_SVC"
sudo chown root:root "$KUBELET_SVC"
echo "Fixed: $KUBELET_SVC"
fi
# Fix kubeadm drop-in
KUBEADM_DROPIN="/etc/systemd/system/kubelet.service.d/10-kubeadm.conf"
if [ -f "$KUBEADM_DROPIN" ]; then
sudo chmod 600 "$KUBEADM_DROPIN"
sudo chown root:root "$KUBEADM_DROPIN"
echo "Fixed: $KUBEADM_DROPIN"
fi
echo "Fixing kubelet.conf permissions..."
if [ -f /etc/kubernetes/kubelet.conf ]; then
sudo chmod 600 /etc/kubernetes/kubelet.conf
sudo chown root:root /etc/kubernetes/kubelet.conf
echo "Fixed: /etc/kubernetes/kubelet.conf"
fi
echo "Fixing kubelet config.yaml permissions..."
if [ -f /var/lib/kubelet/config.yaml ]; then
sudo chmod 600 /var/lib/kubelet/config.yaml
sudo chown root:root /var/lib/kubelet/config.yaml
echo "Fixed: /var/lib/kubelet/config.yaml"
fi
echo "Fixing PKI directory permissions..."
sudo chmod -R 600 /etc/kubernetes/pki/*.crt 2>/dev/null
sudo chmod -R 600 /etc/kubernetes/pki/*.key 2>/dev/null
sudo chown -R root:root /etc/kubernetes/pki/ 2>/dev/null
echo "Fixed: /etc/kubernetes/pki/"
echo "All file permission fixes applied."
echo "Run 'sudo kube-bench run --group 4.1' to verify."
Section 4.2: Kubelet Configuration
Kubelet configuration checks are the most security-critical worker node checks. The kubelet is the agent that runs containers — misconfigured kubelet settings directly expose workloads.
Anonymous Authentication
[FAIL] 4.2.1 Ensure that the --anonymous-auth argument is set to false
What it checks: Whether the kubelet API accepts unauthenticated requests.
Why it matters: The kubelet API (port 10250) provides endpoints for:
- Reading Pod status and logs
- Executing commands inside containers (
/exec) - Port-forwarding to containers
With anonymous auth enabled, anyone who can reach port 10250 can do all of the above without authentication.
Audit:
# Check running process
ps -ef | grep kubelet | grep -o '\-\-anonymous-auth=[a-z]*'
# Check config file
grep -A3 'authentication:' /var/lib/kubelet/config.yaml
Remediation in /var/lib/kubelet/config.yaml:
authentication:
anonymous:
enabled: false
webhook:
enabled: true
cacheTTL: 0s
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
Restart kubelet:
sudo systemctl restart kubelet
sudo systemctl status kubelet
Verify:
sudo kube-bench run --check 4.2.1
Webhook Authentication
[FAIL] 4.2.2 Ensure that the --authorization-mode argument is not set to AlwaysAllow
What it checks: Whether the kubelet authorizes API requests through the API server (Webhook mode) rather than allowing everything.
Why it matters: With AlwaysAllow, any authenticated request to the kubelet is approved regardless of RBAC. Webhook mode delegates authorization to the API server, enforcing the same RBAC rules.
Remediation in /var/lib/kubelet/config.yaml:
authorization:
mode: Webhook
webhook:
cacheAuthorizedTTL: 0s
cacheUnauthorizedTTL: 0s
Read-Only Port
[FAIL] 4.2.4 Ensure that the --read-only-port argument is set to 0
What it checks: Whether the kubelet exposes a read-only API on port 10255.
Why it matters: Port 10255 provides unauthenticated, unencrypted access to kubelet metrics and Pod information. This is a significant information leak:
# What an attacker can do with the read-only port
curl http://<node-ip>:10255/pods # List all Pods with full spec (env vars, volumes)
curl http://<node-ip>:10255/spec # Node hardware spec
curl http://<node-ip>:10255/metrics # Node and container metrics
Audit:
# Check if port 10255 is listening
ss -tulnp | grep 10255
# Check config
grep 'readOnlyPort' /var/lib/kubelet/config.yaml
Remediation in /var/lib/kubelet/config.yaml:
readOnlyPort: 0
Then restart kubelet:
sudo systemctl restart kubelet
# Verify port is no longer listening
ss -tulnp | grep 10255
# Should return empty
Protect Kernel Defaults
[FAIL] 4.2.6 Ensure that the --protect-kernel-defaults argument is set to true
What it checks: Whether the kubelet enforces kernel parameter requirements rather than silently modifying them.
Why it matters: Some workloads require specific sysctl settings. Without --protect-kernel-defaults=true, the kubelet may adjust kernel parameters to meet container requirements — silently weakening the node’s security posture. With this flag, the kubelet refuses to start if the required kernel parameters are not already set correctly.
Audit:
grep 'protectKernelDefaults' /var/lib/kubelet/config.yaml
Remediation in /var/lib/kubelet/config.yaml:
protectKernelDefaults: true
Important: Before enabling this, ensure your kernel parameters are already set correctly. Otherwise kubelet will fail to start.
# Check current kernel parameters
sysctl vm.overcommit_memory
sysctl vm.panic_on_oom
sysctl kernel.panic
sysctl kernel.panic_on_oops
# Set them if needed
sudo sysctl -w vm.overcommit_memory=1
sudo sysctl -w vm.panic_on_oom=0
sudo sysctl -w kernel.panic=10
sudo sysctl -w kernel.panic_on_oops=1
# Make persistent
echo "vm.overcommit_memory=1" | sudo tee -a /etc/sysctl.d/90-kubelet.conf
echo "vm.panic_on_oom=0" | sudo tee -a /etc/sysctl.d/90-kubelet.conf
echo "kernel.panic=10" | sudo tee -a /etc/sysctl.d/90-kubelet.conf
echo "kernel.panic_on_oops=1" | sudo tee -a /etc/sysctl.d/90-kubelet.conf
sudo sysctl --system
Then restart kubelet:
sudo systemctl restart kubelet
TLS Configuration
[FAIL] 4.2.10 Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate
What it checks: Whether the kubelet HTTPS endpoint uses proper TLS certificates.
Remediation in /var/lib/kubelet/config.yaml:
tlsCertFile: /var/lib/kubelet/pki/kubelet.crt
tlsPrivateKeyFile: /var/lib/kubelet/pki/kubelet.key
Certificate Rotation
[PASS] 4.2.11 Ensure that the --rotate-certificates argument is not set to false
[PASS] 4.2.12 Verify that the RotateKubeletServerCertificate argument is set to true
What it checks: Whether kubelet automatically rotates its client and server certificates before they expire.
Audit:
grep -E 'rotateCertificates|serverTLSBootstrap' /var/lib/kubelet/config.yaml
Expected configuration:
rotateCertificates: true
serverTLSBootstrap: true
kubeadm enables certificate rotation by default.
Strong Cryptographic Ciphers
[WARN] 4.2.13 Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers
Remediation in /var/lib/kubelet/config.yaml:
tlsCipherSuites:
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
- TLS_RSA_WITH_AES_256_GCM_SHA384
- TLS_RSA_WITH_AES_128_GCM_SHA256
Complete kubelet config.yaml (Hardened)
Here is a complete hardened kubelet configuration file that passes all CIS Level 1 and most Level 2 checks:
# /var/lib/kubelet/config.yaml
apiVersion: kubelet.config.k8s.io/v1beta1
kind: KubeletConfiguration
# Authentication
authentication:
anonymous:
enabled: false # 4.2.1: Disable anonymous auth
webhook:
enabled: true # Use API server for auth
cacheTTL: 0s
x509:
clientCAFile: /etc/kubernetes/pki/ca.crt
# Authorization
authorization:
mode: Webhook # 4.2.2: Webhook authorization
webhook:
cacheAuthorizedTTL: 0s
cacheUnauthorizedTTL: 0s
# TLS
tlsCertFile: /var/lib/kubelet/pki/kubelet.crt # 4.2.10
tlsPrivateKeyFile: /var/lib/kubelet/pki/kubelet.key
tlsCipherSuites: # 4.2.13: Strong ciphers
- TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
- TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
- TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305
- TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
# Certificate rotation
rotateCertificates: true # 4.2.11
serverTLSBootstrap: true # 4.2.12
# Security
readOnlyPort: 0 # 4.2.4: Disable read-only port
protectKernelDefaults: true # 4.2.6: Protect kernel defaults
makeIPTablesUtilChains: true # 4.2.7
# Cluster
clusterDomain: cluster.local
clusterDNS:
- 10.96.0.10
# Resource management
cgroupDriver: systemd
File Permission Summary Table
| File/Directory | Expected Permission | Expected Owner | Check IDs |
|---|---|---|---|
| kubelet service file | 600 |
root:root |
4.1.1, 4.1.2 |
/etc/kubernetes/kubelet.conf |
600 |
root:root |
4.1.5, 4.1.6 |
/var/lib/kubelet/config.yaml |
600 |
root:root |
4.1.9, 4.1.10 |
/etc/kubernetes/pki/*.crt |
600 or 644 |
root:root |
Varies |
/etc/kubernetes/pki/*.key |
600 |
root:root |
Varies |
/var/lib/kubelet/ |
750 |
root:root |
General best practice |
Complete Sample Worker Node Output
[INFO] 4 Worker Node Security Configuration
[INFO] 4.1 Worker Node Configuration Files
[PASS] 4.1.1 Ensure that the kubelet service file permissions are set to 600 or more restrictive
[PASS] 4.1.2 Ensure that the kubelet service file ownership is set to root:root
[PASS] 4.1.5 Ensure that the --kubeconfig kubelet.conf file permissions are set to 600 or more restrictive
[PASS] 4.1.6 Ensure that the --kubeconfig kubelet.conf file ownership is set to root:root
[PASS] 4.1.9 If the kubelet config.yaml configuration file is being used, validate permissions set to 600 or more restrictive
[PASS] 4.1.10 If the kubelet config.yaml configuration file is being used, ensure ownership is set to root:root
[INFO] 4.2 Kubelet
[PASS] 4.2.1 Ensure that the --anonymous-auth argument is set to false
[PASS] 4.2.2 Ensure that the --authorization-mode argument is not set to AlwaysAllow
[PASS] 4.2.3 Ensure that the --client-ca-file argument is set as appropriate
[PASS] 4.2.4 Ensure that the --read-only-port argument is set to 0
[PASS] 4.2.6 Ensure that the --protect-kernel-defaults argument is set to true
[PASS] 4.2.7 Ensure that the --make-iptables-util-chains argument is set to true
[PASS] 4.2.10 Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate
[PASS] 4.2.11 Ensure that the --rotate-certificates argument is not set to false
[PASS] 4.2.12 Verify that the RotateKubeletServerCertificate argument is set to true
[WARN] 4.2.13 Ensure that the Kubelet only makes use of Strong Cryptographic Ciphers
== Summary node ==
15 checks PASS
0 checks FAIL
1 checks WARN
0 checks INFO
Practice Questions
Q1: What command scans only worker node components with kube-bench?
Show Answer
sudo kube-bench run --targets node
This runs all checks in Section 4.1 (Worker Node Configuration Files) and Section 4.2 (Kubelet). It must be run on the worker node itself (or as a Kubernetes Job with nodeSelector targeting worker nodes).
Q2: Why is readOnlyPort: 0 critical in the kubelet configuration?
Show Answer
The kubelet read-only port (10255) provides unauthenticated, unencrypted HTTP access to:
/pods— Full Pod specifications including environment variables, volume mounts, and Secrets references/spec— Node hardware specifications/metrics— Node and container metrics
Setting readOnlyPort: 0 disables this port entirely. Any client needing kubelet data should use the authenticated HTTPS port (10250) instead.
Q3: Describe the complete remediation for kubelet anonymous authentication (check 4.2.1).
Show Answer
-
Edit the kubelet configuration file:
sudo vi /var/lib/kubelet/config.yaml -
Set the authentication section:
authentication: anonymous: enabled: false webhook: enabled: true x509: clientCAFile: /etc/kubernetes/pki/ca.crt -
Restart kubelet:
sudo systemctl restart kubelet -
Verify kubelet is running:
sudo systemctl status kubelet -
Re-run the check:
sudo kube-bench run --check 4.2.1Expected:
[PASS] 4.2.1
Q4: What is the risk of not setting protectKernelDefaults: true, and what must you do before enabling it?
Show Answer
Risk: Without protectKernelDefaults: true, the kubelet may silently modify kernel parameters (sysctl values) to meet container requirements. This can weaken the node’s security posture without the administrator’s knowledge — for example, changing vm.overcommit_memory or kernel.panic settings.
Before enabling: Ensure the required kernel parameters are already correctly set:
sudo sysctl -w vm.overcommit_memory=1
sudo sysctl -w vm.panic_on_oom=0
sudo sysctl -w kernel.panic=10
sudo sysctl -w kernel.panic_on_oops=1
Make them persistent in /etc/sysctl.d/. If these aren’t set correctly before enabling the flag, kubelet will fail to start.
Q5: Which file permission checks does kube-bench perform on worker nodes, and what is the expected permission for each?
Show Answer
| Check | File | Expected Permission | Expected Owner |
|---|---|---|---|
| 4.1.1 | kubelet service file | 600 |
root:root |
| 4.1.2 | kubelet service file | root:root ownership |
root:root |
| 4.1.5 | /etc/kubernetes/kubelet.conf |
600 |
root:root |
| 4.1.6 | /etc/kubernetes/kubelet.conf |
root:root ownership |
root:root |
| 4.1.9 | /var/lib/kubelet/config.yaml |
600 |
root:root |
| 4.1.10 | /var/lib/kubelet/config.yaml |
root:root ownership |
root:root |
Fix all with:
sudo chmod 600 <file>
sudo chown root:root <file>
Next: Learn how to fix failures systematically with Remediation Strategies →
| ← Scanning Control Plane | Remediation Strategies → |