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:
- Les NetworkPolicies treballen a L3/L4: entenen d'IP i de ports, no de rutes HTTP ni de mètodes.
api-reservespot arribar apostgres-reserves, sí, però la política no distingeix entre una consulta legítima i un abocament complet de la taula de clients. - No registren res. Un intent de connexió denegat és completament invisible. Si algú està sondejant la xarxa interna, no ens n'assabentem.
- 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-reservesen 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
- Les capes de defensa en xarxa
- Microsegmentació com a principi
- Control del trànsit sortint
- El problema de les IP davant dels noms de domini
- Xifratge en trànsit dins del clúster
- Què és una malla de serveis i què costa
- Istio, Linkerd i Cilium comparats
- Polítiques d'autorització de nivell 7
- Protecció del perímetre
- Seguretat del pla de control
- Registre i visibilitat del trànsit
- Confiança zero aplicada a Rutas Norte
- Errors comuns i consells
- Exercicis
- Conclusió
- 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.
- 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-devsense restriccions de xarxa pot arribar arutas-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"
doneAquella 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.
- 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:
- Algú troba una fallada a
api-reservesi aconsegueix executar codi. - El procés té accés legítim a
postgres-reserves: pot llegir la taula de clients amb noms, DNI, telèfons i correus. - 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: 53Sense 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.
- 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:
- Ara:
ipBlockamb 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. - 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.exampleOK 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.exampleAquesta 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.
- 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 |
Sí: veu tot el trànsit del node |
Un pod amb CAP_NET_RAW i hostNetwork |
Sí: 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 | Sí |
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: trueAvantatge: 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.
- 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-webpot ferPOST /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-web→api-reserves→postgres-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:
- Activar el xifratge WireGuard del CNI (una opció de configuració).
- Configurar TLS a
postgres-reserves, que és on són les dades personals. - Mantenir les NetworkPolicies estrictes amb control de sortida.
- 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.
- 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 | Sí | Sí | Sí | Sí |
| Autorització L7 | Molt completa | Sí (amb waypoint) | Bàsica | Sí |
| Encaminament avançat | El més complet | Complet | Suficient | Bo |
| Multiclúster | Molt madur | Sí | Sí | Sí |
| 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.
- 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 |
Sí | — |
POST /reserves |
Sí | — |
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:
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 | Sí |
| 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-webFunciona 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.
- 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 | Sí: 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 | Sí: ú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(","))'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:
SecRuleEngine DetectionOnly: registra però no bloqueja.- Analitzar els falsos positius durant setmanes de trànsit real.
- Ajustar les regles problemàtiques.
- 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.
- 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 |
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'anticDetall 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:
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):
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)"'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.
- 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:
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 FORWARDEDI el que de debò importa, les denegacions:
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 deniedLlegeix-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.
- 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-reservesiinformes-ocupaciohan de poder parlar entre si (el CronJob consulta la base de dades).botiga-webha de poder arribar aapi-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:
- Quins problemes concrets resoldria a la plataforma actual.
- Què costaria, de forma quantificada.
- Quines alternatives més barates cobreixen part del benefici.
- Criteris objectius i mesurables per revisar la decisió.
- 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- Què està passant? Justifica la teva lectura línia per línia.
- Quines capes de defensa han funcionat i quines no?
- Enumera les accions immediates, per ordre de prioritat.
- 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"
donedeploy/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 | Sí | 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 | Sí | 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-web→api-reserves→postgres, 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:
- Activar WireGuard al CNI (aquesta setmana).
- Configurar TLS a
postgres-reserves(aquest trimestre). - Implantar visibilitat de fluxos (aquest trimestre).
- 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-notif → api-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-notif → postgres:5432 FORWARDED |
Legítim per si sol: el worker consulta reserves pendents |
| 03:47:11 | worker-notif → redis-cache:6379 DROPPED |
Anòmal. El worker no fa servir la caché. És un sondeig |
| 03:47:12 | worker-notif → 192.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-notif → 192.0.2.55:8443 DROPPED |
Reintent en un altre port: busca una sortida |
| 03:47:13 | worker-notif → 192.0.2.55:53 DROPPED |
Reintent al port DNS: una tècnica clàssica per travessar tallafocs permissius |
| 03:47:20 | worker-notif → postgres: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
YAMLEn 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
toFQDNsde 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
- Què és Kubernetes?
- Arquitectura de Kubernetes
- Conceptes i Terminologia Clau
- Configuració d'un Clúster de Kubernetes
- La CLI de Kubernetes: kubectl
- Objectes, Manifests YAML i el Model Declaratiu
- El Projecte del Curs: la Plataforma Rutas Norte
Mòdul 2: Components Principals de Kubernetes
- Pods
- ReplicaSets
- Deployments
- Actualitzacions, Rollbacks i Estratègies de Desplegament
- Serveis
- Namespaces
- Etiquetes, Selectors i Anotacions
Mòdul 3: Gestió de Configuració i Secrets
- ConfigMaps
- Secrets
- Variables d'Entorn
- Quotes i Límits de Recursos
- LimitRanges i Classes de Qualitat de Servei (QoS)
- ServiceAccounts i Accés a l'API des dels Pods
Mòdul 4: Xarxes a Kubernetes
- Xarxes de Clúster
- Tipus de Serveis
- DNS Intern i Descobriment de Serveis
- Controladors d'Ingress
- TLS i Gestió de Certificats amb cert-manager
- Polítiques de Xarxa
Mòdul 5: Emmagatzematge a Kubernetes
- Volums
- Volums Persistents
- Reclamacions de Volums Persistents
- Classes d'Emmagatzematge
- Aprovisionament Dinàmic, Expansió i Snapshots
- Còpies de Seguretat i Restauració de Dades
Mòdul 6: Conceptes Avançats de Kubernetes
- StatefulSets
- DaemonSets
- Treballs i CronJobs
- Init Containers, Sidecars i Patrons Multicontenidor
- Planificació: Afinitat, Taints i Toleracions
- Definicions de Recursos Personalitzats (CRDs)
- Operadors i el Patró Controlador
Mòdul 7: Monitoratge i Registre
- Verificacions de Salut i Sondes
- Servidor de Mètriques i kubectl top
- Monitoratge amb Prometheus
- Visualització i Alertes amb Grafana i Alertmanager
- Registre Centralitzat amb Elasticsearch, Fluentd i Kibana (EFK)
- Depuració d'Aplicacions i Esdeveniments del Clúster
Mòdul 8: Seguretat a Kubernetes
- Control d'Accés Basat en Rols (RBAC)
- Contextos de Seguretat i Enduriment del Contenidor
- Polítiques de Seguretat de Pods i Pod Security Standards
- Seguretat de Xarxa
- Seguretat d'Imatges
- Auditoria, Escaneig i Gestió de Vulnerabilitats
Mòdul 9: Escalat i Rendiment
- Autoescalat Horitzontal de Pods
- Autoescalat Vertical de Pods
- Autoescalat de Clúster
- Escalat per Esdeveniments i Mètriques Personalitzades amb KEDA
- Alta Disponibilitat: PodDisruptionBudgets i Topologia
- Ajust de Rendiment
Mòdul 10: Ecosistema i Eines de Kubernetes
- Minikube i Entorns Locals amb kind
- Kubeadm
- Helm
- Kustomize
- GitOps amb Argo CD i Flux
- Kubernetes Gestionat: EKS, AKS i GKE
Mòdul 11: Estudis de Cas i Aplicacions del Món Real
- Desplegament d'una Aplicació Web
- Execució d'Aplicacions amb Estat
- CI/CD amb Kubernetes
- Estratègies de Desplegament: Blue-Green i Canary
- Gestió Multi-Clúster
- Operació en Producció: Incidències, Runbooks i Costos
