CreaRack-SL

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

  1. Generar nueva key/token
  2. Configurar en el servicio destino (Dokploy, Cloudflare, etc.)
  3. Verificar que funciona
  4. Revocar la key antigua
  5. Documentar la rotación

Nunca borrar la key antigua antes de verificar que la nueva funciona.


1. GEMINI_API_KEY (Google AI)

PasoAcción
Generarconsole.cloud.google.com → APIs → Credentials → Create API Key
ConfigurarDokploy PROD → Environment Variables → GEMINI_API_KEY = nueva key
RedeployDokploy → Manual deploy (o push un commit)
Verificarssh root@crearack.com "docker exec crearack-pro-zcmvsl-web-1 python manage.py perf_review --ai"
RevocarGoogle Console → Credentials → Delete key anterior

2. GH_PAT (GitHub Personal Access Token)

PasoAcción
Generargithub.com → Settings → Developer Settings → Personal Access Tokens → Generate new
Permisosrepo (full control) — mínimo necesario
ConfigurarCloudflare Pages (workspace) → Environment Variables → GH_PAT
ConfigurarDokploy PROD + STAGE si se usa para webhooks
VerificarWorkspace: comprobar que GitHub integration funciona (list issues, repo activity)
RevocarGitHub → PAT → Delete token anterior

3. HOLDED_API_KEY (Holded ERP)

PasoAcción
Generarapp.holded.com → Ajustes → Integraciones → API → Regenerar
ConfigurarCloudflare Pages (workspace) → Environment Variables → HOLDED_API_KEY
RedeployPush commit al workspace para que CF Pages redespliegue
VerificarWorkspace: MCP tool holded_list_invoices
RevocarHolded regenera automáticamente (la anterior queda invalidada)

4. HETZNER_API_TOKEN

PasoAcción
Generarconsole.hetzner.cloud → Security → API Tokens → Generate
PermisosRead & Write
ConfigurarCloudflare Pages (workspace) → HETZNER_API_TOKEN
RedeployPush commit al workspace
VerificarWorkspace: MCP tool get_servers_status
RevocarHetzner Console → API Tokens → Revoke anterior

5. UPTIMEROBOT_API_KEY

PasoAcción
Generaruptimerobot.com → My Settings → API Settings → Create Main API Key
ConfigurarCloudflare Pages (workspace) → UPTIMEROBOT_API_KEY
RedeployPush commit al workspace
VerificarWorkspace: MCP tool get_uptime
RevocarUptimeRobot → 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í.

PasoAcción
Generarpowershell scripts/generate-mcp-tokens.ps1 (4 tokens: Edu, Dani, Txell, CI) — guardar en la bóveda ANTES de seguir
Configurargh secret set MCP_TOKENS_NEW -R CreaRackSL/CreaRackSL-workspace → gh workflow run rotate-mcp-tokens.yml (PATCHea el env de CF Pages)
Aplicargh workflow run cf-pages-deploy.yml — el env solo aplica en el SIGUIENTE deploy
ConsumidoresGH 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
Verificartools/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
Limpiargh secret delete MCP_TOKENS_NEW

7. WORKSPACE_API_KEY (CreaRack Pro → Workspace)

PasoAcción
GenerarGenerar random: openssl rand -hex 32
ConfigurarCloudflare Pages (workspace) → WORKSPACE_API_KEY
ConfigurarDokploy PROD → WORKSPACE_API_KEY (mismo valor)
RedeployAmbos: workspace (push) + PROD (push o manual)
VerificarWorkspace dashboard → métricas de CreaRack Pro cargan

8. MCP_SIGNING_SECRET (OAuth)

PasoAcción
Generaropenssl rand -hex 32
ConfigurarCloudflare Pages (workspace) → MCP_SIGNING_SECRET
RedeployPush commit al workspace
VerificarReconectar 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.

PasoAcción
GenerarCloudflare 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.
DistribuirSincronizar 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.
RevocarZero Trust → el token anterior → revoke, solo tras verificar las 6 copias.

Inventario de copias (sincronizar TODAS en cada rotación):

  1. GitHub Actions secrets — CF_ACCESS_CLIENT_ID + CF_ACCESS_CLIENT_SECRET en CreaRack-Pro (y workspace si su CI los usa).
  2. Dokploy env vars — PROD (crearack-pro-zcmvsl-web-1) y STAGE + redeploy de cada uno.
  3. OPS — /opt/bib-reindex/.cf-access-secret (crons del Bibliotecario).
  4. /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.
  5. claude.ai → panel de routines (cuenta de Edu) — Maintenance Weekly Agent.
  6. 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.

PasoAcción
GenerarGitHub → Settings → Developer settings → Tokens (classic) → Generate → marcar solo read:packages (los fine-grained no van bien con GHCR).
Guardarenv var GHCR_PULL_TOKEN (User) en el PC de Edu (para operarlo sin volcarlo al chat).
Distribuirdocker 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).
Verificardocker pull ghcr.io/crearacksl/crearack-base:latest en cada server = OK; un deploy construye FROM base sin error de auth.
RevocarGitHub → 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

FechaSecretRazónQuien
10-07-2026MCP_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 6Edu (Claude)
14-07-2026CF_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 playbookEdu (Claude)
15-07-2026ghcr-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 playbookEdu (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]]