CKS — Scanning Control Plane Components
Exam Context
| Property | Value |
|---|---|
| Domain | Domain 1 — Cluster Setup |
| Weight | 10% |
| Objective | Use CIS benchmark to review the security configuration of Kubernetes components |
Control plane components are the highest-value targets in a Kubernetes cluster. A compromised API server or etcd instance means total cluster compromise. This topic covers exactly what kube-bench checks on each control plane component and how to interpret the results.
Running the Control Plane Scan
SSH into your control plane node and run:
# Full master scan
sudo kube-bench run --targets master
# Or as a Kubernetes Job (see previous topic for full YAML)
kubectl apply -f kube-bench-master-job.yaml
kubectl logs job/kube-bench-master
The scan covers four main component groups:
- Section 1.1 — Control Plane Node Configuration Files
- Section 1.2 — API Server
- Section 1.3 — Controller Manager
- Section 1.4 — Scheduler
- Section 2 — etcd
API Server Security Checks
The API server is the largest section — and the most critical. Every request to the cluster flows through it.
Critical Flag: --anonymous-auth
[FAIL] 1.2.1 Ensure that the --anonymous-auth argument is set to false
What it checks: Whether the API server accepts unauthenticated requests.
Why it matters: With --anonymous-auth=true (the default), anyone who can reach the API server can make requests as system:anonymous. Combined with permissive RBAC, this exposes cluster internals.
Audit:
# Check the running process
ps -ef | grep kube-apiserver | grep -o '\-\-anonymous-auth=[a-z]*'
# Check the static Pod manifest
grep 'anonymous-auth' /etc/kubernetes/manifests/kube-apiserver.yaml
Expected output (secure):
--anonymous-auth=false
Remediation:
# Edit the API server manifest
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
Add or modify in the command section:
spec:
containers:
- command:
- kube-apiserver
- --anonymous-auth=false
# ... other flags
After saving, the kubelet will automatically restart the API server Pod (static Pod behavior). Wait approximately 30-60 seconds:
# Verify the API server restarted
kubectl get pods -n kube-system | grep kube-apiserver
# Verify the fix
kube-bench run --check 1.2.1
Expected output:
[PASS] 1.2.1 Ensure that the --anonymous-auth argument is set to false
Critical Flag: --authorization-mode
[FAIL] 1.2.6 Ensure that the --authorization-mode argument is not set to AlwaysAllow
[PASS] 1.2.7 Ensure that the --authorization-mode argument includes Node
[PASS] 1.2.8 Ensure that the --authorization-mode argument includes RBAC
What it checks: The authorization mode must include both Node and RBAC, and must NOT be AlwaysAllow.
Audit:
grep 'authorization-mode' /etc/kubernetes/manifests/kube-apiserver.yaml
Expected output (secure):
- --authorization-mode=Node,RBAC
Remediation (if needed):
spec:
containers:
- command:
- kube-apiserver
- --authorization-mode=Node,RBAC
Why Node authorization: The Node authorizer grants kubelets access to only the resources (Secrets, ConfigMaps, PVs) related to Pods scheduled on their node — preventing one compromised kubelet from accessing another node’s data.
Critical Flag: --enable-admission-plugins
[WARN] 1.2.10 Ensure that the admission control plugin EventRateLimit is set
[WARN] 1.2.12 Ensure that the admission control plugin AlwaysPullImages is set
[PASS] 1.2.17 Ensure that the --enable-admission-plugins argument includes NodeRestriction
What it checks: Which admission controllers are enabled.
Key admission plugins for CKS:
| Plugin | Purpose | Level |
|---|---|---|
NodeRestriction |
Limits what nodes can modify — prevents compromised kubelet from modifying other nodes’ objects | Level 1 |
PodSecurity |
Enforces Pod Security Standards (replacement for PodSecurityPolicy) | Level 1 |
EventRateLimit |
Prevents event flooding DoS attacks | Level 2 |
AlwaysPullImages |
Forces image pull on every container start, preventing cached image attacks | Level 2 |
Audit:
grep 'enable-admission-plugins' /etc/kubernetes/manifests/kube-apiserver.yaml
Recommended configuration:
- --enable-admission-plugins=NodeRestriction,PodSecurity
For Level 2 compliance:
- --enable-admission-plugins=NodeRestriction,PodSecurity,EventRateLimit,AlwaysPullImages
Critical Flag: --audit-log-path
[FAIL] 1.2.19 Ensure that the --audit-log-path argument is set
[FAIL] 1.2.20 Ensure that the --audit-log-maxage argument is set to 30 or as appropriate
[FAIL] 1.2.21 Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate
[FAIL] 1.2.22 Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate
What it checks: Whether API audit logging is enabled and properly configured.
Why it matters: Without audit logs, you have no record of who did what in the cluster. This is critical for security incident investigation and compliance.
Remediation — add all four flags:
spec:
containers:
- command:
- kube-apiserver
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
- --audit-policy-file=/etc/kubernetes/audit/audit-policy.yaml
You must also create the audit log directory and mount it:
volumeMounts:
- name: audit-log
mountPath: /var/log/kubernetes/audit
- name: audit-policy
mountPath: /etc/kubernetes/audit
readOnly: true
volumes:
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
Create a minimal audit policy:
sudo mkdir -p /etc/kubernetes/audit
# /etc/kubernetes/audit/audit-policy.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
- level: RequestResponse
resources:
- group: ""
resources: ["pods"]
- group: "apps"
resources: ["deployments"]
- level: Metadata
stages:
- ResponseComplete
Critical Flag: --tls-cert-file and --tls-private-key-file
[PASS] 1.2.27 Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate
What it checks: Whether the API server uses TLS for all client connections.
Audit:
grep -E 'tls-cert-file|tls-private-key-file' /etc/kubernetes/manifests/kube-apiserver.yaml
Expected output:
- --tls-cert-file=/etc/kubernetes/pki/apiserver.crt
- --tls-private-key-file=/etc/kubernetes/pki/apiserver.key
kubeadm sets these by default, so this check typically passes on kubeadm clusters.
Critical Flag: --profiling
[FAIL] 1.2.16 Ensure that the --profiling argument is set to false
What it checks: Whether the API server exposes profiling endpoints (/debug/pprof/).
Why it matters: Profiling endpoints expose internal performance data and can leak sensitive information about the API server’s memory and CPU usage patterns.
Remediation:
- --profiling=false
etcd Security Checks
etcd stores all cluster state — Secrets, ConfigMaps, RBAC rules, everything. A compromised etcd means total cluster takeover.
Client Certificate Authentication
[FAIL] 2.2 Ensure that the --client-cert-auth argument is set to true
Audit:
grep 'client-cert-auth' /etc/kubernetes/manifests/etcd.yaml
Remediation:
spec:
containers:
- command:
- etcd
- --client-cert-auth=true
Peer Auto-TLS
[FAIL] 2.6 Ensure that the --peer-auto-tls argument is not set to true
What it checks: etcd peer communication should NOT use auto-generated self-signed TLS certificates.
Why it matters: Auto-TLS uses self-signed certificates that any rogue etcd instance could generate — providing no real authentication between peers.
Audit:
grep 'peer-auto-tls' /etc/kubernetes/manifests/etcd.yaml
Remediation — ensure this flag is NOT present or is set to false:
- --peer-auto-tls=false
Instead, configure explicit peer TLS certificates:
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-client-cert-auth=true
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
Data Encryption
[WARN] 2.7 Ensure that a unique Certificate Authority is used for etcd
Audit:
# Check if etcd CA is different from the Kubernetes CA
diff /etc/kubernetes/pki/etcd/ca.crt /etc/kubernetes/pki/ca.crt
If the files differ, etcd uses a unique CA (PASS). kubeadm creates a separate etcd CA by default.
Full etcd Audit Summary
# Run only etcd checks
kube-bench run --targets etcd
Expected output:
[INFO] 2 Etcd Node Configuration
[PASS] 2.1 Ensure that the --cert-file and --key-file arguments are set as appropriate
[PASS] 2.2 Ensure that the --client-cert-auth argument is set to true
[PASS] 2.3 Ensure that the --auto-tls argument is not set to true
[PASS] 2.4 Ensure that the --peer-cert-file and --peer-key-file arguments are set as appropriate
[PASS] 2.5 Ensure that the --peer-client-cert-auth argument is set to true
[PASS] 2.6 Ensure that the --peer-auto-tls argument is not set to true
[PASS] 2.7 Ensure that a unique Certificate Authority is used for etcd
== Summary etcd ==
7 checks PASS
0 checks FAIL
0 checks WARN
0 checks INFO
Scheduler Checks
The scheduler has fewer security checks, but they still matter.
Profiling
[FAIL] 1.4.1 Ensure that the --profiling argument is set to false
Remediation in /etc/kubernetes/manifests/kube-scheduler.yaml:
spec:
containers:
- command:
- kube-scheduler
- --profiling=false
# ... other flags
Bind Address
[FAIL] 1.4.2 Ensure that the --bind-address argument is set to 127.0.0.1
What it checks: The scheduler should only listen on localhost.
Why it matters: If the scheduler binds to 0.0.0.0, its metrics and healthz endpoints are accessible from the network.
Remediation:
- --bind-address=127.0.0.1
Full Scheduler Scan
# Run scheduler-specific checks
kube-bench run --group 1.4
Expected output (after remediation):
[INFO] 1.4 Scheduler
[PASS] 1.4.1 Ensure that the --profiling argument is set to false
[PASS] 1.4.2 Ensure that the --bind-address argument is set to 127.0.0.1
== Summary ==
2 checks PASS
0 checks FAIL
0 checks WARN
0 checks INFO
Controller Manager Checks
Profiling
[FAIL] 1.3.2 Ensure that the --profiling argument is set to false
Remediation in /etc/kubernetes/manifests/kube-controller-manager.yaml:
- --profiling=false
Service Account Credentials
[FAIL] 1.3.3 Ensure that the --use-service-account-credentials argument is set to true
What it checks: Whether the controller manager uses individual service account credentials for each controller.
Why it matters: Without this, all controllers share the controller manager’s credentials — if any single controller is exploited, the attacker gets full controller-manager privileges.
Remediation:
- --use-service-account-credentials=true
Service Account Private Key
[FAIL] 1.3.4 Ensure that the --service-account-private-key-file argument is set as appropriate
Remediation:
- --service-account-private-key-file=/etc/kubernetes/pki/sa.key
kubeadm sets this by default.
Bind Address
[FAIL] 1.3.7 Ensure that the --bind-address argument is set to 127.0.0.1
Remediation:
- --bind-address=127.0.0.1
Full Controller Manager Scan
kube-bench run --group 1.3
Expected output (after remediation):
[INFO] 1.3 Controller Manager
[PASS] 1.3.1 Ensure that the --terminated-pod-gc-threshold argument is set as appropriate
[PASS] 1.3.2 Ensure that the --profiling argument is set to false
[PASS] 1.3.3 Ensure that the --use-service-account-credentials argument is set to true
[PASS] 1.3.4 Ensure that the --service-account-private-key-file argument is set as appropriate
[PASS] 1.3.5 Ensure that the --root-ca-file argument is set as appropriate
[PASS] 1.3.6 Ensure that the RotateKubeletServerCertificate argument is set to true
[PASS] 1.3.7 Ensure that the --bind-address argument is set to 127.0.0.1
== Summary ==
7 checks PASS
0 checks FAIL
0 checks WARN
0 checks INFO
Interpreting Results: Priority of Fixes
When you see a mixed report of PASS, FAIL, and WARN, prioritize fixes in this order:
Priority Matrix
| Priority | Status | Impact | Action |
|---|---|---|---|
| P0 — Critical | FAIL | Authentication/Authorization bypass | Fix immediately |
| P1 — High | FAIL | Missing audit logs, TLS issues | Fix in same session |
| P2 — Medium | FAIL | Profiling enabled, bind-address issues | Fix next |
| P3 — Low | FAIL | File permission issues | Fix last |
| P4 — Review | WARN | Manual verification needed | Review and decide |
Example Prioritized Fix Order
Given this scan output:
[FAIL] 1.2.1 --anonymous-auth not set to false → P0 (auth bypass)
[FAIL] 1.2.19 --audit-log-path not set → P1 (no audit trail)
[FAIL] 1.2.16 --profiling not set to false → P2 (info leak)
[FAIL] 1.1.5 scheduler manifest permissions too open → P3 (file perms)
[WARN] 1.2.10 EventRateLimit admission plugin not set → P4 (review)
Fix in order: 1.2.1 → 1.2.19 → 1.2.16 → 1.1.5 → review 1.2.10.
Complete Sample Output
Here’s a realistic full kube-bench master scan output for reference:
[INFO] 1 Control Plane Security Configuration
[INFO] 1.1 Control Plane Node Configuration Files
[PASS] 1.1.1 Ensure that the API server pod specification file permissions are set to 600 or more restrictive
[PASS] 1.1.2 Ensure that the API server pod specification file ownership is set to root:root
[PASS] 1.1.3 Ensure that the controller manager pod specification file permissions are set to 600 or more restrictive
[PASS] 1.1.4 Ensure that the controller manager pod specification file ownership is set to root:root
[PASS] 1.1.5 Ensure that the scheduler pod specification file permissions are set to 600 or more restrictive
[PASS] 1.1.6 Ensure that the scheduler pod specification file ownership is set to root:root
[PASS] 1.1.7 Ensure that the etcd pod specification file permissions are set to 600 or more restrictive
[PASS] 1.1.8 Ensure that the etcd pod specification file ownership is set to root:root
[PASS] 1.1.9 Ensure that the Container Network Interface file permissions are set to 600 or more restrictive
[PASS] 1.1.10 Ensure that the Container Network Interface file ownership is set to root:root
[PASS] 1.1.11 Ensure that the etcd data directory permissions are set to 700 or more restrictive
[PASS] 1.1.12 Ensure that the etcd data directory ownership is set to etcd:etcd
[PASS] 1.1.13 Ensure that the admin.conf file permissions are set to 600
[PASS] 1.1.14 Ensure that the admin.conf file ownership is set to root:root
[PASS] 1.1.15 Ensure that the scheduler.conf file permissions are set to 600 or more restrictive
[PASS] 1.1.16 Ensure that the scheduler.conf file ownership is set to root:root
[PASS] 1.1.17 Ensure that the controller-manager.conf file permissions are set to 600 or more restrictive
[PASS] 1.1.18 Ensure that the controller-manager.conf file ownership is set to root:root
[PASS] 1.1.19 Ensure that the Kubernetes PKI directory and file permissions are set to 600 or more restrictive
[PASS] 1.1.20 Ensure that the Kubernetes PKI certificate file permissions are set to 600 or more restrictive
[PASS] 1.1.21 Ensure that the Kubernetes PKI key file permissions are set to 600
[INFO] 1.2 API Server
[FAIL] 1.2.1 Ensure that the --anonymous-auth argument is set to false
[PASS] 1.2.2 Ensure that the --token-auth-file parameter is not set
[PASS] 1.2.6 Ensure that the --authorization-mode argument is not set to AlwaysAllow
[PASS] 1.2.7 Ensure that the --authorization-mode argument includes Node
[PASS] 1.2.8 Ensure that the --authorization-mode argument includes RBAC
[WARN] 1.2.10 Ensure that the admission control plugin EventRateLimit is set
[WARN] 1.2.12 Ensure that the admission control plugin AlwaysPullImages is set
[PASS] 1.2.13 Ensure that the admission control plugin SecurityContextDeny is set if PodSecurityPolicy is not used
[FAIL] 1.2.16 Ensure that the --profiling argument is set to false
[PASS] 1.2.17 Ensure that the --audit-log-path argument is set
[PASS] 1.2.18 Ensure that the --audit-log-maxage argument is set to 30 or as appropriate
[PASS] 1.2.19 Ensure that the --audit-log-maxbackup argument is set to 10 or as appropriate
[PASS] 1.2.20 Ensure that the --audit-log-maxsize argument is set to 100 or as appropriate
[PASS] 1.2.27 Ensure that the --tls-cert-file and --tls-private-key-file arguments are set as appropriate
[PASS] 1.2.28 Ensure that the --client-ca-file argument is set as appropriate
[INFO] 1.3 Controller Manager
[PASS] 1.3.1 Ensure that the --terminated-pod-gc-threshold argument is set as appropriate
[FAIL] 1.3.2 Ensure that the --profiling argument is set to false
[PASS] 1.3.3 Ensure that the --use-service-account-credentials argument is set to true
[PASS] 1.3.4 Ensure that the --service-account-private-key-file argument is set as appropriate
[PASS] 1.3.5 Ensure that the --root-ca-file argument is set as appropriate
[PASS] 1.3.6 Ensure that the RotateKubeletServerCertificate argument is set to true
[PASS] 1.3.7 Ensure that the --bind-address argument is set to 127.0.0.1
[INFO] 1.4 Scheduler
[FAIL] 1.4.1 Ensure that the --profiling argument is set to false
[PASS] 1.4.2 Ensure that the --bind-address argument is set to 127.0.0.1
== Summary master ==
38 checks PASS
4 checks FAIL
2 checks WARN
0 checks INFO
Practice Questions
Q1: What command scans only the control plane components with kube-bench?
Show Answer
sudo kube-bench run --targets master
This runs all checks in Sections 1.1 (Configuration Files), 1.2 (API Server), 1.3 (Controller Manager), and 1.4 (Scheduler). To also include etcd:
sudo kube-bench run --targets master,etcd
Q2: The kube-bench output shows [FAIL] 1.2.1 Ensure that the --anonymous-auth argument is set to false. Describe the exact remediation steps.
Show Answer
-
Open the API server static Pod manifest:
sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml -
Add or modify the flag in the
commandsection:- --anonymous-auth=false -
Save the file. The kubelet automatically detects the change and restarts the API server Pod.
-
Wait 30-60 seconds and verify:
kubectl get pods -n kube-system | grep kube-apiserver -
Re-run the specific check:
sudo kube-bench run --check 1.2.1Expected:
[PASS] 1.2.1
Q3: Why does kube-bench check --profiling=false on the API server, scheduler, AND controller manager?
Show Answer
Profiling endpoints (/debug/pprof/) expose internal runtime data including memory allocations, goroutine stacks, CPU usage patterns, and heap profiles. This information can help an attacker:
- Understand internal code paths for exploit development
- Identify memory patterns that reveal sensitive data handling
- Cause resource exhaustion by triggering expensive profiles
Each component has its own profiling endpoint, so each must be disabled individually:
- API server: check 1.2.16
- Controller manager: check 1.3.2
- Scheduler: check 1.4.1
Q4: A kube-bench scan shows etcd check 2.6 failing: --peer-auto-tls is set to true. What is the security risk and how do you fix it?
Show Answer
Security risk: --peer-auto-tls=true means etcd automatically generates self-signed certificates for peer-to-peer communication. Any rogue etcd process could generate its own certificate and join the cluster — there’s no trusted CA to validate peer identity.
Fix: Replace auto-TLS with explicit peer certificates in /etc/kubernetes/manifests/etcd.yaml:
- --peer-auto-tls=false
- --peer-cert-file=/etc/kubernetes/pki/etcd/peer.crt
- --peer-key-file=/etc/kubernetes/pki/etcd/peer.key
- --peer-client-cert-auth=true
- --peer-trusted-ca-file=/etc/kubernetes/pki/etcd/ca.crt
kubeadm configures these correctly by default. If they’re missing, regenerate etcd peer certificates using kubeadm init phase certs etcd-peer.
Q5: After fixing 4 FAIL results on the API server, how do you verify all fixes without re-running the entire master scan?
Show Answer
Use the --check or --group flags to run specific checks:
# Verify individual checks
sudo kube-bench run --check 1.2.1
sudo kube-bench run --check 1.2.16
sudo kube-bench run --check 1.2.19
sudo kube-bench run --check 1.3.2
# Or verify the entire API server section
sudo kube-bench run --group 1.2
# Or verify all master checks (recommended as final verification)
sudo kube-bench run --targets master
Using --check for individual verification is fastest during remediation. Run the full --targets master scan as the final step to confirm no regressions.
Next: Scan worker nodes with Scanning Worker Nodes →
| ← kube-bench Setup | Scanning Worker Nodes → |