Hem tancat dues dimensions de la seguretat de Rutas Norte. Amb RBAC (08-01) controlem qui pot fer què contra l'API. Amb els contextos de seguretat (08-02) i les polítiques d'admissió (08-03) controlem què pot fer un contenidor un cop està corrent, i vam aconseguir que el clúster ho fes complir per si sol.

Queda una dimensió sencera: què pot parlar amb què. I aquí ja vam fer feina important a 04-06, quan vam posar una NetworkPolicy deny-all a rutas-norte-pro i vam autoritzar cada conversa una a una. Però aleshores vam assenyalar tres límits incòmodes que vam deixar aparcats:

  1. Les NetworkPolicies treballen a L3/L4: entenen d'IP i de ports, no de rutes HTTP ni de mètodes. api-reserves pot arribar a postgres-reserves, sí, però la política no distingeix entre una consulta legítima i un abocament complet de la taula de clients.
  2. No registren res. Un intent de connexió denegat és completament invisible. Si algú està sondejant la xarxa interna, no ens n'assabentem.
  3. Dins del clúster, tot el trànsit viatja sense xifrar un cop passat el TLS de l'Ingress (04-05). Qui pugui observar la xarxa del node veu les consultes a postgres-reserves en clar, amb noms, DNI i telèfons inclosos.

I hi ha una quarta cosa de la qual amb prou feines hem parlat: el trànsit sortint. És la via natural per la qual surt una base de dades de clients.

Aquesta lliçó no repeteix la sintaxi de NetworkPolicy: la dones per sabuda i la fem servir. El que construïm aquí és l'estratègia de xarxa completa d'una plataforma en producció, per capes.

Advertiment important. El disseny de l'arquitectura de xarxa d'una plataforma de producció l'ha de revisar un professional de seguretat, que avaluarà el model d'amenaces concret. I com que el trànsit de Rutas Norte transporta dades personals de clients —nom, DNI, telèfon i correu—, les decisions sobre xifratge en trànsit, control de sortida i registre de fluxos les han de conèixer i aprovar també el responsable de compliment normatiu. L'enfocament d'aquesta lliçó és exclusivament defensiu: entendre els camins pels quals es pot fugar informació per tancar-los i detectar-los.

Contingut

  1. Les capes de defensa en xarxa
  2. Microsegmentació com a principi
  3. Control del trànsit sortint
  4. El problema de les IP davant dels noms de domini
  5. Xifratge en trànsit dins del clúster
  6. Què és una malla de serveis i què costa
  7. Istio, Linkerd i Cilium comparats
  8. Polítiques d'autorització de nivell 7
  9. Protecció del perímetre
  10. Seguretat del pla de control
  11. Registre i visibilitat del trànsit
  12. Confiança zero aplicada a Rutas Norte
  13. Errors comuns i consells
  14. Exercicis
  15. Conclusió

  1. Les capes de defensa en xarxa

La seguretat de xarxa no és un mecanisme: és una sèrie de capes, cadascuna de les quals assumeix que l'anterior pot fallar. Aquest és el model complet que construirem.

flowchart TB
    I["Internet"] --> WAF["Capa 1: perímetre<br/>WAF + limitació de peticions<br/>+ protecció DoS"]
    WAF --> ING["Capa 2: Ingress<br/>TLS terminat (04-05)<br/>capçaleres de seguretat"]
    ING --> NP["Capa 3: microsegmentació<br/>NetworkPolicy deny-all + permisos<br/>explícits (04-06)"]
    NP --> MESH["Capa 4: mTLS + autorització L7<br/>identitat per càrrega de treball<br/>(malla de serveis)"]
    MESH --> APP["Càrregues de Rutas Norte"]
    APP --> EGR["Capa 5: control de sortida<br/>només la passarel·la de pagaments"]
    EGR --> EXT["pagos.proveedorexterno.example"]

    APP -.-> OBS["Capa 6: visibilitat<br/>registre de fluxos<br/>(Hubble)"]
    CP["Capa 7: pla de control<br/>apiserver, etcd, kubelet"] -.-> APP

    style NP fill:#d5e8f9,stroke:#36c
    style EGR fill:#f9d5d5,stroke:#c33
Capa Què mitiga Estat a Rutas Norte
1. Perímetre Atacs des d'internet, denegació de servei, abús Per construir
2. Ingress amb TLS Escolta del trànsit entre client i plataforma Fet (04-05)
3. Microsegmentació Moviment lateral després de comprometre un pod Fet (04-06), a reforçar
4. mTLS i autorització L7 Escolta interna, suplantació entre serveis Per decidir
5. Control de sortida Exfiltració de dades de clients Per construir
6. Visibilitat Ceguesa davant d'un incident Per construir
7. Pla de control Compromís total del clúster Per revisar

L'ordre importa: cada capa es justifica pel que passaria si l'anterior fallés. Si el WAF no atura un atac, l'Ingress encara valida el certificat. Si algú aconsegueix executar codi a botiga-web, la microsegmentació impedeix que arribi a postgres-reserves. Si hi arribés, el mTLS impediria que es fes passar per api-reserves. I si tot l'anterior fallés, el control de sortida impediria que les dades sortissin del clúster.

  1. Microsegmentació com a principi

La microsegmentació consisteix a tractar cada càrrega de treball com el seu propi segment de xarxa, amb regles explícites de què pot parlar amb què. És el contrari del model tradicional de "xarxa interna de confiança darrere d'un tallafocs".

Per què el model del perímetre no serveix a Kubernetes

En una xarxa clàssica, el tallafocs separava "dins" de "fora", i a dins tothom es parlava. En un clúster de Kubernetes, per defecte, tots els pods poden parlar amb tots els pods de tots els namespaces. Un curl des de botiga-web arriba a postgres-reserves sense cap obstacle.

Recorda el que vam dir a 02-06: un namespace no és una frontera de seguretat per si sol. Sense NetworkPolicies, rutas-norte-dev i rutas-norte-pro són a la mateixa xarxa plana.

La conseqüència pràctica: si algú aconsegueix executar codi a botiga-web —el component més exposat, perquè atén directament internet— té accés de xarxa a tota la plataforma. La microsegmentació converteix això en "té accés de xarxa a api-reserves, i només al port 8080".

Els tres principis

Principi 1: denegar per defecte als tres entorns.

A 04-06 vam aplicar deny-all a rutas-norte-pro. Això no n'hi ha prou. rutas-norte-dev i rutas-norte-pre també ho necessiten, per dues raons:

  • Si desenvolupament i preproducció estan oberts, una política que funciona allà pot fallar a producció, i la fallada es descobreix en el pitjor moment.
  • Un pod compromès a rutas-norte-dev sense restriccions de xarxa pot arribar a rutas-norte-pro, perquè la xarxa del clúster és plana.

A 08-03 vam resoldre això elegantment amb una política de Kyverno que genera la deny-all a tot namespace nou de Rutas Norte, amb synchronize: true perquè torni si algú l'esborra. És la garantia que cap namespace futur neixi obert.

Principi 2: polítiques per component, no per namespace.

És temptador escriure "tot el de rutas-norte-pro pot parlar amb tot el de rutas-norte-pro". És un error: reprodueix dins del namespace el mateix model pla que volíem evitar.

Enfocament Què permet si es compromet botiga-web
Per namespace Accés a postgres-reserves, redis-cache, api-reserves i tota la resta
Per component Només api-reserves:8080. Res més

La diferència és enorme i el cost és escriure sis polítiques en lloc d'una.

Principi 3: revisió periòdica de quines converses continuen sent necessàries.

Les polítiques de xarxa es degraden igual que el RBAC. S'afegeix un permís per depurar i s'hi queda. Es retira un component i la seva política sobreviu. Es canvia un port i es deixa el vell "per si de cas".

La matriu de comunicacions autoritzades de Rutas Norte, tal com va quedar a 04-06, ha de cabre en una taula i revisar-se cada trimestre:

Origen Destí Port Justificació Última revisió
Ingress botiga-web 8080 Trànsit públic 2026-07
Ingress api-reserves 8080 API pública 2026-07
botiga-web api-reserves 8080 Consultes i reserves 2026-07
api-reserves postgres-reserves 5432 Dades de reserves i clients 2026-07
api-reserves redis-cache 6379 Caché de disponibilitat 2026-07
api-reserves Internet (passarel·la) 443 Cobraments 2026-07
worker-notificacions postgres-reserves 5432 Llegeix reserves pendents 2026-07
worker-notificacions Internet (SMTP) 587 Enviament de correus 2026-07
informes-ocupacio postgres-reserves 5432 Agregats nocturns 2026-07
Prometheus Tots 9090 Mètriques (07-03) 2026-07
Tots CoreDNS 53 Resolució de noms 2026-07

Cada fila ha de tenir un perquè viu. Una pregunta útil a la revisió: si esborro aquesta regla ara mateix, què es trenca? Si ningú no ho sap, cal esbrinar-ho (a preproducció) o treure-la.

Un guió per detectar polítiques orfes:

#!/usr/bin/env bash
# k8s/seguretat/politiques-orfes.sh
# Detecta NetworkPolicies el podSelector de les quals no encaixa amb cap pod.
set -euo pipefail
NS="${1:-rutas-norte-pro}"

kubectl get networkpolicy -n "$NS" -o json | jq -r '
  .items[] | select(.spec.podSelector.matchLabels != null)
  | "\(.metadata.name)\t" +
    ([.spec.podSelector.matchLabels | to_entries[] | "\(.key)=\(.value)"] | join(","))' \
| while IFS=$'\t' read -r politica selector; do
    n=$(kubectl get pods -n "$NS" -l "$selector" --no-headers 2>/dev/null | wc -l)
    [[ "$n" -eq 0 ]] && echo "ORFE: $politica (selector: $selector) no encaixa amb cap pod"
  done
ORFE: permetre-exportador-heretat (selector: app=exportador-heretat) no encaixa amb cap pod

Aquella política va sobreviure al component que protegia. No és perillosa per si mateixa, però és soroll que dificulta la revisió, i si demà algú desplega alguna cosa amb aquella etiqueta hereta permisos que ningú no va decidir.

  1. Control del trànsit sortint

Aquí hi ha, probablement, el buit més greu que queda a Rutas Norte.

Per què l'egrés importa tant

Pensa en la seqüència d'un incident:

  1. Algú troba una fallada a api-reserves i aconsegueix executar codi.
  2. El procés té accés legítim a postgres-reserves: pot llegir la taula de clients amb noms, DNI, telèfons i correus.
  3. Ara necessita treure aquelles dades del clúster.

El pas 3 és el que converteix un compromís en una fuga de dades. Sense control de sortida, el pas 3 és trivial: una petició HTTPS a qualsevol servidor d'internet.

Regla que cal interioritzar: la política d'entrada limita qui entra; la política de sortida limita què pot sortir. Gairebé tothom escriu la primera i oblida la segona. I la segona és la que decideix si un incident es queda en "algú va executar codi" o escala a "es van filtrar les dades personals de 200.000 clients".

La majoria de les guies de NetworkPolicy només parlen d'Ingress. Comprova les teves polítiques: si policyTypes no inclou Egress, el trànsit de sortida està completament obert.

La sortida que Rutas Norte necessita

Auditem quines sortides són legítimes:

Component Destí extern Port És imprescindible?
api-reserves pagos.proveedorexterno.example 443 Sí: cobraments
worker-notificacions Servidor SMTP del proveïdor 587 Sí: correus de confirmació
botiga-web Cap No necessita sortir a internet
postgres-reserves Cap No
redis-cache Cap No
informes-ocupacio Cap No

Quatre dels sis components no necessiten sortir a internet en absolut. Això és el primer que cal aprofitar: tancar-los la sortida del tot és gratis i elimina la via d'exfiltració per a la major part de la plataforma.

La política de sortida base

Recordant que a 04-06 vam deixar deny-all amb policyTypes: [Ingress, Egress], cada component necessita explícitament la seva sortida. El mínim universal és DNS:

# k8s/base/xarxa/permetre-dns.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: permetre-dns-sortida
  namespace: rutas-norte-pro
spec:
  podSelector: {}                 # tots els pods
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - protocol: UDP
          port: 53
        - protocol: TCP
          port: 53

Sense això, cap pod no resol noms i tot falla d'una forma molt confusa: els serveis no es troben entre si encara que les polítiques d'entrada estiguin bé. És l'error més freqüent en activar Egress per primera vegada.

La sortida a la passarel·la de pagaments

api-reserves necessita arribar a pagos.proveedorexterno.example. Aquí és on apareix el problema de l'apartat següent, però vegem primer la solució amb ipBlock:

# k8s/base/xarxa/api-reserves-sortida.yaml
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-reserves-sortida
  namespace: rutas-norte-pro
  annotations:
    seguretat.rutasnorte.example/justificacio: >-
      Sortida a la passarel·la de pagaments externa. Els rangs d'IP els publica el
      proveïdor a la seva documentació i es revisen mensualment mitjançant el
      treball programat "verificar-rangs-passarel-la". Última verificació
      documentada al registre de canvis de xarxa.
spec:
  podSelector:
    matchLabels:
      app: api-reserves
  policyTypes: [Egress]
  egress:
    # 1. DNS (imprescindible)
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }
    # 2. Base de dades i caché (trànsit intern)
    - to:
        - podSelector:
            matchLabels:
              app: postgres-reserves
      ports:
        - { protocol: TCP, port: 5432 }
    - to:
        - podSelector:
            matchLabels:
              app: redis-cache
      ports:
        - { protocol: TCP, port: 6379 }
    # 3. Passarel·la de pagaments: NOMÉS aquests rangs, NOMÉS el 443
    - to:
        - ipBlock:
            cidr: 203.0.113.0/24        # rang publicat pel proveïdor
        - ipBlock:
            cidr: 198.51.100.64/26      # rang secundari
      ports:
        - { protocol: TCP, port: 443 }

L'important d'aquest manifest no és el que permet, sinó el que no permet: api-reserves no pot connectar amb cap altra adreça d'internet. Si algú aconsegueix executar codi allà i vol enviar la taula de clients a un servidor propi, la connexió no surt.

Nota sobre ipBlock i les IP privades: ipBlock es refereix a IP de xarxa, i els pods del clúster també tenen IP. Un ipBlock: 0.0.0.0/0 inclouria tot el trànsit intern del clúster. Quan es vol "tot internet menys la xarxa interna", cal fer servir except:

    - to:
        - ipBlock:
            cidr: 0.0.0.0/0
            except:
              - 10.0.0.0/8        # xarxa de pods i serveis
              - 172.16.0.0/12
              - 192.168.0.0/16
      ports:
        - { protocol: TCP, port: 443 }

Això és "qualsevol destí públic al 443", que és molt més lax que la llista de rangs del proveïdor i no ho recomanem per a api-reserves. Ho incloem perquè és un patró que apareix sovint i convé entendre per què és pitjor: permet enviar dades a qualsevol servidor d'internet que parli HTTPS.

El contenidor ambaixador

Recorda de 06-04 que la connexió amb la passarel·la de pagaments passa per un contenidor ambaixador dins del pod d'api-reserves. Això té una conseqüència de xarxa important: com que els contenidors d'un pod comparteixen el namespace de xarxa, la política s'aplica al pod sencer, no al contenidor. L'ambaixador i el contenidor principal tenen exactament els mateixos permisos de xarxa.

L'ambaixador aporta altres avantatges —centralitza els reintents, la lògica de temps d'espera i les credencials de la passarel·la— però no és un límit de seguretat de xarxa. Si volguéssim que només l'ambaixador pogués parlar amb la passarel·la, hauria de ser un pod a part amb la seva pròpia política.

És un bon exemple d'una confusió comuna: el patró ambaixador és un patró d'arquitectura, no d'aïllament.

  1. El problema de les IP davant dels noms de domini

Aquí arriba la limitació estructural de les NetworkPolicies.

Les NetworkPolicies estàndard de Kubernetes treballen amb adreces IP i rangs CIDR. No entenen de noms de domini. No existeix un camp to: dnsName: pagos.proveedorexterno.example.

I això no és un descuit: les polítiques es tradueixen a regles de tallafocs al kernel del node, que actua sobre paquets IP. Quan el paquet hi arriba, el nom ja s'ha resolt i no en queda cap rastre.

Els tres problemes pràctics

Problema 1: les IP canvien. Els serveis al núvol roten adreces. La documentació del proveïdor de pagaments pot publicar rangs avui i canviar-los en tres mesos. Si el teu ipBlock es queda desfasat, els pagaments deixen de funcionar i el diagnòstic és especialment ingrat perquè res als logs no diu "una NetworkPolicy ha bloquejat això".

Problema 2: un rang és més del que vols. 203.0.113.0/24 són 256 adreces. Si el proveïdor comparteix infraestructura, aquell rang pot incloure servidors d'altres clients seus. Has autoritzat la sortida a 256 destins per arribar a un.

Problema 3: l'escenari invers és pitjor. Si un servei legítim comparteix IP amb un servei d'emmagatzematge genèric (cosa habitual als núvols públics), autoritzar el primer autoritza el segon, i aleshores sí que hi ha una via d'exfiltració.

Les tres opcions

Opció Com funciona Avantatges Inconvenients
Passarel·la de sortida (egress gateway) Tot el trànsit sortint passa per uns pods concrets amb IP fixa, que apliquen les regles Punt únic de control i registre; IP d'origen estable perquè el proveïdor la pugui filtrar Una altra peça per mantenir; punt únic de fallada
Proxy amb llista de permesos Un proxy HTTP(S) al qual apunten les aplicacions, amb una llista de dominis autoritzats Filtra per nom de domini, inclòs SNI; registre complet de destins Les aplicacions s'han de configurar per fer-lo servir; afegeix latència
Polítiques basades en DNS (Cilium) El CNI observa les respostes DNS i programa les regles amb les IP que retornen S'escriu el nom directament; s'actualitza sol Requereix Cilium com a CNI

Opció A: proxy de sortida amb llista de permesos

És l'opció més portable i la que dona millor visibilitat. Un Squid o un proxy dedicat en un pod, amb una llista de dominis:

# k8s/base/xarxa/proxy-sortida-configmap.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: proxy-sortida-config
  namespace: rutas-norte-sistema
data:
  dominis-permesos.txt: |
    # Passarel·la de pagaments: cobraments de bitllets
    pagos.proveedorexterno.example
    # Correu sortint: confirmacions de reserva
    smtp.proveedorcorreo.example
    # RES MÉS. Qualsevol altre destí es rebutja i es registra.

Les aplicacions es configuren amb HTTPS_PROXY, i la NetworkPolicy només permet sortir cap al proxy:

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: api-reserves-sortida-per-proxy
  namespace: rutas-norte-pro
spec:
  podSelector:
    matchLabels:
      app: api-reserves
  policyTypes: [Egress]
  egress:
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - { protocol: UDP, port: 53 }
    # Única sortida permesa: el proxy. Ni una IP d'internet directament.
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: rutas-norte-sistema
          podSelector:
            matchLabels:
              app: proxy-sortida
      ports:
        - { protocol: TCP, port: 3128 }

Avantatge decisiu: el proxy registra cada destí sol·licitat, permès o denegat. Això resol parcialment el problema de la manca de registre de les NetworkPolicies, almenys per al trànsit sortint, que és el que més importa vigilar.

Inconvenient honest: una aplicació que ignori les variables HTTPS_PROXY se salta el proxy. Per això la NetworkPolicy continua sent necessària: impedeix la sortida directa, obligant a passar pel proxy encara que l'aplicació no col·labori. Les dues capes juntes funcionen; cap per separat.

Opció B: polítiques basades en DNS amb Cilium

Si el CNI és Cilium, la CiliumNetworkPolicy permet escriure el nom directament:

apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
  name: api-reserves-sortida-dns
  namespace: rutas-norte-pro
spec:
  endpointSelector:
    matchLabels:
      app: api-reserves
  egress:
    # DNS amb inspecció: Cilium observa les respostes per programar les regles
    - toEndpoints:
        - matchLabels:
            io.kubernetes.pod.namespace: kube-system
            k8s-app: kube-dns
      toPorts:
        - ports:
            - { port: "53", protocol: UDP }
          rules:
            dns:
              - matchPattern: "*"
    # Sortida per NOM DE DOMINI, no per IP
    - toFQDNs:
        - matchName: "pagos.proveedorexterno.example"
      toPorts:
        - ports:
            - { port: "443", protocol: TCP }

Com funciona: Cilium intercepta les respostes DNS del pod, veu quina IP va retornar pagos.proveedorexterno.example i programa dinàmicament la regla per a aquella IP, amb el temps de vida que indiqui el registre DNS. Si demà el proveïdor canvia d'IP, la política continua funcionant sense que ningú toqui res.

És, amb diferència, la solució més elegant. El seu cost és la dependència del CNI: adoptar Cilium és una decisió de plataforma que va molt més enllà d'aquesta política.

Recomanació per a Rutas Norte

Un enfocament en dos temps:

  1. Ara: ipBlock amb els rangs publicats pel proveïdor, més un treball programat mensual que verifiqui que continuen sent vàlids i avisi si canvien. És la solució que funciona amb qualsevol CNI i no requereix peces noves.
  2. A mitjà termini: proxy de sortida amb llista de permesos, principalment pel registre de destins. Si en algun moment es migra a Cilium (cosa que també aportaria els avantatges de l'apartat 11), se substitueixen les polítiques per toFQDNs.

I la comprovació que cal automatitzar en qualsevol cas:

#!/usr/bin/env bash
# k8s/seguretat/verificar-sortida.sh
# Confirma que els components que no han de sortir a internet, no en surten.
set -uo pipefail
NS=rutas-norte-pro

comprovar_bloqueig() {
  local carrega="$1" desti="$2"
  local sortida
  sortida=$(kubectl exec -n "$NS" "$carrega" -- \
    timeout 5 wget -q -O- --timeout=4 "$desti" 2>&1 || echo "BLOQUEJAT")
  if [[ "$sortida" == *"BLOQUEJAT"* ]]; then
    echo "OK    $carrega no arriba a $desti"
  else
    echo "FALLA $carrega SÍ que arriba a $desti <-- via d'exfiltració oberta"
  fi
}

comprovar_bloqueig deploy/botiga-web           https://ejemplo-externo.example
comprovar_bloqueig statefulset/postgres-reserves https://ejemplo-externo.example
comprovar_bloqueig deploy/api-reserves         https://ejemplo-externo.example
OK    deploy/botiga-web no arriba a https://ejemplo-externo.example
OK    statefulset/postgres-reserves no arriba a https://ejemplo-externo.example
OK    deploy/api-reserves no arriba a https://ejemplo-externo.example

Aquesta prova s'ha d'executar a rutas-norte-pre en cada desplegament. És la verificació de la capa que impedeix una fuga de dades personals, i com a tal hauria d'estar a l'expedient que revisa el responsable de compliment.

  1. Xifratge en trànsit dins del clúster

A 04-05 vam posar TLS a l'Ingress amb cert-manager: el trànsit entre el navegador del client i la plataforma va xifrat fins a https://www.rutasnorte.example. Perfecte. Però què passa després de l'Ingress?

Què queda sense xifrar

flowchart LR
    C["Navegador"] -->|"HTTPS<br/>xifrat (04-05)"| ING["Ingress"]
    ING -->|"HTTP<br/>EN CLAR"| TW["botiga-web"]
    TW -->|"HTTP<br/>EN CLAR"| API["api-reserves"]
    API -->|"protocol PostgreSQL<br/>EN CLAR"| PG[("postgres-reserves<br/>dades personals")]
    API -->|"RESP<br/>EN CLAR"| R[("redis-cache")]
    style PG fill:#f9d5d5,stroke:#c33

Tot el que hi ha a la dreta de l'Ingress viatja sense xifrar per la xarxa del clúster. I per aquell tram passen, en clar:

  • Les consultes SQL amb noms, DNI, telèfons i correus de clients.
  • Les respostes de l'API amb les dades de les reserves.
  • Les credencials de la base de dades, a la salutació inicial de cada connexió.

Qui pot veure aquell trànsit?

Aquesta és la pregunta que decideix si val la pena l'esforç:

Escenari Veu el trànsit intern?
Un pod normal amb NetworkPolicies estrictes No (no pot ni connectar)
Un pod amb hostNetwork: true : veu tot el trànsit del node
Un pod amb CAP_NET_RAW i hostNetwork : pot capturar paquets
Algú amb accés al node (SSH, contenidor privilegiat) Sí, tot
El proveïdor del núvol o del centre de dades Depèn del contracte i la infraestructura
Un atacant que compromet el CNI

Fixa't en com es connecten les lliçons: a 08-02 vam prohibir hostNetwork i CAP_NET_RAW, i a 08-03 vam fer que el clúster ho rebutgi. Aquella feina és el que fa que el trànsit intern sense xifrar sigui un risc acceptable en molts escenaris. El xifratge intern és la defensa per a quan aquella capa falla, o quan la normativa ho exigeix explícitament.

Les tres opcions de xifratge intern

Opció Què xifra Cost Identitat
TLS a l'aplicació Només el que l'aplicació implementi Canvis de codi a cada component Certificats gestionats a mà
Xifratge del CNI (WireGuard, IPsec) Tot el trànsit entre nodes Baix: una opció de configuració Per node, no per càrrega
mTLS amb malla de serveis Tot el trànsit entre pods de la malla Alt: una capa d'infraestructura completa Per càrrega de treball

Opció 1: TLS a nivell d'aplicació

És el que ja fem amb la passarel·la de pagaments (l'ambaixador parla HTTPS). Per al trànsit intern significaria configurar PostgreSQL amb ssl = on, generar certificats, distribuir-los i configurar cada client.

# Fragment: PostgreSQL amb TLS obligatori
env:
  - name: POSTGRES_INITDB_ARGS
    value: "--auth-host=scram-sha-256"
volumeMounts:
  - name: certificats-tls
    mountPath: /var/lib/postgresql/certs
    readOnly: true

Avantatge: xifra el tram que més importa (les dades personals) amb poc desplegament. Inconvenient: cal fer-ho component a component, gestionar la rotació de certificats i confiar que cada client verifiqui realment el certificat (molts clients de base de dades, per defecte, no ho fan).

Per a Rutas Norte, xifrar específicament la connexió amb postgres-reserves és una mesura d'alt valor i cost moderat, i probablement el primer pas a fer.

Opció 2: xifratge a nivell de CNI

Diversos CNI poden xifrar automàticament tot el trànsit entre nodes amb WireGuard o IPsec.

# Cilium amb xifratge WireGuard (valors de Helm)
encryption:
  enabled: true
  type: wireguard
  nodeEncryption: true
# Calico amb WireGuard
kubectl patch felixconfiguration default --type=merge \
  -p '{"spec":{"wireguardEnabled":true}}'
A favor En contra
Esforç Mínim: una opció
Cobertura Tot el trànsit entre nodes, sense excepcions Només entre nodes: dos pods al mateix node no es xifren
Rendiment WireGuard és molt eficient Una mica de CPU; amb IPsec, més
Identitat Autentica nodes, no càrregues de treball

Aquell últim punt és la limitació essencial: el xifratge del CNI protegeix contra qui observi la xarxa entre nodes, però no aporta identitat per servei. botiga-web i api-reserves al mateix node es parlen igual d'en clar, i cap dels dos pot demostrar criptogràficament qui és.

Tot i així, la relació valor/esforç és excel·lent. Si el CNI ho admet, activa-ho.

Opció 3: mTLS amb una malla de serveis

És l'apartat següent, perquè dona molt més que xifratge.

  1. Què és una malla de serveis i què costa

Una malla de serveis (service mesh) és una capa d'infraestructura que s'ocupa de la comunicació entre serveis, traient-la del codi de les aplicacions.

Com funciona

Al model clàssic, s'injecta un proxy sidecar (habitualment Envoy) a cada pod. Tot el trànsit que entra i surt del pod passa per aquell proxy, que és qui aplica el xifratge, l'autorització i la recollida de mètriques. L'aplicació no se n'assabenta: continua fent http://api-reserves:8080.

flowchart LR
    subgraph P1["Pod botiga-web"]
        A1["nginx"] <--> S1["proxy sidecar"]
    end
    subgraph P2["Pod api-reserves"]
        S2["proxy sidecar"] <--> A2["Node.js"]
    end
    S1 <-->|"mTLS<br/>xifratge + identitat<br/>+ autorització L7"| S2
    CP["Pla de control<br/>emet certificats<br/>distribueix polítiques"] -.-> S1
    CP -.-> S2

Què et dona

Capacitat Què resol Es pot tenir sense malla?
Identitat criptogràfica per càrrega Cada servei té un certificat que demostra qui és Molt difícil a mà
mTLS automàtic Xifratge i autenticació mútua sense tocar codi Difícil, component a component
Autorització L7 "Només botiga-web pot fer POST /reserves" No amb NetworkPolicy
Reintents i temps d'espera Resiliència sense codi Sí, a cada aplicació
Talla-circuits Aïllar un servei degradat Sí, amb biblioteques
Desplegaments canari per percentatge Enviar el 5 % del trànsit a la versió nova Parcialment (11-04)
Observabilitat de la comunicació Mètriques de latència i errors de cada crida Parcialment (07-03)

La identitat per càrrega de treball és la joia de la corona i mereix que l'entenguem bé. Sense malla, quan postgres-reserves rep una connexió des d'una IP, l'única cosa que sap és que ve d'aquella IP. Amb la NetworkPolicy sabem que aquella IP correspon a un pod amb l'etiqueta app: api-reserves... però les etiquetes les posa qui crea el pod, i una IP es pot suplantar.

Amb mTLS, postgres-reserves rep un certificat que diu spiffe://cluster.local/ns/rutas-norte-pro/sa/api-reserves, signat per l'autoritat certificadora del clúster, que ningú més no pot falsificar. Això sí que és identitat. I encaixa perfectament amb les ServiceAccounts de 03-06.

Què et costa

Aquesta és la part que les presentacions comercials passen per sobre:

Cost Detall
Complexitat operativa Una peça d'infraestructura crítica més per actualitzar, vigilar i depurar
Recursos Un sidecar per pod: entre 50 i 100 MiB de memòria i una mica de CPU. Amb 50 pods, són diversos GiB
Latència Entre 1 i 5 ms per salt. Amb diversos salts encadenats, es nota
Corba d'aprenentatge Conceptes nous: VirtualService, DestinationRule, AuthorizationPolicy, PeerAuthentication
Depuració més difícil Un error 503 pot venir de l'aplicació o del proxy. Cal aprendre a llegir els logs d'Envoy
Acoblament a les actualitzacions Actualitzar la malla sol implicar reiniciar tots els pods
Casos rars Treballs que acaben i el sidecar continua viu; protocols que el proxy no entén bé

Aquell últim punt té un exemple directe a Rutas Norte: el CronJob informes-ocupacio acaba la seva feina, però el sidecar continua corrent i el Job mai no es marca com a completat. Hi ha solucions (contenidors sidecar natius des de 1.29, o cridar el punt de terminació del proxy), però és exactament el tipus de fricció imprevista que apareix.

Necessita Rutas Norte una malla de serveis?

Respondrem amb honestedat, perquè és una decisió cara.

Arguments a favor:

  • Transporta dades personals entre serveis i el xifratge intern és exigible en una anàlisi de risc seriosa.
  • L'autorització L7 permetria regles com "només botiga-web pot fer POST /reserves", que NetworkPolicy no pot expressar.
  • La identitat criptogràfica elimina la suplantació de serveis.

Arguments en contra:

  • Són sis components. Una malla brilla amb desenes o centenars de microserveis i comunicacions complexes.
  • El graf de crides és gairebé lineal: botiga-webapi-reservespostgres-reserves/redis-cache. No hi ha una topologia que justifiqui una capa d'encaminament.
  • L'equip de plataforma és petit. Afegir una malla mal operada pot empitjorar la seguretat (certificats caducats, polítiques mal enteses, actualitzacions ajornades).
  • Bona part del benefici s'aconsegueix més barat: xifratge del CNI per al trànsit, TLS a PostgreSQL per al crític, i NetworkPolicies estrictes per a la segmentació.

Decisió per a Rutas Norte: no, encara no. El pla alternatiu:

  1. Activar el xifratge WireGuard del CNI (una opció de configuració).
  2. Configurar TLS a postgres-reserves, que és on són les dades personals.
  3. Mantenir les NetworkPolicies estrictes amb control de sortida.
  4. Revisar la decisió quan es compleixi algun d'aquests criteris objectius: més de quinze serveis, comunicació entre diversos equips que no es coordinen, un requisit normatiu explícit de mTLS, o la necessitat real de desplegaments canari per percentatge de trànsit.

I una recomanació general que va més enllà d'aquest curs:

Una malla de serveis és una resposta excel·lent a un problema que cal tenir primer. Adoptar-la "perquè és el modern", sense la complexitat que la justifica, afegeix risc operatiu sense afegir seguretat neta. Si l'operes malament, és pitjor que no tenir-la.

Dit això, cal conèixer-la, perquè el dia que la plataforma creixi serà la resposta correcta. Anem amb les opcions.

  1. Istio, Linkerd i Cilium comparats

Istio (sidecar) Istio (ambient) Linkerd Cilium Service Mesh
Arquitectura Sidecar Envoy per pod Agent per node (ztunnel) + waypoint L7 opcional Sidecar propi (linkerd2-proxy, en Rust) eBPF al kernel + Envoy només per a L7
Memòria per pod 50-100 MiB Gairebé zero (sense sidecar) 10-20 MiB Gairebé zero per a L4
Latència afegida 2-5 ms 1-3 ms <1 ms Mínima
Complexitat Alta Mitjana Baixa Mitjana-alta
mTLS automàtic
Autorització L7 Molt completa Sí (amb waypoint) Bàsica
Encaminament avançat El més complet Complet Suficient Bo
Multiclúster Molt madur
Requereix un CNI concret No No No Sí: Cilium
Comunitat i ecosistema El més gran Creixent Sòlida Creixent
Corba d'aprenentatge Pronunciada Mitjana Suau Mitjana
Govern CNCF (graduat) CNCF CNCF (graduat) CNCF (graduat)

Criteris d'elecció

Istio en mode sidecar si necessites el conjunt de funcions més complet: encaminament sofisticat, multiclúster complex, integració amb passarel·les d'API, extensions amb WebAssembly. És l'opció amb més capacitat i també la que més costa operar. Necessites gent dedicada.

Istio en mode ambient és l'evolució que ataca el principal retret al model sidecar: el cost per pod. En lloc d'un proxy a cada pod, hi ha un agent per node (ztunnel) que dona mTLS i L4, i només es desplega un proxy L7 (waypoint) on de debò cal autorització de nivell 7. Redueix moltíssim el consum i elimina el problema dels Jobs que no acaben. Si avui haguéssim de triar Istio, seria aquest mode.

Linkerd si vols mTLS i observabilitat amb la mínima complexitat. El seu proxy en Rust és notablement lleuger i la seva filosofia és fer poques coses i fer-les bé. Per a una plataforma com Rutas Norte, si algun dia calgués una malla, aquesta seria la primera candidata: cobreix el que necessitem (mTLS, identitat, autorització bàsica) sense la superfície d'Istio.

Cilium Service Mesh si ja fas servir Cilium com a CNI. En integrar la malla al CNI evites una capa completa: el mTLS i les polítiques L4 s'apliquen en eBPF, dins del kernel, sense proxy. Només es desplega Envoy per a les polítiques L7. És l'opció més eficient, i a més porta les polítiques per nom de domini de l'apartat 4 i la visibilitat de Hubble de l'apartat 11. La contrapartida és que lliga la decisió de la malla a la del CNI.

  1. Polítiques d'autorització de nivell 7

Aquest és el buit concret que NetworkPolicy no pot omplir, i convé veure'l amb un exemple real.

El límit de L3/L4

La nostra política actual diu: botiga-web pot connectar amb api-reserves al port 8080. Això és tot el que pot expressar. Tan bon punt la connexió està oberta, botiga-web pot fer:

Petició Hi hauria de poder? Ho impedeix la NetworkPolicy?
GET /disponibilitat
POST /reserves
GET /admin/clients No No
DELETE /reserves/12345 No No
GET /metrics Discutible No

Una política L7 sí que pot distingir. Vegem com s'expressaria amb Istio, prenent l'exemple de l'enunciat: només api-reserves pot fer POST /reserves.

# Exemple de política L7 amb Istio (només si s'adoptés una malla)
apiVersion: security.istio.io/v1
kind: AuthorizationPolicy
metadata:
  name: api-reserves-autoritzacio
  namespace: rutas-norte-pro
spec:
  selector:
    matchLabels:
      app: api-reserves
  action: ALLOW
  rules:
    # Regla 1: botiga-web pot consultar disponibilitat i crear reserves
    - from:
        - source:
            # IDENTITAT CRIPTOGRÀFICA, no una etiqueta ni una IP
            principals:
              - "cluster.local/ns/rutas-norte-pro/sa/botiga-web"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/disponibilitat", "/linies", "/salut", "/preparat"]
        - operation:
            methods: ["POST"]
            paths: ["/reserves"]

    # Regla 2: només el worker de notificacions consulta reserves pendents
    - from:
        - source:
            principals:
              - "cluster.local/ns/rutas-norte-pro/sa/worker-notificacions"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/reserves/pendents"]

    # Regla 3: Prometheus només pot llegir les mètriques
    - from:
        - source:
            principals:
              - "cluster.local/ns/monitoratge/sa/prometheus"
      to:
        - operation:
            methods: ["GET"]
            paths: ["/metrics"]

Amb action: ALLOW, tot el que no encaixi amb cap regla es denega. És el mateix principi de denegació per defecte de les NetworkPolicies, ara aplicat a mètodes i rutes HTTP.

Observa la línia clau:

principals:
  - "cluster.local/ns/rutas-norte-pro/sa/botiga-web"

Això no és una etiqueta ni una IP: és la identitat SPIFFE que la malla verifica criptogràficament a la salutació mTLS. Un pod no pot reclamar aquella identitat sense tenir la clau privada corresponent. I fixa't que es basa en la ServiceAccount, tancant el cercle amb 03-06 i 08-01: la ServiceAccount passa de ser només la identitat davant de l'API a ser també la identitat davant dels altres serveis.

La comparació completa

NetworkPolicy (04-06) AuthorizationPolicy (malla)
Nivell L3/L4: IP i port L7: mètode, ruta, capçaleres
Identitat Etiquetes del pod (suplantables) Certificat criptogràfic
Xifratge No Sí (mTLS)
Registre de denegacions No
Requereix instal·lar Res (amb CNI compatible) Una malla completa
Cost operatiu Baix Alt

Les dues són complementàries, no alternatives. La NetworkPolicy és la primera línia i la més barata; la política L7 és l'afinació. Si t'haguessis de quedar amb una, queda't amb la NetworkPolicy: sense ella, la política L7 protegeix un servei al qual qualsevol pot continuar connectant-se per altres vies.

L'alternativa sense malla

Sense malla, l'autorització L7 la fa la mateixa aplicació. api-reserves pot validar al seu codi qui la crida:

# L'aplicació valida un token de servei injectat com a Secret
env:
  - name: TOKEN_SERVEI_ESPERAT
    valueFrom:
      secretKeyRef:
        name: tokens-interns
        key: botiga-web

Funciona i per a sis serveis és perfectament raonable, però té inconvenients reals: cal implementar-ho a cada servei, rotar els tokens a mà, i si algú llegeix el Secret (RBAC, 08-01) pot suplantar el servei. El mTLS de la malla resol les tres coses automàticament. És exactament el tipus de cost que va creixent amb el nombre de serveis i que, arribat un punt, justifica la malla.

  1. Protecció del perímetre

Tot l'anterior protegeix l'interior. Ara, la porta d'entrada.

Exposició mínima: per què no hi ha NodePort a producció

A 04-02 vam veure els tipus de Service. Repassem-ne la implicació de seguretat:

Tipus Exposició A producció?
ClusterIP Només dins del clúster : per defecte per a tot
NodePort Un port (30000-32767) a tots els nodes No
LoadBalancer Un balancejador del proveïdor Només per a l'Ingress
Ingress HTTP/HTTPS per nom i ruta : única entrada

Per què NodePort no val a producció:

  • Obre el port a tots els nodes, inclosos els que no executen el pod.
  • Se salta l'Ingress, i amb ell el TLS (04-05), el WAF i la limitació de peticions.
  • El port és en un rang alt i poc convencional, difícil de protegir amb un tallafocs convencional.
  • No té nom: qualsevol que arribi a la IP del node arriba al servei.

I recorda de 08-02 que hostPort és encara pitjor, perquè ni tan sols hi ha un Service al davant.

La regla de Rutas Norte: un únic punt d'entrada, el balancejador de l'Ingress. Tota la resta és ClusterIP.

# Auditoria: hi ha algun servei exposat que no hi hauria de ser?
kubectl get svc -A -o json | jq -r '
  .items[] | select(.spec.type == "NodePort" or .spec.type == "LoadBalancer")
  | "\(.spec.type)\t\(.metadata.namespace)/\(.metadata.name)\t" +
    ([.spec.ports[] | "\(.port)->\(.nodePort // "-")"] | join(","))'
LoadBalancer	ingress-nginx/ingress-nginx-controller	80->31023,443->30987

Un únic LoadBalancer, el de l'Ingress. Qualsevol altra línia en aquella sortida és una pregunta que cal respondre.

WAF i limitació de peticions

Un tallafocs d'aplicació web (WAF) inspecciona les peticions HTTP i bloqueja patrons d'atac coneguts: injecció SQL, execució de scripts entre llocs, recorregut de directoris. Amb ingress-nginx es pot activar ModSecurity amb el conjunt de regles OWASP:

# k8s/base/ingress/configmap-waf.yaml
apiVersion: v1
kind: ConfigMap
metadata:
  name: ingress-nginx-controller
  namespace: ingress-nginx
data:
  enable-modsecurity: "true"
  enable-owasp-modsecurity-crs: "true"
  modsecurity-snippet: |
    SecRuleEngine On
    SecRequestBodyAccess On
    SecRequestBodyLimit 5242880
    SecAuditEngine RelevantOnly
    SecAuditLogParts ABIJDEFHZ
    # Nivell de paranoia 1: pocs falsos positius. Pujar-lo amb compte.
    SecAction "id:900000,phase:1,nolog,pass,t:none,setvar:tx.paranoia_level=1"

Advertiment seriós sobre els WAF: un WAF mal ajustat bloqueja peticions legítimes. Si botiga-web deixa de poder crear reserves perquè una regla del WAF considera sospitós un nom amb apòstrof, has creat una incidència. El procediment correcte és idèntic al del PSA a 08-03:

  1. SecRuleEngine DetectionOnly: registra però no bloqueja.
  2. Analitzar els falsos positius durant setmanes de trànsit real.
  3. Ajustar les regles problemàtiques.
  4. Només aleshores, SecRuleEngine On.

I la limitació de peticions, per anotació a l'Ingress:

apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: rutas-norte
  namespace: rutas-norte-pro
  annotations:
    cert-manager.io/cluster-issuer: letsencrypt-produccio   # de 04-05
    # Limitació de peticions
    nginx.ingress.kubernetes.io/limit-rps: "20"
    nginx.ingress.kubernetes.io/limit-connections: "10"
    nginx.ingress.kubernetes.io/limit-burst-multiplier: "3"
    # Mida màxima del cos: evita esgotar memòria
    nginx.ingress.kubernetes.io/proxy-body-size: "2m"
    # Temps d'espera: evita connexions que es queden penjades
    nginx.ingress.kubernetes.io/proxy-read-timeout: "30"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "30"
    # Capçaleres de seguretat
    nginx.ingress.kubernetes.io/configuration-snippet: |
      more_set_headers "X-Content-Type-Options: nosniff";
      more_set_headers "X-Frame-Options: DENY";
      more_set_headers "Referrer-Policy: strict-origin-when-cross-origin";
      more_set_headers "Permissions-Policy: geolocation=(), microphone=(), camera=()";
      more_set_headers "Strict-Transport-Security: max-age=31536000; includeSubDomains";
spec:
  ingressClassName: nginx
  tls:
    - hosts: [www.rutasnorte.example]
      secretName: rutasnorte-tls
  rules:
    - host: www.rutasnorte.example
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: botiga-web
                port: { number: 80 }
          - path: /api
            pathType: Prefix
            backend:
              service:
                name: api-reserves
                port: { number: 8080 }

Les capçaleres, una a una:

Capçalera Què mitiga
X-Content-Type-Options: nosniff Que el navegador endevini el tipus de contingut i executi alguna cosa com a script
X-Frame-Options: DENY Que la pàgina s'incrusti en un marc d'un altre lloc (clickjacking)
Referrer-Policy Que es filtrin URL internes en navegar a llocs externs
Permissions-Policy Accés no sol·licitat a càmera, micròfon o ubicació
Strict-Transport-Security Que un client torni a connectar per HTTP sense xifrar

Un detall sobre HSTS: max-age=31536000 són 365 dies, i els navegadors ho recorden. Si algun dia el teu TLS deixa de funcionar, els clients que ja van visitar el lloc no podran entrar per HTTP com a alternativa. És el desitjable des del punt de vista de seguretat, però cal ser-ne conscient abans d'activar-ho.

Protecció contra denegació de servei

Nivell de l'atac On es mitiga
Volumètric (saturar l'amplada de banda) Fora del clúster: proveïdor de núvol o CDN
Esgotament de connexions Balancejador i limit-connections
Peticions costoses limit-rps, i a l'aplicació: paginació i temps d'espera
Esgotament de recursos interns resources.limits (03-04) i autoescalat (mòdul 9)

Un punt important que s'oblida: l'autoescalat no és una defensa contra la denegació de servei, és una manera de pagar-la. Si api-reserves escala a 50 rèpliques davant d'un atac, has convertit una caiguda en una factura. El límit superior de l'HPA (09-01) és també un control de seguretat i de cost.

  1. Seguretat del pla de control

Tot l'anterior protegeix les càrregues. El pla de control és l'objectiu de més valor: qui el controla, controla tot el clúster.

Accés a l'apiserver

Control Com
No exposar-lo a internet Punt final privat, o llista d'IP permeses
Autenticació forta OIDC amb segon factor, mai certificats compartits
Deshabilitar l'accés anònim --anonymous-auth=false
Registre d'auditoria Política d'auditoria (08-06)
Limitació de peticions --max-requests-inflight, prioritat i equitat de l'API
# Comprovar si l'apiserver està exposat públicament
kubectl cluster-info
Kubernetes control plane is running at https://10.0.4.11:6443

Una IP privada és bon senyal. Si veiessis una IP pública sense restricció d'origen, és una troballa de seguretat de primer nivell.

A Kubernetes gestionat (10-06), els tres grans proveïdors permeten restringir l'accés al punt final. És de les primeres coses que cal configurar en un clúster nou.

etcd

etcd guarda tot l'estat del clúster, inclosos els Secrets. Com vam dir a 03-02, si etcd no està xifrat, els Secrets són en clar al disc.

Xifratge en repòs:

# /etc/kubernetes/xifratge/config.yaml (als nodes del pla de control)
apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
  - resources:
      - secrets
      - configmaps
    providers:
      # KMS extern: la clau mestra viu fora del clúster. Preferible.
      - kms:
          apiVersion: v2
          name: kms-rutasnorte
          endpoint: unix:///var/run/kmsplugin/socket.sock
          cachesize: 1000
      - identity: {}     # sense xifrar: necessari per poder llegir l'antic

Detall important: l'ordre dels proveïdors importa. El primer es fa servir per escriure; tots es fan servir per llegir. Tenir identity: {} al final permet llegir els objectes que es van escriure abans d'activar el xifratge. I després d'activar-lo, cal reescriure'ls:

kubectl get secrets -A -o json | kubectl replace -f -

Sense aquell pas, els Secrets antics continuen en clar a etcd per sempre.

Altres controls d'etcd:

Control Per què
TLS mutu entre etcd i l'apiserver Ningú més no ha de poder parlar amb etcd
Accés restringit al port 2379 Només des del pla de control
Còpies de seguretat xifrades Una còpia d'etcd sense xifrar és una còpia de tots els Secrets
Custòdia de les còpies Amb la mateixa protecció que la base de dades de clients

Aquell punt de les còpies és fàcil de passar per alt i molt greu. A 05-06 vam parlar de Velero i de les dades personals; una còpia d'etcd conté, a més, totes les credencials del clúster. S'ha de xifrar, emmagatzemar amb accés restringit i registrat, i provar-ne la restauració periòdicament.

El kubelet

Cada node executa un kubelet amb una API al port 10250. Sense protecció, permet llistar pods, llegir logs i executar ordres en qualsevol contenidor d'aquell node.

Ajust Valor correcte Què evita
--anonymous-auth false Peticions sense credencials
--authorization-mode Webhook Que qualsevol identitat vàlida ho pugui tot
--read-only-port 0 El port 10255, sense autenticació, exposa metadades dels pods
--protect-kernel-defaults true Que el kubelet modifiqui paràmetres del kernel

Comprovació:

# Ha de respondre 401: l'API del kubelet no accepta peticions anònimes
kubectl get --raw /api/v1/nodes/node-pro-01/proxy/pods -v6 2>&1 | grep -i "response"

I una NetworkPolicy o regla de tallafocs que impedeixi als pods arribar al port 10250 dels nodes és una defensa molt rendible, perquè tanca una via d'escalada coneguda.

Recorda de 08-01 que el permís get nodes/proxy és de nivell crític precisament per això: permet parlar amb el kubelet i, a través d'ell, executar en qualsevol pod del node.

Aïllament dels nodes del pla de control

Els nodes del pla de control no han d'executar càrregues d'aplicació. Kubernetes ho aconsegueix amb un taint (06-05):

kubectl describe node node-control-01 | grep -A2 Taints
Taints:  node-role.kubernetes.io/control-plane:NoSchedule

Aquell taint impedeix que s'hi planifiquin pods normals. La raó és directa: un pod en un node del pla de control està a una fallada de distància dels certificats del clúster i de la base de dades d'etcd.

I cal verificar que ningú no l'ha tolerat per comoditat:

kubectl get pods -A -o json | jq -r '
  .items[]
  | select(.spec.tolerations[]? |
      .key == "node-role.kubernetes.io/control-plane" or
      (.operator == "Exists" and (.key // "") == ""))
  | "\(.metadata.namespace)/\(.metadata.name)"'
kube-system/kube-proxy-hd8k2
kube-system/cilium-p2m4x

Només components del sistema que legítimament corren a tots els nodes. Si hi aparegués un pod de rutas-norte-pro, cal investigar per què es va posar aquella tolerància. Compte amb la tolerància universal (operator: Exists sense key): tolera tots els taints, inclòs el del pla de control, i sovint es posa sense adonar-se'n.

  1. Registre i visibilitat del trànsit

Tornem al segon límit que vam assenyalar a 04-06: les NetworkPolicies no registren res.

Què significa exactament

Quan una NetworkPolicy denega una connexió, el paquet es descarta al kernel. No hi ha esdeveniment de Kubernetes, no hi ha log, no hi ha mètrica. Des del pod origen, la connexió simplement esgota el seu temps d'espera.

Les conseqüències són dues, i totes dues són dolentes:

Per operar: depurar "per què el meu servei no connecta" és dolorós. Pot ser DNS, una política, un Service mal configurat o l'aplicació. Res no et diu quin.

Per a la seguretat: si algú compromet un pod i comença a sondejar la xarxa interna, generarà desenes de connexions denegades i no se n'assabentarà ningú. Just el senyal més clar d'un moviment lateral és invisible.

Hubble: observabilitat de fluxos amb Cilium

Hubble, el component d'observabilitat de Cilium, resol això aprofitant que Cilium ja inspecciona cada paquet en eBPF.

helm upgrade cilium cilium/cilium --namespace kube-system --reuse-values \
  --set hubble.enabled=true \
  --set hubble.relay.enabled=true \
  --set hubble.ui.enabled=true \
  --set hubble.metrics.enabled="{dns,drop,tcp,flow,port-distribution,httpV2}"

Veure fluxos en temps real:

hubble observe --namespace rutas-norte-pro --follow
TIMESTAMP             SOURCE                          DESTINATION                    TYPE          VERDICT
Aug  6 09:14:22.481   rutas-norte-pro/botiga-web-x2   rutas-norte-pro/api-reserves-a1:8080   to-endpoint   FORWARDED
Aug  6 09:14:22.503   rutas-norte-pro/api-reserves-a1 rutas-norte-pro/postgres-reserves-0:5432  to-endpoint   FORWARDED
Aug  6 09:14:23.117   rutas-norte-pro/api-reserves-a1 rutas-norte-pro/redis-cache-0:6379      to-endpoint   FORWARDED

I el que de debò importa, les denegacions:

hubble observe --namespace rutas-norte-pro --verdict DROPPED --last 100
TIMESTAMP             SOURCE                            DESTINATION                              TYPE          VERDICT   REASON
Aug  6 03:47:11.229   rutas-norte-pro/botiga-web-x2k4   rutas-norte-pro/postgres-reserves-0:5432 to-endpoint   DROPPED   Policy denied
Aug  6 03:47:11.884   rutas-norte-pro/botiga-web-x2k4   rutas-norte-pro/redis-cache-0:6379       to-endpoint   DROPPED   Policy denied
Aug  6 03:47:12.401   rutas-norte-pro/botiga-web-x2k4   203.0.113.44:443                         to-stack     DROPPED   Policy denied
Aug  6 03:47:13.055   rutas-norte-pro/botiga-web-x2k4   198.51.100.7:22                          to-stack     DROPPED   Policy denied

Llegeix-ho amb atenció, perquè aquella sortida explica una història completa. botiga-web —el component més exposat— ha intentat, en cinc segons: connectar amb la base de dades de clients, connectar amb la caché, sortir a internet per HTTPS i sortir a internet per SSH. botiga-web no fa res d'això en el seu funcionament normal. És exactament el patró d'algú explorant què pot arribar a assolir des d'un pod compromès.

Les polítiques van fer la seva feina: tot DROPPED. Però sense Hubble, això hauria passat completament desapercebut.

Preguntes que es poden respondre després d'un incident

Pregunta Consulta
Què va intentar assolir aquest pod? hubble observe --pod <nom> --last 1000
Va sortir algú a internet des de producció? hubble observe --namespace rutas-norte-pro --to-fqdn "*"
Quan van començar els intents anòmals? hubble observe --verdict DROPPED --since 24h
Qui va parlar amb la base de dades? hubble observe --to-pod rutas-norte-pro/postgres-reserves-0
Quines rutes HTTP es van demanar? hubble observe --protocol http --http-path "/admin*"

Convertir-ho en alertes

Hubble exposa mètriques Prometheus, així que s'integra directament amb el mòdul 7:

# k8s/base/monitoratge/regles-xarxa.yaml
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: alertes-seguretat-xarxa
  namespace: monitoratge
  labels:
    app.kubernetes.io/part-of: rutas-norte
spec:
  groups:
    - name: seguretat-xarxa
      rules:
        - alert: ConnexionsDenegadesAnomales
          expr: |
            sum by (source_pod, source_namespace) (
              rate(hubble_drop_total{
                source_namespace="rutas-norte-pro",
                reason="POLICY_DENIED"
              }[5m])
            ) > 0.5
          for: 5m
          labels:
            severity: avis
            equip: plataforma
          annotations:
            summary: >-
              El pod {{ $labels.source_pod }} genera connexions denegades de forma
              sostinguda
            description: >-
              Més de 0,5 connexions denegades per segon durant 5 minuts.
              Pot ser un desplegament amb una política mal configurada, o un pod
              compromès sondejant la xarxa. Investiga amb:
              hubble observe --pod {{ $labels.source_namespace }}/{{ $labels.source_pod }} --verdict DROPPED
            runbook_url: https://wiki.rutasnorte.example/runbooks/connexions-denegades

        - alert: SortidaInesperadaDesDeProduccio
          expr: |
            sum by (source_pod) (
              rate(hubble_flows_processed_total{
                source_namespace="rutas-norte-pro",
                destination_namespace="",
                verdict="FORWARDED"
              }[5m])
            ) > 0
          for: 2m
          labels:
            severity: critica
            equip: plataforma
          annotations:
            summary: >-
              Trànsit sortint a internet des de {{ $labels.source_pod }}
            description: >-
              Només api-reserves (passarel·la de pagaments) i worker-notificacions (SMTP)
              han de sortir a internet. Qualsevol altre origen és una possible via
              d'exfiltració de dades de clients. Escalar immediatament.

Aquella segona alerta és de les més valuoses de tota la plataforma: avisa d'una possible exfiltració de dades personals en curs. La ruta a Alertmanager i el seu encaminament són les del mòdul 7; l'única cosa nova és la font de la mètrica.

Alternatives sense Cilium

Eina Què aporta
Registre de fluxos del proveïdor (VPC Flow Logs) Fluxos a nivell de xarxa virtual, sense identitat de pod
Calico Enterprise Registre de fluxos amb context de Kubernetes (producte comercial)
Falco amb regles de xarxa Connexions inesperades observades des de les crides al sistema (08-06)
Registre del proxy de sortida Destins externs sol·licitats, amb nom de domini

Per a Rutas Norte, sense Cilium avui, la combinació pràctica és: registre del proxy de sortida per al trànsit extern (que és el crític) i Falco per a les connexions anòmales, que veurem a la lliçó següent.

  1. Confiança zero aplicada a Rutas Norte

La confiança zero (zero trust) és un model que es resumeix en una frase: mai no confiïs, verifica sempre. Res no es considera de confiança per la seva ubicació a la xarxa.

Els principis i la seva traducció

Principi A Rutas Norte
Res no és de confiança per la seva ubicació Un pod a rutas-norte-pro no té accés privilegiat per ser-hi
Verificar identitat a cada petició ServiceAccounts (03-06) i RBAC (08-01); mTLS si hi hagués malla
Mínim privilegi RBAC ajustat i polítiques de xarxa per component
Assumir el compromís Microsegmentació i control de sortida limiten el dany
Xifrar en trànsit TLS a l'Ingress, WireGuard al CNI, TLS a PostgreSQL
Registrar i verificar-ho tot Hubble, auditoria de l'apiserver (08-06), Falco

La idea d'assumir el compromís és la que més canvia el disseny. No es tracta de "com evito que entrin", sinó de "quan entrin, què poden fer i en quant de temps me n'assabento?".

La taula de defensa completa

Capa Mecanisme Atac que mitiga Estat
Perímetre WAF + OWASP CRS Injecció SQL, XSS, recorregut de directoris Per implantar
Perímetre Limitació de peticions Abús, força bruta, denegació de servei a nivell d'aplicació Per implantar
Perímetre Només Ingress, sense NodePort Exposició accidental de serveis interns Fet
Transport TLS amb cert-manager Escolta del trànsit client-plataforma Fet (04-05)
Transport Capçaleres de seguretat Clickjacking, degradació a HTTP, filtrat d'URL Per implantar
Transport WireGuard al CNI Escolta del trànsit entre nodes Recomanat
Transport TLS a PostgreSQL Escolta de dades personals en trànsit Recomanat
Segmentació deny-all als tres entorns Moviment lateral després d'un compromís Fet (04-06) + Kyverno (08-03)
Segmentació Polítiques per component Abast del moviment lateral Fet (04-06)
Segmentació Control de sortida per ipBlock Exfiltració de dades de clients Prioritari
Identitat ServiceAccounts dedicades Suplantació de processos davant de l'API Fet (03-06)
Identitat mTLS amb malla Suplantació entre serveis Ajornat, amb criteris
Autorització RBAC de mínim privilegi Abús de credencials legítimes Fet (08-01)
Autorització Polítiques L7 Accés a rutes no autoritzades de l'API Ajornat
Enduriment securityContext restrictiu Escapada del contenidor al node Fet (08-02)
Enduriment PSA + Kyverno Que algú desplegui alguna cosa insegura Fet (08-03)
Pla de control apiserver privat, etcd xifrat Compromís total del clúster Per revisar
Pla de control kubelet autenticat, sense port de només lectura Execució en pods via kubelet Per revisar
Visibilitat Hubble o registre del proxy Ceguesa davant del sondeig intern i l'exfiltració Prioritari
Visibilitat Alertes sobre denegacions Detecció tardana d'un incident Per implantar

Les tres prioritats que surten d'aquesta taula, per ordre: control de sortida, visibilitat del trànsit i revisió del pla de control. Totes ataquen el mateix escenari: algú ja és a dins.

Errors Comuns i Consells

Escriure només polítiques d'Ingress. Si policyTypes no inclou Egress, la sortida està oberta i amb ella la via d'exfiltració. És l'error més greu i més freqüent d'aquesta lliçó.

Activar Egress i oblidar el DNS. Tot deixa de funcionar d'una forma molt confusa: els noms no resolen i sembla un problema de Service. La regla cap a kube-dns al port 53, UDP i TCP, és la primera que cal escriure.

Fer servir ipBlock: 0.0.0.0/0 sense except. Inclou la xarxa interna del clúster, així que la política és molt més permissiva del que sembla.

Confiar que els ipBlock d'un proveïdor extern no canvien. Canvien. Programa una verificació mensual o fes servir un proxy amb llista de dominis.

Creure que el contenidor ambaixador aïlla la xarxa. Comparteix el namespace de xarxa amb la resta del pod. És un patró d'arquitectura, no un límit de seguretat.

Escriure polítiques per namespace en lloc de per component. Reprodueix la xarxa plana dins del namespace. El cost de fer-ho bé són cinc polítiques més.

Deixar rutas-norte-dev i rutas-norte-pre sense polítiques. La xarxa del clúster és plana entre namespaces, i una política que només es prova a producció es prova en el pitjor moment.

Adoptar una malla de serveis sense necessitar-la. És infraestructura crítica: mal operada, empitjora la seguretat. Defineix criteris objectius per revisar la decisió.

Activar un WAF directament en mode bloqueig. Bloquejarà peticions legítimes i tindràs una incidència. DetectionOnly primer, setmanes d'anàlisi, i després bloqueig.

Fer servir NodePort a producció "perquè és més ràpid de configurar". Se salta el TLS, el WAF i la limitació de peticions, i obre el port a tots els nodes.

Oblidar que l'autoescalat no defensa de la denegació de servei. Converteix una caiguda en una factura. Posa un límit superior a l'HPA.

Xifrar etcd i no reescriure els Secrets existents. Els antics continuen en clar. kubectl get secrets -A -o json | kubectl replace -f -.

No xifrar les còpies de seguretat d'etcd. Una còpia d'etcd conté tots els Secrets del clúster.

Toleràncies universals. Un operator: Exists sense key tolera tots els taints, inclòs el del pla de control, i se sol posar sense adonar-se'n.

Consell d'or: dibuixa el diagrama de què parla amb què i comprova que les polítiques ho reprodueixen exactament, ni una fletxa més. Després, fes la pregunta clau: si comprometen aquest pod, on pot arribar i per on poden treure les dades? Si la resposta a la segona part no és "enlloc", hi ha feina pendent a l'egrés.

Exercicis

Exercici 1: tancar la sortida dels components que no la necessiten

Quatre dels sis components de Rutas Norte no necessiten sortir a internet: botiga-web, postgres-reserves, redis-cache i informes-ocupacio. Escriu una única NetworkPolicy que els permeti només l'imprescindible dins del clúster i els tanqui completament la sortida a internet.

Tingues en compte que:

  • Tots necessiten DNS.
  • postgres-reserves i informes-ocupacio han de poder parlar entre si (el CronJob consulta la base de dades).
  • botiga-web ha de poder arribar a api-reserves.
  • Tots han de poder ser consultats per Prometheus.

Inclou l'ordre que verificaria que la sortida està efectivament tancada.

Exercici 2: decidir sobre la malla de serveis

La direcció de Rutas Norte planteja desplegar Istio "perquè és el que fan servir les empreses grans". Prepara una resposta tècnica d'una pàgina que inclogui:

  1. Quins problemes concrets resoldria a la plataforma actual.
  2. Què costaria, de forma quantificada.
  3. Quines alternatives més barates cobreixen part del benefici.
  4. Criteris objectius i mesurables per revisar la decisió.
  5. Una recomanació clara.

Exercici 3: investigar un incident amb els fluxos de xarxa

A les 03:47 salta l'alerta ConnexionsDenegadesAnomales. Consultes Hubble i obtens:

TIMESTAMP             SOURCE                              DESTINATION                                TYPE         VERDICT   REASON
Aug  6 03:47:09.112   rutas-norte-pro/worker-notif-k9x2   rutas-norte-pro/api-reserves-a1:8080       to-endpoint  FORWARDED
Aug  6 03:47:11.229   rutas-norte-pro/worker-notif-k9x2   rutas-norte-pro/postgres-reserves-0:5432   to-endpoint  FORWARDED
Aug  6 03:47:11.884   rutas-norte-pro/worker-notif-k9x2   rutas-norte-pro/redis-cache-0:6379         to-endpoint  DROPPED   Policy denied
Aug  6 03:47:12.401   rutas-norte-pro/worker-notif-k9x2   192.0.2.55:443                             to-stack     DROPPED   Policy denied
Aug  6 03:47:12.902   rutas-norte-pro/worker-notif-k9x2   192.0.2.55:8443                            to-stack     DROPPED   Policy denied
Aug  6 03:47:13.455   rutas-norte-pro/worker-notif-k9x2   192.0.2.55:53                              to-stack     DROPPED   Policy denied
Aug  6 03:47:20.001   rutas-norte-pro/worker-notif-k9x2   rutas-norte-pro/postgres-reserves-0:5432   to-endpoint  FORWARDED
  1. Què està passant? Justifica la teva lectura línia per línia.
  2. Quines capes de defensa han funcionat i quines no?
  3. Enumera les accions immediates, per ordre de prioritat.
  4. Què hauria passat sense control de sortida? I sense Hubble?

Solucions

Solució 1

# k8s/base/xarxa/sortida-tancada.yaml
# Política de sortida per als components que NO han d'arribar a internet.
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: sense-sortida-a-internet
  namespace: rutas-norte-pro
  labels:
    app.kubernetes.io/part-of: rutas-norte
  annotations:
    seguretat.rutasnorte.example/justificacio: >-
      botiga-web, postgres-reserves, redis-cache i informes-ocupacio no tenen
      cap necessitat d'arribar a internet. Tancar-los la sortida elimina la via
      d'exfiltració de dades personals per a quatre dels sis components.
spec:
  podSelector:
    matchExpressions:
      - key: app
        operator: In
        values:
          - botiga-web
          - postgres-reserves
          - redis-cache
          - informes-ocupacio
  policyTypes: [Egress]
  egress:
    # 1. DNS: imprescindible per a tots
    - to:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: kube-system
          podSelector:
            matchLabels:
              k8s-app: kube-dns
      ports:
        - { protocol: UDP, port: 53 }
        - { protocol: TCP, port: 53 }

    # 2. botiga-web -> api-reserves
    #    (el podSelector d'aquesta regla filtra el destí, no l'origen;
    #     els altres components simplement no fan servir aquest permís)
    - to:
        - podSelector:
            matchLabels:
              app: api-reserves
      ports:
        - { protocol: TCP, port: 8080 }

    # 3. informes-ocupacio -> postgres-reserves
    - to:
        - podSelector:
            matchLabels:
              app: postgres-reserves
      ports:
        - { protocol: TCP, port: 5432 }

    # NO HI HA cap regla amb ipBlock: la sortida a internet està tancada.

I la política d'entrada perquè Prometheus pugui raspar mètriques (és Ingress, complementària):

apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: permetre-prometheus
  namespace: rutas-norte-pro
spec:
  podSelector: {}
  policyTypes: [Ingress]
  ingress:
    - from:
        - namespaceSelector:
            matchLabels:
              kubernetes.io/metadata.name: monitoratge
          podSelector:
            matchLabels:
              app.kubernetes.io/name: prometheus
      ports:
        - { protocol: TCP, port: 9090 }

Una observació important sobre la primera política: en fer servir un podSelector amb matchExpressions que agrupa quatre components, tots comparteixen les mateixes regles de sortida. Això vol dir que redis-cache tècnicament podria connectar amb api-reserves:8080, encara que no ho faci.

És un compromís conscient entre concisió i precisió. La versió estrictament correcta seria una política per component, i és el que recomanaríem a producció seguint el principi 2 de l'apartat 2. Aquesta versió agrupada és acceptable perquè l'objectiu declarat de l'exercici —tancar la sortida a internet— es compleix igualment, i cap dels permisos interns concedits de més no dona accés a dades personals. Documentar aquell raonament és tan important com el YAML.

Verificació:

#!/usr/bin/env bash
NS=rutas-norte-pro
for objectiu in deploy/botiga-web statefulset/postgres-reserves statefulset/redis-cache; do
  echo -n "$objectiu -> internet: "
  if kubectl exec -n "$NS" "$objectiu" -- \
       timeout 5 sh -c 'wget -q -O- --timeout=4 https://ejemplo-externo.example' \
       >/dev/null 2>&1; then
    echo "ARRIBA <-- FALLA"
  else
    echo "bloquejat (correcte)"
  fi
  echo -n "$objectiu -> DNS: "
  kubectl exec -n "$NS" "$objectiu" -- \
    timeout 5 nslookup api-reserves >/dev/null 2>&1 \
    && echo "resol (correcte)" || echo "NO RESOL <-- FALLA"
done
deploy/botiga-web -> internet: bloquejat (correcte)
deploy/botiga-web -> DNS: resol (correcte)
statefulset/postgres-reserves -> internet: bloquejat (correcte)
statefulset/postgres-reserves -> DNS: resol (correcte)
statefulset/redis-cache -> internet: bloquejat (correcte)
statefulset/redis-cache -> DNS: resol (correcte)

Comprovar el DNS és tan important com comprovar el bloqueig: una política de sortida que bloqueja internet i el DNS trenca la plataforma sense que sigui evident per què.

Solució 2

Informe tècnic: adopció d'una malla de serveis a la plataforma Rutas Norte

1. Quins problemes resoldria

Problema real Ho resol? És urgent avui?
Trànsit intern sense xifrar, incloses dades personals Sí, mTLS automàtic Sí, és un risc real
Un servei no pot verificar criptogràficament qui el crida Sí, identitat SPIFFE Moderat: hi ha 6 serveis, tots interns i controlats
No hi ha autorització per ruta HTTP Sí, AuthorizationPolicy Baix: el graf de crides és gairebé lineal
Reintents i temps d'espera duplicats a cada servei Baix: ja està implementat
Falta observabilitat de latència entre serveis Parcialment Baix: el mòdul 7 ja cobreix l'essencial
Desplegaments canari per percentatge de trànsit Baix: avui es fa per rèpliques (11-04)

De sis problemes, un és urgent (xifratge) i cinc són millores desitjables sense urgència.

2. Cost quantificat

Concepte Estimació
Memòria de sidecars 6 components × ~15 pods × 80 MiB ≈ 1,2 GiB de memòria addicional
CPU de sidecars ~0,05 nuclis per pod ≈ 0,75 nuclis
Latència afegida 2-5 ms per salt; al camí botiga-webapi-reservespostgres, fins a 10 ms per petició
Implantació inicial 3-4 setmanes d'una persona de plataforma
Operació contínua ~1 dia al mes: actualitzacions, certificats, diagnòstic
Formació de l'equip 2 setmanes per a 4 persones
Risc d'incidència Mitjà-alt els primers mesos: és la causa més comuna de "el servei retorna 503 i no sé per què"

Amb Istio en mode ambient o amb Linkerd, la memòria i la latència baixarien substancialment. La complexitat operativa i el risc dels primers mesos, no.

3. Alternatives més barates

Alternativa Cobreix Cost
WireGuard al CNI Xifratge de tot el trànsit entre nodes Una opció de configuració; hores de feina
TLS a postgres-reserves Xifratge del tram amb dades personals, fins i tot dins del mateix node 1-2 dies
NetworkPolicies estrictes (ja fetes) Segmentació L3/L4 Fet
Tokens de servei a l'aplicació Autenticació bàsica entre serveis 2-3 dies per servei
Hubble o registre del proxy de sortida Visibilitat de fluxos 1 setmana

Les dues primeres juntes cobreixen el 80 % del benefici de seguretat per menys del 5 % del cost.

4. Criteris objectius per revisar la decisió

Es reobrirà l'avaluació quan es compleixi qualsevol d'aquests:

  • La plataforma superi els 15 serveis amb comunicació entre ells.
  • Més de tres equips despleguin al clúster sense coordinació diària.
  • Aparegui un requisit normatiu o contractual explícit de mTLS entre serveis (per exemple, una certificació d'un client corporatiu).
  • Calguin desplegaments canari per percentatge de trànsit que no es puguin aproximar amb rèpliques.
  • Calgui encaminament entre diversos clústers (11-05).
  • L'equip de plataforma arribi a 5 persones o més, amb capacitat per dedicar-ne una a la malla.

5. Recomanació

No adoptar una malla de serveis ara. En el seu lloc, en aquest ordre:

  1. Activar WireGuard al CNI (aquesta setmana).
  2. Configurar TLS a postgres-reserves (aquest trimestre).
  3. Implantar visibilitat de fluxos (aquest trimestre).
  4. Revisar la decisió cada sis mesos contra els criteris anteriors.

Si en el futur s'adopta, la primera candidata seria Linkerd per la seva relació entre benefici i complexitat, o Istio en mode ambient si per aleshores cal encaminament avançat.

Nota final: aquesta recomanació és una valoració d'arquitectura basada en la mida i la topologia actuals. L'ha de validar el professional de seguretat de l'organització, especialment pel que fa al xifratge del trànsit amb dades personals, on el responsable de compliment normatiu té l'última paraula sobre quin nivell de protecció és exigible.

Solució 3

1. Què està passant, línia per línia

Hora Flux Lectura
03:47:09 worker-notifapi-reserves:8080 FORWARDED Anòmal. El worker llegeix de la base de dades, no crida l'API. La política ho permet (probablement un permís ampli de més), però no és el seu comportament normal
03:47:11 worker-notifpostgres:5432 FORWARDED Legítim per si sol: el worker consulta reserves pendents
03:47:11 worker-notifredis-cache:6379 DROPPED Anòmal. El worker no fa servir la caché. És un sondeig
03:47:12 worker-notif192.0.2.55:443 DROPPED Molt greu. Intent de sortida a una IP externa que no és la passarel·la de pagaments ni l'SMTP
03:47:12 worker-notif192.0.2.55:8443 DROPPED Reintent en un altre port: busca una sortida
03:47:13 worker-notif192.0.2.55:53 DROPPED Reintent al port DNS: una tècnica clàssica per travessar tallafocs permissius
03:47:20 worker-notifpostgres:5432 FORWARDED El més preocupant: després de fallar totes les sortides, torna a la base de dades

La seqüència completa —quatre segons, tres ports diferents cap a la mateixa IP externa, sondeig de serveis interns, i tornada a la base de dades— no correspon a cap comportament legítim de worker-notificacions. La signatura és la d'un procés compromès buscant per on treure informació, i l'última línia suggereix que continua accedint a dades.

Un detall temporal important: són les 03:47. El CronJob informes-ocupacio corre a les 03:00. Caldrà descartar que hi hagi relació.

2. Capes que van funcionar i que no

Capa Resultat
Control de sortida (Egress) Va funcionar. Els tres intents de sortir a internet van ser bloquejats. Aquesta és la capa que ha impedit la fuga de dades
Microsegmentació Va funcionar parcialment. Va bloquejar Redis, però va permetre arribar a api-reserves
Visibilitat (Hubble) Va funcionar. Sense ella, aquest incident seria invisible
Alertes Van funcionar. L'alerta va saltar i per això estem investigant
Prevenció del compromís inicial Va fallar. Alguna cosa va permetre executar codi al worker
Política de xarxa del worker Massa permissiva. No hauria de poder arribar a api-reserves
Autorització L7 Absent. Amb accés a api-reserves:8080, pot cridar qualsevol ruta
Xifratge intern Absent. El trànsit cap a postgres va en clar

3. Accions immediates, per prioritat

# Acció Per què
1 Aïllar el pod sense destruir-lo: canviar la seva etiqueta app perquè els Services i les polítiques deixin d'aplicar-s'hi, deixant-lo corrent Talla l'accés conservant la memòria i l'estat per a l'anàlisi. Esborrar-lo destrueix les proves
2 Aplicar una política que li denegui tot, inclosa la base de dades Atura l'accés a dades personals en curs
3 Rotar les credencials de postgres-reserves i revisar els pg_stat i logs de la base de dades El procés va tenir accés legítim; cal saber què va consultar
4 Consultar el registre d'auditoria de l'apiserver (08-06): va fer servir la ServiceAccount? Va llegir Secrets? Determinar l'abast real
5 Revisar els fluxos de les últimes 72 hores d'aquell pod i de tots els del namespace Establir quan va començar i si hi ha altres pods afectats
6 Notificar el responsable de compliment normatiu Hi ha indicis d'accés no autoritzat a dades personals. Els terminis de notificació comencen a córrer des del coneixement del fet
7 Escanejar la imatge del worker a la recerca de vulnerabilitats conegudes (08-06) Trobar el vector d'entrada
8 Endurir la política del worker: treure l'accés a api-reserves Corregir el permís de més
9 Revisar la resta de polítiques buscant permisos igual d'amplis El mateix error pot ser en altres llocs

Les ordres dels dos primers passos:

# 1. Aïllar: treure-li l'etiqueta que el fa destinatari de polítiques i Services
kubectl label pod -n rutas-norte-pro worker-notif-k9x2 app-
kubectl label pod -n rutas-norte-pro worker-notif-k9x2 estat=en-quarantena

# 2. Denegació total per al pod en quarantena
kubectl apply -f - <<'YAML'
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: quarantena
  namespace: rutas-norte-pro
spec:
  podSelector:
    matchLabels:
      estat: en-quarantena
  policyTypes: [Ingress, Egress]
  # Sense regles: es denega absolutament tot
YAML

En treure l'etiqueta app, el ReplicaSet detecta que li falta una rèplica i crea un pod nou i net: el servei es recupera sol mentre el pod compromès queda aïllat per a l'anàlisi. És una tècnica que convé tenir assajada abans de necessitar-la.

4. Els escenaris contrafactuals

Sense control de sortida: les tres connexions a 192.0.2.55 haurien estat FORWARDED. El procés tenia accés legítim a postgres-reserves, és a dir, a la taula de clients amb noms, DNI, telèfons i correus. Això hauria estat una fuga de dades personals consumada, amb obligació de notificació a l'autoritat de control i als afectats. La política d'egrés —aquelles línies de YAML que gairebé ningú no escriu— és l'única cosa que va separar un incident contingut d'una bretxa de dades.

Sense Hubble: no hi hauria alerta. Les connexions denegades són silencioses: sense registre de fluxos, el pod compromès hauria continuat consultant la base de dades indefinidament, i probablement hauria trobat abans o després una via de sortida (una configuració canviada, un permís nou, una política mal revisada). L'incident s'hauria descobert setmanes o mesos després, o mai.

Aquest exercici resumeix la lliçó sencera: la prevenció va fallar —sempre acaba fallant en algun punt—, la contenció va funcionar, i la detecció va permetre reaccionar. Les tres són necessàries, i la que més es descuida és la tercera.

Conclusió

Hem construït l'estratègia de xarxa completa de Rutas Norte, per capes:

  • La microsegmentació parteix de denegar per defecte als tres entorns, amb polítiques per component i no per namespace, i amb revisió periòdica de quines converses continuen sent necessàries.
  • El control del trànsit sortint és la capa que decideix si un compromís es converteix en una fuga de dades. Quatre dels sis components de Rutas Norte no necessiten sortir a internet en absolut, i tancar-los la sortida és la mesura de més valor per menys cost de tota la lliçó.
  • Les NetworkPolicies treballen amb IP, no amb noms de domini. Les sortides són la passarel·la de sortida, el proxy amb llista de dominis permesos (que a més registra els destins) o les polítiques toFQDNs de Cilium.
  • Dins del clúster, després del TLS de l'Ingress, tot viatja en clar. Les opcions són TLS a l'aplicació (prioritari per a postgres-reserves), xifratge a nivell de CNI amb WireGuard (barat i efectiu entre nodes) o mTLS amb una malla de serveis.
  • Una malla de serveis dona identitat criptogràfica per càrrega, mTLS automàtic i autorització L7, a canvi de complexitat operativa, recursos i latència. Rutas Norte no la necessita avui, i hem fixat criteris objectius per revisar aquella decisió. Istio (sidecar o ambient), Linkerd i Cilium Service Mesh cobreixen perfils diferents.
  • Al perímetre: un únic punt d'entrada (res de NodePort), WAF adoptat gradualment, limitació de peticions i capçaleres de seguretat.
  • El pla de control mereix revisió pròpia: apiserver no exposat, etcd xifrat i amb els Secrets reescrits, còpies de seguretat xifrades, kubelet autenticat sense port de només lectura, i nodes de control aïllats amb taints.
  • El que li falta a NetworkPolicy és registre. Hubble, o el registre d'un proxy de sortida, converteix el sondeig intern i els intents d'exfiltració en una cosa visible i alertable.
  • La confiança zero es resumeix a assumir el compromís: no "com evito que entrin", sinó "quan entrin, què poden fer i en quant me n'assabento".

Amb això, la xarxa de Rutas Norte està segmentada, amb la sortida controlada i amb visibilitat del que passa. Però fixa't en una cosa: tot el que hem fet en aquest mòdul protegeix el clúster en temps d'execució. Donem per fet que les imatges que executem són les que ens pensem.

I aquella suposició és fràgil. registry.rutasnorte.example/api-reserves:2.7.1 és una etiqueta, i una etiqueta es pot reassignar: la imatge que es va descarregar ahir pot no ser la d'avui. La imatge base sobre la qual es va construir pot contenir programari que ningú no ha revisat. Les dependències que es van instal·lar durant la construcció vénen de repositoris públics. I res, absolutament res, no impedeix avui que algú amb accés al registre pugi una imatge modificada amb el mateix nom i que el clúster l'executi sense piular, amb tots els nostres securityContext perfectament aplicats a un contenidor que fa una cosa diferent de la que ens pensem.

La lliçó següent, 08-05, Seguretat d'Imatges, recorre la cadena de subministrament completa: on es pot enverinar una imatge, com construir imatges mínimes, per què el digest és l'única referència realment reproduïble, com signar amb Cosign i —el més important— com fer que el clúster rebutgi qualsevol imatge que no estigui signada, amb la política d'admissió que ho garanteix.

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