fix: let the panel reach the API over plain HTTP, deliberately

The e-ink firmware carries a certificate-authority bundle fixed when it
was built, so it cannot validate a chain rooted in an authority created
afterwards. Let's Encrypt's ISRG Root YR was issued in May 2026 and is
not even in an up-to-date Ubuntu CA bundle yet; the kit's firmware
predates it. The handshake fails before a request is ever sent, which is
why neither Traefik nor the application saw anything at all while the
device reported "API connection cannot be established".

Ruled out first, with evidence: TLS 1.2 and the ECDHE-RSA-AES-GCM suites
an ESP32 needs are both offered, and the intermediate is not
cross-signed by an older root, so no alternate path exists in what is
served.

A Traefik router now serves four device paths over :80, ahead of the
entrypoint-wide redirect. The administration stays on TLS. The device
token travels in clear; it is used for nothing else and is revocable
from the settings page, and the image URL is an unguessable content hash.

DEVICE_ALLOW_HTTP existed but was never read — a setting that does
nothing misrepresents what it protects. The device routes now refuse an
unencrypted request unless it is set, so opening this door is a written
decision rather than the silent consequence of a proxy change.

DEPLOY.md records the whole diagnosis, including the commands that
distinguish a TLS failure from an application one, and what to do the
day the firmware learns the new roots.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012cSY9pVhZmJUKNN7wf1Myd
This commit is contained in:
2026-09-21 22:17:16 +02:00
co-authored by Claude Opus 5
parent efd91c3e8e
commit eae3f89aca
8 changed files with 192 additions and 16 deletions
+55 -11
View File
@@ -109,16 +109,60 @@ la sauvegarde préalable.
## Le panneau tombe sur le certificat TLS
Certains firmwares ESP32 échouent sur une chaîne de certificats que tous les
navigateurs acceptent. Si l'écran n'arrive à joindre le serveur qu'en clair,
`DEVICE_ALLOW_HTTP=true` existe — mais c'est le dernier recours, pas le premier.
**C'est arrivé sur cette installation, et c'est réglé — voici pourquoi, pour le
jour où ça recommence.**
Dans l'ordre :
Le firmware de l'écran embarque un magasin d'autorités de certification **figé
au moment de sa compilation**. Il ne peut donc pas valider une chaîne qui
s'enracine sur une autorité créée après lui. Let's Encrypt a mis en service la
racine `ISRG Root YR` le 13 mai 2026 ; le firmware du kit est antérieur, et la
poignée de main TLS échoue **avant** qu'une seule requête soit émise — d'où le
symptôme déroutant : l'écran affiche « API connection cannot be established »
et ni Traefik ni l'application n'ont rien vu passer.
1. Vérifiez que l'écran atteint bien le domaine : `docker compose logs app | grep /api/setup`.
2. Vérifiez la chaîne servie : `openssl s_client -connect horaires.ita-ito.com:443 -servername horaires.ita-ito.com | head -20`. Une chaîne incomplète est le cas le plus fréquent et se corrige côté Traefik, pas côté écran.
3. En dernier recours seulement, exposez un hôte virtuel en clair réservé aux
routes `/api/setup`, `/api/display`, `/api/log` et `/api/device/image/*`.
Le jeton d'appareil circulerait alors en clair : il est distinct de tout
autre secret et révocable depuis *Paramètres → Appareils*, ce qui rend
l'arbitrage tenable — mais l'administration, elle, reste en TLS.
Diagnostic, dans l'ordre :
1. **L'écran a-t-il seulement atteint le serveur ?**
`docker compose logs app --since 30m | grep /api/setup` et
`docker logs traefik --since 30m | grep horaires`.
Si les deux sont vides, l'échec est dans la couche TLS, pas dans l'application.
2. **Quelle chaîne est servie, et jusqu'à quelle racine ?**
```bash
echo | openssl s_client -connect horaires.ita-ito.com:443 \
-servername horaires.ita-ito.com 2>/dev/null | grep -E "^ *[0-9] s:"
```
Comparez la racine à ce que le système connaît :
```bash
openssl crl2pkcs7 -nocrl -certfile /etc/ssl/certs/ca-certificates.crt \
| openssl pkcs7 -print_certs -noout | grep "Root YR"
```
Si une machine à jour ne la connaît pas, un firmware de 2025 encore moins.
3. **Le protocole est-il en cause ?** Souvent soupçonné, rarement coupable :
```bash
echo | openssl s_client -connect horaires.ita-ito.com:443 -tls1_2 | grep "Cipher is"
```
Un ESP32 a besoin de TLS 1.2 et d'une suite `ECDHE-RSA-AES*-GCM`.
Deux corrections possibles :
- **Préférer une racine ancienne.** `preferredChain: "ISRG Root X1"` sur le
resolver dans `traefik.yml`, puis renouvellement. `ISRG Root X1` est dans à
peu près tous les magasins depuis 2021. Aucun compromis de sécurité, mais
cela dépend de ce que Let's Encrypt propose encore comme chaîne alternative,
et cela touche le Traefik partagé par tous les sites.
- **Servir l'écran en clair** — ce qui est fait ici. Le routeur
`horaires-device` dans `docker-compose.prod.yml` expose sur `:80` les seules
routes `/api/setup`, `/api/display`, `/api/log` et `/api/device/`, avec une
priorité qui passe devant la redirection HTTP→HTTPS générale. L'administration
reste en TLS. Le jeton d'appareil circule alors en clair : il ne sert à rien
d'autre et se révoque depuis *Paramètres → Appareils*.
L'application **refuse** une requête d'appareil non chiffrée tant que
`DEVICE_ALLOW_HTTP=true` n'est pas posé : ouvrir cette porte est une décision
écrite, pas la conséquence silencieuse d'une configuration de proxy.
Le jour où le firmware apprend les racines récentes, retirez le routeur
`horaires-device` et remettez `DEVICE_ALLOW_HTTP=false`.