Saltar a contenido

0017. Rotación y renovación de claves (operativa)

Estado: Propuesto Fecha: 2026-09-08 Autor: Duval Alcivar Módulo(s) afectado(s): svc-identidad, todos los servicios que validan tokens (ADR 0006)

Contexto

El ADR 0011 define cómo se firma: RSA-2048/PEM, RS256, kid en el header y un JWKS público en GET /.well-known/jwks.json. Pero la operativa —cuándo se rota, quién lo hace, cómo se revoca de emergencia una clave comprometida y qué pasa con los validadores que cachean el JWKS— está solo esbozada (0011 §5c). Falta un procedimiento completo, repetible y monitoreable para no depender de que un humano "recuerde" rotar la clave.

Decisión

La rotación es un runbook documentado y ejecutable (no un procedimiento oral). Núcleo: publish both, switch kid, retire after grace.

1. Ciclo de vida de una clave

Cada clave RSA tiene un estado y un kid inmutable:

Estado Qué significa Dónde aparece
active firma los JWT nuevos (JWT_CURRENT_KID) en el JWKS público
retiring ya no firma, pero acepta tokens firmados por ella en el JWKS público (ventana de gracia)
retired fuera del JWKS; solo es archivo cifrado para auditoría en la bóveda/backup, no en el JWKS

El kid usa el formato id-<AAMM>-<sec> (ej. id-2609-01), que permite saber de un vistazo qué clave es y de qué mes.

2. Rotación programada (cada 90 días)

Procedimiento ejecutable por on-call (script scripts/rotate_keys.py o manual):

  1. Generar la nueva clave: openssl genrsa -out private.pem 2048, derivar la pública y asignar el kid secuencial.
  2. Publicar ambas en el JWKS: la retiring (vieja) + la active (nueva).
  3. Cambiar JWT_CURRENT_KID a la nueva clave (variable de entorno / secret del despliegue). A partir de aquí los JWT nuevos se firman con la nueva.
  4. Verificar por métricas que los validadores ya no reciben tokens con el kid viejo (ventana de gracia de 48–72 h, ver §4).
  5. Retirar la vieja del JWKS y archivarla cifrada (gpg/age en el bucket/bóveda de auditoría). Los tokens emitidos con ella quedan inválidos automáticamente al vencer su exp.

Nada de esto toca a los demás servicios: validan contra el JWKS y cambian de clave sola. Lo único que exige coordinación es la duración de la ventana de gracia (§4).

3. Rotación de emergencia (clave comprometida)

Ante sospecha de compromiso:

  1. Generar y publicar una clave nueva de inmediato (mismo paso 1–3 de §2, pero sin esperar los 90 días).
  2. Retirar del JWKS la clave comprometida en menos de 5 minutos (retirada inmediata, no ventana de gracia). Los validadores con caché quedan inútiles solo hasta su TTL.
  3. Revocar todos los refresh tokens (POST /api/v1/auth/logout-all masivo / job de revocación): fuerza relogin de todos los usuarios.
  4. Archivar la clave comprometida cifrada de todos modos, para el análisis forense post-incidente.

4. Regla de la ventana de gracia

La ventana debe ser mayor o igual al TTL de caché del JWKS que usan los demás servicios (ADR 0006). Si un validador cachea el JWKS 24 h, retirar antes rompe sus validaciones; la ventana de 48–72 h cubre validadores con TTL de hasta 24 h + margen. Al reducir el TTL de caché se puede reducir la ventana (y la exposición post-emergencia).

5. Configuración y secretos

Ajuste Dónde vive Valor típico
JWT_CURRENT_KID env / secret del despliegue id-2609-01
Lista de claves activas/retiring secret manager (vuelca al JWKS endpoint) JSON de keys
Archivo de claves rotadas bucket/bóveda cifrada retención ≥ 1 año
Rotación programada calendario/runbook on-call cada 90 días

6. Monitoreo

  • Métrica jwt_kid_en_uso{jwt_kid="..."} por hora: alerta si aparece un kid fuera del conjunto esperado (token firmado con clave no publicada).
  • Métrica jwt_token_invalido{razon="kid_desconocido"}: pico = clave retirada demasiado pronto o ataque.
  • Alerta de "última rotación > 100 días" para no dejar vencer la cadencia.

Alternativas descartadas

  • Clave fija sin rotación — un compromiso equivale a falsificación de tokens por tiempo indefinido; sin procedimiento de rotación la clave nunca se cambia.
  • Rotación sin ventana de gracia — rompe a cualquier servicio que aún tenga la clave vieja cacheada (ADR 0006).
  • HS256 compartido por todos — ya descartado en el 0011: compromete a todos los servicios a la vez.
  • Guardar las claves viejas en texto plano en el repo — la filtración del repo lo sería todo; las claves solo viven en secretos y bóveda cifrada.

Consecuencias

  • Rotar es un procedimiento repetible de 5 pasos, auditable y ejecutable por on-call sin depender del autor del ADR.
  • La ventana de gracia acota el alcance de una emergencia: a lo más se retraza la invalidación lo que dure el TTL de caché de los validadores.
  • Compromiso → respuesta en minutos: rotación inmediata + revocación masiva de refresh (fuerza relogin global).
  • Deuda futura: automatizar la rotación completa con un pipeline (candidato a job APScheduler del transversal 0015 + secret manager), en vez de runbook.

Este ADR complementa el 0011 (firma JWT, claves y JWKS) con la operativa de rotación. La caducidad de los tokens que la rotación invalida está en el 0020; la purga de las sesiones revocadas en el 0021.