illustration

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

  1. PostgreSQL (Synapse, Keycloak, MAS)

  2. Keycloak

  3. MAS

  4. Synapse

  5. 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.