La lliçó anterior va acabar amb dues fragilitats. comandes-servei s'identifica davant de Keycloak amb un client_secret que viu en un fitxer de configuració, i inventari accepta el seu token sense poder saber si la connexió TLS que li arriba surt del contenidor de comandes o d'un procés qualsevol que hagi copiat aquest secret. I la contrasenya de km0_analitica continua on la va deixar 05-05: a la variable d'entorn AIRFLOW_CONN_KM0_ANALITICA, visible per a qualsevol que pugui fer docker inspect o llegir el docker-compose.yml. Totes dues són la mateixa malaltia: la xarxa interna es continua tractant com un lloc de confiança on n'hi ha prou amb "ser a dins" perquè et creguin, i els secrets que permeten ser a dins estan repartits per fitxers, imatges i entorns.
Aquesta lliçó abandona el perímetre. Primer, el principi: zero trust, cada crida s'autentica, s'autoritza i es xifra, tant se val d'on vingui. Després, l'eina per identificar serveis: certificats per càrrega de treball i mTLS, amb una PKI interna que els emet i rota automàticament, i la malla de serveis com a manera d'obtenir-ho sense tocar el codi. A continuació, l'autorització servei a servei: què pot cridar comandes i què només pot llegir analitica. I per acabar, la gestió de secrets: què és un secret, per què no pot ser al codi, a la imatge ni a l'entorn, i com HashiCorp Vault el custodia, el lliura i, en el cas de les bases de dades, el genera sota demanda amb caducitat. El codi deixa comandes i inventari parlant amb certificats a tots dos costats, inventari autoritzant per identitat de servei, i el DAG km0_vendes_diaries entrant a km0_analitica amb credencials que Vault crea i destrueix per ell. El gateway de la vora, la limitació de taxa i l'auditoria són la lliçó següent.
Contingut
- Del perímetre a zero trust
- Identitat de càrrega de treball: certificats per servei
- mTLS: autenticació a tots dos extrems
- PKI interna: CA arrel, intermèdia, emissió i rotació
- Malla de serveis: mTLS sense tocar el codi
- Autorització servei a servei
- Amenaces internes i contramesures
- Gestió de secrets: què és un secret i on no va
- HashiCorp Vault: motors, autenticació, leases i auditoria
- Injecció de secrets als serveis
- Quilòmetre Zero: PKI amb OpenSSL i gRPC amb mTLS
- Quilòmetre Zero: Vault i credencials dinàmiques per a
km0_analitica - Errors Comuns i Consells
- Exercicis
- Conclusió
- Del perímetre a zero trust
El model clàssic de seguretat és el castell amb fossat: un tallafoc separa "fora" (hostil) de "dins" (de confiança), i un cop a dins, els serveis es parlen lliurement. La fal·làcia 4 de 01-04, "la xarxa és segura", és exactament la creença que el fossat n'hi ha prou. I no n'hi ha prou, per raons que ja hem vist acumular-se:
- El "dins" és enorme i heterogeni: desenes de serveis, centenars de treballs de Spark i Airflow, contenidors que es creen i es destrueixen, proveïdors de núvol, un portàtil de desenvolupador amb VPN.
- Un sol component compromès (un
catalegamb una dependència vulnerable, una imatge amb una porta del darrere) dona accés lateral a tot:hdfs dfs -rmdes de qualsevol contenidor,SELECT *akm0_comandes, publicar acomandes.esdeveniments. - Els atacants amb més impacte solen ser interns, o externs que ja han travessat el fossat: credencials filtrades, phishing a un operador, un proveïdor amb accés.
Zero trust (confiança zero) substitueix "confio perquè ets a dins" per "no confio en ningú per la seva posició a la xarxa": cada petició, també entre serveis interns, ha de demostrar qui la fa (autenticació), ser avaluada contra una política (autorització), i viatjar xifrada. Els tres pilars ja els tenim per a usuaris (06-01, 06-03) i per al xifratge (06-02); el que falta és que els serveis tinguin identitat pròpia i verificable, i que res d'això no depengui de secrets escampats.
| Perímetre | Zero trust |
|---|---|
| Autenticació a la vora; a dins, cap | Autenticació a cada salt, incloent-hi servei → servei i servei → base de dades |
| Autorització per posició a la xarxa (IP, subxarxa, VLAN) | Autorització per identitat (certificat, token) i política explícita |
| Xifratge fins a la vora; a dins, en clar | Xifratge a tots els enllaços (06-02) |
| Un compromís intern es propaga lateralment | El radi de dany és la llista de permisos de la identitat compromesa |
| Secrets compartits i estàtics | Identitats per càrrega de treball, credencials de vida curta, generades sota demanda |
- Identitat de càrrega de treball: certificats per servei
Perquè inventari sàpiga que li parla comandes, comandes necessita una identitat de càrrega de treball (workload identity): una credencial lligada al servei, no a un humà, que no sigui una contrasenya copiada a mà. La forma més sòlida és un certificat X.509 amb la identitat del servei al SAN: comandes presenta el seu certificat, inventari el verifica contra la CA interna i en llegeix el nom.
SPIFFE (Secure Production Identity Framework for Everyone) estandarditza això: cada càrrega de treball té un SPIFFE ID en forma d'URI, spiffe://km0.internal/comandes, que va al SAN del certificat (un SVID, SPIFFE Verifiable Identity Document), i SPIRE és la implementació de referència que emet i rota aquests certificats verificant a quin node i amb quins atributs corre cada procés (attestation). Istio i altres malles fan servir SPIFFE internament. A Quilòmetre Zero farem servir un SAN DNS (comandes.km0.internal) i un URI SPIFFE als certificats, perquè el codi de verificació serveixi amb qualsevol dels dos.
L'important del certificat com a identitat, davant d'un client_secret:
- La clau privada es genera on es fa servir i no viatja mai: la CA només signa la clau pública. Robar la configuració no dona la clau.
- Té caducitat curta incorporada (hores o dies) i es rota automàticament: un certificat robat caduca abans que es pugui explotar gaire.
- La verificació no exigeix consultar ningú: cadena de signatures fins a la CA, com a 06-02.
- Vincula la identitat a la connexió, no a un token que es pugui reenviar: amb mTLS,
inventarisap que l'altre extrem d'aquest socket éscomandes.
- mTLS: autenticació a tots dos extrems
En el TLS de 06-02, només el servidor presentava certificat. mTLS (mutual TLS) afegeix que el client també el presenti i que el servidor el verifiqui. El handshake és el mateix amb un pas més:
sequenceDiagram
participant P as comandes (client)
participant I as inventari (servidor)
P->>I: ClientHello
I->>P: ServerHello + Certificate(inventari) + CertificateRequest (CAs acceptades)
P->>P: Verifica el cert d'inventari: cadena → km0-ca, SAN = inventari, dates
P->>I: Certificate(comandes) + CertificateVerify (signatura amb la privada de comandes) + Finished
I->>I: Verifica el cert de comandes: cadena → km0-ca, dates.<br/>Extreu SAN: comandes.km0.internal / spiffe://km0.internal/comandes
I->>P: Finished
Note over P,I: Canal xifrat; TOTS DOS saben amb qui parlen.<br/>La identitat del client queda disponible per autoritzar cada RPC.
Què comprova cada extrem:
| Comprovació | Client sobre el servidor | Servidor sobre el client |
|---|---|---|
| Signatura vàlida fins a una CA de confiança | Sí (km0-ca) |
Sí (km0-ca; sovint una CA diferent de la dels servidors) |
| Dates de validesa | Sí | Sí |
| Nom | El SAN conté el host al qual s'ha connectat (inventari) |
El SAN conté una identitat coneguda (comandes.km0.internal); no n'hi ha prou que sigui vàlid |
| Ús de clau | extendedKeyUsage = serverAuth |
extendedKeyUsage = clientAuth |
| Revocació | CRL/OCSP, o certificats tan curts que no calgui | Igual |
L'última fila és una decisió de disseny important: la revocació de certificats a TLS (llistes CRL, consultes OCSP) és lenta i fràgil; la pràctica moderna és emetre certificats d'hores i rotar-los, de manera que revocar sigui simplement deixar de renovar.
mTLS conviu amb els tokens de 06-01, no els substitueix: el certificat diu quin servei fa la crida; el JWT a les metadades diu en nom de quin usuari. inventari veurà tots dos: "comandes (certificat) reserva estoc per a u-anna (token)".
- PKI interna: CA arrel, intermèdia, emissió i rotació
Una PKI (public key infrastructure) és el conjunt de CAs, procediments i eines que emeten, distribueixen i roten certificats. La de 06-02 (un km0-ca.key en un directori, signatures a mà amb OpenSSL) serveix per aprendre, però no per a producció, on calen:
flowchart TB
R[CA arrel km0-root<br/>fora de línia, 10 anys<br/>només signa intermèdies] --> I1[CA intermèdia km0-serveis<br/>a Vault PKI o cert-manager, 1 any]
R --> I2[CA intermèdia km0-clients<br/>certificats de client, 1 any]
I1 --> S1[inventari.km0.internal<br/>24 h, renovat cada 16 h]
I1 --> S2[comandes.km0.internal<br/>24 h]
I1 --> S3[repartiment.km0.internal<br/>24 h]
I2 --> C1[operador-jsala<br/>certificat de client, 30 dies]
- CA arrel fora de línia: la seva clau privada només es fa servir per signar CAs intermèdies, un cop l'any, des d'un equip aïllat; si una intermèdia es compromet, es revoca aquesta intermèdia i se n'emet una altra sense tocar els magatzems de confiança (que només contenen l'arrel).
- CA intermèdia en línia: la que signa certificats de serveis cada poques hores. Ha d'estar automatitzada i protegida (HSM en producció exigent, o Vault amb la seva clau segellada).
- Emissió automàtica: el servei (o un agent al seu costat) genera un parell de claus, envia un CSR amb la seva identitat, la PKI verifica que té dret a aquesta identitat (pel seu compte de Kubernetes, el seu rol AppRole, el seu node) i signa. Ningú no toca
openssla mà. - Rotació automàtica: l'agent renova el certificat als dos terços de la seva vida, i el servei el recarrega sense reiniciar (gRPC i Envoy ho suporten).
- Eines: Vault PKI (motor
pkide Vault: la CA intermèdia viu a Vault i emet per API amb polítiques per rol), cert-manager (a Kubernetes: un recursCertificatei el controlador l'emet i el renova, amb Vault, una CA pròpia o Let's Encrypt com a emissor), SPIRE, step-ca (smallstep). L'apartat 11 fa la PKI a mà amb OpenSSL per entendre què automatitzen aquestes eines; l'apartat 12 configura el motorpkide Vault perquè sigui ell qui emeti.
- Malla de serveis: mTLS sense tocar el codi
Configurar mTLS a cada servei, a cada llenguatge, amb rotació, és feina repetida i propensa a errors. Una malla de serveis (service mesh) ho treu del codi: al costat de cada servei corre un sidecar (un proxy Envoy) que intercepta tot el seu trànsit entrant i sortint; el servei parla en clar amb el seu sidecar a localhost, i els sidecars parlen entre ells amb mTLS, amb certificats que el pla de control de la malla emet i rota (SPIFFE per sota). La malla afegeix a més polítiques d'autorització (apartat 6), mètriques, traces (07-02) i patrons de resiliència (07-04) sense que comandes en sàpiga res.
flowchart LR
subgraph Pod1["comandes"]
A[comandes.py] -->|clar, localhost| E1[Envoy sidecar]
end
subgraph Pod2["inventari"]
E2[Envoy sidecar] -->|clar, localhost| B[servidor.py]
end
E1 -->|mTLS<br/>spiffe://.../comandes → spiffe://.../inventari| E2
CP[Pla de control<br/>Istio / Linkerd:<br/>CA, polítiques, configuració] -.certificats, polítiques.-> E1
CP -.-> E2
Istio (Envoy com a sidecar o en mode ambient sense sidecar, pla de control istiod, polítiques PeerAuthentication i AuthorizationPolicy) i Linkerd (proxy propi en Rust, més lleuger, mTLS per defecte) són les dues malles principals; Consul Connect és la de HashiCorp. Totes pressuposen Kubernetes, el desplegament del qual és tema de 07-05: aquí n'hi ha prou amb saber que la malla és la manera industrial d'obtenir el que a l'apartat 11 farem a mà, i que el preu és un proxy per instància (latència d'~1 ms per salt, memòria) i un pla de control per operar.
Quan mTLS al codi i quan malla: amb pocs serveis i un sol llenguatge, al codi (grpcio ho fa bé); a partir d'una desena de serveis, o amb diversos llenguatges, o quan es volen polítiques i observabilitat uniformes, la malla. Quilòmetre Zero, amb sis serveis en Python, ho fa al codi en aquesta lliçó i avaluarà la malla en passar a Kubernetes.
- Autorització servei a servei
Amb mTLS, inventari sap que el crida comandes. Ara decideix si comandes pot fer això. És el RBAC de 06-01 aplicat a identitats de servei, i la política s'expressa com una taula que, a Quilòmetre Zero, viu al mateix servei (i a la malla, si n'hi hagués, com a AuthorizationPolicy):
| Servei cridant | inventari |
comandes |
cataleg |
pagaments |
Kafka |
|---|---|---|---|---|---|
comandes |
ReservarEstoc, AlliberarReserva, ConsultarEstoc |
— | ObtenirProducte |
Cobrar, Reemborsar |
Escriu comandes.esdeveniments |
cataleg |
ConsultarEstoc |
— | — | — | Llegeix inventari.alertes |
repartiment |
— | LlistarPerRepartidor, MarcarLliurada |
— | — | Escriu repartiment.posicions; llegeix comandes.esdeveniments |
analitica (Airflow, Flink, Spark) |
ConsultarEstoc (només lectura) |
— | ObtenirProducte |
— | Llegeix comandes.esdeveniments, repartiment.posicions; escriu repartiment.panell |
pagaments |
— | ActualitzarEstatPagament |
— | — | Escriu pagaments.esdeveniments |
Cada cel·la buida és una crida que el servidor rebutja amb PERMISSION_DENIED encara que el certificat sigui vàlid i el JWT de l'usuari també. Amb aquesta taula, un analitica compromès pot llegir estoc i catàleg, i res més: no reserva, no cobra, no publica comandes. És el mínim privilegi per a serveis, i el seu efecte es mesura a l'exercici 1.
Dos matisos. Primer, la identitat de servei i la d'usuari es combinen: ReservarEstoc exigeix que el cridant sigui comandes i que el JWT tingui rol client o operador; comandes no pot reservar sense un usuari al darrere llevat que sigui amb el seu token de màquina svc-comandes (06-03), que només val per a AlliberarReserva en compensacions. Segon, la política hauria de viure fora del codi tan bon punt creixi (OPA de 06-01, o la malla), però la seva semàntica és sempre aquesta taula.
- Amenaces internes i contramesures
| Amenaça (dins del perímetre) | Exemple a Quilòmetre Zero | Contramesura | Lliçó |
|---|---|---|---|
| Escolta a la xarxa interna | Un contenidor captura el JWT de l'Anna entre comandes i inventari |
TLS a tots els enllaços | 06-02 |
| Suplantació de servei | Un procés s'anuncia com a inventari i rep reserves |
Certificat de servidor verificat pel client | 06-02 |
| Suplantació de client | Un procés amb el client_secret robat crida inventari com a comandes |
mTLS: identitat lligada a una clau privada que no viatja | 06-04 |
| Moviment lateral | cataleg compromès crida pagaments.Reemborsar |
Autorització servei a servei: cataleg no és a la llista |
06-04 |
| Secret al repositori o a la imatge | La contrasenya de km0_analitica al docker-compose.yml |
Vault; escaneig de secrets a CI | 06-04 |
| Secret de llarga vida robat | Una contrasenya de PostgreSQL vàlida durant anys | Credencials dinàmiques amb TTL | 06-04 |
| Publicació d'esdeveniments falsos | comanda.creada fabricat a comandes.esdeveniments |
HMAC de l'embolcall + autenticació del productor a Kafka (mTLS/SASL) + ACL | 06-02, 06-04 |
| Esborrat massiu | hdfs dfs -rm -r /km0/esdeveniments des de qualsevol contenidor |
Identitat per càrrega de treball + ACL a HDFS + object lock als backups | 06-04, 04-03 |
| Administrador amb excés d'accés | Un DBA llegeix telèfons a km0_comandes |
Xifratge de camp amb clau fora de la BD | 06-02 |
| Ús indegut no detectat | Algú consulta 10.000 comandes en una hora | Auditoria i detecció d'anomalies | 06-05 |
- Gestió de secrets: què és un secret i on no va
Un secret és qualsevol dada el coneixement de la qual dona accés: contrasenyes de bases de dades, client_secret de Keycloak, claus d'API de la passarel·la de pagament, claus de signatura, claus privades de certificats, KEK del xifratge de camps, tokens d'accés a MinIO, la clau HMAC d'esdeveniments. Quilòmetre Zero n'acumula ja més d'una dotzena. I hi ha llocs on no han de ser:
| On sol acabar | Per què és un problema |
|---|---|
| El codi font / el repositori | Queda a l'historial de Git per sempre; qualsevol clon el té; els escàners de GitHub en troben milers cada dia |
| La imatge del contenidor | docker history i qualsevol registre on es pugi la imatge l'exposen; les imatges es comparteixen entre entorns |
Variables d'entorn en clar (docker-compose.yml, manifestos) |
Visibles amb docker inspect, a /proc/<pid>/environ, en bolcats d'error, heretades pels processos fills, i versionades juntament amb el codi |
| Fitxers de configuració sense xifrar al servidor | Copiats als backups, llegibles per qualsevol procés amb el mateix usuari |
| Logs | Una cadena de connexió impresa "per depurar" |
| Missatges de xat, tiquets, wikis | El clàssic "et passo la contrasenya per Slack" |
La contrasenya analitica:analitica d'AIRFLOW_CONN_KM0_ANALITICA és a tres d'aquestes files alhora. El que cal és un sistema que custodiï els secrets xifrats, controli qui pot llegir cadascun, lliuri cada secret només al procés autoritzat i només mentre el necessiti, roti sense intervenció i registri cada accés. Això és un gestor de secrets.
- HashiCorp Vault: motors, autenticació, leases i auditoria
Vault és el gestor de secrets de referència fora dels núvols públics (i a dins, quan es vol neutralitat). Conceptes:
- Segellat: les dades de Vault estan xifrades amb una clau mestra que, en arrencar, cal reconstruir amb diverses parts (Shamir) o amb un KMS de núvol (auto-unseal). Un Vault segellat no lliura res.
- Motors de secrets (secrets engines), muntats en rutes:
kv(versió 2): parells clau-valor estàtics amb versionat:kv/km0/comandes/keycloak→{client_secret: ...}.database: credencials dinàmiques. Vault té un usuari administrador a PostgreSQL i, quan un client autoritzat ho demana, executaCREATE ROLE "v-airflow-abc123" WITH PASSWORD '...' VALID UNTIL '...'amb elsGRANTdefinits, retorna l'usuari i la contrasenya, i els destrueix quan expira el lease. Cada treball té el seu propi usuari, efímer i auditable.pki: la CA intermèdia de l'apartat 4:vault write pki_int/issue/km0-serveis common_name=comandes.km0.internal ttl=24hretorna certificat i clau.transit: el KMS de l'envelope encryption de 06-02: Vault xifra i desxifra DEKs amb claus que no surten mai.- Altres: AWS/GCP (credencials de núvol temporals), SSH, RabbitMQ, Kubernetes.
- Mètodes d'autenticació: com un client demostra a Vault qui és: AppRole (un
role_idfix més unsecret_idd'un sol ús i vida curta que es lliura a l'arrencada), Kubernetes (el token del compte de servei del pod, que Vault verifica contra l'API server: sense secret previ), TLS certificates, OIDC/JWT (un token de Keycloak:comandes-serveipodria autenticar-se a Vault amb ell), AWS/GCP IAM, iuserpass/tokenper a humans i desenvolupament. - Polítiques: HCL que diu quines rutes pot llegir o escriure cada identitat:
path "kv/data/km0/comandes/*" { capabilities = ["read"] }. Denegació per defecte. - Leases i renovació: tot secret dinàmic té un lease amb TTL; el client el renova mentre el necessita, o Vault el revoca en expirar. Revocar un lease revoca la credencial al sistema destí (el
DROP ROLEa PostgreSQL). - Rotació: per a secrets estàtics,
kvguarda versions i el servei recarrega; per a la contrasenya arrel de PostgreSQL que fa servir Vault,rotate-rootla canvia per una que només Vault coneix. - Auditoria: un audit device escriu cada petició i resposta (amb els secrets hashejats) a fitxer o syslog: qui va demanar què, quan, amb quina política. Alimentarà l'auditoria de 06-05.
Alternatives: AWS Secrets Manager i Parameter Store, Google Secret Manager, Azure Key Vault (gestionats, integrats amb l'IAM de cada núvol, amb rotació programada per a RDS i similars); Kubernetes Secrets (objectes base64 a etcd: útils com a mecanisme de lliurament al pod, però no són un gestor: etcd s'ha de xifrar en repòs, no hi ha versionat ni credencials dinàmiques, i qualsevol amb accés al namespace els llegeix; el patró habitual és que un operador, External Secrets o el Vault Agent Injector, els ompli des de Vault); SOPS (Mozilla: xifra fitxers YAML/JSON amb claus de KMS o age per poder versionar-los; adequat per a configuració, no per a credencials dinàmiques); Doppler, Infisical, 1Password Secrets Automation.
- Injecció de secrets als serveis
Custodiar el secret no n'hi ha prou; cal fer-lo arribar al procés de manera que no reaparegui als llocs de l'apartat 8:
| Mecanisme | Com | Avantatges | Inconvenients |
|---|---|---|---|
El servei crida Vault (hvac) |
El codi s'autentica (AppRole, Kubernetes) i llegeix el que necessita en arrencar i en renovar | Control total; leases renovables; el secret només en memòria | El servei depèn de Vault a l'arrencada; codi a cada servei |
| Agent / sidecar (Vault Agent, Vault Agent Injector, External Secrets) | Un procés auxiliar s'autentica, obté els secrets, els escriu en un fitxer temporal en memòria (tmpfs) i els renova; el servei només llegeix el fitxer |
Sense codi; renovació transparent; llenguatge indiferent | Un procés més; el servei ha de recarregar quan el fitxer canvia |
| Variable d'entorn injectada en arrencar | L'agent o l'orquestrador l'omple des de Vault just abans de l'exec |
Compatible amb programari que només llegeix env |
Tots els problemes de l'entorn (visible a /proc, heretada) excepte el d'estar versionada; sense renovació |
| Fitxer xifrat al repositori (SOPS) | La CI o l'arrencada el desxifra amb una clau del KMS | Versionat i revisable | Sense dinàmics; la clau del KMS és el nou secret |
La regla: preferir memòria o tmpfs a disc, fitxers a entorn, i leases renovables a valors fixos. Per a Airflow, que no és codi propi, el patró és un backend de secrets: Airflow suporta VaultBackend (apache-airflow-providers-hashicorp), amb el qual PostgresHook(postgres_conn_id="km0_analitica") cerca la connexió a Vault en lloc de a la variable d'entorn; l'apartat 12 el configura i, a més, fa servir hvac per demanar credencials dinàmiques.
- Quilòmetre Zero: PKI amb OpenSSL i gRPC amb mTLS
Ampliem la PKI de 06-02 amb una CA de client conceptualment separada (aquí, per senzillesa, la mateixa arrel signa tots dos usos, però el certificat de client porta clientAuth i un SPIFFE ID), i emetem certificats per a comandes i inventari:
# km0/certs/emetre.sh: emet un certificat de servei amb SAN DNS + URI SPIFFE
# Ús: ./emetre.sh comandes | ./emetre.sh inventari
set -e
S=$1
cd "$(dirname "$0")"
openssl ecparam -name prime256v1 -genkey -noout -out "$S.key" # la clau neix aquí i no en surt
openssl req -new -key "$S.key" -subj "/CN=$S" -out "$S.csr"
cat > "$S.ext" <<EOF
subjectAltName = DNS:$S, DNS:$S.km0.internal, DNS:localhost, URI:spiffe://km0.internal/$S
extendedKeyUsage = serverAuth, clientAuth
keyUsage = digitalSignature
EOF
# 24 h de validesa: en producció això ho fa Vault PKI o cert-manager cada 16 h
openssl x509 -req -in "$S.csr" -CA km0-ca.crt -CAkey km0-ca.key -CAcreateserial \
-days 1 -sha256 -extfile "$S.ext" -out "$S.crt"
rm "$S.csr" "$S.ext"
openssl x509 -in "$S.crt" -noout -subject -enddate -ext subjectAltNameCada servei rep serverAuth i clientAuth perquè gairebé tots són totes dues coses (comandes és servidor per a la web i client d'inventari). El servidor inventari exigeix ara certificat de client:
# km0/serveis/inventari/servidor.py (arrencada amb mTLS)
credencials = grpc.ssl_server_credentials(
private_key_certificate_chain_pairs=[(_llegir("certs/inventari.key"),
_llegir("certs/inventari.crt"))],
root_certificates=_llegir("certs/km0-ca.crt"), # CA amb què verificar els CLIENTS
require_client_auth=True, # sense certificat de client vàlid, no hi ha handshake
)
servidor = grpc.server(futures.ThreadPoolExecutor(max_workers=10),
interceptors=[ServeiInterceptor(), AuthInterceptor()]) # primer quin servei?, després quin usuari?
servidor.add_secure_port("0.0.0.0:50051", credencials)El client comandes presenta el seu:
# km0/serveis/comandes/client_inventari.py (mTLS)
credencials = grpc.ssl_channel_credentials(
root_certificates=_llegir("certs/km0-ca.crt"), # per verificar inventari
private_key=_llegir("certs/comandes.key"), # identitat de comandes
certificate_chain=_llegir("certs/comandes.crt"),
)
canal = grpc.secure_channel("inventari:50051", credencials)I l'interceptor que autoritza per identitat de servei. gRPC exposa el certificat del client a context.auth_context(); d'ell n'extraiem els SAN i els comparem amb la política de l'apartat 6:
# km0/serveis/inventari/servei_interceptor.py
import grpc, contextvars
# Política servei→mètode (la fila d''inventari' de la taula de l'apartat 6)
PERMISOS_SERVEI = {
"spiffe://km0.internal/comandes": {"ReservarEstoc", "AlliberarReserva", "ConsultarEstoc"},
"spiffe://km0.internal/cataleg": {"ConsultarEstoc"},
"spiffe://km0.internal/analitica": {"ConsultarEstoc"},
"spiffe://km0.internal/operador": {"ConsultarEstoc", "AjustarEstoc", "ObservarCanvis"},
}
_SERVEI_ACTUAL = contextvars.ContextVar("servei_km0")
def _avortar(codi, detall):
def handler(request, context):
context.abort(codi, detall)
return grpc.unary_unary_rpc_method_handler(handler)
def _spiffe_id_de(auth_context: dict) -> str | None:
"""auth_context és {clau: [bytes,...]}; els SAN arriben a 'x509_subject_alternative_name'."""
for san in auth_context.get("x509_subject_alternative_name", []):
s = san.decode()
if s.startswith("spiffe://"):
return s
return None
class ServeiInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
metode = handler_call_details.method.rsplit("/", 1)[-1] # 'ReservarEstoc'
# L'interceptor no té el context de la crida; embolcallem el handler real
handler = continuation(handler_call_details)
if handler is None:
return None
def embolcallat(request, context):
spiffe = _spiffe_id_de(context.auth_context())
if spiffe is None:
context.abort(grpc.StatusCode.UNAUTHENTICATED, "certificat sense SPIFFE ID")
if metode not in PERMISOS_SERVEI.get(spiffe, set()):
context.abort(grpc.StatusCode.PERMISSION_DENIED,
f"{spiffe} no pot invocar {metode}")
_SERVEI_ACTUAL.set(spiffe)
return handler.unary_unary(request, context)
return grpc.unary_unary_rpc_method_handler(
embolcallat, request_deserializer=handler.request_deserializer,
response_serializer=handler.response_serializer)
def servei_cridant() -> str:
return _SERVEI_ACTUAL.get()(Per als mètodes de streaming com ObservarCanvis s'embolcalla handler.unary_stream de la mateixa manera; s'omet per brevetat.) Ara una reserva registra totes dues identitats: a ReservarEstoc, servei_cridant() retorna spiffe://km0.internal/comandes i claims_de(context)["sub"] (06-01) retorna u-anna. Prova el rebuig emetent un certificat per a cataleg (./emetre.sh cataleg) i cridant ReservarEstoc amb ell: PERMISSION_DENIED: spiffe://km0.internal/cataleg no pot invocar ReservarEstoc. I sense certificat de client (ssl_channel_credentials només amb la CA): el handshake falla abans d'arribar a cap interceptor, amb UNAVAILABLE: ... peer did not return a certificate.
Al docker-compose.yml, cada servei munta només la seva clau i el seu certificat més la CA; km0-ca.key no es munta a cap. Per no reescriure certificats de 24 hores a mà, l'apartat següent delega l'emissió a Vault.
- Quilòmetre Zero: Vault i credencials dinàmiques per a
km0_analitica
km0_analiticaVault a docker-compose.yml
# km0/docker-compose.yml (fragment)
vault:
image: hashicorp/vault:1.17
command: ["vault", "server", "-dev", "-dev-root-token-id=km0-dev-root",
"-dev-listen-address=0.0.0.0:8200"]
# ADVERTÈNCIA: el mode -dev arrenca sense segellar, en memòria (tot es perd en reiniciar),
# sense TLS i amb un token arrel fix. Serveix per aprendre l'API. En producció: emmagatzematge
# Raft, TLS, auto-unseal amb KMS, token arrel revocat després de la configuració inicial.
cap_add: [ IPC_LOCK ]
ports: [ "8200:8200" ]
postgres-analitica:
image: postgres:16
environment: { POSTGRES_USER: analitica_admin, POSTGRES_PASSWORD: canviam, POSTGRES_DB: km0_analitica }
# analitica_admin només el farà servir Vault; després, 'rotate-root' farà que ni nosaltres la sapiguemConfiguració inicial, un sol cop (en producció això és un script versionat, executat amb un token d'administració que després es revoca):
export VAULT_ADDR=http://localhost:8200 VAULT_TOKEN=km0-dev-root
# 1. Secrets estàtics: el client_secret de Keycloak de comandes (06-03) i la clau HMAC d'esdeveniments (06-02)
vault secrets enable -path=kv -version=2 kv
vault kv put kv/km0/comandes/keycloak client_id=comandes-servei client_secret="$(openssl rand -base64 32)"
vault kv put kv/km0/comu/esdeveniments hmac_key="$(openssl rand -base64 32)"
# 2. Credencials dinàmiques de PostgreSQL per a km0_analitica
vault secrets enable database
vault write database/config/km0_analitica \
plugin_name=postgresql-database-plugin \
allowed_roles="airflow-escriptura,spark-lectura" \
connection_url="postgresql://{{username}}:{{password}}@postgres-analitica:5432/km0_analitica?sslmode=disable" \
username="analitica_admin" password="canviam"
vault write -f database/rotate-root/km0_analitica # Vault canvia 'canviam' per una cosa que només ell sap
vault write database/roles/airflow-escriptura \
db_name=km0_analitica \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT, INSERT, DELETE ON vendes_diaries TO \"{{name}}\";" \
default_ttl="2h" max_ttl="8h"
vault write database/roles/spark-lectura \
db_name=km0_analitica \
creation_statements="CREATE ROLE \"{{name}}\" WITH LOGIN PASSWORD '{{password}}' VALID UNTIL '{{expiration}}'; \
GRANT SELECT ON ALL TABLES IN SCHEMA public TO \"{{name}}\";" \
default_ttl="1h" max_ttl="4h"
# 3. PKI: la CA intermèdia que emet els certificats de servei de l'apartat 11
vault secrets enable -path=pki_int pki
vault secrets tune -max-lease-ttl=8760h pki_int
vault write -format=json pki_int/intermediate/generate/internal common_name="km0 serveis CA" | jq -r .data.csr > int.csr
openssl x509 -req -in int.csr -CA certs/km0-ca.crt -CAkey certs/km0-ca.key -CAcreateserial -days 365 \
-sha256 -extensions v3_ca -out int.crt # l'arrel (fora de línia) signa la intermèdia
vault write pki_int/intermediate/set-signed [email protected]
vault write pki_int/roles/km0-serveis \
allowed_domains="km0.internal" allow_subdomains=true allow_bare_domains=false \
allowed_uri_sans="spiffe://km0.internal/*" server_flag=true client_flag=true max_ttl="24h"
# 4. Polítiques: què pot llegir cada identitat (denegació per defecte)
vault policy write airflow - <<EOF
path "database/creds/airflow-escriptura" { capabilities = ["read"] }
path "kv/data/km0/comu/esdeveniments" { capabilities = ["read"] }
EOF
vault policy write comandes - <<EOF
path "kv/data/km0/comandes/keycloak" { capabilities = ["read"] }
path "kv/data/km0/comu/esdeveniments" { capabilities = ["read"] }
path "pki_int/issue/km0-serveis" { capabilities = ["update"] }
EOF
# 5. Autenticació AppRole: role_id fix per servei + secret_id d'un sol ús lliurat en arrencar
vault auth enable approle
vault write auth/approle/role/airflow token_policies="airflow" token_ttl=1h token_max_ttl=8h \
secret_id_ttl=10m secret_id_num_uses=1
vault write auth/approle/role/comandes token_policies="comandes" token_ttl=1h token_max_ttl=24h \
secret_id_ttl=10m secret_id_num_uses=1
# 6. Auditoria
vault audit enable file file_path=/vault/logs/audit.logFixa't en rotate-root: després d'aquesta ordre, la contrasenya canviam deixa de valer i ningú no la coneix llevat de Vault. I en secret_id_num_uses=1: el secret_id es lliura al contenidor en arrencar (per l'orquestrador, o per un operador el primer cop), es bescanvia per un token de Vault i mor. El que queda a la configuració del servei és el role_id, que per si sol no serveix de res.
El flux de credencials dinàmiques
sequenceDiagram
participant A as Airflow (tasca carregar_postgres)
participant V as Vault
participant PG as PostgreSQL km0_analitica
A->>V: POST /auth/approle/login (role_id, secret_id)
V-->>A: token de Vault (1 h, política airflow)
A->>V: GET /database/creds/airflow-escriptura
V->>PG: CREATE ROLE "v-approle-airflow-esc-x7Kq..." PASSWORD '...' VALID UNTIL now()+2h; GRANT ...
V-->>A: {username, password, lease_id, lease_duration: 7200}
A->>PG: connexió amb v-approle-airflow-esc-x7Kq / DELETE + INSERT vendes_diaries
A->>V: PUT /sys/leases/revoke (lease_id) — en acabar la tasca
V->>PG: DROP ROLE "v-approle-airflow-esc-x7Kq..."
Note over V,PG: Si la tasca mor sense revocar, Vault executa el DROP en expirar el lease (2 h)
Cada execució de carregar_postgres entra a PostgreSQL amb un usuari que no existia deu segons abans i no existirà deu segons després; pg_stat_activity i els logs de PostgreSQL mostren v-approle-airflow-escriptura-..., diferent a cada run, de manera que "qui va esborrar les files del dia 19?" té una resposta exacta al log d'auditoria de Vault.
Codi: hvac al DAG
# km0/dags/km0_secrets.py: accés a Vault des de les tasques d'Airflow
# pip install hvac psycopg
import os, hvac, psycopg
from contextlib import contextmanager
VAULT_ADDR = "http://vault:8200"
def client_vault() -> hvac.Client:
"""S'autentica per AppRole. El role_id és configuració; el secret_id arriba a
l'arrencada del worker (fitxer tmpfs escrit per l'orquestrador) i només val un cop."""
c = hvac.Client(url=VAULT_ADDR)
with open("/run/secrets/vault_secret_id") as f: # tmpfs, no variable d'entorn
secret_id = f.read().strip()
c.auth.approle.login(role_id=os.environ["VAULT_ROLE_ID"], secret_id=secret_id)
assert c.is_authenticated()
return c
def llegir_kv(c: hvac.Client, ruta: str) -> dict:
"""Secret estàtic: p. ex. llegir_kv(c, 'km0/comu/esdeveniments')['hmac_key']"""
return c.secrets.kv.v2.read_secret_version(path=ruta, mount_point="kv")["data"]["data"]
@contextmanager
def connexio_analitica(c: hvac.Client, rol: str = "airflow-escriptura"):
"""Credencials dinàmiques: usuari i contrasenya nous, revocats en sortir del bloc."""
creds = c.secrets.database.generate_credentials(name=rol)
usuari, contrasenya, lease = creds["data"]["username"], creds["data"]["password"], creds["lease_id"]
print(f"[vault] credencial {usuari} (lease {creds['lease_duration']} s)")
try:
with psycopg.connect(host="postgres-analitica", dbname="km0_analitica",
user=usuari, password=contrasenya, sslmode="require") as conn:
yield conn
finally:
c.sys.revoke_lease(lease_id=lease) # DROP ROLE immediat: no esperem el TTL
print(f"[vault] lease {lease} revocat")I la tasca de dags/vendes_diaries.py (05-05) queda així, sense PostgresHook ni connexió configurada a l'entorn:
# km0/dags/vendes_diaries.py (tasca carregar_postgres, versió amb Vault)
from km0_secrets import client_vault, connexio_analitica
def carregar_postgres(ds, ti, **_):
files = llegir_parquet_del_dia(ds) # com a 05-05
vault = client_vault()
with connexio_analitica(vault, "airflow-escriptura") as conn, conn.cursor() as cur:
cur.execute("DELETE FROM vendes_diaries WHERE dia = %s", (ds,))
cur.executemany("INSERT INTO vendes_diaries VALUES (%s, %s, %s, %s, %s)", files)
total = sum(f[4] for f in files)
conn.commit() # DELETE + INSERT en una transacció (idempotent)
ti.xcom_push(key="total", value=total)Al docker-compose.yml d'Airflow desapareix la línia AIRFLOW_CONN_KM0_ANALITICA: postgresql://analitica:analitica@... i apareix VAULT_ROLE_ID (no secret) i el muntatge tmpfs de /run/secrets. Per a les connexions que Airflow gestiona pel seu compte (WebHDFS, Spark), es configura el backend airflow.providers.hashicorp.secrets.vault.VaultBackend a airflow.cfg, i PostgresHook(postgres_conn_id=...) les cercaria a kv/km0/airflow/connections/<id> de manera transparent.
Per a comandes, el mateix client_vault() llegeix kv/km0/comandes/keycloak per al client_secret de 06-03 (que per fi no és al codi) i demana el seu certificat a pki_int/issue/km0-serveis amb common_name=comandes.km0.internal i uri_sans=spiffe://km0.internal/comandes, renovant-lo a les 16 hores: l'emetre.sh de l'apartat 11 queda com a referència didàctica.
Errors Comuns i Consells
- "És intern, no necessita autenticació". És la fal·làcia 4 amb altres paraules. Cada crida entre serveis s'autentica (certificat), s'autoritza (política) i es xifra.
- mTLS amb certificat vàlid però sense comprovar el nom. Que el client tingui un certificat de
km0-canomés diu que és algú de la plataforma;analiticano ha de poder reservar estoc. Extreu el SAN i aplica la política. - Certificats d'un any rotats a mà. Caduquen un diumenge a les 3:00 i ningú no se'n recorda. Certificats curts amb emissió i renovació automàtiques (Vault PKI, cert-manager, SPIRE).
- Muntar la clau de la CA als serveis "perquè es generin el seu certificat". La clau de la CA només la té la PKI; els serveis envien un CSR.
- Vault en mode dev en producció, o amb el token arrel en ús diari. El mode dev és una aula; el token arrel es revoca després de la configuració inicial.
- Un secret a Vault i una còpia "per si de cas" a l'entorn. La còpia és la que es filtra. Un sol lloc.
- Leases que ningú no renova ni revoca. Una tasca llarga mor a mitges perquè la seva credencial ha caducat; o milers de rols zombis a PostgreSQL perquè ningú no revocava. Renovar a les tasques llargues, revocar en acabar, TTL màxim raonable.
- Kubernetes Secrets com a gestor de secrets. Són un mecanisme de lliurament en base64; sense xifratge d'etcd, sense auditoria per accés, sense dinàmics. Omple'ls des de Vault o fes servir l'injector.
- Secrets als logs. Un
print(creds)per depurar acaba a Elasticsearch. Imprimeix el nom d'usuari i ellease_id, mai la contrasenya. - Consell: activa un escàner de secrets a CI (gitleaks, trufflehog) i un pre-commit hook: el moment més barat per trobar una clau al codi és abans del commit.
- Consell: assaja el "dia dolent": revoca tots els leases d'un rol (
vault lease revoke -prefix database/creds/airflow-escriptura) i comprova que el DAG es recupera al següent run demanant credencials noves. Un sistema de secrets que mai no s'ha rotat en producció no se sap rotar. - Advertència de compliment normatiu. L'accés a bases de dades amb dades personals (
km0_analiticaconté agregats, peròkm0_comandesno) i la seva auditoria estan subjectes al RGPD; els procediments de custòdia de claus i les polítiques de Vault s'han de revisar amb un professional de seguretat.
Exercicis
Exercici 1: cataleg compromès
Una dependència de cataleg inclou codi maliciós que executa ordres arbitràries dins del contenidor. Enumera què pot fer l'atacant contra inventari, comandes, pagaments, Kafka, km0_comandes i Vault (a) amb la plataforma tal com estava al final de 06-03 i (b) amb el que s'ha implantat en aquesta lliçó. Per a (b), indica exactament quina peça (certificat, política d'inventari, política de Vault, ACL de Kafka) bloqueja cada intent, i què sí que pot continuar fent.
Exercici 2: Rotar la CA intermèdia
Se sospita que la clau de km0 serveis CA (la intermèdia a Vault) està compromesa. Descriu els passos per substituir-la sense aturar la plataforma, en ordre, i què passa amb les connexions mTLS existents i amb les noves a cada pas. Per què no cal tocar km0-ca.crt a cap servei? Què hauria passat si els serveis haguessin confiat directament en el certificat de la intermèdia en lloc de l'arrel?
Exercici 3: Flink i la clau HMAC
El consumidor de Flink de comandes.esdeveniments (05-04) necessita la clau HMAC d'esdeveniments (06-02) per verificar cada embolcall, i corre com un job de llarga durada (setmanes) en un clúster de Flink. Dissenya com obté la clau de Vault (mètode d'autenticació, política, mecanisme d'injecció), com s'assabenta d'una rotació de la clau sense reiniciar el job, i com verifica esdeveniments signats amb la clau anterior durant la transició. Compara-ho amb les credencials dinàmiques del DAG: per què aquí no serveix un lease de 2 hores?
Solucions
Exercici 1.
(a) Al final de 06-03: cataleg és a la xarxa interna i té la CA per verificar servidors, però inventari no exigeix certificat de client; l'atacant no pot fabricar un token d'usuari (no té la clau privada de Keycloak), però pot cridar inventari amb qualsevol token que capturi o, si cataleg té client_secret propi a l'entorn, obtenir un token svc-cataleg i provar ReservarEstoc (dependria només de ROLS_PER_METODE); pot publicar a comandes.esdeveniments (Kafka sense autenticació de productor: esdeveniments falsos, encara que l'HMAC de 06-02 els faria rebutjar si ja estava implantat); pot connectar-se a km0_comandes si hi ha una cadena de connexió a l'entorn de cataleg o si Cassandra no exigeix autenticació; pot llegir docker-compose.yml o /proc/*/environ del seu contenidor; Vault no existia. (b) Amb aquesta lliçó: inventari exigeix mTLS, cataleg té certificat (spiffe://km0.internal/cataleg) i la política només li permet ConsultarEstoc: ReservarEstoc, AlliberarReserva i AjustarEstoc fallen amb PERMISSION_DENIED (bloqueja: PERMISOS_SERVEI a ServeiInterceptor); comandes i pagaments apliquen la mateixa taula: cataleg no té cap cel·la, tot denegat; Kafka amb autenticació per certificat i ACL: cataleg només pot llegir inventari.alertes, no escriure a comandes.esdeveniments (bloqueja: ACL del broker); km0_comandes: cataleg no té credencials (no hi ha cadena de connexió al seu entorn; les de comandes són dinàmiques i viuen a la memòria de comandes); Vault: cataleg només es pot autenticar amb el seu role_id i un secret_id ja consumit; encara que obtingués un token de Vault, la política cataleg només li deixa llegir el seu propi kv/km0/cataleg/* i emetre certificats amb el seu propi nom (el rol km0-serveis s'hauria de restringir per AppRole a allowed_domains concrets, o fer servir un rol per servei; és una millora que convé aplicar). El que sí que pot fer: tot el que pot fer cataleg: llegir tot el catàleg i l'estoc (ConsultarEstoc), llegir alertes d'inventari, generar URL presignats de km0-fotos (amb les credencials de MinIO de cataleg, que haurien de ser també dinàmiques o de mínim abast), i servir contingut alterat a la web. El radi de dany ha passat de "tota la plataforma" a "la llista de permisos de cataleg", que és la definició de zero trust.
Exercici 2.
(1) Generar una nova CA intermèdia a Vault (pki_int2/intermediate/generate/internal), signar-la amb l'arrel fora de línia, instal·lar-la amb set-signed, i crear-hi el rol km0-serveis. (2) Canviar la ruta d'emissió que fan servir els serveis (o l'agent) a pki_int2/issue/km0-serveis; a mesura que cada servei renova (a les 16 h com a màxim), obté un certificat de la nova intermèdia, la cadena del qual inclou el nou certificat intermedi. Les connexions existents continuen funcionant (TLS no reverifica a mitja sessió); les noves es verifiquen contra km0-ca.crt, que signa totes dues intermèdies, així que tots dos tipus de certificat són vàlids durant la transició. (3) Passades 24 hores (el max_ttl), ja no existeix cap certificat de la intermèdia antiga; es revoca la intermèdia antiga a l'arrel (es publica a la CRL de l'arrel, si es fa servir) i s'elimina pki_int. (4) Si es vol tolerància zero, es pot afegir a més la intermèdia compromesa a una llista de rebuig als servidors durant la finestra. No cal tocar km0-ca.crt perquè els serveis confien en l'arrel, i l'arrel no s'ha compromès: qualsevol intermèdia signada per ella és acceptable, i el certificat de servei porta la cadena completa al handshake. Si haguessin confiat en la intermèdia directament, caldria actualitzar el magatzem de confiança de cada servei abans d'emetre amb la nova, i amb dues intermèdies simultànies durant la transició: exactament el desplegament coordinat que la jerarquia arrel-intermèdia evita.
Exercici 3.
Autenticació: el job de Flink corre en un clúster (YARN, Kubernetes o standalone); si és Kubernetes, mètode kubernetes de Vault amb el compte de servei del job (sense secret previ); si no, AppRole amb secret_id lliurat per l'orquestrador en llançar el job. Política: path "kv/data/km0/comu/esdeveniments" { capabilities = ["read"] } i res més (Flink no necessita PostgreSQL ni PKI, llevat del seu propi certificat per a Kafka, que seria pki_int/issue/km0-serveis). Injecció: Vault Agent com a sidecar (o contenidor auxiliar) que renova el token, escriu hmac_key en un fitxer tmpfs i el reescriu a cada canvi de versió del secret a kv; el job llegeix el fitxer en un RichFunction.open() i el vigila (un fil que comprova el mtime cada minut, o el mateix agent que envia SIGHUP). Rotació: el kv v2 guarda versions; la rotació consisteix a escriure la nova clau com a versió N+1 sense esborrar la N, i el fitxer injectat conté totes dues (hmac_key i hmac_key_anterior, o un petit JSON amb kid → clau); els productors signen amb la nova i inclouen un kid a l'embolcall; el consumidor verifica amb la clau que indiqui kid, acceptant l'anterior durant la retenció del tòpic (7 dies, per exemple) i rebutjant-la després. Comparació: la credencial dinàmica del DAG és una identitat temporal per a una tasca de minuts; un lease de 2 hores obligaria el job de Flink a reautenticar-se i recarregar constantment, i no té sentit com a model: la clau HMAC és un secret compartit de llarga vida que canvia per rotació programada, no una credencial per execució. El que sí que és de vida curta és el token de Vault amb què l'agent llegeix el secret, i aquest sí que es renova cada hora.
Conclusió
Zero trust és la resposta definitiva a la fal·làcia 4: la xarxa no és segura, així que cap salt no es refia de la seva posició en ella; cada crida s'autentica, s'autoritza i es xifra. La identitat d'un servei és un certificat de vida curta la clau del qual no surt mai del procés, amb un nom (DNS o SPIFFE ID) al SAN, i mTLS fa que tots dos extrems de cada connexió sàpiguen amb qui parlen; una PKI amb arrel fora de línia, intermèdies en línia i emissió automàtica (Vault PKI, cert-manager, SPIRE) converteix la rotació en una cosa que passa sola, i una malla de serveis (Istio, Linkerd) ho ofereix sense tocar el codi quan els serveis es multipliquen. Sobre aquesta identitat, l'autorització servei a servei redueix el radi de dany de qualsevol compromís a una fila d'una taula. Els secrets que queden (contrasenyes de bases de dades, claus d'API, claus HMAC) surten del codi, de les imatges i de les variables d'entorn per viure a Vault: motors kv, database, pki i transit, autenticació AppRole o Kubernetes, polítiques de denegació per defecte, leases amb TTL i revocació, auditoria de cada accés, i injecció en memòria o tmpfs. Quilòmetre Zero té ara comandes i inventari amb mTLS i ServeiInterceptor autoritzant per SPIFFE ID, Vault a docker-compose.yml, i el DAG km0_vendes_diaries entrant a km0_analitica amb un usuari que Vault crea per a cada execució i destrueix en acabar; la contrasenya que 05-05 va deixar a AIRFLOW_CONN_KM0_ANALITICA ja no existeix, i la d'analitica_admin només la coneix Vault.
Tot això protegeix l'interior. Però la plataforma té un exterior: Internet, des d'on arriben els navegadors de l'Anna, el Marc i la Llúcia, l'app de 140 repartidors, i també els bots, els robatoris de credencials i les 40.000 peticions per segon de la primera hora de la Setmana de la Verema. Fins ara cada servei ha fet la seva pròpia terminació TLS, la seva pròpia verificació de tokens i la seva pròpia defensa. L'última lliçó del mòdul construeix la vora: una passarel·la única que termina TLS, valida tokens, encamina, limita la taxa per client amb un token bucket a Redis, i deixa registre immutable de qui ha fet què, per tancar el mòdul amb la taula completa de qui pot fer què a Quilòmetre Zero.
Curs d'Arquitectures Distribuïdes
Mòdul 1: Introducció als Sistemes Distribuïts
- Conceptes Bàsics de Sistemes Distribuïts
- Models de Sistemes Distribuïts
- Avantatges i Desafiaments dels Sistemes Distribuïts
- Les Fal·làcies de la Computació Distribuïda
- Temps, Rellotges i Ordenació d'Esdeveniments
- Del Monòlit a la Plataforma Distribuïda: el Cas Quilòmetre Zero
Mòdul 2: Comunicació en Sistemes Distribuïts
- Protocols de Comunicació
- RPC i RMI
- gRPC i Serialització de Dades
- Missatgeria i Cues de Missatges
- Patrons de Comunicació Asíncrona
Mòdul 3: Consistència i Replicació
- Models de Consistència
- El Teorema CAP i PACELC
- Algorismes de Consens
- Replicació de Dades
- Transaccions Distribuïdes i Sagues
Mòdul 4: Emmagatzematge Distribuït
- Particionament de Dades i Hashing Consistent
- Sistemes de Fitxers Distribuïts
- Emmagatzematge d'Objectes
- Bases de Dades Distribuïdes
- Memòries Cau Distribuïdes
Mòdul 5: Computació Distribuïda
- Models de Computació Distribuïda
- MapReduce i Hadoop
- Spark i Computació en Memòria
- Processament de Fluxos de Dades
- Planificació de Treballs i Pipelines de Dades
Mòdul 6: Seguretat en Sistemes Distribuïts
- Autenticació i Autorització
- Xifratge i Protecció de Dades
- Gestió d'Identitats
- Seguretat entre Serveis: mTLS i Gestió de Secrets
- Passarel·les d'API, Limitació de Taxa i Auditoria
Mòdul 7: Monitoratge i Manteniment
- Monitoratge de Sistemes Distribuïts
- Logs Centralitzats i Traçabilitat Distribuïda
- Gestió de Fallades i Recuperació
- Patrons de Resiliència: Timeouts, Reintents i Circuit Breaker
- Automatització i Orquestració
- Proves en Sistemes Distribuïts i Enginyeria del Caos
