CKS — Scanning Control Plane Components

13 minute read

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:

  1. Section 1.1 — Control Plane Node Configuration Files
  2. Section 1.2 — API Server
  3. Section 1.3 — Controller Manager
  4. Section 1.4 — Scheduler
  5. 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
  1. Open the API server static Pod manifest:

    sudo vi /etc/kubernetes/manifests/kube-apiserver.yaml
    
  2. Add or modify the flag in the command section:

    - --anonymous-auth=false
    
  3. Save the file. The kubelet automatically detects the change and restarts the API server Pod.

  4. Wait 30-60 seconds and verify:

    kubectl get pods -n kube-system | grep kube-apiserver
    
  5. Re-run the specific check:

    sudo kube-bench run --check 1.2.1
    

    Expected: [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 →

Updated: