Overview
System Hardening is about the Linux hosts that run Kubernetes. You reduce the attack surface by finding and stopping services that are not needed, removing packages, closing listening ports and blocking unneeded kernel modules. You apply least privilege to identities on the node and in the cloud account behind it, such as sudo rights, SSH access and node instance roles. You limit which networks can reach node and control plane ports with host firewalls or cloud security groups.
The most tested competency is kernel hardening. You load an AppArmor profile on a node with apparmor_parser, confirm it with aa-status, and attach it to a container through securityContext.appArmorProfile. You place a seccomp profile under /var/lib/kubelet/seccomp and reference it with seccompProfile type Localhost, or use RuntimeDefault. A Pod referencing a profile missing from its node will not start.
How it's tested
System Hardening tasks happen partly on a node shell and partly in a Pod spec. You might identify and stop a service listening on an unexpected port, remove a package, or load a provided AppArmor profile on a worker and apply it to a Deployment. Grading checks node state and the running Pod.
-
Load then reference
A profile file is provided on a node; you load or copy it into place there, then reference it by exact name in the Pod securityContext and pin the Pod to that node if needed.
-
Find the listener
A port or process is named, and you use ss -tlpn, systemctl and package tools to identify, stop, disable or remove what is serving it.
-
Tighten host access
A user, group or firewall rule gives too much access, and you narrow it without locking yourself out of the host.
A task gives an AppArmor profile file on node01 and asks that Pod writer use it. Loading it with apparmor_parser -q on node01 and then setting appArmorProfile type Localhost with localhostProfile equal to the profile name inside the file, not the file name, applies it. If the Pod can land on another node, add a nodeSelector, because the profile exists only where you loaded it.
Competencies 4
Official competencies from the CNCF / The Linux Foundation outline, in outline order.
3.1 Minimize host OS footprint (reduce attack surface)Removing services, packages and modules a node does not need.
- What it means
- Every package, running service, open port and loaded kernel module on a node is something an attacker could use. Minimizing the footprint means a node runs only what Kubernetes and its workloads require.
- What to understand
- Find listeners with ss -tlpn or netstat -tulpn and map the PID to a service with systemctl status. Stop and disable unneeded services with systemctl disable --now, and remove packages with apt or dnf. Blacklist unused kernel modules in /etc/modprobe.d. Minimal, container-focused operating systems such as Flatcar or Bottlerocket reduce the footprint further.
- How it shows up
- Expect a task to name a port or service and ask you to stop it permanently, not only for this boot.
- Common confusion
- Running systemctl stop without disable, so the service returns after a reboot.
3.2 Using least-privilege identity and access managementNarrow user, sudo, SSH and cloud identities on nodes.
- What it means
- Identities that operate nodes, both Linux users and cloud roles attached to instances, should have only the rights they need. A compromised node with a powerful cloud role can reach far beyond the cluster.
- What to understand
- On the host, review /etc/passwd, /etc/group and /etc/sudoers.d, remove unused accounts, lock shell access for service users and prefer key-based SSH with root login disabled. In the cloud, keep node instance roles minimal and use workload identity features so Pods do not inherit node credentials. Inside Pods, run as non-root users and avoid hostPID, hostNetwork and privileged mode.
- How it shows up
- A task may ask you to remove a user from a privileged group or delete an unneeded account.
- Common confusion
- Confusing Linux users on the node with Kubernetes users, which exist only as certificate or token identities.
3.3 Minimize external access to the networkLimiting which networks reach nodes and cluster ports.
- What it means
- Nodes and control plane components listen on many ports. Only the ones needed by clients or other nodes should be reachable, and only from expected networks.
- What to understand
- Know the key ports: 6443 for the API server, 2379-2380 for etcd, 10250 for the kubelet, 10257 and 10259 for the controller manager and scheduler, and 30000-32767 for NodePorts. Restrict them with ufw or iptables on the host and with cloud security groups or firewall rules. Inside the cluster, NetworkPolicies and avoiding unnecessary NodePort and LoadBalancer Services serve the same goal.
- How it shows up
- A task might ask you to allow SSH and the API port from one CIDR while denying other inbound traffic.
- Common confusion
- Blocking 10250 from the control plane, which breaks kubectl logs and exec.
3.4 Appropriately use kernel hardening tools such as AppArmor, seccompConfining container processes with AppArmor and seccomp.
- What it means
- AppArmor is a Linux security module that restricts which files, capabilities and network operations a program may use. Seccomp filters the system calls a process may make. Kubernetes lets you attach both to Pods and containers.
- What to understand
- AppArmor profiles are loaded per node with apparmor_parser and listed by aa-status; reference them with securityContext.appArmorProfile type Localhost and localhostProfile, a field that replaced the beta annotation in v1.30. Seccomp profiles live under the kubelet's seccomp directory, normally /var/lib/kubelet/seccomp, and are referenced by a path relative to it. RuntimeDefault applies the container runtime's default filter. Unconfined disables confinement.
- How it shows up
- Expect to apply a provided profile to a named container and confirm the Pod runs with it.
- Common confusion
- Using the profile file name for AppArmor when Kubernetes needs the profile name declared inside the file.
High-yield concepts
The terms, rules and formulas this domain turns into questions most often.
-
apparmor_parser
Loads or replaces an AppArmor profile in the kernel.
- Remember
- Run apparmor_parser -q file on every node where the Pod may run.
- Tested as
- Required step before any Localhost AppArmor reference works.
- Trap
- Loading the profile on the control plane while the Pod runs on a worker.
-
aa-status
Lists loaded AppArmor profiles and their enforce or complain mode.
- Remember
- Confirms the exact profile name to reference.
- Tested as
- Quick verification after loading a profile.
- Trap
- Assuming complain mode blocks anything; it only logs.
-
appArmorProfile field
securityContext field with type RuntimeDefault, Localhost or Unconfined.
- Remember
- Localhost needs localhostProfile set to the loaded profile name.
- Tested as
- Current syntax since v1.30; annotations are deprecated.
- Trap
- Copying old container.apparmor.security.beta.kubernetes.io examples.
-
seccompProfile Localhost
References a seccomp JSON file on the node.
- Remember
- The path is relative to /var/lib/kubelet/seccomp, for example profiles/audit.json.
- Tested as
- Common exam task with a supplied JSON file.
- Trap
- Writing the absolute path, which the kubelet rejects.
-
RuntimeDefault seccomp
The container runtime's built-in syscall filter.
- Remember
- Restricted Pod Security requires RuntimeDefault or Localhost.
- Tested as
- Links System Hardening to Domain 4 admission tasks.
- Trap
- Leaving it unset and assuming a default filter applies.
-
ss -tlpn
Shows listening TCP sockets with owning processes.
- Remember
- Map the PID to a unit with systemctl status PID.
- Tested as
- First step in any 'what is listening' task.
- Trap
- Killing the process instead of disabling the service, so it restarts.
-
systemctl disable --now
Stops a service and prevents it starting at boot.
- Remember
- Combine with package removal when the task says remove.
- Tested as
- Makes footprint reductions persistent.
- Trap
- Using stop alone.
-
Control plane ports
6443 API server, 2379-2380 etcd, 10250 kubelet, 30000-32767 NodePorts.
- Remember
- Only 6443 normally needs exposure outside the node network.
- Tested as
- Firewall tasks hinge on these numbers.
- Trap
- Exposing etcd ports to clients outside the control plane.
Side by side
Concepts candidates mix up, compared on the point that decides the answer.
AppArmor vs seccomp
| Concept | Meaning | Key difference | Common trap |
|---|---|---|---|
| AppArmor | Path and capability based mandatory access control | Profile loaded into the kernel by name | Assuming the profile travels with the Pod to any node |
| seccomp | Filter on which system calls a process may make | Profile is a JSON file read by the runtime | Placing the JSON outside the kubelet seccomp directory |
| RuntimeDefault | Runtime-provided default policy for either tool | No file to manage | Thinking it is applied when the field is absent |
| Unconfined | No profile restrictions | Only for troubleshooting | Selecting it to get a Pod running under a deadline |
Reducing node exposure
| Concept | Meaning | Key difference | Common trap |
|---|---|---|---|
| Disable a service | Stops and prevents a daemon from starting | Software remains installed | Leaving the binary for an attacker to start |
| Remove a package | Deletes the software from the host | Most permanent option | Removing a dependency that kubelet or the runtime needs |
| Host firewall rule | Blocks network access to a port | Service keeps running locally | Locking out your own SSH session |
Exam traps
Each trap is an answer that looks right. Learn the clue that should change your mind.
-
Profile name mismatch
- What candidates pick
- Setting localhostProfile to the profile's file name.
- Why it looks right
- The file name is the most visible identifier in the task.
- Why it's wrong
- AppArmor uses the name declared after the profile keyword inside the file, which may differ.
Clue to watch for aa-status output listing a name different from the file.
-
Pod stuck in Blocked or CreateContainerError
- What candidates pick
- Switching to Unconfined to make the Pod run.
- Why it looks right
- It removes the error immediately.
- Why it's wrong
- The task requires confinement, so an unconfined Pod fails grading even though it runs.
Clue to watch for Events mentioning a profile that cannot be found.
-
Seccomp path
- What candidates pick
- Writing /var/lib/kubelet/seccomp/profiles/x.json in the Pod spec.
- Why it looks right
- Absolute paths feel unambiguous.
- Why it's wrong
- The kubelet accepts only paths relative to its seccomp root, and the Pod fails validation.
Clue to watch for A provided file already sitting inside the seccomp directory.
-
Temporary fixes
- What candidates pick
- Killing the process that owns the port.
- Why it looks right
- The port closes and the check looks done.
- Why it's wrong
- The service manager restarts it, and the grader finds it listening again.
Clue to watch for The words 'permanently' or 'must not start'.
-
Over-broad firewall
- What candidates pick
- Denying all inbound traffic on the node.
- Why it looks right
- It closes the unwanted port with one rule.
- Why it's wrong
- The kubelet and CNI need specific ports, and blocking them breaks the node.
Clue to watch for A task naming only one port or service to block.
Answer framework
Follow these steps for node-level hardening so changes are persistent and Pods still start.
- Identify the exact node from the task and ssh to it, because profiles and services are per host and not cluster objects.
- Inspect current state with aa-status, ls /var/lib/kubelet/seccomp, ss -tlpn or systemctl list-units before changing anything.
- Make the change persistent: load the profile, place the JSON file, disable the service or remove the package as the task wording requires.
- Update the Pod or Deployment securityContext with the exact profile name or relative path, and pin scheduling to the prepared node.
- Confirm the Pod reaches Running and inspect it, or rerun ss and systemctl, to show the final state matches the task.
Practice question
Want more like this? The CKS practice test has questions for every domain.
Checklist
0 of 10
Tick each one you can do without notes. Your ticks stay in this browser.
Study strategy
Set up a kubeadm lab with at least one worker running Ubuntu, because AppArmor is enabled there by default. Write a tiny AppArmor profile that denies writes to a directory, load it, apply it to a busybox Pod and prove the write fails. Repeat with a seccomp JSON file that logs or blocks a syscall, then switch the same Pod to RuntimeDefault.
Once profiles feel routine, practise node footprint work. Install a few extra services such as an FTP or web server, then find them with ss -tlpn, disable them and remove their packages. Add and remove users and sudo rights, and write ufw rules that allow 6443 and 22 from one range only.
Because the domain is only 10%, cap your study time and move on once AppArmor and seccomp tasks take under five minutes. Keep the kubernetes.io security context page bookmarked, since it holds both field formats. Reread the tutorial pages on AppArmor and seccomp once before exam day.
FAQ
How much does System Hardening count on the CKS?
System Hardening is the lightest domain at 10% of the score in the official curriculum. Plan for one or two tasks, most likely including AppArmor or seccomp.
Do I still use the AppArmor annotation?
No. Since Kubernetes v1.30 the appArmorProfile field in securityContext is the supported method, and the annotation is deprecated. The exam runs v1.35, so use the field.
Where must seccomp profiles be stored?
Under the kubelet seccomp root, by default /var/lib/kubelet/seccomp on each node. The Pod spec references them with a relative path such as profiles/name.json.
Will I need to write an AppArmor profile from scratch?
Tasks usually supply the profile. Your job is to load it on the right node, find its declared name and apply it, though reading a profile to understand what it blocks is useful.
What happens if a Pod references a profile that is not loaded?
The container does not start, and events point to the missing profile. Load the profile on that node or schedule the Pod onto a node that has it.
Is cloud IAM tested if the exam has no cloud console?
The competency mentions least-privilege identity broadly, so expect host-level users, groups and sudo in tasks. Cloud roles appear more as concepts, such as not letting Pods inherit node credentials.
Other DevOps & Containers certifications
Where the same subject shows up in other exams. Cards marked "Same subject" open the matching domain.
Test your Domain 3 knowledge
Check that you can load and apply AppArmor and seccomp profiles and shrink a node's attack surface under exam timing.