La lliçó anterior va tancar amb dotze mesures prioritzades per a Nimbus, i la número u era l'MFA. No va ser casualitat: diverses de les restants giren també al voltant de la mateixa pregunta —qui ets i què pots fer—. Quan la meitat de la plantilla treballa fora de l'oficina, les dades són al núvol, hi ha una consultora amb accés remot i l'API la consumeix una app mòbil, el perímetre deixa de ser una línia a la xarxa. El que queda separant un atacant de les agendes de les clíniques és una credencial i una decisió d'autorització. Aquesta lliçó desenvolupa totes dues: el cicle de vida d'una identitat i els comptes orfes, què diuen avui les guies sobre contrasenyes, les formes d'MFA ordenades per la seva resistència real al phishing, SSO i els protocols d'identitat amb la diferència exacta entre OAuth 2.0 i OpenID Connect, sessions i JWT amb la seva validació correcta davant de la ingènua, els cinc models d'autorització amb el disseny concret de Nimbus, i els comptes privilegiats amb accés just-in-time i recertificació.

Contingut

  1. La identitat com a nou perímetre
  2. Cicle de vida de la identitat i comptes orfes
  3. Factors d'autenticació i les seves debilitats
  4. Contrasenyes: què diuen avui les guies
  5. MFA ordenat per resistència al phishing
  6. SSO, federació i protocols d'identitat
  7. Sessions i tokens: galetes i JWT
  8. Models d'autorització i l'RBAC de Nimbus
  9. Multi-tenència i aïllament
  10. Comptes privilegiats, de servei i de tercers
  11. Recertificació: la revisió periòdica d'accessos

  1. La identitat com a nou perímetre

El model clàssic dividia el món en dins i fora, i posava el control a la frontera. Aquest model descriu cada vegada pitjor la realitat de Nimbus:

Element On és Ho cobreix un perímetre de xarxa?
Base de dades i buckets (A-01, A-02, A-03) Núvol del proveïdor (A-05) No
19 empleats en remot Cases, cafeteries, trens No
App mòbil dels clients Milers de dispositius aliens No
Repositori i CI/CD (A-06) SaaS extern No
Consultora externa (A-19) La seva pròpia xarxa No
Correu, passarel·la de pagament, correu transaccional SaaS externs No

L'única cosa que travessa tots aquests contextos és la identitat. Si un atacant obté les credencials de la Lucía, no necessita travessar cap tallafoc: entra per la porta principal de cadascun d'aquests sistemes, des de qualsevol lloc del món, i tot el que faci semblarà legítim.

D'aquí la formulació que resumeix l'enfocament Zero Trust de 01-03 aplicat a aquest domini:

La identitat és el nou perímetre. No es confia en una petició per venir «de dins»; es confia per poder verificar qui la fa, amb quin dispositiu, per a quin recurs i sota quines condicions, a cada petició.

Tres conceptes que cal separar amb precisió, perquè es confonen constantment:

Concepte Pregunta Exemple a Nimbus
Identificació Qui dius que ets? Introduir [email protected]
Autenticació Ho pots demostrar? Contrasenya + clau FIDO2
Autorització Què pots fer? La Lucía pot desplegar; no pot veure nòmines

I el parany recurrent: la majoria dels incidents greus no són fallades d'autenticació, són fallades d'autorització. L'IDOR de 01-01 passava amb una sessió perfectament vàlida. Autenticar bé i autoritzar malament és el patró número u de l'OWASP Top 10.


  1. Cicle de vida de la identitat i comptes orfes

Una identitat no és un registre estàtic: és un procés amb tres moments, i el tercer és el que gairebé sempre falla.

flowchart LR
    A["ALTA\nQui entra, amb quins\naccessos i qui ho aprova"] --> B["CANVIS\nCanvi de rol, projecte,\npermisos temporals"]
    B --> C["BAIXA\nRevocacio completa\na tots els sistemes"]
    B -->|"si ningu no retira\nel que hi havia"| D["ACUMULACIO\nDE PRIVILEGIS"]
    C -->|"si s oblida\nalgun sistema"| E["COMPTE\nORFE"]
    D --> F["Un usuari amb\nmes permisos que\nqualsevol administrador"]
    E --> G["Acces valid sense\npropietari ni vigilancia"]

2.1 Alta

Els quatre requisits d'una alta correcta:

  1. Basada en el rol, no en la còpia. L'error clàssic és «dona-li els mateixos permisos que a l'Iván». Es copien també els permisos que l'Iván va acumular per projectes antics, i el privilegi es propaga com una infecció. Ha d'existir un perfil d'accés per rol predefinit.
  2. Amb aprovació registrada del propietari de l'actiu (el camp «propietari» de l'inventari de 01-04 serveix exactament per a això).
  3. Amb caducitat si és temporal. Un becari, un contracte de tres mesos o una consultora han de tenir data de fi al sistema, no a la memòria d'algú.
  4. Amb identitat nominal. Res de comptes compartits: sense identitat nominal no hi ha traçabilitat, i sense traçabilitat no hi ha no-repudi (01-01).

2.2 Canvis: l'acumulació de privilegis

És el problema silenciós. El Rubén va entrar a suport, va ajudar sis mesos a facturació, va participar en la migració de dades i ara coordina l'equip. Si a cada canvi es van afegir permisos sense retirar els anteriors, el Rubén té avui més accessos que qualsevol altra persona de Nimbus, i ningú no ho ha decidit mai.

La regla: tot canvi de rol és una baixa del rol anterior i una alta del nou, no una suma. I tot permís temporal neix amb data de fi.

2.3 Baixa: el cas de Nimbus

Un desenvolupador marxa de Nimbus. La Lucía desactiva el seu compte de correu el mateix dia i dona el procés per acabat. Sis mesos després, en la revisió d'inventari, apareix això:

Sistema Estat després de la «baixa» Risc
Correu corporatiu Desactivat
Compte del proveïdor cloud (A-05) Actiu, amb permisos de lectura sobre els buckets Accés a dades de clients des de qualsevol lloc
GitHub Actiu com a col·laborador extern del repositori Accés al codi i a l'historial, inclosos secrets antics
VPN Desactivada
Base de dades Usuari personal dev_carlos actiu Accés directe a dades de producció
Clau SSH al servidor d'aplicacions Present a authorized_keys Accés al servidor sense passar per cap autenticació centralitzada
Eina de tiquets (SaaS) Activa Dades de clients a les incidències
Grup de WhatsApp de suport Present Captures de pantalla amb dades de clients
Token personal de GitHub creat per ell Actiu i sense caducitat Accés al codi encara que es desactivi el compte

Vuit accessos vius d'una persona que ja no treballa aquí. I cap d'ells no apareix en un tallafoc, perquè tots són legítims des del punt de vista tècnic.

Per què els comptes orfes són especialment perillosos, més enllà de l'obvi:

  • Ningú no els vigila. Un ús anòmal del compte de la Lucía es pot notar; ningú no troba a faltar dev_carlos.
  • Ningú no els manté. La seva contrasenya no es rota, no se'ls afegeix MFA quan es desplega, no se'n revisen els permisos.
  • Són l'objectiu preferit del credential stuffing. Si aquella persona reutilitzava la contrasenya i apareix en una bretxa d'un altre servei, la porta continua oberta.
  • No sempre hi ha mala intenció. L'exempleat que continua accedint «per ajudar» crea un problema igual de seriós: accés sense autorització vigent i sense control.

El procediment de baixa correcte per a Nimbus:

LLISTA DE COMPROVACIO DE BAIXA — s executa l ULTIM DIA, no despres
[ ] Desactivar la identitat central (SSO) — bloqueja tot el federat
[ ] Revocar sessions i tokens actius (no n hi ha prou de desactivar el compte:
    una sessio viva continua funcionant)
[ ] Revisar la llista de sistemes NO federats, un a un:
    [ ] Proveidor cloud   [ ] GitHub i tokens personals
    [ ] Base de dades     [ ] Claus SSH a authorized_keys
    [ ] SaaS de tiquets   [ ] Passarela de pagament  [ ] Correu transaccional
[ ] Retirar de grups i canals de missatgeria
[ ] Reassignar la propietat dels seus actius de l inventari (01-04)
[ ] Rotar els secrets compartits que coneixia
[ ] Recuperar el portatil i la clau de recuperacio de xifratge
[ ] Registrar la baixa completada, amb data i responsable

Els dos punts que més s'obliden són la revocació de sessions i tokens —desactivar un compte no invalida una sessió ja emesa, com vas veure al pass-the-cookie de 02-03— i la rotació de secrets compartits: si aquella persona coneixia la contrasenya del NAS o la clau de la passarel·la, aquestes credencials estan compromeses per definició.

La mesura estructural que redueix el problema a la meitat: com més sistemes estiguin federats amb un únic proveïdor d'identitat, menys caselles cal marcar. És la raó principal per la qual l'SSO és una mesura de seguretat i no només de comoditat.


  1. Factors d'autenticació i les seves debilitats

Un factor és una categoria de prova. L'autenticació multifactor exigeix factors de categories diferents; dues contrasenyes no són dos factors.

Factor Què és Exemples Debilitats
Alguna cosa que saps Coneixement Contrasenya, PIN, resposta a pregunta Es pot robar, endevinar, reutilitzar, fer-ne phishing i compartir sense deixar rastre
Alguna cosa que tens Possessió Clau FIDO2, app d'autenticació, targeta, telèfon Es perd, es roba; l'SMS és interceptable; els codis són phishejables
Alguna cosa que ets Biometria Empremta, rostre No es pot canviar si es compromet; falsos positius i negatius; a la pràctica desbloqueja un dispositiu, no autentica al servei
(Contextual) On i com IP, país, dispositiu conegut, hora No és un factor per si sol: és un senyal de risc que modula l'exigència

Tres precisions que corregeixen malentesos freqüents:

  1. La biometria gairebé mai no viatja al servidor. Quan l'Iván desbloqueja el mòbil amb l'empremta, la dada biomètrica no surt del dispositiu: desbloqueja una clau privada emmagatzemada al maquinari segur. El que el servei rep és una prova criptogràfica. Aquesta distinció és la que fa que les passkeys siguin sòlides i no un problema de privacitat.
  2. Les preguntes de seguretat no són un factor, són una contrasenya pitjor: la resposta sol ser pública o endevinable, i no es pot canviar el nom de l'escola de la teva infància.
  3. El context no substitueix un factor, però és enormement valuós combinat: exigir reautenticació quan canvia el país o el dispositiu és el que converteix un token robat en un token inútil.

  1. Contrasenyes: què diuen avui les guies

Les recomanacions van canviar substancialment en l'última dècada, i moltes organitzacions continuen aplicant les del 2005 —que empitjoren la seguretat—. La referència actual és la guia NIST SP 800-63B.

Pràctica tradicional Recomanació actual Per què va canviar
Mínim 8 caràcters amb majúscules, minúscules, números i símbols Mínim 12-15 caràcters, sense exigir composició La complexitat obligatòria produeix Empresa2026! de forma predictible; la longitud aporta molta més resistència
Caducitat cada 90 dies Sense caducitat forçada, tret d'indici de compromís La rotació obligatòria genera variacions trivials (…2026!…2027!) i augmenta l'ús de notes escrites
Prohibir enganxar al camp Permetre enganxar sempre Prohibir-ho impedeix l'ús de gestors de contrasenyes, que és el que de debò ajuda
Preguntes de seguretat com a suport Eliminar-les Respostes públiques o endevinables
Pistes de contrasenya Eliminar-les Són una filtració parcial de la contrasenya
Comprovar contra llistes de contrasenyes filtrades Impedeix d'arrel el credential stuffing i el diccionari
Permetre tots els caràcters, inclosos espais i unicode Facilita frases de pas llargues
Longitud màxima ≥ 64 caràcters Un màxim baix delata que la contrasenya podria no emmagatzemar-se correctament

La comprovació contra llistes de filtrades és la mesura individual més eficaç d'aquesta taula, i la menys implantada. Impedeix que algú triï una contrasenya que ja és als diccionaris que fan servir els atacants. S'implementa sense enviar la contrasenya enlloc: s'envien els primers caràcters del hash i es compara localment la resta (model de k-anonimat).

Gestors de contrasenyes. Per a Nimbus són obligatoris, i l'argument és simple: fan possible complir l'única cosa que de debò importa —una contrasenya llarga, aleatòria i diferent per servei— sense memoritzar res. L'objecció habitual («i si comprometen el gestor?») ignora l'alternativa real, que no és memoritzar 40 contrasenyes fortes: és reutilitzar-ne tres de febles. Un gestor amb contrasenya mestra forta i MFA és, amb una diferència enorme, l'opció més segura. Avantatge afegit i poc conegut: el gestor no omple les credencials en un domini que no coincideix, cosa que el converteix en un detector de phishing passiu.

L'emmagatzematge segur de contrasenyes al servidor —per què mai no es guarden en clar ni amb un hash simple, i què són bcrypt, scrypt i Argon2 amb el seu factor de cost i la seva sal— s'estudia en detall a 03-04. Aquí n'hi ha prou amb la regla: Nimbus mai no emmagatzema contrasenyes recuperables, i cap procés legítim no pot retornar a un client la seva contrasenya actual.


  1. MFA ordenat per resistència al phishing

L'MFA és la mesura més rendible de la lliçó, però no totes les formes d'MFA valen el mateix. La diferència decisiva és si el segon factor pot ser capturat i reutilitzat per un atacant interposat.

Mètode Com funciona Resistència al phishing Altres debilitats Ús a Nimbus
SMS Codi de 6 dígits per missatge Molt baixa: el codi es teclegea i es pot reenviar en temps real Intercanvi de SIM, interceptació, dependència de cobertura Només com a últim recurs; millor que res
Correu electrònic Codi o enllaç a la bústia Molt baixa Si el correu cau, cau tot Evitar
TOTP (app d'autenticació) Codi de 6 dígits derivat d'un secret compartit i del temps Baixa-mitjana: continua sent un codi que es pot demanar El secret es pot copiar en configurar-lo; finestra de 30 s Mínim acceptable per a comptes normals
Push simple Notificació amb «Aprovar / Denegar» Baixa: vulnerable a MFA fatigue (02-03) Aprovació sense context Insuficient per si sol
Push amb number matching Mostra un número a la pantalla que cal introduir a l'app Mitjana: exigeix mirar el dispositiu original Encara phishejable amb esforç Acceptable
FIDO2 / WebAuthn i passkeys Criptografia de clau pública vinculada al domini Alta: resistent al phishing per disseny Cost de la clau física; gestió de còpia de seguretat Objectiu per a tots els comptes administratius
Certificat de client Certificat al dispositiu Alta Gestió de PKI (03-06) Per a servei a servei

5.1 Per què FIDO2 és qualitativament diferent

Tots els mètodes anteriors comparteixen un defecte de fons: produeixen un secret que l'usuari transmet. Si l'usuari és en un web fals, transmet el secret a l'atacant, que el reenvia al lloc real en segons. Això és exactament el que fan els kits de phishing amb proxy invers, i per això «tenim MFA» va deixar de ser una resposta suficient.

FIDO2 trenca aquest esquema amb dues propietats:

  1. La clau privada no surt mai del dispositiu. No hi ha cap secret que l'usuari pugui lliurar, ni tan sols volent.
  2. La signatura està vinculada al domini (origin binding). El navegador inclou a l'operació l'origen real de la pàgina. Si l'usuari és a nimbusreservas.example.portal-facturas.example, la clau produeix una signatura per a aquest domini, que el servidor legítim rebutja. L'atac no falla perquè l'usuari se n'adoni: falla perquè és criptogràficament invàlid.

Formulat d'una altra manera: amb TOTP o SMS, la defensa depèn que l'usuari detecti el web fals. Amb FIDO2, la defensa funciona encara que l'usuari no detecti res. És la diferència entre una mesura que depèn del criteri humà i una que no. Recorda el principi de 02-03: si la teva defensa exigeix que 38 persones encertin sempre, no tens defensa.

Les passkeys són FIDO2 amb la clau sincronitzada entre els dispositius de l'usuari a través del seu ecosistema, cosa que elimina la fricció principal (perdre la clau) a canvi de confiar en el sincronitzador.

5.2 Verificació de TOTP, explicada

Encara que l'objectiu sigui FIDO2, TOTP continua sent el mínim realista per a molts comptes. Convé entendre com funciona per dins.

import hmac, hashlib, struct, time

def codi_totp(secret: bytes, moment: int | None = None,
              pas: int = 30, digits: int = 6) -> str:
    """Genera el codi TOTP de 6 digits (RFC 6238)."""
    # 1. El "comptador" es el nombre d intervals de 30 s des de 1970.
    #    Emissor i verificador el calculen per separat: no es transmet res.
    comptador = int((moment or time.time()) // pas)

    # 2. HMAC-SHA1 del comptador amb el secret compartit.
    #    HMAC garanteix que nomes qui coneix el secret genera el valor (03-04).
    resum = hmac.new(secret, struct.pack(">Q", comptador), hashlib.sha1).digest()

    # 3. "Truncament dinamic": els 4 ultims bits indiquen des d on llegir.
    desplacament = resum[-1] & 0x0F
    valor = struct.unpack(">I", resum[desplacament:desplacament + 4])[0]
    valor &= 0x7FFFFFFF                      # es descarta el bit de signe

    # 4. Es redueixen a 6 digits.
    return str(valor % (10 ** digits)).zfill(digits)


def verificar_totp(secret: bytes, codi: str, usuari_id: int,
                   finestra: int = 1) -> bool:
    """Verificacio correcta: tolerancia de rellotge, temps constant i
       proteccio contra reutilitzacio."""
    ara = time.time()

    # (a) S accepten l interval actual i un abans/despres: els rellotges
    #     no estan perfectament sincronitzats. Finestres majors debiliten.
    for delta in range(-finestra, finestra + 1):
        esperat = codi_totp(secret, ara + delta * 30)
        # (b) Comparacio en temps constant: evita deduir el codi
        #     mesurant quant triga la comparacio a fallar.
        if hmac.compare_digest(esperat, codi):
            # (c) Un codi nomes pot fer-se servir UNA vegada: sense aixo, un
            #     codi capturat serveix durant els 30-90 s seguents.
            if cache.get(f"totp:{usuari_id}:{codi}"):
                return False
            cache.set(f"totp:{usuari_id}:{codi}", "1", expira=120)
            return True
    return False

Les quatre decisions que separen aquesta implementació d'una d'ingènua:

  • (a) Finestra de tolerància acotada. Sense tolerància, els usuaris amb el rellotge lleugerament desviat no poden entrar. Amb una finestra gran (per exemple ±5), el codi val gairebé cinc minuts i l'atac en temps real es torna trivial. ±1 és l'equilibri habitual.
  • (b) hmac.compare_digest. Una comparació normal de cadenes acaba així que troba una diferència, i aquest temps és mesurable. En temps constant no es filtra res.
  • (c) Consum d'un sol ús. És el control que més s'oblida: sense ell, un codi capturat per phishing continua sent vàlid durant la finestra restant, que és justament el que necessita l'atacant interposat.
  • El secret s'emmagatzema xifrat a la base de dades, no en clar: qui el llegeixi pot generar codis indefinidament.

I la conclusió honesta: per ben implementat que estigui, TOTP continua sent phishejable, perquè l'usuari teclegea un codi en una pàgina. Per això la línia de treball per a Nimbus és TOTP com a mínim general i FIDO2 per a l'administratiu.

5.3 MFA fatigue i la seva correcció

Connectant amb 02-03: el bombardeig de notificacions explota que aprovar és un botó sense context. Les correccions, per ordre d'eficàcia:

  1. Number matching: obliga a mirar la pantalla de l'origen i a introduir un número, cosa que fa impossible aprovar per inèrcia.
  2. Context a la notificació: aplicació, ubicació aproximada, dispositiu. Una petició des d'un altre país és evident.
  3. Límit d'intents: després de 3 peticions denegades o ignorades, es bloqueja el compte i s'alerta.
  4. Alerta a seguretat davant de ràfegues: una ràfega de peticions MFA significa que algú ja té la contrasenya, amb independència que l'aprovi o no. És un senyal d'alt valor que gairebé ningú no vigila.

  1. SSO, federació i protocols d'identitat

6.1 Per què l'SSO és una mesura de seguretat

Amb inici de sessió únic, l'usuari s'autentica un cop davant d'un proveïdor d'identitat i accedeix a totes les aplicacions federades.

Avantatge Per què importa a Nimbus
Una baixa ho desactiva tot Redueix dràsticament la llista de caselles de l'apartat 2.3
MFA un cop, aplicat a tot arreu No cal configurar-lo servei per servei
Un únic punt de registre Tots els inicis de sessió en un registre, correlacionables
Polítiques centralitzades Exigir dispositiu conegut o reautenticació per risc, en un sol lloc
Menys contrasenyes Menys reutilització, menys phishing eficaç

El contrapès, que cal assumir conscientment: el proveïdor d'identitat es converteix en l'actiu més crític de tots. Si cau, ningú no entra enlloc; si el comprometen, es compromet tot. Per això: MFA FIDO2 obligatori per als seus administradors, registre exhaustiu, i un procediment d'accés d'emergència amb credencials guardades fora de línia per al cas d'indisponibilitat.

6.2 OAuth 2.0 davant d'OpenID Connect: la diferència exacta

És la confusió més estesa del domini, i produeix sistemes insegurs.

OAuth 2.0 OpenID Connect (OIDC)
Per a què serveix Autorització: donar a una aplicació accés limitat a recursos en nom de l'usuari Autenticació: acreditar qui és l'usuari
Pregunta que respon «Pot aquesta app llegir el teu calendari?» «Qui ets?»
Què lliura Access token: una clau amb permisos ID token (un JWT signat) amb la identitat
Qui el consumeix L'API que rep el token L'aplicació que fa el login
Relació És la base És una capa a sobre d'OAuth 2.0

La frase que cal fixar: OAuth 2.0 no és un protocol d'autenticació. Fer servir un token d'accés com a prova d'identitat és un error clàssic: aquest token diu què es pot fer, no qui ets, i podria haver estat emès per a una altra aplicació. Si el que necessites és «qui és aquest usuari», fes servir OIDC i valida l'ID token.

SAML és el veterà: mateix objectiu que OIDC (autenticació federada), basat en XML i asseveracions signades, molt implantat a l'entorn corporatiu. Per a integracions noves es prefereix OIDC per la seva simplicitat i el seu encaix natural amb aplicacions mòbils i SPA; SAML continua sent obligat quan un client corporatiu gran ho exigeix, i és una petició habitual en un SaaS B2B com Nimbus.

6.3 El flux correcte per a la SPA de Nimbus: Authorization Code + PKCE

sequenceDiagram
    participant U as Usuari
    participant SPA as SPA de Nimbus
    participant IDP as Proveidor d identitat
    participant API as API de Nimbus

    SPA->>SPA: Genera code_verifier aleatori<br/>i code_challenge = SHA256(verifier)
    SPA->>U: Redirigeix a l IdP amb code_challenge
    U->>IDP: S autentica (contrasenya + FIDO2)
    IDP->>IDP: Valida credencials i consentiment
    IDP->>SPA: Redirigeix amb un CODI d un sol us
    SPA->>IDP: Bescanvia codi + code_verifier
    IDP->>IDP: Comprova SHA256(verifier) == challenge
    IDP->>SPA: ID token (qui ets) + Access token (que pots)
    SPA->>API: GET /api/v1/reservas<br/>Authorization: Bearer <access token>
    API->>API: Valida signatura, iss, aud, exp i scope
    API->>SPA: 200 amb les dades del tenant de l usuari

Per què PKCE és imprescindible i no un extra. El codi d'autorització viatja pel navegador, i en una aplicació pública (SPA o app mòbil) no hi ha manera de guardar un secret de client: qualsevol pot descompilar l'app o llegir el JavaScript. Si un atacant intercepta el codi, podria bescanviar-lo per tokens.

PKCE ho impedeix amb una idea senzilla: l'aplicació genera un valor aleatori (code_verifier), envia només el seu hash en iniciar el flux i presenta el valor original en bescanviar. Qui no tingui el code_verifier no pot fer servir el codi robat, i aquest valor no surt mai del navegador que va iniciar el flux.

Els quatre errors freqüents en aquest flux:

  • Fer servir el flux implícit (tokens a l'URL): obsolet i desaconsellat.
  • Guardar els tokens a localStorage: accessible des de JavaScript, per tant robable amb un XSS. És preferible una galeta HttpOnly gestionada per un intermediari de servidor.
  • No validar aud a l'API: acceptar un token emès per a una altra aplicació.
  • No validar el paràmetre state: és la protecció contra CSRF del mateix flux d'autenticació.

  1. Sessions i tokens: galetes i JWT

7.1 Galetes segures

Set-Cookie: sessio=aB3xK9...; HttpOnly; Secure; SameSite=Lax;
            Path=/; Max-Age=3600; Domain=app.nimbusreservas.example
Atribut Què fa Quin atac atura
HttpOnly El JavaScript no la pot llegir Robatori de sessió mitjançant XSS
Secure Només s'envia per HTTPS Captura en trànsit
SameSite=Lax No s'envia en peticions originades per un altre lloc CSRF (02-02)
Max-Age=3600 Vida curta amb renovació Pass-the-cookie: redueix la finestra de la galeta robada
Path i Domain acotats Abast mínim Que la galeta viatgi a subdominis innecessaris
Prefix __Host- al nom Força Secure, Path=/ i absència de Domain Fixació de galeta des d'un subdomini compromès

SameSite=Strict davant de Lax: Strict és més segur però trenca la navegació des d'enllaços externs (l'usuari arriba al tauler i hi apareix com a no autenticat). Lax és l'equilibri habitual; per a operacions sensibles es combina amb token anti-CSRF.

7.2 JWT: estructura i què no ficar-hi dins

Un JWT té tres parts separades per punts: capçalera, càrrega útil i signatura, codificades en base64url.

eyJhbGciOiJSUzI1NiIsImtpZCI6IjIwMjYtMDMifQ.eyJpc3MiOiJodHRwczovL2lkc...
└──── capcalera ───┘ └──────── carrega util ───────┘ └─ signatura ─┘
{
  "iss": "https://identidad.nimbusreservas.example",
  "sub": "usr_8812",
  "aud": "https://api.nimbusreservas.example",
  "exp": 1774000000,
  "iat": 1773999100,
  "jti": "9f2a4c1e-77bb-4a10-9c31-7d2e5a6b1c04",
  "tenant_id": 42,
  "roles": ["recepcio"]
}

El primer que cal entendre, i el que més es malinterpreta: un JWT està signat, no xifrat. Qualsevol que el tingui pot llegir-ne el contingut sense cap clau. La signatura garanteix integritat i origen, no confidencialitat.

Què NO ha d'anar mai dins d'un JWT:

No incloure Per què
Contrasenyes o secrets És text llegible per qualsevol
Dades personals sensibles (nom del pacient, servei mèdic) Viatja a cada petició, es registra en registres i proxies, queda al navegador
Números de targeta o compte Mateix motiu, amb obligacions específiques
Dades que canvien sovint El token és una foto: si es revoca un rol, el token vell continua dient el contrari
Estructures grans El JWT viatja a cada petició: penalitza el rendiment

El que sí que ha de portar: un identificador de subjecte (sub), el tenant, els rols o àmbits mínims, i sempre iss, aud, exp, iat i jti.

7.3 Validació correcta davant de validació ingènua

# ============ VALIDACIO INGENUA I VULNERABLE ============
import jwt

def usuari_de_token_MALAMENT(token: str):
    # (1) Sense verificar la signatura: qualsevol pot fabricar el token que vulgui
    dades = jwt.decode(token, options={"verify_signature": False})
    # (2) Sense comprovar caducitat, emissor ni audiencia
    # (3) Es confia en els rols que venen a dins sense mes
    return Usuari(id=dades["sub"], tenant=dades["tenant_id"],
                  roles=dades["roles"])

Les tres fallades i per què cadascuna és explotable:

  1. Sense verificar la signatura, un atacant compon un JWT amb "roles": ["admin"] i "tenant_id": 7 i entra com a administrador de qualsevol clínica. La variant històrica d'aquesta fallada és acceptar l'algorisme none que el mateix token declara a la seva capçalera: mai no s'ha de confiar en l'alg del token per decidir com validar-lo.
  2. Sense comprovar exp, un token robat fa un any continua servint. Sense iss i aud, un token vàlid emès per un altre sistema o per a una altra aplicació s'accepta aquí.
  3. Confiar cegament en els rols del token significa que un canvi de permisos o una baixa no tenen efecte fins que el token caduqui.
# ============ VALIDACIO CORRECTA ============
import jwt
from jwt import PyJWKClient
from fastapi import HTTPException

JWKS = PyJWKClient("https://identidad.nimbusreservas.example/.well-known/jwks.json")
EMISSOR   = "https://identidad.nimbusreservas.example"
AUDIENCIA = "https://api.nimbusreservas.example"

def usuari_de_token(token: str):
    try:
        # (1) La clau publica s obte de l emissor segons el 'kid' de la
        #     capcalera. Permet ROTAR claus sense desplegar l API.
        clau = JWKS.get_signing_key_from_jwt(token).key

        dades = jwt.decode(
            token,
            clau,
            algorithms=["RS256"],        # (2) algorisme FIXAT per nosaltres,
                                         #     mai el que digui el token
            issuer=EMISSOR,              # (3) qui el va emetre
            audience=AUDIENCIA,          # (4) per a qui va ser emes
            options={"require": ["exp", "iat", "iss", "aud", "sub", "jti"],
                     "verify_exp": True},
            leeway=30,                   # (5) tolerancia de rellotge: 30 s
        )
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "Sessio caducada")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "Token no valid")

    # (6) REVOCACIO: la signatura valida no basta. Es comprova que el token
    #     no estigui a la llista de revocats (baixa, tancament de sessio, robatori).
    if cache.get(f"revocat:{dades['jti']}"):
        raise HTTPException(401, "Sessio revocada")

    # (7) Els permisos SENSIBLES es resolen contra la font de veritat,
    #     no contra el que digui el token emes fa 50 minuts.
    permisos = carregar_permisos_vigents(dades["sub"], dades["tenant_id"])

    return Usuari(id=dades["sub"], tenant=dades["tenant_id"],
                  roles=permisos, jti=dades["jti"])

Les set decisions, explicades:

  1. Claus des de JWKS per kid. Permet rotar la clau de signatura sense tocar l'API. Codificar la clau pública al codi converteix la rotació en un desplegament, i el que és difícil no es fa.
  2. algorithms=["RS256"] fixat. Tanca l'atac de confusió d'algorisme (per exemple, fer passar la clau pública RSA com a secret HMAC).
  3. i 4. issuer i audience. Sense aud, un token legítim per a un altre servei del mateix emissor seria acceptat per l'API de Nimbus.
  4. leeway. Evita fallades espúries per desfasament de rellotges; 30 s és raonable, minuts no.
  5. Revocació per jti. És el punt feble estructural del JWT: com que és autocontingut, és vàlid fins que caduca encara que l'usuari hagi estat donat de baixa. La llista de revocats amb vida igual a la del token resol el problema amb un cost mínim.
  6. Permisos vigents des de la font de veritat. Els rols del token serveixen com a pista ràpida; les decisions sensibles es prenen amb dades actuals.

7.4 Vida dels tokens i refresh

Token Vida recomanada On es guarda Notes
Access token 5-15 minuts Memòria de l'aplicació Curt perquè és difícil de revocar
Refresh token Hores o dies, amb rotació Galeta HttpOnly Secure SameSite Cada ús n'emet un de nou i invalida l'anterior
Token de servei (API) Màxim 90 dies, rotació automatitzada Gestor de secrets Mai sense caducitat

Rotació de refresh amb detecció de reutilització, que és la peça elegant: si un refresh token ja usat es presenta un altre cop, significa que algú en té una còpia robada. La resposta correcta és invalidar tota la família de tokens d'aquella sessió i forçar la reautenticació. És un mecanisme de detecció de robatori que funciona sense més infraestructura.

7.5 El token sense caducitat de 01-04, corregit

Recordem la troballa: el compte de servei nimbus-deploy-bot amb un token personal sense caducitat i permisos d'escriptura a tots els repositoris. Va ser a més la primera baula de l'atac encadenat de 02-02.

Tot el que hi ha de malament:

Problema Conseqüència
Sense caducitat Robat un cop, serveix per sempre
Àmbit total sobre tots els repositoris El compromís no està acotat
Lligat a un compte personal de tipus bot No hi ha propietari clar ni traçabilitat
Emmagatzemat a la configuració del CI i probablement en portàtils Múltiples còpies, superfície multiplicada
Sense registre d'ús revisat Ningú no notaria un ús anòmal
Sense rotació Mai no ha canviat des de la seva creació

La correcció, per ordre de preferència:

  1. Eliminar-lo i fer servir credencials efímeres del propi CI (identitat federada de curta durada emesa per a cada execució). El millor secret és el que no existeix.
  2. Si no és possible: token d'aplicació amb àmbit per repositori, caducitat de 90 dies, rotació automatitzada i emmagatzematge al gestor de secrets.
  3. En qualsevol cas: alerta davant de qualsevol ús des d'una IP no esperada i revisió trimestral de tokens vius.
-- Consulta d higiene: tokens sense caducitat o sense us recent.
-- Hauria d executar-se mensualment i retornar SEMPRE zero files.
SELECT id, nom, propietari, creat_el, ultim_us, ambit
  FROM tokens_api
 WHERE caduca_el IS NULL                            -- mai no caduquen
    OR caduca_el > now() + interval '90 days'       -- vida excessiva
    OR ultim_us < now() - interval '30 days'        -- creats i oblidats
 ORDER BY creat_el;

Els tokens sense ús en 30 dies són tan perillosos com els eterns, i per la mateixa raó que els comptes orfes: ningú no els trobaria a faltar si algú comencés a fer-los servir.


  1. Models d'autorització i l'RBAC de Nimbus

Autenticar respon «qui ets». Autoritzar respon «què pots fer», i és on passen la majoria dels incidents greus.

Model Com decideix Avantatge Inconvenient Exemple
DAC (discrecional) El propietari del recurs decideix qui hi accedeix Flexible, intuïtiu Ingovernable a escala; origen dels enllaços «qualsevol amb l'enllaç» El full de nòmines compartit per la Sara (01-04)
MAC (obligatori) Una política central i immutable, per etiquetes de classificació Molt estricte Rígid i car Entorns militars; SELinux
RBAC (per rols) Permisos associats a rols, rols assignats a persones Simple, auditable, escalable No expressa condicions (hora, ubicació, propietat) El model de Nimbus
ABAC (per atributs) Regles sobre atributs d'usuari, recurs i context Molt expressiu i granular Difícil de raonar i d'auditar «Només des d'Espanya, en horari laboral, si el recurs és del seu tenant»
ReBAC (per relacions) Segons la relació entre subjecte i objecte en un graf Natural per a compartició i jerarquies Requereix infraestructura específica «Ho pot veure perquè és el professional assignat a aquella cita»

Què li convé a Nimbus: RBAC com a base, amb atributs per a les condicions. És el model híbrid que fa servir gairebé tothom: els rols defineixen el gruix dels permisos i unes poques regles contextuals cobreixen el que els rols no expressen (el tenant, l'horari, el dispositiu).

8.1 El disseny RBAC de Nimbus

Rols de client (dins d'una clínica o gimnàs):

Rol Pot No pot
Administrador de centre Gestionar usuaris del seu centre, veure tota l'agenda, configurar serveis, veure facturació Sortir del seu tenant; accedir a dades d'un altre centre
Recepció Crear, modificar i cancel·lar cites; consultar dades de contacte Veure notes clíniques; exportar en massa; gestionar usuaris
Professional Veure la seva agenda i les notes dels seus pacients Veure l'agenda d'altres professionals; gestionar usuaris; facturació

Rols interns de Nimbus:

Rol Pot No pot
Suport (Rubén) Veure metadades d'incidències; suplantar un usuari només amb justificació i aprovació, amb registre Accedir a dades de clients sense tiquet; exportar; veure notes clíniques
Desenvolupament (Iván) Accés total a preproducció amb dades pseudonimitzades Accés a producció sense aprovació explícita i temporal
Sistemes (Lucía) Administrar infraestructura; accés a producció amb MFA i registre Llegir dades de negoci de forma rutinària; esborrar registres d'auditoria
Administració (Sara) Facturació i RH Accés a dades de clients finals
# roles.yaml — definicio declarativa de la matriu de permisos.
# Avantatge de tenir-lo al repositori: es revisa com a codi,
# es versiona i el seu canvi deixa rastre a l historial.
roles:
  centre_admin:
    hereta: [recepcio]
    permisos:
      - usuaris:llegir
      - usuaris:crear
      - usuaris:desactivar
      - facturacio:llegir
      - configuracio:escriure
    condicions:
      tenant: propi              # mai fora del seu tenant

  recepcio:
    permisos:
      - cites:llegir
      - cites:crear
      - cites:modificar
      - cites:cancellar
      - clients:llegir_contacte    # contacte SI, notes cliniques NO
    condicions:
      tenant: propi
      exportacio_massiva: denegada

  professional:
    permisos:
      - cites:llegir_propies
      - notes_cliniques:llegir_propies
      - notes_cliniques:escriure_propies
    condicions:
      tenant: propi
      abast: agenda_assignada    # atribut: nomes LES SEVES cites

  nimbus_suport:
    permisos:
      - incidencies:llegir
      - metadades_tenant:llegir
      - suplantar_usuari         # accio d alt risc
    condicions:
      suplantar_usuari:
        requereix_tiquet: true
        requereix_aprovacio: segon_operador
        durada_maxima: 30m
        registre: obligatori
        notificar_al_client: true

  nimbus_sistemes:
    permisos:
      - infraestructura:administrar
      - desplegament:executar
    condicions:
      mfa: fido2
      acces_produccio: just_in_time     # es sollicita, caduca sol
      registre_sessio: obligatori
    denegat:
      - auditoria:esborrar                # NINGU no pot esborrar auditoria

Quatre decisions d'aquest disseny que mereixen atenció:

  1. professional fa servir un atribut (agenda_assignada), no un rol. «Els seus» pacients no es pot expressar només amb rols: és una relació entre l'usuari i el recurs. Aquí és on l'RBAC pur es queda curt i s'hi afegeix ABAC/ReBAC.
  2. suplantar_usuari és un permís explícit amb condicions, no una capacitat implícita del rol de suport. És la correcció del «veure com a client» que va permetre accedir a 40 clíniques a l'exercici de 02-02. I es notifica al client: la transparència és en si mateixa un control.
  3. auditoria:esborrar apareix com a denegat explícit per a tothom, inclòs sistemes. És la separació de funcions de 01-03: qui administra no pot esborrar la prova del que va fer, coherent amb l'INSERT sense UPDATE de nimbus_api.
  4. El fitxer és al repositori. Un canvi de permisos passa per revisió i queda a l'historial. Els permisos que es canvien a mà en una consola no deixen rastre comprensible.

I a la base de dades, la contrapartida del mateix disseny, coherent amb els rols de 01-03:

-- Rol de nomes lectura per al tauler de suport: mai no veu notes cliniques
CREATE ROLE nimbus_suport_rol NOLOGIN;
GRANT USAGE ON SCHEMA public TO nimbus_suport_rol;

-- Vista que EXCLOU les columnes sensibles: el minim privilegi
-- s implementa a l esquema, no en la confianca en el codi
CREATE VIEW v_incidencies_suport AS
SELECT r.id, r.tenant_id, r.data_hora, r.estat, c.nom AS client
  FROM reserves r JOIN clients_finals c ON c.id = r.client_id;
-- deliberadament sense r.notes_cliniques ni c.dni

GRANT SELECT ON v_incidencies_suport TO nimbus_suport_rol;
REVOKE ALL ON reserves, clients_finals, nomines FROM nimbus_suport_rol;

  1. Multi-tenència i aïllament

Nimbus és multi-tenant: una única base de dades amb les dades de totes les clíniques. L'aïllament entre clients és el producte: si falla, no hi ha negoci.

Ja has treballat les dues peces —el filtre per tenant_id que va corregir l'IDOR a 01-01 i la seguretat a nivell de fila (RLS) que el fa obligatori des del motor a 01-03—. El que correspon aquí és com encaixa l'aïllament en el control d'accés:

Capa Què garanteix Què passa si falla sola
Token amb tenant_id L'API sap a quin tenant pertany la petició Si es manipula, les capes següents l'aturen
Filtre a la consulta Cada consulta es limita al tenant Un oblit exposa dades alienes
RLS a PostgreSQL El motor ho imposa encara que el codi ho oblidi És la xarxa de seguretat
Rol de BD amb mínim privilegi Acota el dany d'una injecció Limita l'abast
Auditoria amb tenant_id Permet detectar accessos creuats És la capa detectiva

La regla de disseny, en una frase: el tenant_id mai no procedeix d'un paràmetre que envia el client; procedeix sempre del token validat al servidor. Un endpoint que accepti ?tenant_id= del client és un IDOR amb un altre nom.

I la comprovació que convé automatitzar: una prova al CI que, amb el token d'un tenant, intenti accedir a un recurs d'un altre i exigeixi un 404. És una prova de cinc línies que hauria detectat l'IDOR original abans d'arribar a producció.


  1. Comptes privilegiats, de servei i de tercers

Els comptes amb permisos elevats són l'objectiu final de gairebé qualsevol atac. Mereixen un tractament diferent de la resta.

10.1 Separació del compte administratiu

El problema: si la Lucía navega, llegeix correu i administra la infraestructura amb el mateix compte, un phishing reeixit contra el seu correu lliura directament el control de tota la producció.

La solució: dues identitats per a la mateixa persona.

Compte Ús Restriccions
[email protected] Correu, documents, navegació, reunions Sense permisos administratius
[email protected] Només administració Sense correu ni navegació; MFA FIDO2 obligatori; només des d'equip gestionat; sessió registrada; accés just-in-time

El cost és una molèstia diària moderada; el benefici és que el vector més probable (el phishing al correu) deixa de conduir a l'actiu més crític.

10.2 PAM i accés just-in-time

PAM (gestió d'accessos privilegiats) agrupa les pràctiques de control dels comptes amb més poder:

Pràctica Què aporta Aplicació a Nimbus
Caixa forta de credencials Les contrasenyes administratives no les coneix ningú: se sol·liciten Contrasenya de superusuari de PostgreSQL, compte arrel del proveïdor cloud
Accés just-in-time El permís no existeix fins que se sol·licita, i caduca sol Accés a producció concedit per 2 hores amb justificació
Gravació de sessió Traçabilitat del que es va fer Sessions del bastió
Aprovació d'un segon Separació de funcions Operacions destructives o accés a dades de clients
Rotació automàtica Redueix la finestra d'una credencial filtrada Credencials de servei i de base de dades

El canvi mental que proposa el just-in-time: en lloc de preguntar «qui ha de tenir accés permanent?», es pregunta «per què algú tindria accés permanent?». En un sistema ben dissenyat, l'estat normal d'un compte administratiu és sense privilegis; els privilegis es concedeixen per minuts i desapareixen sols. És el principi de mínim privilegi de 01-03 portat a l'eix del temps: no només el mínim de permisos, també la mínima durada.

10.3 L'accés remot de la consultora (A-19)

És l'actiu amb criticitat crítica i propietària la Marta de l'inventari de 01-04, i el patró exacte de l'incident de Target que analitzarem a 02-06.

Situació actual:

Aspecte Estat Risc
Comptes Compartits per diversos tècnics Sense traçabilitat: no se sap qui va fer què
Permisos Administratius totals i permanents Sense límit d'abast ni de temps
MFA No Una contrasenya filtrada dona accés ple
Registre No revisat Un ús indegut passaria inadvertit
Caducitat Cap L'accés sobreviurà al contracte
Contracte Sense detall d'abast ni obligacions Sense base per exigir res

Configuració objectiu:

  1. Comptes nominals per tècnic de la consultora. Sense excepcions: sense nominació no hi ha no-repudi.
  2. MFA obligatori a tots els accessos.
  3. Accés just-in-time: sense privilegis de manera predeterminada; se sol·liciten amb motiu, els aprova la Lucía o la Marta i caduquen en hores.
  4. Abast acotat: només els sistemes que realment necessiten, mai «administrador de tot».
  5. Sessions registrades i revisades per mostreig.
  6. Caducitat automàtica de l'accés alineada amb el contracte, amb revisió trimestral.
  7. Alerta davant de qualsevol accés fora de la finestra pactada.

Nota: les obligacions contractuals amb el proveïdor —abast, confidencialitat, notificació d'incidents, subcontractació, auditoria— i la gestió del risc de tercers com a procés es tracten a 04-04, i les seves implicacions en matèria de protecció de dades a 06-03. S'han de validar amb assessoria jurídica.

10.4 Comptes de servei

Els comptes que fan servir els sistemes (no les persones) són el punt cec habitual: no tenen qui els reclami ni qui els revisi.

Regla Motiu
Un compte per servei, mai compartit entre diversos Permet acotar el dany i saber qui va fer què
Propietari humà assignat Algú ha de respondre'n a la recertificació
Sense accés interactiu (NOLOGIN, sense shell) No ha de poder fer-se servir com a porta d'entrada humana
Permisos mínims i explícits Com els rols nimbus_api i nimbus_informes de 01-03
Credencials efímeres o rotades Redueix la finestra d'una filtració
Ús registrat i amb línia base coneguda Un servei té un patró molt predictible: qualsevol desviació és sospitosa

  1. Recertificació: la revisió periòdica d'accessos

Tot l'anterior es degrada amb el temps. La recertificació és la revisió periòdica en què el propietari de cada actiu confirma —o retira— els accessos concedits.

Com es fa a Nimbus, de forma sostenible:

Element Decisió
Freqüència Semestral per a accessos normals; trimestral per a privilegiats i tercers
Qui revisa El propietari de l'actiu de l'inventari de 01-04, no l'equip tècnic
Què es revisa Llista de persones amb accés, nivell, data de concessió i data d'últim ús
Regla per defecte El que no es confirma explícitament, es retira. Si cal actuar per retirar, no es retira mai
Sortida Llista signada i datada: és l'evidència que demanarà una auditoria (06-04)

La dada que fa eficient la revisió és la data d'últim ús. Un permís sense ús en sis mesos gairebé mai no és necessari, i presentar la llista ordenada per aquest camp converteix una revisió tediosa en una decisió ràpida.

-- Insum per a la recertificacio: accessos sense us recent.
-- S envia al propietari de cada actiu perque confirmi o retiri.
SELECT u.email,
       u.rol,
       a.nom                   AS actiu,
       p.concedit_el,
       p.concedit_per,
       MAX(l.ts)               AS ultim_us,
       CASE WHEN MAX(l.ts) IS NULL THEN 'MAI USAT'
            WHEN MAX(l.ts) < now() - interval '180 days' THEN 'SENSE US 6 MESOS'
            ELSE 'ACTIU' END   AS estat
  FROM permisos p
  JOIN usuaris u ON u.id = p.usuari_id
  JOIN actius  a ON a.id = p.actiu_id
  LEFT JOIN accessos_log l ON l.usuari_id = u.id AND l.actiu_id = a.id
 GROUP BY u.email, u.rol, a.nom, p.concedit_el, p.concedit_per
HAVING MAX(l.ts) IS NULL OR MAX(l.ts) < now() - interval '180 days'
 ORDER BY a.nom, ultim_us NULLS FIRST;

Un permís «MAI USAT» és la millor notícia possible en una recertificació: es retira sense discussió, sense risc de trencar res i amb benefici immediat de reducció de superfície.


Errors Comuns i Consells

Errors comuns:

  • Donar de baixa només el correu. És l'error de l'apartat 2.3: queden vius vuit accessos en sistemes no federats.
  • Desactivar el compte sense revocar sessions ni tokens. Una sessió emesa continua funcionant; un token sense caducitat, per sempre.
  • Copiar els permisos d'un altre empleat en donar d'alta. Propaga privilegis acumulats que ningú no va decidir concedir.
  • Tractar tot l'MFA com a equivalent. SMS i FIDO2 es diferencien en si l'atac de phishing funciona o no.
  • Forçar caducitat de contrasenyes cada 90 dies. Produeix variacions trivials i contrasenyes escrites en paper; les guies actuals ho desaconsellen tret d'indici de compromís.
  • Fer servir OAuth 2.0 com si fos autenticació. Un token d'accés diu què es pot fer, no qui ets. Per a identitat, OIDC i validació de l'ID token.
  • Guardar tokens a localStorage. Un XSS els roba. Galeta HttpOnly o intermediari de servidor.
  • Confiar en un JWT sense verificar signatura, exp, iss i aud, o deixar que el token triï l'algorisme.
  • Ficar dades personals dins del JWT. Està signat, no xifrat: qualsevol el llegeix.
  • Acceptar tenant_id com a paràmetre del client. És un IDOR amb un altre nom.
  • Concedir accés privilegiat permanent perquè «és més còmode». L'estat normal d'un compte administratiu hauria de ser sense privilegis.
  • Recertificar amb la regla «es retira el que algú demani retirar». Per defecte s'ha de retirar el que no es confirma.

Consells:

  • Comença pel que més rendeix: FIDO2 als comptes administratius i caducitat a tots els tokens. Són dues tardes de feina i tanquen els dos vectors més explotats.
  • Escriu la llista de comprovació de baixa de l'apartat 2.3 i executa-la l'últim dia, no després. Guarda'n constància signada.
  • Separa el compte administratiu del personal per a la Lucía i la Marta. És incòmode una setmana i protegeix durant anys.
  • Posa el fitxer roles.yaml al repositori i tracta'l com a codi: revisió, historial i desplegament controlat.
  • Afegeix al CI la prova d'aïllament entre tenants. Cinc línies que vigilen permanentment la fallada més cara d'un SaaS.
  • Executa avui la consulta de tokens sense caducitat. Si retorna files, ja tens la teva tasca de la setmana.
  • A la recertificació, ordena sempre per data d'últim ús: converteix una revisió tediosa en decisions d'un segon.

Exercicis

Exercici 1 — Dissenyar la baixa d'un empleat i detectar el que falta

L'Iván deixa Nimbus. La Lucía executa el següent:

  1. Desactiva el seu compte de correu.
  2. El treu del canal intern de missatgeria.
  3. Recupera el portàtil.
  4. El retira del repositori de GitHub com a col·laborador.

Dos mesos després, en la revisió trimestral, es detecta activitat: algú ha executat un desplegament a les 4:10 de la matinada.

Es demana: (a) enumera almenys sis accessos que probablement continuïn vius i explica per què cadascun és perillós; (b) escriu la llista de comprovació de baixa completa que s'hauria d'haver aplicat; (c) explica com va poder executar-se un desplegament si el compte de GitHub estava retirat, amb dues hipòtesis tècniques diferents; (d) proposa tres mesures estructurals que redueixin aquest risc amb independència de la disciplina de qui executi la baixa.

Exercici 2 — Auditar una implementació de tokens

Aquest és el codi d'autenticació d'una nova versió de l'API de Nimbus:

import jwt, datetime
from fastapi import HTTPException

SECRET = "nimbus-2026"

def emetre(usuari):
    carrega = {
        "sub": usuari.email,
        "nom": usuari.nom_complet,
        "dni": usuari.dni,
        "tenant_id": usuari.tenant_id,
        "roles": usuari.roles,
        "notes_internes": usuari.notes,
    }
    return jwt.encode(carrega, SECRET, algorithm="HS256")

def validar(token: str):
    dades = jwt.decode(token, SECRET,
                       algorithms=["HS256", "none"],
                       options={"verify_exp": False})
    return Usuari(id=dades["sub"], tenant=dades["tenant_id"],
                  roles=dades["roles"])

@app.post("/api/v1/sesion")
def login(email: str, password: str):
    u = autenticar(email, password)
    if not u:
        raise HTTPException(401, "Usuari no trobat o clau incorrecta")
    return {"token": emetre(u), "expira": "mai"}

Es demana: (a) identifica tots els problemes de seguretat, classificant-los en crítics, alts i mitjans; (b) explica l'impacte concret dels dos més greus sobre les dades de les clíniques; (c) reescriu emissió i validació correctament; (d) explica què canvia si l'API es desplega en tres instàncies i cal revocar una sessió immediatament.

Exercici 3 — Autorització d'una funcionalitat nova

Nimbus llança els informes d'ocupació per professional. Requisits de negoci:

  • L'administrador de centre veu l'informe de tots els professionals del seu centre.
  • El professional veu només el seu propi informe.
  • Recepció no accedeix a aquesta funcionalitat.
  • Suport de Nimbus (Rubén) pot veure l'informe d'un centre només amb un tiquet obert i aprovació d'un segon operador, durant 30 minuts com a màxim, i el centre ha de ser notificat.
  • Els informes es poden exportar a CSV, amb un màxim de 5.000 files i 6 peticions per hora i usuari.
  • Un professional donat de baixa fa més de 90 dies no ha d'aparèixer.

Es demana: (a) decideix quin model o combinació de models d'autorització faries servir i justifica-ho; (b) escriu el fragment de roles.yaml corresponent; (c) escriu el codi Python de l'endpoint amb totes les comprovacions d'autorització, indicant en comentaris quin atac prevé cadascuna; (d) indica tres esdeveniments que registraries i una alerta que construiries sobre ells.


Solucions

Solució 1

(a) Accessos que probablement continuïn vius:

Accés Per què és perillós
Compte del proveïdor cloud (A-05) Accés a buckets, base de dades i a la mateixa infraestructura; és l'actiu del qual depenen gairebé tots
Token personal de GitHub creat per l'Iván Sobreviu a la retirada del compte: un token és una credencial independent
Usuari personal a PostgreSQL (dev_ivan) Accés directe a dades de producció sense passar per l'API ni pels seus controls
Clau SSH a authorized_keys dels servidors Accés als servidors sense autenticació centralitzada; invisible per a l'SSO
Secrets que coneixia (contrasenya de BD, claus de la passarel·la, credencial del correu transaccional) Continuen sent vàlids: els coneix algú sense relació amb l'empresa
Eines SaaS no federades (tiquets, monitoratge, gestor d'errors) Contenen dades de clients i traces de producció
Sessions actives en navegadors i app mòbil Desactivar el compte no invalida una sessió emesa (pass-the-cookie, 02-03)
Còpies locals de dades al seu portàtil o en emmagatzematge personal Ni tan sols requereixen accés: les dades ja van sortir

(b) La llista de comprovació de l'apartat 2.3, executada l'últim dia, amb constància signada i amb reassignació de la propietat dels seus actius de l'inventari.

(c) Dues hipòtesis del desplegament nocturn:

  1. Token personal viu. L'Iván va crear un token de GitHub amb permisos d'escriptura i sense caducitat —el patró de la troballa de 01-04—. Retirar el compte com a col·laborador no invalida el token, que continua autenticant davant de l'API del servei. És la hipòtesi més probable.
  2. Clau SSH o credencial d'infraestructura. El desplegament no va passar per GitHub: es va executar directament contra el servidor amb una clau SSH que va quedar a authorized_keys, o amb credencials del proveïdor cloud que coneixia.

En tots dos casos, la lliçó és la mateixa: la identitat no és només el compte. És el compte més tots els artefactes que ell va crear i que viuen de forma independent.

(d) Tres mesures estructurals:

  1. Federar tot el que sigui possible amb el proveïdor d'identitat, de manera que una única baixa desactivi el màxim nombre d'accessos. Redueix la dependència de la disciplina humana.
  2. Prohibir per política els tokens sense caducitat i les claus SSH personals als servidors, substituint-los per credencials efímeres emeses pel CI i accés per bastió amb identitat central. Un artefacte que no existeix no s'oblida.
  3. Recertificació trimestral amb la consulta de l'apartat 11, que detecta el que es va escapar del procés. És la xarxa de seguretat: assumeix que la baixa fallarà alguna vegada i limita quant de temps passa fins que es detecta.

Solució 2

(a) Problemes trobats:

Gravetat Problema Detall
Crític algorithms=["HS256", "none"] Acceptar none permet fabricar tokens sense signatura: qualsevol es converteix en administrador de qualsevol tenant
Crític SECRET = "nimbus-2026" al codi Secret feble, endevinable i present al repositori; permet signar tokens arbitraris
Crític verify_exp: False i "expira": "mai" Els tokens no caduquen mai: robar-ne un equival a accés permanent
Alt Sense exp, iss, aud, iat ni jti a la càrrega Impossible acotar validesa, origen, destinatari ni revocar
Alt dni, nom i notes_internes dins del JWT El JWT està signat però no xifrat: dades personals llegibles per qualsevol i presents en registres i navegador
Alt Sense mecanisme de revocació Una baixa o un robatori no tenen efecte
Mitjà HS256 amb secret compartit Qualsevol servei que validi pot també emetre; amb RS256 només l'emissor signa
Mitjà sub és el correu Un canvi de correu trenca la identitat; millor un identificador estable
Mitjà Missatge d'error que distingeix casos «Usuari no trobat o clau incorrecta» és acceptable, però convé un missatge totalment uniforme i un temps de resposta constant per evitar l'enumeració d'usuaris (02-02)

(b) Impacte dels dos més greus. Amb none acceptat, un atacant construeix un token amb "tenant_id": 7 i "roles": ["centre_admin"], sense signatura, i l'API l'accepta. Resultat: accés complet a l'agenda i les dades de qualsevol clínica, sense necessitat de credencials, sense phishing i sense explotar res més. Combinat amb l'absència de caducitat, un token obtingut una sola vegada —d'un registre, d'una captura, d'un portàtil— dona accés indefinit. En termes de la tríada CIA, això és una pèrdua total de confidencialitat i integritat sobre dades que revelen informació de salut, i desencadena obligacions de notificació (06-03).

(c) Codi correcte:

import jwt, uuid, datetime
from jwt import PyJWKClient
from fastapi import HTTPException

EMISSOR   = "https://identidad.nimbusreservas.example"
AUDIENCIA = "https://api.nimbusreservas.example"
JWKS      = PyJWKClient(f"{EMISSOR}/.well-known/jwks.json")
CLAU_PRIVADA = gestor_secrets.llegir("jwt/clau-privada")   # mai al codi

def emetre(usuari):
    ara = datetime.datetime.now(datetime.timezone.utc)
    carrega = {
        "iss": EMISSOR,
        "aud": AUDIENCIA,
        "sub": str(usuari.id),                  # identificador estable, no el correu
        "tenant_id": usuari.tenant_id,
        "roles": usuari.roles,                  # pista rapida; el sensible es recarrega
        "iat": ara,
        "exp": ara + datetime.timedelta(minutes=15),   # vida curta
        "jti": str(uuid.uuid4()),               # identificador per revocar
        # sense nom, sense dni, sense notes internes
    }
    return jwt.encode(carrega, CLAU_PRIVADA, algorithm="RS256",
                      headers={"kid": "2026-03"})

def validar(token: str):
    try:
        clau = JWKS.get_signing_key_from_jwt(token).key
        dades = jwt.decode(token, clau,
                           algorithms=["RS256"],       # fixat, mai del token
                           issuer=EMISSOR, audience=AUDIENCIA,
                           options={"require": ["exp", "iat", "iss",
                                                "aud", "sub", "jti"]},
                           leeway=30)
    except jwt.ExpiredSignatureError:
        raise HTTPException(401, "Sessio caducada")
    except jwt.InvalidTokenError:
        raise HTTPException(401, "Token no valid")

    if cache.get(f"revocat:{dades['jti']}"):
        raise HTTPException(401, "Sessio revocada")

    permisos = carregar_permisos_vigents(dades["sub"], dades["tenant_id"])
    return Usuari(id=dades["sub"], tenant=dades["tenant_id"],
                  roles=permisos, jti=dades["jti"])

(d) Revocació immediata amb tres instàncies. Un JWT és autocontingut: les tres instàncies el validen sense consultar ningú, així que una baixa no té efecte fins que caduca. Solucions, combinables:

  1. Llista de revocació compartida (Redis) consultada per jti, amb vida igual a la del token: màxim 15 minuts d'entrades. Cost: una consulta ràpida per petició; és el que fa el codi anterior.
  2. Vida curta de l'access token (5-15 min) amb refresh rotatori. La revocació real s'aplica al refresh, que sí que es comprova contra la base de dades: la finestra màxima d'exposició és la vida de l'access token.
  3. Marca d'invalidació per usuari (tokens_valids_des_de): es rebutja tot token amb iat anterior a aquesta marca. Revoca totes les sessions d'una persona amb un sol camp, ideal per a una baixa o un compromís.

El compromís de fons: el JWT és ràpid i sense estat, però la revocació exigeix estat. La solució pràctica no és triar un extrem, sinó escurçar la vida del token perquè la finestra sigui acceptable i mantenir un mecanisme de revocació per a l'urgent.

Solució 3

(a) Model triat: RBAC com a base + ABAC/ReBAC per a l'abast i les condicions. Justificació:

  • Els tres perfils (administrador de centre, professional, recepció) són rols: l'RBAC els expressa de forma directa i auditable.
  • «Només el seu propi informe» no és un rol: és una relació entre l'usuari i el recurs (el professional assignat). Requereix ReBAC o un atribut.
  • «Amb tiquet, aprovació, 30 minuts i notificació» són condicions contextuals: ABAC.
  • «Professional donat de baixa fa més de 90 dies» és un atribut temporal del recurs.

L'RBAC pur obligaria a crear un rol per professional, cosa ingovernable. L'ABAC pur seria expressiu però molt difícil d'auditar. L'híbrid és el que fan servir els sistemes reals.

(b) Fragment de roles.yaml:

permisos_nous:
  informes_ocupacio:llegir_centre:
    roles: [centre_admin]
    condicions:
      tenant: propi
      professionals_visibles: baixa_fa_menys_de_90_dies

  informes_ocupacio:llegir_propi:
    roles: [professional]
    condicions:
      tenant: propi
      abast: professional_id == usuari.professional_id

  informes_ocupacio:exportar:
    roles: [centre_admin, professional]
    limits:
      files_maximes: 5000
      peticions_per_hora: 6
    registre: obligatori

  informes_ocupacio:llegir_suport:
    roles: [nimbus_suport]
    condicions:
      requereix_tiquet: true
      requereix_aprovacio: segon_operador
      durada_maxima: 30m
      notificar_al_client: true
      registre: obligatori
    denegat_per: [recepcio]

(c) Endpoint amb les comprovacions:

@app.get("/api/v1/informes/ocupacion")
@limitador.limit("6/hour")                     # abus d API i DDoS aplicatiu (02-02)
def informe_ocupacio(professional_id: int | None = None,
                     format: str = "json",
                     usuari = Depends(usuari_actual)):

    # 1. AUTORITZACIO PER ROL. Recepcio no hi arriba.
    #    Preve: escalada vertical de privilegis.
    if not usuari.te_algun(["centre_admin", "professional",
                            "nimbus_suport"]):
        raise HTTPException(403)

    # 2. TENANT DES DEL TOKEN, MAI DEL CLIENT.
    #    Preve: IDOR entre tenants (01-01) i acces creuat.
    tenant = usuari.tenant_id

    # 3. ABAST SEGONS EL ROL (la part ABAC/ReBAC).
    #    Preve: escalada horitzontal entre professionals del mateix centre.
    if usuari.te("professional"):
        if professional_id not in (None, usuari.professional_id):
            raise HTTPException(404)          # 404, no 403: no revela existencia
        professional_id = usuari.professional_id

    # 4. SUPORT: condicions estrictes i tracabilitat.
    #    Preve: l abus de "veure com a client" de l exercici de 02-02.
    if usuari.te("nimbus_suport"):
        sessio = sessions_suport.vigent(usuari.id, tenant)
        if not sessio or not sessio.aprovada_per_segon or sessio.caducada():
            raise HTTPException(403, "Requereix tiquet aprovat i vigent")
        tenant = sessio.tenant_id             # el tenant el fixa l aprovacio
        notificar_al_client(tenant, usuari.id, "acces_suport_informe")

    # 5. CONSULTA ACOTADA: filtre de tenant, exclusio de baixes i LIMIT.
    #    Preve: exposicio de dades de mes i DDoS aplicatiu.
    files = db.execute("""
        SELECT p.id, p.nom, COUNT(r.id) AS cites,
               SUM(CASE WHEN r.estat='completada' THEN 1 ELSE 0 END) AS completades
          FROM professionals p
          LEFT JOIN reserves r ON r.professional_id = p.id
                              AND r.tenant_id = :t
         WHERE p.tenant_id = :t
           AND (p.baixa_el IS NULL OR p.baixa_el > now() - interval '90 days')
           AND (:pid::int IS NULL OR p.id = :pid)
         GROUP BY p.id, p.nom
         LIMIT 5000""",
        {"t": tenant, "pid": professional_id}).fetchall()

    # 6. AUDITORIA: qui, que, quant, per a qui.
    registrar_auditoria(usuari_id=usuari.id, tenant_id=tenant,
                        accio="informe_ocupacio",
                        format=format, num_registres=len(files),
                        professional_id=professional_id)

    if format == "csv":
        return exportar_csv(files, maxim=5000)
    return {"dades": [dict(f) for f in files]}

(d) Tres esdeveniments a registrar i una alerta:

Esdeveniment Camps
informe_ocupacio_consultat usuari_id, tenant_id, rol, professional_id, num_registres, format
acces_suport_iniciat usuari_id, tenant_id, tiquet, aprovat_per, caduca_el
exportacio_csv usuari_id, tenant_id, num_registres, ip

Alerta: accés de suport a més de 3 tenants diferents en 24 hores, o qualsevol accés de suport sense tiquet associat. És exactament el senyal que hauria detectat en hores —en lloc de en tres dies— l'incident del tauler de suport de l'exercici de 02-02, i és barat de construir perquè els camps ja són al registre. Una segona alerta útil: qualsevol exportació CSV fora de l'horari laboral del tenant, que aprofita que Nimbus coneix l'horari de cada centre.


Conclusió

Has treballat el domini que més incidents decideix avui. Saps per què la identitat és el nou perímetre: cap dels actius rellevants de Nimbus no és darrere d'una frontera de xarxa, i l'única cosa que travessa tots els contextos és qui ets. Has separat amb precisió identificació, autenticació i autorització, i has fixat el parany que es repeteix a tot el curs: la majoria dels incidents greus no són fallades d'autenticació, sinó d'autorització. Has recorregut el cicle de vida de la identitat i el seu punt feble, la baixa, amb el cas concret dels vuit accessos que sobreviuen a una desactivació de correu, la llista de comprovació que ho evita i les dues coses que sempre s'obliden: revocar sessions i tokens, i rotar els secrets compartits.

En contrasenyes t'endús l'actualització de les guies —longitud sobre complexitat, sense caducitat forçada, permetre enganxar, i la mesura més eficaç i menys implantada: comprovar contra llistes de filtrades— i l'argument definitiu a favor dels gestors. En MFA has après la jerarquia que de debò importa, la de resistència al phishing, i la raó per la qual FIDO2 és qualitativament diferent: la clau no surt mai del dispositiu i la signatura està vinculada al domini, de manera que la defensa funciona encara que l'usuari no detecti res. Has vist per dins la verificació TOTP amb les seves quatre decisions —finestra acotada, comparació en temps constant, consum d'un sol ús i secret xifrat— i la correcció de l'MFA fatigue amb number matching.

En federació has fixat la diferència exacta entre OAuth 2.0, que autoritza, i OpenID Connect, que autentica, juntament amb el flux Authorization Code + PKCE i la raó per la qual PKCE és imprescindible en aplicacions públiques. En sessions i tokens has recorregut els atributs de galeta que neutralitzen XSS, CSRF i captura en trànsit, i has comparat una validació de JWT ingènua amb una de correcta: signatura verificada amb clau de l'emissor, algorisme fixat per nosaltres, iss, aud i exp comprovats, revocació per jti i permisos sensibles recarregats de la font de veritat. I has corregit el token sense caducitat de 01-04, la primera baula de l'atac encadenat de 02-02.

En autorització has comparat DAC, MAC, RBAC, ABAC i ReBAC i has construït el disseny real de Nimbus: rols de client i rols interns en un roles.yaml versionat, amb suplantar_usuari com a permís condicionat i notificat, i auditoria:esborrar denegat a tothom. Has tancat l'aïllament multi-tenant amb la seva regla d'or —el tenant_id ve sempre del token, mai del client— i la seva prova automatitzada al CI. I has acabat amb els comptes privilegiats: la separació del compte administratiu, PAM i l'accés just-in-time que converteix el mínim privilegi en mínima durada, la configuració objectiu per a l'accés de la consultora (A-19) i la recertificació periòdica la regla decisiva de la qual és que el que no es confirma, es retira.

Ja tens l'arsenal complet del mòdul: saps què s'ataca, com, amb què es defensa i com es controla l'accés. Falta la prova de realitat. A l'última lliçó del mòdul, Casos d'Estudi d'Incidents de Ciberseguretat (02-06), aplicarem un mètode d'anàlisi a incidents reals i públics —Target, WannaCry, Equifax, SolarWinds, Colonial Pipeline i una fuita per emmagatzematge al núvol mal configurat—, mapejarem cadascun als conceptes del curs, n'extraurem els patrons que es repeteixen sense excepció i reconstruirem hora a hora un incident fictici de ransomware a Nimbus que entra per l'accés remot de la consultora, per respondre a l'única pregunta que importa: què hauria canviat el desenllaç.

Curs de Fonaments de Seguretat Informàtica

Mòdul 1: Introducció a la Seguretat Informàtica

Mòdul 2: Ciberseguretat

Mòdul 3: Criptografia

Mòdul 4: Gestió de Riscos i Mesures de Protecció

Mòdul 5: Eines i Tècniques de Seguretat

Mòdul 6: Bones Pràctiques i Normatives

Mòdul 7: Projecte Final

© Copyright 2026. Tots els drets reservats