Saltar a contenido

Servidor inverso — Nota de arquitectura

Traducción automática del original francés.

La ruta completa

flowchart TB
    subgraph internet["Internet"]
        client["Client<br/>navigateur"]
    end

    subgraph ovh["OVH"]
        dns["Zone DNS savethemall.eu<br/>A → <IP publique>"]
    end

    subgraph maison["LAN <sous-reseau LAN>"]
        velop["Routeur Velop<br/>NAT 443 → nas:8080"]

        subgraph nas["NAS Synology · le NAS"]
            nascaddy["nas-caddy — EDGE WAN<br/>TLS explicite, certs copiés"]
            statique["Sites statiques locaux<br/>weber · chroniques · rgpd"]
        end

        subgraph vmd["vm-docker · vm-docker"]
            caddy["Caddy<br/>routage interne + CrowdSec"]
            authentik["Authentik<br/>outpost embarqué"]
            app["Conteneur applicatif<br/>infrastructure_network"]
        end
    end

    client -->|"résout le nom"| dns
    client -->|"HTTPS 443"| velop
    velop --> nascaddy
    nascaddy -->|"local"| statique
    nascaddy -->|"import to_vmdocker<br/>header_up Host"| caddy
    caddy -->|"forward_auth"| authentik
    authentik -.->|"302 vers auth.savethemall.eu<br/>si pas de session"| client
    caddy -->|"reverse_proxy nom:port"| app

¿Por qué dos niveles

Uno habría sido suficiente funcionalmente. Hay dos porque ambas máquinas no pueden hacer lo mismo:

  • El NAS es el único que ve la red WAN. El NAT del router apunta a él. Por lo tanto, alberga los sitios estáticos que deben sobrevivir a la desaparición de vm-docker.
  • vm-docker es el único que puede bloquear. El NAS se ejecuta en un núcleo DSM sin xt_set, sin árbol /lib/modules, y sudo exige una contraseña: no es posible ningún bloqueo L3. Por lo tanto, CrowdSec reside en vm-docker, en modo 403.

Las dos consecuencias que cuestan horas

Un filtrado L3 en vm-docker no bloquea nada

Todas las solicitudes WAN llegan a vm-docker con nas como origen. Una regla por IP de cliente es inherentemente ciega.

Lo que funciona: trusted_proxies static le NAS/32 en las opciones globales de Caddy, que le permite leer el X-Forwarded-For de nas-caddy. Los módulos que consumen la IP de cliente (incluido CrowdSec) entonces ven la verdadera.

Los sitios estáticos servidos por el NAS no pasan por CrowdSec

Nunca atraviesan el Caddy de vm-docker. El filtrado L7 no los cubre. Es una limitación intencional, no un olvido.

La autenticación

El fragmento compartido, en el Caddyfile de vm-docker:

(authentik) {
    forward_auth authentik-server:9000 {
        uri /outpost.goauthentik.io/auth/caddy
        copy_headers X-authentik-username X-authentik-groups X-authentik-email X-authentik-name
    }
}

Y su equivalente en el verdadero borde, para los sitios estáticos servidos localmente por el NAS:

(authentik_edge) {
    forward_auth vm-docker:9092 {
        uri /outpost.goauthentik.io/auth/caddy
        copy_headers X-authentik-username X-authentik-groups X-authentik-email X-authentik-name
    }
}

Lo que forward_auth hace, solicitud por solicitud

sequenceDiagram
    autonumber
    participant N as Navigateur
    participant C as Caddy
    participant A as Authentik
    participant S as Service

    N->>C: GET /une-page
    C->>A: sous-requête /outpost.goauthentik.io/auth/caddy
    alt pas de session
        A-->>C: 401
        C-->>N: 302 vers auth.savethemall.eu
        N->>A: connexion
        A-->>N: 302 vers /outpost.goauthentik.io/callback
        Note over N,C: c'est CETTE route qui doit exister<br/>dans un `handle` frère, sinon le callback<br/>tombe dans le reverse_proxy de l'app
        N->>C: GET /outpost.goauthentik.io/callback
        C->>A: relais vers authentik-server:9000
        A-->>N: cookie de session, puis 302 vers /une-page
        N->>C: GET /une-page (avec le cookie)
    end
    C->>A: sous-requête d'autorisation
    A-->>C: 200 + en-têtes X-authentik-*
    C->>S: reverse_proxy, en-têtes recopiés
    S-->>N: la page

El servicio nunca ve la contraseña: recibe una solicitud ya autorizada, con la identidad en los encabezados. También es su límite: no tiene forma de saber si una sesión ha finalizado.

forward_auth se ejecuta antes de handle

L'ordre d'exécution des directives Caddy n'est pas l'ordre du fichier. forward_auth est ordonné avant handle et reverse_proxy : il attrape la requête en premier, quoi qu'on écrive après lui — dans le même bloc comme dans un snippet importé.

flowchart TB
    req["Requête<br/>/outpost.goauthentik.io/callback"]

    subgraph fichier["Ce que le fichier donne à lire"]
        f1["1. handle /outpost…/*"]
        f2["2. forward_auth"]
        f3["3. reverse_proxy app"]
    end

    subgraph reel["L'ordre réellement appliqué par Caddy"]
        r1["1. forward_auth"]
        r2["2. handle"]
        r3["3. reverse_proxy"]
    end

    req --> fichier
    fichier -.->|"n'est PAS<br/>l'ordre d'exécution"| reel
    r1 --> perdu["forward_auth intercepte<br/>→ 302, boucle de connexion"]

    classDef bad stroke-width:3px,stroke-dasharray: 5 5
    class perdu bad

Conséquence : la route de l'outpost ne peut pas être posée une fois pour toutes dans le snippet (authentik). Les deux écritures suivantes ont été essayées, elles passent caddy validate, se rechargent sans erreur, et ne changent rien :

(authentik) {
    handle /outpost.goauthentik.io/* { reverse_proxy authentik-server:9000 }   # sans effet
    reverse_proxy /outpost.goauthentik.io/* authentik-server:9000              # sans effet
    forward_auth authentik-server:9000 { ... }
}

Le seul endroit où elle prend est le site, avec l'application elle aussi dans un handle : deux handle frères sont des routes mutuellement exclusives, et la plus spécifique gagne avant que forward_auth n'entre en jeu.

site.savethemall.eu {
    import tls_cert site.savethemall.eu
    handle /outpost.goauthentik.io/* {
        reverse_proxy authentik-server:9000
    }
    handle {
        import authentik
        reverse_proxy monservice:8080
    }
}

La sonde qui distingue les deux formes en une requête, sans compte ni session :

curl -s -o /dev/null -w '%{http_code}\n' https://<site>/outpost.goauthentik.io/zzz
# 404 → la route outpost existe, c'est Authentik qui répond
# 302 → pas de route ; forward_auth a intercepté

Ce n'est pas une panne pour les sites qui ne l'ont pas

De los 39 sitios que importan authentik, 4 utilizan este handle. Los 35 restantes funcionan. El handle es una configuración que cumple con la documentación de Authentik, no una condición para que funcione. Generalizar la forma requeriría reescribir 35 bloques individualmente, lo cual sería una tarea compleja, utilizando la sonda como criterio, no sed.

Primero OIDC, luego forward_auth, nunca ambos

Para un nuevo servicio, primero buscar integrations.goauthentik.io/documentation/<service>. Si la aplicación es capaz de utilizar OIDC, se puede conectar.

forward_auth no conoce la identidad de la aplicación: ni cuenta, ni rol, ni desconexión propia — simplemente una barrera. Sigue siendo la mejor opción para aquellos que no tienen conocimiento del usuario, como un sitio estático.

Tenerlos ambos es una trampa

El proxy intercepta /api/auth/callback/* antes de que llegue a la aplicación → crea un bucle de conexión, con tres componentes que parecen funcionar correctamente.

Cerrar la conexión local (DISABLE_PASSWORD_AUTH) después de la primera conexión OIDC exitosa, nunca antes.

Certificados

  • Emitidos por acme.sh en DNS-01 en OVH, un certificado por dominio, en /opt/docker/acme/certs/<fqdn>/.
  • Cargar explícitamente: auto_https off en ambos servidores Caddy, y una macro (tls_cert) por sitio.
  • Recargar después del renovación mediante la API de administración de Caddy (POST http://127.0.0.1:2019/load), publicada únicamente en la red local.
  • El NAS no emite nada: los certificados se le copian diariamente por un rsync desde vm-docker.

Detalle crucial: un upstream designado por su IP

reverse_proxy https://le NAS

Caddy envía entonces Host: nas:443. La mayoría de las aplicaciones realizan redirecciones absolutas a partir de este encabezado, y el cliente se dirige a una dirección interna. Siempre añadir:

reverse_proxy https://le NAS {
    header_up Host {host}
}

Dernière vérification de cette page : 2026-09-08