diff --git a/stacks/matrix.poneyworld.net/README.md b/stacks/matrix.poneyworld.net/README.md index 4544f48..a232ab3 100644 --- a/stacks/matrix.poneyworld.net/README.md +++ b/stacks/matrix.poneyworld.net/README.md @@ -194,6 +194,28 @@ elle.** Aucun des deux ne le signalera : coturn et LiveKit démarrent normalement en annonçant une adresse erronée, et seuls les appels échouent — sans erreur dans les logs, puisque du point de vue des serveurs tout va bien. +## Le chemin des requêtes clientes + +`matrix.poneyworld.net` passe par Traefik, puis nginx, puis Synapse. Deux +conséquences à ne pas défaire : + +- nginx doit propager la chaîne avec `$proxy_add_x_forwarded_for`, jamais + `$remote_addr` : ce dernier vaut l'adresse de **Traefik**, et l'utiliser + écrase l'en-tête posé en amont. Synapse voit alors tous les clients sous une + seule adresse — la limitation de débit à la connexion devient un compteur + partagé par tous les comptes, et les journaux d'authentification perdent leur + intérêt. Côté Synapse, le listener 8008 doit porter `x_forwarded: true`. +- `crowdsec@file` est posé sur les routeurs `matrix_nginx` (API client) et + `element` (interface web), **délibérément pas sur `synapse`** : ce dernier + sert la fédération, qui arrive de milliers de serveurs distants. Une décision + de bannissement à cet endroit couperait les correspondants. + +Contrôle de la chaîne, une seule adresse en `172.x` signalant le problème : + +```sql +SELECT ip, count(*) FROM user_ips GROUP BY ip ORDER BY count DESC LIMIT 10; +``` + ## Versions d'images Toutes les images sont épinglées au patch. Les tags d'amont ne suivent pas la diff --git a/stacks/matrix.poneyworld.net/docker-compose.yml b/stacks/matrix.poneyworld.net/docker-compose.yml index 0681d62..9f88c1e 100644 --- a/stacks/matrix.poneyworld.net/docker-compose.yml +++ b/stacks/matrix.poneyworld.net/docker-compose.yml @@ -7,6 +7,15 @@ services: synapse: image: matrixdotorg/synapse:v1.159.0 restart: unless-stopped + security_opt: + - no-new-privileges:true + # Forme longue : `depends_on` court n'ordonne que le DÉMARRAGE, sans + # attendre que la base accepte les connexions. + depends_on: + db: + condition: service_healthy + redis: + condition: service_healthy env_file: - env_file volumes: @@ -26,6 +35,8 @@ services: db: image: postgres:18.6 restart: unless-stopped + security_opt: + - no-new-privileges:true env_file: - env_file environment: @@ -33,6 +44,12 @@ services: POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C volumes: - matrix_db:/var/lib/postgresql + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 15s + timeout: 3s + retries: 20 + start_period: 30s networks: - default labels: @@ -42,12 +59,27 @@ services: redis: image: valkey/valkey:9.1.1 restart: unless-stopped + security_opt: + - no-new-privileges:true + healthcheck: + test: ["CMD", "valkey-cli", "ping"] + interval: 15s + timeout: 3s + retries: 20 networks: - default nginx: image: nginx:1.30.4 restart: unless-stopped + security_opt: + - no-new-privileges:true + # `proxy_pass http://synapse:8008` est résolu au CHARGEMENT de la config : + # sans ce depends_on, nginx s'arrête sur « host not found in upstream » au + # démarrage de la machine et ne revient que par la boucle `restart`. + depends_on: + synapse: + condition: service_healthy volumes: - ./nginx/matrix.conf:/etc/nginx/conf.d/matrix.conf:ro - ./nginx/www:/var/www/:ro @@ -60,11 +92,14 @@ services: - traefik.http.routers.matrix_nginx.entrypoints=web-secure - traefik.http.routers.matrix_nginx.rule=Host(`matrix.poneyworld.net`) - traefik.http.routers.matrix_nginx.tls.certresolver=letsencrypt + - traefik.http.routers.matrix_nginx.middlewares=crowdsec@file - traefik.http.services.matrix_nginx.loadbalancer.server.port=80 web: image: vectorim/element-web:v1.12.26 restart: unless-stopped + security_opt: + - no-new-privileges:true volumes: - ./element/config.json:/app/config.json:ro networks: @@ -76,12 +111,15 @@ services: - traefik.http.routers.element.entrypoints=web-secure - traefik.http.routers.element.rule=Host(`chat.poneyworld.net`) - traefik.http.routers.element.tls.certresolver=letsencrypt + - traefik.http.routers.element.middlewares=crowdsec@file - traefik.http.services.element.loadbalancer.server.port=80 livekit: image: livekit/livekit-server:v1.13.5 command: --config /etc/livekit.yaml restart: unless-stopped + security_opt: + - no-new-privileges:true environment: # `keys:` a été retiré de livekit.yaml : la paire arrive ici, où # l'interpolation ${...} de Compose s'applique. @@ -105,8 +143,14 @@ services: lk-jwt: image: ghcr.io/element-hq/lk-jwt-service:0.6.0 restart: unless-stopped + security_opt: + - no-new-privileges:true + depends_on: + - livekit environment: - LIVEKIT_JWT_BIND: ":8081" + # Pas de LIVEKIT_JWT_BIND : le healthcheck de l'image assemble + # `http://localhost:` + la valeur, ce qui donne un double deux-points et + # le fait échouer. Le défaut (:8080) est le seul réglage qui fonctionne. LIVEKIT_URL: wss://livekit.opytex.org LIVEKIT_KEY: ${LIVEKIT_KEY:?} LIVEKIT_SECRET: ${LIVEKIT_SECRET:?} @@ -120,11 +164,13 @@ services: - traefik.http.routers.lkjwt.entrypoints=web-secure - traefik.http.routers.lkjwt.rule=Host(`livekit.opytex.org`) && (PathPrefix(`/sfu/get`) || PathPrefix(`/get_token`) || PathPrefix(`/healthz`)) - traefik.http.routers.lkjwt.tls.certresolver=letsencrypt - - traefik.http.services.lkjwt.loadbalancer.server.port=8081 + - traefik.http.services.lkjwt.loadbalancer.server.port=8080 coturn: image: coturn/coturn:4.17.2 restart: unless-stopped + security_opt: + - no-new-privileges:true # Les deux secrets ne peuvent pas venir du fichier de conf : coturn n'y # interpole rien et n'offre aucune option `...File`. Voir README.md. command: @@ -134,6 +180,12 @@ services: - --cli-password=${TURN_CLI_PASSWORD:?} volumes: - ./coturn/turnserver.conf:/etc/coturn/turnserver.conf:ro + # `verbose` dans turnserver.conf journalise chaque allocation et chaque + # permission : sans borne, le fichier json croît sans fin. + logging: + driver: "json-file" + options: + max-size: "1m" # 3478 doit être publié en UDP autant qu'en TCP : un client TURN tente # l'UDP en premier. 5349 (TURNS) reste en TCP seul, c'est du TLS. ports: diff --git a/stacks/matrix.poneyworld.net/nginx/matrix.conf b/stacks/matrix.poneyworld.net/nginx/matrix.conf index 34e0f6a..de6652a 100644 --- a/stacks/matrix.poneyworld.net/nginx/matrix.conf +++ b/stacks/matrix.poneyworld.net/nginx/matrix.conf @@ -5,7 +5,10 @@ server { # Traefik -> nginx -> synapse location /_matrix { proxy_pass http://synapse:8008; - proxy_set_header X-Forwarded-For $remote_addr; + # $remote_addr vaut ici l'adresse de Traefik, pas celle du client : l'utiliser + # ÉCRASE l'en-tête posé par Traefik et rend tous les clients identiques aux + # yeux de Synapse. $proxy_add_x_forwarded_for complète la chaîne existante. + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; client_max_body_size 128m; }