Volver a la wiki

Incidente s58: CF Access 403 — Help Widget bloqueado tras endurecer bypass-everyone (s57)

Resumen

CampoValor
Fecha detección2026-05-12
Sesións58
SeveridadAlta (PROD — Help Widget 100% inoperativo)
Estado✅ Resuelto — fix mergeado en commit 881f96f
AfectadosTodos los usuarios que abrieran el Help Widget en PROD
Detectado porEdu (antes de que cargara ningún usuario externo)

Síntomas observados


Causa raíz

En sesión s57 (2026-05-12, tarde-noche) se eliminó la policy bypass-everyone de dos apps Cloudflare Access:

El Service Token CF Access se distribuyó correctamente a:

Pero los callers Django runtime de PROD no estaban en esa lista. Concretamente, las 3 llamadas que hace core/api_help.py (a través de _workspace_headers()) a workspace.crearack.com:

CallerEndpointSíntoma
Proxy bib_askPOST /api/mcp“Could not connect to the assistant”
Traducciones ENGET /api/biblioteca/wiki-titlesPanel sin títulos
Lista artículosGET /api/biblioteca/wikiPanel sin documentación

A partir del endurecimiento, todas devolvían 403 (o redirect 302 a login de CF Access) porque los headers CF-Access-Client-Id y CF-Access-Client-Secret no se enviaban.

Gap sistémico

s57 realizó un audit de callers exhaustivo para workflows/scripts batch, pero no incluyó los callers Django runtime en el inventario. El Help Widget funciona en tiempo real desde el proceso Django de PROD — un tipo de caller distinto que no había sido considerado en el checklist de hardening.


Fix aplicado (commit 881f96f)

1. core/api_help.py — _workspace_headers()

def _workspace_headers():
    token = getattr(settings, "WORKSPACE_MCP_TOKEN", "")
    headers = {
        "Authorization": f"Bearer {token}",
        "Content-Type": "application/json",
    }
    cf_id = getattr(settings, "CF_ACCESS_CLIENT_ID", "")
    cf_secret = getattr(settings, "CF_ACCESS_CLIENT_SECRET", "")
    if cf_id and cf_secret:
        headers["CF-Access-Client-Id"] = cf_id
        headers["CF-Access-Client-Secret"] = cf_secret
    return headers

Un único cambio en el helper compartido fija los 3 callers simultáneamente (todos pasan por _workspace_headers()).

2. scripts/harness/bib_report_check.py — _cf_access_headers()

Nuevo helper que inyecta los headers CF Access en las 2 funciones que llaman a /api/biblioteca/recent-reports y /api/biblioteca/skip-log. El hook ya no degrada silenciosamente si las env vars están configuradas.

def _cf_access_headers() -> dict[str, str]:
    cf_id = os.environ.get("CF_ACCESS_CLIENT_ID", "").strip()
    cf_secret = os.environ.get("CF_ACCESS_CLIENT_SECRET", "").strip()
    if cf_id and cf_secret:
        return {"CF-Access-Client-Id": cf_id, "CF-Access-Client-Secret": cf_secret}
    return {}

3. config/settings/base.py

CF_ACCESS_CLIENT_ID = os.environ.get("CF_ACCESS_CLIENT_ID", "")
CF_ACCESS_CLIENT_SECRET = os.environ.get("CF_ACCESS_CLIENT_SECRET", "")

Vacío en dev local (compatible — no se llama a workspace desde local).

4. compose.yml + compose.prod.yml

Propagan las 2 env vars al contenedor web, siguiendo el patrón de WORKSPACE_MCP_TOKEN. En PROD los valores los inyecta Dokploy desde panel.


Pasos manuales post-merge (pendiente verificar)

  1. ☐ Configurar CF_ACCESS_CLIENT_ID y CF_ACCESS_CLIENT_SECRET en Dokploy panel del stack PROD.
  2. ☐ Redeploy del stack PROD desde Dokploy.
  3. ☐ Verificar Help Widget vivo: hacer una pregunta + abrir lista de artículos.
  4. ☐ Configurar las 2 env vars en la PowerShell local de cada miembro del staff (para que bib_report_check.py opere al 100%, no en degraded mode — Regla 24).

Lección aprendida (Lección s58)

Cuando se endurezca CF Access (quitar bypass-everyone o equivalente), el audit de callers debe incluir explícitamente los callers Django runtime, no solo workflows/scripts batch.

El checklist de hardening de CF Access debe tener una sección dedicada a:


Archivos modificados

ArchivoCambio
core/api_help.py_workspace_headers() inyecta headers CF Access si configurados
scripts/harness/bib_report_check.py_cf_access_headers() helper + integrado en 2 funciones
config/settings/base.pyDeclara CF_ACCESS_CLIENT_ID + CF_ACCESS_CLIENT_SECRET
compose.ymlPropaga 2 env vars al contenedor web
compose.prod.ymlPropaga 2 env vars al contenedor web
CHANGELOG.mdEntrada v1.0.73 (Sesión 58)
RELEASE_NOTES.mdEntrada hotfix v1.0.73

Véase también

Subir