From 5e220591e70e8a5eb5ae329d9f20d11f7c5618ba Mon Sep 17 00:00:00 2001 From: Eduardo David Paredes Vara Date: Mon, 25 May 2026 05:47:11 +0000 Subject: [PATCH] =?UTF-8?q?chore:=20actualizar=20meta=20del=20repo=20y=20c?= =?UTF-8?q?onfiguraci=C3=B3n=20base?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - .gitignore: añadir reglas para .env, *.live.yaml, .codex, .vscode, certs - README.md y COOLIFY-TEMPLATE.md: actualizados con nuevos stacks - NUEVA-APP.md: guía para añadir nuevas apps al homelab - PROMPT-SSO-ISSUE.md: contexto de debugging SSO/Authentik - .github/copilot-instructions.md: instrucciones de arquitectura para Copilot - docker-compose.yml raíz: ajustes al stack de Portainer Co-Authored-By: Claude Sonnet 4.6 --- .github/copilot-instructions.md | 37 ++++++ .gitignore | 22 ++++ COOLIFY-TEMPLATE.md | 2 +- NUEVA-APP.md | 201 ++++++++++++++++++++++++++++++++ PROMPT-SSO-ISSUE.md | 191 ++++++++++++++++++++++++++++++ README.md | 17 ++- docker-compose.yml | 24 +--- 7 files changed, 469 insertions(+), 25 deletions(-) create mode 100644 .github/copilot-instructions.md create mode 100644 .gitignore create mode 100644 NUEVA-APP.md create mode 100644 PROMPT-SSO-ISSUE.md diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..42a9c6d --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,37 @@ +# Instrucciones para Copilot + +## Comandos de build, test y lint + +- Este repositorio no tiene un build, lint o suite de tests centralizados; la validacion principal se hace con `docker compose config` por stack. +- Validar el stack raiz de Portainer: `docker compose -f docker-compose.yml config` +- Validar la variante inicial de bootstrap por 9443: `docker compose -f docker-compose.yml -f docker-compose.9443.yml config` +- Levantar el bootstrap inicial de Portainer: `docker compose -f docker-compose.yml -f docker-compose.9443.yml up -d` +- Validar un stack individual usando su plantilla versionada de variables: `docker compose --env-file /stack.env -f /docker-compose.yml config` +- Levantar un stack individual en local con su plantilla de variables: `docker compose --env-file /stack.env -f /docker-compose.yml up -d` +- Los unicos scripts ejecutables del repo estan en `n8n-observability/`: + - Validar la sintaxis de un script: `bash -n n8n-observability/scripts/server-summary.sh` + - Ejecutar un script: `./n8n-observability/scripts/server-summary.sh` + - Ejecutar el digest de logs para una ventana concreta: `SINCE="24 hours ago" ./n8n-observability/scripts/log-digest.sh` + +## Arquitectura de alto nivel + +- El repositorio agrupa varios stacks Docker Compose pensados para desplegarse desde Portainer. El orden base es: stack raiz de Portainer, despues `Traefik/`, luego `authentik/` y despues el resto de stacks de aplicaciones. +- El `docker-compose.yml` de la raiz define Portainer detras de Traefik. Separa la UI autenticada del host de API/app movil, protegido con una IP allowlist. `docker-compose.9443.yml` no es un stack independiente: es solo un override para anadir el puerto de bootstrap y debe combinarse con el compose raiz. +- `Traefik/` es la capa compartida de entrada. Mantiene la red externa `proxy`, el estado de ACME/Let's Encrypt y la configuracion dinamica basada en ficheros bajo `/opt/traefik/dynamic`. +- `authentik/` es la capa compartida de SSO. Publica el middleware reutilizable de Traefik `ths-authentik@docker` y la ruta del outpost bajo `/outpost.goauthentik.io/`; los demas stacks consumen ese middleware en vez de redefinir su propia integracion de auth. +- Cada carpeta de stack es autocontenida: incluye su propio `docker-compose.yml`, una plantilla `stack.env` versionada y, normalmente, un README con notas de despliegue y operacion. +- La mayoria de stacks de aplicacion siguen el mismo patron de red: una red bridge privada para trafico app/base de datos/cache, mas la red externa `proxy` solo para los servicios publicados por Traefik. +- `mail-relay/` es infraestructura compartida para SMTP saliente. Los stacks que necesitan correo se conectan a la red `mail_internal` y usan `mail-relay` como host SMTP. +- Hay integraciones cruzadas deliberadas entre stacks: Gitea incluye un contenedor runner para Actions, Nextcloud monta `/opt/paperless/media` en solo lectura y Paperless sincroniza su bandeja de entrada desde Nextcloud WebDAV mediante `paperless-inbox-sync`. + +## Convenciones clave + +- `stack.env` es la plantilla versionada de configuracion de cada stack. La documentacion de despliegue indica de forma consistente copiar `stack.env` a `.env` o cargarlo directamente como archivo de entorno; al anadir variables nuevas, actualiza `stack.env` y respeta los nombres ya existentes. +- Los datos persistentes del host se guardan en bind mounts bajo `/opt//...`, no dentro del arbol del repositorio. Las rutas nuevas deben seguir esa convencion. +- Los bind mounts usan de forma consistente sufijos de relabel SELinux como `:Z`. No los elimines salvo que el stack deje de usar bind mounts del host de forma intencionada. +- Los servicios expuestos por HTTP deben unirse a la red `proxy` y definir labels de Traefik para router y service, especialmente `traefik.http.services..loadbalancer.server.port`. +- Los servicios solo internos se mantienen fuera de `proxy` y normalmente no llevan labels de Traefik. Solo anade `mail_internal` cuando el servicio necesite llegar al relay SMTP. +- El nombre del middleware compartido de Authentik es `ths-authentik@docker`. Reutilizalo desde las labels de los routers en vez de crear variantes por stack, salvo que el stack necesite un comportamiento distinto de verdad. +- El stack raiz de Portainer mantiene de forma intencionada routers de Traefik separados para la UI, el acceso directo a la API y dominios adicionales. Conserva esa separacion cuando cambies el enrutado raiz. +- La documentacion del repositorio esta mayoritariamente en espanol. Mantén ese estilo al editar READMEs o documentacion de servicios. +- Cuando cambies un stack, lee siempre su README junto con `docker-compose.yml` y `stack.env`; muchos README contienen restricciones operativas que no se deducen solo del compose. diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..70b9fa1 --- /dev/null +++ b/.gitignore @@ -0,0 +1,22 @@ +# Archivos de entorno con secretos reales — usar stack.env como plantilla +.env +**/.env + +# Configuraciones live con datos reales de producción +*.live.yaml + +# Backups y temporales +*.bak +*.tmp + +# Archivos de auth del CLI de Codex/OpenAI +.codex + +# Secretos de VSCode / editores +.vscode/settings.json + +# Otros archivos de secretos comunes +*.pem +*.key +*.p12 +*.pfx diff --git a/COOLIFY-TEMPLATE.md b/COOLIFY-TEMPLATE.md index 3aa3280..bef790a 100644 --- a/COOLIFY-TEMPLATE.md +++ b/COOLIFY-TEMPLATE.md @@ -168,7 +168,7 @@ docker network create mail_internal 1. **Ports Exposes**: set to the app's HTTP port (must match `loadbalancer.server.port` label) 2. **Domain**: set FQDN (e.g. `myapp.sherlockhomeless.net`) 3. **Base Directory**: set to the subdirectory (e.g. `/gitea`, `/n8n`) -4. **Environment Variables**: fill from `stack.env` template +4. **Environment Variables**: fill from `.env` (copy values from `stack.env` template if needed) --- diff --git a/NUEVA-APP.md b/NUEVA-APP.md new file mode 100644 index 0000000..7e106f0 --- /dev/null +++ b/NUEVA-APP.md @@ -0,0 +1,201 @@ +# Checklist: Añadir una nueva app + +Cada vez que levantes un nuevo servicio que deba ser accesible desde internet, sigue estos pasos. + +--- + +## 1. Registrar el dominio (DNS + Tunnel) + +```bash +cd cloudflared +./add-domain.sh +``` + +Esto hace en un solo comando: +- Crea el registro DNS `.sherlockhomeless.net` → Cloudflare Tunnel (CNAME con proxy naranja) +- Añade el hostname al ingress del tunnel → `http://traefik:80` + +Espera ~30 segundos para que Cloudflare propague el DNS. + +--- + +## 2. Labels de Traefik en el docker-compose.yml + +Añade estas labels al servicio que quieres exponer. Sustituye `miapp` por el nombre corto del servicio +y `${MI_DOMINIO}` por la variable de entorno del dominio (ej. `${MIAPP_DOMAIN}`). + +### Caso A — App pública con CrowdSec y sin login (APIs, webhooks) + +```yaml +labels: + traefik.enable: "true" + traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" + traefik.http.routers.miapp.rule: "Host(`${MI_DOMINIO}`)" + traefik.http.routers.miapp.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" + traefik.http.routers.miapp.tls: "true" + traefik.http.routers.miapp.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" + traefik.http.routers.miapp.middlewares: "crowdsec-bouncer@file" + traefik.http.services.miapp.loadbalancer.server.port: "XXXX" +``` + +### Caso B — App privada con CrowdSec + Authentik SSO (panel de admin, dashboards) + +```yaml +labels: + traefik.enable: "true" + traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" + traefik.http.routers.miapp.rule: "Host(`${MI_DOMINIO}`)" + traefik.http.routers.miapp.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" + traefik.http.routers.miapp.tls: "true" + traefik.http.routers.miapp.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" + traefik.http.routers.miapp.middlewares: "crowdsec-bouncer@file,${TRAEFIK_AUTH_MIDDLEWARE}" + traefik.http.services.miapp.loadbalancer.server.port: "XXXX" +``` + +> El orden importa: CrowdSec bloquea primero, luego Authentik autentica. +> `${TRAEFIK_AUTH_MIDDLEWARE}` se resuelve a `ths-authentik@docker`. + +### Caso C — App con su propio login (no necesita Authentik) + +```yaml +labels: + traefik.enable: "true" + traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}" + traefik.http.routers.miapp.rule: "Host(`${MI_DOMINIO}`)" + traefik.http.routers.miapp.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}" + traefik.http.routers.miapp.tls: "true" + traefik.http.routers.miapp.tls.certresolver: "${TRAEFIK_CERTRESOLVER}" + traefik.http.routers.miapp.middlewares: "crowdsec-bouncer@file" + traefik.http.services.miapp.loadbalancer.server.port: "XXXX" +``` + +Igual que el Caso A — CrowdSec siempre, Authentik solo cuando la app no tiene auth propia. + +--- + +## 3. Variables de entorno obligatorias en stack.env / .env + +Incluye siempre estas variables en el `stack.env` de la nueva app para que las labels funcionen: + +```env +##### Traefik / dominios ##### +TRAEFIK_DOCKER_NETWORK=proxy +MIAPP_DOMAIN=miapp.sherlockhomeless.net +TRAEFIK_ENTRYPOINT_SECURE=websecure +TRAEFIK_CERTRESOLVER=letsencrypt +TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker # solo si usas Caso B +``` + +> **Gotcha conocido**: Traefik NO expande variables de entorno dentro de labels. +> Los valores como puertos y dominios que van dentro de `Host(...)` deben estar +> ya resueltos cuando docker compose procesa el archivo. Usa variables de entorno +> solo donde docker compose las expande (fuera de las comillas de las labels si es un valor directo). + +--- + +## 4. Configurar Authentik (solo Caso B) + +Si usas `ths-authentik@docker`, Authentik debe conocer la app para que el outpost la sirva correctamente. + +### En el panel de Authentik (`https://auth.sherlockhomeless.net`): + +1. **Crear Provider** → Applications → Providers → Create + - Tipo: **Proxy Provider** + - Name: `Mi App` + - External host: `https://miapp.sherlockhomeless.net` + - Authentication flow: `default-authentication-flow` + - Authorization flow: `default-provider-authorization-implicit-consent` + - Mode: **Forward auth (single application)** + +2. **Crear Application** → Applications → Create + - Name: `Mi App` + - Slug: `miapp` + - Provider: el creado en el paso anterior + +3. **Asociar al Embedded Outpost** → Outposts → `authentik Embedded Outpost` → Edit + - Añade la nueva aplicación a la lista de aplicaciones seleccionadas + - Guarda + +> Si no completas el paso 3, Authentik redirige al login pero luego da error 404. + +--- + +## 5. Red Docker + +El contenedor debe estar en la red `proxy` para que Traefik lo pueda alcanzar: + +```yaml +networks: + proxy: + external: true + # red interna opcional para bases de datos, etc. + miapp_internal: + driver: bridge +``` + +Y el servicio que expone la app debe tener: + +```yaml +services: + miapp: + networks: + - proxy + - miapp_internal # si tiene db +``` + +Los contenedores de base de datos u otros servicios internos solo necesitan la red interna, +**no** la red `proxy`. + +--- + +## 6. Desplegar + +```bash +cd miapp +docker compose --env-file .env up -d +``` + +--- + +## Resumen visual + +``` +Nueva app + │ + ├── 1. ./cloudflared/add-domain.sh → DNS + Tunnel + │ + ├── 2. Labels en docker-compose.yml + │ ├── Caso A (pública) → crowdsec-bouncer@file + │ ├── Caso B (privada) → crowdsec-bouncer@file + ths-authentik@docker + │ └── Caso C (auth propia)→ crowdsec-bouncer@file + │ + ├── 3. Variables en stack.env / .env + │ + ├── 4. Authentik Provider + App + Outpost (solo Caso B) + │ + ├── 5. Red proxy en docker-compose.yml + │ + └── 6. docker compose --env-file .env up -d +``` + +--- + +## Middlewares de referencia + +| Middleware | Definido en | Qué hace | +|---|---|---| +| `crowdsec-bouncer@file` | `/opt/traefik/dynamic/crowdsec-bouncer.yml` | Bloquea IPs baneadas por CrowdSec y filtra con AppSec WAF | +| `ths-authentik@docker` | Labels del stack `authentik` | ForwardAuth hacia Authentik — requiere login SSO | + +--- + +## Gotchas conocidos + +| # | Problema | Causa | Solución | +|---|----------|-------|----------| +| 1 | Traefik no enruta la app | `traefik.enable: "true"` falta | Añadir el label | +| 2 | Traefik no encuentra el contenedor | Contenedor no está en la red `proxy` | Añadir red proxy al servicio | +| 3 | SSL no funciona | `tls.certresolver` mal escrito o dominio no existe en DNS | Verificar DNS con `dig miapp.sherlockhomeless.net` | +| 4 | Authentik redirige pero da 404 | App no añadida al Embedded Outpost | Paso 4.3 | +| 5 | CrowdSec da 403 | IP baneada | `docker exec crowdsec cscli decisions list` — si apareces, `cscli decisions delete --ip ` | +| 6 | Variable no expandida en label | Traefik no expande vars en labels de docker | Hardcodear el valor o usar la variable en stack.env | diff --git a/PROMPT-SSO-ISSUE.md b/PROMPT-SSO-ISSUE.md new file mode 100644 index 0000000..c1b2fe1 --- /dev/null +++ b/PROMPT-SSO-ISSUE.md @@ -0,0 +1,191 @@ +# Prompt: SSO OIDC + Forward Auth — Diagnostico y alternativas + +## Contexto + +Proyecto en `/home/felidae/Portainer_repo/`. Infraestructura Docker Compose con: +- **Traefik v3.6.13** como reverse proxy (red `proxy`) +- **Authentik** como SSO via Forward Auth (`ths-authentik@docker`) +- **CrowdSec** como IPS/IDS via plugin Traefik (`crowdsec-bouncer@file`) +- **Homepage** como dashboard (descubre labels Docker) + +Se han levantado dos nuevos servicios que necesitan SSO con Authentik + +enlaces compartidos publicos (sin login): + +| Servicio | Dominio | Imagen | +|----------|---------|--------| +| **OpenGist** | `opengist.sherlockhomeless.net` | `ghcr.io/thomiceli/opengist:latest` (puerto 6157) | +| **OneTimeSecret** | `ots.sherlockhomeless.net` | `onetimesecret/onetimesecret:latest` (puerto 3000, necesita Redis) | + +--- + +## Que funciona + +1. **DNS**: registros A creados con `cloudflared/add-domain.sh` apuntando a `193.70.84.224` +2. **Traefik**: rutas TLS con Let's Encrypt para ambos dominios +3. **CrowdSec**: bouncer activo, bloqueando IPs en todos los routers +4. **Forward Auth**: redirige a Authentik al visitar las URLs raiz (302 a `/application/o/authorize/`) +5. **Enlaces compartidos publicos**: las rutas de gist (`/{user}/{gist}`) y secretos (`/secret/*`, `/private/*`) evitan Authentik correctamente +6. **Callbacks OIDC**: las rutas `/auth/*` (OTS) y `/oauth/*` (OpenGist) evitan Authentik, llegan al backend +7. **Homepage**: labels Docker puestos, descubre ambos servicios + +### Routers Traefik configurados + +**OpenGist** (`opengist/docker-compose.yml`): +``` +Router 1 (prio 20, SIN Authentik): + Host(opengist.sherlockhomeless.net) && (PathRegexp(^/[a-z...]+/[a-z0-9]+) || PathPrefix(/oauth/)) + → crowdsec-bouncer@file + +Router 2 (prio 10, CON Authentik): + Host(opengist.sherlockhomeless.net) + → crowdsec-bouncer@file, ths-authentik@docker +``` + +**OTS** (`onetimesecret/docker-compose.yml`): +``` +Router 1 (prio 20, SIN Authentik): + Host(ots.sherlockhomeless.net) && (PathPrefix(/secret/) || PathPrefix(/private/) || PathPrefix(/auth/)) + → crowdsec-bouncer@file + +Router 2 (prio 10, CON Authentik): + Host(ots.sherlockhomeless.net) + → crowdsec-bouncer@file, ths-authentik@docker +``` + +--- + +## Donde se rompe + +### Problema principal + +El usuario visita la URL, Authentik pide login (Forward Auth funciona), pero +tras autenticarse en Authentik **la app no reconoce al usuario como logueado**. +La app muestra su propio formulario/boton de login. + +La causa raiz es la diferencia entre Forward Auth y reconocimiento de sesion +dentro de la app: + +- **Forward Auth**: protege la RUTA a nivel HTTP. Authentik verifica si la + peticion tiene sesion valida. Si no, redirige al login. Si si, deja pasar + la peticion y añade headers (`X-Authentik-Username`, etc.). +- **La app**: tiene su propio sistema de sesiones. No sabe leer los headers + `X-Authentik-*`. Necesita su propio flujo OIDC/OAuth para crear una sesion + interna. + +Para cerrar ese gap se configuro OIDC en ambos servicios apuntando a Authentik: + +| App | OIDC Provider (Authentik) | Redirect URI | Config en la app | +|-----|--------------------------|--------------|------------------| +| OpenGist | pk=45, client_id=`MZb3LrTP...` | `/oauth/callback` | `OG_OIDC_CLIENT_ID`, `OG_OIDC_CLIENT_SECRET`, `OG_OIDC_ISSUER` | +| OTS | pk=46, client_id=`Bqd0erWf...` | `/auth/oidc/callback` | `OIDC_CLIENT_ID`, `OIDC_CLIENT_SECRET`, `OIDC_ISSUER`, `AUTH_SSO_ENABLED=true`, `AUTHENTICATION_MODE=full`, `AUTH_SECRET=...` | + +### Sintoma actual reportado por el usuario + +En `https://ots.sherlockhomeless.net/signin` aparece el boton "Sign in with +Authentik". Al hacer clic estando ya logueado en Authentik, **no cambia nada**, +sigue pidiendo sign in. El flujo OIDC aparentemente no completa la creacion +de sesion en OTS. + +Posibles causas: +1. La cookie de sesion de Authentik no se envia en el callback (problema de + dominio/cookie SameSite) +2. El callback OIDC falla en OTS (error de configuracion Rodauth/OmniAuth) +3. Forward Auth interfiere en alguna parte del flujo (aunque las rutas de + callback ya se excluyeron) +4. La sesion de Authentik no persiste entre Forward Auth y OIDC + +### Otros detalles tecnicos + +- **Authentik**: el dominio es `auth.sherlockhomeless.net`. Los providers OIDC + usan issuer por provider (`issuer_mode: per_provider`). + Issuer OpenGist: `https://auth.sherlockhomeless.net/application/o/MZb3LrTP.../` + Issuer OTS: `https://auth.sherlockhomeless.net/application/o/Bqd0erWf.../` + +- **OTS healthcheck**: la imagen oficial tiene un healthcheck built-in + (`bin/healthcheck.sh`) que falla en nuestro entorno. Se deshabilito con + `healthcheck: { disable: true }` porque Traefik se niega a rutear + contenedores unhealthy. + +- **OTS full auth**: requiere `AUTH_SECRET` (valor generado con `openssl rand -hex 48`), + SQLite en `/app/data/auth.db` (montado en `/opt/onetimesecret/data:Z`, uid 1001), + y Redis para los datos de la app. + +- **OpenGist**: usa `OG_OPENGIST_HOME=/opengist` con SQLite interno. No + requiere base de datos externa. + +--- + +## Archivos clave + +``` +Portainer_repo/ +├── opengist/ +│ ├── docker-compose.yml +│ ├── .env +│ └── stack.env +├── onetimesecret/ +│ ├── docker-compose.yml +│ ├── .env +│ └── stack.env +├── authentik/ +│ ├── docker-compose.yml +│ ├── create-auth-app.sh +│ └── .env +├── Traefik/docker-compose.yml +├── crowdsec/docker-compose.yml +├── cloudflared/ +│ ├── add-domain.sh +│ └── .env +└── NUEVA-APP.md (guia de despliegue de nuevas apps) +``` + +### Credenciales OIDC en Authentik + +Obtenibles via API con token generado desde `docker exec ths-authentik-server`: + +```bash +# OpenGist OIDC provider (pk=45) +curl -sk -H "Authorization: Bearer $TOKEN" \ + https://auth.sherlockhomeless.net/api/v3/providers/oauth2/45/ + +# OTS OIDC provider (pk=46) +curl -sk -H "Authorization: Bearer $TOKEN" \ + https://auth.sherlockhomeless.net/api/v3/providers/oauth2/46/ +``` + +--- + +## Objetivo para el siguiente agente + +Conseguir que el flujo SSO funcione de extremo a extremo: +1. Usuario visita `https://opengist.sherlockhomeless.net/` o `https://ots.sherlockhomeless.net/` +2. Authentik muestra su pagina de login (Forward Auth) — esto YA funciona +3. Usuario se autentica en Authentik +4. Al volver a la app, el usuario aparece **logueado automaticamente** + (la app reconoce al usuario via OIDC sin requerir clic extra) +5. Los enlaces compartidos (`/{user}/{gist}`, `/secret/*`) siguen siendo + publicos (sin login) — esto YA funciona + +### Ideas a explorar + +- **Alternativa A — Quitar Forward Auth, usar solo OIDC**: si las apps + soportan auto-redirect al provider OIDC cuando no hay sesion, se elimina + la doble autenticacion. El login seria "clic en boton" en vez de + "automatico al entrar", pero el SSO seria mas limpio. + +- **Alternativa B — Authentik en modo proxy (no forward auth)**: configurar + Authentik como reverse proxy real en vez de forward auth. Authentik + manejaria todo el flujo OIDC y pasaria el usuario autenticado al backend. + +- **Alternativa C — Header auth nativo**: investigar si OpenGist u OTS + soportan autenticacion por headers de proxy inverso (tipo `Remote-User`). + Si es asi, se puede usar solo Forward Auth sin OIDC, pasando el header + `X-Authentik-Username` como `Remote-User`. + +- **Alternativa D — Autentik OAuth2 en vez de OIDC**: probar si el flujo + OAuth2 simple (sin OpenID Connect) funciona mejor con estas apps. + +- **Alternativa E — Debug del flujo OIDC actual**: revisar logs de OTS + (`docker logs onetimesecret`) y de Authentik durante el flujo OIDC para + identificar exactamente donde falla el callback. Puede ser un problema + de SameSite cookie, CORS, o redirect_uri mismatch. diff --git a/README.md b/README.md index 1ab81fa..331d908 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ Este repositorio contiene la configuración de Portainer y múltiples stacks de - [Stacks Disponibles](#stacks-disponibles) - [Configuración](#configuración) - [Uso](#uso) +- [**→ Añadir una nueva app**](NUEVA-APP.md) ## 📖 Descripción @@ -27,7 +28,8 @@ La arquitectura sigue este orden de despliegue: 1. **Portainer** (gestor de contenedores) - acceso directo por puerto 9443 2. **Traefik** (reverse proxy) - desplegado desde Portainer 3. **Authentik** (SSO) - desplegado desde Portainer -4. **Resto de stacks** - desplegables desde Portainer +4. **CrowdSec** (IPS/IDS + bouncer) - protección de seguridad colaborativa para todos los servicios +5. **Resto de stacks** - desplegables desde Portainer Todos los servicios se comunican a través de la red Docker `proxy` y están protegidos por Traefik con SSL. @@ -122,7 +124,7 @@ nslookup auth.tudominio.com - Repository URL: `` - Repository reference: `main` - Compose path: `Traefik/docker-compose.yml` -5. Añade el archivo de variables de entorno: `Traefik/stack.env` (o configúralas manualmente) +5. Añade el archivo de variables de entorno: `Traefik/.env` (puedes partir de `Traefik/stack.env` como plantilla) 6. Haz clic en **Deploy the stack** Verifica que Traefik esté funcionando: @@ -153,6 +155,8 @@ Una vez desplegado Authentik, necesitarás configurar: Para instrucciones detalladas, consulta el [README de Authentik](authentik/README.md). +> **Importante**: para proteger nuevas apps con `ths-authentik@docker`, no basta con añadir el middleware en Traefik. También hay que crear en Authentik el **Proxy Provider**, la **Application** y asociarlos al **Embedded Outpost**. El patrón completo está documentado en `authentik/README.md`. + ### Paso 9: Configurar Variables de Entorno para Portainer UI Una vez Traefik y Authentik están funcionando, actualiza el archivo `.env` en la raíz del proyecto: @@ -216,8 +220,13 @@ cd .. | **Traefik** | Reverse proxy con SSL automático | `Traefik/` | [README](Traefik/README.md) | | **Portainer** | Gestor visual de Docker | Raíz (docker-compose.yml) | - | | **Authentik** | Sistema de autenticación SSO (Forward Auth) | `authentik/` | [README](authentik/README.md) | +| **CrowdSec** | IPS/IDS colaborativo + bouncer Traefik + Grafana | `crowdsec/` | [README](crowdsec/README.md) | +| **Homepage** | Dashboard principal expuesto por Traefik | `homepage/` | [README](homepage/README.md) | +| **Dozzle** | Visor de logs Docker protegido con Authentik | `dozzle/` | [README](dozzle/README.md) | +| **Beszel** | Monitorización ligera de host y contenedores | `beszel/` | [README](beszel/README.md) | | **Gitea** | Servidor Git autoalojado con Actions | `gitea/` | [README](gitea/README.md) | | **n8n** | Plataforma de automatización de workflows | `n8n/` | [README](n8n/README.md) | +| **Mail Relay** | Relay SMTP de salida para aplicaciones | `mail-relay/` | [README](mail-relay/README.md) | | **AdGuard** | Bloqueador de anuncios DNS con DoT | `adguard/` | [README](adguard/README.md) | | **Trilium** | Aplicación de notas jerárquicas | `trilium/` | [README](trilium/README.md) | | **Wireguard** | VPN rápida y segura | `wireguard/` | [README](wireguard/README.md) | @@ -255,7 +264,7 @@ TRAEFIK_AUTH_MIDDLEWARE=ths-authentik@docker ### Configuraciones por Stack -Cada stack puede tener su propio archivo `stack.env` o `.env` en su carpeta correspondiente. +Cada stack usa su propio archivo `.env` local. El archivo `stack.env` se conserva solo como plantilla de ejemplo para copiar o tomar como referencia. ## 🎯 Uso @@ -352,7 +361,7 @@ sudo chcon -Rt svirt_sandbox_file_t /opt/portainer/data ## 📝 Notas Adicionales -- **Orden de despliegue**: Siempre desplegar Portainer (9443) → Traefik → Authentik → Resto de stacks +- **Orden de despliegue**: Siempre desplegar Portainer (9443) → Traefik → Authentik → CrowdSec → Resto de stacks - **Registros DNS**: Configurar ANTES de desplegar Traefik para que Let's Encrypt funcione correctamente - **Puerto 9443**: Mantén el acceso por puerto 9443 como respaldo en caso de problemas con Traefik - **Backups**: Considera hacer backup regular de `/opt/portainer/data` y el archivo de secretos diff --git a/docker-compose.yml b/docker-compose.yml index 2ea645d..f2a5c66 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,7 +1,9 @@ services: portainer: - image: portainer/portainer-ee:2.33.7 + image: portainer/portainer-ee:2.39.1 container_name: portainer + profiles: + - legacy restart: unless-stopped volumes: @@ -17,26 +19,8 @@ services: - proxy labels: - - "traefik.enable=true" - - "traefik.docker.network=proxy" - - # 1) UI protegida Authentik - - "traefik.http.routers.portainer.rule=Host(`portainer.thehomelesssherlock.com`)" - - "traefik.http.routers.portainer.entrypoints=websecure" - - "traefik.http.routers.portainer.tls.certresolver=letsencrypt" - - "traefik.http.routers.portainer.middlewares=ths-authentik@docker" - - "traefik.http.services.portainer.loadbalancer.server.port=9000" - - # 2) API/App móvil SIN Authentik, SOLO por VPN (WireGuard) - - "traefik.http.middlewares.portainer-api-ip.ipallowlist.sourcerange=10.8.0.0/24,172.18.0.1/32" - - "traefik.http.routers.portainer-direct.rule=Host(`portainer-api.thehomelesssherlock.com`)" - - "traefik.http.routers.portainer-direct.entrypoints=websecure" - - "traefik.http.routers.portainer-direct.tls.certresolver=letsencrypt" - - "traefik.http.routers.portainer-direct.middlewares=portainer-api-ip" - - "traefik.http.routers.portainer-direct.service=portainer" - - "traefik.http.routers.portainer-direct.priority=100" + - "traefik.enable=false" networks: proxy: external: true -