Volver a la wiki

Cloudflare Security — Configuración de los 3 zones

Cloudflare Security — Configuración de los 3 zones

Referencia técnica de la postura de seguridad en Cloudflare aplicada a los 3 dominios de Edu tras la sesión 57 (12-05-2026). Incluye inventario de zones, settings concretos aplicados, decisiones con su razonamiento, comandos curl de verificación, y mini-runbook para replicar a un dominio nuevo.

Versión coloquial para el equipo: [[workspace—guias—dominios-y-seguridad-cloudflare]]

Última actualización: 24-07-2026 (banner de deltas post-s57). Base: 12-05-2026 (sesión 57 cierre completo).

🔄 Actualización 24-07-2026 — qué ha cambiado desde s57 (el cuerpo de abajo documenta el estado de mayo; estos deltas MANDAN):

  • Apex crearack.com PROXIFICADO tras CF (s204, 07-07) — Transform Rule + Traefik trustedIPs; wiki [[concept—infra—cloudflare-proxy-apex]]. SSH a PROD ya NO va por el dominio: solo NetBird.
  • HSTS de crearack.com subido a 1 año + preload (s224, 15-07) — max-age=31536000; includeSubDomains; preload (el envío a hstspreload.org sigue siendo decisión manual de Edu). always_use_https ON.
  • DMARC en p=reject en crearack.com y channelassistance.com (21-07, s230) — rua a logcrearack@esfericlabs.com + TXT de autorización externa en esfericlabs.com.
  • Service Token CF Access rotado (s222-s223) — el secret vigente y sus 6 copias: playbook [[crearack-tech—guides—secret-rotation-playbook]] §9. Policy de /api/mcp = Service Auth (un token en policy Bypass NO autentica — footgun s223). La mención a “1Password compartido” de abajo es de mayo; la decisión de gestor de credenciales sigue abierta (task #33).
  • App DR Backup Service ELIMINADA (s78) — su path quedó bajo la app principal.
  • channelassistance.com ya no está en PythonAnywhere — servidor propio Hetzner CX23 DCA (~06/2026).
  • esfericlabs.com ya no está parked — landing CreaRack EN VIVO desde el 10-07-2026 (el caveat 4.5 del cert de origin quedó resuelto).
  • Firewalls Hetzner solo-NetBird (s222) — wiki [[concept—infra—hetzner-firewalls]] (sustituye a la vieja capa “tailnet”: Tailscale se retiró en s85 → NetBird).
  • Workers Paid 5 $/mes en la cuenta desde 07-07 (workspace MCP/functions; el resto de zonas sigue Free).

1. Inventario de zones (actualizado 24-07-2026)

ZoneZone IDAccount IDTipo de origenPlan
crearack.comb61ecf3f6372c9b928bfdfc39b8debacca1cf7d247ed0293b623b75e6d462bd9Hetzner CCX23 (Dokploy) — crearack-pro-zcmvsl-* · apex proxificado tras CF (s204)Free
channelassistance.comccc6ad4c9f5d579cbf5eda33820a588c(mismo)Servidor propio Hetzner CX23 DCA (antes PythonAnywhere)Free
esfericlabs.com55bb0ec67de292352518249c9b78d102(mismo)Landing CreaRack EN VIVO (home v2 desde 10-07-2026)Free

Registrador: los 3 en Cloudflare Registrar → DNSSEC sincronizado al instante sin riesgo de desync de 48h.


2. Postura de seguridad — matriz consolidada

Estado base s57 con los deltas de julio anotados. ✅ = aplicado. ⚠ = aplicado con caveat documentado. — = no aplica.

Settingcrearack.comchannelassistance.comesfericlabs.com
SSL modestrictstrictstrict (s57 PM via API)
TLS min version1.2 (s57 AM)1.2 (s57 AM)1.2 (s57 PM via API, antes 1.0)
TLS 1.3on (sin 0-RTT)on (s57 PM via API, antes zrt)on (sin 0-RTT)
Always Use HTTPSon (reactivado s224 tras el paréntesis ACME del flip a proxy)on (s57 AM)on (s57 PM via API, antes off)
HSTS1 año + includeSubDomains + preload (s224; antes 6mo)enabled, max_age=6mo, include_subdomains, nosniff (s57 AM)enabled, max_age=1año, include_subdomains, nosniff (s57 PM via API, antes disabled)
Security Levelmediummedium (s57 PM via API, antes essentially_off)medium
Bot Fight Mode⚠ off (deuda asumida — rompería cron Bibliotecario y Agent/MCP; reconfirmado s204: NO activar)⚠ off (mismo motivo)on (s57 PM toggle manual Edu — revisar formularios de la landing, ver 4.2)
Block AI botson (s57 AM)on (s57 AM)on (s57 PM toggle manual Edu)
DNSSECononon
DMARC TXTp=reject (21-07-2026 · rua → logcrearack@)p=reject (21-07-2026)TXT de autorización externa <dominio>._report._dmarc (21-07)

3. CF Access apps Critical (post-s57 · ver banner para rotaciones posteriores)

Account ID: ca1cf7d247ed0293b623b75e6d462bd9

AppApp IDDomainPolicies
API Biblioteca841d6589-afd8-4418-9b83-a0ac78ae5172workspace.crearack.com/api/biblioteca/Service Token (Service Auth) + Staff Allow
Workspace MCP APIfc31f796-8505-4d82-a334-e42afe171d7bworkspace.crearack.com/api/mcpService Auth (desde s223 — antes Bypass, que NO autentica service tokens) + Staff Allow
DR Backup Service—workspace.crearack.com/api/maintenance/*ELIMINADA en s78 (tumbaba el SSO de la app principal por especificidad de path; su path quedó bajo CreaRackSL Workspace)

Service Token compartido (todo el equipo):

⚠ CF Access reusable policies NO se pueden eliminar via DELETE /apps/{id}/policies/{id} (devuelve can not delete reusable policies through this endpoint). Detach via PUT /apps/{id} con el array policies SIN la policy. Footgun s57.


4. Decisions log

4.1 Bot Fight Mode OFF en crearack.com y channelassistance.com (deuda asumida)

Por qué: Bot Fight Mode básico bloquea User-Agents que parecen automatizados (curl/X.Y, python-requests/X.Y). El cron del Bibliotecario lanza queries con UA crearack-staging-bib-communities/1.0 y otros tipos, y CF respondía con challenge HTML (no JSON 403, sino una página HTML interstitial) → el cron interpretaba el body como respuesta válida pero el JSON parsing fallaba.

Decisión (s57 AM, reconfirmada s204 con el apex ya proxificado): NO activar Bot Fight Mode en las zonas activas — además del cron, rompería el Local Agent y el MCP. Bloqueo del cron/Agent > beneficio anti-bot básico. Block AI bots (también gratis) cubre el caso de scraping IA, que era la preocupación principal.

Alternativa futura: upgrade a Cloudflare Pro y usar Super Bot Fight Mode con WAF rules custom que whiteliste los UAs internos.

4.2 Bot Fight Mode ON en esfericlabs.com

Misma feature, decisión opuesta: cuando era parked no había nada que romper. La landing está EN VIVO desde el 10-07-2026 → si los formularios lead capture dan problemas, revisar aquí primero (desactivar o subir a Pro+Super).

4.3 TLS 1.3 sin 0-RTT en channelassistance.com (cambio del valor por defecto)

Estado por defecto en CF: zrt (TLS 1.3 con 0-RTT habilitado). Cambio s57 PM: on (TLS 1.3 sin 0-RTT).

Por qué: 0-RTT (Zero Round Trip Time resumption) acelera reconexiones HTTPS pero abre ventana de replay attack en endpoints idempotentes. channelassistance es una app con login y formularios POST. La pérdida de latencia al desactivar 0-RTT es marginal (~50-100ms en clientes recurrentes) — el cambio es conservador correcto.

4.4 Security Level “Medium” en channelassistance.com (subido desde “Essentially Off”)

Estado pre-s57: essentially_off. Detectado por auditoría CSV interna. Sin razón aparente que lo justificara (no había crones de fuera contra esa zona). Subido a medium — el default recomendado por CF para apps web. Sin fricción a usuarios normales; solo challenges a IPs de baja reputación.

4.5 esfericlabs.com endurecido antes de tener tráfico

Estado pre-s57: SSL full (no strict), TLS min 1.0, Always HTTPS off, HSTS disabled. Estado post-s57: todo blindado al máximo nivel.

Por qué endurecer un dominio parked: para que cuando se despliegue la landing comercial, no haya que recordar configurar cada item bajo presión de fecha de lanzamiento. Funcionó: la landing salió el 10-07-2026 heredando el hardening, sin errores 526 (sirve vía CF Pages con HTTPS válido).


5. Comandos de verificación

5.1 Estado actual de una zone — todos los settings críticos

$h = @{ Authorization = "Bearer $env:CF_API_TOKEN"; "Content-Type" = "application/json" }
$zone = "<zone-id>"
foreach ($s in @("ssl","min_tls_version","always_use_https","security_header","security_level","tls_1_3")) {
    $r = Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/$s" -Headers $h
    "$s = $($r.result.value | ConvertTo-Json -Compress -Depth 3)"
}

5.2 Verificar HSTS desde fuera (sin token)

curl.exe -I "https://crearack.com" | Select-String -Pattern "strict-transport-security"
# Esperado (desde s224): strict-transport-security: max-age=31536000; includeSubDomains; preload

5.3 Verificar TLS mínimo

# Probar TLS 1.0 (debe fallar)
curl.exe --tls-max 1.0 -v "https://crearack.com" 2>&1 | Select-String -Pattern "tls_protocol_version|handshake"
# Esperado: alguna referencia a tls_protocol_version not_supported / handshake failure

# Probar TLS 1.2 (debe funcionar)
curl.exe --tls-max 1.2 -v "https://crearack.com" 2>&1 | Select-String -Pattern "SSL connection|HTTP/"
# Esperado: SSL connection using TLSv1.2 + HTTP/2 200

5.4 Verificar DNSSEC propagación

nslookup -q=DNSKEY crearack.com 1.1.1.1
# Debe devolver al menos una DNSKEY con flags 256/257

# Alternativa con dig si está instalado:
dig +short DNSKEY crearack.com

5.5 Probar acceso a app CF Access (smoke test Service Token)

# Sin headers — debe dar 403 (tras eliminar bypass-everyone en s57)
curl.exe -s -o nul -w "%{http_code}" "https://workspace.crearack.com/api/mcp"

# Con Service Token — debe dar 200 (o método no permitido si es POST-only, pero NUNCA 403)
curl.exe -s -o nul -w "%{http_code}" `
  -H "CF-Access-Client-Id: $env:CF_ACCESS_CLIENT_ID" `
  -H "CF-Access-Client-Secret: $env:CF_ACCESS_CLIENT_SECRET" `
  "https://workspace.crearack.com/api/mcp"

6. Endpoints API Cloudflare relevantes

AcciónEndpointMétodoToken scope
Listar zones de la cuenta/accounts/{account_id}/... o /zones?per_page=50GETZone:Zone:Read
Leer setting de zone/zones/{zone_id}/settings/{setting}GETZone:Zone Settings:Read
Modificar setting de zone/zones/{zone_id}/settings/{setting}PATCHZone:Zone Settings:Edit
Estado DNSSEC/zones/{zone_id}/dnssecGET / PATCHZone:DNSSEC:Edit (scope dedicado, NO incluido en Zone Settings)
Bot Management/zones/{zone_id}/bot_managementGET / PUTZone:Bot Management:Edit (scope dedicado)
Listar apps CF Access/accounts/{account_id}/access/appsGETAccess:Apps and Policies:Read
Modificar app CF Access/accounts/{account_id}/access/apps/{app_id}PUTAccess:Apps and Policies:Edit
WAF Managed Rules (Block AI bots)/zones/{zone_id}/rulesets/...GET / PATCHZone:Firewall Services:Edit

⚠ Footgun s57: token con scope Zone Settings:Edit NO cubre Bot Management ni DNSSEC — scopes dedicados separados. Para hardening completo via API, crear token con los 3-4 scopes que apliquen. Desde julio existe el token permanente CF_CLAUDE_TOKEN para los huecos del MCP CF (memoria reference_cf_claude_token_gaps — OJO: no cubre Access). Alternativa pragmática: panel manual (1 click por toggle).


7. Footguns aprendidos s57

7.1 Bot Fight Mode rompe crons con UA genérico

Bot Fight Mode básico activado en una zona donde corre un cron que llama via curl/python-requests/etc con UA por defecto → CF devuelve challenge HTML (no 403 JSON), el cron interpreta como respuesta válida pero el parsing JSON falla con Unexpected token <. Mitigación: NO activar en zonas con crons IPs no allowlistadas, o usar Super Bot Fight Mode (Pro+) con reglas custom whitelist.

7.2 claude.ai/connectors no permite headers HTTP custom

El sistema de connectors de Claude.ai web (OAuth) no soporta envío de headers HTTP custom al servidor MCP. Esto significa que un MCP detrás de CF Access NO funciona vía connector de Claude.ai web — solo vía .mcp.json local de Claude Code CLI. Limitación arquitectónica, no parametrizable.

7.3 claude.ai MCP connector toma precedencia sobre .mcp.json local

Si un MCP con el mismo slug está registrado AMBOS como connector en claude.ai web Y en .mcp.json local del repo, el de claude.ai gana. Para forzar el uso del local, eliminar el connector en claude.ai/settings/connectors y reiniciar Claude Code.

7.4 CF API token con resource filter vacío → 403 críptico

Al crear un token en CF, cada permission line tiene su propio “Apply to” (resource filter). Si una line se deja vacía, ese permiso no aplica a nada y los endpoints relacionados devuelven 10000 Authentication error. Rellenar Resources de CADA permission line.

7.5 CF Access reusable policies NO se pueden eliminar via DELETE

DELETE /accounts/{id}/access/apps/{app_id}/policies/{policy_id} devuelve can not delete reusable policies through this endpoint. Solución: detach via PUT /accounts/{id}/access/apps/{app_id} con el array policies SIN la policy a eliminar (también requiere pasar name, domain, type, self_hosted_domains, session_duration).

7.6 wiki_create_page y wiki_update_page schema laxo y silencioso (s57)

Síntoma: pasar parámetros a nivel root del tool call en lugar de anidados en front_matter produce páginas con front-matter mínimo, body vacío, y meta_updates: [] en la respuesta sin avisar del problema.

Schema correcto (functions/api/mcp/tools.ts):

Footgun extra: pasar patch como JSON string crudo a wiki_update_page lo escribe literalmente al .md sin parsing, corrompiendo la página. Detectado al crear estas mismas páginas — la primera versión quedó en JSON crudo de una línea. Fix moderno: usar wiki_update_content (API s91 con body string explícito, la única vía que alinea D1+repo).

Además, sources debe ser {type, ref, last_seen}[], NO string[]. Si se pasan strings sueltos, el commit a GitHub OK pero CF Pages Deploy falla con InvalidContentEntryDataError y bloquea TODOS los deploys posteriores. Detectado en s56. Para arrays vacíos [] está OK.


8. Mini-runbook · replicar hardening a un dominio nuevo

Cuando se añada un cuarto dominio a la cuenta CF (ejemplo: una sub-brand nueva):

8.1 Pre-requisito · CF API token

Crear en https://dash.cloudflare.com/profile/api-tokens con scopes:

Resources: Include All zones from <cuenta>. TTL: 1h (auto-revoca). (O usar el permanente CF_CLAUDE_TOKEN si sus scopes cubren.)

8.2 Settings vía API

$h = @{ Authorization = "Bearer $env:CF_API_TOKEN"; "Content-Type" = "application/json" }
$zone = "<nuevo-zone-id>"  # obtener con GET /zones

# SSL Full Strict
Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/ssl" -Method PATCH -Headers $h -Body '{"value":"strict"}'

# TLS min 1.2
Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/min_tls_version" -Method PATCH -Headers $h -Body '{"value":"1.2"}'

# Always HTTPS on
Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/always_use_https" -Method PATCH -Headers $h -Body '{"value":"on"}'

# TLS 1.3 sin 0-RTT
Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/tls_1_3" -Method PATCH -Headers $h -Body '{"value":"on"}'

# Security Level medium
Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/security_level" -Method PATCH -Headers $h -Body '{"value":"medium"}'

# HSTS (max_age 1 año, include_subdomains, nosniff)
$hsts = @{
  value = @{
    strict_transport_security = @{
      enabled = $true
      max_age = 31536000
      include_subdomains = $true
      preload = $false
      nosniff = $true
    }
  }
} | ConvertTo-Json -Depth 5 -Compress
Invoke-RestMethod -Uri "https://api.cloudflare.com/client/v4/zones/$zone/settings/security_header" -Method PATCH -Headers $h -Body $hsts

8.3 Settings manuales (panel CF)

8.4 Verificación post-hardening

Ejecutar sección 5 de esta página contra el nuevo dominio. Esperar 24h para confirmar DNSSEC propagado.

8.5 Documentación


9. Pendientes / verificaciones diferidas


Véase también

Subir