ADR — Patrón CF Access Service Token en callers Django runtime
Contexto
Tras la sesión 57 (11-12 mayo 2026), se endureció CF Access en las apps “Workspace MCP API” y “API Biblioteca” eliminando la policy bypass-everyone. Se distribuyó el Service Token a todos los callers conocidos (batch: GitHub Actions, scripts CLI, crons). Sin embargo, los callers Django runtime de PROD no estaban catalogados como callers y quedaron sin los headers necesarios, causando el incidente del Help Widget (sesión 58).
Esta decisión documenta el patrón técnico adoptado y el protocolo de audit para futuros endurecimientos de CF Access.
Decisión
Patrón de inyección de headers CF Access
Cualquier caller Django que llame a un endpoint protegido por CF Access debe usar un helper centralizado que inyecte los headers condicionalmente:
# Patrón estándar — inyección condicional (no rompe dev local)
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
Principios clave:
- Condicional: si las vars están vacías (dev local), no se envían → no rompe entornos sin CF Access.
- Centralizado: un solo helper por archivo (
_workspace_headers()enapi_help.py,_cf_access_headers()en scripts harness) cubre todos los callers del módulo. - Settings explícitos: las vars se declaran en
config/settings/base.pycon default vacío, documentando que existen. - Compose propagado:
compose.ymlycompose.prod.ymlincluyen las vars para que Dokploy las pueda inyectar en PROD.
Variables de entorno
| Variable | Descripción | Dónde configurar |
|---|---|---|
CF_ACCESS_CLIENT_ID | Service Token ID de CF Access | Dokploy panel (PROD) + PowerShell local (dev) |
CF_ACCESS_CLIENT_SECRET | Service Token Secret de CF Access | Dokploy panel (PROD) + PowerShell local (dev) |
Siguen el mismo patrón que WORKSPACE_MCP_TOKEN (precedente establecido antes de s57).
Protocolo de audit para futuros endurecimientos CF Access
Cuando se elimine bypass-everyone (o equivalente) de cualquier app CF Access, el audit de callers debe cubrir todas las capas:
| Capa | Dónde buscar |
|---|---|
| GitHub Actions workflows | .github/workflows/*.yml |
| Scripts CLI / crons | scripts/**/*.py, scripts/**/*.sh |
| Callers Django runtime ⚠️ | */api_*.py, */views*.py, cualquier archivo que use requests, urllib, o httpx para llamar a workspace.crearack.com |
| Frontend (si aplica) | Llamadas directas a workspace desde el browser (generalmente no aplica por CORS) |
⚠️ La capa más fácil de olvidar es Django runtime — no aparece en los workflows de GitHub y sólo se descubre revisando activamente los archivos de la app Django.
Alternativas consideradas
| Alternativa | Razón de descarte |
|---|---|
Mantener bypass-everyone en PROD | Reduce la postura de seguridad; CF Access pierde su valor como capa de autenticación M2M |
| Middleware Django centralizado para inyectar headers | Sobreingeniería para el número actual de callers; el patrón helper es suficiente y más explícito |
| Proxy interno (evitar CF Access desde Django) | Añade complejidad de infraestructura; el patrón de Service Token es el estándar CF para M2M |
Consecuencias
- Positivas: Todos los callers Django runtime ahora se autentican correctamente contra CF Access. El patrón es reutilizable para futuros módulos.
- Negativas / deuda: El audit manual de callers sigue siendo necesario — no existe automatización que verifique que todos los callers tienen los headers.
- Deuda técnica pendiente: Considerar un test de integración o linter que detecte llamadas a
workspace.crearack.comsinCF-Access-*headers en el futuro.
Estado
Implementado en PR #26 (v1.0.73 hotfix, 2026-05-12).
Véase también
- [[incident—20260512—help-widget-cf-access-s58]]