Skip to main content

CKS, security first!

·16 mins
certification kubernetes kubectl docker container cks security falco trivy bom nginx istio cilium
Romain Boulanger
Author
Romain Boulanger
Infra/Cloud Architect with DevSecOps mindset
Table of Contents

Post updates:

29 July 2025: Updating of the content of the blog post, in particular the domains, following the changes to the certification in October 2024.

26 September 2023: Restructuring of the content with the addition of new categories present in the certification exam.

What is CKS?
#

The Certified Kubernetes Security Specialist or CKS is the third Kubernetes certification offered by the Linux Foundation. Its aim is to assess your knowledge of cluster configuration and all the associated best practices, both in terms of infrastructure and Kubernetes objects, with several major themes closely linked to security.

In my opinion, this certification is the hardest of the three, due to the multitude of areas covered, the range of Kubernetes-related tools to be handled, and also the limited time available to complete the various exercises.

As far as I’m concerned, whether it was my first pass or my re-certifications, I managed to complete all the exercises (15-17 questions) even though I didn’t always have enough time to test each one. Once I’d finished the questions, I only had about ten minutes left. Tough, isn’t it?

To pass the exam, you need to have a score of 67% on a panel of 15 to 20 questions, that’s one percentage point higher than for the CKA or CKAD.

For those familiar with practicing on killer.sh, I found the certification to be fairly similar to the level of this simulator in terms of requirements. Even though killer.sh goes into much more detail in certain areas, I would recommend achieving a high score there to approach the actual exam with peace of mind.

The required skills
#

As with previous certifications, several areas are assessed with an associated weighting that represents the proportion of that area in the exam.

  • Cluster setup (15%): Implement NetworkPolicy or CiliumNetworkPolicy, use kube-bench to secure all the components of a cluster, know how to create Ingress with the specifics of Ingress NGINX;

  • Cluster Hardening (15%): Modify access to the API, setting up RBAC at cluster and user level, managing accounts services and updating the cluster, not forgetting updating the cluster with kubeadm;

  • System Hardening (10%): Use of Seccomp or AppArmor using profiles to restrict the execution of a container as well as everything relating to the securityContext block;

  • Minimize Microservice Vulnerabilities (20%): Manage Secrets and mount them within a Pod, set up a RuntimeClass with gVisor and use Istio or Cilium to encrypt connections between Pods;

  • Supply Chain Security (20%): Learn the best practices to implement in a Dockerfile, set up an ImagePolicyWebhook to check images, scan images to identify the latest critical vulnerabilities or generate an SBOM with bom or trivy;

  • Monitoring, Logging and Runtime Security (20%) : Knowing how to identify a process running in a container, making a container immutable, knowing how to use Falco to detect suspicious behaviour and implement new macros or rules, and finally, defining an Audit Policy to log events.

The difficulty of this certification comes from the need to be familiar with and proficient in using numerous third-party tools.

For example, you will need trivy to analyse your images and determine whether or not they contain vulnerabilities (CVEs).

During the exam you are allowed to consult the official documentation for the tools concerned, but a basic command is recommended to avoid wasting time…

Needless to say, the areas covered by CKAD and CKA will always be useful in all the exercises.

The exam interface
#

Since my first exam in 2021, the exam interface has changed a lot. The official exam takes place in a remote desktop environment on an Ubuntu virtual machine with XFCE as the graphical interface, as does the killer.sh platform, which I’ll tell you about later.

This new test interface is a little confusing for macOS and Windows users, as copying and pasting into the Terminal is done using the CTRL+MAJ C and CTRL+MAJ V keys. What’s more, I’ve noticed that there’s a bit of latency when using the mouse or keyboard.

This user guide lists all the information you need to know about this interface, called ExmanUI. A must read to avoid unpleasant surprises!

The good news is that the k alias for kubectl and command completion are already configured, as is vim, which has a default configuration for tabs.

What I used as resources
#

If you want to take this certification, I can only recommend this very comprehensive course on the Udemy platform from Zeal Vora: Certified Kubernetes Security Specialist 2025. It will help you cover all the chapters of the exam, with the possibility of doing the exercises on a Kubeadm cluster that you can deploy at home.

For those who don’t want to pay, Kim Wüstkamp’s course (the creator of killer.sh and killercoda.com) is available on Youtube. However, this has not been updated recently and does not benefit from the latest certification updates.

Once you have completed the theoretical part, you should start by practising on killercoda to solve the exercises as quickly as possible. Don’t hesitate to use the Playground to practise on parts that are not covered by this platform, such as setting up Pod to Pod encryption with Cilium or Istio, for example.

Once you have mastered all the scenarios, you can run the killer.sh simulator to assess your level, bearing in mind that you you get two chances to complete the same set of exercises, with each attempt entitling you to a 36-hour session on the simulator.

Once your two attempts have expired, you can always consult the exercises and the associated corrections to reproduce the exercises on your Kubernetes cluster with the aim of being fully ready.

Feel free to do the exercises over and over again, as well as the bonus questions, to cover as many areas as possible. As mentioned above, the final exam is a real speed race, so you’ll need to be able to solve the exercises without wasting time!

What you need to know
#

The aim of this section is to list all the commands and concepts to bear in mind, as well as the documentation to consult, in order to gain speed in the final exam.

Don’t hesitate to read the articles on CKAD and CKA which also contain lots of little tricks that won’t be repeated here.

Areas such as updating a cluster, managing roles and permissions (RBAC) and creating and analysing Secrets will not be listed here.

First and foremost, you may be asked to modify files on your machines. Don’t forget to make a copy of it in case you make a mistake!

Example for the kube-apiserver:

cd /etc/kubernetes/manifests/
cp kube-apiserver.yaml ~/kube-apiserver.yaml.bak

Ingress with TLS
#

CKA showed you how to manipulate an Ingress object, CKS goes further by setting up a certificate for it.

The documentation gives an overview of the steps involved:

  • Within the Ingress namespace, create a Secret in TLS format: kubectl -n [namespace] create secrets tls [secret name] --cert=./[file .crt] --key=./[file .key].

  • Add the tls block in the Ingress :

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: ingress-tls
spec:
  tls:
  - hosts:
      - [host] # Must match the host within the ‘rules’
    secretName: [secret name]
  rules:
  [...]

Once the configuration is set up, cURL becomes a valuable tool. It allows you to display the certificates during command execution to ensure that your secret is indeed being used in the request.

curl -v https://[domain name]

CertificateSigningRequest
#

To generate a certificate that will allow a user to authenticate himself through the kube-apiserver, there are several steps to follow.

This link summarises all the steps involved:

  • Creating a private key: openssl genrsa -out myuser.key 3072
  • Generating a CSR: openssl req -new -key myuser.key -out myuser.csr -subj "/CN=myuser"
  • Creating of the object within the cluster:
cat <<EOF | kubectl apply -f -
apiVersion: certificates.k8s.io/v1
kind: CertificateSigningRequest
metadata:
  name: myuser
spec:
  request: # To be filled in with the command `cat myuser.csr | base64 -w0`
  signerName: kubernetes.io/kube-apiserver-client
  expirationSeconds: 86400
  usages:
  - client auth
EOF

The documentation suggests using the command cat myuser.csr | base64 | tr -d "\n" to generate the base64 contents of request. Personally, I find the cat myuser.csr | base64 -w0 command a little easier to remember and, above all, quicker.

  • Approving the CSR: kubectl certificate approve myuser
  • Retrieving the certificate: kubectl get csr myuser -o jsonpath='{.status.certificate}'| base64 -d > myuser.crt

NetworkPolicy
#

As far as NetworkPolicy is concerned, I advise you to use the template in the documentation allowing you to resolve the various use cases (ipBlock, namespaceSelector, podSelector).

Generally, you will be asked to create a rule that denies both ingress and egress traffic. It couldn’t be simpler, and the documentation provides an example already configured.

Finally, to test that a connection is indeed blocked to a given IP or domain, you can use this command line:

kubectl exec pod -- curl -m 1 [IP or domain name]

The -m or --connect-timeout means you can define the maximum time before the request goes into timeout, so you don’t have to wait too long (especially when time is running out!).

CiliumNetworkPolicy
#

A new topic, CiliumNetworkPolicy is now part of the exam content.

As with NetworkPolicy, you will be asked to restrict network traffic potentially on several levels of the OSI model with different possibilities:

  • Layer 3: Endpoints, Services, Entities, Node, IP/CIDR and DNS
  • Layer 4: Ports, ICMP and SNI
  • Layer 7: HTTP and DNS

The documentation is crucial for recovering and adapting the examples to suit your needs.

In addition, don’t forget the possibility of denying specific access using ingressDeny or egressDeny blocks:

apiVersion: "cilium.io/v2"
kind: CiliumNetworkPolicy
metadata:
  name: "l3-rule-deny"
spec:
  endpointSelector:
    matchLabels:
      role: backend
  ingressDeny:
  - fromEndpoints:
    - matchLabels:
        role: frontend

This CiliumNetworkPolicy prevents Pods with the label role: frontend from reaching Pods with the label role: frontend.

CIS Kubernetes Benchmark with kube-bench
#

If you need to harden the configuration of a cluster, you will be asked to use the kube-bench command, giving you recommendations from the CIS Kubernetes Benchmark on the manipulations to be carried out on different components in order to guarantee an optimum security level.

You may be asked to resolve a few rules on the controlplane, etcd or a specific worker with the number of the rule (1.1.2), its description or the particular parameter to be modified (for example: Ensure that the –profiling argument is set to false).

To do this, you can run, in the case of the controlplane:

kube-bench run --targets master | grep "profiling" -A10
# Retrieve the rule number
kube-bench run --targets master --check [rule number]

The kube-bench will load the controplane rules, the grep will retrieve the rule content and the -A10 will display the next ten lines following the grep result. This retrieves the number of the associated rule to get all the information, including the solution for obtaining the well-known PASS to complete the exercise.

AppArmor
#

The Kubernetes documentation provides all the necessary information for setting up an AppArmor profile on a Pod. However, a few practical commands:

# Recover the profile name by reading the file
cat [profile filename]

# Move the profile to the worker
scp [profile filename] node01:

# Login to the worker with ssh
ssh node01

# Load profile
apparmor_parser [profile filename]

# Check that the profile is correctly set up
aa-status | grep [profile name]

And here’s the securityContext block to put in the Pod:

securityContext:
  appArmorProfile:
    type: Localhost
    localhostProfile: [profile name]

Seccomp
#

For Seccomp, here too, the documentation provides an overview of how to configure a Pod.

Like the appArmorProfile, the seccompProfile is placed in a securityContext, like this:

apiVersion: v1
kind: Pod
metadata:
  name: audit-pod
  labels:
    app: audit-pod
spec:
  securityContext:
    seccompProfile:
      type: Localhost
      localhostProfile: [profile name]

Finally, before creating your Pod with its associated Seccomp configuration, you need to install the profile in the path /var/lib/kubelet/seccomp/profiles/ within each node of the cluster.

RuntimeClass
#

To create a specific runtime class, commonly needed for gVisor, which helps isolate and limit system calls, follow this guide.

In order, you will need to :

  • Install gVisor on the cluster nodes
  • Create a RuntimeClass object like this:
kind: RuntimeClass
metadata:
  name: gvisor 
handler: runsc
  • Add the runtimeClassName: gvisor field to your Pod.

To ensure that gVisor is used correctly in your iPod, you can use the dmseg command:

$ kubectl exec sec -- dmesg
[   0.000000] Starting gVisor...
[   0.447244] Verifying that no non-zero bytes made their way into /dev/zero...
[   0.675412] Synthesizing system calls...
[   0.837421] Preparing for the zombie uprising...
[   0.893032] Waiting for children...
[   1.087694] Constructing home...
[   1.318251] Reticulating splines...
[   1.750988] Recruiting cron-ies...
[   1.921829] Singleplexing /dev/ptmx...
[   2.298970] Feeding the init monster...
[   2.382319] Moving files to filing cabinet...
[   2.513948] Ready!

The Starting gVisor… message denotes that the container is being run with runsc.

List image vulnerabilities with Trivy
#

Trivy is an open source tool that scans images and provides a table of the vulnerabilities (CVEs) found, displaying the criticality of each.

During the scan, you will be required to check whether one or more images contain specific vulnerabilities identified by a particular number (CVE-*). To carry out this operation, the command line can be useful:

trivy image <mon image> | grep [CVE-*]
# or
trivy i <mon image> | grep [CVE-*] # faster

Generate SBOM images with Bom
#

The SBOM for Software Bill of Materials is a detailed and complete inventory of all the components present within a system or, in the case of certification, a container image.

Bom is the CNCF’s official tool for performing this task and obtaining a file containing all this information.

There are a few commands to keep in mind:

# List options
bom --help

# Creating a file with SBOM
bom generate --image=[image:tag] --output=sbon.spdx

# Check for the presence of a package in several images
for i in httpd:2.4.65 nginx:1.29.0 caddy:2.10.0; do bom generate --image=$i | grep [package]; done

Runtime security with Falco
#

When Falco is installed on the machine, and this will probably be the case during the exam, you can run it directly using the command: falco -U to speed up start-up.

You may be asked to add or modify a rule.

If you need to add fields to the message associated with a default Falco rule, you can find all the parameters via this link. For example, you may be asked to add a timestamp at the beginning of the message (%evt.time).

To avoid overwriting the Falco configuration, retrieve the rule to be modified from the /etc/falco/falco_rules.yaml file, then copy the block of code to the /etc/falco/falco_rules.local.yaml file and make the changes.

Finally, if you want to know the rules that are triggered by the tool, simply: falco -U | grep [Rule name].

EncryptionConfiguration and etcd
#

To encrypt secrets and other information within etcd, you will need an EncryptionConfiguration, an example is given here.

In most cases, you will be asked to encrypt secrets only, so it is necessary to clean up this configuration to make it look like this:

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
    providers:
      - aesgcm:
          keys:
            - name: key1
              secret: c2VjcmV0IGlzIHNlY3VyZQ==
      - identity: {} # Allows secrets to be read in plain text

I’ve inverted the aesgcm and identity providers to encrypt by default.

In addition, the --encryption-provider-config parameter needs to be added to the kube-apiserver by making it point to this file. Don’t forget to mount this configuration on a volume as indicated in step 3 of this documentation.

Finally, to retrieve the value of a secret from etcd, you can run this command:

ETCDCTL_API=3 etcdctl --cert [cert] --key [key] --cacert [ca] get secrets /registry/secrets/[namespace]/[secret name]

Audit Policy
#

As always, the Kubernetes documentation gives a very good example of an audit configuration file, you can take inspiration from it and adapt it to suit the requirement.

Don’t forget the --audit-policy-file parameter with the location of your file in the kube-apiserver as well as the volume mount for your configuration file. You can find all the parameters a little further on in the documentation.

Finally, restart the kube-apiserver so that the configuration is effective by moving the place of the static Pod YAML file:

cd /etc/kubernetes/manifests/
mv kube-apiserver.yaml ..
mv ../kube-apiserver.yaml .

ImagePolicyWebhook
#

The implementation of ImagePolicyWebhook within the kube-apiserver is well explained in this part of the documentation based on the template below:

apiVersion: apiserver.config.k8s.io/v1
kind: AdmissionConfiguration
plugins:
- name: ImagePolicyWebhook
  configuration:
    imagePolicy:
      kubeConfigFile: <path-to-kubeconfig-file>
      allowTTL: 50
      denyTTL: 50
      retryBackoff: 500
      defaultAllow: true # Set to false to test that the ImagePolicyWebhook has been set up correctly

As with the audit configuration, it is important to check that this file is actually mounted on a kube-apiserver volume, set the --admission-control-config-file parameter, and also add ImagePolicyWebhook to the admission-plugins:

apiVersion: v1
kind: Pod
metadata:
[...]
  name: kube-apiserver
  namespace: kube-system
spec:
  containers:
  - command:
    - kube-apiserver
    - --advertise-address=172.30.1.2
    - --allow-privileged=true
    - --authorization-mode=Node,RBAC
    - --client-ca-file=/etc/kubernetes/pki/ca.crt
    - --enable-admission-plugins=NodeRestriction # ImagePolicyWebhook to be added

Like this:

- --enable-admission-plugins=NodeRestriction,ImagePolicyWebhook

Don’t forget to restart the kube-apiserver as explained above in the Audit Policy section.

SecurityContext
#

The SecurityContext or security context is used to define the execution context of a Pod or a container within a Pod. The aim is often to restrict privileges or configure a specific user to run a container.

This documentation groups together most of the possible scenarios and shows a few reusable code snippets.

Key point: If a SecurityContext is defined both at the Pod level and at the container level within the same Pod, the SecurityContext of the container will overwrite that of the Pod for identical keys. This is explained here.

Remove malicious processes
#

To identify a process by a given port, you can use the netstat command.

This is often installed, but if it isn’t, you can run this command:

apt-get install -y net-tools

It’s easy to spot a process in question and identify it:

netstat -plantu | grep [port]

This command should return something like this:

tcp        0      0 0.0.0.0:660            0.0.0.0:*               LISTEN      20987/malware

20987 being the process identifier (PID) and malware the name of the executable.

Last but not least, kill the process and delete the malicious binary:

# Identify the binary path
$ ls -l /proc/20987/exe
lrwxrwxrwx 1 root root 0 Sep 23 15:49 /proc/20987/exe -> /usr/local/bin/malware
# Kill the process
$ kill -9 20987
# Remove binary
$ rm /usr/local/bin/malware

Pod-to-pod encryption
#

This new section of the exam includes two new tools: Cilium (already used above) and, especially, Istio.

Both enable Pod-to-Pod encryption with different configurations.

For Cilium: IPsec Transparent Encryption or WireGuard Transparent Encryption.

You can follow the documentation by using either the CLI or Helm to install and validate everything!

Istio uses Sidecar mode, Ambient mode is not included.

Don’t forget to redeploy the Pods once the annotation is in place.

kubectl label namespace [namespace] istio-injection=enabled
kubectl delete po --force --grace-period=0 [pods...]

CKS, the hardest certification?
#

Oh yes!

The CKS, as mentioned above, is a condensed version in terms of difficulty of the first two exams (CKAD and CKA), its scope is very wide and time is very, very precious! Handling critical components doesn’t make the task any easier either.

Practise, try to get good reflexes with the documentation so you know where to look, but above all, don’t panic! If a question proves too difficult or your tests don’t work, put it aside and try to come back to it later if you still have time.

And as always, good luck to those who want to take the certification! :-)