From dbcd5c424074e0103c607e063567dac6cd4d48eb Mon Sep 17 00:00:00 2001 From: Johan LEROY Date: Wed, 23 Sep 2026 12:35:20 +0200 Subject: [PATCH] fix(deploy): passe la prod sur prod.enervision-g3.dynv6.net MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dynv6 sert mal un TXT _acme-challenge à la racine de la zone : l'API ne le liste ni ne le supprime, et un seul de ses trois serveurs le renvoie. Le défi DNS-01 de la prod échouait donc à chaque essai, alors que rec. et dev. passaient. La prod rejoint ses voisines en sous-domaine, ce qui aligne aussi les trois noms sur les environnements. - provision-host.sh : hôte prod.$DOMAINE, enregistrement A prod publié. - Makefile : --dnssleep 90, le temps que les trois serveurs de dynv6 servent le TXT avant la validation multi-réseaux de Let's Encrypt. - deploy.yml, ADR 0018, 10-infra.md, infra/README.md, Terraform. --- .github/workflows/deploy.yml | 2 +- Makefile | 6 +++--- ...8-noms-publics-certificats-dns01-et-frontal-sni.md | 11 +++++++---- docs/architecture/10-infra.md | 2 +- infra/README.md | 2 +- infra/terraform/environments/vm-eni/variables.tf | 2 +- scripts/provision-host.sh | 8 ++++---- 7 files changed, 18 insertions(+), 15 deletions(-) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 1e24499..b5f057b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -28,7 +28,7 @@ jobs: timeout-minutes: 30 environment: name: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }} - url: ${{ github.ref_name == 'main' && 'https://enervision-g3.dynv6.net' || github.ref_name == 'dev' && 'https://rec.enervision-g3.dynv6.net' || 'https://dev.enervision-g3.dynv6.net' }} + url: ${{ github.ref_name == 'main' && 'https://prod.enervision-g3.dynv6.net' || github.ref_name == 'dev' && 'https://rec.enervision-g3.dynv6.net' || 'https://dev.enervision-g3.dynv6.net' }} env: ENVIRONNEMENT: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }} PORT_HTTPS: ${{ github.ref_name == 'main' && '10443' || github.ref_name == 'dev' && '8443' || '9443' }} diff --git a/Makefile b/Makefile index 1cb5669..9d1e036 100644 --- a/Makefile +++ b/Makefile @@ -244,14 +244,14 @@ DNS01_JETON_FICHIER ?= $(abspath $(CURDIR)/../dns.token) acme-sh = docker run --rm --user "$$(id -u):$$(id -g)" -e $(DNS01_JETON_VAR) -e AUTO_UPGRADE=0 \ -v "$(CURDIR)/infra/proxy/acme:/acme.sh" -v "$(CURDIR)/infra/proxy/tls:/tls" $(ACME_SH) -# acme.sh sort en 2 quand le certificat n'est pas encore à renouveler, et recopie le jeton dans -# acme/account.conf : d'où le chmod, qui le soustrait aux autres comptes de la machine. +# acme.sh sort en 2 quand le certificat n'est pas à renouveler, et recopie le jeton dans +# acme/account.conf, d'où le chmod. `--dnssleep` : Let's Encrypt valide depuis plusieurs réseaux. tls-dns01: ## Certificat Let's Encrypt par DNS-01, renouvelé seulement à échéance. Jeton : ../dns.token @case "$(PUBLIC_HOST)" in *.local | localhost) echo "PUBLIC_HOST=$(PUBLIC_HOST) n'est pas un nom public"; exit 1 ;; esac @test -r "$(DNS01_JETON_FICHIER)" || { echo "Jeton DNS illisible : $(DNS01_JETON_FICHIER)"; exit 1; } @mkdir -p infra/proxy/acme && chmod 700 infra/proxy/acme @$(DNS01_JETON_VAR)="$$(tr -d '[:space:]' < "$(DNS01_JETON_FICHIER)")"; export $(DNS01_JETON_VAR); \ - $(acme-sh) --issue --server letsencrypt --dns $(DNS01_API) -d "$(PUBLIC_HOST)"; \ + $(acme-sh) --issue --server letsencrypt --dns $(DNS01_API) --dnssleep 90 -d "$(PUBLIC_HOST)"; \ code=$$?; chmod -R go-rwx infra/proxy/acme; [ $$code -eq 0 ] || [ $$code -eq 2 ] || exit $$code @$(acme-sh) --install-cert --ecc -d "$(PUBLIC_HOST)" \ --fullchain-file /tls/fullchain.pem --key-file /tls/privkey.pem diff --git a/docs/adr/0018-noms-publics-certificats-dns01-et-frontal-sni.md b/docs/adr/0018-noms-publics-certificats-dns01-et-frontal-sni.md index 448e980..27b6928 100644 --- a/docs/adr/0018-noms-publics-certificats-dns01-et-frontal-sni.md +++ b/docs/adr/0018-noms-publics-certificats-dns01-et-frontal-sni.md @@ -20,16 +20,19 @@ et API, depuis les postes comme depuis la VM. ## Décision -**Des noms publics qui visent l'IP privée.** `enervision-g3.dynv6.net`, zone gratuite de dynv6, -porte la prod, et deux enregistrements A portent `rec.` et `dev.`. `provision-host.sh` les publie -par l'API dynv6 : le DNS est décrit par le code comme le reste. Tout +**Des noms publics qui visent l'IP privée.** Dans `enervision-g3.dynv6.net`, zone gratuite de +dynv6, trois enregistrements A portent `prod.`, `rec.` et `dev.`. `provision-host.sh` les publie +par l'API dynv6 : le DNS est décrit par le code comme le reste. La prod n'est pas à la racine de +la zone : dynv6 y sert mal un TXT `_acme-challenge`, que l'API ne liste ni ne supprime et qu'un +seul de ses trois serveurs renvoie (constaté le 23/09), si bien que son défi DNS-01 échoue. Tout poste du réseau de l'école les résout sans configuration ; hors de ce réseau, l'IP ne mène nulle part. **Des certificats Let's Encrypt par défi DNS-01.** Le défi passe par l'API dynv6, qui pose l'enregistrement TXT : Let's Encrypt n'a jamais à joindre la VM. `make tls-dns01` (acme.sh épinglé) le joue dans chaque stack ; il ne renouvelle qu'à échéance, d'où son rejeu à chaque -déploiement et chaque nuit par cron. Un certificat par environnement plutôt qu'un joker : chaque +déploiement et chaque nuit par cron. `--dnssleep 90` laisse aux trois serveurs de dynv6 le temps +de servir le TXT avant que Let's Encrypt ne le cherche depuis plusieurs réseaux. Un certificat par environnement plutôt qu'un joker : chaque stack garde le sien, et la clé de la prod n'est pas lisible depuis le clone de dev. **Un frontal SNI sur 443, le seul composant exposé.** `infra/front`, un nginx sur le réseau de diff --git a/docs/architecture/10-infra.md b/docs/architecture/10-infra.md index 0750c8e..8cea7bd 100644 --- a/docs/architecture/10-infra.md +++ b/docs/architecture/10-infra.md @@ -248,7 +248,7 @@ les secrets et les certificats, et ne démarre rien. |---|---|---|---| | Branche, environnement GitHub | toute branche lancée à la main, `dev` | `dev`, `rec` | `main`, `prod` | | Dossier, projet Compose | `/srv/enervision/dev`, `enervision-dev` | `/srv/enervision/rec`, `enervision-rec` | `/srv/enervision/prod`, `enervision-prod` | -| URL | `https://dev.enervision-g3.dynv6.net` | `https://rec.enervision-g3.dynv6.net` | `https://enervision-g3.dynv6.net` | +| URL | `https://dev.enervision-g3.dynv6.net` | `https://rec.enervision-g3.dynv6.net` | `https://prod.enervision-g3.dynv6.net` | | Proxy HTTP, HTTPS, PROXY protocol, sur `127.0.0.1` | `8083`, `9443`, `9444` | `8081`, `8443`, `8444` | `10080`, `10443`, `10444` | | PostgreSQL, Mailpit, Airflow, sur `127.0.0.1` | `5435`, `8027`, `8084` | `5434`, `8026`, `8082` | `5433`, `8025`, `8080` | | Supervision (profil `monitoring`) | à la demande, `make monitoring-up` | à la demande, `make monitoring-up` | active, `COMPOSE_PROFILES=monitoring` | diff --git a/infra/README.md b/infra/README.md index 506009f..6f548ce 100644 --- a/infra/README.md +++ b/infra/README.md @@ -40,7 +40,7 @@ sur `dev` et sur `main`, et a chaque lancement manuel d'une autre branche pour ` Noms et certificats (ADR 0018) : avant l'apply, la zone `domaine` doit exister chez dynv6 et son jeton se trouver dans `/dns.token` (600, proprietaire). L'apply fait alors pointer la -zone, `rec` et `dev` vers la machine, obtient un certificat Let's Encrypt par environnement et planifie leur +zone, `prod`, `rec` et `dev` vers la machine, obtient un certificat Let's Encrypt par environnement et planifie leur renouvellement ; sans jeton, chaque environnement garde un certificat auto-signe. Le frontal SNI (`infra/front`) se demarre une fois depuis le dossier de la prod, `make front-up`. diff --git a/infra/terraform/environments/vm-eni/variables.tf b/infra/terraform/environments/vm-eni/variables.tf index 50e60f3..41e46a0 100644 --- a/infra/terraform/environments/vm-eni/variables.tf +++ b/infra/terraform/environments/vm-eni/variables.tf @@ -45,7 +45,7 @@ variable "depot_url" { variable "domaine" { type = string - description = "Zone dynv6 de la prod, rec. et dev. en sous-domaines (ADR 0018). Son jeton doit se trouver dans /dns.token sur la machine : provision-host.sh y fait pointer la zone et ses deux sous-domaines vers la machine." + description = "Zone dynv6 des trois environnements, prod., rec. et dev. en sous-domaines (ADR 0018). Son jeton doit se trouver dans /dns.token sur la machine : provision-host.sh y fait pointer la zone et ses trois sous-domaines vers la machine." default = "enervision-g3.dynv6.net" } diff --git a/scripts/provision-host.sh b/scripts/provision-host.sh index 70e01ba..dcd28c8 100755 --- a/scripts/provision-host.sh +++ b/scripts/provision-host.sh @@ -141,7 +141,7 @@ preparer() { # jeton passe par l'environnement du seul processus Python, jamais par `argv`. publier_dns() { [[ -r "$JETON_DNS" ]] || { echo "pas de jeton $JETON_DNS : ni DNS ni Let's Encrypt"; return 0; } - DNS_TOKEN="$(tr -d '[:space:]' < "$JETON_DNS")" python3 - "$DOMAINE" "$ADRESSE" rec dev <<'PY' \ + DNS_TOKEN="$(tr -d '[:space:]' < "$JETON_DNS")" python3 - "$DOMAINE" "$ADRESSE" prod rec dev <<'PY' \ || echo "DNS : dynv6 refuse la mise à jour de $DOMAINE, enregistrements inchangés" >&2 import json, os, sys, time, urllib.request @@ -198,10 +198,10 @@ verifier_outils mkdir -p "$RACINE" publier_dns -# Supervision active en prod seulement (ADR 0016). Le frontal (infra/front) publie 80 et 443 et -# relaie vers les ports `front` ; les stacks ne publient plus rien hors de la boucle locale. +# Supervision active en prod seulement (ADR 0016). La prod vit sur `prod.` et non à la racine : +# dynv6 ne sert pas de façon fiable un TXT `_acme-challenge` à la racine de la zone (ADR 0018). # env branche hôte https http front pg mailpit airflow profils grafana prometheus alertmanager -preparer prod main "$DOMAINE" 127.0.0.1:10443 127.0.0.1:10080 127.0.0.1:10444 5433 8025 8080 monitoring 3001 9090 9093 +preparer prod main "prod.$DOMAINE" 127.0.0.1:10443 127.0.0.1:10080 127.0.0.1:10444 5433 8025 8080 monitoring 3001 9090 9093 preparer rec dev "rec.$DOMAINE" 127.0.0.1:8443 127.0.0.1:8081 127.0.0.1:8444 5434 8026 8082 "" 3002 9091 9094 preparer dev dev "dev.$DOMAINE" 127.0.0.1:9443 127.0.0.1:8083 127.0.0.1:9444 5435 8027 8084 "" 3003 9092 9095