fix(deploy): passe de DuckDNS à deSEC, filtré par l'école

Le filtrage du réseau de l'école bloque duckdns.org, site et API, depuis
les postes comme depuis la VM : sans API, pas de défi DNS-01. deSEC
(dedyn.io) répond depuis les deux.

- Domaine enervision-g3.dedyn.io ; provision-host.sh publie par l'API
  deSEC l'enregistrement du domaine et son joker vers la VM.
- make tls-desec remplace tls-duckdns. acme.sh recopie le jeton dans
  acme/account.conf : le dossier est retiré aux autres comptes.
- deploy.yml ne demande un certificat qu'à un .env déjà réaligné sur
  le domaine deSEC, pour ne pas faire échouer un déploiement en cours
  de migration.
This commit is contained in:
Johan LEROY
2026-09-23 11:38:44 +02:00
parent 3e871a3e8b
commit d687d7dc58
8 changed files with 74 additions and 48 deletions
+3 -2
View File
@@ -28,7 +28,7 @@ jobs:
timeout-minutes: 30 timeout-minutes: 30
environment: environment:
name: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }} name: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }}
url: ${{ github.ref_name == 'main' && 'https://enervision-g3.duckdns.org' || github.ref_name == 'dev' && 'https://rec.enervision-g3.duckdns.org' || 'https://dev.enervision-g3.duckdns.org' }} url: ${{ github.ref_name == 'main' && 'https://enervision-g3.dedyn.io' || github.ref_name == 'dev' && 'https://rec.enervision-g3.dedyn.io' || 'https://dev.enervision-g3.dedyn.io' }}
env: env:
ENVIRONNEMENT: ${{ github.ref_name == 'main' && 'prod' || github.ref_name == 'dev' && 'rec' || 'dev' }} 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' }} PORT_HTTPS: ${{ github.ref_name == 'main' && '10443' || github.ref_name == 'dev' && '8443' || '9443' }}
@@ -54,7 +54,8 @@ jobs:
echo "::endgroup::" echo "::endgroup::"
echo "::group::Reconstruit et redémarre la stack" echo "::group::Reconstruit et redémarre la stack"
if [ -r /srv/enervision/duckdns.token ]; then make tls-duckdns; fi # Un `.env` pas encore réaligné par provision-host.sh porte encore un nom en `.local`.
if [ -r ../desec.token ] && grep -q '^PUBLIC_HOST=.*\.dedyn\.io$' .env; then make tls-desec; fi
make stack-up make stack-up
if [ "${ENVIRONNEMENT}" = prod ]; then make front-up; fi if [ "${ENVIRONNEMENT}" = prod ]; then make front-up; fi
echo "::endgroup::" echo "::endgroup::"
+13 -12
View File
@@ -73,7 +73,7 @@ DEMO_NOW ?= 2024-12-31T00:00:00Z
migrate migrate-test bootstrap-admin services-up demo-data demo-data-force \ migrate migrate-test bootstrap-admin services-up demo-data demo-data-force \
ml-lint ml-typecheck ml-test ml-check ml-train ml-score mlflow-up detect-alerts recommendations \ ml-lint ml-typecheck ml-test ml-check ml-train ml-score mlflow-up detect-alerts recommendations \
airflow-lint airflow-test airflow-check airflow-up airflow-down airflow-logs \ airflow-lint airflow-test airflow-check airflow-up airflow-down airflow-logs \
tls-selfsigned tls-acme tls-renew tls-duckdns front-up stack-up stack-down stack-logs \ tls-selfsigned tls-acme tls-renew tls-desec front-up stack-up stack-down stack-logs \
e2e-install e2e-prepare e2e load-smoke load-test load-stress load-limits \ e2e-install e2e-prepare e2e load-smoke load-test load-stress load-limits \
db-ensure-supervision monitoring-up monitoring-down monitoring-logs monitoring-check db-ensure-supervision monitoring-up monitoring-down monitoring-logs monitoring-check
@@ -236,20 +236,21 @@ tls-renew: ## Renouvelle les certificats Let's Encrypt et recharge le proxy
$(COMPOSE_PROD) exec proxy nginx -s reload $(COMPOSE_PROD) exec proxy nginx -s reload
# Pourquoi : la VM n'a qu'une IP privée, que Let's Encrypt ne joint pas ; le défi DNS-01 passe # Pourquoi : la VM n'a qu'une IP privée, que Let's Encrypt ne joint pas ; le défi DNS-01 passe
# par l'API DuckDNS (ADR 0018). Le jeton transite par l'environnement, jamais par `argv`. # par l'API deSEC (ADR 0018). Le jeton transite par l'environnement, jamais par `argv`.
ACME_SH := neilpang/acme.sh:3.1.6 ACME_SH := neilpang/acme.sh:3.1.6
DUCKDNS_TOKEN_FILE ?= $(abspath $(CURDIR)/../duckdns.token) DESEC_TOKEN_FILE ?= $(abspath $(CURDIR)/../desec.token)
acme-sh = docker run --rm --user "$$(id -u):$$(id -g)" -e DuckDNS_Token -e AUTO_UPGRADE=0 \ acme-sh = docker run --rm --user "$$(id -u):$$(id -g)" -e DEDYN_TOKEN -e AUTO_UPGRADE=0 \
-v "$(CURDIR)/infra/proxy/acme:/acme.sh" -v "$(CURDIR)/infra/proxy/tls:/tls" $(ACME_SH) -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 : rejouable à chaque déploiement. # acme.sh sort en 2 quand le certificat n'est pas encore à renouveler, et recopie le jeton dans
tls-duckdns: ## Certificat Let's Encrypt par DNS-01 DuckDNS, renouvelé seulement à échéance # acme/account.conf : d'où le chmod, qui le soustrait aux autres comptes de la machine.
@case "$(PUBLIC_HOST)" in *.duckdns.org) ;; *) echo "PUBLIC_HOST=$(PUBLIC_HOST) n'est pas un nom DuckDNS"; exit 1 ;; esac tls-desec: ## Certificat Let's Encrypt par DNS-01 deSEC, renouvelé seulement à échéance
@test -r "$(DUCKDNS_TOKEN_FILE)" || { echo "Jeton DuckDNS illisible : $(DUCKDNS_TOKEN_FILE)"; exit 1; } @case "$(PUBLIC_HOST)" in *.dedyn.io) ;; *) echo "PUBLIC_HOST=$(PUBLIC_HOST) n'est pas un nom deSEC"; exit 1 ;; esac
@mkdir -p infra/proxy/acme @test -r "$(DESEC_TOKEN_FILE)" || { echo "Jeton deSEC illisible : $(DESEC_TOKEN_FILE)"; exit 1; }
@DuckDNS_Token="$$(cat "$(DUCKDNS_TOKEN_FILE)")"; export DuckDNS_Token; \ @mkdir -p infra/proxy/acme && chmod 700 infra/proxy/acme
$(acme-sh) --issue --server letsencrypt --dns dns_duckdns -d "$(PUBLIC_HOST)"; \ @DEDYN_TOKEN="$$(tr -d '[:space:]' < "$(DESEC_TOKEN_FILE)")"; export DEDYN_TOKEN; \
code=$$?; [ $$code -eq 0 ] || [ $$code -eq 2 ] || exit $$code $(acme-sh) --issue --server letsencrypt --dns dns_desec -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)" \ @$(acme-sh) --install-cert --ecc -d "$(PUBLIC_HOST)" \
--fullchain-file /tls/fullchain.pem --key-file /tls/privkey.pem --fullchain-file /tls/fullchain.pem --key-file /tls/privkey.pem
@$(COMPOSE_PROD) exec -T proxy nginx -s reload 2>/dev/null \ @$(COMPOSE_PROD) exec -T proxy nginx -s reload 2>/dev/null \
@@ -1,4 +1,4 @@
# 0018 - Noms DuckDNS, certificats Let's Encrypt par DNS-01 et frontal SNI sans port # 0018 - Noms deSEC, certificats Let's Encrypt par DNS-01 et frontal SNI sans port
- Statut : accepté - Statut : accepté
- Date : 2026-09-23 - Date : 2026-09-23
@@ -13,21 +13,24 @@ rien de présentable à un jury, et rien d'utilisable par quelqu'un qui n'a pas
poste. poste.
Contraintes : la VM n'a qu'une IP privée, `10.101.200.37`, que ni Internet ni Let's Encrypt ne Contraintes : la VM n'a qu'une IP privée, `10.101.200.37`, que ni Internet ni Let's Encrypt ne
joignent, et le réseau de l'école ne doit pas être touché. Deux vérifications faites le 23/09 : joignent, et le réseau de l'école ne doit pas être touché. Vérifications faites le 23/09 : les
les résolveurs de l'école rendent bien une adresse privée pour un nom public, et la VM sort en résolveurs de l'école rendent bien une adresse privée pour un nom public, la VM sort en HTTPS
HTTPS vers Let's Encrypt. vers Let's Encrypt et vers l'API de deSEC, mais le filtrage de l'école bloque duckdns.org, site
et API, depuis les postes comme depuis la VM.
## Décision ## Décision
**Des noms publics qui visent l'IP privée.** `enervision-g3.duckdns.org` porte la prod, **Des noms publics qui visent l'IP privée.** `enervision-g3.dedyn.io`, zone gratuite de deSEC,
`rec.` et `dev.` en sous-domaines, que DuckDNS résout d'office. Tout poste du réseau de l'école porte la prod, et un enregistrement joker `*` porte `rec.` et `dev.`. `provision-host.sh` publie
les résout sans configuration ; hors de ce réseau, l'IP ne mène nulle part. ces deux enregistrements par l'API deSEC : le DNS est décrit par le code comme le reste. 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 DuckDNS, qui pose **Des certificats Let's Encrypt par défi DNS-01.** Le défi passe par l'API deSEC, qui pose
l'enregistrement TXT : Let's Encrypt n'a jamais à joindre la VM. `make tls-duckdns` (acme.sh l'enregistrement TXT : Let's Encrypt n'a jamais à joindre la VM. `make tls-desec` (acme.sh
épinglé) le joue dans chaque stack ; il ne renouvelle qu'à échéance, d'où son rejeu à chaque é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 : DuckDNS ne tient qu'un déploiement et chaque nuit par cron. Un certificat par environnement plutôt qu'un joker : chaque
TXT à la fois, ce qui interdit un certificat unique pour le domaine et son joker. 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 **Un frontal SNI sur 443, le seul composant exposé.** `infra/front`, un nginx sur le réseau de
l'hôte, lit le nom demandé dans le ClientHello et relaie le flux TLS intact vers la stack visée, l'hôte, lit le nom demandé dans le ClientHello et relaie le flux TLS intact vers la stack visée,
@@ -49,6 +52,8 @@ clone de la prod, en retard sur `main`, sans dépendre de son `.env.example`.
- **Garder `/etc/hosts` et l'auto-signé** : trois manipulations par poste et trois - **Garder `/etc/hosts` et l'auto-signé** : trois manipulations par poste et trois
avertissements, précisément ce qu'il fallait supprimer. avertissements, précisément ce qu'il fallait supprimer.
- **DuckDNS** : premier choix, inscription en un clic, mais bloqué par le filtrage de l'école :
sans son API, pas de défi DNS-01.
- **nip.io ou sslip.io** : résolution sans compte, mais aucun moyen d'y obtenir un certificat. - **nip.io ou sslip.io** : résolution sans compte, mais aucun moyen d'y obtenir un certificat.
- **Services à certificat joker public (traefik.me, local-ip.co)** : leur clé privée est publiée - **Services à certificat joker public (traefik.me, local-ip.co)** : leur clé privée est publiée
par conception, n'importe qui peut usurper ces noms. par conception, n'importe qui peut usurper ces noms.
@@ -67,9 +72,12 @@ clone de la prod, en retard sur `main`, sans dépendre de son `.env.example`.
aux services homonymes, tombe : le frontal ne joint que des ports de la boucle locale. aux services homonymes, tombe : le frontal ne joint que des ports de la boucle locale.
- Sans le frontal, plus rien n'est joignable sur la VM. `deploy.yml` le relance à chaque - Sans le frontal, plus rien n'est joignable sur la VM. `deploy.yml` le relance à chaque
déploiement de la prod, et son `restart: unless-stopped` le ramène après un redémarrage. déploiement de la prod, et son `restart: unless-stopped` le ramène après un redémarrage.
- Le jeton DuckDNS vit dans `/srv/enervision/duckdns.token`, jamais dans git, GitHub ni le state - Le jeton deSEC vit dans `/srv/enervision/desec.token`, jamais dans git, GitHub ni le state
Terraform. Qui le détient peut repointer les trois noms. Terraform ; acme.sh en garde une copie dans `infra/proxy/acme/`, retirée à la lecture des
- DuckDNS devient une dépendance : s'il tombe, les noms cessent de résoudre et les autres comptes. Qui le détient peut repointer les trois noms.
- deSEC devient une dépendance : s'il tombe, les noms cessent de résoudre et les
renouvellements échouent. Les certificats valent 90 jours, la marge est large. renouvellements échouent. Les certificats valent 90 jours, la marge est large.
- Un filtrage de l'école qui viendrait à bloquer deSEC arrêterait les renouvellements, pas les
noms : la résolution passe par les serveurs DNS de l'école, pas par le site.
- Les noms sont publics mais ne mènent qu'à une IP privée : ils révèlent l'existence de la VM, - Les noms sont publics mais ne mènent qu'à une IP privée : ils révèlent l'existence de la VM,
pas son contenu. pas son contenu.
+4 -4
View File
@@ -238,7 +238,7 @@ Deux conséquences se propagent jusqu'à l'application, et elles ne se devinent
Statut : `En cours`. Décision et motifs dans Statut : `En cours`. Décision et motifs dans
l'[ADR 0009](../adr/0009-deux-environnements-compose-sur-la-vm-eni.md), étendue à un troisième l'[ADR 0009](../adr/0009-deux-environnements-compose-sur-la-vm-eni.md), étendue à un troisième
environnement par l'[ADR 0017](../adr/0017-environnement-dev-a-la-demande.md) ; noms, environnement par l'[ADR 0017](../adr/0017-environnement-dev-a-la-demande.md) ; noms,
certificats et frontal sans port dans l'[ADR 0018](../adr/0018-noms-duckdns-certificats-dns01-et-frontal-sni.md). certificats et frontal sans port dans l'[ADR 0018](../adr/0018-noms-desec-certificats-dns01-et-frontal-sni.md).
La VM `eadl-2025-nantes-g3` porte le développement, la recette et la production, chacun dans son La VM `eadl-2025-nantes-g3` porte le développement, la recette et la production, chacun dans son
clone du dépôt, son `.env` et son projet Compose. Le nom de projet préfixe volumes, réseau et clone du dépôt, son `.env` et son projet Compose. Le nom de projet préfixe volumes, réseau et
conteneurs : rien n'est partagé. `scripts/provision-host.sh` prépare les trois dossiers, génère conteneurs : rien n'est partagé. `scripts/provision-host.sh` prépare les trois dossiers, génère
@@ -248,20 +248,20 @@ les secrets et les certificats, et ne démarre rien.
|---|---|---|---| |---|---|---|---|
| Branche, environnement GitHub | toute branche lancée à la main, `dev` | `dev`, `rec` | `main`, `prod` | | 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` | | Dossier, projet Compose | `/srv/enervision/dev`, `enervision-dev` | `/srv/enervision/rec`, `enervision-rec` | `/srv/enervision/prod`, `enervision-prod` |
| URL | `https://dev.enervision-g3.duckdns.org` | `https://rec.enervision-g3.duckdns.org` | `https://enervision-g3.duckdns.org` | | URL | `https://dev.enervision-g3.dedyn.io` | `https://rec.enervision-g3.dedyn.io` | `https://enervision-g3.dedyn.io` |
| Proxy HTTP, HTTPS, PROXY protocol, sur `127.0.0.1` | `8083`, `9443`, `9444` | `8081`, `8443`, `8444` | `10080`, `10443`, `10444` | | 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` | | 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` | | Supervision (profil `monitoring`) | à la demande, `make monitoring-up` | à la demande, `make monitoring-up` | active, `COMPOSE_PROFILES=monitoring` |
| Grafana, Prometheus, Alertmanager, sur `127.0.0.1` | `3003`, `9092`, `9095` | `3002`, `9091`, `9094` | `3001`, `9090`, `9093` | | Grafana, Prometheus, Alertmanager, sur `127.0.0.1` | `3003`, `9092`, `9095` | `3002`, `9091`, `9094` | `3001`, `9090`, `9093` |
Les trois noms sont publics chez DuckDNS et visent l'IP privée de la VM : rien à déclarer sur Les trois noms sont publics chez deSEC et visent l'IP privée de la VM : rien à déclarer sur
les postes du réseau de l'école, et rien n'est joignable hors de ce réseau. Trois noms distincts les postes du réseau de l'école, et rien n'est joignable hors de ce réseau. Trois noms distincts
sont nécessaires : le cookie `__Secure-ev_refresh` est posé par hôte, pas par port. sont nécessaires : le cookie `__Secure-ev_refresh` est posé par hôte, pas par port.
Aucune stack ne publie hors de la boucle locale. Le frontal `infra/front`, sur le réseau de Aucune stack ne publie hors de la boucle locale. Le frontal `infra/front`, sur le réseau de
l'hôte, écoute 80 et 443 : il redirige le premier, et aiguille le second d'après le nom demandé l'hôte, écoute 80 et 443 : il redirige le premier, et aiguille le second d'après le nom demandé
(SNI) vers l'écouteur PROXY protocol de la stack visée, sans déchiffrer le TLS. Chaque stack (SNI) vers l'écouteur PROXY protocol de la stack visée, sans déchiffrer le TLS. Chaque stack
garde son certificat Let's Encrypt, obtenu par défi DNS-01 (`make tls-duckdns`) et renouvelé à garde son certificat Let's Encrypt, obtenu par défi DNS-01 (`make tls-desec`) et renouvelé à
chaque déploiement ainsi que chaque nuit par `/etc/cron.d/enervision-tls`. chaque déploiement ainsi que chaque nuit par `/etc/cron.d/enervision-tls`.
Le déploiement est décrit dans [50-cicd.md](50-cicd.md) : un runner GitHub Actions installé sur Le déploiement est décrit dans [50-cicd.md](50-cicd.md) : un runner GitHub Actions installé sur
+4 -4
View File
@@ -38,10 +38,10 @@ Apres l'apply, la machine porte `/srv/enervision/dev`, `/srv/enervision/rec` et
manuel, `make stack-up` dans chaque dossier ; les suivants sont joues par le runner a chaque push manuel, `make stack-up` dans chaque dossier ; les suivants sont joues par le runner a chaque push
sur `dev` et sur `main`, et a chaque lancement manuel d'une autre branche pour `dev`. sur `dev` et sur `main`, et a chaque lancement manuel d'une autre branche pour `dev`.
Noms et certificats (ADR 0018) : avant l'apply, l'enregistrement DuckDNS de `domaine` doit viser Noms et certificats (ADR 0018) : avant l'apply, le jeton deSEC de `domaine` doit se trouver dans
`ssh_host`, et son jeton se trouver dans `<racine>/duckdns.token` (600, proprietaire). L'apply `<racine>/desec.token` (600, proprietaire). L'apply fait alors pointer le domaine et son joker
obtient alors un certificat Let's Encrypt par environnement et planifie leur renouvellement ; vers la machine, obtient un certificat Let's Encrypt par environnement et planifie leur
sans jeton, chaque environnement garde un certificat auto-signe. Le frontal SNI (`infra/front`) 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`. se demarre une fois depuis le dossier de la prod, `make front-up`.
Retirer le runner se fait a la main, depuis les parametres du depot : `terraform destroy` ne le Retirer le runner se fait a la main, depuis les parametres du depot : `terraform destroy` ne le
+3 -3
View File
@@ -78,12 +78,12 @@ Renouvellement, à passer en tâche planifiée sur la machine :
### Let's Encrypt par DNS-01, le mode de la VM ### Let's Encrypt par DNS-01, le mode de la VM
La VM n'a qu'une IP privée : le défi HTTP-01 y est impossible. Ses trois noms sont chez DuckDNS, La VM n'a qu'une IP privée : le défi HTTP-01 y est impossible. Ses trois noms sont chez deSEC,
dont l'API pose l'enregistrement TXT du défi DNS-01, et acme.sh le fait sans rien ouvrir dont l'API pose l'enregistrement TXT du défi DNS-01, et acme.sh le fait sans rien ouvrir
([ADR 0018](../../docs/adr/0018-noms-duckdns-certificats-dns01-et-frontal-sni.md)). ([ADR 0018](../../docs/adr/0018-noms-desec-certificats-dns01-et-frontal-sni.md)).
```bash ```bash
make tls-duckdns # PUBLIC_HOST lu dans .env, jeton dans ../duckdns.token (600) make tls-desec # PUBLIC_HOST lu dans .env, jeton dans ../desec.token (600)
``` ```
La cible est rejouable : acme.sh ne renouvelle qu'à trente jours de l'échéance, installe le La cible est rejouable : acme.sh ne renouvelle qu'à trente jours de l'échéance, installe le
@@ -45,8 +45,8 @@ variable "depot_url" {
variable "domaine" { variable "domaine" {
type = string type = string
description = "Nom DuckDNS de la prod, rec. et dev. en sous-domaines (ADR 0018). Son enregistrement doit viser ssh_host, et son jeton se trouver dans <racine>/duckdns.token sur la machine." description = "Domaine deSEC de la prod, rec. et dev. en sous-domaines (ADR 0018). Son jeton doit se trouver dans <racine>/desec.token sur la machine : provision-host.sh y fait pointer le domaine et son joker vers la machine."
default = "enervision-g3.duckdns.org" default = "enervision-g3.dedyn.io"
} }
variable "adresse_publique" { variable "adresse_publique" {
+23 -7
View File
@@ -15,10 +15,10 @@ set -euo pipefail
DEPOT="${REPO_URL:-https://github.com/ineszang/ProjetPiscine_EnerVision.git}" DEPOT="${REPO_URL:-https://github.com/ineszang/ProjetPiscine_EnerVision.git}"
RACINE="${RACINE:-/srv/enervision}" RACINE="${RACINE:-/srv/enervision}"
DOMAINE="${DOMAINE:-enervision-g3.duckdns.org}" DOMAINE="${DOMAINE:-enervision-g3.dedyn.io}"
ADRESSE="${PUBLIC_IP:-$(hostname -I | awk '{print $1}')}" ADRESSE="${PUBLIC_IP:-$(hostname -I | awk '{print $1}')}"
PROPRIETAIRE="${PROPRIETAIRE:-${SUDO_USER:-}}" PROPRIETAIRE="${PROPRIETAIRE:-${SUDO_USER:-}}"
JETON_DUCKDNS="$RACINE/duckdns.token" JETON_DESEC="$RACINE/desec.token"
COMPOSE_MINIMALE="2.24.4" COMPOSE_MINIMALE="2.24.4"
erreur() { echo "erreur : $*" >&2; exit 1; } erreur() { echo "erreur : $*" >&2; exit 1; }
@@ -130,19 +130,34 @@ preparer() {
| grep -q " does match"; then | grep -q " does match"; then
(cd "$dossier" && PUBLIC_HOST="$hote" PUBLIC_IP="$ADRESSE" ./scripts/tls-selfsigned.sh --force) (cd "$dossier" && PUBLIC_HOST="$hote" PUBLIC_IP="$ADRESSE" ./scripts/tls-selfsigned.sh --force)
fi fi
if [[ -r "$JETON_DUCKDNS" && "$hote" == *.duckdns.org ]]; then if [[ -r "$JETON_DESEC" && "$hote" == *.dedyn.io ]]; then
make -C "$dossier" --no-print-directory tls-duckdns PUBLIC_HOST="$hote" \ make -C "$dossier" --no-print-directory tls-desec PUBLIC_HOST="$hote" \
|| echo "$env : pas de certificat Let's Encrypt, l'auto-signé reste en place" >&2 || echo "$env : pas de certificat Let's Encrypt, l'auto-signé reste en place" >&2
fi fi
echo "$env : $dossier sur $branche, https://$hote" echo "$env : $dossier sur $branche, https://$hote"
} }
# Le jeton passe par `curl --config -`, donc par l'entrée standard et jamais par `argv`.
publier_dns() {
[[ -r "$JETON_DESEC" ]] || { echo "pas de jeton $JETON_DESEC : ni DNS ni Let's Encrypt"; return 0; }
local enregistrements
enregistrements="[{\"subname\": \"\", \"type\": \"A\", \"ttl\": 3600, \"records\": [\"$ADRESSE\"]},
{\"subname\": \"*\", \"type\": \"A\", \"ttl\": 3600, \"records\": [\"$ADRESSE\"]}]"
if printf 'header = "Authorization: Token %s"\n' "$(tr -d '[:space:]' < "$JETON_DESEC")" \
| curl -fsS --max-time 20 --config - -X PATCH -H "Content-Type: application/json" \
--data "$enregistrements" "https://desec.io/api/v1/domains/$DOMAINE/rrsets/" >/dev/null; then
echo "DNS : $DOMAINE et *.$DOMAINE visent $ADRESSE"
else
echo "DNS : deSEC refuse la mise à jour de $DOMAINE, enregistrements inchangés" >&2
fi
}
planifier_renouvellement() { planifier_renouvellement() {
[[ "$(id -u)" -eq 0 && -n "$PROPRIETAIRE" && -d /etc/cron.d ]] || return 0 [[ "$(id -u)" -eq 0 && -n "$PROPRIETAIRE" && -d /etc/cron.d ]] || return 0
cat > /etc/cron.d/enervision-tls <<CRON cat > /etc/cron.d/enervision-tls <<CRON
# Renouvellement Let's Encrypt des trois environnements (ADR 0018), écrit par provision-host.sh. # Renouvellement Let's Encrypt des trois environnements (ADR 0018), écrit par provision-host.sh.
PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
23 4 * * * $PROPRIETAIRE for e in prod rec dev; do make -C $RACINE/\$e --no-print-directory tls-duckdns; done 2>&1 | logger -t enervision-tls 23 4 * * * $PROPRIETAIRE for e in prod rec dev; do make -C $RACINE/\$e --no-print-directory tls-desec; done 2>&1 | logger -t enervision-tls
CRON CRON
chmod 644 /etc/cron.d/enervision-tls chmod 644 /etc/cron.d/enervision-tls
echo "renouvellement planifié : /etc/cron.d/enervision-tls" echo "renouvellement planifié : /etc/cron.d/enervision-tls"
@@ -150,6 +165,7 @@ CRON
verifier_outils verifier_outils
mkdir -p "$RACINE" mkdir -p "$RACINE"
publier_dns
# Supervision active en prod seulement (ADR 0016). Le frontal (infra/front) publie 80 et 443 et # 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. # relaie vers les ports `front` ; les stacks ne publient plus rien hors de la boucle locale.
@@ -176,6 +192,6 @@ L'installer sous le propriétaire de $RACINE, sinon git refuse ces dépôts et l
échappe : relancer au besoin ce script avec PROPRIETAIRE=<utilisateur du runner>. échappe : relancer au besoin ce script avec PROPRIETAIRE=<utilisateur du runner>.
Données historiques : git ne porte pas data/raw, déposer les fichiers dans chaque dossier avant Données historiques : git ne porte pas data/raw, déposer les fichiers dans chaque dossier avant
de déclencher le DAG historical_import. de déclencher le DAG historical_import.
Noms et certificats : l'enregistrement DuckDNS de $DOMAINE doit viser $ADRESSE, et son jeton Noms et certificats : le jeton deSEC de $DOMAINE doit se trouver dans $JETON_DESEC (600,
se trouver dans $JETON_DUCKDNS (600, propriétaire du runner). Sans jeton : auto-signé. propriétaire du runner). Sans lui, ni enregistrement DNS ni Let's Encrypt : auto-signé.
FIN FIN