Secret Rotation Playbook — CreaRack Pro
Secret Rotation Playbook — CreaRack Pro
Fecha: 12-04-2026
Objetivo: Procedimiento estándar para rotar cada token/key con zero-downtime.
Cuándo rotar: Cada 6 meses, o inmediatamente si se sospecha compromiso.
Principio general
- Generar nueva key/token
- Configurar en el servicio destino (Dokploy, Cloudflare, etc.)
- Verificar que funciona
- Revocar la key antigua
- Documentar la rotación
Nunca borrar la key antigua antes de verificar que la nueva funciona.
1. GEMINI_API_KEY (Google AI)
| Paso | Acción |
|---|
| Generar | console.cloud.google.com → APIs → Credentials → Create API Key |
| Configurar | Dokploy PROD → Environment Variables → GEMINI_API_KEY = nueva key |
| Redeploy | Dokploy → Manual deploy (o push un commit) |
| Verificar | ssh root@crearack.com "docker exec crearack-pro-zcmvsl-web-1 python manage.py perf_review --ai" |
| Revocar | Google Console → Credentials → Delete key anterior |
2. GH_PAT (GitHub Personal Access Token)
| Paso | Acción |
|---|
| Generar | github.com → Settings → Developer Settings → Personal Access Tokens → Generate new |
| Permisos | repo (full control) — mínimo necesario |
| Configurar | Cloudflare Pages (workspace) → Environment Variables → GH_PAT |
| Configurar | Dokploy PROD + STAGE si se usa para webhooks |
| Verificar | Workspace: comprobar que GitHub integration funciona (list issues, repo activity) |
| Revocar | GitHub → PAT → Delete token anterior |
3. HOLDED_API_KEY (Holded ERP)
| Paso | Acción |
|---|
| Generar | app.holded.com → Ajustes → Integraciones → API → Regenerar |
| Configurar | Cloudflare Pages (workspace) → Environment Variables → HOLDED_API_KEY |
| Redeploy | Push commit al workspace para que CF Pages redespliegue |
| Verificar | Workspace: MCP tool holded_list_invoices |
| Revocar | Holded regenera automáticamente (la anterior queda invalidada) |
4. HETZNER_API_TOKEN
| Paso | Acción |
|---|
| Generar | console.hetzner.cloud → Security → API Tokens → Generate |
| Permisos | Read & Write |
| Configurar | Cloudflare Pages (workspace) → HETZNER_API_TOKEN |
| Redeploy | Push commit al workspace |
| Verificar | Workspace: MCP tool get_servers_status |
| Revocar | Hetzner Console → API Tokens → Revoke anterior |
5. UPTIMEROBOT_API_KEY
| Paso | Acción |
|---|
| Generar | uptimerobot.com → My Settings → API Settings → Create Main API Key |
| Configurar | Cloudflare Pages (workspace) → UPTIMEROBOT_API_KEY |
| Redeploy | Push commit al workspace |
| Verificar | Workspace: MCP tool get_uptime |
| Revocar | UptimeRobot → API Settings → Delete anterior |
6. MCP_TOKENS (Workspace auth)
Reescrito en s216 (10-07-2026, rotación P1): tokens con nombre Edu/Dani/Txell/CI,
escritura del env var vía workflow (el token CF del perfil no tiene scope Pages),
.mcp.json versionado usa ${BIB_MCP_TOKEN} — NUNCA volver a hardcodear el valor ahí.
El token DR es var propia desde s185 (DR_MAINTENANCE_TOKEN) y NO se toca aquí.
| Paso | Acción |
|---|
| Generar | powershell scripts/generate-mcp-tokens.ps1 (4 tokens: Edu, Dani, Txell, CI) — guardar en la bóveda ANTES de seguir |
| Configurar | gh secret set MCP_TOKENS_NEW -R CreaRackSL/CreaRackSL-workspace → gh workflow run rotate-mcp-tokens.yml (PATCHea el env de CF Pages) |
| Aplicar | gh workflow run cf-pages-deploy.yml — el env solo aplica en el SIGUIENTE deploy |
| Consumidores | GH secret MCP_TOKEN en workspace + CreaRack-Pro = token CI · OPS ssh root@100.96.245.233 "echo '<token_CI>' > /opt/bib-reindex/.token" · env User BIB_MCP_TOKEN en los 3 PCs (cada uno el SUYO) · WORKSPACE_MCP_TOKEN en Dokploy PROD y STAGE (proxy del Help Widget de CreaRack, core/api_help.py) = token CI + redeploy de cada stack — ⚠️ olvidado en la rotación de s216: el Help estuvo caído 4 semanas (10-07 → 06-08-2026) devolviendo 401 en silencio |
| Verificar | tools/list con token nuevo = 200 y con el viejo = 401 · un workflow que use MCP_TOKEN (p.ej. Bibliotecario-Lint) en verde · un cron de OPS a mano · el Help Widget de crearack.com carga el índice de artículos y responde una pregunta |
| Limpiar | gh secret delete MCP_TOKENS_NEW |
7. WORKSPACE_API_KEY (CreaRack Pro → Workspace)
| Paso | Acción |
|---|
| Generar | Generar random: openssl rand -hex 32 |
| Configurar | Cloudflare Pages (workspace) → WORKSPACE_API_KEY |
| Configurar | Dokploy PROD → WORKSPACE_API_KEY (mismo valor) |
| Redeploy | Ambos: workspace (push) + PROD (push o manual) |
| Verificar | Workspace dashboard → métricas de CreaRack Pro cargan |
8. MCP_SIGNING_SECRET (OAuth)
| Paso | Acción |
|---|
| Generar | openssl rand -hex 32 |
| Configurar | Cloudflare Pages (workspace) → MCP_SIGNING_SECRET |
| Redeploy | Push commit al workspace |
| Verificar | Reconectar Claude Code al workspace MCP |
9. CF_ACCESS_CLIENT_SECRET (service token de CF Access)
El mismo service token de CF Access protege workspace.crearack.com/api/mcp (conector MCP del equipo) + los crons del Bibliotecario en OPS + el Ingest en GitHub Actions + el Maintenance Weekly Agent. Client-id fijo: c0cfca1ac691093a2c48945017d27ba4.access. La validación ocurre en el edge de CF antes del Worker → una copia desincronizada rompe a su consumidor de inmediato (login de CF Access / 302), aunque el resto siga bien.
| Paso | Acción |
|---|
| Generar | Cloudflare Zero Trust → Access → Service Auth → Service Tokens → Refresh/Rotate del token “Bibliotecario crons + MCP”. /rotate mantiene el client-id; el secret nuevo aparece una sola vez → guardarlo en la bóveda ANTES de seguir. |
| Distribuir | Sincronizar las 6 copias de abajo. Nunca volcar el secret al chat; entre máquinas por portapapeles RDP o scp de fichero temporal, siempre con backup previo (.bak-). |
| Verificar (por hash) | En cada copia: sha256 del secret == el de un perfil que SÍ conecta (no comparar el valor en claro). Un secret viejo mide 64 igual que el bueno pero está muerto. |
| Verificar (efecto) | /api/mcp con los 3 headers (CF-Access-Client-Id/-Secret + Bearer): 302 = CF Access rechaza (secret/id malo) · 401/403 = pasa Access, falla el Bearer de la app · 202/405 = auth OK. Además: Ingest de GH y un cron de OPS en verde. |
| Revocar | Zero Trust → el token anterior → revoke, solo tras verificar las 6 copias. |
Inventario de copias (sincronizar TODAS en cada rotación):
- GitHub Actions secrets —
CF_ACCESS_CLIENT_ID + CF_ACCESS_CLIENT_SECRET en CreaRack-Pro (y workspace si su CI los usa).
- Dokploy env vars — PROD (
crearack-pro-zcmvsl-web-1) y STAGE + redeploy de cada uno.
- OPS —
/opt/bib-reindex/.cf-access-secret (crons del Bibliotecario).
/opt/dr-backups/.cf-access (STAGE) — copia de referencia DR. ⚠️ Es la que se olvidó en la rotación de s222 (quedó con el secret viejo → Txell copió de ahí y no conectaba). NO omitir.
- claude.ai → panel de routines (cuenta de Edu) — Maintenance Weekly Agent.
- env var User
CF_ACCESS_CLIENT_SECRET en los 3 PCs (Edu/Dani/Txell). Los .mcp.json (Pro + workspace, gitignored) lo leen como ${CF_ACCESS_CLIENT_SECRET} → el valor NO se hardcodea ahí.
⚠️ No es una copia, pero revísalo en la misma ventana: la app de Access de path más específico sobre /api/mcp (“Workspace MCP API”) debe tener la policy del service token en acción Service Auth (non_identity), NUNCA Bypass — o el token no autentica y la petición cae al login del IdP. Editable en vivo por el MCP de Cloudflare. Footgun: cf_access_service_token_needs_service_auth_not_bypass.
10. GHCR pull token (imagen base privada crearack-base)
Token classic ghcr-pull-crearack-base con scope read:packages (mínimo). Sirve para que STAGE y PROD hagan docker pull de la imagen base privada ghcr.io/crearacksl/crearack-base durante el build de cada deploy (s224 — deploys de >15 min a ~1 min). Sin él, el deploy falla al resolver el FROM privado.
| Paso | Acción |
|---|
| Generar | GitHub → Settings → Developer settings → Tokens (classic) → Generate → marcar solo read:packages (los fine-grained no van bien con GHCR). |
| Guardar | env var GHCR_PULL_TOKEN (User) en el PC de Edu (para operarlo sin volcarlo al chat). |
| Distribuir | docker login ghcr.io -u <usuario_github> --password-stdin en STAGE y PROD (vía SSH por NetBird; queda en /root/.docker/config.json de root, que Dokploy usa para el FROM privado del build). |
| Verificar | docker pull ghcr.io/crearacksl/crearack-base:latest en cada server = OK; un deploy construye FROM base sin error de auth. |
| Revocar | GitHub → Tokens (classic) → revoke el anterior tras re-loguear los 2 servers. |
Scope mínimo (solo lectura de packages, sin repos ni escritura). El package crearack-base debe seguir privado (contiene el inventario de deps del proyecto — decisión de Edu s224: nada expuesto).
Registro de rotaciones
| Fecha | Secret | Razón | Quien |
|---|
| 10-07-2026 | MCP_TOKENS (Edu/Dani/Txell + nuevo CI) | Higiene (auditoría s216, P1): token de Edu compartido idéntico en los .mcp.json locales de los 3 PCs (atribución rota — todo actuaba como “edu”). NO llegó a git (gitignored; verificado contra el historial). ⚠️ Post-mortem 06-08: dejó huérfano WORKSPACE_MCP_TOKEN en Dokploy PROD (Help Widget 401 durante 4 semanas) → consumidor añadido a la sección 6 | Edu (Claude) |
| 14-07-2026 | CF_ACCESS_CLIENT_SECRET (service token, mismo client-id) | Higiene (auditoría s219, P1b). Ejecutada en s222; remate de copias rezagadas (/opt/dr-backups/.cf-access STAGE + PC de Txell/Dani) en s223-s224 → motivó la sección 9 de este playbook | Edu (Claude) |
| 15-07-2026 | ghcr-pull-crearack-base (PAT classic read:packages) | Nuevo (s224): auth del docker pull de la imagen base privada crearack-base en STAGE+PROD (deploys de >15 min a ~1 min). Sección 10 de este playbook | Edu (Claude) |
| ___ | ___ | Rotación programada / Sospecha compromiso | ___ |
Mantener este registro actualizado. Guardar fuera de git (junto con SECRETS_VAULT).
Véase también
- [[runbook—infra—rotate-mcp-token]]
- [[crearack-tech—guides—security-guide]]
- [[crearack-tech—guides—gemini-api-setup]]
- [[crearack-tech—reports—security-audit-04-04-2026]]
- [[crearack-tech—reports—security-audit]]