draft: Tutoriel Matrix sur a5l.fr — Synapse + Element + Element Call avec SSO Keycloak

This commit is contained in:
Cédrix
2026-05-16 10:12:32 +02:00
parent 1e9549bdd3
commit 89bfc4e9f5
2 changed files with 503 additions and 0 deletions
@@ -0,0 +1,5 @@
{
"title": "Déployer Matrix sur a5l.fr avec SSO Keycloak et Element Call",
"slug": "tutoriel-matrix-sur-a5l-fr-synapse-element-element-call-avec-sso-keycloak",
"_updated_at": "2026-05-16 08:12:32"
}
@@ -0,0 +1,498 @@
# Déployer Matrix sur a5l.fr avec SSO Keycloak et Element Call
## Vue d'ensemble de l'architecture
Voici la pile complète à monter, et les sous-domaines associés :
```
a5l.fr → délégation .well-known vers matrix.a5l.fr
matrix.a5l.fr → Synapse (homeserver)
auth.a5l.fr (ou matrix-auth.a5l.fr) → MAS (Matrix Authentication Service)
chat.a5l.fr → Element Web
mrtc.a5l.fr → MatrixRTC backend (lk-jwt-service + LiveKit SFU)
idp.a5l.fr → Keycloak (déjà en place)
turn.a5l.fr → coturn (optionnel mais recommandé)
```
Et les briques applicatives :
| Brique | Rôle |
|--------|------|
| **Synapse** | Homeserver Matrix : messages, rooms, fédération |
| **PostgreSQL** | Base de données de Synapse |
| **MAS** (Matrix Authentication Service) | Délègue l'auth à Keycloak en OIDC, sert d'OP aux clients Matrix (obligatoire pour Element X) |
| **Element Web** | Client web (les desktop et mobiles l'embarquent) |
| **LiveKit SFU** | Serveur de média WebRTC pour les appels audio/vidéo |
| **lk-jwt-service** | Émet les JWT permettant aux utilisateurs Matrix d'accéder à LiveKit |
| **coturn** | Serveur TURN pour les NAT difficiles (utile derrière FAI) |
| **Keycloak** | Votre IdP existant sur idp.a5l.fr |
> [!WARNING]
> **Pourquoi MAS et pas directement OIDC dans Synapse ?** Synapse supporte historiquement OIDC nativement, mais **Element X (les nouvelles apps mobiles iOS et Android) refuse de s'authentifier sans MAS**. Element X mise sur le mécanisme "next-gen auth" de Matrix, qui passe par MAS. Comme votre cible est "tous appareils", MAS est non-négociable. Les clients legacy (Element Web/Desktop) acceptent aussi MAS.
## Prérequis matériels et réseau
**Serveur :**
- 4 Go RAM minimum, 8 Go recommandé pour confort
- 50 Go SSD pour démarrer (PostgreSQL grossit avec l'historique fédéré)
- Docker + Docker Compose installés
- Un reverse proxy (Nginx ou Caddy) sur le serveur
**Réseau / FAI :**
- IP publique non-CGNAT (vérifiez)
- Ports à ouvrir et rediriger vers le serveur :
| Port | Protocole | Usage |
|------|-----------|-------|
| 443 | TCP | HTTPS pour tous les services web |
| 8448 | TCP | Fédération Matrix |
| 3478 | UDP/TCP | coturn STUN/TURN |
| 5349 | UDP/TCP | coturn STUN/TURN sur TLS |
| 50000-50100 | UDP | LiveKit (plage WebRTC) |
**DNS chez votre registrar :**
```
matrix IN A <IP_PUBLIQUE>
auth IN A <IP_PUBLIQUE>
chat IN A <IP_PUBLIQUE>
mrtc IN A <IP_PUBLIQUE>
turn IN A <IP_PUBLIQUE>
; Découverte fédération Matrix
_matrix._tcp.a5l.fr IN SRV 10 5 8448 matrix.a5l.fr.
```
## Étape 1 — Déléguer Matrix depuis a5l.fr
Pour que `@user:a5l.fr` fonctionne (alors que Synapse tourne sur `matrix.a5l.fr`), le domaine racine doit servir deux fichiers de découverte.
Sur le serveur qui répond à `https://a5l.fr/`, exposez :
**`/.well-known/matrix/server`** :
```json
{ "m.server": "matrix.a5l.fr:443" }
```
**`/.well-known/matrix/client`** :
```json
{
"m.homeserver": {
"base_url": "https://matrix.a5l.fr"
},
"org.matrix.msc4143.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://mrtc.a5l.fr/livekit/jwt"
}
]
}
```
Le bloc `rtc_foci` (MSC4143) est ce qui dit aux clients Element X d'utiliser **votre** backend MatrixRTC plutôt que celui d'Element.
Servez ces deux fichiers avec `Content-Type: application/json` et `Access-Control-Allow-Origin: *`.
## Étape 2 — Configurer Keycloak
Dans votre realm Keycloak (disons `homelab`), créez deux clients OIDC :
### Client `matrix-auth-service` (pour MAS)
- **Client ID** : `matrix-auth-service`
- **Client authentication** : ON (confidentiel)
- **Standard flow** : activé
- **Valid redirect URIs** : `https://auth.a5l.fr/upstream/callback/01HXXXXXX` (l'UUID sera donné par MAS au premier démarrage, ajustez)
- **Web origins** : `https://auth.a5l.fr`
- Récupérez le **Client Secret**
Vérifiez que les mappers exposent bien `preferred_username`, `email`, `name` dans l'access token.
### Restriction d'accès
Créez un rôle client `matrix-user` et assignez-le aux comptes Keycloak autorisés à utiliser Matrix. Configurez un *Client Scope* pour n'autoriser que ces rôles à obtenir un token.
## Étape 3 — Déployer Synapse, MAS, et PostgreSQL
Voici un `docker-compose.yml` de base. Adaptez les chemins de volumes à votre arborescence.
```yaml
services:
postgres:
image: postgres:16
restart: unless-stopped
environment:
POSTGRES_USER: synapse
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: synapse
POSTGRES_INITDB_ARGS: "--lc-collate=C --lc-ctype=C --encoding=UTF8"
volumes:
- ./data/postgres:/var/lib/postgresql/data
networks:
- matrix
synapse:
image: ghcr.io/element-hq/synapse:latest
restart: unless-stopped
depends_on:
- postgres
environment:
SYNAPSE_SERVER_NAME: a5l.fr
SYNAPSE_REPORT_STATS: "no"
volumes:
- ./data/synapse:/data
ports:
- "127.0.0.1:8008:8008"
networks:
- matrix
mas:
image: ghcr.io/element-hq/matrix-authentication-service:latest
restart: unless-stopped
depends_on:
- postgres
command: ["server", "--config=/config/config.yaml"]
volumes:
- ./data/mas:/config
ports:
- "127.0.0.1:8080:8080"
networks:
- matrix
networks:
matrix:
driver: bridge
```
### Configuration MAS (`./data/mas/config.yaml`)
MAS est l'élément le plus subtil de la pile. Il joue deux rôles : **client OIDC** vers Keycloak (en upstream) et **serveur OAuth/OIDC** pour Synapse et les clients Matrix (en downstream).
```yaml
http:
listeners:
- name: web
resources:
- name: discovery
- name: human
- name: oauth
- name: compat
- name: graphql
- name: assets
binds:
- address: '[::]:8080'
public_base: https://auth.a5l.fr/
issuer: https://auth.a5l.fr/
database:
uri: postgresql://synapse:${POSTGRES_PASSWORD}@postgres/mas
matrix:
homeserver: a5l.fr
endpoint: http://synapse:8008/
secret: ${MATRIX_SHARED_SECRET}
passwords:
enabled: false # On délègue entièrement à Keycloak
upstream_oauth2:
providers:
- id: 01HXXXXXXKEYCLOAK
issuer: https://idp.a5l.fr/realms/homelab
human_name: a5l SSO
client_id: matrix-auth-service
client_secret: ${KEYCLOAK_CLIENT_SECRET}
token_endpoint_auth_method: client_secret_basic
scope: "openid profile email"
claims_imports:
subject:
template: "{{ user.sub }}"
localpart:
action: require
template: "{{ user.preferred_username }}"
displayname:
action: suggest
template: "{{ user.name }}"
email:
action: suggest
template: "{{ user.email }}"
clients:
- client_id: 0000000000000000000SYNAPSE
client_auth_method: client_secret_basic
client_secret: ${SYNAPSE_MAS_SECRET}
```
Générez l'UUID `01HXXXXXXKEYCLOAK` avec `mas-cli config generate` (ou un ULID), et mettez la même valeur dans la *Valid redirect URI* côté Keycloak : `https://auth.a5l.fr/upstream/callback/01HXXXXXXKEYCLOAK`.
### Configuration Synapse (`./data/synapse/homeserver.yaml`)
Points clés à ajouter ou modifier après l'initialisation :
```yaml
server_name: "a5l.fr"
public_baseurl: "https://matrix.a5l.fr/"
listeners:
- port: 8008
type: http
tls: false
bind_addresses: ['0.0.0.0']
x_forwarded: true
resources:
- names: [client, federation, openid]
compress: false
database:
name: psycopg2
args:
user: synapse
password: ${POSTGRES_PASSWORD}
database: synapse
host: postgres
cp_min: 5
cp_max: 10
# Délégation à MAS (remplace oidc_providers + password_config)
experimental_features:
msc3861:
enabled: true
issuer: https://auth.a5l.fr/
client_id: 0000000000000000000SYNAPSE
client_auth_method: client_secret_basic
client_secret: ${SYNAPSE_MAS_SECRET}
admin_token: ${MAS_ADMIN_TOKEN}
account_management_url: https://auth.a5l.fr/account/
# Requis par Element Call
msc3266_enabled: true
msc4222_enabled: true
max_event_delay_duration: 24h
# Désactiver l'auth locale puisque MAS gère tout
password_config:
enabled: false
enable_registration: false
```
## Étape 4 — Déployer le backend MatrixRTC (Element Call)
Ajoutez au `docker-compose.yml` :
```yaml
livekit:
image: livekit/livekit-server:latest
restart: unless-stopped
command: --config /etc/livekit.yaml
volumes:
- ./data/livekit/livekit.yaml:/etc/livekit.yaml:ro
network_mode: host # nécessaire pour la plage UDP WebRTC
lk-jwt-service:
image: ghcr.io/element-hq/lk-jwt-service:latest
restart: unless-stopped
environment:
LK_JWT_PORT: 8081
LIVEKIT_URL: https://mrtc.a5l.fr/livekit/sfu
LIVEKIT_KEY: ${LIVEKIT_KEY}
LIVEKIT_SECRET: ${LIVEKIT_SECRET}
LIVEKIT_FULL_ACCESS_HOMESERVERS: a5l.fr
ports:
- "127.0.0.1:8081:8081"
networks:
- matrix
```
**`./data/livekit/livekit.yaml`** :
```yaml
port: 7880
bind_addresses:
- "127.0.0.1"
rtc:
tcp_port: 7881
port_range_start: 50000
port_range_end: 50100
use_external_ip: true
keys:
${LIVEKIT_KEY}: ${LIVEKIT_SECRET}
turn:
enabled: false # On utilise coturn dédié
```
## Étape 5 — Element Web
```yaml
element:
image: vectorim/element-web:latest
restart: unless-stopped
volumes:
- ./data/element/config.json:/app/config.json:ro
ports:
- "127.0.0.1:8082:80"
networks:
- matrix
```
**`./data/element/config.json`** (minimal) :
```json
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.a5l.fr",
"server_name": "a5l.fr"
}
},
"features": {
"feature_video_rooms": true,
"feature_element_call_video_rooms": true,
"feature_group_calls": true
},
"element_call": {
"url": "https://chat.a5l.fr",
"use_exclusively": true,
"participant_limit": 8
}
}
```
## Étape 6 — Reverse proxy
Voici un squelette Nginx. La partie sensible est le routage `/livekit/jwt/` et `/livekit/sfu/` sur `mrtc.a5l.fr`.
```nginx
# matrix.a5l.fr
server {
listen 443 ssl http2;
listen 8448 ssl http2; # Fédération
server_name matrix.a5l.fr;
ssl_certificate /etc/letsencrypt/live/matrix.a5l.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/matrix.a5l.fr/privkey.pem;
client_max_body_size 100M;
location ~ ^(/_matrix|/_synapse/client) {
proxy_pass http://127.0.0.1:8008;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
}
# auth.a5l.fr (MAS)
server {
listen 443 ssl http2;
server_name auth.a5l.fr;
ssl_certificate /etc/letsencrypt/live/auth.a5l.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/auth.a5l.fr/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
}
}
# mrtc.a5l.fr (Element Call backend)
server {
listen 443 ssl http2;
server_name mrtc.a5l.fr;
ssl_certificate /etc/letsencrypt/live/mrtc.a5l.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/mrtc.a5l.fr/privkey.pem;
location ^~ /livekit/jwt/ {
proxy_pass http://127.0.0.1:8081/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
location ^~ /livekit/sfu/ {
proxy_pass http://127.0.0.1:7880/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_send_timeout 120;
proxy_read_timeout 120;
proxy_buffering off;
}
}
# chat.a5l.fr (Element Web)
server {
listen 443 ssl http2;
server_name chat.a5l.fr;
ssl_certificate /etc/letsencrypt/live/chat.a5l.fr/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/chat.a5l.fr/privkey.pem;
location / {
proxy_pass http://127.0.0.1:8082;
}
}
```
## Étape 7 — Premier démarrage et test
```bash
# Génération des secrets une fois pour toutes
openssl rand -hex 32 # pour chaque secret du .env
docker compose up -d postgres
sleep 5
docker compose exec postgres psql -U synapse -c "CREATE DATABASE mas;"
# Initialiser Synapse
docker compose run --rm synapse generate
# Éditez data/synapse/homeserver.yaml avec la conf ci-dessus
# Démarrer
docker compose up -d
```
**Tests :**
1. `https://a5l.fr/.well-known/matrix/client` doit renvoyer le JSON correct
2. `https://federationtester.matrix.org/#a5l.fr` doit passer au vert
3. Sur Element Web (`https://chat.a5l.fr`), cliquez "Continuer avec a5l SSO" → vous devez être redirigé vers Keycloak → après login, retour sur Element connecté
4. Créez un salon, lancez un appel vidéo → vérifiez les logs LiveKit
## Pièges spécifiques au homelab derrière FAI
**NAT et WebRTC.** Vos appels passeront mal sans coturn si vos correspondants sont derrière des NAT symétriques (4G/5G notamment). Déployez coturn sur le port 3478/5349 et déclarez-le dans la config LiveKit (section `turn_servers`) ou laissez le client utiliser le TURN annoncé par Synapse via `turn_uris`.
**IP dynamique.** Configurez un client DDNS qui met à jour vos enregistrements A. Pour la plupart des registrars, un cron toutes les 5 minutes suffit. Les enregistrements SRV n'ont pas besoin d'être touchés.
**Fédération bavarde.** Si vous rejoignez `#matrix:matrix.org` ou un autre gros salon, votre base PostgreSQL va grossir vite. Activez `mjolnir` ou configurez `state_compressor` après quelques semaines.
**Sauvegarde.** Critique :
```bash
docker compose exec postgres pg_dumpall -U synapse | gzip > backup-$(date +%F).sql.gz
tar czf data-$(date +%F).tar.gz data/synapse/media_store data/mas data/livekit
```
## Pour aller plus vite
Sincèrement, si vous n'êtes pas attaché à comprendre chaque brique, **utilisez `matrix-docker-ansible-deploy`** de spantaleev. Le playbook gère MAS, Synapse, Element Web, Element Call, LiveKit, coturn, le SSO Keycloak, les `.well-known`, certbot — tout, avec les bonnes versions ensemble. Vous éditez un `vars.yml`, vous lancez `ansible-playbook setup.yml --tags=setup-all,start`, et c'est en ligne. Le tutoriel ci-dessus vous donne la compréhension de ce que le playbook fait sous le capot, ce qui reste précieux pour le débogage.
## Et après ?
Une fois que ça tourne, des extensions utiles :
- **synapse-admin** pour gérer les utilisateurs en interface web
- **mautrix-signal / mautrix-whatsapp** : ponts vers Signal, WhatsApp, Telegram, etc., pour centraliser vos messageries dans Element
- **hookshot** pour les notifications GitHub/GitLab dans des salons
- **maubot** pour des bots utilitaires