El CKS (Certified Kubernetes Security Specialist) és la tercera i més exigent de les certificacions de Kubernetes de la CNCF. Certifica que saps endurir un clúster, detectar el que hi passa a dins i respondre quan alguna cosa va malament. És la certificació que acredita el perfil que, a Rutas Norte, s'assegura que postgres-reserves —que desa dades personals dels viatgers— estigui xifrat, aïllat, auditat i vigilat.
Té una particularitat que el separa dels altres dos exàmens: per presentar-t'hi necessites tenir el CKA vigent. No és una recomanació, és un requisit administratiu. I hi ha una raó: el CKS assumeix que ja saps administrar el clúster i es dedica exclusivament a assegurar-lo, amb eines externes que no apareixen als altres exàmens.
Tot el contingut d'aquesta lliçó té un enfocament estrictament defensiu: endurir, detectar i respondre. No s'ensenyen ni es descriuen tècniques ofensives.
Avís imprescindible. Preu, durada, nombre de tasques, percentatge d'aprovat, versió de Kubernetes avaluada i repartiment de dominis canvien amb el temps. El que segueix és orientatiu. Consulta sempre el temari oficial vigent al web de la Linux Foundation / CNCF (
training.linuxfoundation.org,cncf.io/certification/cks) abans de matricular-te.Avís de seguretat. Les configuracions que apareixen aquí són material d'estudi orientat a un examen. Abans d'aplicar qualsevol política de seguretat a un entorn real, l'ha de revisar un professional de seguretat amb coneixement del context, del model d'amenaces i dels requisits regulatoris de l'organització.
Contingut
- Què certifica el CKS i el requisit del CKA vigent
- El format de l'examen i per què és el més exigent
- Els dominis del temari i el seu pes orientatiu
- Mapa complet: cada objectiu del CKS i la seva lliçó en aquest curs
- Les eines que el CKS exigeix manejar
- Procediments que cal executar sense dubtar
- Pla d'estudi i trampes de l'examen
- Vuit tasques tipus CKS resoltes
- Què certifica el CKS i el requisit del CKA vigent
1.1 El perfil
El CKS acredita competència per assegurar aplicacions i plataformes basades en contenidors durant tot el seu cicle de vida: construcció, desplegament i execució. En termes de Rutas Norte:
| Fase | Què es certifica | Exemple |
|---|---|---|
| Construcció | Que les imatges que entren al clúster són netes i verificables | Escanejar rutas-norte/api-reserves:2.4 amb Trivy i signar-la abans de publicar-la |
| Desplegament | Que el que s'admet al clúster compleix una política | Impedir que un pod privilegiat arribi a rutas-norte-pro |
| Execució | Que es detecta i es respon al que és anòmal | Detectar una shell oberta dins d'un contenidor de producció i aïllar el pod |
| Plataforma | Que el clúster en si està endurit | Xifrar Secrets a etcd, auditar l'API, restringir RBAC |
1.2 El requisit de partida
Has de tenir un CKA vigent (no caducat) en el moment de presentar-te al CKS. Conseqüències pràctiques:
- No pots fer el CKS primer. L'ordre és forçós: CKA → CKS.
- Si el teu CKA caduca (la validesa ronda els dos anys) abans que facis l'examen del CKS, no t'hi podràs presentar fins a renovar-lo.
- La certificació CKS, un cop obtinguda, té la seva pròpia validesa independent.
- Verifica al handbook oficial com es comprova aquest requisit i amb quanta antelació.
Consell de planificació: no deixis passar massa temps entre el CKA i el CKS. A més del risc de caducitat, el CKS reutilitza molt del CKA (RBAC, kubeadm, pods estàtics, diagnòstic) i és una llàstima perdre aquest estat de forma.
1.3 En què es diferencia del CKA i del CKAD
| Aspecte | CKA | CKAD | CKS |
|---|---|---|---|
| Pregunta central | Funciona el clúster? | Funciona la meva aplicació? | És segur tot això? |
| Eines externes | Cap | Cap | kube-bench, Trivy, Falco, AppArmor, seccomp, Kyverno/OPA, gVisor |
| Documentació permesa | kubernetes.io | kubernetes.io | kubernetes.io + docs de Trivy, Falco i AppArmor |
| Prerequisit | Cap | Cap | CKA vigent |
| Nre. de tasques orientatiu | 15-20 | 15-20 | 15-16 (menys, però més llargues) |
| Dificultat percebuda | Mitjana-alta | Mitjana | Alta |
- El format de l'examen i per què és el més exigent
2.1 Característiques
| Aspecte | Descripció orientativa |
|---|---|
| Tipus | 100 % pràctic, terminal al navegador, supervisat |
| Durada | Al voltant de dues hores |
| Nombre de tasques | Aproximadament 15-16 |
| Aprovat | Aproximadament un 67 % |
| Clústers | Uns quants, amb canvi de context per tasca |
| Puntuació | Per tasca, amb puntuacions parcials |
| Documentació permesa | kubernetes.io/docs, més els webs oficials de Trivy, Falco i AppArmor |
| Prerequisit | CKA vigent |
| Validesa | Al voltant de dos anys |
Orientatiu. Verifica-ho al web oficial, inclosa la llista exacta de dominis de documentació permesos, que ha variat entre revisions del programa.
2.2 Per què es considera el més dur
Quatre raons concretes:
- Eines fora de Kubernetes. Has de saber invocar
kube-bench, llegir-ne la sortida i arreglar el que assenyala; executartrivy imagei interpretar severitats; escriure una regla de Falco i saber on viu el seu fitxer de configuració. Res d'això és akubectl. - Tasques llargues. Amb ~15 tasques en 120 minuts són ~8 minuts de mitjana, però una sola tasca pot demanar-te editar el manifest de l'apiserver, reiniciar el pla de control i esperar que torni. Els temps morts compten.
- Feina al node, no només a l'API. AppArmor, seccomp,
audit-policy, xifratge d'etcd ikube-benches configuren ambsshisudosobre fitxers del sistema. - La fallada és catastròfica si t'equivoques. Un error a
/etc/kubernetes/manifests/kube-apiserver.yamldeixa l'API caiguda i bloqueja la resta de les tasques d'aquell clúster. Cal saber-se'n recuperar.
2.3 La regla que salva l'examen: còpia de seguretat abans de tocar
Abans d'editar qualsevol fitxer crític del pla de control:
Si l'API no torna després del canvi, restaures i ho tornes a intentar:
Compte amb on deses la còpia. Mai dins de /etc/kubernetes/manifests/: el kubelet intentaria arrencar el fitxer .bak com un pod estàtic addicional i tindries dos apiservers barallant-se pel port. Desa-la a /root/ o /tmp/.
2.4 Com comprovar que l'apiserver ha tornat
# El pla de control triga entre 20 i 60 segons a recuperar-se
sudo crictl ps | grep kube-apiserver
kubectl get nodesSi kubectl respon connection refused, mira els logs del contenidor:
- Els dominis del temari i el seu pes orientatiu
Repartiment publicat en el moment d'escriure; verifica'l al web oficial.
| Domini | Pes orientatiu | De què va |
|---|---|---|
| Configuració del clúster (Cluster Setup) | ~15 % | NetworkPolicies, CIS benchmark, Ingress amb TLS, accés al node, verificació de binaris |
| Enduriment del clúster (Cluster Hardening) | ~15 % | RBAC mínim, ServiceAccounts, restricció de l'accés a l'API, actualitzacions freqüents |
| Enduriment del sistema (System Hardening) | ~10 % | Minimitzar la superfície del SO, IAM, restringir accés de xarxa, AppArmor, seccomp |
| Minimitzar vulnerabilitats en microserveis | ~20 % | Contextos de seguretat, OS-level security domains, Secrets, sandboxing (gVisor), mTLS |
| Cadena de subministrament de la imatge (Supply Chain) | ~20 % | Imatges base mínimes, llistes de registres permesos, signatura i validació, anàlisi estàtica, escaneig |
| Monitoratge, registre i seguretat en temps d'execució | ~20 % | Anàlisi de comportament, detecció d'amenaces, immutabilitat, logs d'auditoria |
Els tres dominis de més pes (microserveis, cadena de subministrament i runtime, ~60 % entre els tres) són precisament els que vas treballar a les lliçons 08-02, 08-05 i 08-06.
- Mapa complet: cada objectiu del CKS i la seva lliçó en aquest curs
4.1 Configuració del clúster (~15 %)
| Objectiu oficial | Lliçó del curs |
|---|---|
| Fer servir seguretat de xarxa a nivell de xarxa (NetworkPolicies) | 04-06-politiques-de-xarxa, 08-04-seguretat-de-xarxa |
| Fer servir el CIS benchmark per revisar la configuració dels components | 08-06-auditoria-escaneig-i-vulnerabilitats |
| Configurar objectes Ingress correctament amb seguretat TLS | 04-04-controladors-dingress, 04-05-tls-i-certificats-amb-cert-manager |
| Protegir les metadades i els endpoints del node | 08-04-seguretat-de-xarxa |
| Minimitzar l'ús i l'accés als elements de la interfície gràfica | 08-01-control-dacces-basat-en-rols |
| Verificar binaris de plataforma abans del desplegament | 08-05-seguretat-dimatges |
4.2 Enduriment del clúster (~15 %)
| Objectiu oficial | Lliçó del curs |
|---|---|
| Restringir l'accés a l'API de Kubernetes | 08-01-control-dacces-basat-en-rols, 03-06-serviceaccounts-i-acces-a-la-api |
| Fer servir RBAC per minimitzar l'exposició | 08-01-control-dacces-basat-en-rols |
| Anar amb compte amb les ServiceAccounts (deshabilitar la default, tokens) | 03-06-serviceaccounts-i-acces-a-la-api |
| Actualitzar Kubernetes amb freqüència | 10-02-kubeadm |
4.3 Enduriment del sistema (~10 %)
| Objectiu oficial | Lliçó del curs |
|---|---|
| Minimitzar la petjada del sistema operatiu amfitrió | 08-02-contextos-de-seguretat-i-enduriment |
| Minimitzar els rols d'IAM | 08-01-control-dacces-basat-en-rols, 10-06-kubernetes-gestionat-eks-aks-gke |
| Minimitzar l'accés de xarxa extern | 04-06-politiques-de-xarxa, 08-04-seguretat-de-xarxa |
| Fer servir eines d'enduriment del nucli: AppArmor, seccomp | 08-02-contextos-de-seguretat-i-enduriment, 08-03-politiques-de-seguretat-de-pods |
4.4 Minimitzar vulnerabilitats en microserveis (~20 %)
| Objectiu oficial | Lliçó del curs |
|---|---|
| Aplicar dominis de seguretat a nivell de SO (PSA, securityContext) | 08-02-contextos-de-seguretat-i-enduriment, 08-03-politiques-de-seguretat-de-pods |
| Gestionar els Secrets de Kubernetes | 03-02-secrets, 08-06-auditoria-escaneig-i-vulnerabilitats |
| Fer servir entorns d'execució en sandbox (gVisor, kata) | 08-02-contextos-de-seguretat-i-enduriment |
| Implementar xifratge pod-a-pod amb mTLS | 08-04-seguretat-de-xarxa |
4.5 Cadena de subministrament de la imatge (~20 %)
| Objectiu oficial | Lliçó del curs |
|---|---|
| Minimitzar la petjada de la imatge base | 08-05-seguretat-dimatges |
| Restringir els registres permesos | 08-05-seguretat-dimatges, 08-03-politiques-de-seguretat-de-pods |
| Signar i validar imatges (Cosign) | 08-05-seguretat-dimatges |
| Fer servir anàlisi estàtica de les càrregues de treball | 08-06-auditoria-escaneig-i-vulnerabilitats, 11-03-cicd-amb-kubernetes |
| Escanejar imatges a la recerca de vulnerabilitats conegudes | 08-06-auditoria-escaneig-i-vulnerabilitats |
4.6 Monitoratge, registre i seguretat en temps d'execució (~20 %)
| Objectiu oficial | Lliçó del curs |
|---|---|
| Anàlisi de comportament a nivell de syscall i procés | 08-06-auditoria-escaneig-i-vulnerabilitats (Falco) |
| Detectar amenaces en infraestructura, apps, xarxes i emmagatzematge | 08-06-auditoria-escaneig-i-vulnerabilitats, 07-04-visualitzacio-i-alertes-amb-grafana |
| Detectar totes les fases d'un atac, allà on passi | 08-06-auditoria-escaneig-i-vulnerabilitats, 11-06-operacio-en-produccio |
| Fer anàlisi forense profunda d'activitats sospitoses | 07-06-depuracio-i-esdeveniments-del-cluster, 08-06-auditoria-escaneig-i-vulnerabilitats |
| Assegurar la immutabilitat dels contenidors en execució | 08-02-contextos-de-seguretat-i-enduriment |
| Fer servir els logs d'auditoria per monitorar l'accés | 08-06-auditoria-escaneig-i-vulnerabilitats |
- Les eines que el CKS exigeix manejar
Aquestes nou peces van aparèixer al mòdul 8. Aquí les repassem orientades a l'examen: la comanda exacta, el fitxer exacte i la comprovació exacta.
5.1 kube-bench: l'estàndard CIS
kube-bench comprova la configuració del clúster contra el CIS Kubernetes Benchmark i et diu què està malament i com arreglar-ho.
# Executar-lo sobre el node de control
kube-bench run --targets=master
# Sobre un node treballador
kube-bench run --targets=node
# Només un control concret
kube-bench run --targets=master --check=1.2.20Sortida típica:
[INFO] 1 Control Plane Security Configuration
[INFO] 1.2 API Server
[FAIL] 1.2.20 Ensure that the --profiling argument is set to false (Automated)
[PASS] 1.2.21 Ensure that the --audit-log-path argument is set (Automated)
== Remediations master ==
1.2.20 Edit the API server pod specification file
/etc/kubernetes/manifests/kube-apiserver.yaml
on the control plane node and set the below parameter.
--profiling=falseEl que cal saber fer: llegir el [FAIL], anar a la secció == Remediations ==, aplicar exactament el que diu al fitxer que indica, i verificar tornant a executar kube-bench amb --check.
Els FAIL més habituals i el seu arranjament:
| Control | Arranjament a /etc/kubernetes/manifests/kube-apiserver.yaml |
|---|---|
--profiling |
- --profiling=false |
--anonymous-auth |
- --anonymous-auth=false |
--authorization-mode |
- --authorization-mode=Node,RBAC (mai AlwaysAllow) |
--audit-log-path |
- --audit-log-path=/var/log/kubernetes/audit.log |
--kubelet-certificate-authority |
Apuntar al CA del clúster |
--insecure-port |
Ja no existeix en versions modernes; si apareix, eliminar-lo |
I al kubelet (/var/lib/kubelet/config.yaml):
authentication:
anonymous:
enabled: false # mai true
webhook:
enabled: true
authorization:
mode: Webhook # mai AlwaysAllow
readOnlyPort: 0 # tancar el port 10255
protectKernelDefaults: trueDesprés d'editar-lo: sudo systemctl restart kubelet.
5.2 Trivy: escaneig d'imatges
# Escanejar una imatge
trivy image rutas-norte/api-reserves:2.4
# Només vulnerabilitats crítiques i altes
trivy image --severity CRITICAL,HIGH rutas-norte/api-reserves:2.4
# Només les que tenen pedaç disponible
trivy image --ignore-unfixed --severity CRITICAL nginx:1.27-alpine
# Sortida compacta, ideal per a l'examen
trivy image --severity CRITICAL --format table --quiet postgres:16
# Escanejar manifests de Kubernetes (anàlisi estàtica)
trivy config /ruta/a/manifests/
# Escanejar un clúster complet
trivy k8s --report summary clusterSortida:
rutas-norte/api-reserves:2.4 (alpine 3.20)
==========================================
Total: 3 (CRITICAL: 3)
┌────────────┬────────────────┬──────────┬───────────────────┬───────────────┐
│ Library │ Vulnerability │ Severity │ Installed Version │ Fixed Version │
├────────────┼────────────────┼──────────┼───────────────────┼───────────────┤
│ openssl │ CVE-2024-XXXXX │ CRITICAL │ 3.1.4-r5 │ 3.1.6-r0 │
└────────────┴────────────────┴──────────┴───────────────────┴───────────────┘Tasca típica de l'examen: "d'aquests quatre pods, elimina els que facin servir imatges amb vulnerabilitats CRITICAL". Procediment:
# 1. Treure les imatges dels pods del namespace
kubectl get pods -n rutas-norte-pro \
-o custom-columns='POD:.metadata.name,IMATGE:.spec.containers[*].image'
# 2. Escanejar-les una a una
for img in nginx:1.27-alpine postgres:16 redis:7-alpine node:20-alpine; do
echo "=== $img ==="
trivy image --severity CRITICAL --quiet "$img" | grep -c CVE || true
done
# 3. Eliminar els que correspongui
kubectl delete pod <nom> -n rutas-norte-pro5.3 Falco: detecció en temps d'execució
Falco observa les crides al sistema i dispara alertes quan el comportament coincideix amb una regla.
| Fitxer | Contingut |
|---|---|
/etc/falco/falco.yaml |
Configuració general: sortides, format, fitxers de regles carregats |
/etc/falco/falco_rules.yaml |
Regles per defecte (no editar) |
/etc/falco/falco_rules.local.yaml |
Les teves regles i sobreescriptures |
/etc/falco/rules.d/ |
Directori de regles addicionals |
# Estat i logs
sudo systemctl status falco
sudo journalctl -u falco -f
sudo journalctl -u falco --since "10 minutes ago" | grep Warning
# Recarregar després de canviar regles
sudo systemctl restart falco
# Validar la sintaxi de les regles sense arrencar
falco --validate /etc/falco/falco_rules.local.yamlUna sortida típica:
09:14:22.113 Notice A shell was spawned in a container with an attached terminal
(user=root container_id=a1b2c3d4 container_name=api-reserves
shell=sh parent=runc cmdline=sh)Modificar el format de sortida d'una regla existent (tasca molt freqüent): se sobreescriu a falco_rules.local.yaml.
# /etc/falco/falco_rules.local.yaml
- rule: Terminal shell in container
output: >
%evt.time,%user.name,%container.id,%container.name,%proc.name
override:
output: replaceEscriure una regla nova:
- rule: Escriptura sospitosa a /etc dins de contenidor
desc: Detecta escriptures a /etc dins d un contenidor
condition: >
container and
evt.type in (open, openat) and
evt.is_open_write=true and
fd.name startswith /etc
output: >
Escriptura a /etc detectada (usuari=%user.name contenidor=%container.name
fitxer=%fd.name comanda=%proc.cmdline)
priority: WARNING
tags: [filesystem, container]I desar la sortida on demani l'enunciat:
5.4 AppArmor aplicat a un pod
Flux complet, que cal saber de memòria:
# 1. Al NODE on correrà el pod: crear el perfil
sudo tee /etc/apparmor.d/k8s-rutas-norte-deny-write <<'EOF'
#include <tunables/global>
profile k8s-rutas-norte-deny-write flags=(attach_disconnected) {
#include <abstractions/base>
file,
deny /** w, # denegar tota escriptura
}
EOF
# 2. Carregar-lo en mode enforce
sudo apparmor_parser -q /etc/apparmor.d/k8s-rutas-norte-deny-write
# 3. Comprovar que està carregat
sudo aa-status | grep rutas-norte4. Aplicar-lo al pod. Des de Kubernetes 1.30 hi ha un camp natiu a securityContext (abans només existia l'anotació):
apiVersion: v1
kind: Pod
metadata:
name: worker-notificacions
namespace: rutas-norte-pro
spec:
containers:
- name: worker
image: busybox:1.36
command: ["sleep", "3600"]
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-rutas-norte-deny-writeForma antiga, encara acceptada i la que apareix en molts enunciats:
metadata:
annotations:
container.apparmor.security.beta.kubernetes.io/worker: localhost/k8s-rutas-norte-deny-writeComprovació:
kubectl exec worker-notificacions -n rutas-norte-pro -- touch /tmp/prova
# touch: /tmp/prova: Permission deniedTrampes: el perfil ha d'estar carregat al node on es planifiqui el pod (fes servir nodeName o etiquetes si el clúster en té uns quants); el nom a localhostProfile és el nom del perfil, no la ruta del fitxer; type: RuntimeDefault aplica el perfil per defecte del runtime i type: Unconfined desactiva AppArmor.
5.5 seccomp aplicat a un pod
apiVersion: v1
kind: Pod
metadata:
name: api-reserves-seccomp
namespace: rutas-norte-pro
spec:
securityContext:
seccompProfile:
type: RuntimeDefault # el perfil per defecte del runtime: el més comú
containers:
- name: api
image: nginx:1.27-alpineAmb un perfil propi, el fitxer ha de ser a /var/lib/kubelet/seccomp/ del node:
sudo mkdir -p /var/lib/kubelet/seccomp/profiles
sudo tee /var/lib/kubelet/seccomp/profiles/auditar.json <<'EOF'
{
"defaultAction": "SCMP_ACT_LOG"
}
EOF securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/auditar.json # ruta RELATIVA a /var/lib/kubelet/seccomp/type |
Significat |
|---|---|
RuntimeDefault |
Perfil per defecte del runtime (bloqueja syscalls perilloses). La resposta correcta el 80 % de les vegades. |
Localhost |
Perfil propi a /var/lib/kubelet/seccomp/<localhostProfile> |
Unconfined |
Sense restricció. Mai és la resposta que busquen. |
Trampa clàssica: posar la ruta absoluta a localhostProfile. És relativa al directori seccomp del kubelet.
5.6 Política d'auditoria de l'apiserver
És la tasca llarga per excel·lència. Dues parts: escriure la política i connectar-la a l'apiserver.
Part 1 — la política, a /etc/kubernetes/audit/policy.yaml:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived
rules:
# No registrar peticions de només lectura sorolloses
- level: None
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["events"]
# Secrets i ConfigMaps: només metadades, mai el contingut
- level: Metadata
resources:
- group: ""
resources: ["secrets", "configmaps"]
# Pods del namespace crític: cos complet de petició i resposta
- level: RequestResponse
namespaces: ["rutas-norte-pro"]
resources:
- group: ""
resources: ["pods"]
# Tota la resta, a nivell de metadades
- level: MetadataEls quatre nivells, que cal distingir:
| Nivell | Què es registra |
|---|---|
None |
Res |
Metadata |
Qui, què, quan, sobre quin recurs. Sense cossos. |
Request |
Metadades + cos de la petició |
RequestResponse |
Metadades + cos de petició i de resposta |
Part 2 — connectar-la a l'apiserver, a /etc/kubernetes/manifests/kube-apiserver.yaml:
spec:
containers:
- command:
- kube-apiserver
- --audit-policy-file=/etc/kubernetes/audit/policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=30
- --audit-log-maxbackup=10
- --audit-log-maxsize=100
volumeMounts:
- mountPath: /etc/kubernetes/audit
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit
name: audit-log
readOnly: false
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreateLa trampa que suspèn aquesta tasca: afegir els arguments i oblidar els volums. L'apiserver corre en un contenidor: si no muntes els directoris de l'amfitrió, no veu ni la política ni pot escriure el log, i entra en bucle de reinici.
Comprovació:
5.7 Pod Security Admission
El controlador d'admissió integrat que va substituir les PodSecurityPolicies. S'activa etiquetant el namespace.
| Nivell | Què permet |
|---|---|
privileged |
Tot. Sense restriccions. |
baseline |
Impedeix el que és òbviament perillós: privilegiats, hostNetwork, hostPID, capacitats afegides perilloses |
restricted |
Enduriment fort: runAsNonRoot, allowPrivilegeEscalation: false, drop: ALL, seccomp RuntimeDefault |
| Mode | Efecte |
|---|---|
enforce |
Rebutja els pods que no compleixen |
audit |
Els permet, però ho anota al log d'auditoria |
warn |
Els permet i avisa l'usuari al terminal |
kubectl label namespace rutas-norte-pro \
pod-security.kubernetes.io/enforce=restricted \
pod-security.kubernetes.io/enforce-version=v1.30 \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/audit=restricted \
--overwriteComprovació (un pod que no compleix ha de ser rebutjat):
Error from server (Forbidden): pods "insegur" is forbidden:
violates PodSecurity "restricted:v1.30": privileged (container "insegur"
must not set securityContext.privileged=true), allowPrivilegeEscalation != false,
unrestricted capabilities, runAsNonRoot != true, seccompProfileTrampa: si el namespace ja té pods que no compleixen, enforce no els expulsa; només bloqueja els nous. Fes servir warn i audit per detectar els existents.
5.8 Kyverno o OPA Gatekeeper
Quan la política que demanen va més enllà del que PSA cobreix (per exemple, "només imatges del registre corporatiu"), es fa servir un motor de polítiques.
Kyverno (sintaxi YAML, més directa):
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: nomes-registre-corporatiu
spec:
validationFailureAction: Enforce
background: false
rules:
- name: comprovar-registre
match:
any:
- resources:
kinds:
- Pod
namespaces:
- rutas-norte-pro
validate:
message: "Es permeten nomes imatges de registry.rutasnorte.es"
pattern:
spec:
containers:
- image: "registry.rutasnorte.es/*"# Exigir etiquetes obligatòries
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: exigir-etiqueta-propietari
spec:
validationFailureAction: Enforce
rules:
- name: comprovar-propietari
match:
any:
- resources:
kinds: ["Deployment", "StatefulSet"]
validate:
message: "Falta l'etiqueta 'propietari'"
pattern:
metadata:
labels:
propietari: "?*"OPA Gatekeeper (dos objectes: la plantilla amb Rego i la restricció):
apiVersion: templates.gatekeeper.sh/v1
kind: ConstraintTemplate
metadata:
name: k8sallowedrepos
spec:
crd:
spec:
names:
kind: K8sAllowedRepos
validation:
openAPIV3Schema:
type: object
properties:
repos:
type: array
items:
type: string
targets:
- target: admission.k8s.gatekeeper.sh
rego: |
package k8sallowedrepos
violation[{"msg": msg}] {
container := input.review.object.spec.containers[_]
satisfied := [good | repo := input.parameters.repos[_]
good := startswith(container.image, repo)]
not any(satisfied)
msg := sprintf("imatge no permesa: %v", [container.image])
}
---
apiVersion: constraints.gatekeeper.sh/v1beta1
kind: K8sAllowedRepos
metadata:
name: registres-permesos
spec:
match:
kinds:
- apiGroups: [""]
kinds: ["Pod"]
namespaces: ["rutas-norte-pro"]
parameters:
repos:
- "registry.rutasnorte.es/"Comprovació en tots dos casos:
kubectl run prova --image=docker.io/nginx -n rutas-norte-pro
# Error from server: admission webhook denied the request: ...5.9 gVisor mitjançant RuntimeClass
gVisor (runtime runsc) intercepta les crides al sistema en espai d'usuari, i aïlla el contenidor del nucli de l'amfitrió. Es fa servir quan la càrrega és poc fiable.
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: gvisor
handler: runsc # ha de coincidir amb el handler configurat a containerdapiVersion: v1
kind: Pod
metadata:
name: carrega-no-fiable
namespace: rutas-norte-pre
spec:
runtimeClassName: gvisor
containers:
- name: app
image: nginx:1.27-alpineComprovació (el nucli vist des de dins és el de gVisor, no el de l'amfitrió):
Trampa: la RuntimeClass no instal·la res. El handler runsc ha d'estar ja configurat a /etc/containerd/config.toml del node. A l'examen, normalment ja hi és i només cal crear la RuntimeClass i assignar-la.
5.10 NetworkPolicies de denegació per defecte
El patró que cal escriure de memòria:
# 1. Denegar TOT al namespace
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: denegar-tot
namespace: rutas-norte-pro
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress# 2. Permetre DNS (imprescindible, s'oblida sempre)
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: permetre-dns
namespace: rutas-norte-pro
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53# 3. Permetre només el que cal
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-a-postgres
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
app: api-reserves
policyTypes:
- Egress
egress:
- to:
- podSelector:
matchLabels:
app: postgres-reserves
ports:
- protocol: TCP
port: 5432Trampa mortal: aplicar la denegació total i oblidar el DNS. Tot el namespace deixa de resoldre noms i els símptomes semblen una altra cosa completament diferent.
- Procediments que cal executar sense dubtar
Sis coreografies que cauen gairebé segur. Practica-les fins que surtin sense documentació.
6.1 ServiceAccount amb RBAC mínim
# 1. La ServiceAccount
kubectl create serviceaccount app-reserves -n rutas-norte-pro
# 2. El Role amb el mínim imprescindible
kubectl create role app-reserves-role \
--verb=get,list \
--resource=configmaps \
--resource-name=config-api \
-n rutas-norte-pro
# 3. El binding
kubectl create rolebinding app-reserves-binding \
--role=app-reserves-role \
--serviceaccount=rutas-norte-pro:app-reserves \
-n rutas-norte-pro
# 4. Assignar-la al Deployment
kubectl set serviceaccount deployment/api-reserves app-reserves -n rutas-norte-proComprovació:
kubectl auth can-i get configmap/config-api \
--as=system:serviceaccount:rutas-norte-pro:app-reserves -n rutas-norte-pro # yes
kubectl auth can-i get configmaps \
--as=system:serviceaccount:rutas-norte-pro:app-reserves -n rutas-norte-pro # no (list de tots)
kubectl auth can-i create pods \
--as=system:serviceaccount:rutas-norte-pro:app-reserves -n rutas-norte-pro # noL'ús de --resource-name és la clau del "mínim privilegi" i una resposta molt valorada.
6.2 Desactivar el muntatge automàtic del token
Tres nivells, i cal saber quin demana l'enunciat:
# A) A la ServiceAccount: afecta tots els pods que la facin servir
apiVersion: v1
kind: ServiceAccount
metadata:
name: app-reserves
namespace: rutas-norte-pro
automountServiceAccountToken: false# B) Al Pod: guanya sobre la ServiceAccount
apiVersion: v1
kind: Pod
metadata:
name: api-reserves
namespace: rutas-norte-pro
spec:
serviceAccountName: app-reserves
automountServiceAccountToken: false
containers:
- name: api
image: rutas-norte/api-reserves:2.4# C) Apedaçar la ServiceAccount "default" del namespace (tasca molt típica)
kubectl patch serviceaccount default -n rutas-norte-pro \
-p '{"automountServiceAccountToken": false}'Comprovació:
kubectl exec api-reserves -n rutas-norte-pro -- ls /var/run/secrets/kubernetes.io/serviceaccount
# ls: ...: No such file or directory6.3 Xifrar Secrets en repòs a etcd
Pas 1 — el fitxer de configuració, a /etc/kubernetes/enc/enc.yaml:
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: clau1
secret: <CLAU_BASE64_DE_32_BYTES>
- identity: {}La clau es genera així:
Ordre dels proveïdors (això és el que es pregunta):
- El primer de la llista és el que es fa servir per xifrar.
- Tots es fan servir per desxifrar, en ordre.
identity: {}significa "sense xifrar". Si va primer, tot s'escriu en clar.
Pas 2 — connectar-lo a l'apiserver:
- --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
volumeMounts:
- name: enc
mountPath: /etc/kubernetes/enc
readOnly: true
volumes:
- name: enc
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreatePas 3 — rexifrar els Secrets ja existents (no es xifren sols):
Comprovació (el valor a etcd ja no està en clar):
sudo ETCDCTL_API=3 etcdctl get /registry/secrets/rutas-norte-pro/credencials-postgres \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key | hexdump -C | head -300000000 2f 72 65 67 69 73 74 72 79 2f 73 65 63 72 65 74 |/registry/secret|
00000020 6b 38 73 3a 65 6e 63 3a 61 65 73 63 62 63 3a 76 |k8s:enc:aescbc:v|La marca k8s:enc:aescbc:v1: confirma que està xifrat. Sense xifratge veuries el valor llegible.
6.4 Endurir un pod insegur que et donen fet
Aquesta és la tasca CKS per antonomàsia: et donen un manifest i et demanen deixar-lo conforme. La llista de comprovació mental:
| Què buscar | Què posar |
|---|---|
privileged: true |
privileged: false (o eliminar) |
hostNetwork, hostPID, hostIPC en true |
Eliminar-los |
hostPath a rutes del sistema |
Substituir per emptyDir o eliminar |
Falta runAsNonRoot |
runAsNonRoot: true + runAsUser: <no-0> |
Falta allowPrivilegeEscalation |
allowPrivilegeEscalation: false |
Capacitats afegides (SYS_ADMIN, NET_ADMIN...) |
capabilities: {drop: ["ALL"]} |
| Sistema de fitxers escrivible | readOnlyRootFilesystem: true |
| Sense perfil seccomp | seccompProfile: {type: RuntimeDefault} |
| Token muntat innecessàriament | automountServiceAccountToken: false |
| Ports privilegiats sense necessitat | Revisar |
Resultat tipus, conforme al nivell restricted:
apiVersion: v1
kind: Pod
metadata:
name: api-reserves-endurit
namespace: rutas-norte-pro
spec:
automountServiceAccountToken: false
securityContext:
runAsNonRoot: true
runAsUser: 10001
runAsGroup: 10001
fsGroup: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: api
image: registry.rutasnorte.es/api-reserves:2.4
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Nota pràctica: amb readOnlyRootFilesystem: true, gairebé qualsevol aplicació necessita un emptyDir a /tmp per funcionar.
6.5 Aïllar un pod sospitós i aturar l'anàlisi
Procediment de resposta davant d'un pod amb comportament anòmal. Enfocament defensiu: contenir, preservar evidències, restaurar.
# 1. Aïllar-lo del Service (traient l'etiqueta que el selecciona)
kubectl label pod api-reserves-abc123 -n rutas-norte-pro app-
# 2. Tallar-li la xarxa completament amb una NetworkPolicy dirigida
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: quarantena-api-abc123
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
quarantena: "true"
policyTypes:
- Ingress
- Egress
EOF
kubectl label pod api-reserves-abc123 -n rutas-norte-pro quarantena=trueSense regles ingress ni egress, el pod queda completament aïllat però continua viu per a l'anàlisi. Aquest és el punt: no l'esborris encara.
# 3. Preservar evidències
kubectl logs api-reserves-abc123 -n rutas-norte-pro > /opt/evidencies/logs.txt
kubectl describe pod api-reserves-abc123 -n rutas-norte-pro > /opt/evidencies/describe.txt
kubectl get events -n rutas-norte-pro --sort-by=.lastTimestamp > /opt/evidencies/esdeveniments.txt
sudo journalctl -u falco --since "1 hour ago" > /opt/evidencies/falco.txt
# 4. Quan l'anàlisi ha acabat: eliminar-lo
kubectl delete pod api-reserves-abc123 -n rutas-norte-pro --force --grace-period=0El Deployment recrearà un pod net. Si la causa era la imatge, cal corregir-la abans.
6.6 Trobar qui va fer què al registre d'auditoria
# Buscar accions d'un usuari concret
sudo grep '"username":"suport"' /var/log/kubernetes/audit/audit.log | tail -5
# Buscar accessos a Secrets
sudo cat /var/log/kubernetes/audit/audit.log | \
jq 'select(.objectRef.resource=="secrets") | {user:.user.username, verb:.verb, ns:.objectRef.namespace, name:.objectRef.name, t:.requestReceivedTimestamp}'
# Buscar eliminacions en un namespace
sudo cat /var/log/kubernetes/audit/audit.log | \
jq 'select(.verb=="delete" and .objectRef.namespace=="rutas-norte-pro") | {user:.user.username, res:.objectRef.resource, name:.objectRef.name}'
# Sense jq disponible
sudo grep '"verb":"delete"' /var/log/kubernetes/audit/audit.log | \
grep 'rutas-norte-pro' | tail -3Sortida esperada:
{"user":"suport","verb":"get","ns":"rutas-norte-pro","name":"credencials-postgres","t":"2026-08-06T09:12:44Z"}Els camps que cal conèixer del registre d'auditoria:
| Camp | Contingut |
|---|---|
user.username |
Qui |
verb |
Quina acció (get, list, create, delete, patch) |
objectRef.resource |
Sobre quin tipus de recurs |
objectRef.namespace / .name |
Sobre quin objecte exacte |
sourceIPs |
Des d'on |
responseStatus.code |
Si va tenir èxit (200/201) o va ser denegat (403) |
requestReceivedTimestamp |
Quan |
level |
Nivell amb què es va registrar |
- Pla d'estudi i trampes de l'examen
7.1 Pla de quatre setmanes (partint del CKA aprovat)
| Setmana | Focus | Què fer |
|---|---|---|
| 1 | Enduriment del clúster i del sistema | Repassar 08-01, 08-02, 08-03. Executar kube-bench i arreglar tots els FAIL. Practicar AppArmor i seccomp deu vegades cadascun. |
| 2 | Microserveis i xarxa | Repassar 04-06, 08-04. Escriure de memòria: denegar-tot, permetre-DNS, permetre-selectiu. Pod Security Admission en els tres nivells. Kyverno amb tres polítiques diferents. |
| 3 | Cadena de subministrament | Repassar 08-05, 08-06, 11-03. Escanejar amb Trivy deu imatges diferents. Signar i verificar amb Cosign. Política de registres permesos. Anàlisi estàtica de manifests. |
| 4 | Runtime, auditoria i simulacres | Repassar 08-06, 07-06. Escriure tres regles de Falco. Configurar auditoria des de zero cinc vegades. Xifrar etcd des de zero tres vegades. Simulacres cronometrats. |
Regla clau: les tasques d'"editar l'apiserver" (auditoria, xifratge, kube-bench) cal repetir-les fins que el cicle complet (editar → esperar → verificar → recuperar-se si falla) baixi de 8 minuts.
7.2 Les trampes de l'examen
| Trampa | Com evitar-la |
|---|---|
| Editar l'apiserver i deixar-lo trencat sense adonar-te'n | Còpia de seguretat a /root/ abans, i kubectl get nodes després |
Desar la còpia dins de /etc/kubernetes/manifests/ |
S'arrenca com a pod estàtic addicional. Desa-la fora. |
| Configurar auditoria sense muntar els volums | L'apiserver no arrenca. Arguments i volums, sempre. |
| Xifrar etcd i no rexifrar els Secrets existents | Els antics continuen en clar. kubectl get secrets -A -o json | kubectl replace -f - |
identity: {} primer a la llista de proveïdors |
No xifra res. El proveïdor de xifratge va primer. |
| Denegar tot el trànsit i oblidar el DNS | El namespace deixa de funcionar sencer |
| Perfil d'AppArmor carregat al node equivocat | Fixar el node amb nodeName o carregar-lo a tots |
Ruta absoluta a localhostProfile de seccomp |
És relativa a /var/lib/kubelet/seccomp/ |
Aplicar PSA restricted i esperar que expulsi pods existents |
Només bloqueja els nous |
Editar falco_rules.yaml en lloc de falco_rules.local.yaml |
Es perd a la següent actualització; i l'examen sol demanar el local |
| No reiniciar el servei després de canviar configuració | systemctl restart falco / restart kubelet |
| Perdre temps instal·lant eines | A l'examen ja hi són instal·lades. Busca-les amb which trivy, which kube-bench. |
7.3 Recursos oficials
| Recurs | Per a què |
|---|---|
| Pàgina oficial del CKS (Linux Foundation) | Temari vigent, requisits, preu, dominis de documentació permesos |
cncf/curriculum a GitHub |
El PDF amb els objectius exactes |
kubernetes.io/docs/concepts/security/ |
La secció de seguretat completa |
falco.org/docs/ |
Permesa a l'examen: sintaxi de regles i camps |
aquasecurity.github.io/trivy/ |
Permesa: flags i formats de sortida |
apparmor.net / manual d'AppArmor |
Permesa: sintaxi de perfils |
| Simulador inclòs amb la matrícula | Pràctica en entorn equivalent |
- Vuit tasques tipus CKS resoltes
Escenaris de Rutas Norte. Enfocament exclusivament defensiu.
Tasca 1 — Endurir un pod insegur (pes ~7 %, objectiu: 6 min)
Context:
rutas-norte-pro. El Podworker-notificacionscorre privilegiat, ambhostPIDi amb capacitats afegides. Modifica'l perquè compleixi l'estàndardrestricted: sense privilegis, sense escalada, sense capacitats, arrel de només lectura, usuari no root i perfil seccomp per defecte.
apiVersion: v1
kind: Pod
metadata:
name: worker-notificacions
namespace: rutas-norte-pro
spec:
# hostPID: true ← ELIMINAT
securityContext:
runAsNonRoot: true
runAsUser: 10001
seccompProfile:
type: RuntimeDefault
containers:
- name: worker
image: busybox:1.36
command: ["sleep", "3600"]
securityContext:
privileged: false
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
volumeMounts:
- name: tmp
mountPath: /tmp
volumes:
- name: tmp
emptyDir: {}Comprovació:
kubectl get pod worker-notificacions -n rutas-norte-pro
kubectl exec worker-notificacions -n rutas-norte-pro -- id
kubectl exec worker-notificacions -n rutas-norte-pro -- touch /arrelLa trampa: privileged, capabilities i readOnlyRootFilesystem van al securityContext del contenidor; runAsNonRoot i seccompProfile poden anar al del pod. I cal esborrar i recrear el pod: gairebé tot el securityContext és immutable.
Tasca 2 — Política d'auditoria (pes ~9 %, objectiu: 12 min)
Context:
rutas-norte-pro. Configura l'auditoria de l'apiserver per registrar a nivellMetadatatot accés a Secrets, a nivellRequestResponsetot canvi sobre Pods arutas-norte-pro, i res per a peticions de lectura d'esdeveniments. El log ha d'anar a/var/log/kubernetes/audit/audit.logi conservar-se 15 dies.
sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/apiserver.bak
sudo mkdir -p /etc/kubernetes/audit /var/log/kubernetes/audit
sudo tee /etc/kubernetes/audit/policy.yaml <<'EOF'
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages:
- RequestReceived
rules:
- level: None
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["events"]
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
- level: RequestResponse
namespaces: ["rutas-norte-pro"]
resources:
- group: ""
resources: ["pods"]
- level: Metadata
EOF
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml - --audit-policy-file=/etc/kubernetes/audit/policy.yaml
- --audit-log-path=/var/log/kubernetes/audit/audit.log
- --audit-log-maxage=15
volumeMounts:
- mountPath: /etc/kubernetes/audit
name: audit-policy
readOnly: true
- mountPath: /var/log/kubernetes/audit
name: audit-log
volumes:
- name: audit-policy
hostPath:
path: /etc/kubernetes/audit
type: DirectoryOrCreate
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreateComprovació:
sudo crictl ps | grep kube-apiserver
kubectl get secrets -n rutas-norte-pro
sudo tail -1 /var/log/kubernetes/audit/audit.log | jq '{user:.user.username,res:.objectRef.resource,level:.level}'La trampa: les regles s'avaluen en ordre i guanya la primera que casa. Si poses - level: Metadata (el catch-all) al principi, la resta no s'aplica mai. I sense els volums, l'apiserver no arrenca.
Tasca 3 — Xifrar Secrets a etcd (pes ~9 %, objectiu: 12 min)
Context:
rutas-norte-pro. Xifra els Secrets en repòs fent serviraescbc. Assegura't que el Secretcredencials-postgres, que ja existeix, queda xifrat.
sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/apiserver.bak
sudo mkdir -p /etc/kubernetes/enc
CLAU=$(head -c 32 /dev/urandom | base64)
sudo tee /etc/kubernetes/enc/enc.yaml <<EOF
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
providers:
- aescbc:
keys:
- name: clau1
secret: ${CLAU}
- identity: {}
EOF
sudo chmod 600 /etc/kubernetes/enc/enc.yaml
sudo vim /etc/kubernetes/manifests/kube-apiserver.yaml - --encryption-provider-config=/etc/kubernetes/enc/enc.yaml
volumeMounts:
- name: enc
mountPath: /etc/kubernetes/enc
readOnly: true
volumes:
- name: enc
hostPath:
path: /etc/kubernetes/enc
type: DirectoryOrCreate# Esperar que l'API torni
until kubectl get nodes >/dev/null 2>&1; do sleep 3; done
# Rexifrar els Secrets existents
kubectl get secrets -n rutas-norte-pro -o json | kubectl replace -f -Comprovació:
sudo ETCDCTL_API=3 etcdctl get /registry/secrets/rutas-norte-pro/credencials-postgres \
--cacert=/etc/kubernetes/pki/etcd/ca.crt \
--cert=/etc/kubernetes/pki/etcd/server.crt \
--key=/etc/kubernetes/pki/etcd/server.key | hexdump -C | head -2La trampa: sense el kubectl replace, els Secrets antics continuen en clar i la tasca no puntua. I identity: {} ha d'anar després d'aescbc, mai abans.
Tasca 4 — NetworkPolicy de denegació per defecte (pes ~7 %, objectiu: 7 min)
Context:
rutas-norte-pro. Aplica denegació per defecte de tot el trànsit d'entrada i sortida al namespace. Després, permet que els pods continuïn resolent DNS i queapi-reservesarribi apostgres-reservesal 5432.
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: denegar-tot
namespace: rutas-norte-pro
spec:
podSelector: {}
policyTypes: [Ingress, Egress]
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: permetre-dns
namespace: rutas-norte-pro
spec:
podSelector: {}
policyTypes: [Egress]
egress:
- to:
- namespaceSelector:
matchLabels:
kubernetes.io/metadata.name: kube-system
ports:
- protocol: UDP
port: 53
- protocol: TCP
port: 53
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: api-a-postgres
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
app: api-reserves
policyTypes: [Egress]
egress:
- to:
- podSelector:
matchLabels:
app: postgres-reserves
ports:
- protocol: TCP
port: 5432
---
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: postgres-des-de-api
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
app: postgres-reserves
policyTypes: [Ingress]
ingress:
- from:
- podSelector:
matchLabels:
app: api-reserves
ports:
- protocol: TCP
port: 5432
EOFComprovació:
kubectl exec deploy/api-reserves -n rutas-norte-pro -- nslookup postgres-reserves
kubectl exec deploy/api-reserves -n rutas-norte-pro -- nc -zv postgres-reserves 5432
kubectl exec deploy/botiga-web -n rutas-norte-pro -- nc -zv -w 3 postgres-reserves 5432 # ha de fallarLa trampa: cal obrir les dues direccions. L'egress d'api-reserves no n'hi ha prou si postgres-reserves té l'ingress denegat. I sense la política de DNS, ni tan sols es resol el nom.
Tasca 5 — Escaneig amb Trivy i retirada d'imatges vulnerables (pes ~6 %, objectiu: 6 min)
Context:
rutas-norte-pre. Escaneja les imatges dels pods del namespace. Elimina els pods la imatge dels quals tingui vulnerabilitats de severitat CRITICAL. Desa a/opt/vulnerables.txtla llista d'imatges descartades.
kubectl get pods -n rutas-norte-pre \
-o custom-columns='POD:.metadata.name,IMG:.spec.containers[*].image' --no-headersbotiga-web-1 nginx:1.27-alpine
api-reserves-1 node:18.0.0
redis-1 redis:7-alpine
worker-1 ubuntu:20.04> /opt/vulnerables.txt
for img in nginx:1.27-alpine node:18.0.0 redis:7-alpine ubuntu:20.04; do
n=$(trivy image --severity CRITICAL --quiet --format json "$img" \
| jq '[.Results[].Vulnerabilities // []] | flatten | length')
echo "$img -> CRITICAL: $n"
[ "$n" -gt 0 ] && echo "$img" >> /opt/vulnerables.txt
donenginx:1.27-alpine -> CRITICAL: 0
node:18.0.0 -> CRITICAL: 7
redis:7-alpine -> CRITICAL: 0
ubuntu:20.04 -> CRITICAL: 3La trampa: si els pods pertanyen a un Deployment, esborrar-los no serveix: es recreen amb la mateixa imatge. Cal escalar a 0 o esborrar el Deployment, segons demani l'enunciat. Llegeix-lo amb cura.
Tasca 6 — AppArmor sobre un pod (pes ~7 %, objectiu: 8 min)
Context:
rutas-norte-pro. Al nodenode-1hi ha un perfil d'AppArmor sense carregar a/opt/perfils/deny-write. Carrega'l en mode enforce i crea el Podauditor(imatgebusybox:1.36, comandasleep 3600) en aquell node amb aquell perfil aplicat.
El nom que apareix a aa-status és el que cal fer servir, no el del fitxer.
apiVersion: v1
kind: Pod
metadata:
name: auditor
namespace: rutas-norte-pro
spec:
nodeName: node-1
containers:
- name: auditor
image: busybox:1.36
command: ["sleep", "3600"]
securityContext:
appArmorProfile:
type: Localhost
localhostProfile: k8s-deny-writeComprovació:
kubectl get pod auditor -n rutas-norte-pro -o wide
kubectl exec auditor -n rutas-norte-pro -- touch /tmp/xLa trampa: nodeName: node-1 és imprescindible; si el pod es planifica en un altre node on el perfil no està carregat, queda en Blocked amb l'esdeveniment cannot enforce AppArmor profile. I si el pod queda en Pending, comprova que node-1 no estigui acordonat.
Tasca 7 — Regla de Falco i captura de l'evidència (pes ~8 %, objectiu: 9 min)
Context:
rutas-norte-pro. Falco està instal·lat anode-1. Modifica la sortida de la regla que detecta shells en contenidors perquè mostri exactamenthora,usuari,id-contenidor,nom-contenidor,proces, i desa a/opt/shells.logles deteccions dels últims 10 minuts.
ssh node-1
sudo tee -a /etc/falco/falco_rules.local.yaml <<'EOF'
- rule: Terminal shell in container
output: >
%evt.time,%user.name,%container.id,%container.name,%proc.name
override:
output: replace
EOF
sudo falco --validate /etc/falco/falco_rules.local.yaml
sudo systemctl restart falco
sudo systemctl status falco --no-pager | head -5# Provocar una detecció per verificar (des d'un altre terminal)
kubectl exec -it deploy/api-reserves -n rutas-norte-pro -- sh -c "echo prova"
# Capturar l'evidència
sudo journalctl -u falco --since "10 minutes ago" --no-pager \
| grep "Terminal shell" > /opt/shells.log
cat /opt/shells.logLa trampa: cal editar falco_rules.local.yaml, mai falco_rules.yaml. I sense systemctl restart falco el canvi no fa efecte. Si falco --validate dona error de sintaxi, arregla'l abans de reiniciar: un fitxer invàlid deixa el servei caigut.
Tasca 8 — Política d'admissió: només registres permesos (pes ~8 %, objectiu: 9 min)
Context:
rutas-norte-pro. Kyverno està instal·lat. Crea una política que rebutgi arutas-norte-proqualsevol Pod la imatge del qual no provingui deregistry.rutasnorte.es/. Demostra que funciona.
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: nomes-registre-corporatiu
spec:
validationFailureAction: Enforce
background: false
rules:
- name: comprovar-registre
match:
any:
- resources:
kinds:
- Pod
namespaces:
- rutas-norte-pro
validate:
message: "Es permeten nomes imatges de registry.rutasnorte.es/"
pattern:
spec:
=(initContainers):
- image: "registry.rutasnorte.es/*"
containers:
- image: "registry.rutasnorte.es/*"Comprovació:
Error from server: admission webhook "validate.kyverno.svc-fail" denied the request:
policy Pod/rutas-norte-pro/prova-ko for resource violation:
nomes-registre-corporatiu:
comprovar-registre: 'validation error: Es permeten nomes imatges de
registry.rutasnorte.es/'La trampa: validationFailureAction: Audit només registra; per rebutjar cal Enforce. I el prefix =() a initContainers significa "si existeix aquest camp, valida'l"; sense ell, un pod sense initContainers fallaria la validació.
Errors Comuns i Consells
Errors que costen punts
| Error | Conseqüència | Prevenció |
|---|---|---|
| Trencar l'apiserver sense còpia de seguretat | Perds totes les tasques d'aquell clúster | cp a /root/ abans d'editar |
Còpia de seguretat dins de manifests/ |
Dos apiservers en conflicte | Desa-la fora d'aquell directori |
| Arguments d'auditoria sense volums | L'apiserver no arrenca | Arguments i volumeMounts i volumes |
| Regla catch-all al principi de la política d'auditoria | Les altres no s'apliquen | L'ordre importa: d'específic a general |
identity: {} abans del proveïdor de xifratge |
No es xifra res | El xifrador va primer |
| No rexifrar els Secrets existents | La meitat de la tasca sense puntuar | kubectl get secrets -A -o json | kubectl replace -f - |
| Denegar tot i oblidar el DNS | El namespace queda inservible | Política de DNS sempre al costat de la de denegació |
Ruta absoluta a localhostProfile (seccomp) |
El pod no arrenca | Ruta relativa a /var/lib/kubelet/seccomp/ |
Editar falco_rules.yaml |
Canvi al fitxer equivocat | Fer servir falco_rules.local.yaml |
Oblidar systemctl restart després de canviar config |
El canvi no s'aplica | Reiniciar i verificar amb status |
Modificar securityContext amb kubectl edit |
Camps immutables: falla | Exportar, esborrar, recrear |
| Esborrar pods gestionats per un Deployment | Es recreen idèntics | Escalar a 0 o corregir la plantilla |
Consells
- Repeteix les tasques d'apiserver fins a automatitzar-les. Auditoria i xifratge són les que més punts valen i les que més temps consumeixen si dubtes.
- Tingues un
until kubectl get nodesa mà per esperar que l'API torni sense quedar-te mirant. - Comprova quines eines hi ha instal·lades tot just començar una tasca que les requereixi:
which trivy kube-bench falco. - La documentació de Falco i Trivy està permesa. Tingues localitzada la secció de camps de Falco (
%evt.time,%container.name, ...) i la de flags de Trivy. - Verifica sempre amb una prova negativa. No n'hi ha prou que el que està permès funcioni: cal veure que el que està prohibit falla.
- Pensa en defensa, no en atac. L'examen mesura endurir, detectar i respondre. Totes les respostes correctes van en aquesta direcció.
- Recorda el requisit: sense CKA vigent, no hi ha CKS.
Exercicis
Exercici 1 — Cicle complet d'enduriment de l'apiserver
En un clúster de pràctica muntat amb kubeadm, i cronometrant:
- Executa
kube-bench run --targets=masteri anota tots els[FAIL]de la secció 1.2. - Corregeix-ne almenys tres editant
/etc/kubernetes/manifests/kube-apiserver.yaml. - Verifica que l'API torna i que
kube-benchara marca[PASS]en aquests controls. - Fes còpia de seguretat del manifest abans de començar i demostra que saps restaurar-la.
Objectiu: 20 minuts, amb el clúster funcionant al final.
Exercici 2 — Resposta davant d'un pod sospitós
Simula el procediment complet de contenció a rutas-norte-pro:
- Crea un Deployment
api-reservesamb 3 rèpliques i un Service que el seleccioni. - Tria un dels pods com a "sospitós".
- Aïlla'l del Service sense matar-lo.
- Talla-li tota la comunicació de xarxa amb una NetworkPolicy dirigida, i deixa la resta del namespace funcionant.
- Preserva logs,
describei esdeveniments a/opt/evidencies/. - Comprova que el Service continua servint amb 2 endpoints i que el pod aïllat no té connectivitat.
Exercici 3 — Cadena de subministrament d'extrem a extrem
Per a la imatge nginx:1.27-alpine:
- Escaneja-la amb Trivy filtrant per CRITICAL i HIGH amb pedaç disponible.
- Crea una política de Kyverno que exigeixi que tots els pods de
rutas-norte-protinguinrunAsNonRoot: trueiallowPrivilegeEscalation: false. - Demostra que un pod que no compleix és rebutjat i que un que compleix és admès.
- Activa a més Pod Security Admission a nivell
restricteden modewarnsobrerutas-norte-prei comprova l'avís.
Solucions
Solució a l'Exercici 1
# 0. Còpia de seguretat SEMPRE primer
sudo cp /etc/kubernetes/manifests/kube-apiserver.yaml /root/apiserver.bak
# 1. Diagnòstic
kube-bench run --targets=master | grep -E '^\[FAIL\]' | head[FAIL] 1.2.20 Ensure that the --profiling argument is set to false
[FAIL] 1.2.21 Ensure that the --audit-log-path argument is set
[FAIL] 1.2.22 Ensure that the --audit-log-maxage argument is set to 30 or as appropriateRecordant que --audit-log-path necessita el seu volum:
volumeMounts:
- mountPath: /var/log/kubernetes/audit
name: audit-log
volumes:
- name: audit-log
hostPath:
path: /var/log/kubernetes/audit
type: DirectoryOrCreate# 3. Esperar i verificar
until kubectl get nodes >/dev/null 2>&1; do sleep 3; done
kubectl get nodes
kube-bench run --targets=master --check=1.2.20,1.2.21,1.2.22[PASS] 1.2.20 Ensure that the --profiling argument is set to false
[PASS] 1.2.21 Ensure that the --audit-log-path argument is set
[PASS] 1.2.22 Ensure that the --audit-log-maxage argument is set to 304. Restauració (per practicar la recuperació):
sudo cp /root/apiserver.bak /etc/kubernetes/manifests/kube-apiserver.yaml
until kubectl get nodes >/dev/null 2>&1; do sleep 3; doneL'error habitual en aquest exercici és afegir --audit-log-path sense el volum: l'apiserver arrenca i mor en bucle, i el diagnòstic és sudo crictl logs <id>.
Solució a l'Exercici 2
# 1. Muntatge
kubectl create deployment api-reserves --image=nginx:1.27-alpine \
--replicas=3 -n rutas-norte-pro
kubectl expose deployment api-reserves --port=80 -n rutas-norte-pro
kubectl get endpoints api-reserves -n rutas-norte-pro# 2-3. Triar i aïllar del Service
SOSPITOS=$(kubectl get pods -n rutas-norte-pro -l app=api-reserves \
-o jsonpath='{.items[0].metadata.name}')
kubectl label pod $SOSPITOS -n rutas-norte-pro app-
kubectl label pod $SOSPITOS -n rutas-norte-pro quarantena=trueCompte: en treure l'etiqueta app, el ReplicaSet el considera perdut i crea un pod nou. Això és el desitjable: el servei es recupera sol mentre el sospitós continua viu per a l'anàlisi.
# 4. Tall total de xarxa al pod en quarantena
cat <<'EOF' | kubectl apply -f -
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: quarantena
namespace: rutas-norte-pro
spec:
podSelector:
matchLabels:
quarantena: "true"
policyTypes: [Ingress, Egress]
EOF
# 5. Evidències
mkdir -p /opt/evidencies
kubectl logs $SOSPITOS -n rutas-norte-pro > /opt/evidencies/logs.txt
kubectl describe pod $SOSPITOS -n rutas-norte-pro > /opt/evidencies/describe.txt
kubectl get events -n rutas-norte-pro --sort-by=.lastTimestamp > /opt/evidencies/esdeveniments.txt
# 6. Verificació
kubectl get endpoints api-reserves -n rutas-norte-pro # 3 endpoints, cap el sospitós
kubectl exec $SOSPITOS -n rutas-norte-pro -- \
timeout 3 wget -qO- http://api-reserves || echo "aillat correctament"La NetworkPolicy sense regles ingress ni egress és un tall total. El pod continua existint, els seus logs es poden llegir (això passa per l'API, no per la seva xarxa) i el kubectl exec funciona pel mateix motiu.
Solució a l'Exercici 3
--ignore-unfixed és clau: filtra el soroll de vulnerabilitats sense pedaç disponible, que no pots remeiar actualitzant.
# 2. Política de Kyverno
apiVersion: kyverno.io/v1
kind: ClusterPolicy
metadata:
name: exigir-no-root
spec:
validationFailureAction: Enforce
background: false
rules:
- name: comprovar-securitycontext
match:
any:
- resources:
kinds: ["Pod"]
namespaces: ["rutas-norte-pro"]
validate:
message: "Els pods han de definir runAsNonRoot=true i allowPrivilegeEscalation=false"
pattern:
spec:
=(securityContext):
runAsNonRoot: true
containers:
- securityContext:
allowPrivilegeEscalation: falsekubectl apply -f exigir-no-root.yaml
# 3. Prova negativa
kubectl run dolent --image=nginx:1.27-alpine -n rutas-norte-pro
# Error from server: admission webhook denied the request: ...
# 3bis. Prova positiva
cat <<'EOF' | kubectl apply -f -
apiVersion: v1
kind: Pod
metadata:
name: bo
namespace: rutas-norte-pro
spec:
securityContext:
runAsNonRoot: true
runAsUser: 10001
containers:
- name: c
image: nginx:1.27-alpine
securityContext:
allowPrivilegeEscalation: false
EOF
kubectl get pod bo -n rutas-norte-pro# 4. Pod Security Admission en mode avís
kubectl label namespace rutas-norte-pre \
pod-security.kubernetes.io/warn=restricted \
pod-security.kubernetes.io/warn-version=v1.30 --overwrite
kubectl run avisat --image=nginx:1.27-alpine -n rutas-norte-preWarning: would violate PodSecurity "restricted:v1.30": allowPrivilegeEscalation != false,
unrestricted capabilities, runAsNonRoot != true, seccompProfile
pod/avisat createdFixa't en la diferència: Kyverno amb Enforce rebutja; PSA en mode warn avisa i crea. Saber triar el mecanisme i el mode segons el que demana l'enunciat és exactament el que mesura aquest domini.
Recordatori: aquestes polítiques són exercicis d'estudi. Abans d'aplicar alguna cosa semblant a un entorn real, que ho revisi un professional de seguretat amb coneixement del context de l'organització.
Conclusió
El CKS és la certificació que tanca el triangle: el CKA demostra que saps mantenir el clúster dret, el CKAD que saps construir-hi a sobre, i el CKS que saps defensar-lo. És el més exigent dels tres perquè et treu de kubectl i et porta als fitxers del node, a eines externes i a decisions on equivocar-se deixa el pla de control caigut.
L'essencial d'aquesta lliçó:
- El CKS exigeix CKA vigent per presentar-s'hi. No hi ha drecera possible.
- L'examen permet, a més de
kubernetes.io, la documentació de Trivy, Falco i AppArmor. Aprofita-ho. - Els tres dominis de més pes —microserveis, cadena de subministrament i runtime, ~60 % entre els tres— són els del mòdul 8 d'aquest curs.
- Nou eines cal manejar-les sense dubtar: kube-bench, Trivy, Falco, AppArmor, seccomp, audit-policy, Pod Security Admission, Kyverno/OPA i gVisor amb RuntimeClass, més les NetworkPolicies de denegació per defecte.
- Sis procediments cal tenir-los automatitzats: RBAC mínim, desactivar l'automuntatge del token, xifrar etcd, endurir un pod, aïllar un pod sospitós i llegir el registre d'auditoria.
- Abans de tocar l'apiserver: còpia de seguretat fora de
manifests/. Després: verificar que l'API torna. - Les trampes que més suspenen: volums oblidats a l'auditoria,
identityprimer al xifratge, Secrets sense rexifrar, DNS bloquejat per la denegació total i perfils d'AppArmor al node equivocat. - Consulta sempre el temari oficial vigent al web de la Linux Foundation / CNCF, inclosa la llista de documentació permesa, que ha canviat entre revisions.
- I recorda: qualsevol configuració de seguretat destinada a un entorn real l'ha de revisar un professional de seguretat.
Ja coneixes les tres certificacions, els seus temaris i la seva correspondència amb el que has estudiat. El que falta és la part que cap de les tres no ensenya i que decideix més aprovats del que sembla: la tècnica d'examen. A la propera lliçó veurem com es prepara l'entorn de supervisió, què teclejar als primers seixanta segons de l'examen, com fer servir la documentació permesa sense perdre temps, com repartir els minuts entre tasques, quins errors costen més punts i què fer —abans, durant i després— perquè el dia de l'examen no hi hagi sorpreses.
Curs de Kubernetes
Mòdul 1: Introducció a Kubernetes
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
