Amb la identitat verificada (07-01) i el canal xifrat (07-02), un atacant ja no pot llegir el token de l'Ana ni fer-se passar per Comandes. Però pot continuar sent un usuari legítim que envia una petició malintencionada: un id amb caràcters estranys, un JSON de 50 MB, un filtre de MongoDB disfressat de cadena, o simplement GET /v1/comandes/com-88214 per veure si cola. Aquesta lliçó tracta del que cada servei fa amb el que rep i amb el que desa: els principis que ordenen tota la resta, l'OWASP API Security Top 10 recorregut amb exemples de TechCorp, validació i sanejament d'entrada amb zod, les injeccions (SQL, NoSQL, ordres, logs), capçaleres amb helmet, dependències, secrets al codi, protecció de dades personals (RGPD a nivell pràctic), auditoria, proves de seguretat i resposta davant de vulnerabilitats. Acaba amb la llista de comprovació que s'incorpora a la plantilla techcorp/plantilla-servei-node. Autenticació (07-01), TLS (07-02) i contenidors/Kubernetes (07-04) només s'enllacen.
Avís. Les mesures d'aquesta lliçó són les habituals en un equip de desenvolupament, no un programa de seguretat complet. L'aplicació real del RGPD, de PCI DSS i de qualsevol política de retenció o auditoria s'ha de revisar amb un professional de seguretat i amb el responsable de compliment normatiu/protecció de dades de l'organització.
Contingut
- Principis que ordenen la resta
- OWASP API Security Top 10 (2023) amb exemples de TechCorp
- Validació i sanejament d'entrada amb
zod - Injecció: SQL, NoSQL, ordres i logs
- Capçaleres de seguretat amb
helmet - Gestió de dependències
- Secrets al codi: mai, i com assegurar-ho
- Protecció de dades personals: RGPD a la pràctica
- Auditoria d'accions sensibles
- Proves de seguretat: SAST, DAST, revisió i pentest
- Resposta davant de vulnerabilitats
- Llista de comprovació de seguretat per servei
- Principis que ordenen la resta
Sis principis que es repeteixen a cada decisió d'aquest mòdul:
| Principi | Significat | Ja aplicat a TechCorp |
|---|---|---|
| Mínim privilegi | Cada component només pot fer el que necessita | Usuaris svc_* per esquema (02-04), permisos de RabbitMQ per cua (07-02), scopes per client (07-01) |
| Defensa en profunditat | Diverses capes independents; cap no és l'única | JWT verificat al gateway i al servei; TLS i signatura HMAC; validació i consultes parametritzades |
| Fallar segur | Davant del dubte, denegar; davant de l'error, no revelar | AuthorizationPolicy amb ALLOW explícit; 500 ERROR_INTERN sense traça (04-02); config invàlida → no arrenca (04-03) |
| Superfície mínima | Menys portes, menys risc | Inventari/Pagaments/Notificacions no exposats (03-04); x-powered-by desactivat; imatge sense npm de desenvolupament (05-01) |
| Seguretat per disseny | Es decideix en dissenyar, no s'apedaça al final | Propietari del recurs al cas d'ús; esdeveniments sense dades innecessàries (02-05) |
| Shift left | Comprovar tan aviat com es pugui en el cicle | Lint, proves, npm audit, Trivy i gitleaks a CI (05-03) abans de desplegar |
- OWASP API Security Top 10 (2023) amb exemples de TechCorp
L'OWASP API Security Top 10 és la llista de referència de riscos en APIs. Recórrer-la amb casos propis és la millor manera d'interioritzar-la:
| # | Risc | Com es veuria a TechCorp | Remei i on |
|---|---|---|---|
| API1 | BOLA (Broken Object Level Authorization) | L'Ana demana GET /v1/comandes/com-88214 (d'un altre client) i l'obté |
Comprovació de propietari a cada ruta amb {id} (07-01): comanda aliena → 404 |
| API2 | Autenticació trencada | Acceptar alg: none, tokens d'hores, password grant, JWKS sense verificar aud |
jwtVerify amb algorithms, issuer, audience; tokens de 5 min (07-01) |
| API3 | Exposició excessiva de dades / propietat a nivell d'objecte | GET /v1/comandes/{id} retorna l'email i el keycloakSub de la rèplica clients_ref; PATCH que accepta estat o total des del client |
DTOs explícits (aDto/aRepresentacio de 04-02/04-04): es trien els camps que surten; esquemes zod amb .strict() que rebutgen camps no previstos |
| API4 | Consum de recursos sense límit | Un client demana ?limit=100000, envia cossos de 50 MB o fa 10.000 POST /v1/comandes per minut |
limit màx. 100 (03-01/04-02), express.json({ limit: '100kb' }), rate limit 300/min al gateway (03-04), timeout i bulkhead (06-03) |
| API5 | Autorització trencada a nivell de funció | Un client crida GET /v1/comandes?estat=PENDENT (llistat d'operador) o POST /v1/productes |
requereixRol('operador') a rutes d'operació (07-01); rutes d'administració no exposades pel gateway públic |
| API6 | Accés sense restricció a fluxos de negoci sensibles | Un bot crea milers de comandes d'un producte en oferta i esgota l'estoc | Rate limit per usuari (no només per IP), CAPTCHA al web per a pics, límits de negoci (màx. N unitats per comanda/client) |
| API7 | SSRF | Un webhook configurable per l'usuari o una "URL d'imatge" que Catàleg descarrega apunta a http://10.0.0.5:15672 (RabbitMQ) o a http://169.254.169.254 (metadades del núvol) |
No acceptar URLs arbitràries de l'usuari; si és imprescindible, llista blanca de dominis, resoldre DNS i rebutjar IPs privades, sense seguir redireccions; egress restringit (07-04) |
| API8 | Mala configuració | CORS amb *, x-powered-by, traces d'error al client, NODE_ENV diferent de production |
CORS amb orígens concrets (03-04), helmet (apartat 5), middlewareErrors que amaga detalls (04-02) |
| API9 | Inventari d'APIs deficient | Una ruta /v1/comandes-legacy oblidada sense autenticació; una versió antiga encara desplegada |
OpenAPI com a font de veritat (03-06), contractes Pact (04-05), el gateway com a únic punt d'entrada, retirar versions amb data |
| API10 | Consum insegur d'APIs de tercers | Confiar cegament en la resposta de la passarel·la de pagament o d'un proveïdor d'adreces | Validar també el que arriba de tercers amb zod (l'ACL traductorProducte de 04-04 fa exactament això), timeouts, HMAC als webhooks (07-02) |
Els remeis ja existeixen majoritàriament al codi de TechCorp; aquesta lliçó els reuneix i afegeix els que falten.
- Validació i sanejament d'entrada amb
zod
zodRegla: tot el que entra per la xarxa es valida contra un esquema abans de tocar-ho, tant el que ve de l'usuari com el que ve d'un altre servei o d'un esdeveniment. A 04-02 i 04-04 ja es fa amb zod; aquí s'endureix:
// servei-comandes/src/rutes/esquemes.js
const { z } = require('zod');
// Identificadors amb patró tancat: res de "com-88213' OR 1=1" ni de rutes amb "../"
const ComandaId = z.string().regex(/^com-[0-9a-f]{8}$/, 'comandaId no vàlid');
const ClientId = z.string().regex(/^c-[0-9]{1,10}$/);
const ProducteId = z.string().regex(/^p-[0-9]{1,10}$/);
const PAISOS = ['ES', 'PT', 'FR']; // llista blanca, no "qualsevol ISO"
const Adreca = z.object({
carrer: z.string().trim().min(1).max(120),
codiPostal: z.string().regex(/^[0-9]{5}$/),
ciutat: z.string().trim().min(1).max(80),
pais: z.enum(PAISOS)
}).strict(); // camps desconeguts → 400 (no s'ignoren en silenci)
const CrearComanda = z.object({
clientId: ClientId,
linies: z.array(z.object({ producteId: ProducteId, quantitat: z.number().int().min(1).max(50) }).strict()).min(1).max(30),
adrecaEnviament: Adreca
}).strict();
const LlistarComandes = z.object({
estat: z.enum(['PENDENT', 'CONFIRMADA', 'CANCELLADA']).optional(),
clientId: ClientId.optional(),
limit: z.coerce.number().int().min(1).max(100).default(20),
cursor: z.string().max(200).optional()
}).strict();
module.exports = { ComandaId, CrearComanda, LlistarComandes };I a les rutes, també els paràmetres de ruta, que molts obliden:
// servei-comandes/src/rutes/comandes.js (fragments)
encaminador.get('/v1/comandes/:id', requereixScope('comandes:llegir'), async (req, res, next) => {
try {
const id = ComandaId.parse(req.params.id); // ZodError → 400 PETICIO_INVALIDA pel middlewareErrors (04-02)
// ... propietari (07-01), repositori.obtenir(id), ETag ...
} catch (err) { next(err); }
});
encaminador.post('/v1/comandes', requereixScope('comandes:crear'), async (req, res, next) => {
try {
const peticio = CrearComanda.parse(req.body); // a partir d'aquí, "peticio" és de confiança en forma; el negoci decideix la resta
// ...
} catch (err) { next(err); }
});I a app.js els límits globals que ja portava la plantilla, ara justificats: express.json({ limit: '100kb', strict: true }) (un cos més gran → 413; strict rebutja JSON que no sigui objecte o array), i al gateway proxy-body-size: 2m a l'Ingress (05-02) com a sostre absolut. Sanejament enfront de validació: TechCorp prefereix rebutjar (400) a "netejar" silenciosament; l'única normalització acceptada és trim() i la de majúscules en codis (pais.toUpperCase()), i sempre a l'esquema, mai dispersa.
- Injecció: SQL, NoSQL, ordres i logs
La injecció passa quan dades de l'usuari s'interpreten com a codi o estructura. Quatre variants que afecten TechCorp:
SQL (PostgreSQL amb pg). Mai concatenar; sempre paràmetres posicionals, com ja fa ComandaRepositori a 04-04:
// MALAMENT: si estat = "PENDENT' OR '1'='1", ho retorna tot; si conté "; DROP TABLE comandes; --", pitjor
await bd.query(`SELECT * FROM comandes.comandes WHERE estat = '${estat}'`);
// BÉ: el valor viatja a part de la sentència; el motor mai no l'interpreta com a SQL
await bd.query('SELECT * FROM comandes.comandes WHERE estat = $1 AND client_id = $2 ORDER BY creat_en DESC LIMIT $3', [estat, clientId, limit]);
// Els identificadors (nom de columna per a ORDER BY) NO es poden parametritzar: llista blanca
const COLUMNES_ORDRE = { creatEn: 'creat_en', total: 'total_centims' };
const columna = COLUMNES_ORDRE[ordre] ?? 'creat_en';NoSQL (MongoDB a Catàleg). El risc és diferent: no és text, sinó objectes. Si una ruta fa colleccio.find({ sku: req.query.sku }) i el client envia ?sku[$ne]=x, Express (amb qs) construeix { sku: { $ne: 'x' } } i la consulta retorna tots els productes; amb $where o $regex pot ser pitjor. Regla: mai passar objectes de l'usuari al filtre; validar amb zod que cada valor és una cadena (o un número) i construir el filtre al repositori amb les claus fixes:
// servei-cataleg/src/rutes/productes.js
const Consulta = z.object({ ids: z.string().optional(), categoria: z.string().regex(/^[a-z-]{1,40}$/).optional(), limit: z.coerce.number().int().min(1).max(100).default(20) }).strict();
const q = Consulta.parse(req.query); // ?categoria[$ne]=x → falla: no és string
// repositori: const filtre = {}; if (q.categoria) filtre.categoria = q.categoria; // la clau la posa el codi, el valor és un string validatA més, express.json no interpreta operadors, però un cos { "filtre": { "$where": "..." } } sí que arribaria com a objecte: mateix remei (esquema .strict(), res de filtre lliure).
Ordres del sistema. Gairebé mai no fan falta en un servei; si n'hi ha (generar un PDF, convertir una imatge), execFile('convert', [ruta]) amb arguments en array, mai exec(\convert ${nom}`)`, i el nom de fitxer validat amb patró. Encara millor: una llibreria nativa.
Logs. El log injection: un usuari posa a carrer el text "Gran Vía 12\n{\"nivell\":\"info\",\"missatge\":\"comanda pagada\"}" i, si el log fos text, hi apareixeria una línia falsa. Amb pino (06-01) el valor va serialitzat dins d'un JSON (els salts de línia s'escapen com a \n), així que l'estructura no es trenca; la regla és no construir missatges concatenant entrada de l'usuari (log.info(\comanda de ${nom}`)) sinó passar-la com a camp (log.info({ clientId }, 'comanda creada')`), i recordar que les dades personals no van al log en cap cas (apartat 8).
- Capçaleres de seguretat amb
helmet
helmethelmet és un conjunt de middlewares d'Express que fixa capçaleres HTTP defensives. Al gateway (el que veu el navegador) i a cada servei (defensa en profunditat; i alguns, com el BFF, serveixen HTML):
// gateway/servidor.js i plantilla de servei (app.js), després de middlewareRequestId
const helmet = require('helmet');
app.use(helmet({
contentSecurityPolicy: false, // l'API no serveix HTML; la CSP la defineix la SPA/BFF que sí que el serveix
strictTransportSecurity: { maxAge: 31_536_000, includeSubDomains: true }, // HSTS (07-02): coherent amb l'Ingress
crossOriginResourcePolicy: { policy: 'same-site' }
}));| Capçalera | Efecte | Qui la necessita |
|---|---|---|
Strict-Transport-Security |
Només HTTPS durant un any | Gateway (i l'Ingress, 07-02) |
X-Content-Type-Options: nosniff |
El navegador no "endevina" tipus MIME (una resposta JSON no s'executa com a script) | Tots |
X-Frame-Options: DENY / CSP frame-ancestors |
Evita el clickjacking | BFF/web; innòcua en APIs |
Content-Security-Policy |
Quins orígens d'script/estil es permeten | botiga-web i bff-mobil si serveixen HTML; desactivada a l'API |
Referrer-Policy, X-DNS-Prefetch-Control, Cross-Origin-* |
Fuites d'informació en navegar | Web |
(elimina) X-Powered-By |
No anunciar Express | Tots (app.disable('x-powered-by') ja a 04-02) |
helmet no substitueix CORS (03-04): CORS diu des de quin origen pot cridar el navegador; helmet diu com ha de tractar el navegador la resposta.
- Gestió de dependències
Un servei-comandes típic arrossega 300 paquets transitius; la majoria de les vulnerabilitats reals entren per aquí. La política de TechCorp:
| Pràctica | Com | Quan |
|---|---|---|
package-lock.json versionat i npm ci |
El lockfile fixa versions exactes; npm ci falla si no coincideix amb package.json (05-01, 05-03) |
Sempre |
npm audit --omit=dev --audit-level=high |
Falla el pipeline si hi ha vulnerabilitat alta/crítica amb pedaç en dependències de producció | A ci.yml, etapa de lint |
| Dependabot o Renovate | Pull requests automàtiques d'actualització, agrupades per setmana; els pedaços de seguretat, immediats | Configuració a .github/dependabot.yml |
| Política d'actualització | Menors i pedaços: s'accepten si passen CI i contracte; majors: revisió de l'equip | Regla del Luis (05-03) |
| Dependències mínimes | Abans d'afegir un paquet: ho porta Node? (fetch, crypto, AbortSignal), està mantingut?, quants transitius arrossega? |
Revisió de PR |
| Fixar la imatge base i les accions de CI | node:20-alpine amb digest (07-04), actions/checkout@v4 per versió |
Plantilla |
| SBOM | Llista de materials de cada imatge (Syft, 07-04) per saber en minuts si "fem servir la llibreria X" | Esment; es genera a CI |
I npm audit a CI no és infal·lible: revisa l'avís, no el silenciïs amb --force; si no hi ha pedaç, avalua el risc (la funció vulnerable es fa servir?) i documenta l'excepció amb data de revisió.
- Secrets al codi: mai, i com assegurar-ho
La regla existeix des de 04-03 (els secrets arriben per l'entorn o per fitxers _FILE, mai al repositori ni a la imatge); aquí es fa verificable:
# .github/workflows/servei-node-ci.yml (fragment, el workflow reutilitzable de 05-03)
- name: Cercar secrets a l'historial
uses: gitleaks/gitleaks-action@v2 # detecta claus AWS, tokens, contrasenyes en URLs, claus privades...
env: { GITLEAKS_LICENSE: "" } # gratuït per a repos d'organització amb llicència; alternativa: git-secrets o trufflehogI en local, un hook de pre-commit amb gitleaks protect --staged. Si un secret arriba al repositori, l'única resposta correcta és rotar-lo (l'historial de git és públic per sempre per a qui l'hagi clonat): la mecànica de rotació —Secrets, External Secrets, reinicis— és de 07-04. Afegeix al .gitignore de la plantilla .env, *.pem, *.key; i recorda que un secret en un console.log, en una traça d'error o a la URL d'una petició (?token=) també és una fuita: per això redact (06-01) i configPerALog() (04-03).
- Protecció de dades personals: RGPD a la pràctica
TechCorp tracta dades personals de l'Ana (nom, correu, adreça) i dades de pagament. Sense pretendre substituir el responsable de protecció de dades, les decisions tècniques que un equip ha de prendre:
- Minimització. Ha de portar
comanda.creadaelclient.emaili elnom(02-02)? Es necessiten perquè Notificacions enviï el correu sense cridar Clients (autonomia, 03-02). Alternatives: (a) l'esdeveniment porta les dades i tots els consumidors les reben (Inventari no les necessita); (b) l'esdeveniment porta nomésclientIdi Notificacions consultaGET /v1/clients/{id}amb el seu token de servei. Decisió de TechCorp: (a) per autonomia i perquè Notificacions és l'únic subscriptor que les fa servir avui, amb tres condicions: les dades personals només van acomanda.creada(no a la resta d'esdeveniments de la saga), RabbitMQ va xifrat (07-02) i les cues i la DLQ tenen retenció limitada (missatges a.dlqde més de 7 dies es purguen després de revisar-se: no són un arxiu); i es revisarà cap a (b) si apareix un segon consumidor que no les necessita. - Pseudonimització. Fora de Clients, els serveis desen
clientId, no el nom; els logs i mètriques porten només identificadors (06-01); les traces de Jaeger no porten cossos. - Xifratge en repòs. A les bases de dades gestionades està actiu per defecte (discos xifrats); per a columnes especialment sensibles es pot xifrar a nivell d'aplicació amb una clau del gestor de secrets, a costa de no poder indexar-les. Còpies de seguretat xifrades i amb la mateixa retenció.
- Dret de supressió. El flux de 02-04:
servei-clientsanonimitza i publicaclient.eliminat; Comandes esborraclients_refi anonimitza adreces de comandes antigues que la normativa fiscal obligui a conservar; Notificacions no desa res. És un cas d'ús més, amb prova automatitzada. - Dades de targeta: MAI a TechCorp. El navegador envia la targeta directament a la passarel·la (formulari o SDK del proveïdor); TechCorp rep un token (
tok_…) queservei-pagamentsfa servir per cobrar. Així l'abast de PCI DSS es redueix al mínim (SAQ A). Ni el número, ni el CVV, ni tan sols "els quatre últims dígits" llevat que la passarel·la els retorni ja emmascarats.*.targetaaredactés una xarxa de seguretat per si algú s'equivoca, no un permís. - Registre de tractaments i retenció: cada servei documenta quines dades personals desa, per a què i durant quant de temps (taula al seu
README), i unCronJobaplica la retenció (p. ex. anonimitzar adreces de comandes de més de 5 anys).
- Auditoria d'accions sensibles
Els logs de 06-01 expliquen què ha passat tècnicament; l'auditoria explica qui ha fet què sobre què, i s'ha de poder demostrar mesos després. Accions auditables a TechCorp: cancel·lar una comanda (qui, motiu), canviar un preu a Catàleg, canviar l'estat d'un pagament manualment, reprocessar la DLQ (scripts/reprocessarDlq.js, 06-03), accedir a dades d'un client per part d'un operador, canviar rols a Keycloak. Requisits: immutable (només inserció, sense UPDATE/DELETE per a l'usuari svc_*), separada dels logs operatius (retenció diferent: anys enfront de setmanes), i correlacionable amb X-Request-Id i el sub del token (X-Usuari-Id, 07-01).
// @techcorp/comu-http/src/auditoria.js — un registre per acció sensible
function crearAuditoria({ bd, servei }) {
return {
async registrar(req, { accio, recurs, recursId, detall = {} }) {
// taula <esquema>.auditoria(id, ocorregut_en, servei, accio, recurs, recurs_id, usuari_id, rols, request_id, detall jsonb)
// L'usuari svc_* només té INSERT sobre ella; la lectura, un rol a part (auditor)
await bd.query(
'INSERT INTO auditoria (ocorregut_en, servei, accio, recurs, recurs_id, usuari_id, rols, request_id, detall) VALUES (now(), $1, $2, $3, $4, $5, $6, $7, $8)',
[servei, accio, recurs, recursId, req.usuari?.sub ?? 'sistema', (req.usuari?.roles ?? []).join(','), req.id, JSON.stringify(detall)]
);
}
};
}
// Ús a la cancel·lació (03-01): await auditoria.registrar(req, { accio: 'COMANDA_CANCELLADA', recurs: 'comanda', recursId: comanda.comandaId, detall: { motiu } });Amb volum alt, la taula se substitueix per esdeveniments auditoria.* cap a un magatzem de només escriptura (o el log estructurat amb tipus: 'auditoria' enviat a un tenant de Loki amb retenció llarga i sense esborrat); l'important és la separació i la immutabilitat, no la tecnologia. detall no conté dades personals: identificadors i valors de negoci.
- Proves de seguretat: SAST, DAST, revisió i pentest
| Tipus | Què fa | Eina a TechCorp | Quan |
|---|---|---|---|
| SAST (anàlisi estàtica) | Cerca patrons perillosos al codi sense executar-lo | eslint-plugin-security a la configuració de la plantilla; Semgrep amb les regles p/nodejs i p/owasp-top-ten a CI |
Cada PR |
| Anàlisi de dependències | Vulnerabilitats conegudes | npm audit, Dependabot (apartat 6) |
Cada PR i diari |
| Escaneig d'imatges | CVEs a la imatge | Trivy (07-04) | Cada build |
| DAST (dinàmic) | Ataca l'API desplegada | OWASP ZAP en mode API (zap-api-scan.py amb l'OpenAPI de 03-06) contra staging, després de l'E2E de 05-03 |
Cada desplegament a staging (informe), setmanal (bloquejant en alt) |
| Revisió de codi | Ulls humans amb llista de comprovació | Llista de l'apartat 12 a la plantilla de PR; revisor d'un altre equip per a rutes amb {id} o dades personals |
Cada PR |
| Pentest | Un professional extern intenta trencar-ho | Empresa externa; abast: gateway, botiga-web, Keycloak, webhooks |
Anual i abans de l'auditoria de Pagaments |
# .github/workflows/servei-node-ci.yml (fragment SAST)
- name: Semgrep
uses: semgrep/semgrep-action@v1
with: { config: "p/nodejs p/owasp-top-ten p/secrets" }I les proves de seguretat d'aplicació són proves normals: el test/comandes.auth.test.js de 07-01 (401/403/404) i casos com "un id que no compleix el patró retorna 400", "un cos amb un camp desconegut retorna 400", "?categoria[$ne]=x retorna 400" viuen a Jest al costat de la resta i protegeixen contra regressions.
- Resposta davant de vulnerabilitats
Tard o d'hora algú trobarà alguna cosa. El que TechCorp deixa preparat:
- Canal de report:
[email protected]i un fitxer/.well-known/security.txtashop.techcorp.example(RFC 9116) amb contacte i política; agraïment públic (sense recompensa formal a la fase 1). - Triatge: gravetat amb CVSS; crítica/alta → incident de seguretat amb el procés de 06-05 (canal, responsable, cronologia), encara que no hi hagi caiguda de servei.
- Apedaçament: branca des de
main, prova que reprodueix la fallada, desplegament pel pipeline normal (canary si escau, 05-04); dependència vulnerable → actualització o mitigació temporal (bloquejar la ruta al gateway). - Comunicació: interna (tots els equips, perquè el mateix patró pot ser en un altre servei) i, si hi ha hagut accés a dades personals, notificació a l'autoritat de control en 72 hores i als afectats segons el RGPD: decisió del responsable de protecció de dades, no de l'equip tècnic.
- Postmortem sense culpa (06-05) amb accions: nova regla de Semgrep, nou cas a la llista de comprovació, prova de regressió.
- Llista de comprovació de seguretat per servei
Aquesta taula s'afegeix a techcorp/plantilla-servei-node/SEGURETAT.md i a la plantilla de pull request; cada servei la revisa en crear-se i a cada canvi rellevant:
| Àrea | Comprovació | Referència |
|---|---|---|
| Autenticació | autenticar() a /v1/*; sondes i /metrics sense token; algorithms, issuer, audience fixats |
07-01 |
| Autorització | Tota ruta amb {id} comprova propietari o rol; requereixScope/requereixRol a rutes d'operació |
07-01, API1/API5 |
| Entrada | Esquemes zod .strict() per a cos, query i paràmetres de ruta; ids amb patró; limit ≤ 100; express.json({ limit }) |
Apartat 3, API4 |
| Sortida | DTOs explícits; mai email/keycloakSub de tercers; errors sense traça |
Apartat 2 (API3), 04-02 |
| Injecció | Consultes parametritzades; filtres MongoDB amb claus fixes; sense exec amb entrada de l'usuari; log per camps |
Apartat 4 |
| Capçaleres | helmet, x-powered-by off, CORS amb orígens concrets |
Apartat 5 |
| Dependències | npm ci, npm audit a CI, Dependabot actiu, sense paquets sense mantenir |
Apartat 6 |
| Secrets | Cap al repo/imatge/log; gitleaks a CI; redact i configPerALog() cobreixen els nous |
Apartat 7, 07-04 |
| Dades personals | Taula de tractaments al README; retenció amb CronJob; reacciona a client.eliminat si desa dades de clients; res de targetes |
Apartat 8 |
| Auditoria | Accions sensibles registrades amb usuari_id i request_id; taula de només inserció |
Apartat 9 |
| Proves | Casos 400/401/403/404 a Jest; Semgrep i ZAP en verd o amb excepcions documentades | Apartat 10 |
| Canal | TLS a BD i broker, usuari svc_* mínim |
07-02 |
| Plataforma | securityContext, NetworkPolicy, ServiceAccount propi |
07-04 |
Errors Comuns i Consells
- Validar només el cos i deixar
req.params.idireq.querysense esquema: la meitat de les injeccions i BOLA entren per aquí. - Esquemes permissius (
z.objectsense.strict(),z.string()sensemax) que accepten qualsevol camp o cadenes de 10 MB: la validació ha de ser tan estricta com el contracte OpenAPI. - "Sanejar" en comptes de rebutjar: treure caràcters "perillosos" amb expressions regulars casolanes mai no cobreix tots els casos i canvia les dades de l'usuari d'amagat.
- Confiar en la resposta de tercers perquè "és la passarel·la": API10 existeix perquè els tercers també s'equivoquen i també els ataquen.
- Registrar el cos de la petició en
info"per depurar" i descobrir setmanes després que Loki desa adreces i correus. - Silenciar
npm auditamb--forceoaudit-level=criticalperquè CI passi: cada excepció, documentada amb data. - Considerar l'auditoria "un log més" i esborrar-la als 30 dies amb la retenció de Loki.
- Consell: cada regla d'aquesta lliçó que es pugui automatitzar (lint, Semgrep, gitleaks, ZAP, prova Jest) val més que la mateixa regla en un document; la llista de comprovació és per al que no es pot automatitzar.
Exercicis
Exercici 1. Un desenvolupador proposa afegir a servei-cataleg la ruta GET /v1/productes/cercar?filtre=<json> que rep un objecte de filtre de MongoDB "perquè el front pugui fer consultes flexibles". Explica el problema amb la numeració d'OWASP i proposa un disseny segur que cobreixi les necessitats reals (cercar per text, categoria i rang de preu).
Exercici 2. Escriu l'esquema zod per a POST /v1/comandes/{id}/cancellacio (03-01: motiu d'una llista permesa, comentari opcional) incloent-hi el paràmetre de ruta, i el registre d'auditoria corresponent amb crearAuditoria.
Exercici 3. La Marta pregunta si, per complir el RGPD, n'hi ha prou que Notificacions "esborri el correu quan arribi client.eliminat". Enumera quins altres llocs de la plataforma poden contenir el correu de l'Ana i quina mesura s'aplica a cadascun.
Solucions
Solució 1. És API8/API3 i una injecció NoSQL de manual: un objecte de filtre arbitrari permet { "$where": "..." }, { "preu": { "$gt": 0 } } sobre camps que no s'havien de filtrar, exposar camps interns (costProveidor), consultes sense índex que tomben Mongo (API4) i, amb $lookup en agregacions, llegir altres col·leccions. Disseny segur: paràmetres amb nom i esquema tancat, Cerca = z.object({ q: z.string().trim().min(2).max(60).optional(), categoria: z.string().regex(/^[a-z-]{1,40}$/).optional(), preuMin: z.coerce.number().min(0).optional(), preuMax: z.coerce.number().max(100000).optional(), limit: ...màx 100, cursor }).strict(); el repositori construeix el filtre amb claus fixes ({ nomNormalitzat: { $regex: escapar(q), $options: 'i' } } amb q escapat o, millor, un índex de text i $text: { $search: q }; preuCentims: { $gte, $lte }); només camps indexats; i la ruta retorna el DTO aDto de sempre. Si el front necessita alguna cosa més flexible, és un cas per a GraphQL al BFF (03-03), amb esquema tipat, no per passar objectes de Mongo.
Solució 2.
const MOTIUS = ['CLIENT_PENEDIT', 'ERROR_COMANDA', 'SENSE_ESTOC', 'FRAU'];
const Cancellacio = z.object({ motiu: z.enum(MOTIUS), comentari: z.string().trim().max(300).optional() }).strict();
encaminador.post('/v1/comandes/:id/cancellacio', requereixScope('comandes:crear'), async (req, res, next) => {
try {
const id = ComandaId.parse(req.params.id);
const { motiu, comentari } = Cancellacio.parse(req.body);
const comanda = await repositori.obtenir(id);
// propietari/rol com a 07-01 (client: només la seva → si no, 404; operador: qualsevol; FRAU només operador)
if (motiu === 'FRAU' && !req.usuari.roles.includes('operador')) throw new ErrorNegoci('PROHIBIT', 'Motiu reservat a operadors', 403);
await cancellarComanda({ comanda, motiu, usuari: req.usuari }); // transició + outbox comanda.cancellada (04-04)
await auditoria.registrar(req, { accio: 'COMANDA_CANCELLADA', recurs: 'comanda', recursId: id, detall: { motiu, teComentari: Boolean(comentari) } });
res.status(202).set('Location', `/v1/comandes/${id}`).end();
} catch (err) { next(err); }
});El comentari no va a l'auditoria (text lliure de l'usuari, pot contenir dades personals): només si existeix. I l'auditoria es registra després de la transició, dins de la mateixa transacció si el repositori ho permet, per no auditar cancel·lacions que han fallat.
Solució 3. Llocs i mesures: (1) Base de dades de Clients: anonimitzar la fila (no esborrar, per integritat i obligacions fiscals), origen de l'esdeveniment. (2) clients_ref a Comandes (02-04): esborrar la fila i anonimitzar adreca_enviament de comandes antigues en rebre client.eliminat. (3) Missatges comanda.creada a cues, .reintent i .dlq de RabbitMQ: retenció curta i purga (apartat 8); si hi ha missatges a la DLQ del client, es processen o es descarten. (4) Logs a Loki: no hi hauria d'haver cap correu gràcies a redact i a la regla de no registrar-lo; si una auditoria en troba algun, és un incident (apartat 11) i cal esborrar-lo del magatzem. (5) Traces a Jaeger: sense cossos ni dades personals per disseny (06-02); comprovar els atributs d'span propis. (6) Còpies de seguretat: no s'editen; es documenta que la dada desapareix en expirar la retenció de la còpia (i s'anonimitza en restaurar). (7) Keycloak: esborrar o desactivar l'usuari (el sub), que és on viu el correu de login. (8) Proveïdor de correu de Notificacions (si conserva historial d'enviaments): configurar retenció mínima o sol·licitar l'esborrat per la seva API. (9) La taula d'auditoria: no ha de contenir correus (només usuari_id), i per disseny es conserva. La resposta a la Marta: "esborrar a Notificacions" és una de nou accions, i per això el dret de supressió és un cas d'ús amb llista i prova, no un DELETE.
Conclusió
Aquesta lliçó ha convertit la seguretat "del codi" en pràctiques concretes de TechCorp: sis principis (mínim privilegi, defensa en profunditat, fallar segur, superfície mínima, seguretat per disseny, shift left); l'OWASP API Security Top 10 llegit amb casos propis i remeis que ja existien (propietari del recurs, DTOs, limit ≤ 100, rate limit, CORS, OpenAPI i Pact) o que s'han afegit; esquemes zod .strict() amb ids per patró (^com-[0-9a-f]{8}$), llistes blanques i límits, també en paràmetres de ruta i query, amb express.json({ limit: '100kb' }); consultes parametritzades a pg, filtres de MongoDB amb claus fixes, execFile i logs per camps; helmet al gateway i als serveis; npm ci, npm audit, Dependabot i SBOM; gitleaks a CI i rotació davant de qualsevol fuita; RGPD pràctic (minimització amb la decisió de mantenir email a comanda.creada sota condicions, pseudonimització, xifratge en repòs, client.eliminat, retenció, i mai dades de targeta: tokenització de la passarel·la i abast PCI DSS mínim); auditoria immutable i separada amb crearAuditoria correlacionada per usuari_id i request_id; SAST amb ESLint/Semgrep, DAST amb ZAP contra staging, revisió amb llista i pentest anual; un procés de resposta amb security.txt i notificació RGPD; i la llista de comprovació que ara forma part de plantilla-servei-node. Queda l'última capa, la que sosté totes les altres: la imatge que executa el codi, el pod que la conté, els secrets que li arriben, la xarxa que l'envolta i els permisos del clúster. És el tema de la lliçó següent: seguretat en contenidors i Kubernetes.
Curs de Microserveis
Mòdul 1: Introducció als Microserveis
- Conceptes Bàsics de Microserveis
- Avantatges i Desavantatges dels Microserveis
- Comparació amb l'Arquitectura Monolítica
- Quan Adoptar Microserveis: Criteris de Decisió
- El Cas Pràctic del Curs: la Botiga Online de TechCorp
Mòdul 2: Disseny de Microserveis
- Principis de Disseny de Microserveis
- Descomposició d'Aplicacions Monolítiques
- Definició de Bounded Contexts
- Gestió de Dades: una Base de Dades per Servei
- Consistència Distribuïda: Sagues, CQRS i Event Sourcing
Mòdul 3: Comunicació entre Microserveis
- APIs RESTful
- Missatgeria Asíncrona
- Protocols de Comunicació: gRPC, GraphQL
- API Gateway i Backend for Frontend
- Descobriment de Serveis i Balanceig de Càrrega
- Contractes i Versionat d'APIs
Mòdul 4: Implementació de Microserveis
- Elecció de Tecnologies i Eines
- Desenvolupament d'un Microservei Simple
- Gestió de Configuració
- Integració Pràctica: Consumir APIs i Publicar Esdeveniments
- Proves en Microserveis: Unitàries, d'Integració i de Contracte
Mòdul 5: Desplegament i Orquestració
- Contenidors i Docker
- Orquestració amb Kubernetes
- CI/CD per a Microserveis
- Estratègies de Desplegament: Rolling, Blue-Green i Canary
- Service Mesh: Istio i Linkerd
Mòdul 6: Monitoratge i Manteniment
- Monitoratge i Logging
- Traçabilitat Distribuïda amb OpenTelemetry
- Gestió d'Errors i Recuperació
- Escalabilitat i Rendiment
- SLOs, Alertes i Gestió d'Incidents
Mòdul 7: Seguretat en Microserveis
- Autenticació i Autorització
- Seguretat en la Comunicació
- Pràctiques de Seguretat
- Seguretat en Contenidors i Kubernetes
