From bdeaa9fa7feb2aebe0a8b8f6012c3fc00cf3e1db Mon Sep 17 00:00:00 2001 From: Cyber Mawonaj Date: Sat, 25 Jul 2026 22:51:05 -0400 Subject: [PATCH] =?UTF-8?q?D=C3=A9ploiement=20:=20plus=20aucune=20valeur?= =?UTF-8?q?=20d'exemple=20=C3=A0=20corriger=20=C3=A0=20la=20main?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit L'unité web portait ORIGIN=https://veille.example.org : un fichier versionné qu'il fallait éditer à chaque déploiement, et dont l'oubli casse silencieusement les soumissions de formulaire. Remplacé par PROTOCOL_HEADER / HOST_HEADER : l'application déduit son origine des en-têtes du reverse proxy et fonctionne donc sur n'importe quel domaine, sans édition. Vérifié en exécutant réellement le binaire construit : deux domaines différents servis par le même processus, HTTP 200 dans les deux cas, et un ORIGIN figé l'emporte bien sur les en-têtes. Au passage, une affirmation fausse que j'avais écrite en commentaire : « le .env l'emporte sur les variables systemd ». C'est l'inverse — le --env-file de Node ne remplace pas une variable déjà présente dans l'environnement (constaté sur Node 24). Le commentaire dit désormais pourquoi ORIGIN dans .env fonctionne quand même : parce que l'unité ne le déclare pas. Et un contrôle mécanique interdit désormais de le redéclarer. ExecStart passe par /usr/bin/env : NodeSource installe node dans /usr/bin, une compilation manuelle dans /usr/local/bin, un chemin figé échouait sur l'un des deux. Deux contrôles ajoutés à make verifier : aucune valeur d'exemple active dans les unités systemd, et ORIGIN laissé au .env. Co-Authored-By: Claude Opus 5 --- .env.example | 15 ++++++++----- README.md | 25 ++++++++++++++++----- scripts/verifier-conformite.sh | 18 +++++++++++++++ systemd/veille-web.service | 40 +++++++++++++++++++++++++++++----- 4 files changed, 83 insertions(+), 15 deletions(-) diff --git a/.env.example b/.env.example index 0ca9346..e2dc9e7 100644 --- a/.env.example +++ b/.env.example @@ -51,10 +51,15 @@ VEILLE_NOTIF_DESTINATAIRE= # Node ≥ 20.6 : lancer avec `node --env-file=.env build` HOST=127.0.0.1 PORT=3971 -# Obligatoire derrière un reverse proxy, sinon « Cross-site POST form -# submissions are forbidden ». Alternative : PROTOCOL_HEADER + HOST_HEADER. -ORIGIN=https://veille.example.org -# PROTOCOL_HEADER=x-forwarded-proto -# HOST_HEADER=x-forwarded-host # Mise en sommeil de l'app sans trafic (activation par socket systemd). IDLE_TIMEOUT=300 + +# Origine des requêtes. Rien à renseigner dans le cas courant : l'unité +# systemd fait déduire l'origine des en-têtes posés par le reverse proxy +# (PROTOCOL_HEADER / HOST_HEADER), ce qui fonctionne quel que soit le domaine. +# +# Décommentez seulement si vous préférez la figer — adapter-node la retient +# alors d'office. Cela fonctionne parce que l'unité systemd ne déclare PAS +# ORIGIN : le `--env-file` de Node ne remplace jamais une variable déjà +# présente dans l'environnement. +# ORIGIN=https://votre-domaine diff --git a/README.md b/README.md index f208579..92b32b2 100644 --- a/README.md +++ b/README.md @@ -146,12 +146,26 @@ démarre Node qu'à la première requête ; sans trafic pendant `IDLE_TIMEOUT` (300 s), elle s'arrête. Sur un site de veille peu fréquenté, l'empreinte mémoire tombe à zéro entre deux visites — ce qui compte sur un serveur à 5 € par mois. -Avant de démarrer, ajustez `ORIGIN` dans `veille-web.service` : sans lui, les -soumissions de formulaire sont refusées derrière un reverse proxy. +**Aucun fichier n'est à éditer avant de démarrer.** L'unité déduit son origine +des en-têtes posés par le reverse proxy (`PROTOCOL_HEADER` / `HOST_HEADER`), +donc elle fonctionne quel que soit votre domaine. Sans cette origine, +adapter-node refuserait les soumissions de formulaire (« Cross-site POST form +submissions are forbidden »). + +Ces en-têtes ne sont dignes de confiance que parce que l'application n'écoute +que sur `127.0.0.1` : seul le proxy local peut les poser. **N'exposez jamais le +port 3971 directement** — l'origine deviendrait falsifiable par le client. + +Pour figer l'origine plutôt que la déduire, renseignez `ORIGIN=` dans `.env` : +adapter-node la retient d'office, sa résolution s'écrivant +`origin || get_origin(headers)`. ### Reverse proxy -**Caddy** (le plus court, HTTPS automatique) : +Remplacez `veille.example.org` par votre domaine dans les deux exemples. + +**Caddy** (le plus court, HTTPS automatique — il pose les en-têtes attendus +sans configuration supplémentaire) : ```caddy veille.example.org { @@ -180,8 +194,9 @@ server { } ``` -Avec nginx, remplacez `ORIGIN` par `PROTOCOL_HEADER=x-forwarded-proto` et -`HOST_HEADER=x-forwarded-host` dans le service. +Les trois en-têtes `X-Forwarded-*` de cet exemple ne sont pas décoratifs : +ce sont eux dont l'application déduit son origine. Les omettre casserait les +soumissions de formulaire. ### Vérifier diff --git a/scripts/verifier-conformite.sh b/scripts/verifier-conformite.sh index 6b736ed..06d1bef 100644 --- a/scripts/verifier-conformite.sh +++ b/scripts/verifier-conformite.sh @@ -67,6 +67,24 @@ nb_polices=$(find web/static/polices -name '*.woff2' 2>/dev/null | wc -l) ok "polices auto-hébergées ($nb_polices fichiers woff2)" || ko "polices non auto-hébergées — lancer npm run polices" +titre "Déploiement — pas de valeur d'exemple à corriger à la main" + +# Les unités systemd sont copiées telles quelles dans /etc/systemd/system : +# un domaine d'exemple qui y traîne casse le déploiement sans le dire. +gabarits=$(grep -nE '^[^#]*(example\.(org|com)|votre-domaine|CHANGEME|TODO)' systemd/*.service systemd/*.socket systemd/*.timer 2>/dev/null || true) +if [ -z "$gabarits" ]; then + ok "aucune valeur d'exemple active dans les unités systemd" +else + ko "valeur d'exemple à remplacer dans une unité systemd :" + echo "$gabarits" +fi + +if grep -qE '^[^#]*Environment=ORIGIN=' systemd/veille-web.service; then + ko "ORIGIN déclaré dans l'unité : le .env ne pourrait plus le surcharger" +else + ok "ORIGIN laissé au .env, l'origine est déduite des en-têtes du proxy" +fi + titre "§8 — livrables" for chemin in pipeline web data systemd tests README.md README-pipeline.md .env.example Makefile; do diff --git a/systemd/veille-web.service b/systemd/veille-web.service index 2b35978..c77631e 100644 --- a/systemd/veille-web.service +++ b/systemd/veille-web.service @@ -17,16 +17,46 @@ Type=simple User=veille Group=veille WorkingDirectory=/opt/veille-legislative/web -ExecStart=/usr/bin/node --env-file=/opt/veille-legislative/.env build +# `env` plutôt qu'un chemin figé : NodeSource installe dans /usr/bin, une +# compilation manuelle dans /usr/local/bin, et les deux sont dans le PATH par +# défaut de systemd. Un chemin en dur échouerait sur la moitié des machines. +ExecStart=/usr/bin/env node --env-file=/opt/veille-legislative/.env build Environment=NODE_ENV=production Environment=VEILLE_DB=/opt/veille-legislative/data/veille.db # Mise en sommeil après cinq minutes sans requête. Environment=IDLE_TIMEOUT=300 -# Obligatoire derrière un reverse proxy, sinon les soumissions de formulaire -# sont refusées (« Cross-site POST form submissions are forbidden »). -# Alternative : PROTOCOL_HEADER=x-forwarded-proto + HOST_HEADER=x-forwarded-host -Environment=ORIGIN=https://veille.example.org + +# ── Origine des requêtes, derrière un reverse proxy ────────────────────────── +# +# Sans indication d'origine, adapter-node refuse les soumissions de formulaire +# (« Cross-site POST form submissions are forbidden »). +# +# Deux façons de la lui donner. On retient ici celle qui ne dépend pas du +# domaine : l'application déduit l'origine des en-têtes que pose le reverse +# proxy. Cette unité fonctionne donc telle quelle, quel que soit le domaine — +# aucun fichier versionné n'est à éditer au déploiement. +# +# Ces en-têtes ne sont dignes de confiance que parce que l'application n'écoute +# que sur 127.0.0.1 (voir veille-web.socket) : seul le proxy local peut les +# poser. Exposer directement le port rendrait l'origine falsifiable par le +# client — ne le faites pas. +# +# Le reverse proxy DOIT poser ces trois en-têtes ; les exemples nginx et Caddy +# du README le font. +Environment=PROTOCOL_HEADER=x-forwarded-proto +Environment=HOST_HEADER=x-forwarded-host +Environment=ADDRESS_HEADER=x-forwarded-for +Environment=XFF_DEPTH=1 + +# Alternative : figer l'origine au lieu de la déduire. Renseignez +# ORIGIN=https://votre-domaine dans /opt/veille-legislative/.env ; adapter-node +# la retient d'office, sa résolution s'écrivant `origin || get_origin(headers)`. +# +# Cela ne fonctionne QUE parce que ORIGIN n'est pas déclarée ci-dessus : le +# `--env-file` de Node ne remplace pas une variable déjà présente dans +# l'environnement, c'est l'environnement qui gagne (vérifié sur Node 24). Ne +# déclarez donc jamais ORIGIN ici, sinon le fichier .env resterait sans effet. Restart=on-failure RestartSec=5