From 231a1a1c155fc6653a9789fa088fd1b793a9f095 Mon Sep 17 00:00:00 2001 From: matthieu Date: Sun, 28 Jun 2026 13:45:05 +0200 Subject: [PATCH] feat : bake la CA racine MALIO pour joindre GlitchTip HTTPS via Tailscale (Option A) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit GlitchTip est servi en HTTPS sur logs.malio-dev.fr (cert auto-signé interne). Le SDK backend (sur le VPS OVH) le joint via Tailscale en gardant le DSN hostname inchangé : la CA publique est installée dans le trust store de l'image prod, et le hostname est résolu vers l'IP tailnet via extra_hosts côté serveur (documenté). Aucun changement côté GlitchTip. Co-Authored-By: Claude Opus 4.8 (1M context) --- CLAUDE.md | 3 +- deploy/docker/Dockerfile.prod | 9 +++- deploy/docker/malio-dev-root-ca.crt | 31 ++++++++++++ doc/error-tracking.md | 73 +++++++++++++++++++++-------- 4 files changed, 94 insertions(+), 22 deletions(-) create mode 100644 deploy/docker/malio-dev-root-ca.crt diff --git a/CLAUDE.md b/CLAUDE.md index 3934c94..17dcf77 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -224,7 +224,8 @@ ## Error tracking (GlitchTip) - Backend Symfony → GlitchTip (SDK Sentry), org `malio`, projet `sirh-api`. **Prod only**, **inerte sans `SENTRY_DSN`**. - Config : `config/packages/sentry.yaml` (DSN runtime `%env(SENTRY_DSN)%`, release `%app.version%`, 4xx ignorés, `traces_sample_rate: 0`, handler Monolog ERROR+) ; `SentryBundle` enregistré `['prod' => true]` (`config/bundles.php`) ; handler `sentry` en `when@prod` (`config/packages/monolog.yaml`). DSN runtime via l'env_file serveur, jamais committé/baké. -- **Transport réseau** : GlitchTip est interne (bloqué Sophos), SIRH sur VPS OVH → tunnel **Tailscale** sur le host prod. Frontend hors périmètre (ajout futur via proxy nginx `/ingest`). +- **Transport réseau** : GlitchTip est interne (bloqué Sophos), servi HTTPS sur `logs.malio-dev.fr` (cert auto-signé) ; SIRH sur VPS OVH → tunnel **Tailscale** entre les deux hôtes (GlitchTip `100.111.223.34`, OVH `100.93.52.45`). Topologie retenue (Option A) : DSN **inchangé** (`https://…@logs.malio-dev.fr/3`), hostname résolu vers l'IP tailnet via `extra_hosts` dans le `docker-compose` serveur, et **CA racine MALIO bakée** dans l'image (`deploy/docker/Dockerfile.prod` + `deploy/docker/malio-dev-root-ca.crt`). Frontend hors périmètre (ajout futur via proxy nginx `/ingest`). +- Test d'envoi : `php bin/console sentry:test` (prod-only, `SENTRY_DSN` requis) → Issue dans `sirh-api`. - Doc : `doc/error-tracking.md`. ## Backend Conventions diff --git a/deploy/docker/Dockerfile.prod b/deploy/docker/Dockerfile.prod index 6b0a936..743d921 100644 --- a/deploy/docker/Dockerfile.prod +++ b/deploy/docker/Dockerfile.prod @@ -39,10 +39,17 @@ FROM php:8.4-fpm AS production RUN apt-get update && apt-get install -y \ libicu-dev libpq-dev libpng-dev libzip-dev libxml2-dev \ - nginx supervisor \ + nginx supervisor ca-certificates \ && docker-php-ext-install -j$(nproc) intl pdo_pgsql zip gd opcache \ && rm -rf /var/lib/apt/lists/* +# CA racine interne MALIO (auto-signée) — permet au SDK Sentry/HttpClient de joindre +# GlitchTip en HTTPS sur logs.malio-dev.fr (cert *.malio-dev.fr). Le host est résolu vers +# l'IP tailnet via `extra_hosts` dans le docker-compose du serveur (cf. doc/error-tracking.md). +# Sans cette CA approuvée, le SDK logue « Message not sent » et rien ne remonte. +COPY deploy/docker/malio-dev-root-ca.crt /usr/local/share/ca-certificates/malio-dev-root-ca.crt +RUN update-ca-certificates + # PHP production config RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini" COPY docker/php/config/php.ini "$PHP_INI_DIR/conf.d/99-app.ini" diff --git a/deploy/docker/malio-dev-root-ca.crt b/deploy/docker/malio-dev-root-ca.crt new file mode 100644 index 0000000..087d73d --- /dev/null +++ b/deploy/docker/malio-dev-root-ca.crt @@ -0,0 +1,31 @@ +-----BEGIN CERTIFICATE----- +MIIFZzCCA0+gAwIBAgIUOiZigxwgIgtLipnLnu4eSgItc5MwDQYJKoZIhvcNAQEL +BQAwQzELMAkGA1UEBhMCRlIxEjAQBgNVBAoMCU1BTElPLURFVjEgMB4GA1UEAwwX +TUFMSU8tREVWIExvY2FsIFJvb3QgQ0EwHhcNMjYwNjI1MTYxMjIwWhcNMzYwNjIy +MTYxMjIwWjBDMQswCQYDVQQGEwJGUjESMBAGA1UECgwJTUFMSU8tREVWMSAwHgYD +VQQDDBdNQUxJTy1ERVYgTG9jYWwgUm9vdCBDQTCCAiIwDQYJKoZIhvcNAQEBBQAD +ggIPADCCAgoCggIBALqHXVWEae9aKtveLfSpxYy9RS0Aslw2Ls9+LWI33lpMRs02 +QssE9wquf3WGjz8NnHUWl5RM0QHC0DOCCddcbnRBciDRJeTaU43IGdNg+TSY+7aM +3t/jysZrpc/eu/udlIs7npCPaOGnRiuGN68Fkf9Q70FtmaASpusUe7J3jKDinznr +R2hARplO4OF01tFauu039A4yudLrZTUFTldicuZ6a5U3NhajgfNZA+pyJqvL3tLT +lXG3KupPD9BsbWe4zSM96CmyHM22QNlcL+M5XG5+EtDtM07tkDcyxFOsREjQHvSQ +NH+7h6G/QBHHKkYJhdyiuvpj6b5tEJBM2PVgy1T2JX5TuOBOLx6HvHLbNjUY/JI5 +0sIjnHbeybQCOfnKNAwidtnqjAfVg+XJ9UZCiGJOeRJOdN5isvvqEKydsX4ouCTj +89kwBbfCJeCS6BiadvNFUwnM0PksV0ovnOiUEEAPHRiP74jZ3IvH95BEwiZzyLpy +tXiJMW7cJMaqlT3jNwq3P00irfrpJNy4S1Mg2cBQh5ucv+PcMBfQT8YiarzlTQJo +saksh/2C43WH+qIFAL2aeD+rKReVBZcGa1XOBI8FUJTu3rLd37+iS4N2BUKq4fWo +FttuX5NOfeU3BRDLlCJ2AXau7o0czVy896R9iZTfBJC95QWD07PdHgoctuexAgMB +AAGjUzBRMB0GA1UdDgQWBBRNU0WsMg/pqo5XF/WXx78GrAzD5TAfBgNVHSMEGDAW +gBRNU0WsMg/pqo5XF/WXx78GrAzD5TAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 +DQEBCwUAA4ICAQBFXsuT7Rm2oJBlWT/RsJtmWr95NoFLHovVDycgM8Vjm+E8hv/m +AcSjPjZDmXQLOrN31T/XUAs0nURHxSFgVzdIKpq2gOlGgHkZRMAW/iTON9Cqjn81 +Arjp5fjAJyFkoCiT3eTOElpteF4NhL8xMFaOg1Y2CEfOYO9OZR7Z38HdB6IArVwr +W3Dxq3DPtarCeo1k8SHJmJzUduYCltV8urB43gIiI2Hqd7aAlpkTfDhruKxxr7sJ +3/TpemJDCN9m8XMv2QvxqpMwH6EXg/7oqit5k0MvD445f3xt9vZydmV/x6F7u/A/ +gJitN+ixA4AKv7Lw210vaupiChqdY+78TXgLoPJ2/l2QPWG/R7Fb4yNZ2rEd6lyt +KLPxHDcdZetFnyqyaoB2SNtLx9hNUE5G3udU6DkNhDfQlDhqEG4f7GAInOu/cMWE +2uiIUEjcGSLM+XrrTFRc1tdXy6hnu+sw5ckvhwJ+kjah/pVGz21/y5a0p42AUznI +iN7HBV8YaSkeJLvBPnfakUAat1R98e0l72DucHe8RF44NmZCywpaUBsTpNy+bO2f +atqp4/ZEGJJlJ38rLv9bAuwr6d8x6T+m0oHknqtJHcWfO0kr4l3Lxsd8mRpGgmBe +zOjqjrat4vSc04Rqic4UV2IEoWCiSS/TSiBx8JAB6Ck0+YR9dUgXVQsFFg== +-----END CERTIFICATE----- diff --git a/doc/error-tracking.md b/doc/error-tracking.md index a43fdc8..79416d6 100644 --- a/doc/error-tracking.md +++ b/doc/error-tracking.md @@ -8,8 +8,22 @@ compatible SDK Sentry), org `malio`, projet `sirh-api`. **Prod uniquement**, **i ## Contrainte réseau & transport -GlitchTip est sur le réseau interne (bloqué par Sophos). SIRH tourne sur un VPS OVH public. Le -container PHP joint GlitchTip via un **tunnel Tailscale** monté sur le host de prod. +GlitchTip est sur le réseau interne (bloqué par Sophos), servi en **HTTPS sur +`logs.malio-dev.fr`** (cert auto-signé par la CA interne « MALIO-DEV Local Root CA »). SIRH tourne +sur un **VPS OVH** public. Le lien passe par un **tunnel Tailscale** entre les deux hôtes. + +**Topologie retenue (Option A — HTTPS + hostname mappé sur le tailnet) :** +- Tailscale est installé **sur l'hôte GlitchTip** (IP tailnet `100.111.223.34`) **et sur le VPS + OVH** (IP tailnet `100.93.52.45`). +- Le **DSN reste inchangé** : `https://@logs.malio-dev.fr/` (même endpoint que le + navigateur → pas de souci `ALLOWED_HOSTS`, Host header et cert cohérents). +- Côté SIRH, le nom `logs.malio-dev.fr` est résolu vers l'**IP tailnet de GlitchTip** via + `extra_hosts` dans le `docker-compose` du serveur. +- La **CA racine MALIO** est bakée dans l'image SIRH (`deploy/docker/Dockerfile.prod`) pour que le + SDK accepte le TLS auto-signé. + +> Pré-requis : le nginx qui sert `logs.malio-dev.fr` en 443 doit écouter sur une interface +> joignable via le tailnet (typiquement `0.0.0.0:443` → joignable sur `100.111.223.34:443`). ## Fichiers concernés @@ -19,28 +33,34 @@ container PHP joint GlitchTip via un **tunnel Tailscale** monté sur le host de | `config/bundles.php` | `SentryBundle` enregistré `['prod' => true]` | | `config/packages/monolog.yaml` | handler `sentry` (service) en `when@prod` | | `.env` | bloc documenté `SENTRY_DSN` (vide → inerte) | +| `deploy/docker/Dockerfile.prod` | bake la CA racine MALIO (`update-ca-certificates`) pour le TLS interne | +| `deploy/docker/malio-dev-root-ca.crt` | certificat **public** de la CA interne (aucune clé privée) | ## Activation (runbook) -1. **Tailscale sur le host prod OVH** : +1. **Tailscale sur les deux hôtes** (GlitchTip **et** VPS OVH) : ```bash curl -fsSL https://tailscale.com/install.sh | sh sudo tailscale up # ou --authkey tskey-auth-XXXX (headless) - tailscale status && tailscale ip -4 + tailscale ip -4 # GlitchTip → 100.111.223.34 ; OVH → 100.93.52.45 ``` -2. **Vérifier l'accès à GlitchTip** depuis le host : +2. **Vérifier l'accès** depuis le VPS OVH (tunnel + nginx 443 de GlitchTip) : ```bash - tailscale ping - curl -sS -o /dev/null -w "%{http_code}\n" http://:/_health/ + tailscale ping 100.111.223.34 + curl -sSk -o /dev/null -w "%{http_code}\n" https://100.111.223.34/ # réponse HTTP = tunnel OK ``` -3. **Routage container → tailnet** : pointer `SENTRY_DSN` sur l'**IP tailnet** de GlitchTip - (le container ne résout pas MagicDNS). Repli si non routé : sidecar `tailscale/tailscale` - + `network_mode: service:tailscale`. -4. **Créer le projet GlitchTip** `sirh-api` (plateforme `php-symfony`) dans l'org `malio`, - récupérer le DSN (Settings → Client Keys). -5. **Injecter le DSN** dans l'env_file serveur (pas dans l'image), puis redéployer : +3. **Mapper le hostname vers l'IP tailnet** dans le `docker-compose` du serveur OVH (service `php`), + pour que le container résolve `logs.malio-dev.fr` : + ```yaml + extra_hosts: + - "logs.malio-dev.fr:100.111.223.34" + ``` +4. **Projet GlitchTip** : déjà créé (org `malio`, projet `sirh-api`, id `3`). DSN de base affiché + dans *Settings → Client Keys* : `https://@logs.malio-dev.fr/3`. +5. **Injecter le DSN tel quel** (hostname conservé) dans l'env_file serveur (pas dans l'image), + puis rebuild/redéployer l'image (la CA est bakée au build) : ```env - SENTRY_DSN=http://@100.x.y.z:/ + SENTRY_DSN=https://@logs.malio-dev.fr/3 ``` ```bash docker compose up -d @@ -62,20 +82,33 @@ Sortie attendue : `Sending test message... done.` → une **Issue de test** appa `sirh-api` côté GlitchTip. Si l'envoi échoue (`Message not sent`), le problème est réseau (Tailscale/route/port) ou DSN, pas applicatif. -Pré-check connectivité depuis le host prod (ex. IP tailnet GlitchTip `100.111.223.34`) : +Pré-check connectivité depuis le VPS OVH (`-k` ignore le cert juste pour ce test) : ```bash tailscale ping 100.111.223.34 -curl -sS -o /dev/null -w "%{http_code}\n" http://100.111.223.34:/_health/ # 200 attendu +curl -sSk -o /dev/null -w "%{http_code}\n" https://100.111.223.34/ # réponse HTTP = tunnel OK +# Avec résolution du hostname (comme le container) + validation par la CA : +curl --resolve logs.malio-dev.fr:443:100.111.223.34 \ + --cacert deploy/docker/malio-dev-root-ca.crt \ + -sS -o /dev/null -w "%{http_code}\n" https://logs.malio-dev.fr/ ``` Alternative sans commande dédiée : déclencher un `throw new \RuntimeException('glitchtip test')` temporaire dans un endpoint, ou un `$logger->error('glitchtip test')` (niveau ERROR+ → Issue). -## CA HTTPS (conditionnel) +## CA HTTPS interne (bakée dans l'image) -Uniquement si le DSN cible l'HTTPS interne `logs.malio-dev.fr` (cert auto-signé) : baker la CA -racine MALIO dans `deploy/docker/Dockerfile.prod` (stage production). Recommandé : préférer -l'endpoint **HTTP** via le tailnet (déjà chiffré par WireGuard) → pas de CA. +GlitchTip est en HTTPS avec un cert auto-signé par la **CA interne MALIO**. Le SDK refuse un TLS +non approuvé (« Message not sent »). La CA publique (`deploy/docker/malio-dev-root-ca.crt`, aucune +clé privée) est donc installée dans le trust store de l'image au build (`deploy/docker/Dockerfile.prod`, +stage production) : + +```dockerfile +COPY deploy/docker/malio-dev-root-ca.crt /usr/local/share/ca-certificates/malio-dev-root-ca.crt +RUN update-ca-certificates +``` + +Combinée à l'`extra_hosts` (hostname → IP tailnet), le container fait confiance à +`logs.malio-dev.fr` et l'atteint via le tunnel. Design détaillé : `docs/superpowers/specs/2026-06-28-glitchtip-backend-error-tracking-design.md`.