Ga naar inhoud

Omgekeerde proxy — architectuurnotitie

Machinevertaling van het Franse origineel.

De volledige route

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

Waarom twee lagen

Eén zou voldoende zijn. Er zijn er twee omdat beide machines niet hetzelfde kunnen doen:

  • De NAS is de enige die toegang heeft tot het WAN. De NAT van de router wijst naar deze. Hij bevat dus de statische websites die moeten blijven werken, zelfs als vm-docker uitvalt.
  • vm-docker is de enige die blokkades kan uitvoeren. Het NAS draait op een DSM-kernel zonder xt_set, zonder boom/lib/modules, en sudo vereist een wachtwoord: er zijn geen L3-blokkades mogelijk. De CrowdSec-oplossing draait dus op vm-docker, met 403 applicaties.

De twee gevolgen die uren kosten

Een L3-filter op vm-docker blokkeert niets

Alle WAN-verzoeken komen op vm-docker binnen met nas als bron. Een regel per client-IP is fundamenteel ondoordacht.

Wat wel werkt: trusted_proxies static le NAS/32 in de globale instellingen van Caddy, waardoor het de X-Forwarded-For van nas-caddy leest. De modules die de client-IP gebruiken (waaronder CrowdSec) zien dan de echte.

De statische bestanden die via het NAS worden aangeboden, gaan niet via CrowdSec

Ze gaan nooit door Caddy van vm-docker. De L7-oplossing beschermt ze niet. Dit is een bewuste beperking, geen vergissing.

Authenticatie

De code die gedeeld is, in het Caddyfile van 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
    }
}

En de overeenkomstige oplossing voor statische bestanden die lokaal via het NAS worden aangeboden:

(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
    }
}

Wat forward_auth doet, per verzoek

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

De service ziet nooit het wachtwoord: het ontvangt een verzoek dat al is geautoriseerd, met de identiteit in de headers. Dit is ook zijn beperking: hij kan niet weten of een sessie is beëindigd.

forward_auth gaat voor 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

Van de 39 sites die authentik gebruiken, zijn er 4 die dit handle gebruiken. De andere 35 werken. Het handle is een versterking conform de Authenticik-documentatie, en geen vereiste voor het functioneren. Het generaliseren van de vorm vereist het herschrijven van 35 blokken één voor één — een klus die veel tijd kost, waarbij de sonde als criterium dient, en niet sed.

OIDC eerst, forward_auth daarna, nooit beide

Voor een nieuw service, eerst integrations.goauthentik.io/documentation/<service> onderzoeken. Als de applicatie OIDC ondersteunt, kan deze worden aangesloten.

forward_auth kent de applicatie-identiteit niet: geen account, geen rol, geen eigen afmelden — alleen een muur. Het blijft een goede optie voor situaties waarin er geen sprake is van gebruikers, zoals een statische website.

Het gebruik van beide tegelijk is een valkuil

De proxy vangt /api/auth/callback/* op voordat de applicatie → een lus van verbinding, met drie componenten die allemaal goed lijken.

Sluit de lokale verbinding (DISABLE_PASSWORD_AUTH) na de eerste succesvolle OIDC-verbinding, nooit eerder.

Certificaten

  • Uitgegeven door acme.sh via DNS-01 bij OVH, één certificaat per domein, in /opt/docker/acme/certs/<fqdn>/.
  • Expliciet geladen: auto_https off in beide Caddy-instanties, en een macro (tls_cert) per site.
  • Herladen na vernieuwing via de Caddy-beheer API (POST http://127.0.0.1:2019/load), publiée sur la boucle locale uniquement.
  • Le NAS n'émet rien : les certificats lui sont copiés par un rsync quotidien depuis vm-docker.

Een detail dat veel impact heeft: een upstream-server geïdentificeerd via zijn IP-adres

reverse_proxy https://le NAS

Caddy stuurt vervolgens Host: nas:443. De meeste applicaties maken hierbij een absolute redirect, en de client gaat naar een intern adres. Het is altijd belangrijk om toe te voegen:

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

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