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):
- Generar la nueva clave:
openssl genrsa -out private.pem 2048, derivar la pública y asignar elkidsecuencial. - Publicar ambas en el JWKS: la
retiring(vieja) + laactive(nueva). - Cambiar
JWT_CURRENT_KIDa la nueva clave (variable de entorno / secret del despliegue). A partir de aquí los JWT nuevos se firman con la nueva. - Verificar por métricas que los validadores ya no reciben tokens con el
kidviejo (ventana de gracia de 48–72 h, ver §4). - Retirar la vieja del JWKS y archivarla cifrada (gpg/
ageen el bucket/bóveda de auditoría). Los tokens emitidos con ella quedan inválidos automáticamente al vencer suexp.
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:
- Generar y publicar una clave nueva de inmediato (mismo paso 1–3 de §2, pero sin esperar los 90 días).
- 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.
- Revocar todos los refresh tokens (
POST /api/v1/auth/logout-allmasivo / job de revocación): fuerza relogin de todos los usuarios. - 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 unkidfuera 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.