Toutes les pages
Sur cette page

Faire tourner le relais derrière un proxy inverse

Un proxy inverse est un serveur web à vous qui reçoit la connexion du navigateur et la transmet au relais. Il vous en faut un quand vous voulez HTTPS pour un relais sur votre LAN ou sur un serveur, quand le relais doit vivre sous un sous-chemin d’un site que vous faites déjà tourner (https://example.com/oar/), ou quand la périphérie d’un tunnel (par exemple Cloudflare) se trouve entre le navigateur et le relais. Un proxy sur la machine même du relais fonctionne sans aucun réglage. Un proxy sur une autre machine en demande deux ou trois, présentés plus bas. Cinq clés de config.json renseignent le relais sur le proxy, et chacune règle un point précis ; elles sont expliquées ensuite.

Ce que le proxy doit faire #

La page du relais, son API et sa connexion en direct utilisent toutes un seul port (3333 sauf si vous avez modifié port). Le proxy doit :

  • Laisser passer les upgrades WebSocket. L’app garde une connexion Socket.IO ouverte vers le relais, sur le chemin /socket.io/ (avec un préfixe, <prefix>/socket.io/). Si les upgrades ne passent pas, la page se charge mais n’affiche jamais de mises à jour en direct.
  • Ne pas mettre en tampon ni couper les connexions de longue durée. Ce socket reste ouvert tant que la page est ouverte, et les turns y sont diffusés au fil de l’eau. Utilisez de longs délais de lecture et d’inactivité, et ne mettez pas les réponses en tampon.
  • Transmettre l’en-tête Host, ou envoyer X-Forwarded-Host. Le relais construit l’adresse d’un lien partagé à partir de lui et Ajouté en 0.9.11 n’accepte la connexion en direct que depuis une page à cette adresse (voir allowedOrigins). X-Forwarded-Host et X-Forwarded-Proto ne comptent que venant d’un proxy auquel le relais fait confiance.
  • Envoyer X-Forwarded-Proto quand le proxy termine TLS, pour que le relais sache que le navigateur a utilisé HTTPS (voir Cookies et HTTPS), ainsi que X-Forwarded-For, pour qu’il connaisse l’adresse du client.
  • Garder le chemin tel quel. Ne le réécrivez pas. Si vous servez le relais sous un préfixe, le relais retire lui-même le préfixe (voir remotePath).

Vous n’avez pas besoin de règle de cache. Le relais envoie Cache-Control: no-store pour sa page, son service worker, son manifeste et son API, ainsi que CDN-Cache-Control: no-store et Cloudflare-CDN-Cache-Control: no-store. N’ajoutez pas de règle qui met /api/shared/* en cache : les vues partagées interrogent régulièrement le relais pour le nouveau contenu, et une réponse en cache montrerait aux lecteurs un ancien instantané.

Un exemple Caddy minimal pour un relais sur la même machine. Caddy laisse passer les upgrades WebSocket de lui-même et ajoute X-Forwarded-Proto :

relay.example.com {
    reverse_proxy 127.0.0.1:3333
}

Pour nginx ou tout autre proxy, cherchez dans sa propre documentation comment laisser passer les upgrades et régler les délais, en suivant la liste ci-dessus. Pour les tunnels, voir Tunnel SSH et Cloudflare Tunnel ; pour le token d’accès, voir Accès.

Un proxy sur une autre machine Ajouté en 0.9.11 #

Les tunnels intégrés et un proxy sur la machine même du relais joignent le relais par 127.0.0.1, et le trustProxy par défaut croit leurs en-têtes transmis. Un proxy sur un autre hôte, ou dans un autre conteneur ou une autre VM, est un pair différent. Tant que vous ne lui faites pas confiance, le relais ignore ses X-Forwarded-For, X-Forwarded-Host et X-Forwarded-Proto ; donc :

  • tous les visiteurs ont l’adresse du proxy : un seul blocage de connexion pour eux tous, et une seule adresse dans chaque entrée du journal d’accès ;
  • les nouveaux liens de partage commencent par http:// et l’hôte que le proxy a envoyé ;
  • le cookie de connexion n’est pas marqué Secure.

Réglez trois clés dans le fichier de config et redémarrez le relais :

{
  "localhostOnly": false,
  "trustProxy": "192.0.2.10",
  "allowedOrigins": ["https://relay.example.com"]
}
  • localhostOnly: false permet au relais d’écouter au-delà du bouclage. Gardez le port fermé à tout sauf au proxy.
  • trustProxy est l’adresse du proxy, son sous-réseau, ou le nombre de proxys devant le relais (1 pour un seul). Voir plus bas.
  • allowedOrigins n’est nécessaire que si le proxy réécrit l’en-tête Host et n’envoie pas de X-Forwarded-Host. Voir plus bas.

Un proxy sur la machine même du relais n’a besoin ni de trustProxy ni de localhostOnly: false, et de allowedOrigins seulement s’il réécrit Host. Sous un préfixe d’URL, réglez aussi remotePath.

Les clés qui renseignent le relais sur le proxy #

Les cinq figurent dans le tableau de config.json. Redémarrez le relais après les avoir modifiées.

trustProxy #

Par défaut "loopback". Elle décide de qui le relais croit les en-têtes X-Forwarded-*. Avec la valeur par défaut, seul un proxy qui se connecte depuis la même machine est cru, ce qui convient à Caddy, nginx ou cloudflared tournant à côté du relais.

Ce que le relais tire des en-têtes d’un proxy de confiance :

  • si la requête est arrivée en HTTPS, ce qui décide si le cookie de connexion porte le drapeau Secure, et le schéma d’un nouveau lien de partage ;
  • l’hôte auquel le navigateur s’est adressé (X-Forwarded-Host), pour les nouveaux liens de partage et pour la vérification d’origine de la connexion en direct ;
  • l’adresse du client, pour le blocage de connexion, la limite de présence des vues partagées et le journal d’accès des liens partagés. Le pays dans ce journal (cf-ipcountry) n’est lui aussi lu que venant d’un proxy de confiance.

Si votre proxy tourne sur une autre machine, le relais voit un pair non local et ignore ces en-têtes. Donnez à trustProxy ce qu’accepte le trust proxy d’Express : un nombre de sauts (1), une adresse ou un sous-réseau ("10.0.0.0/8"), une liste de ceux-ci, ou "loopback".

Seulement avec un proxy devant

Ne réglez trustProxy plus largement que "loopback" que si chaque connexion au relais passe vraiment par votre proxy. Sinon, quiconque peut joindre le port peut envoyer un X-Forwarded-Proto falsifié.

Ajouté en 0.9.11 Chaque utilisation d’un en-tête transmis suit trustProxy. En 0.9.10, la limite de présence des vues partagées et l’adresse d’un nouveau lien de partage les lisaient venant de n’importe quel client.

allowedOrigins Ajouté en 0.9.11 #

Par défaut []. La connexion en direct (Socket.IO) n’accepte la poignée de main d’un navigateur que depuis l’origine propre du relais : l’Origin que le navigateur envoie doit avoir l’hôte auquel le navigateur s’est adressé, c’est-à-dire X-Forwarded-Host quand un proxy de confiance en envoie un, et Host sinon. Une page d’un autre site ne peut pas utiliser votre cookie de connexion pour ouvrir une connexion en direct. Les clients qui n’envoient pas d’Origin, comme les scripts, ne sont pas concernés.

Un proxy qui réécrit l’en-tête Host vers l’adresse interne du relais et n’envoie pas de X-Forwarded-Host casse cette comparaison. Listez alors l’origine publique, exactement comme le navigateur l’affiche, avec schéma, hôte et tout port autre que celui par défaut :

"allowedOrigins": ["https://relay.example.com"]

Sans cela, la page se charge et vous pouvez vous connecter, mais rien ne se met à jour en direct, et le relais écrit une fois par origine dans le journal :

[socket] Refused a handshake from origin https://relay.example.com (host 127.0.0.1:3333); list it in allowedOrigins if a proxy rewrites the Host header

remotePath #

Par défaut "". Réglez-la sur le préfixe quand le proxy sert le relais sous un sous-chemin, par exemple "/oar". Une barre oblique initiale est ajoutée et les barres obliques finales sont retirées.

Quand elle est réglée, le relais :

  • retire le préfixe de chaque requête entrante ; le proxy transmet donc le chemin avec le préfixe et ne le retire pas ;
  • met le préfixe dans le chemin du cookie de connexion, le chemin du socket (/oar/socket.io/), et les adresses des fichiers et des liens partagés.

Sans elle, un relais sous un préfixe ne trouve ni ses propres fichiers ni son socket, parce que la page demande /socket.io/ et /api/... à la racine du site. Ouvrez le relais avec une barre oblique finale (https://example.com/oar/).

Une app installée appartient à l’adresse depuis laquelle elle a été installée ; un relais sous un préfixe est donc une app à part. Voir Installer comme app.

publicHostnames #

Par défaut []. Listez les noms d’hôte qui joignent le relais lui-même, par exemple ["relay.example.com"]. Le relais ne s’en sert qu’à un endroit : la preview lane refuse un previews.publicBaseUrl sur l’un de ces noms (ou sur localhost ou 127.0.0.1), pour qu’une app en aperçu ne partage jamais de cookie de connexion avec le relais. Si vous placez le relais derrière un proxy sous un nouveau nom et utilisez les aperçus, ajoutez ce nom ici.

tunnelMarkerHeaders #

Par défaut []. Les noms des en-têtes que votre proxy ou la périphérie de votre tunnel ajoute à chaque requête qu’il transmet. Le relais s’en sert pour le garde-fou des chemins du worker de session ci-dessous. cf-ray, qu’ajoute Cloudflare, est toujours dans la liste. La valeur peut être un tableau ou une chaîne séparée par des virgules. La variable d’environnement COPILOT_TUNNEL_MARKER_HEADERS (séparée par des virgules) ajoute d’autres noms ; Ajouté en 0.9.11 les deux listes sont fusionnées, donc tunnelMarkerHeaders: [] ne désactive plus la variable. Voir Variables d’environnement.

La pastille d’état

tunnelMarkerHeaders ne fait pas passer la pastille d’état à l’ambre. La pastille est ambre tant que le tunnel Cloudflare géré par le relais lui-même est connecté (voir le menu de la conversation). Un proxy que vous faites tourner vous-même ne la fait jamais passer à l’ambre.

Le garde-fou des chemins du worker de session #

Chaque session d’agent tourne dans un worker sur la machine du relais, et le worker parle au relais par un WebSocket sur /api/session-worker/ws (avec un préfixe, <prefix>/api/session-worker/ws). Un proxy ou un tunnel transmet au relais tous les chemins de son nom d’hôte ; sans garde-fou, ce chemin serait donc joignable depuis internet. Seuls les workers locaux doivent l’utiliser.

Ajouté en 0.9.11 Le garde-fou ne laisse passer une requête vers ce chemin que si elle vient de la machine même du relais et que rien ne l’a transmise :

  • le pair qui se connecte est en bouclage (127.0.0.1 ou ::1) ;
  • elle ne porte aucun en-tête marqueur : cf-ray, ou l’un de ceux de tunnelMarkerHeaders ;
  • elle ne porte ni X-Forwarded-For, ni Forwarded, ni X-Real-IP.

Tout le reste est refusé avant la vérification de la connexion :

  • une requête simple reçoit 403 avec {"error":"Session worker endpoints are reachable from this machine only"} ;
  • un upgrade WebSocket reçoit 403 Forbidden et la connexion est fermée.

Chaque refus est écrit dans le journal sous la forme [tunnel-guard] Blocked session-worker path …: not local (…), en nommant ce qui a trahi la requête. Les workers locaux se connectent à 127.0.0.1 directement, sans aucun de ces en-têtes, et ne sont pas concernés. Les liens partagés continuent de fonctionner par le tunnel : le garde-fou porte sur le chemin du worker seulement, pas sur le fait qu’une requête soit anonyme.

Cloudflare ajoute cf-ray et Caddy ajoute X-Forwarded-For de lui-même ; le garde-fou fonctionne donc avec eux sans réglage. Avec nginx, envoyez X-Forwarded-For avec proxy_set_header. Un intermédiaire qui n’ajoute rien du tout, comme un tunnel SSH inverse ou un simple proxy TCP, ressemble à un processus local ; le chemin n’est alors protégé que par le token d’accès. Avec un tel intermédiaire, faites-lui ajouter un en-tête avec une valeur fixe non vide, par exemple X-Oar-Edge: 1, et listez son nom : "tunnelMarkerHeaders": ["x-oar-edge"]. Ou refusez le chemin devant le relais.

Les points d’accès réservés à la machine du relais #

Ajouté en 0.9.11 Deux choses ne répondent qu’aux requêtes venant de la machine même du relais qu’aucun proxy n’a transmises, selon la même règle que le garde-fou des chemins :

  • le socket des workers de session, ci-dessus ;
  • l’exécution d’une opération d’image (POST /api/image-operations/…/execute), qu’appellent les propres workers du relais. Tout le reste reçoit 403 Image operation execution is reachable from this machine only (L’exécution d’opérations d’image n’est accessible que depuis cette machine).

Tout le reste ne demande que le token d’accès, d’où qu’il vienne. Cela inclut 🌄 Restart web relay (Redémarrer le relais web), le retrait d’un redémarrage en file, avec le bouton Cancel (Annuler) de sa bannière, et 🚮 Empty queue (Vider la file) : tout navigateur connecté peut s’en servir, y compris par le LAN et à travers n’importe quel proxy ou tunnel.

Jusqu’à la 0.9.10, ces trois actions étaient censées être réservées à la machine du relais, mais la vérification laissait tout passer par un tunnel ou un proxy sur la même machine, et refusait une connexion LAN directe alors que la page affichait un succès. Si des utilisateurs distants ne doivent pas redémarrer le relais ni vider sa queue, faites refuser par le proxy POST /api/relay/shutdown, POST /api/relay/shutdown/cancel et POST /api/queue/empty. oar restart sur la machine du relais fonctionne toujours.

Cookies et HTTPS #

Le cookie de connexion est HttpOnly et SameSite=Lax, dure 30 jours et a le chemin / (ou le préfixe remotePath). Il porte Secure quand le relais considère la requête comme sécurisée.

  • Le proxy termine TLS sur la même machine. Le proxy envoie X-Forwarded-Proto: https, le trustProxy par défaut le croit, et le cookie est Secure.
  • Le proxy termine TLS sur une autre machine. Réglez trustProxy comme ci-dessus. Tant que vous ne l’avez pas fait, le cookie n’a pas de drapeau Secure, mais il fonctionne quand même.
  • Configurations mixtes. Si vous ouvrez aussi le relais directement en http simple sur le LAN, cette requête n’est pas sécurisée, donc son cookie n’a pas de drapeau Secure ; les requêtes HTTPS passant par le proxy reçoivent le drapeau. Les navigateurs gardent les cookies par adresse.

Vérifier la configuration #

  1. Ouvrez l’adresse du proxy dans un navigateur. La connexion ou la liste des conversations apparaît.
  2. Regardez la pastille d’état : elle passe au vert et la page affiche des mises à jour en direct quand vous démarrez un turn. Un état de reconnexion sans fin signifie que le WebSocket ne passe pas.
  3. Sous un préfixe, ouvrez l’adresse avec la barre oblique finale. La page, ses styles et la connexion en direct se chargent, et le chemin du cookie est le préfixe (regardez dans les outils de développement du navigateur).
  4. Créez un lien partagé (Partage). Son adresse utilise le nom d’hôte public et https, ainsi que le préfixe si vous en avez réglé un.
  5. Testez le garde-fou depuis l’extérieur : demandez https://relay.example.com/api/session-worker/ws. Vous devez voir 403 et une ligne [tunnel-guard] dans le journal du relais. Si vous obtenez une autre réponse, votre proxy n’ajoute ni en-tête marqueur ni X-Forwarded-For.
  6. Derrière un proxy sur une autre machine : connectez-vous depuis deux appareils, et vérifiez dans Settings → Sharing (Réglages → Partage) que le journal d’accès d’un lien partagé affiche les adresses des lecteurs, pas celle du proxy. S’il affiche celle du proxy, trustProxy ne correspond pas au proxy.

Si ça ne marche pas #

Ce que vous voyez Cause probable
La page se charge mais se reconnecte sans fin, ou ne se met jamais à jour Le proxy ne laisse pas passer les upgrades WebSocket sur /socket.io/, ou ferme les connexions inactives.
La page se charge mais rien ne se met à jour en direct, et le journal dit Refused a handshake from origin Le proxy réécrit l’en-tête Host. Listez l’origine publique dans allowedOrigins, ou faites transmettre Host par le proxy.
La connexion affiche Too many failed sign-in attempts (Trop de tentatives de connexion échouées) pour tout le monde à la fois Le relais ne fait pas confiance à un proxy sur une autre machine ; tous les visiteurs partagent donc son adresse et son blocage. Réglez trustProxy.
Sous un préfixe, la page est vide ou des fichiers manquent remotePath n’est pas réglée, ou le proxy retire le préfixe. Le proxy doit le transmettre.
Sous un préfixe, on vous demande sans cesse de vous reconnecter Le chemin du cookie est le préfixe remotePath ; le navigateur ne l’envoie donc que sous ce préfixe. Ouvrez le relais sous ce préfixe, et avec la barre oblique finale.
Les liens partagés ou les liens de fichiers ont une mauvaise adresse ou pas de préfixe remotePath n’est pas réglée, ou le proxy n’envoie ni Host ni X-Forwarded-Host / X-Forwarded-Proto, ou le relais ne fait pas confiance au proxy.
Les liens de partage commencent par http://, ou le cookie n’a pas de drapeau Secure, derrière un proxy sur une autre machine trustProxy vaut encore "loopback".
Le chemin du worker répond sans 403 à travers votre proxy Le proxy n’ajoute ni en-tête marqueur ni X-Forwarded-For. Voir le garde-fou des chemins.
La pastille ne passe jamais à l’ambre Normal : seul le tunnel Cloudflare géré par le relais lui-même le fait.
Les aperçus refusent de démarrer sur le nouveau nom Ajoutez le nom d’hôte du relais à publicHostnames.

Pour la première connexion et le token d’accès, voir Premier lancement.