
Installation et configuration d'une infrastructure Matrix complĂšte avec authentification SSO via Keycloak et Matrix Authentication Service (MAS)
đŻ Objectifs
Installer un service de messagerie interne sécurisé en s'appuyant sur Matrix, pas de fédération dans ce cas précis.
Les utilisateurs pourront communiquer via l'application mobile Element X ou via leur navigateur sur poste de travail quelle que soit leur position géographique.
Infrastructure Matrix sécurisée et modulaire, comprenant :
-
Synapse : Le serveur Matrix (homeserver).
-
Element : Le client web.
-
Keycloak : Le fournisseur d'identité (SSO).
-
Matrix Authentication Service (MAS) : Le service d'authentification OAuth2/OIDC.
đ PrĂ©requis
L'architecture est conçue pour ĂȘtre dĂ©ployĂ©e sur un ou plusieurs serveurs avec Docker, derriĂšre un reverse proxy (HAProxy).
Dans ce lab, 2 VMs, une pour la stack synapse, l'autre pour les services d'authentification Keycloak et MAS.
Domaines publics :
-
matrix.domaine.com â Synapse / Element
-
auth.domaine.com â Keycloak
-
mas.domaine.com â Matrix Authentication Service
AccÚs aux entrées DNS pour l'ajout des enregistrement de type A.
Certificats TLS : Gérés par HAProxy (Let's Encrypt).
Docker et Docker Compose : Installés sur chaque VM.
Réseau : Les VMs doivent pouvoir communiquer entre elles sur les ports internes.
| VM | IP | Services |
|---|---|---|
| VM1 | 192.168.10.210 | Synapse, Element, Nginx |
| VM2 | 192.168.10.118 | Keycloak, MAS |
đ§ Ătape 1 : Installation de Synapse et Element (VM Synapse)
1.1 Structure des dossiers
/srv/docker/matrix/
âââ docker-compose.yml
âââ data/
â âââ homeserver.yaml
âââ element/
â âââ config.json
âââ nginx/
â âââ nginx.conf
âââ .env
1.2 docker-compose.yml
services:
postgres:
image: postgres:17-alpine
container_name: synapse_db
restart: unless-stopped
environment:
POSTGRES_USER: ${SYN_POSTGRES_USER}
POSTGRES_PASSWORD: ${SYN_POSTGRES_PASSWORD}
POSTGRES_DB: ${SYN_POSTGRES_DB}
POSTGRES_INITDB_ARGS: --encoding=UTF-8 --lc-collate=C --lc-ctype=C
volumes:
- ./postgres_data:/var/lib/postgresql/data
networks:
- matrix_net
synapse:
image: matrixdotorg/synapse:1.98.0
container_name: synapse
restart: unless-stopped
depends_on:
- postgres
ports:
- "8008:8008"
extra_hosts:
- "auth.domaine.com:XX.XX.XX.XX" # IP publique
volumes:
- ./data:/data
environment:
- SYNAPSE_SERVER_NAME=matrix.domaine.com
- SYNAPSE_REPORT_STATS=no
logging:
driver: journald
options:
tag: "synapse"
labels: "synapse,production"
networks:
- matrix_net
element:
image: vectorim/element-web:v1.12.22
container_name: element
restart: unless-stopped
volumes:
- ./element/config.json:/app/config.json:ro
networks:
- matrix_net
nginx:
image: nginx:stable
container_name: nginx
restart: unless-stopped
volumes:
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro
networks:
- matrix_net
depends_on:
- synapse
- element
networks:
matrix_net:
driver: bridge
Renseigner le fichier .env pour les variables.
1.3 homeserver.yaml (data/homeserver.yaml)
Générer un secret pour MAS
openssl rand -hex 32
server_name: "matrix.domaine.com"
public_baseurl: https://matrix.domaine.com/
database:
name: psycopg2
args:
user: utilisateur_postgres # identique Ă ${SYN_POSTGRES_USER}
password: motdepasse_postgres # identique Ă ${SYN_POSTGRES_PASSWORD}
database: nomdelabase_postgres # identique Ă ${SYN_POSTGRES_DB}
host: postgres
port: 5432
# Délégation OAuth2 vers MAS
matrix_authentication_service:
enabled: true
endpoint: http://192.168.10.118:8888 # IP de la VM MAS
secret: "secret_généré_avec_openssl"
suppress_key_server_warning: true
1.4 nginx/nginx.conf
server {
listen 80;
server_name matrix.domaine.com;
location ~ ^/(_matrix|_synapse) {
proxy_pass http://synapse:8008;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
location / {
proxy_pass http://element:80;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto https;
}
}
1.5 element/config.json
{
"default_server_config": {
"m.homeserver": {
"base_url": "https://matrix.domaine.com",
"server_name": "matrix.domaine.com"
}
}
}
đ Ătape 2 : Installation de Keycloak (VM auth)
2.1 Structure des dossiers
/srv/docker/keycloak/
âââ docker-compose.yml
âââ .env
âââ keycloak_data/
2.2 docker-compose.yml
services:
postgres:
env_file:
- ./.env
image: postgres:17-alpine
container_name: keycloak-db
environment:
POSTGRES_DB: ${KC_POSTGRES_DB}
POSTGRES_USER: ${KC_POSTGRES_USER}
POSTGRES_PASSWORD: ${KC_POSTGRES_PASSWORD}
volumes:
- ./postgres_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U ${KC_POSTGRES_USER} -d ${KC_POSTGRES_DB}"]
interval: 10s
timeout: 5s
retries: 5
networks:
- keycloak_net
keycloak:
env_file:
- ./.env
image: quay.io/keycloak/keycloak:26.6.4
container_name: keycloak
environment:
KC_DB: ${KC_DB}
KC_DB_URL: ${KC_DB_URL}
KC_DB_USERNAME: ${KC_DB_USERNAME}
KC_DB_PASSWORD: ${KC_DB_PASSWORD}
KC_HOSTNAME: ${KC_HOSTNAME}
KC_HTTP_PORT: 80
KC_BOOTSTRAP_ADMIN_USERNAME: ${KC_BOOTSTRAP_ADMIN_USERNAME}
KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD}
KC_PROXY_HEADERS: xforwarded
KC_HOSTNAME_STRICT: false
KC_LOG_LEVEL: info
logging:
driver: journald
options:
tag: "keycloak-auth"
labels: "keycloak,production"
volumes:
- ./keycloak_data:/opt/keycloak/data
depends_on:
- postgres
command: start --http-enabled=true
networks:
- keycloak_net
ports:
- "80:80"
networks:
keycloak_net:
driver: bridge
Renseigner le fichier .env pour les variables.
2.3 Configuration du client OIDC dans Keycloak
Sur le site https://it-tools.tech/ulid-generator générer un ULID
Se connecter Ă l'interface d'administration : https://auth.domaine.com
Créer un realm (ex: domaine.com).
Aller dans Clients â Create client.
Paramétrer le client mas :
Client ID : mas
Client authentication : On (confidential)
Authentication flow : Standard flow (activé)
Direct access grants : Off
Implicit flow : Off
Valid redirect URIs : https://mas.domaine.com/upstream/callback/ULID
Web origins : https://mas.domaine.com
Dans l'onglet Credentials, noter le Client secret.
2.4 Configuration fail2ban
sudo vim /etc/fail2ban/filter.d/keycloak-journald.conf
[Definition]
failregex = \s+WARN\s+\[org\.keycloak\.events\]\s+.*type="LOGIN_ERROR".*ipAddress="<HOST>".*$
ignoreregex =
sudo vim /etc/fail2ban/jail.local
enabled = true
port = 80
filter = keycloak-journald
backend = systemd
journalmatch = CONTAINER_TAG=keycloak-auth
maxretry = 3
findtime = 300
bantime = 86400
action = iptables-multiport[name=keycloak, port="80", protocol=tcp]
sudo systemctl restart fail2ban
sudo fail2ban-client status keycloak
sudo iptables -L -n | grep keycloak
đ Ătape 3 : Installation de Matrix Authentication Service (VM auth)
3.1 Structure des dossiers
/srv/docker/mas/
âââ docker-compose.yml
âââ config.yaml
âââ .env
âââ db_data/
3.2 docker-compose.yml
services:
db:
image: postgres:17-alpine
container_name: mas-db
restart: unless-stopped
environment:
POSTGRES_USER: ${MAS_POSTGRES_USER}
POSTGRES_PASSWORD: ${MAS_POSTGRES_PASSWORD}
POSTGRES_DB: ${MAS_POSTGRES_DB}
volumes:
- ./db_data:/var/lib/postgresql/data
networks:
- mas_net
matrix-auth-service:
image: ghcr.io/element-hq/matrix-authentication-service:v1.19.0
container_name: matrix-auth-service
restart: unless-stopped
depends_on:
- db
ports:
- "8888:8080" # Port exposé pour Synapse et HAProxy
- "8081:8081" # Healthcheck
volumes:
- ./config.yaml:/app/config/config.yaml:ro
networks:
- mas_net
networks:
mas_net:
driver: bridge
3.3 Génération et configuration de config.yaml
Génération :
docker run ghcr.io/element-hq/matrix-authentication-service config generate > config.yaml
Configuration minimale config.yaml :
http:
listeners:
- name: web
resources:
- name: discovery
- name: human
- name: oauth
- name: compat
- name: graphql
- name: assets
binds:
- address: '[::]:8080'
proxy_protocol: false
- name: internal
resources:
- name: health
binds:
- host: localhost
port: 8081
proxy_protocol: false
trusted_proxies:
- 192.168.10.0/24
- 172.16.0.0/12
- 10.0.0.0/10
- 127.0.0.1/8
- fd00::/8
- ::1/128
public_base: https://mas.domaine.com
issuer: https://mas.domaine.com
database:
uri: postgres://mas:833ea31192d18d8029ebaad5a2e6fa0904fc4bbc4c7c51e1842f52b7cdc4876d@db:5432/mas
max_connections: 10
min_connections: 0
connect_timeout: 30
idle_timeout: 600
max_lifetime: 1800
email:
from: '"domaine Chat" <noreply@domaine.com>'
reply_to: '"domaine Support" <noreply@domaine.com>'
transport: smtp
smtp:
host: smtp.domaine.com
port: 587
tls: starttls
username: "user@domaine.com"
password: "motdepasse"
mode: starttls
hostname: domaine.com
secrets:
données générées automatiquement
passwords:
enabled: true
schemes:
- version: 1
algorithm: argon2id
minimum_complexity: 3
matrix:
kind: synapse
homeserver: matrix.domaine.com
secret: généré_automatiquement
endpoint: http://192.168.10.210:8008
upstream_oauth2:
providers:
- id: "ULID généré précédemment"
human_name: "Keycloak"
brand_name: "keycloak"
issuer: "https://auth.domaine.com/realms/mon_realms"
client_id: "mas"
client_secret: "secret_généré_par_keycloak"
token_endpoint_auth_method: "client_secret_basic"
scope: "openid profile email"
claims_imports:
localpart:
action: require
template: "{{ user.preferred_username }}"
displayname:
action: suggest
template: "{{ user.name }}"
email:
action: suggest
template: "{{ user.email }}"
3.4 Validation de la configuration
docker run -v /srv/docker/mas:/config ghcr.io/element-hq/matrix-authentication-service --config /config/config.yaml config check
đ Ătape 4 : Configuration du Reverse Proxy HAProxy
Configuration pour HAProxy :
# Backend Matrix (matrix.domaine.com)
frontend public
bind *:443 ssl crt /etc/ssl/private/domaine.com.pem
bind *:80
acl is_chat hdr(host) -i matrix.domaine.com
use_backend matrix_nginx if is_chat
backend matrix_nginx
server synapse-vm 192.168.10.210:80 # IP de la VM Synapse (port Nginx)
# Backend Keycloak (auth.domaine.com)
frontend keycloak
bind *:443 ssl crt /etc/ssl/private/domaine.com.pem
acl is_keycloak hdr(host) -i auth.domaine.com
use_backend keycloak_backend if is_keycloak
backend keycloak_backend
server keycloak-vm 192.168.10.118:80 # IP de la VM Keycloak
# Backend MAS (mas.domaine.com)
frontend mas
bind *:443 ssl crt /etc/ssl/private/domaine.com.pem
acl is_mas hdr(host) -i mas.domaine.com
use_backend mas_backend if is_mas
backend mas_backend
server mas-vm 192.168.10.118:8888 # IP de la VM MAS (port exposé)
đ Ătape 5 : Synchronisation et dĂ©marrage
5.1 Ordre de démarrage
-
PostgreSQL (Synapse, Keycloak, MAS)
-
Keycloak
-
MAS
-
Synapse
-
Element et Nginx
5.2 Commandes de démarrage
# VM Synapse
docker compose up -d
# VM Keycloak
docker compose up -d
# VM MAS
docker compose up -d
5.3 Vérification des services
ConnectivitĂ© Synapse → MAS :
# Depuis la VM Synapse
curl -v http://192.168.10.118:8888/health
ConnectivitĂ© MAS → Synapse :
# Depuis la VM MAS
curl -v http://192.168.10.210:8008/_matrix/client/versions
đ§Ș Ătape 6 : Tests d'authentification
https://mas.domaine.com/login.
"Se connecter avec Keycloak".
Saisir les identifiants.
AprĂšs authentification, redirection vers Element.
Si l'utilisateur n'existe pas : Il est créé automatiquement dans Synapse.
Si erreur de type Localpart not available : utilisateur déjà existant dans synapse, pour le supprimer:
docker compose exec synapse psql -U synapse -d synapse -c "DELETE FROM users WHERE name = '@test:matrix.domaine.com';"
đ RĂ©sumĂ© des paramĂštres importants
| Service | Domaine / URL | Port interne | Port exposé | RÎle |
|---|---|---|---|---|
| Synapse | matrix.domaine.com | 8008 | 8008 | Homeserver Matrix |
| Element | matrix.domaine.com | 80 | - | Client web |
| Keycloak | auth.domaine.com | 80 | 80 | Fournisseur d'identité |
| MAS | mas.domaine.com | 8080 | 8888 | Service d'authentification OAuth2 |
| HAProxy | - | - | 443 | Reverse proxy TLS |
đ§ DĂ©pannage
| Erreur | Cause | Solution |
|---|---|---|
| Connection refused sur 8008 | Port Synapse non exposé | Ajouter ports: - "8008:8008" dans le service Synapse |
| invalid_redirect_uri | Mauvaise URI dans Keycloak | Vérifier Valid Redirect URIs pour le client mas |
| Localpart not available | Compte existant dans Synapse | Supprimer l'ancien compte ou configurer un préfixe dans MAS |
| 504 Gateway Time-out | Timeout entre MAS et Synapse | Vérifier la connectivité et les timeouts du proxy |
| 401 Unauthorized (introspection) | Secret MAS/Synapse différent | Synchroniser le secret dans les deux configurations |
đ Commandes utiles
Gestion des utilisateurs MAS :
# Créer un utilisateur
docker compose exec matrix-auth-service mas-cli manage register-user --username nom
# Créer un administrateur
docker compose exec matrix-auth-service mas-cli manage register-user --username admin --admin
# Lister les utilisateurs
docker compose exec matrix-auth-service mas-cli manage list-users
Vérification réseau :
# Tester l'écoute d'un port sur l'hÎte
ss -pantu | grep <port>
# Tester la connexion depuis un conteneur
docker compose exec <service> curl -v <url>
đ SĂ©curitĂ©
Keycloak : Protéger avec fail2ban.
Certificats : Utiliser Let's Encrypt pour les domaines publics.
Mises à jour : Planifier des mises à jour réguliÚres de Synapse, MAS et Keycloak.
âš Bonus
đ ImplĂ©mentation des fonctionnalitĂ©s d'appels vocaux et vidĂ©o
Ajout des fonctionnalités d'appels vocaux et vidéo à l'infrastructure Matrix existante, en utilisant LiveKit et le service lk-jwt-service.
đ PrĂ©requis spĂ©cifiques
Créer un sous-domaine dédié, ici: live.domaine.com
Ports ouverts sur le pare-feu (NAT) :
-
443/TCP : Signalisation WebSocket (via HAProxy).
-
7881/TCP : Fallback TCP pour les médias.
-
7882/UDP et une plage 50100-50200/UDP : Flux média principaux.
-
8448/TCP : Fédération Matrix (si activé pour la validation OpenID).
Ătape 1 : Configuration de HAProxy
Ajout des rĂšgles suivantes Ă la configuration HAProxy pour rediriger le trafic vers LiveKit et le service JWT.
Ordre des rĂšgles important : les rĂšgles spĂ©cifiques (comme /sfu/get) doivent ĂȘtre placĂ©es avant la rĂšgle gĂ©nĂ©rique.
frontend livekit_https
bind *:443 ssl crt /etc/ssl/private/domaine.com.pem
mode http
# ACL pour les endpoints du service JWT (prioritaires)
acl is_jwt path_beg /sfu/get /healthz /get_token
use_backend jwt_backend if is_jwt
# Par défaut, tout le reste va à LiveKit (signalisation)
default_backend livekit_backend
backend jwt_backend
mode http
# Redirige vers le service JWT sur le port 8081
server jwt-vm 192.168.10.210:8081
backend livekit_backend
mode http
# Redirige vers LiveKit sur le port 7880
server livekit-vm 192.168.10.210:7880
Ătape 2 : Configuration des services Docker
Nouveau dossier dédié /srv/docker/live
Générer des clés sécurisées pour LIVEKIT_KEY et LIVEKIT_SECRET.
docker run --rm livekit/livekit-server:latest generate-keys
Créer un nouveau fichier docker-compose.yml (ou ajoutez ces services à la stack synapse existante) pour LiveKit et le service JWT.
Variables d'environnement importantes :
-
LIVEKIT_URL=wss://live.domaine.com : URL WebSocket du serveur LiveKit.
-
LIVEKIT_FULL_ACCESS_HOMESERVERS=matrix.domaine.com : Le server_name de Synapse.
-
LIVEKIT_CS_API_URL_OVERRIDES=matrix.domaine.com=http://192.168.10.210:8008 : Force le service JWT à utiliser l'URL interne de Synapse (port 8008) pour la validation OpenID, au lieu du port de fédération (8448).
services:
lk-jwt-service:
image: ghcr.io/element-hq/lk-jwt-service:latest
container_name: lk-jwt-service
restart: unless-stopped
environment:
LIVEKIT_JWT_BIND: ":8081"
LIVEKIT_URL: "wss://live.domaine.com"
LIVEKIT_KEY: "${LIVEKIT_KEY}"
LIVEKIT_SECRET: "${LIVEKIT_SECRET}"
LIVEKIT_FULL_ACCESS_HOMESERVERS: "matrix.domaine.com"
LIVEKIT_CS_API_URL_OVERRIDES: "matrix.domaine.com=http://192.168.10.210:8008"
ports:
- "8081:8081"
networks:
- matrix_net
livekit:
image: livekit/livekit-server:latest
container_name: livekit
restart: unless-stopped
command: --config /etc/livekit.yaml
volumes:
- ./livekit.yaml:/etc/livekit.yaml:ro
ports:
- "7880:7880/tcp"
- "7881:7881/tcp"
- "7882:7882/udp"
- "50100-50200:50100-50200/udp"
networks:
- matrix_net
Ătape 3 : Configuration de LiveKit
Créer un fichier livekit.yaml pour configurer le serveur LiveKit.
port: 7880
bind_addresses:
- "0.0.0.0"
rtc:
tcp_port: 7881
port_range_start: 50100
port_range_end: 50200
node_ip: XX.XX.XX.XX # IP publique
keys:
devkey: "identique_Ă _LIVEKIT_KEY"
room:
auto_create: false
turn:
enabled: false
domain: localhost
cert_file: ""
key_file: ""
tls_port: 5349
udp_port: 443
external_tls: true
logging:
level: info
Ătape 4 : Configuration de Synapse
Dans homeserver.yaml, activer les fonctionnalités expérimentales pour le support des appels.
experimental_features:
msc3266_enabled: true
msc4222_enabled: true
msc4140_enabled: true
max_event_delay_duration: 24h
rc_message:
per_second: 0.5
burst_count: 30
rc_delayed_event_mgmt:
per_second: 1
burst_count: 20
Si utilisation de la fédération, configurer un écouteur HTTPS sur le port 8448 :
listeners:
- port: 8448
tls: true
type: http
x_forwarded: true
resources:
- names: [federation]
compress: false
Ătape 5 : Annonce du service aux clients (.well-known)
Mettre à jour le fichier /.well-known/matrix/client servi par le serveur web (Nginx) pour annoncer la présence du backend LiveKit.
{
"m.homeserver": {
"base_url": "https://matrix.domaine.com"
},
"org.matrix.msc4143.rtc_foci": [
{
"type": "livekit",
"livekit_service_url": "https://live.domaine.com"
}
]
}
Ătape 6 : Validation
Démarrer les services
docker compose up -d
Surveiller les logs
docker compose logs -f lk-jwt-service livekit
Tester un appel depuis Element X (mobile) ou Element Web. Vérifier que les logs du service JWT et de LiveKit s'affichent.
đ§ DĂ©pannages
| Erreur | Cause probable | Solution |
|---|---|---|
| OPEN_ID_ERROR sur mobile | Le service JWT n'est pas accessible ou ne peut pas valider le token OpenID. | Vérifier la configuration HAProxy, les variables LIVEKIT_CS_API_URL_OVERRIDES et la connectivité entre JWT et Synapse. |
| Failed to look up user info (timeout) | Le service JWT ne parvient pas Ă joindre Synapse (port 8448) ou utilise le mauvais protocole. | Configurer Synapse en HTTPS sur le port 8448 ou forcer l'URL de l'API Client-Server avec LIVEKIT_CS_API_URL_OVERRIDES. |
| 404 page not found sur /sfu/get | HAProxy ne redirige pas correctement le trafic vers le service JWT. | VĂ©rifier l'ordre des rĂšgles HAProxy (les chemins JWT doivent ĂȘtre prioritaires). |
| Pas de logs dans LiveKit | Le client n'atteint pas LiveKit. | Vérifier la résolution DNS de live.domaine.com, les rÚgles HAProxy et le pare-feu (ports UDP). |
đĄ Visualisation des logs de LiveKit
Pour surveiller et visualiser les logs de LiveKit en temps réel :
# Afficher les logs en direct
docker logs -f livekit
# Extraire les logs dans un fichier
docker logs livekit --since 1h > livekit_logs.txt
Option : Configurer le logging Docker vers journald et utiliser un outil comme logspout ou un serveur Python minimal pour les visualiser dans un navigateur.
đ Conclusion
On dispose d'une infrastructure Matrix complĂšte et sĂ©curisĂ©e, avec une authentification SSO via Keycloak et une intĂ©gration native avec les clients mobiles Element X grĂące Ă Matrix Authentication Service. Cette architecture est modulaire, scalable et prĂȘte Ă ĂȘtre adaptĂ©e Ă d'autres environnements.