CKS — Kubernetes Security Specialist
Cluster Setup Starting From Default Deny
Goal
You get the order into your hands: make one namespace default deny and then reopen only the needed paths one at a time, and you write out yourself what to check in the control plane configuration file.
Why it matters
Kubernetes defaults are all open. A Pod with no policy at all can communicate with any Pod in the cluster, and it reaches the node's metadata address as is. So the first move of security design is not adding a rule that blocks something, but blocking everything and restoring only what is needed. If you reverse this order, you can never know "what you missed." Configuration file auditing follows the same idea. Reading one manifest is faster than observing a running process, and you can turn what is missing into a list.
This lab environment has no real data plane, so you can't confirm that a policy really cuts traffic. Grading checks whether the object's spec is written as intended. What the exam demands is also an exact spec.
Steps
- Create the namespace
cks-netand attach the labeltier=restricted. - Create the NetworkPolicy
default-deny-allincks-net. LeavepodSelectorempty and list bothIngressandEgressinpolicyTypes, but put no ingress/egress rules at all. - Create the NetworkPolicy
allow-dns-egressincks-net. It targets all Pods,policyTypesis onlyEgress, and the egress destination is the Pods labeledk8s-app=kube-dnsin the namespace labeledkubernetes.io/metadata.name=kube-system, with two ports: UDP 53 and TCP 53. - Create the NetworkPolicy
allow-frontend-to-apiincks-net. The target is theapp=apiPods, the allowed ingress source is theapp=frontendPods in the same namespace, and the port is TCP 8080. - Create the NetworkPolicy
deny-node-metadataincks-net. It targets all Pods,policyTypesisEgress, and the egress destination is anipBlockwithcidr: 0.0.0.0/0andexcept: [169.254.169.254/32]. - Create a Secret
shop-tlsof typekubernetes.io/tlsincks-net. Bothtls.crtandtls.keymust be non-empty. - Create the Ingress
shopincks-net. Thehostsofspec.tls[0]isshop.cks.local,secretNameisshop-tls,spec.rules[0].hostis alsoshop.cks.local, and the path/is routed to port 8080 of the Serviceapi. - Write a static Pod manifest that reflects the CIS recommendations in
/root/cks-cluster-setup/kube-apiserver.yaml. It must havekind: Pod, the container namekube-apiserver, thecommandarray containing--anonymous-auth=false,--authorization-mode=Node,RBAC,--profiling=false,--audit-log-path=/var/log/kubernetes/audit.log, and--enable-admission-plugins=NodeRestriction,PodSecurity, and no flag starting with--insecure-portat all.
Notes
- After
kubectl create ns cks-net, runkubectl label ns cks-net tier=restricted - You can't create a policy draft with
kubectl create networkpolicy. It is faster to write the YAML yourself, checking the fields withkubectl explain networkpolicy.spec.egress. - Self-signed certificate example:
openssl req -x509 -newkey rsa:2048 -nodes -keyout tls.key -out tls.crt -days 365 -subj "/CN=shop.cks.local", thenkubectl create secret tls shop-tls --cert=tls.crt --key=tls.key -n cks-net - Common mistake 1: if you apply default deny and forget DNS egress, the application dies not with "connection refused" but with "name not found."
- Common mistake 2: if the
exceptrange is outside thecidr, the API server rejects the policy.
Create the namespace to isolate
Create the namespace cks-net and attach the label tier=restricted.
Create the namespace and attach the label. Write kubectl label ns after kubectl create ns, or you can create it with a single manifest.
A default policy that blocks all ingress and egress
Create the NetworkPolicy default-deny-all in cks-net. Leave podSelector empty and list both
Ingress and Egress in policyTypes, but put no ingress/egress rules at all.
podSelector: {} selects all Pods in the namespace. If you list a direction in policyTypes but leave out the ingress/egress rule list entirely, that direction is entirely denied.
Egress that opens only DNS as an exception
Create the NetworkPolicy allow-dns-egress in cks-net. It targets all Pods,
policyTypes is only Egress, and the egress destination is the Pods labeled k8s-app=kube-dns in the namespace
labeled kubernetes.io/metadata.name=kube-system, with two ports: UDP 53 and TCP 53.
The kube-dns Pods are in the kube-system namespace and carry the label k8s-app=kube-dns. You can select the namespace by the kubernetes.io/metadata.name label. Port 53 is needed for both UDP and TCP.
Ingress that opens only the needed Pod pair
Create the NetworkPolicy allow-frontend-to-api in cks-net. The target is the app=api Pods,
the allowed ingress source is the app=frontend Pods in the same namespace, and the port is TCP 8080.
Select the receiving side with podSelector, and select the sending side with ingress[].from[].podSelector. If you don't list a port, all ports are open.
Carve out the path to node metadata
Create the NetworkPolicy deny-node-metadata in cks-net. It targets all Pods, policyTypes is
Egress, and the egress destination is an ipBlock with cidr: 0.0.0.0/0 and except: [169.254.169.254/32].
An ipBlock can have an except list attached to a single cidr, and the range in except must be inside the cidr.
A TLS Secret for Ingress
Create a Secret shop-tls of type kubernetes.io/tls in cks-net. Both tls.crt and tls.key must be
non-empty.
A Secret of type kubernetes.io/tls must have both the tls.crt and tls.key keys. You can create a self-signed certificate with openssl, or put base64 values directly into the manifest.
Connect TLS to the Ingress
Create the Ingress shop in cks-net. The hosts of spec.tls[0] is shop.cks.local,
secretName is shop-tls, spec.rules[0].host is also shop.cks.local, and the path / is routed to
port 8080 of the Service api.
In an Ingress's spec.tls, you write the hosts list paired with a secretName. If the host in spec.rules and spec.tls[].hosts don't match, the certificate isn't attached.
Audit the kube-apiserver manifest
Write a static Pod manifest that reflects the CIS recommendations in /root/cks-cluster-setup/kube-apiserver.yaml.
It must have kind: Pod, the container name kube-apiserver, the command array containing
--anonymous-auth=false, --authorization-mode=Node,RBAC, --profiling=false,
--audit-log-path=/var/log/kubernetes/audit.log, and
--enable-admission-plugins=NodeRestriction,PodSecurity, and
no flag starting with --insecure-port at all.
A static Pod manifest is kind: Pod, and the flags go in the spec.containers[0].command array. CIS recommends blocking anonymous authentication, Node+RBAC authorization, disabling profiling, and the NodeRestriction admission plugin, and flags for an unauthenticated plaintext port must not be present at all.