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
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”. /rotatemantiene 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 classicghcr-pull-crearack-base con scope read:packages (mínimo). Sirve para que STAGE y PROD hagan docker pull de la imagen base privadaghcr.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-basedebe 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