Saltar a contenido

Seguridad

Qué vas a aprender

  • Cómo se autentica un cliente en la API de plataforma (token Bearer con hash) y cómo se autoriza (roles viewer/operator/deployer).
  • Por qué las cabeceras x-finaz-* enviadas por el cliente se rechazan.
  • El endurecimiento de contenedores: read_only, cap_drop: ALL, no-new-privileges, usuario no root.
  • Las reglas de manejo de secretos que sigue todo el proyecto (y este manual).

Modelo de amenaza en una línea

La API solo escucha en loopback, pero en el servidor hay otros usuarios y otros servicios. La defensa es en capas: red (loopback), identidad (tokens), autorización (roles y dueño), contenedor (mínimo privilegio) y datos (roles por almacén).

flowchart LR
    C["Cliente"] -->|"Authorization: Bearer TOKEN"| L["127.0.0.1:18300<br/>(solo loopback)"]
    L --> AU{"SHA-256(token)<br/>∈ api_tokens.json?"}
    AU -->|no| E401["401"]
    AU -->|sí| H{"¿trae x-finaz-principal<br/>o x-finaz-roles?"}
    H -->|sí| E401b["401 (identidad forjada)"]
    H -->|no| Z{"¿rol suficiente?"}
    Z -->|no| E403["403"]
    Z -->|sí| O{"¿es el dueño?"}
    O -->|no| E404["404 (sin revelar existencia)"]
    O -->|sí| OK["200 / 201 / 202"]

Autenticación: token Bearer opaco

  • El cliente envía Authorization: Bearer <TOKEN>.
  • La API calcula el SHA-256 del token y lo busca en api_tokens.json, montado read-only. Ese fichero contiene solo hashes: robarlo no da tokens.
  • El principal y sus roles salen del registro, no del cliente.

Por qué se rechazan las cabeceras x-finaz-*

Si el cliente pudiera enviar x-finaz-principal: admin, cualquiera se haría pasar por otro. Por eso toda petición que traiga x-finaz-principal o x-finaz-roles se rechaza con 401 (frontera de confianza). Solo en modo dev/test explícito (FINAZ_API_ALLOW_HEADERS=1 y sin fichero de tokens) se aceptan esas cabeceras.

Emitir un token nuevo (lo hace el operador; el token en claro no se guarda en el servidor):

python3 -c "import secrets; print(secrets.token_urlsafe(32))"   # → <TOKEN>, se entrega al usuario
# se calcula sha256(<TOKEN>) y se añade la entrada al JSON (principal, roles)

Autorización: roles y dueño

Rol Uso típico
viewer Leer recursos
operator Crear flujos y lanzar runs
deployer Operaciones de despliegue

Reglas (finaz_security.autorizar): 401 sin identidad, 403 sin rol, 404 ante dueño distinto (no se revela que el recurso existe).

El smoke deployment/v2/smoke/e2e_batch.py comprueba en vivo los tres casos de 401 (sin token, forjado e inválido) además del flujo correcto: 10/10 en la QA.

Contenedores endurecidos

Cada servicio del runtime declara en deployment/v2/compose.yaml:

Ajuste Efecto
read_only: true Sistema de ficheros raíz de solo lectura (escritura solo en tmpfs y volúmenes)
cap_drop: [ALL] Sin capacidades Linux
security_opt: no-new-privileges:true Ningún proceso puede escalar privilegios
Usuario finaz (uid 10000) Nunca root
tmpfs /tmp limitado Temporales en memoria, con tamaño máximo
Puerto 127.0.0.1:18300 Nada publicado fuera de loopback; los almacenes no publican puertos

Además: imágenes con base pineada por digest, SBOM SPDX por imagen en qa_reports/v2/sbom/, y verify_release.sh que comprueba que los contenedores corren exactamente los IDs del manifiesto de release.

Secretos: reglas de oro

Nunca

  • Secretos en git, en imágenes, en logs, en reportes… ni en este manual.
  • Imprimir un secreto: solo se informa SÍ/NO, longitud o hash.
  • Tokens en git remote o en ficheros del servidor: el push usa un token por entorno (GIT_ASKPASS o variable reenviada por SSH).
  • Copiar .env (que contiene GH_TOKEN) al servidor o a una imagen.

Comprobación rápida

docker inspect de cualquier contenedor del runtime debe mostrar solo rutas *_FILE, nunca valores. Y git status debe estar limpio de secretos antes de cada commit.

Resumen

  • Bearer opaco verificado por hash; principal y roles vienen del registro, no del cliente.
  • x-finaz-* del cliente → 401; 401/403/404 según identidad, rol y dueño.
  • Contenedores read_only, cap_drop: ALL, no-new-privileges, uid 10000, solo loopback.
  • Secretos por fichero, nunca impresos ni versionados.

Para practicar

  1. Un cliente envía un token válido y además x-finaz-roles: deployer. ¿Qué responde la API y por qué?
  2. ¿Por qué devolver 404 y no 403 cuando el recurso es de otro dueño?
  3. Enumera qué ganas con read_only: true si un atacante consigue ejecutar código en api.