Skip to content

Cloudflare Access — apps protegidas

Estado a 2026-08-01 (re-verificado vía CF API, ver recipe abajo). Conviven dos planos de identidad: CF Access (edge) con PocketID como Identity Provider, y PocketID OIDC nativo per-app. El conteo real es 51 apps CF Access self_hosted (subió a 60 desde 54 el 2026-07-19 — se añadieron dj, reorg, rol y un bypass music.monxas.casa/share, más el registro interno monxas.cloudflareaccess.com del propio IdP callback — bajó a 59 el 2026-08-01 al retirar piso, y a 51 el mismo día al retirar las 8 apps señuelo del honeypot Krawl).

Apps protegidas (CF Access — 51 apps self_hosted, verificado 2026-08-01)

Policy default todos: allow { [email protected] OR email_domain=monxas.com }. Sesión 24h.

Enumeración completa — re-verificada 2026-08-01 (51 apps tras retirar piso + Krawl)

Auto-generada

Lista viva vía CF API. Para regenerar: ver API recipes. Policy default de las protegidas: allow [email protected] OR email_domain=monxas.com, sesión 24h.

Protegidas (47) — a nivel dominio:

*-preview, airdrop, apis, audio, bazarr, changedetection, code, deploy-trip-dev, deploy-trip, deploy, dj, dockge, dozzle, esp, etmf, finanzas, grafana, japon, jellyseerr, jira, job-hunter, lidarr, md-api, md-demo, md, music, n8n, nas, nopor, ntfy, ocr, okrs, pdf, pinchflat, prowlarr, radarr, reddit, reorg, rol, slicer, slskd, sonarr, soularr, synology, vera, wp-pulse-app.

dj = "DJ (altavoces casa)" (dj.monxas.casa, ver dj_monxas_casa_cf_access.md). reorg = music-reorg-ui (beets). rol = SillyTavern.

piso (Piso Colón) resuelto 2026-08-01: confirmado decomisionado desde 2026-07-26 (compose en stacks-disabled/piso-retired-20260726). App de CF Access y registro DNS borrados. Queda pendiente borrar a mano el cliente OIDC "Piso Colón" en PocketID (sin API key/sqlite accesible para automatizarlo).

Honeypot Krawl resuelto 2026-08-01: admin, cpanel, dashboard, login, panel, phpmyadmin, webmail, wp-admin eran los 8 subdominios señuelo del honeypot Krawl (projects/homelab-stacks/infra.md), decomisionado sin fecha documentada. Confirmado que ninguno aparecía en las rutas "Desired" de homelab-ctl.py status — no protegían nada real. Borradas las 8 apps de CF Access y sus 8 registros DNS.

⚠️ TODO sin verificar (resto): deploy-trip*, etmf, md*, reddit, slicer, vera, wp-pulse-app tampoco aparecen en las rutas "Desired" de homelab-ctl.py status — puede ser que vivan en un Caddyfile distinto al que gestiona esa herramienta (no confirmado). A diferencia de piso/Krawl, no se encontró evidencia directa (como un stacks-disabled/) de que estas estén realmente decomisionadas — no tocadas.

Bypass (3) — path-specific (API/webhooks, sin login):

  • music.monxas.casa/rest — music-api-bypass
  • music.monxas.casa/share — music-share-bypass (nuevo, no documentado antes de esta auditoría)
  • n8n.monxas.casa/webhook — n8n-webhook-bypass

Protegidas (1) — path-specific dentro de un dominio SIN Access (2026-07-25):

  • home.monxas.casa/local/tablet — tablet-local-protect (ver gotcha 5 y sección "Añadidas 2026-07-25")

Otro (1): monxas.cloudflareaccess.com — registro interno del callback del IdP (Cloudflare Access como cliente OIDC de PocketID), no es una "app" en el sentido normal.

Apps deliberately SIN CF Access

Auth propio fuerte (no need perimeter)

  • home.monxas.casa, matematico.monxas.casa — Home Assistant (dominio raíz; excepción: /local/tablet sí tiene CF Access desde 2026-07-25, ver más abajo)
  • nas.monxas.casa — Synology
  • jellyfin.monxas.casa — Jellyfin (Stash sigue vivo, nopor.monxas.casa, en on-demand como el resto del stack — no decomisionado, ver projects/homelab-stacks/tools.md; 9999 es Dozzle)

OIDC nativo ya integrado (PocketID)

Integrados vía PocketID OIDC per-app (login propio de cada app, no gateados por CF Access): - grafana.monxas.casa — Grafana - pass.monxas.casa — Vaultwarden - bookmarks.monxas.casa — Karakeep - pics.monxas.casa — Immich - podcasts.monxas.casa — Audiobookshelf - paperless.monxas.casa — Paperless

Público / bearer-only / IdP itself

  • pocketid.monxas.casaNUNCA detrás de CF Access (es el IdP)
  • monxas.casa — apex landing
  • cors.monxas.casa — utility público (rate-limited)
  • kiwix.monxas.casa — read-only KB
  • rag.monxas.casa, loki.monxas.casa — APIs con bearer auth

Webhook bypass (special policies)

  • n8n.monxas.casa/webhook — bypass everyone (specific path)
  • music.monxas.casa/rest — bypass everyone (Subsonic API para Amperfy; UI music.monxas.casa protegida). Ver Crear un subdominio.
  • deploy.monxas.casa, deploy-trip*.monxas.casa — CI/CD webhooks (review caso a caso)

API recipes

Automatizado desde 2026-08-07: scripts/add-service.sh <host> <backend-ip:puerto> hace el ciclo completo (tunnel ingress + esta Access app/policy + bloque Caddy en ambos nodos HA + reload + smoke test), leyendo credenciales de secrets/cloudflare.sops.yaml y resolviendo el IdP PocketID vía API. Ver Crear un subdominio. Las recetas manuales de abajo siguen siendo válidas para casos sueltos o depuración.

# Listar apps + policies
source ~/.env  # CF_API_TOKEN
ACC=0e70e9f3f8ffabbdcf9f46b0307a074e
curl -sH "Authorization: Bearer $CF_API_TOKEN" \
  "https://api.cloudflare.com/client/v4/accounts/$ACC/access/apps?per_page=100" | \
  jq '.result[] | {domain, name}'

# Proteger nueva app (crea + policy default)
curl -X POST -H "Authorization: Bearer $CF_API_TOKEN" -H 'Content-Type: application/json' \
  --data '{"name":"X","domain":"x.monxas.casa","type":"self_hosted","session_duration":"24h","app_launcher_visible":false}' \
  "https://api.cloudflare.com/client/v4/accounts/$ACC/access/apps"
# luego copiar app_id de la respuesta y:
curl -X POST -H "Authorization: Bearer $CF_API_TOKEN" -H 'Content-Type: application/json' \
  --data '{"name":"default","decision":"allow","include":[{"email":{"email":"[email protected]"}},{"email_domain":{"domain":"monxas.com"}}]}' \
  "https://api.cloudflare.com/client/v4/accounts/$ACC/access/apps/<APP_ID>/policies"

# Verify external (302 = CF Access OK)
curl -s -o /dev/null -w "%{http_code}\n" https://x.monxas.casa/

Identity Provider

PocketID está configurado como OIDC IdP en CF Zero Trust. El cliente Cloudflare Access existe en PocketID DB con callback https://monxas.cloudflareaccess.com/cdn-cgi/access/callback.

PocketID tiene 11 clientes OIDC registrados: Cloudflare Access, Vaultwarden, Karakeep, Audiobookshelf, Immich, Paperless, Grafana, Remote-Pulse Dashboard, wp-pulse, wp-pulse-app, Piso Colón. El cliente Cloudflare Access es el plano edge; el resto son integraciones OIDC nativas per-app. ⚠️ Piso Colón es un cliente huérfano pendiente de borrar a mano (el servicio se decomisionó el 2026-07-26; CF Access y DNS ya limpiados el 2026-08-01, falta solo este cliente en la UI de PocketID).

Cuando hay sesión válida, CF Access pasa el request al origin. Cuando no, redirige a PocketID. Una vez autenticado, regresa a la app. Sesión 24h shared entre todas las apps.

Añadidas 2026-06-17 (1)

wp-pulse-app (operator UI) — policy allow SOLO [email protected] + [email protected] (no email_domain=monxas.com, porque folele es gmail). App f9213006-…, policy 3399346e-….

Por qué: el operator UI estaba SIN gate real — el Caddy forward_auth apuntaba a pocketid.monxas.casa/forward-auth, endpoint que PocketID no tiene (devuelve la SPA con HTTP 200 para cualquier path/client_id), así que Caddy lo leía como "allow" y dejaba pasar a todos. El backend, sin X-Forwarded-Email, caía al SUPERADMIN fallback → cualquiera en internet = admin total sobre todos los sites. Cerrado moviendo el gate a CF Access + Caddy ahora mapea Cf-Access-Authenticated-User-EmailX-Forwarded-Email para resolución de rol per-user.

Añadidas 2026-07-25 (1)

tablet-local-protecthome.monxas.casa/local/tablet, policy allow ([email protected] OR email_domain=monxas.com). App 38089e97-6e30-4e6b-ab63-eb5334c208c6, policy f9087407-1c09-4eef-a4da-c3f8331791ae.

Por qué: /local/ en Home Assistant sirve estático sin ninguna auth, por diseño de HA — es un detalle de la plataforma, no de home.monxas.casa en particular. El dashboard de la tablet familiar (repo tablet-f1, deployado en /config/www/tablet/) guarda secretos reales en js/config.local.js (HA_TOKEN de larga duración + credenciales de Navidrome) — ese archivo era descargable por cualquiera en internet que conociera la ruta, sin ningún login, verificado con curl externo (200 antes del fix). Se cerró creando una CF Access app scoped al path /local/tablet (no al dominio completo, que sigue con su auth nativa de HA para todo lo demás).

Por qué no rompe la tablet física en casa: monxas.casa tiene DNS split-horizon en Pi-hole (/etc/dnsmasq.d/05-local-homelab.conf: address=/monxas.casa/192.168.0.250) — cualquier dispositivo en la LAN (incluida la tablet) resuelve directo a la VIP de Caddy, sin pasar por el tunnel de Cloudflare ni por Access en absoluto. Solo un cliente que resuelva por DNS público (i.e. desde fuera de casa) atraviesa el tunnel y ve el gate de CF Access. Verificado: curl https://home.monxas.casa/local/tablet/js/config.local.js → 302 (protegido) mientras curl https://home.monxas.casa/ → 200 (resto de HA intacto).

Gotchas

  1. PocketID NO tiene /forward-auth — es un OIDC IdP, no un forward-auth proxy. No uses forward_auth pocketid.monxas.casa en Caddy (devuelve 200 SPA = allow-all silencioso). Gatea con CF Access (PocketID como IdP) o un proxy real (oauth2-proxy/tinyauth) con PocketID OIDC detrás.
  2. App sin policy = BLOQUEA TODO. Crear app y policy en transacción rápida o usar CF dashboard que lo hace junto.
  3. Webhooks/APIs externos: requieren bypass específico por path. Ver patrón n8n/webhook.
  4. Móviles/CLIs (LunaSea, etc.): no soportan OIDC interactivo. Usar Service Tokens (CF API).
  5. Subdomain debe estar en tunnel ingress antes de crear CF Access app, si no, queda inaccesible.
  6. /local/ de Home Assistant sirve estático SIN auth, siempre (diseño de HA, no de la app que viva ahí) — cualquier secreto en JS servido bajo home.monxas.casa/local/... es público hasta que se gatee explícitamente con una CF Access app scoped a ese path (ver "Añadidas 2026-07-25"). Antes de meter cualquier token nuevo en un dashboard bajo /local/, comprobar primero si ese path concreto ya tiene su propia Access app — el dominio raíz de HA no lo cubre.