# Tutoriel Matrix sur a5l.fr — Synapse + Element + Element Call avec SSO Keycloak Je vais d'abord vérifier l'état actuel de l'écosystème Matrix (les choses bougent vite côté Element Call et MSC3861), puis construire le tutoriel sur des bases à jour.Information importante : Element X (les nouveaux clients mobiles Element) **n'accepte pas le SSO Synapse classique** — il faut passer par **MAS (Matrix Authentication Service)**. C'est devenu le standard depuis 2024-2025. Je vérifie aussi l'état d'Element Call.J'ai ce qu'il me faut. Le paysage a évolué : Element X (les nouvelles apps mobiles) exige **MAS (Matrix Authentication Service)** pour le SSO, et Element Call exige un backend **MatrixRTC = LiveKit SFU + lk-jwt-service**. Je vous propose un tutoriel structuré et réaliste. --- # 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 auth IN A chat IN A mrtc IN A turn IN A ; 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