El Mòdul 5 va acabar amb una confessió: en tot el que hem construït fins ara, qualsevol procés podia parlar amb qualsevol servei i llegir qualsevol dada. comandes crida inventari.ReservarEstoc sense dir qui és; el consumidor de Flink llegeix comandes.esdeveniments sense identificar-se; i el monòlit original desava la sessió de l'Anna a la memòria de l'únic procés que existia, cosa que va deixar de funcionar en el moment en què hi va haver dues instàncies darrere d'un balancejador. Les dues preguntes que cap sistema no pot eludir són qui és qui (autenticació) i qui pot fer què (autorització), i en un sistema distribuït tenen una dificultat afegida: la resposta ha de viatjar amb cada petició a través de serveis que no comparteixen memòria, ni procés, ni de vegades ni tan sols equip de desenvolupament.
En aquesta lliçó separarem amb precisió tots dos conceptes, veurem com es desen contrasenyes de manera que una fuita de la base de dades no sigui una catàstrofe, compararem sessions amb estat (galeta més Redis) amb tokens sense estat, desmuntarem un JSON Web Token peça a peça, i estudiarem com aquesta identitat es propaga de la web a comandes i de comandes a inventari. Després construirem l'autorització: rols, atributs i relacions, i el principi de mínim privilegi. El codi deixa Quilòmetre Zero amb un servei identitat que emet tokens RS256, i amb inventari rebutjant qualsevol crida que no arribi signada i autoritzada. Queda deliberadament fora com es delega el login en un proveïdor d'identitat (OAuth 2.0, OpenID Connect, SSO: lliçó 06-03), com s'identifiquen els serveis entre ells amb certificats (06-04) i com es centralitza la validació a la vora (06-05).
Contingut
- Autenticació i autorització: dues preguntes diferents
- Per què una sessió en memòria no sobreviu a la distribució
- Factors d'autenticació i emmagatzematge de contrasenyes
- Sessions amb estat contra tokens sense estat
- JSON Web Tokens: anatomia, signatura i claims
- Caducitat, refresh tokens i revocació
- Errors clàssics amb JWT
- Propagació d'identitat entre serveis
- Models d'autorització: RBAC, ABAC, ReBAC
- On viu la política: centralitzada o a cada servei
- Quilòmetre Zero:
identitat, interceptor gRPC i comprovació ABAC - Errors Comuns i Consells
- Exercicis
- Conclusió
- Autenticació i autorització: dues preguntes diferents
Es confonen constantment perquè solen passar una rere l'altra i perquè totes dues s'abreugen "auth". Val la pena fixar-les:
| Autenticació (AuthN) | Autorització (AuthZ) | |
|---|---|---|
| Pregunta | Ets qui dius que ets? | Pots fer el que demanes? |
| Entrada | Credencials: contrasenya, codi, certificat, token | Identitat ja verificada + acció + recurs |
| Sortida | Una identitat (subjecte) i, sovint, atributs (rols, ids) | Una decisió: permetre o denegar |
| Quan | Un cop per sessió o per token | A cada operació |
| Fallada típica | 401 Unauthorized (mal nom: vol dir "no autenticat") | 403 Forbidden |
| A Quilòmetre Zero | L'Anna demostra ser [email protected] amb la seva contrasenya |
L'Anna pot veure P-2026-000123 perquè és seva; no pot editar formatge-curat |
Un error de disseny freqüent és resoldre la primera i oblidar la segona: l'aplicació comprova que hi ha un usuari vàlid i a partir d'aquí tot es permet. En un marketplace això vol dir que qualsevol client registrat podria, canviant un id a l'URL, veure les comandes del Marc o modificar els preus del Celler Roure Alt. L'autenticació identifica; l'autorització protegeix.
- Per què una sessió en memòria no sobreviu a la distribució
El monòlit de 01-01 feia el que fan tots els frameworks web per defecte: en fer login, desava {"usuari": "anna", "rols": ["client"]} en un diccionari en memòria, generava un identificador aleatori i el retornava en una galeta. A cada petició posterior, cercava la galeta al diccionari. Perfecte amb un sol procés. I a 01-06 la plataforma va passar a tenir:
- Diverses instàncies del mateix servei darrere d'un balancejador: la petició 2 de l'Anna pot aterrar en una instància que mai no va veure el seu login. La solució d'urgència, les sticky sessions (el balancejador envia sempre l'Anna a la mateixa instància), trenca el repartiment de càrrega i perd les sessions quan aquella instància mor o es redesplega.
- Diversos serveis:
comandesrep una petició que ve de la web, però necessita saber que és de l'Anna per consultar les seves comandes i no unes altres, i després cridainventari, que també hauria de saber en nom de qui actua. - Processos sense navegador: l'app de repartidors, el DAG d'Airflow, un consumidor de Kafka. No tenen galetes i no "fan login" en el sentit humà.
Hi ha dues famílies de solucions, i totes dues es fan servir:
flowchart LR
subgraph E["Sessió amb estat"]
C1[Galeta: id opac] --> R[(Redis compartit<br/>sessio:abc123 → anna, rols)]
R --> S1[Instància 1]
R --> S2[Instància 2]
end
subgraph T["Token sense estat"]
C2[Token signat:<br/>sub=anna, rols, exp] --> V1[Instància 1<br/>verifica la signatura]
C2 --> V2[Instància 2<br/>verifica la signatura]
C2 --> V3[inventari<br/>verifica la signatura]
end
En la primera, la identitat viu en un magatzem compartit i la galeta és només una referència. En la segona, la identitat viatja dins del mateix token, protegida per una signatura que qualsevol servei pot verificar sense preguntar a ningú. Abans de comparar-les, cal resoldre el pas previ: com verifiquem la contrasenya de l'Anna el primer cop.
- Factors d'autenticació i emmagatzematge de contrasenyes
Els factors d'autenticació es classifiquen en tres famílies: una cosa que saps (contrasenya, PIN), una cosa que tens (mòbil amb app TOTP, clau FIDO2, targeta), una cosa que ets (empremta, cara). L'autenticació multifactor (MFA) combina dues famílies diferents; dues contrasenyes no són dos factors. La seva implantació a la plataforma, juntament amb la recuperació de comptes, és tema de 06-03; aquí ens centrem en el factor que tota aplicació acaba gestionant: la contrasenya.
La regla: mai xifratge reversible, sempre hash amb sal
Una contrasenya no s'ha de poder recuperar, ni tan sols per part de l'administrador. Si es xifra amb AES i una clau, qui tingui la clau (o la robi juntament amb la base de dades) obté totes les contrasenyes en clar, que els usuaris reutilitzen al banc i al correu. El que és correcte és desar-ne un hash: una funció d'un sol sentit. Però no un hash qualsevol:
| Mètode | Problema |
|---|---|
| MD5 / SHA-1 / SHA-256 de la contrasenya | Rapidíssims: una GPU en prova milers de milions per segon; les taules precalculades (rainbow tables) inverteixen hashes comuns a l'instant |
| SHA-256 amb sal | Anul·la les taules precalculades, però continua sent ràpid: un atac de diccionari per usuari continua sent viable |
| bcrypt, scrypt, Argon2 (hashes de contrasenya) | Dissenyats per ser lents i configurables (cost en temps i, en Argon2/scrypt, en memòria), amb sal aleatòria incorporada |
La sal és un valor aleatori, diferent per a cada usuari, que es concatena a la contrasenya abans de fer-ne el hash i es desa al costat del resultat. Dos usuaris amb la contrasenya estiu2026 tindran hashes diferents, i l'atacant no pot amortitzar un càlcul entre usuaris. El factor de cost fa que verificar una contrasenya trigui, deliberadament, desenes de mil·lisegons: irrellevant per a un login, devastador per a qui n'intenta milers de milions.
Argon2id és la recomanació actual (guanyador de la Password Hashing Competition, resistent a GPU pel seu ús de memòria); bcrypt continua sent perfectament acceptable i és més comú en sistemes existents. A Quilòmetre Zero farem servir argon2-cffi:
# km0/serveis/identitat/contrasenyes.py
# pip install argon2-cffi
from argon2 import PasswordHasher
from argon2.exceptions import VerifyMismatchError
# Paràmetres: 3 iteracions, 64 MiB de memòria, 4 fils. Ajusta'ls perquè
# 'hash' trigui ~100 ms al teu maquinari de producció: és el cost que pagues
# un cop per login i que l'atacant paga milers de milions de vegades.
ph = PasswordHasher(time_cost=3, memory_cost=65536, parallelism=4)
def registrar(contrasenya: str) -> str:
"""Retorna la cadena que es desa a la base de dades. Inclou
algorisme, paràmetres, sal i hash: tot el que cal per verificar."""
return ph.hash(contrasenya)
def verificar(hash_desat: str, contrasenya: str) -> bool:
try:
ph.verify(hash_desat, contrasenya)
except VerifyMismatchError:
return False
return True
def necessita_rehash(hash_desat: str) -> bool:
"""True si el hash es va fer amb paràmetres més febles que els actuals:
al següent login correcte, es recalcula i es desa."""
return ph.check_needs_rehash(hash_desat)
if __name__ == "__main__":
h = registrar("estiu2026")
print(h)
# $argon2id$v=19$m=65536,t=3,p=4$Q2xhdmVBbGVhdG9yaWE$...hash...
print(verificar(h, "estiu2026"), verificar(h, "Estiu2026")) # True FalseFixa't en la cadena resultant: $argon2id$v=19$m=65536,t=3,p=4$<sal>$<hash>. Porta l'algorisme i els paràmetres a dins, de manera que d'aquí a tres anys, quan apugis memory_cost, els hashes antics continuaran verificant-se i necessita_rehash et dirà quins cal actualitzar al següent login. Amb bcrypt el codi és anàleg: bcrypt.hashpw(contrasenya.encode(), bcrypt.gensalt(rounds=12)) i bcrypt.checkpw(...), amb el límit de 72 bytes de contrasenya que convé conèixer.
Dos detalls de disseny que sovint s'obliden: el missatge d'error del login ha de ser idèntic tant si l'usuari no existeix com si la contrasenya és incorrecta (altrament s'enumeren comptes), i el temps de resposta també, cosa que s'aconsegueix verificant contra un hash fictici quan l'usuari no existeix.
- Sessions amb estat contra tokens sense estat
Sessió amb estat: galeta + Redis
La lliçó 04-05 va deixar Redis com a memòria cau del catàleg; és també el magatzem de sessions clàssic. El flux és:
- L'Anna envia usuari i contrasenya a
identitat; es verifica amb Argon2. identitatgenera un id aleatori de 256 bits (secrets.token_urlsafe(32)), desasessio:<id> → {"sub": "u-anna", "roles": ["client"]}a Redis amb TTL, i retorna l'id en una galetaHttpOnly; Secure; SameSite=Lax.- Cada instància de la web, a cada petició, llegeix la galeta, fa
GET sessio:<id>a Redis i obté la identitat.
# Fragment: magatzem de sessions compartit (la web té N instàncies)
import json, secrets, redis
r = redis.Redis(host="redis", decode_responses=True)
TTL_S = 8 * 3600
def crear_sessio(sub: str, roles: list[str]) -> str:
sid = secrets.token_urlsafe(32)
r.set(f"sessio:{sid}", json.dumps({"sub": sub, "roles": roles}), ex=TTL_S)
return sid
def carregar_sessio(sid: str) -> dict | None:
v = r.get(f"sessio:{sid}")
if v is None:
return None
r.expire(f"sessio:{sid}", TTL_S) # sessió lliscant: cada ús la renova
return json.loads(v)
def tancar_sessio(sid: str) -> None:
r.delete(f"sessio:{sid}") # revocació immediata i trivialAvantatges: la revocació és un DEL; el client no veu res de la identitat (l'id és opac); la sessió pot contenir el que es vulgui sense patir per la mida. Inconvenients: cada petició costa un viatge a Redis, Redis és ara un punt crític (si cau, ningú no està autenticat), i sobretot no es propaga: quan la web crida comandes i inventari, aquests serveis no tenen la galeta ni haurien de consultar el Redis de sessions de la web.
Token sense estat
L'alternativa és que identitat emeti un document signat amb la identitat i els seus atributs: un token. Qui el rep en verifica la signatura i confia en el contingut sense consultar ningú. És el mateix principi que els URL presignats de MinIO (04-03), que vam anomenar capacitat: un permís portable, autocontingut, amb caducitat, que es verifica criptogràficament.
| Criteri | Sessió (galeta + Redis) | Token (JWT) |
|---|---|---|
| Estat al servidor | Sí (Redis) | No |
| Cost per petició | Un GET a Redis |
Verificar una signatura (microsegons amb HS256, ~desenes de µs amb RS256) |
| Revocació | Immediata (DEL) |
Difícil: el token és vàlid fins a exp (apartat 6) |
| Propagació entre serveis | No natural | Natural: el token viatja a cada crida |
| Què veu el client | Un id opac | El contingut (signat, no xifrat) |
| Mida | Galeta de ~50 bytes | De centenars de bytes a 1-2 KB per petició |
| Ús típic | Web tradicional monolítica | APIs, microserveis, apps mòbils, màquina a màquina |
A la pràctica moltes plataformes combinen totes dues: la web manté una sessió amb galeta per al navegador (més segura davant de XSS, com veurem) i, en cridar els serveis interns, bescanvia aquesta sessió per un token de vida curta que sí que es propaga. Quilòmetre Zero farà servir tokens des de l'app de repartidors i entre serveis, i la decisió per a la web es discuteix a l'apartat 7.
- JSON Web Tokens: anatomia, signatura i claims
Un JWT (RFC 7519) és una cadena amb tres parts separades per punts, cadascuna en Base64URL:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6ImttMC0yMDI2LTA5In0 . eyJpc3MiOiJodHRwczovL2lkZW50aXRhdC5rbTAiLCJzdWIiOiJ1LWFubmEiLCJhdWQiOlsiY29tYW5kZXMiLCJpbnZlbnRhcmkiXSwiZXhwIjoxNzczNjc0MDAwLCJpYXQiOjE3NzM2NzMxMDAsImp0aSI6IjdmNC4uLiIsInJvbGVzIjpbImNsaWVudCJdfQ . Xk9v...signatura...
- Header:
{"alg": "RS256", "typ": "JWT", "kid": "km0-2026-09"}. L'algorisme de signatura i, opcionalment, l'identificador de la clau (kid) per saber amb quina verificar quan n'hi ha diverses en rotació. - Payload: els claims, un objecte JSON amb afirmacions sobre el subjecte.
- Signatura: es calcula sobre
base64url(header) + "." + base64url(payload)amb l'algorisme indicat. Si algú canvia una lletra del payload, la signatura deixa de quadrar.
Important: el payload està codificat, no xifrat. Qualsevol que tingui el token el pot llegir amb base64 -d. No posis mai en un JWT una dada que el portador no hagi de veure (una contrasenya, el telèfon d'un altre usuari). Existeix una variant xifrada (JWE), poc usada en APIs; el xifratge de dades és tema de 06-02.
Claims estàndard
| Claim | Nom | Significat | A Quilòmetre Zero |
|---|---|---|---|
iss |
issuer | Qui va emetre el token | https://identitat.km0 |
sub |
subject | Identificador del subjecte (estable, no un correu canviant) | u-anna, prod-formatgeria-montblanc, svc-comandes |
aud |
audience | Per a qui és el token; el receptor ha de comprovar que hi és a la llista | ["comandes", "inventari"] |
exp |
expiration | Instant Unix a partir del qual és invàlid | iat + 15 min |
iat |
issued at | Quan es va emetre | — |
nbf |
not before | No vàlid abans d'aquest instant | Rar; útil amb rellotges desincronitzats |
jti |
JWT id | Identificador únic del token | Permet revocar-ne un de concret (apartat 6) i correlacionar l'auditoria (06-05) |
A aquests s'hi afegeixen claims privats propis de l'aplicació. Quilòmetre Zero farà servir roles (llista) i, per als productors, productor_id (slug), i per als repartidors repartidor_id. La regla: pocs claims, estables, i mai els que canvien cada minut (el saldo, el cistell), perquè el token és una foto en el moment de l'emissió.
HS256 contra RS256/ES256: per què els serveis verifiquen amb clau pública
| Algorisme | Tipus | Clau per signar | Clau per verificar | Conseqüència |
|---|---|---|---|---|
| HS256 | HMAC amb SHA-256 (simètric) | Secret compartit | El mateix secret | Tot servei que verifica pot també emetre tokens |
| RS256 | RSA + SHA-256 (asimètric) | Clau privada (només identitat) |
Clau pública (qualsevol) | Els serveis verifiquen sense poder falsificar; signatura de ~256 bytes |
| ES256 | ECDSA P-256 (asimètric) | Clau privada | Clau pública | Com RS256, signatures més curtes (64 bytes) i verificació ràpida; menys suport en llibreries antigues |
| EdDSA | Ed25519 | Clau privada | Clau pública | El més modern i ràpid; suport creixent |
Amb HS256, inventari, comandes, repartiment i analitica necessitarien el secret per verificar, i un compromís de qualsevol d'ells (o de qualsevol dels seus desenvolupadors amb accés a la configuració) permetria fabricar un token d'operador. Amb RS256, només identitat té la clau privada; els altres carreguen la pública, que es pot publicar sense risc (a 06-03 veurem l'estàndard per fer-ho, JWKS). En un sistema distribuït amb més d'un verificador, la signatura asimètrica no és una opció: és l'única sensata.
- Caducitat, refresh tokens i revocació
El preu de no tenir estat és que no es pot retirar un token emès: és vàlid fins a exp, encara que l'usuari tanqui la sessió o se li retiri un rol. Les estratègies, combinables:
- Caducitat curta: tokens d'accés de 5 a 15 minuts. Limita la finestra de dany d'un token robat i la latència amb què s'apliquen els canvis de rol.
- Refresh token: un segon token, de vida llarga (dies o setmanes), amb estat (desat a
identitat, a PostgreSQL o Redis) i d'un sol ús, que serveix únicament per demanar un nou token d'accés. Tancar la sessió o revocar un usuari és esborrar els seus refresh tokens: en 15 minuts com a màxim tots els seus tokens d'accés hauran caducat. La rotació (cada ús del refresh n'emet un de nou i invalida l'anterior) detecta robatoris: si apareix l'antic, algú el va copiar, i s'invalida tota la família. - Llista negra a Redis per
jti: per als casos en què 15 minuts són massa (bloqueig urgent d'un compte compromès), el servei consultaSISMEMBER jwt:revocats <jti>abans d'acceptar el token, amb TTL igual al temps de vida que li quedava. Reintrodueix un viatge a Redis per petició, però només es consulta una estructura petita, i és l'única manera de tenir revocació immediata amb tokens. - Rotació de claus de signatura: publicar la nova clau pública amb un
kidnou, començar a signar amb ella, i retirar l'antiga quan hagin caducat tots els tokens signats amb ella. Els verificadors han d'acceptar diverses claus simultàniament, triades perkid.
sequenceDiagram
participant A as App de l'Anna
participant I as identitat
participant P as comandes
A->>I: POST /login (correu, contrasenya)
I->>I: Argon2 verify
I-->>A: access (15 min, RS256) + refresh (30 dies, opac, a la BD)
A->>P: GET /comandes Authorization: Bearer <access>
P->>P: verificar signatura amb clau pública, exp, aud
P-->>A: 200 comandes de u-anna
Note over A: passen 15 min: access caducat
A->>P: GET /comandes Bearer <access>
P-->>A: 401 (exp)
A->>I: POST /token/refresh (refresh)
I->>I: existeix i no s'ha usat? rotar
I-->>A: nou access + nou refresh
- Errors clàssics amb JWT
L'especificació de JWT és flexible i aquesta flexibilitat ha produït vulnerabilitats famoses. Coneix-les per no repetir-les:
| Error | Què passa | Defensa |
|---|---|---|
Acceptar alg: none |
L'especificació permet tokens "sense signatura"; les llibreries antigues els acceptaven i un atacant podia escriure el payload que volgués | Passar sempre la llista algorithms=["RS256"] a la verificació; no llegir mai l'algorisme del mateix token |
| Confusió d'algorisme (RS256 → HS256) | L'atacant signa amb HS256 fent servir la clau pública (coneguda) com a secret; una llibreria que accepti tots dos algorismes amb la mateixa clau el valida | Igual: llista tancada d'algorismes, i objectes de clau amb tipus (PyJWT ≥ 2 ho impedeix) |
No validar aud |
Un token emès per a l'app de repartidors es reutilitza contra pagaments |
Cada servei verifica que el seu nom és a aud |
No validar exp / iss |
Tokens eterns, o tokens d'un altre emissor amb la mateixa llibreria | Verificació completa per defecte; iss esperat explícit |
| Secrets febles a HS256 | Amb HS256 i secret123, la signatura es trenca per força bruta en minuts |
RS256/ES256; si HS256, secret de ≥ 256 bits aleatoris |
JWT a localStorage |
Qualsevol script injectat (XSS) llegeix localStorage i roba el token; amb una galeta HttpOnly no pot |
A la web, galeta HttpOnly; Secure; SameSite (encara que contingui un JWT), o sessió amb estat; el JWT en memòria de la SPA amb refresh per galeta és el compromís habitual |
| Dades sensibles al payload | El portador les pot llegir i queden en logs i proxies | Només identificadors i rols |
| Tokens d'hores o dies sense refresh | Finestra de dany enorme; canvis de rol que triguen un dia | 5-15 minuts + refresh amb rotació |
| Verificar a cada servei "a mà" | Un s'oblida de aud, un altre de exp |
Una llibreria comuna (km0/serveis/comu/auth.py) i el gateway com a primera barrera (06-05) |
A Quilòmetre Zero: la web desarà el token d'accés només a la memòria de la SPA i el refresh en una galeta HttpOnly; l'app de repartidors, a l'emmagatzematge segur del sistema operatiu; els serveis no el persistiran mai.
- Propagació d'identitat entre serveis
Quan l'Anna confirma la comanda P-2026-000126, la petició travessa diversos salts: web → comandes → inventari (reservar estoc) → pagaments. Qui actua a cada salt?
- En HTTP, el token viatja a la capçalera
Authorization: Bearer <jwt>. - En gRPC, a les metadades de la crida (02-03 les va presentar, i va avançar que servirien per a credencials): la clau
authorizationamb el mateix valorBearer <jwt>. gRPC comprimeix les capçaleres amb HPACK a HTTP/2, així que el cost de repetir-les a cada crida és petit.
Hi ha dos models de propagació:
| Model | Descripció | Avantatges | Inconvenients |
|---|---|---|---|
| Token de l'usuari reenviat (on-behalf-of) | comandes reenvia a inventari el mateix JWT de l'Anna |
inventari sap que la reserva és de l'Anna; auditoria d'extrem a extrem; el mínim privilegi es conserva |
L'aud ha d'incloure tots els serveis de la cadena; un servei intermedi compromès té el token de l'Anna durant 15 min |
| Identitat del servei | comandes crida inventari amb el seu propi token (sub: svc-comandes), i passa l'id d'usuari com a dada |
Tokens de servei simples; no exposa el de l'Anna | inventari ha de confiar que comandes diu la veritat sobre l'usuari: només vàlid si comandes està autenticat com a servei (06-04) |
Quilòmetre Zero farà servir el primer a les crides que executen l'acció d'un usuari (comandes → inventari.ReservarEstoc porta el token de l'Anna), i el segon per a processos autònoms (Airflow llegint el llac, Flink consumint comandes.esdeveniments), que a 06-03 obtindran tokens de màquina i a 06-04 s'identificaran a més per certificat. El mateix mecanisme de metadades servirà a 07-02 per propagar l'identificador de traça.
- Models d'autorització: RBAC, ABAC, ReBAC
Amb la identitat verificada arriba la segona pregunta. Els models principals, de menys a més expressius:
RBAC: control basat en rols
A cada subjecte se li assignen rols, i a cada rol, permisos (acció sobre tipus de recurs). Quilòmetre Zero en defineix quatre:
| Rol | Qui | Permisos (extracte) |
|---|---|---|
client |
Anna, Marc, Llúcia | comandes:crear, comandes:llegir (les pròpies), cataleg:llegir |
productor |
Horta La Vega, Formatgeria Montblanc, Celler Roure Alt | cataleg:llegir, productes:editar (els propis), inventari:ajustar (el propi), comandes:llegir (les que contenen els seus productes) |
repartidor |
Els 140 de furgoneta-3 i la resta |
repartiment:posicio, comandes:llegir (les assignades), comandes:marcar_lliurada |
operador |
Empleats de Quilòmetre Zero | Tot l'anterior + comandes:cancellar, productors:aprovar, campanyes:crear |
RBAC és senzill, auditable ("qui pot aprovar productors? els operadors") i suficient per a permisos de tipus. El seu límit és entre parèntesis a la taula: "les pròpies", "les assignades". Un rol no sap de quin formatge és propietari qui.
ABAC: control basat en atributs
La decisió es pren avaluant atributs del subjecte (rol, productor_id), del recurs (propietari, estat), de l'acció i del context (hora, IP, mercat). La regla que RBAC no podia expressar: "un subjecte amb rol productor pot productes:editar un producte si producte.productor_id == subjecte.productor_id". Amb ABAC, la Formatgeria Montblanc edita formatge-curat i formatge-fresc i no pot tocar vi-crianca. És més expressiu i més difícil d'auditar: per respondre "qui pot editar formatge-curat?" cal avaluar regles, no llegir una taula.
ReBAC: control basat en relacions
Generalitza ABAC modelant les relacions com un graf (usuari:anna és propietari de comanda:P-2026-000123; usuari:marc és membre de productor:celler-roure-alt, que és propietari de producte:vi-crianca) i responent a "existeix un camí que permeti aquesta acció?". És el model de Google Zanzibar i de sistemes com OpenFGA o SpiceDB, i brilla quan hi ha jerarquies i permisos compartits (un productor amb diversos empleats, una carpeta amb subcarpetes). Per a Quilòmetre Zero, RBAC més unes quantes regles ABAC són suficients; ReBAC quedaria per a quan els productors tinguin equips amb permisos delegats.
Mínim privilegi
Transversal als tres: cada subjecte (persona o servei) té exactament els permisos que necessita per a la seva tasca i cap més. analitica llegeix el llac però no pot escriure a km0_comandes; repartiment actualitza posicions però no pot cancel·lar comandes; un operador d'atenció al client veu comandes però no pot crear campanyes. I, per defecte, denegar: el que no està explícitament permès està prohibit. Cada permís extra és superfície d'atac i, quan un servei o un compte es compromet, el radi de dany és exactament la seva llista de permisos.
- On viu la política: centralitzada o a cada servei
Un cop decidides les regles, on s'executen?
| Opció | Com | Avantatges | Inconvenients |
|---|---|---|---|
| A cada servei (codi) | Decoradors i comprovacions a inventari, comandes... |
Sense dependències; latència zero; context complet del domini | Regles duplicades i inconsistents; canviar "qui pot cancel·lar" és un desplegament a diversos serveis |
| Política centralitzada (motor de polítiques) | Cada servei pregunta a un motor amb la tupla (subjecte, acció, recurs, context); el motor avalua regles escrites en un llenguatge declaratiu | Una sola font de veritat; canvis sense redesplegar; auditoria de les regles | Un component més; latència d'una crida (es mitiga amb el motor com a sidecar local); les regles necessiten els atributs del recurs |
El representant més conegut del segon enfocament és Open Policy Agent (OPA) amb el seu llenguatge Rego: els serveis envien un JSON d'entrada i OPA respon allow: true/false. Una regla per al cas de la Formatgeria Montblanc s'escriuria, aproximadament, així:
package km0.productes
default allow := false
allow if {
"operador" in input.subjecte.roles
}
allow if {
input.accio == "editar"
"productor" in input.subjecte.roles
input.recurs.productor_id == input.subjecte.productor_id
}La decisió pragmàtica habitual, i la de Quilòmetre Zero en aquesta lliçó: RBAC gruixut a la vora i a cada servei (té el rol necessari per a aquest mètode?) mitjançant una llibreria comuna, i ABAC fi dins del servei que posseeix les dades (és seu aquest producte?), on hi ha els atributs. Si les regles creixen o cal canviar-les sense redesplegar, s'externalitzen a OPA sense canviar l'estructura: el decorador passa a preguntar al motor.
- Quilòmetre Zero:
identitat, interceptor gRPC i comprovació ABAC
identitat, interceptor gRPC i comprovació ABACConstruirem tres peces: l'emissor de tokens, la verificació a inventari com a interceptor gRPC, i la regla ABAC de productes.
Claus de signatura
# Un sol cop, en desenvolupament. En producció: generades i custodiades pel gestor de secrets (06-04).
mkdir -p km0/serveis/identitat/claus
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 \
-out km0/serveis/identitat/claus/km0-2026-09.pem
openssl pkey -in km0/serveis/identitat/claus/km0-2026-09.pem -pubout \
-out km0/serveis/identitat/claus/km0-2026-09.pubLa .pem (privada) només la munta el contenidor d'identitat; la .pub es distribueix a tots els serveis (a 06-03 se substituirà per un endpoint JWKS).
serveis/identitat/tokens.py
# km0/serveis/identitat/tokens.py
# pip install "PyJWT[crypto]"
import time, uuid
import jwt # PyJWT
ISS = "https://identitat.km0"
KID = "km0-2026-09"
ACCES_TTL_S = 15 * 60
with open("serveis/identitat/claus/km0-2026-09.pem", "rb") as f:
CLAU_PRIVADA = f.read() # només 'identitat' té aquest fitxer
def emetre_acces(sub: str, roles: list[str], aud: list[str], **extra) -> str:
"""Emet un JWT RS256 de 15 minuts. 'extra' permet claims privats
com productor_id o repartidor_id."""
ara = int(time.time())
claims = {
"iss": ISS, "sub": sub, "aud": aud,
"iat": ara, "exp": ara + ACCES_TTL_S,
"jti": str(uuid.uuid4()),
"roles": roles, **extra,
}
return jwt.encode(claims, CLAU_PRIVADA, algorithm="RS256",
headers={"kid": KID})
if __name__ == "__main__":
print(emetre_acces("u-anna", ["client"], ["comandes", "inventari"]))
print(emetre_acces("prod-formatgeria-montblanc", ["productor"],
["cataleg", "inventari"],
productor_id="formatgeria-montblanc"))I la verificació, que no viu a identitat sinó en una llibreria comuna que importen tots els serveis:
# km0/serveis/comu/auth.py
import jwt
from jwt import PyJWKClient # es farà servir a 06-03; aquí carreguem la pública d'un fitxer
ISS = "https://identitat.km0"
CLAUS_PUBLIQUES = {}
with open("serveis/comu/claus/km0-2026-09.pub", "rb") as f:
CLAUS_PUBLIQUES["km0-2026-09"] = f.read()
class TokenInvalid(Exception):
pass
def verificar(token: str, audiencia: str) -> dict:
"""Retorna els claims si el token és vàlid per a aquest servei.
Cada comprovació que segueix correspon a un error de l'apartat 7."""
try:
kid = jwt.get_unverified_header(token).get("kid")
clau = CLAUS_PUBLIQUES[kid] # rotació per kid
return jwt.decode(
token, clau,
algorithms=["RS256"], # llista tancada: mai 'none', mai HS256
audience=audiencia, # aud ha de contenir aquest servei
issuer=ISS, # només el nostre emissor
options={"require": ["exp", "iat", "sub", "jti"]},
leeway=30, # 30 s de tolerància de rellotge (01-05)
)
except (KeyError, jwt.PyJWTError) as e:
raise TokenInvalid(str(e))leeway=30 mereix un comentari: els rellotges d'identitat i d'inventari no estan perfectament sincronitzats (01-05), i un token emès fa 200 ms pot semblar "del futur" (iat posterior al rellotge local) o haver "caducat" mig segon abans. Trenta segons de tolerància eviten errors fantasma sense obrir una finestra significativa.
Interceptor gRPC a inventari
gRPC permet registrar interceptors al servidor: codi que s'executa abans de cada mètode, amb accés a les metadades. És el lloc natural per autenticar, de manera que ReservarEstoc i la resta de mètodes de 02-03 no ho hagin de repetir:
# km0/serveis/inventari/auth_interceptor.py
import grpc
from serveis.comu.auth import verificar, TokenInvalid
# RBAC gruixut per mètode: quins rols poden invocar cada RPC del contracte inventari.proto
ROLS_PER_METODE = {
"/km0.inventari.v1.Inventari/ConsultarEstoc": {"client", "productor", "repartidor", "operador"},
"/km0.inventari.v1.Inventari/ReservarEstoc": {"client", "operador"},
"/km0.inventari.v1.Inventari/AjustarEstoc": {"productor", "operador"},
"/km0.inventari.v1.Inventari/ObservarCanvis": {"operador"},
}
def _avortar(codi, detall):
def handler(request, context):
context.abort(codi, detall)
return grpc.unary_unary_rpc_method_handler(handler)
class AuthInterceptor(grpc.ServerInterceptor):
def intercept_service(self, continuation, handler_call_details):
metode = handler_call_details.method
meta = dict(handler_call_details.invocation_metadata)
auth = meta.get("authorization", "")
if not auth.startswith("Bearer "):
return _avortar(grpc.StatusCode.UNAUTHENTICATED, "falta el token")
try:
claims = verificar(auth[len("Bearer "):], audiencia="inventari")
except TokenInvalid as e:
return _avortar(grpc.StatusCode.UNAUTHENTICATED, f"token invalid: {e}")
permesos = ROLS_PER_METODE.get(metode, set())
if not permesos & set(claims.get("roles", [])):
return _avortar(grpc.StatusCode.PERMISSION_DENIED,
f"{claims['sub']} no pot invocar {metode}")
# Deixem els claims accessibles al mètode a través d'una metadada interna.
# (grpc-python no ofereix un 'context de petició' compartit; el conveni
# de km0 és que el servicer els rellegeix amb 'claims_de(context)'.)
_CLAIMS_ACTUALS.set(claims)
return continuation(handler_call_details)
import contextvars
_CLAIMS_ACTUALS = contextvars.ContextVar("claims_km0")
def claims_de(context) -> dict:
return _CLAIMS_ACTUALS.get()Fixa't en la distinció de codis: UNAUTHENTICATED (no sé qui ets: equivalent a 401) davant de PERMISSION_DENIED (sé qui ets i no pots: 403). Els clients reaccionen de manera diferent a cadascun: el primer dispara un refresh del token; el segon, no.
Al servidor de 02-03 només canvia l'arrencada:
# km0/serveis/inventari/servidor.py (fragment)
servidor = grpc.server(futures.ThreadPoolExecutor(max_workers=10),
interceptors=[AuthInterceptor()])Decorador requereix_rol per a la resta de serveis
Per als serveis HTTP (l'API de comandes, cataleg amb FastAPI o Flask), l'equivalent de l'interceptor és un decorador:
# km0/serveis/comu/auth.py (continuació)
from functools import wraps
from flask import request, g, abort
def requereix_rol(*rols_permesos, audiencia):
def decorador(fn):
@wraps(fn)
def embolcall(*args, **kwargs):
auth = request.headers.get("Authorization", "")
if not auth.startswith("Bearer "):
abort(401)
try:
g.claims = verificar(auth[7:], audiencia)
except TokenInvalid:
abort(401)
if not set(rols_permesos) & set(g.claims.get("roles", [])):
abort(403)
return fn(*args, **kwargs)
return embolcall
return decorador
# Ús a serveis/comandes/api.py:
# @app.get("/comandes/<comanda_id>")
# @requereix_rol("client", "operador", audiencia="comandes")
# def veure_comanda(comanda_id):
# comanda = repositori.carregar(comanda_id)
# if "operador" not in g.claims["roles"] and comanda.client_id != g.claims["sub"]:
# abort(403) # ABAC: només el propietari de la comanda (o un operador) la veu
# return comanda.a_json()Aquell abort(403) de les últimes línies és la comprovació que faltava a l'apartat 1: sense ella, el Marc llegiria la P-2026-000123 de l'Anna canviant l'URL.
La regla ABAC: "la Formatgeria Montblanc només modifica els seus productes"
A inventari, el mètode AjustarEstoc ja ha passat el filtre RBAC (rol productor o operador). La regla fina s'aplica a dins, on hi ha l'atribut del recurs:
# km0/serveis/inventari/servidor.py (fragment del servicer)
from serveis.inventari.auth_interceptor import claims_de
def AjustarEstoc(self, request, context):
claims = claims_de(context)
producte = self._repo.carregar(request.producte) # p. ex. 'formatge-curat'
if producte is None:
context.abort(grpc.StatusCode.NOT_FOUND, "producte desconegut")
es_operador = "operador" in claims["roles"]
es_el_seu_producte = claims.get("productor_id") == producte.productor_id
if not (es_operador or es_el_seu_producte):
context.abort(grpc.StatusCode.PERMISSION_DENIED,
f"{claims['sub']} no es propietari de {request.producte}")
nou = self._repo.ajustar(request.producte, request.mercat, request.delta)
return inventari_pb2.AjustarEstocResponse(estoc=nou)Prova des del client:
# Client de prova (host): dos tokens, dos resultats
from serveis.identitat.tokens import emetre_acces
import grpc, inventari_pb2, inventari_pb2_grpc
canal = grpc.insecure_channel("localhost:50051") # TLS arriba a 06-02, mTLS a 06-04
stub = inventari_pb2_grpc.InventariStub(canal)
t_formatgeria = emetre_acces("prod-formatgeria-montblanc", ["productor"], ["inventari"],
productor_id="formatgeria-montblanc")
t_celler = emetre_acces("prod-celler-roure-alt", ["productor"], ["inventari"],
productor_id="celler-roure-alt")
def ajustar(token, producte, delta):
try:
r = stub.AjustarEstoc(inventari_pb2.AjustarEstocRequest(
producte=producte, mercat="girona", delta=delta),
metadata=(("authorization", f"Bearer {token}"),), timeout=2)
print("OK ", producte, "->", r.estoc)
except grpc.RpcError as e:
print("FAIL", producte, e.code().name, e.details())
ajustar(t_formatgeria, "formatge-curat", +20) # OK formatge-curat -> 45
ajustar(t_celler, "formatge-curat", -45) # FAIL formatge-curat PERMISSION_DENIED prod-celler-roure-alt no es propietari...
ajustar("brossa", "formatge-curat", +1) # FAIL formatge-curat UNAUTHENTICATED token invalid: Not enough segmentsAmb això, inventari ha deixat de ser un servei al qual qualsevol podia buidar l'estoc (la fal·làcia 4 de 01-04). Encara no sap si qui li parla és de debò comandes (això és identitat de servei, 06-04) i encara parla en clar per la xarxa (06-02), però ja no executa res sense saber en nom de qui.
Errors Comuns i Consells
- Confondre 401 i 403. 401/
UNAUTHENTICATEDés "no sé qui ets" i ha de portar el client a autenticar-se (o a refrescar). 403/PERMISSION_DENIEDés "sé qui ets i no pots". Barrejar-los trenca la lògica de reintent dels clients i confon el suport. - Desar contrasenyes amb SHA-256 "perquè és segur". SHA-256 és segur com a hash criptogràfic i pèssim com a hash de contrasenyes: és ràpid. Argon2id o bcrypt, sempre.
- Contrasenyes xifrades "per si l'usuari les oblida". Si li pots enviar la contrasenya per correu, la tens en clar o gairebé. El flux correcte és restablir, mai recuperar.
- Rols al token i rols a la base de dades que es contradiuen. El token és una foto: si retires un rol, triga fins a
expa desaparèixer. Dissenya amb aquesta latència (tokens curts) o revoca perjti. audcom a cadena única en un token que travessa diversos serveis: cadascun rebutjarà el token perquè no és la seva audiència.audadmet una llista, i el token on-behalf-of els ha d'anomenar tots.- Emetre tokens des de diversos llocs. Una única autoritat (
identitat) signa; tota la resta verifica. Si un servei necessita "actuar com" algú, demana un token, no el fabrica. - Confiar en un
X-User-Idque arriba en una capçalera. Sense signatura, qualsevol l'escriu. Només la identitat que ve en un token verificat (o en un certificat, 06-04) és fiable. - Autoritzar només a la vora. El gateway de 06-05 serà una primera barrera còmoda, però cada servei ha de continuar verificant: la crida que arriba d'un altre servei intern no ha passat per la vora.
- Consell: centralitza la verificació en una llibreria (
serveis/comu/auth.py) amb tests que incloguin els atacs de l'apartat 7 (alg: none, HS256 amb la clau pública,audequivocat,exppassat). Un test per vulnerabilitat coneguda val més que qualsevol revisió manual. - Consell: registra cada decisió de denegació amb
sub,jti, mètode i motiu; a 06-05 aquests registres seran la base de l'auditoria i de la detecció d'anomalies. - Advertència de compliment normatiu. El tractament de credencials i d'identificadors de clients està subjecte al RGPD i, si hi ha pagaments, a PCI DSS. El que es descriu aquí és disseny tècnic; la implantació real l'ha de revisar un professional de seguretat i de compliment normatiu.
Exercicis
Exercici 1: Un token robat
Un atacant aconsegueix, mitjançant un script injectat a la web, el token d'accés de l'Anna a les 10:00:00 (exp = 10:15:00). Enumera què pot fer-hi contra comandes i inventari segons el disseny d'aquesta lliçó, fins quan, i quines tres mesures dels apartats 6 i 7 redueixen o eliminen el dany. Després, indica quin mecanisme permetria a un operador tallar-lo a les 10:04 tan bon punt l'Anna avisi, i quin cost té aquest mecanisme a cada petició.
Exercici 2: RBAC i ABAC per a repartiment
Els 140 repartidors de furgoneta-3 fan servir l'app, que crida el servei repartiment amb els mètodes PublicarPosicio(repartidor_id, lat, lon), LlistarLliuraments(repartidor_id) i MarcarLliurada(comanda_id). Un operador pot veure qualsevol posició i qualsevol lliurament. Defineix (a) la taula ROLS_PER_METODE de l'interceptor de repartiment, (b) quin claim privat necessita el token d'un repartidor, (c) la comprovació ABAC de cada mètode en pseudocodi, i (d) què passa si un repartidor crida MarcarLliurada("P-2026-000124") i aquesta comanda està assignada a un altre repartidor.
Exercici 3: HS256 en un sol servei
Un company proposa: "Per a cataleg, que només el crida la web, fem servir HS256 amb un secret a la seva configuració; és més simple i ràpid que RS256". Argumenta en quins casos tindria raó i per què a Quilòmetre Zero, tot i així, es descarta. Inclou a la resposta què passaria el dia que analitica volgués cridar cataleg.
Solucions
Exercici 1.
Amb el token de l'Anna (sub: u-anna, roles: [client], aud: [comandes, inventari]) l'atacant pot fer tot el que pot fer l'Anna: llistar i llegir les seves comandes, crear comandes en nom seu (fins al pas de pagament, que en un disseny correcte exigeix la passarel·la), consultar estoc i reservar estoc en nom seu. No pot editar productes ni veure comandes del Marc: el RBAC i l'ABAC continuen aplicant-se al token, que només porta el rol client i el sub de l'Anna. Ho pot fer fins a les 10:15:00 (més els 30 s de leeway), i no el pot renovar si el refresh token és en una galeta HttpOnly que l'script no pot llegir. Mesures: (1) caducitat curta, que és el que limita el dany a 15 minuts; (2) no tenir el token a localStorage (mantenir-lo a la memòria de la SPA i el refresh en una galeta HttpOnly) redueix molt la probabilitat del robatori i elimina la renovació; (3) rotació de refresh tokens, perquè si d'alguna manera obtingués el refresh, l'ús que en fessin l'atacant i l'Anna generi una detecció i invalidi la família. Per tallar a les 10:04: llista negra per jti a Redis (SADD jwt:revocats <jti> amb TTL fins a exp) consultada per comandes i inventari abans d'acceptar un token; el cost és un SISMEMBER (un viatge a Redis, submil·lisegon) per petició, i una dependència operativa de Redis per a l'autenticació, que és justament el que els tokens sense estat volien evitar; per això s'activa només per als serveis sensibles o es limita a una memòria cau local del conjunt de revocats refrescada cada pocs segons.
Exercici 2.
(a) PublicarPosicio: {repartidor}; LlistarLliuraments: {repartidor, operador}; MarcarLliurada: {repartidor, operador}. Un operador no publica posicions (no condueix). (b) repartidor_id (per exemple furgoneta-3-017), emès per identitat quan l'app fa login. (c) PublicarPosicio: request.repartidor_id == claims.repartidor_id, si no PERMISSION_DENIED (encara millor: ignorar l'argument i fer servir sempre el del token, eliminant la possibilitat de suplantar). LlistarLliuraments: si operador és als rols, qualsevol id; si no, request.repartidor_id == claims.repartidor_id. MarcarLliurada: carregar la comanda; si operador, permetre; si no, comanda.repartidor_assignat == claims.repartidor_id. (d) L'interceptor deixa passar la crida (el rol repartidor pot invocar el mètode); el servicer carrega P-2026-000124, veu que està assignada a un altre repartidor i avorta amb PERMISSION_DENIED i un missatge que no reveli a qui està assignada; la denegació es registra amb sub, jti i comanda per a l'auditoria.
Exercici 3.
Tindria raó si cataleg fos l'únic verificador i l'únic emissor: una aplicació aïllada que emet i verifica els seus propis tokens amb un secret de 256 bits aleatoris és correcta amb HS256, i la verificació HMAC és més ràpida que RSA. Es descarta perquè a Quilòmetre Zero l'emissor és identitat, no cataleg: per verificar amb HS256, cataleg hauria de conèixer el secret d'identitat, i amb ell podria emetre tokens d'operador vàlids per a comandes, inventari i pagaments; el radi de dany de comprometre cataleg (el servei més exposat, el que serveix fotos i llistats a Internet) passaria a ser tota la plataforma. Tampoc no hi ha cap guany real de simplicitat: la llibreria comuna ja verifica RS256 i la clau pública es distribueix sense precaucions. El dia que analitica volgués cridar cataleg amb HS256, caldria donar-li també el secret (un servei més amb capacitat d'emetre), o mantenir dos secrets i dues rutes de verificació; amb RS256, n'hi ha prou que el token d'analitica inclogui cataleg a aud, i cataleg no canvia res.
Conclusió
Autenticar és establir qui és el subjecte; autoritzar és decidir, a cada operació, si pot fer el que demana. En un sistema distribuït la identitat ha de viatjar: una sessió en memòria mor amb la segona instància, una sessió a Redis resol la web però no es propaga als serveis, i un token signat (JWT) porta la identitat a sobre i la fa verificable per qualsevol que tingui la clau pública. Per això la signatura ha de ser asimètrica (RS256/ES256): una única autoritat emet, tothom verifica, i cap verificador no pot fabricar. Un token és una foto amb caducitat: tokens d'accés de minuts, refresh tokens amb estat i rotació, llista negra per jti per a les urgències, i verificació estricta d'alg, aud, iss i exp per no repetir els errors coneguts. Les contrasenyes, quan n'hi ha, es desen amb Argon2id o bcrypt, amb sal i cost, mai de manera reversible. Sobre la identitat verificada es construeix l'autorització: RBAC per a permisos per tipus (client, productor, repartidor, operador), ABAC per a la propietat ("els seus productes", "les seves comandes"), ReBAC quan les relacions es compliquen, amb mínim privilegi i denegació per defecte, aplicada de manera gruixuda a la vora i a cada servei i de manera fina on viuen les dades, amb OPA com a camí d'externalització si les regles creixen. Quilòmetre Zero té ara serveis/identitat/tokens.py, una llibreria serveis/comu/auth.py, un AuthInterceptor a inventari i la primera regla ABAC: la Formatgeria Montblanc només toca els seus formatges.
Però el token de l'Anna encara viatja en clar entre comandes i inventari, igual que el seu telèfon, la seva adreça i els esdeveniments de comandes.esdeveniments que qualsevol amb accés a la xarxa interna podria llegir o alterar. Saber qui és qui no serveix de gaire si la conversa es pot escoltar. La següent lliçó tracta el xifratge i la protecció de dades: en trànsit, amb TLS a gRPC i a cada magatzem; en repòs, amb xifratge de camps i de volums; i en la seva dimensió personal, amb pseudonimització i el dret a l'oblit.
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
