draft: Tutoriel Matrix sur a5l.fr — Synapse + Element + Element Call avec SSO Keycloak
This commit is contained in:
@@ -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
|
||||
Reference in New Issue
Block a user