feat(authentik): añadir script de creación de aplicaciones OAuth
- create-auth-app.sh: script interactivo para crear providers y aplicaciones en Authentik vía API, automatizando la integración SSO - docker-compose.yml y stack.env: ajustes del stack - README.md: documentación actualizada Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+248
-3
@@ -17,13 +17,13 @@ Este stack despliega Authentik configurado para funcionar con Traefik mediante *
|
||||
- Repository URL: `<tu-repositorio>`
|
||||
- Repository reference: `main`
|
||||
- Compose path: `authentik/docker-compose.yml`
|
||||
5. Carga el archivo de variables de entorno: `authentik/stack.env`
|
||||
5. Carga el archivo de variables de entorno: `authentik/.env`
|
||||
6. O añade manualmente las variables de entorno necesarias
|
||||
7. Haz clic en **Deploy the stack**
|
||||
|
||||
### Variables de Entorno Importantes
|
||||
|
||||
Edita el archivo `stack.env` con tus valores:
|
||||
Edita el archivo `.env` con tus valores:
|
||||
|
||||
```env
|
||||
# Secretos (genera valores aleatorios seguros)
|
||||
@@ -213,6 +213,251 @@ labels:
|
||||
traefik.http.routers.portainer.middlewares: "authentik@file"
|
||||
```
|
||||
|
||||
### 7.1 Patrón recomendado para nuevas apps
|
||||
|
||||
En este repositorio, **proteger un servicio con `ths-authentik@docker` no es suficiente por sí solo**. Además del middleware en Traefik, debes crear en Authentik:
|
||||
|
||||
1. un **Proxy Provider**
|
||||
2. una **Application**
|
||||
3. y asociar ese provider al **`authentik Embedded Outpost`**
|
||||
|
||||
Si falta cualquiera de esas piezas, el síntoma típico es ver una página de Authentik con **`Not Found`** al entrar al dominio protegido.
|
||||
|
||||
#### Patrón que seguimos
|
||||
|
||||
Para una nueva app publicada detrás de Traefik, usa este esquema:
|
||||
|
||||
1. **En el stack Docker del servicio**
|
||||
- Añade el router de Traefik para el dominio final
|
||||
- Añade el middleware:
|
||||
|
||||
```yaml
|
||||
labels:
|
||||
traefik.http.routers.mi-app.middlewares: "ths-authentik@docker"
|
||||
```
|
||||
|
||||
2. **En Authentik crea un Proxy Provider**
|
||||
- **Name**: `Provider for Mi App`
|
||||
- **Mode**: `forward_single`
|
||||
- **External host**: `https://mi-app.tudominio.com`
|
||||
- **Authorization flow**: el mismo flow compartido que ya usan las demás apps protegidas
|
||||
- **Intercept header authentication**: activado
|
||||
|
||||
3. **En Authentik crea una Application**
|
||||
- **Name**: `Mi App`
|
||||
- **Slug**: `mi-app`
|
||||
- **Provider**: el provider creado en el paso anterior
|
||||
- **Launch URL**: `https://mi-app.tudominio.com`
|
||||
- **Group**: la categoría que te convenga en el portal de Authentik
|
||||
|
||||
4. **Añade el provider al `authentik Embedded Outpost`**
|
||||
- Ve a **Outposts** → **authentik Embedded Outpost**
|
||||
- Añade el nuevo provider a la lista de aplicaciones/providers publicados por el outpost
|
||||
|
||||
#### Qué copiar de una app existente
|
||||
|
||||
Si ya tienes una app similar funcionando, lo más seguro es **replicar su patrón**:
|
||||
|
||||
- reutiliza el mismo **authorization flow**
|
||||
- reutiliza el mismo **mode** (`forward_single`)
|
||||
- deja `authentication_flow` vacío si el patrón existente también lo deja vacío
|
||||
- mantén `intercept_header_auth` activado si sigues el patrón actual del repositorio
|
||||
|
||||
Las referencias más claras en este entorno son:
|
||||
|
||||
- `Provider for Homepage`
|
||||
- `Provider for Dozzle`
|
||||
- `PiHole`
|
||||
|
||||
#### Checklist rápida antes de dar una app por buena
|
||||
|
||||
1. El servicio responde internamente por su puerto/URL local
|
||||
2. Traefik tiene el router correcto para el dominio
|
||||
3. El router usa `ths-authentik@docker`
|
||||
4. Existe un **Proxy Provider** con el **External host** exacto
|
||||
5. Existe una **Application** enlazada a ese provider
|
||||
6. El provider está añadido al **Embedded Outpost**
|
||||
7. El outpost está **Healthy**
|
||||
8. El dominio resuelve DNS hacia el servidor
|
||||
|
||||
#### Verificación rápida
|
||||
|
||||
Desde el host, una app bien enlazada con Authentik suele devolver un `302` hacia Authentik cuando no hay sesión:
|
||||
|
||||
```bash
|
||||
curl -k -I https://mi-app.tudominio.com
|
||||
```
|
||||
|
||||
Si ves:
|
||||
|
||||
- **`302` a `/application/o/authorize/`** → el patrón está bien enlazado
|
||||
- **página `Not Found` de Authentik** → falta provider/app/outpost o están mal enlazados
|
||||
- **`404/502` de Traefik** → problema de router, servicio o resolución
|
||||
|
||||
### 7.2 Automatizar con la API (sin usar la UI)
|
||||
|
||||
Se puede crear una aplicación completa (Provider + Application + Outpost) mediante la API REST de Authentik sin tocar la interfaz web. Esto es útil para automatizar despliegues o cuando ya conoces el patrón exacto.
|
||||
|
||||
#### Patrón de llamadas API
|
||||
|
||||
El script `create-auth-app.sh` encapsula estas 3 llamadas:
|
||||
|
||||
**1. Crear Proxy Provider**
|
||||
|
||||
```bash
|
||||
curl -sk -X POST "https://auth.tudominio.com/api/v3/providers/proxy/" \
|
||||
-H "Authorization: Bearer <TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Provider for Mi App",
|
||||
"authentication_flow": null,
|
||||
"authorization_flow": "<FLOW_UUID>",
|
||||
"invalidation_flow": "<INVAL_FLOW_UUID>",
|
||||
"property_mappings": ["<UUID1>","<UUID2>","<UUID3>","<UUID4>","<UUID5>"],
|
||||
"external_host": "https://mi-app.midominio.com",
|
||||
"internal_host": "",
|
||||
"internal_host_ssl_validation": true,
|
||||
"mode": "forward_single",
|
||||
"intercept_header_auth": true,
|
||||
"cookie_domain": "",
|
||||
"access_token_validity": "hours=1",
|
||||
"refresh_token_validity": "days=30"
|
||||
}'
|
||||
```
|
||||
|
||||
**2. Crear Application**
|
||||
|
||||
```bash
|
||||
curl -sk -X POST "https://auth.tudominio.com/api/v3/core/applications/" \
|
||||
-H "Authorization: Bearer <TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"name": "Mi App",
|
||||
"slug": "mi-app",
|
||||
"provider": <PROVIDER_PK>,
|
||||
"policy_engine_mode": "any",
|
||||
"meta_launch_url": "https://mi-app.midominio.com"
|
||||
}'
|
||||
```
|
||||
|
||||
**3. Añadir al Embedded Outpost**
|
||||
|
||||
```bash
|
||||
curl -sk -X PATCH "https://auth.tudominio.com/api/v3/outposts/instances/<OUTPOST_UUID>/" \
|
||||
-H "Authorization: Bearer <TOKEN>" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"providers": [<existing_pks>, <new_pk>]}'
|
||||
```
|
||||
|
||||
> Los UUIDs de los flows (`FLOW_UUID`, `INVAL_FLOW_UUID`) y property mappings se copian de un provider existente. El script los obtiene automáticamente.
|
||||
|
||||
#### Generar un token de API
|
||||
|
||||
El script genera un token temporal automáticamente mediante `ak shell` dentro del contenedor. Si quieres hacerlo manual:
|
||||
|
||||
```bash
|
||||
docker exec ths-authentik-server bash -c 'echo "
|
||||
from authentik.core.models import Token, User
|
||||
u = User.objects.filter(username=\"akadmin\").first()
|
||||
t, _ = Token.objects.update_or_create(user=u, identifier=\"mi-script\",
|
||||
defaults={\"intent\": \"api\", \"expiring\": True})
|
||||
print(t.key)
|
||||
" | ak shell 2>/dev/null' | tail -1
|
||||
```
|
||||
|
||||
### 7.3 Script automatizado: `create-auth-app.sh`
|
||||
|
||||
El script `create-auth-app.sh` en este directorio automatiza todo el proceso. Basta con pasarle el dominio y opcionalmente el nombre y slug.
|
||||
|
||||
#### Uso
|
||||
|
||||
```bash
|
||||
cd authentik
|
||||
./create-auth-app.sh <dominio> [nombre] [slug]
|
||||
```
|
||||
|
||||
**Ejemplos:**
|
||||
|
||||
```bash
|
||||
# Especificando todo
|
||||
./create-auth-app.sh crowdsec.sherlockhomeless.net CrowdSec crowdsec
|
||||
|
||||
# Solo dominio (nombre y slug se derivan automáticamente)
|
||||
./create-auth-app.sh mi-app.sherlockhomeless.net
|
||||
# → nombre: "Mi App", slug: "mi-app"
|
||||
|
||||
# Con nombre personalizado pero slug automático
|
||||
./create-auth-app.sh dashboard.sherlockhomeless.net "Mi Dashboard"
|
||||
# → nombre: "Mi Dashboard", slug: "dashboard"
|
||||
```
|
||||
|
||||
#### Qué hace el script
|
||||
|
||||
| Paso | Acción |
|
||||
|------|--------|
|
||||
| 1 | Genera un token temporal de API usando `ak shell` |
|
||||
| 2 | Localiza el authorization flow `default-provider-authorization-implicit-consent` |
|
||||
| 3 | Crea un **Proxy Provider** (o lo salta si ya existe para ese `external_host`) |
|
||||
| 4 | Crea una **Application** (o la salta si ya existe ese slug) |
|
||||
| 5 | Localiza el **Embedded Outpost** |
|
||||
| 6 | Añade el provider al outpost (o lo salta si ya está asociado) |
|
||||
| Verificación | Comprueba que el dominio redirige a Authentik (HTTP 302) |
|
||||
|
||||
#### Requisitos
|
||||
|
||||
- Contenedor `ths-authentik-server` corriendo y healthy
|
||||
- `jq` instalado (`sudo apt install jq`)
|
||||
- El archivo `.env` en el directorio `authentik/` (opcional, para leer `AUTHENTIK_DOMAIN`)
|
||||
|
||||
#### Idempotencia
|
||||
|
||||
El script es seguro re-ejecutarlo. Si el provider, aplicación o asociación ya existen, los salta y continúa. Ideal para usarlo en despliegues repetibles o CI/CD.
|
||||
|
||||
#### Salida esperada
|
||||
|
||||
```
|
||||
=========================================
|
||||
Authentik — Crear Application
|
||||
=========================================
|
||||
Dominio: crowdsec.sherlockhomeless.net
|
||||
External host: https://crowdsec.sherlockhomeless.net
|
||||
Nombre app: CrowdSec
|
||||
Slug: crowdsec
|
||||
=========================================
|
||||
|
||||
[1/6] Generando token temporal de API...
|
||||
Token obtenido correctamente.
|
||||
[2/6] Buscando authorization flow (implicit-consent)...
|
||||
Flow UUID: 08080511-9d73-45f4-b394-19b5a2452422
|
||||
[3/6] Verificando si ya existe un provider...
|
||||
Provider creado: pk=39
|
||||
[4/6] Verificando si ya existe la aplicación...
|
||||
Aplicación creada: pk=51f0c37d-...
|
||||
[5/6] Buscando el Embedded Outpost...
|
||||
Outpost UUID: 27a4dc69-cefa-4575-b853-caed79515a13
|
||||
[6/6] Añadiendo provider al outpost...
|
||||
Provider añadido al outpost.
|
||||
|
||||
=========================================
|
||||
Verificación
|
||||
=========================================
|
||||
OK: https://crowdsec.sherlockhomeless.net redirige a Authentik (302)
|
||||
|
||||
=========================================
|
||||
Resumen
|
||||
=========================================
|
||||
Provider: Provider for CrowdSec (pk=39)
|
||||
Application: CrowdSec (slug=crowdsec, pk=51f0c37d-...)
|
||||
Outpost: authentik Embedded Outpost (...)
|
||||
URL: https://crowdsec.sherlockhomeless.net
|
||||
=========================================
|
||||
```
|
||||
|
||||
#### Casos especiales
|
||||
|
||||
- **Servicios con widget en Homepage**: una cosa es proteger la UI pública con Authentik y otra la URL interna que Homepage usa para el widget. Homepage normalmente debe hablar con la app por red Docker interna, no pasando por el login web.
|
||||
- **Servicios con auth propia + Homepage widget**: a menudo conviene dejar la UI pública protegida por Authentik, pero el widget de Homepage apuntando a la URL interna con credenciales/API key propias.
|
||||
|
||||
## 🔐 Gestión de Usuarios y Grupos
|
||||
|
||||
### Crear Usuarios
|
||||
@@ -252,7 +497,7 @@ En la configuración del provider:
|
||||
Para notificaciones y recuperación de contraseñas:
|
||||
|
||||
1. Ve a **System** → **Settings** → **Email**
|
||||
2. Configura los parámetros SMTP (o usa las variables de entorno en `stack.env`)
|
||||
2. Configura los parámetros SMTP (o usa las variables de entorno en `.env`)
|
||||
|
||||
### Configurar Autenticación de Dos Factores (2FA)
|
||||
|
||||
|
||||
Executable
+327
@@ -0,0 +1,327 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
# ============================================================
|
||||
# create-auth-app.sh — Crea una aplicación protegida por
|
||||
# Authentik (Proxy Provider + Application + Outpost) vía API
|
||||
# ============================================================
|
||||
#
|
||||
# Uso:
|
||||
# ./create-auth-app.sh <dominio> [nombre] [slug]
|
||||
#
|
||||
# Ejemplos:
|
||||
# ./create-auth-app.sh crowdsec.sherlockhomeless.net "CrowdSec" crowdsec
|
||||
# ./create-auth-app.sh mi-app.midominio.com
|
||||
#
|
||||
# Requisitos:
|
||||
# - Contenedor ths-authentik-server corriendo y healthy
|
||||
# - jq instalado (sudo apt install jq)
|
||||
# ============================================================
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
ENV_FILE="${SCRIPT_DIR}/.env"
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Cargar configuración
|
||||
# -----------------------------------------------------------
|
||||
AK_HOST_DEFAULT="https://auth.sherlockhomeless.net"
|
||||
AK_CONTAINER="ths-authentik-server"
|
||||
|
||||
if [ -f "${ENV_FILE}" ]; then
|
||||
set -a; source "${ENV_FILE}"; set +a
|
||||
fi
|
||||
|
||||
AK_HOST="${AUTHENTIK_DOMAIN:-$AK_HOST_DEFAULT}"
|
||||
AK_HOST="https://${AK_HOST#https://}" # normalizar
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Parámetros
|
||||
# -----------------------------------------------------------
|
||||
DOMAIN="${1:-}"
|
||||
APP_NAME="${2:-}"
|
||||
APP_SLUG="${3:-}"
|
||||
|
||||
if [ -z "$DOMAIN" ]; then
|
||||
echo "Uso: $0 <dominio> [nombre] [slug]"
|
||||
echo ""
|
||||
echo "Ejemplos:"
|
||||
echo " $0 crowdsec.sherlockhomeless.net CrowdSec crowdsec"
|
||||
echo " $0 mi-app.midominio.com"
|
||||
echo ""
|
||||
echo "Si no se especifica nombre/slug, se derivan del dominio."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Derivar nombre y slug del dominio si no se especificaron
|
||||
if [ -z "$APP_NAME" ]; then
|
||||
# Extraer subdominio: mi-app.midominio.com -> Mi App
|
||||
SUB=$(echo "$DOMAIN" | cut -d. -f1 | sed 's/-/ /g' | sed 's/\b\(.\)/\u\1/g')
|
||||
APP_NAME="$SUB"
|
||||
fi
|
||||
|
||||
if [ -z "$APP_SLUG" ]; then
|
||||
APP_SLUG=$(echo "$DOMAIN" | cut -d. -f1 | tr '[:upper:]' '[:lower:]')
|
||||
fi
|
||||
|
||||
EXTERNAL_HOST="https://${DOMAIN}"
|
||||
|
||||
echo "========================================="
|
||||
echo " Authentik — Crear Application"
|
||||
echo "========================================="
|
||||
echo " Dominio: ${DOMAIN}"
|
||||
echo " External host: ${EXTERNAL_HOST}"
|
||||
echo " Nombre app: ${APP_NAME}"
|
||||
echo " Slug: ${APP_SLUG}"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Verificar dependencias
|
||||
# -----------------------------------------------------------
|
||||
if ! command -v jq &>/dev/null; then
|
||||
echo "ERROR: jq no está instalado. Instálalo con: sudo apt install jq"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! docker ps --format '{{.Names}}' | grep -q "^${AK_CONTAINER}$"; then
|
||||
echo "ERROR: El contenedor '${AK_CONTAINER}' no está corriendo."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 1. Generar token temporal de API
|
||||
# -----------------------------------------------------------
|
||||
echo "[1/6] Generando token temporal de API..."
|
||||
|
||||
TOKEN_SCRIPT="
|
||||
from authentik.core.models import Token, User
|
||||
user = User.objects.filter(username='akadmin').first()
|
||||
if not user:
|
||||
print('ERROR:NO_USER')
|
||||
else:
|
||||
token, _ = Token.objects.update_or_create(
|
||||
user=user,
|
||||
identifier='setup-script',
|
||||
defaults={'intent': 'api', 'expiring': True, 'expires': None, 'description': 'Token temporal para create-auth-app.sh'}
|
||||
)
|
||||
print(token.key)
|
||||
"
|
||||
AK_TOKEN=$(docker exec "$AK_CONTAINER" bash -c "echo \"$TOKEN_SCRIPT\" | ak shell 2>/dev/null" | grep -v '^###' | grep -v '^$' | grep -v 'objects imported' | tail -1)
|
||||
|
||||
if [ -z "$AK_TOKEN" ] || [[ "$AK_TOKEN" == ERROR:* ]]; then
|
||||
echo "ERROR: No se pudo generar el token de API. ¿Existe el usuario akadmin?"
|
||||
echo "Respuesta: $AK_TOKEN"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Token obtenido correctamente."
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 2. Obtener el authorization flow
|
||||
# -----------------------------------------------------------
|
||||
echo "[2/6] Buscando authorization flow (implicit-consent)..."
|
||||
|
||||
FLOW_JSON=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/flows/instances/?slug=default-provider-authorization-implicit-consent" 2>/dev/null)
|
||||
|
||||
FLOW_UUID=$(echo "$FLOW_JSON" | jq -r '.results[0].pk // empty')
|
||||
|
||||
if [ -z "$FLOW_UUID" ]; then
|
||||
echo "ERROR: No se encontró el flow 'default-provider-authorization-implicit-consent'."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Flow UUID: ${FLOW_UUID}"
|
||||
|
||||
# Buscar un provider existente para copiar sus property_mappings e invalidation_flow
|
||||
EXISTING_PROVIDER=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/providers/proxy/?page_size=1" 2>/dev/null)
|
||||
|
||||
INVAL_FLOW=$(echo "$EXISTING_PROVIDER" | jq -r '.results[0].invalidation_flow // empty')
|
||||
PROPERTY_MAPPINGS=$(echo "$EXISTING_PROVIDER" | jq -c '.results[0].property_mappings // []')
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 3. Verificar si ya existe un provider para este dominio
|
||||
# -----------------------------------------------------------
|
||||
echo "[3/6] Verificando si ya existe un provider para ${EXTERNAL_HOST}..."
|
||||
|
||||
# Listar todos los providers y filtrar por external_host exacto (el API no filtra por este campo)
|
||||
EXISTING_PK=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/providers/proxy/?page_size=100" 2>/dev/null | jq -r --arg HOST "${EXTERNAL_HOST}" '.results[] | select(.external_host == $HOST) | .pk' 2>/dev/null)
|
||||
|
||||
if [ -n "$EXISTING_PK" ]; then
|
||||
echo " Ya existe un provider (pk=${EXISTING_PK}). Saltando creación."
|
||||
PROVIDER_PK="$EXISTING_PK"
|
||||
else
|
||||
# Crear provider
|
||||
PROVIDER_JSON=$(curl -sk -X POST "${AK_HOST}/api/v3/providers/proxy/" \
|
||||
-H "Authorization: Bearer $AK_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"Provider for ${APP_NAME}\",
|
||||
\"authentication_flow\": null,
|
||||
\"authorization_flow\": \"${FLOW_UUID}\",
|
||||
\"invalidation_flow\": \"${INVAL_FLOW}\",
|
||||
\"property_mappings\": ${PROPERTY_MAPPINGS},
|
||||
\"external_host\": \"${EXTERNAL_HOST}\",
|
||||
\"internal_host\": \"\",
|
||||
\"internal_host_ssl_validation\": true,
|
||||
\"mode\": \"forward_single\",
|
||||
\"intercept_header_auth\": true,
|
||||
\"cookie_domain\": \"\",
|
||||
\"access_token_validity\": \"hours=1\",
|
||||
\"refresh_token_validity\": \"days=30\"
|
||||
}" 2>/dev/null)
|
||||
|
||||
PROVIDER_PK=$(echo "$PROVIDER_JSON" | jq -r '.pk // empty')
|
||||
|
||||
if [ -z "$PROVIDER_PK" ]; then
|
||||
echo "ERROR: No se pudo crear el provider."
|
||||
echo "Respuesta: $PROVIDER_JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Provider creado: pk=${PROVIDER_PK}"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 4. Verificar si ya existe una aplicación con este slug
|
||||
# -----------------------------------------------------------
|
||||
echo "[4/6] Verificando si ya existe la aplicación '${APP_SLUG}'..."
|
||||
|
||||
EXISTING_APP=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/core/applications/?slug=${APP_SLUG}" 2>/dev/null | jq -r '.results[0].pk // empty')
|
||||
|
||||
if [ -n "$EXISTING_APP" ]; then
|
||||
echo " Ya existe la aplicación (pk=${EXISTING_APP}). Saltando creación."
|
||||
APP_PK="$EXISTING_APP"
|
||||
else
|
||||
# Crear aplicación
|
||||
APP_JSON=$(curl -sk -X POST "${AK_HOST}/api/v3/core/applications/" \
|
||||
-H "Authorization: Bearer $AK_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"name\": \"${APP_NAME}\",
|
||||
\"slug\": \"${APP_SLUG}\",
|
||||
\"provider\": ${PROVIDER_PK},
|
||||
\"policy_engine_mode\": \"any\",
|
||||
\"meta_launch_url\": \"${EXTERNAL_HOST}\"
|
||||
}" 2>/dev/null)
|
||||
|
||||
APP_PK=$(echo "$APP_JSON" | jq -r '.pk // empty')
|
||||
|
||||
if [ -z "$APP_PK" ]; then
|
||||
echo "ERROR: No se pudo crear la aplicación."
|
||||
echo "Respuesta: $APP_JSON"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Aplicación creada: pk=${APP_PK}"
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 5. Copiar policy binding de una app existente
|
||||
# -----------------------------------------------------------
|
||||
echo "[5/6] Aplicando policy binding..."
|
||||
|
||||
REFERENCE_APP_POLICY=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/policies/bindings/?target=${APP_PK}" 2>/dev/null | jq -r '.results[0].policy // empty')
|
||||
|
||||
if [ -n "$REFERENCE_APP_POLICY" ]; then
|
||||
echo " La app ya tiene un binding. Saltando."
|
||||
else
|
||||
# Tomar la policy del primer binding de cualquier app existente
|
||||
SAMPLE_APP=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/core/applications/?page_size=5" 2>/dev/null | jq -r '.results[] | select(.slug != "'"${APP_SLUG}"'") | .pk' | head -1)
|
||||
|
||||
POLICY_PK=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/policies/bindings/?target=${SAMPLE_APP}" 2>/dev/null | jq -r '.results[0].policy // empty')
|
||||
|
||||
if [ -n "$POLICY_PK" ]; then
|
||||
curl -sk -X POST "${AK_HOST}/api/v3/policies/bindings/" \
|
||||
-H "Authorization: Bearer $AK_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"policy\": \"${POLICY_PK}\", \"target\": \"${APP_PK}\", \"enabled\": true, \"order\": 0}" 2>/dev/null > /dev/null
|
||||
echo " Policy binding creado (policy=${POLICY_PK})."
|
||||
else
|
||||
echo " WARN: No se encontró una policy de referencia. Añade el binding manualmente en Authentik."
|
||||
fi
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 6. Obtener el Embedded Outpost
|
||||
# -----------------------------------------------------------
|
||||
echo "[6/7] Buscando el Embedded Outpost..."
|
||||
|
||||
OUTPOST_JSON=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/outposts/instances/?name=authentik%20Embedded%20Outpost" 2>/dev/null)
|
||||
|
||||
OUTPOST_UUID=$(echo "$OUTPOST_JSON" | jq -r '.results[0].pk // empty')
|
||||
|
||||
if [ -z "$OUTPOST_UUID" ]; then
|
||||
echo "ERROR: No se encontró el Embedded Outpost."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo " Outpost UUID: ${OUTPOST_UUID}"
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# 6. Añadir provider al outpost
|
||||
# -----------------------------------------------------------
|
||||
echo "[7/7] Añadiendo provider al outpost..."
|
||||
|
||||
CURRENT_PROVIDERS=$(curl -sk -H "Authorization: Bearer $AK_TOKEN" \
|
||||
"${AK_HOST}/api/v3/outposts/instances/${OUTPOST_UUID}/" 2>/dev/null | jq -r '.providers | join(",")')
|
||||
|
||||
# Verificar si el provider ya está en la lista
|
||||
if echo "$CURRENT_PROVIDERS" | grep -qw "${PROVIDER_PK}"; then
|
||||
echo " El provider ya está asociado al outpost."
|
||||
else
|
||||
NEW_PROVIDERS="${CURRENT_PROVIDERS},${PROVIDER_PK}"
|
||||
# Convertir a array JSON
|
||||
PROVIDER_ARRAY=$(echo "$NEW_PROVIDERS" | tr ',' '\n' | jq -R 'tonumber' | jq -s '.')
|
||||
|
||||
curl -sk -X PATCH "${AK_HOST}/api/v3/outposts/instances/${OUTPOST_UUID}/" \
|
||||
-H "Authorization: Bearer $AK_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{\"providers\": ${PROVIDER_ARRAY}}" 2>/dev/null > /dev/null
|
||||
|
||||
echo " Provider añadido al outpost."
|
||||
fi
|
||||
|
||||
# -----------------------------------------------------------
|
||||
# Verificación
|
||||
# -----------------------------------------------------------
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Verificación"
|
||||
echo "========================================="
|
||||
|
||||
HTTP_CODE=$(curl -sk -o /dev/null -w "%{http_code}" "${EXTERNAL_HOST}/" 2>/dev/null)
|
||||
REDIRECT=$(curl -sk -o /dev/null -w "%{redirect_url}" "${EXTERNAL_HOST}/" 2>/dev/null)
|
||||
|
||||
if [ "$HTTP_CODE" = "302" ] && echo "$REDIRECT" | grep -q "/application/o/authorize/"; then
|
||||
echo " OK: ${EXTERNAL_HOST} redirige a Authentik (302)"
|
||||
elif [ "$HTTP_CODE" = "502" ] || [ "$HTTP_CODE" = "503" ]; then
|
||||
echo " WARN: El servicio backend no responde (HTTP ${HTTP_CODE})."
|
||||
echo " La protección Authentik funciona, pero el servicio no está UP."
|
||||
elif [ "$HTTP_CODE" = "200" ] && echo "$REDIRECT" | grep -q "authentik"; then
|
||||
echo " WARN: Redirige a Authentik pero con código 200."
|
||||
echo " Puede que necesites loguearte primero."
|
||||
else
|
||||
echo " INFO: HTTP ${HTTP_CODE}"
|
||||
echo " Verifica manualmente: curl -kI ${EXTERNAL_HOST}"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "========================================="
|
||||
echo " Resumen"
|
||||
echo "========================================="
|
||||
echo " Provider: Provider for ${APP_NAME} (pk=${PROVIDER_PK})"
|
||||
echo " Application: ${APP_NAME} (slug=${APP_SLUG}, pk=${APP_PK})"
|
||||
echo " Outpost: authentik Embedded Outpost (${OUTPOST_UUID})"
|
||||
echo " URL: ${EXTERNAL_HOST}"
|
||||
echo "========================================="
|
||||
echo ""
|
||||
echo "Listo. El middleware 'ths-authentik@docker' ya debería proteger ${EXTERNAL_HOST}."
|
||||
echo "Si usas CrowdSec también, añade: crowdsec-bouncer@file,ths-authentik@docker"
|
||||
@@ -8,7 +8,7 @@ services:
|
||||
POSTGRES_USER: ${AUTHENTIK_DB_USER}
|
||||
POSTGRES_DB: ${AUTHENTIK_DB_NAME}
|
||||
volumes:
|
||||
- /opt/authentik/postgres:/var/lib/postgresql/data:Z
|
||||
- ${AUTHENTIK_POSTGRES_PATH}:/var/lib/postgresql/data:Z
|
||||
networks:
|
||||
- ths_authentik_internal
|
||||
|
||||
@@ -18,7 +18,7 @@ services:
|
||||
restart: unless-stopped
|
||||
command: ["redis-server", "--save", "60", "1", "--loglevel", "warning"]
|
||||
volumes:
|
||||
- /opt/authentik/redis:/data:Z
|
||||
- ${AUTHENTIK_REDIS_PATH}:/data:Z
|
||||
networks:
|
||||
- ths_authentik_internal
|
||||
|
||||
@@ -42,26 +42,53 @@ services:
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL: ${AUTHENTIK_BOOTSTRAP_EMAIL}
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD: ${AUTHENTIK_BOOTSTRAP_PASSWORD}
|
||||
AUTHENTIK_BOOTSTRAP_TOKEN: ${AUTHENTIK_BOOTSTRAP_TOKEN}
|
||||
AUTHENTIK_EMAIL__HOST: ${AUTHENTIK_EMAIL__HOST}
|
||||
AUTHENTIK_EMAIL__PORT: ${AUTHENTIK_EMAIL__PORT}
|
||||
AUTHENTIK_EMAIL__USERNAME: ${AUTHENTIK_EMAIL__USERNAME}
|
||||
AUTHENTIK_EMAIL__PASSWORD: ${AUTHENTIK_EMAIL__PASSWORD}
|
||||
AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_EMAIL__FROM}
|
||||
AUTHENTIK_EMAIL__USE_TLS: ${AUTHENTIK_EMAIL__USE_TLS}
|
||||
AUTHENTIK_EMAIL__USE_SSL: ${AUTHENTIK_EMAIL__USE_SSL}
|
||||
|
||||
depends_on:
|
||||
- ths-authentik-postgres
|
||||
- ths-authentik-redis
|
||||
|
||||
expose:
|
||||
- "9000"
|
||||
- "${AUTHENTIK_HTTP_PORT}"
|
||||
|
||||
networks:
|
||||
- ths_authentik_internal
|
||||
- mail_internal
|
||||
- proxy
|
||||
|
||||
labels:
|
||||
traefik.enable: "true"
|
||||
traefik.docker.network: "${TRAEFIK_DOCKER_NETWORK}"
|
||||
|
||||
# Service Authentik (panel + endpoints)
|
||||
traefik.http.services.ths-authentik.loadbalancer.server.port: "9000"
|
||||
traefik.http.services.ths-authentik.loadbalancer.server.port: "${AUTHENTIK_HTTP_PORT}"
|
||||
|
||||
# Panel Authentik
|
||||
traefik.http.routers.ths-authentik.rule: "Host(`${AUTHENTIK_DOMAIN}`)"
|
||||
traefik.http.routers.ths-authentik.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||
traefik.http.routers.ths-authentik.tls: "true"
|
||||
traefik.http.routers.ths-authentik.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||
traefik.http.routers.ths-authentik.service: "ths-authentik"
|
||||
|
||||
# Middleware forwardAuth (para proteger otros servicios) -> usar ths-authentik@docker en tus stacks THS
|
||||
traefik.http.middlewares.ths-authentik.forwardauth.address: "http://ths-authentik-server:${AUTHENTIK_HTTP_PORT}/outpost.goauthentik.io/auth/traefik"
|
||||
traefik.http.middlewares.ths-authentik.forwardauth.trustForwardHeader: "true"
|
||||
traefik.http.middlewares.ths-authentik.forwardauth.authResponseHeaders: "X-Authentik-Username,X-Authentik-Groups,X-Authentik-Email,X-Authentik-Uid,X-Authentik-Jwt"
|
||||
traefik.http.middlewares.ths-authentik.forwardauth.maxResponseBodySize: "1048576"
|
||||
|
||||
# Outpost para subdominios sherlockhomeless.net, además de apex/www
|
||||
traefik.http.routers.ths-authentik-outpost.rule: "(HostRegexp(`[a-z0-9-]+\\.sherlockhomeless\\.net`) || Host(`sherlockhomeless.net`) || Host(`www.sherlockhomeless.net`)) && PathPrefix(`/outpost.goauthentik.io/`)"
|
||||
traefik.http.routers.ths-authentik-outpost.entrypoints: "${TRAEFIK_ENTRYPOINT_SECURE}"
|
||||
traefik.http.routers.ths-authentik-outpost.tls: "true"
|
||||
traefik.http.routers.ths-authentik-outpost.tls.certresolver: "${TRAEFIK_CERTRESOLVER}"
|
||||
traefik.http.routers.ths-authentik-outpost.service: "ths-authentik"
|
||||
traefik.http.routers.ths-authentik-outpost.priority: "1000"
|
||||
|
||||
ths-authentik-worker:
|
||||
image: ${AUTHENTIK_IMAGE}
|
||||
@@ -78,6 +105,13 @@ services:
|
||||
AUTHENTIK_POSTGRESQL__PASSWORD: ${AUTHENTIK_DB_PASSWORD}
|
||||
|
||||
AUTHENTIK_REDIS__HOST: ths-authentik-redis
|
||||
AUTHENTIK_EMAIL__HOST: ${AUTHENTIK_EMAIL__HOST}
|
||||
AUTHENTIK_EMAIL__PORT: ${AUTHENTIK_EMAIL__PORT}
|
||||
AUTHENTIK_EMAIL__USERNAME: ${AUTHENTIK_EMAIL__USERNAME}
|
||||
AUTHENTIK_EMAIL__PASSWORD: ${AUTHENTIK_EMAIL__PASSWORD}
|
||||
AUTHENTIK_EMAIL__FROM: ${AUTHENTIK_EMAIL__FROM}
|
||||
AUTHENTIK_EMAIL__USE_TLS: ${AUTHENTIK_EMAIL__USE_TLS}
|
||||
AUTHENTIK_EMAIL__USE_SSL: ${AUTHENTIK_EMAIL__USE_SSL}
|
||||
|
||||
depends_on:
|
||||
- ths-authentik-postgres
|
||||
@@ -85,8 +119,11 @@ services:
|
||||
|
||||
networks:
|
||||
- ths_authentik_internal
|
||||
- mail_internal
|
||||
|
||||
networks:
|
||||
mail_internal:
|
||||
external: true
|
||||
proxy:
|
||||
external: true
|
||||
ths_authentik_internal:
|
||||
|
||||
+31
-20
@@ -1,30 +1,41 @@
|
||||
##### Imágenes #####
|
||||
AUTHENTIK_POSTGRES_IMAGE=
|
||||
AUTHENTIK_REDIS_IMAGE=
|
||||
AUTHENTIK_IMAGE=
|
||||
AUTHENTIK_POSTGRES_IMAGE=docker.io/library/postgres:16.11-alpine3.22
|
||||
AUTHENTIK_REDIS_IMAGE=docker.io/library/redis:7-alpine
|
||||
AUTHENTIK_IMAGE=ghcr.io/goauthentik/server:latest
|
||||
|
||||
##### Base de datos Authentik #####
|
||||
AUTHENTIK_DB_PASSWORD=
|
||||
AUTHENTIK_DB_USER=
|
||||
AUTHENTIK_DB_NAME=
|
||||
AUTHENTIK_DB_HOST=
|
||||
AUTHENTIK_POSTGRES_PATH=
|
||||
AUTHENTIK_DB_PASSWORD=change-me
|
||||
AUTHENTIK_DB_USER=authentik
|
||||
AUTHENTIK_DB_NAME=authentik
|
||||
AUTHENTIK_DB_HOST=authentik-postgres
|
||||
AUTHENTIK_POSTGRES_PATH=/opt/authentik/postgres
|
||||
|
||||
##### Redis #####
|
||||
AUTHENTIK_REDIS_HOST=
|
||||
AUTHENTIK_REDIS_PATH=
|
||||
AUTHENTIK_REDIS_HOST=authentik-redis
|
||||
AUTHENTIK_REDIS_PATH=/opt/authentik/redis
|
||||
|
||||
##### Authentik #####
|
||||
AUTHENTIK_SECRET_KEY=
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL=
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD=
|
||||
AUTHENTIK_BOOTSTRAP_TOKEN=
|
||||
AUTHENTIK_SECRET_KEY=change-me
|
||||
AUTHENTIK_BOOTSTRAP_EMAIL=admin@sherlockhomeless.net
|
||||
AUTHENTIK_BOOTSTRAP_PASSWORD=change-me
|
||||
AUTHENTIK_BOOTSTRAP_TOKEN=change-me
|
||||
AUTHENTIK_HTTP_PORT=9000
|
||||
|
||||
##### Traefik / dominios #####
|
||||
TRAEFIK_DOCKER_NETWORK=
|
||||
AUTHENTIK_DOMAIN=
|
||||
GITEA_DOMAIN=
|
||||
TRAEFIK_ENTRYPOINT_SECURE=
|
||||
TRAEFIK_CERTRESOLVER=
|
||||
##### Correo (SMTP) #####
|
||||
AUTHENTIK_EMAIL__HOST=mail-relay
|
||||
AUTHENTIK_EMAIL__PORT=587
|
||||
AUTHENTIK_EMAIL__USERNAME=
|
||||
AUTHENTIK_EMAIL__PASSWORD=
|
||||
AUTHENTIK_EMAIL__FROM=authentik@sherlockhomeless.net
|
||||
AUTHENTIK_EMAIL__USE_TLS=false
|
||||
AUTHENTIK_EMAIL__USE_SSL=false
|
||||
|
||||
##### Traefik / dominios #####
|
||||
TRAEFIK_DOCKER_NETWORK=proxy
|
||||
AUTHENTIK_DOMAIN=auth.sherlockhomeless.net
|
||||
GITEA_DOMAIN=gitea.sherlockhomeless.net
|
||||
THS_DOMAIN=sherlockhomeless.net,www.sherlockhomeless.net
|
||||
TRAEFIK_ENTRYPOINT_SECURE=websecure
|
||||
TRAEFIK_CERTRESOLVER=letsencrypt
|
||||
SERVICE_URL_THS_AUTHENTIK_SERVER=https://auth.sherlockhomeless.net
|
||||
SERVICE_FQDN_THS_AUTHENTIK_SERVER=auth.sherlockhomeless.net
|
||||
|
||||
Reference in New Issue
Block a user