Aller au contenu

Matrix et les ponts de messagerie

Activer le serveur Matrix (Synapse), son client web Element et les ponts mautrix (WhatsApp, Signal, Telegram, Messenger, Discord). Pour l’architecture, voir Matrix dans DNF.

  1. Déclarer les services sur l’hôte cible dans etc/config.yaml :

    etc/config.yaml
    services:
    matrix:
    title: "Matrix"
    description: "Messagerie instantanée"
    element: # client web
    turn: # relais audio/vidéo des clients historiques
  2. Déclarer l’administrateur Matrix (clé globale, hors hôtes) :

    etc/config.yaml
    matrix:
    admin: "alice" # local part du compte administrateur

    Ce compte administre les ponts et reçoit les alertes de supervision.

  3. Créer les secrets requis (voir tableau ci-dessous), puis déployer.

L’authentification passe par le SSO Kanidm (OIDC) : tout utilisateur déclaré se connecte avec son compte réseau, aucune création de compte Matrix n’est nécessaire.

À renseigner dans usr/secrets/secrets.yaml :

Clé sopsRôleGénération
matrix-rss-passwordRegistration shared secret Synapseopenssl rand -hex 32
oidc-secret-matrixSecret du client OIDC Kanidmcf. SSO
turn-secretSecret partagé coturn (si turn activé)openssl rand -hex 32
livekit-secretSecret partagé MatrixRTC (si matrixRtc activé)just passwd-livekit
mautrix-doublepuppet-as-tokenToken appservice double puppeting (commun aux ponts)openssl rand -hex 32
mautrix-doublepuppet-hs-tokenIdem, requis par Synapse, jamais utiliséopenssl rand -hex 32

Le serveur fonctionne dans un seul de ces deux modes, réglé par darkone.service.matrix.mas.enable. Le mode choisi change les clients compatibles, la façon d’inscrire des amis et les outils d’administration.

Synapse historique (défaut)MAS activé (recommandé)
Optionmas.enable = falsemas.enable = true
Connexion SSOSynapse est client OIDC de KanidmMAS est client OIDC de Kanidm
Element Web / Desktopouioui
Element X (Android, iOS)nonoui
Connexion par QR codenonoui
Application proposée sur mobileElement ClassicElement X
Portail de compteaucunhttps://matrix.domain.tld/account/
Jetons d’inscriptionAPI admin Synapsednf-mas sur l’hôte
Promotion administrateurSQL dans la base Synapsednf-mas manage promote-admin

Le Matrix Authentication Service 🡕 remplace l’authentification interne de Synapse. Pour ce qu’il change et comment il s’insère, voir l’architecture.

  1. Générer les trois secrets dédiés (idempotent, n’écrase jamais l’existant) :

    Fenêtre de terminal
    just passwd-mas
    Clé sopsRôle
    mas-encryption-secretChiffrement interne MAS
    mas-synapse-secretSecret partagé MAS ↔ Synapse
    mas-rsa-private-keyClé de signature des tokens
  2. Activer l’option dans la config nix de l’hôte :

    # usr/machines/<host>/default.nix
    darkone.service.matrix.mas.enable = true;
  3. Déployer. Sur un serveur neuf, c’est terminé. Sur un serveur qui a déjà des comptes, enchaîner sur la migration sans laisser personne se connecter entre les deux.

MAS devient propriétaire des comptes et des sessions : ils doivent être importés avec l’outil officiel syn2mas, servi par la commande dnf-mas de l’hôte. Sans cet import, tous les comptes sont invisibles et tout le monde est déconnecté.

  1. Sauvegarder, dans cet ordre (le dump doit entrer dans l’instantané) :

    Fenêtre de terminal
    sudo systemctl start postgresqlBackup.service
    sudo systemctl start restic-backups-system-main.service
  2. Déployer la configuration avec mas.enable = true (cf. ci-dessus). La fenêtre de maintenance commence ici.

  3. Arrêter les deux services : syn2mas exige Synapse hors ligne, et MAS ne doit pas provisionner de compte entre-temps.

    Fenêtre de terminal
    sudo systemctl stop matrix-synapse matrix-authentication-service
  4. Vérifier, puis répéter à blanc, puis migrer :

    Fenêtre de terminal
    sudo dnf-mas syn2mas check
    sudo dnf-mas syn2mas migrate --dry-run
    sudo dnf-mas syn2mas migrate

    Le compte-rendu annonce les comptes, liens SSO, jetons d’accès et devices repris. Les centaines d’utilisateurs « ignorés » sont les fantômes des ponts : c’est normal.

  5. Redémarrer, MAS d’abord :

    Fenêtre de terminal
    sudo systemctl start matrix-authentication-service
    sudo systemctl start matrix-synapse

En plus des utilisateurs du SSO, on peut ouvrir une inscription sur jeton pour des comptes locaux à mot de passe (les amis). Sans jeton, aucune inscription n’est possible. Le commutateur est le même dans les deux modes, seule la fabrication du jeton diffère.

# usr/machines/<host>/default.nix
darkone.service.matrix.friendRegistration.enable = true;
  1. Créer un jeton sur l’hôte Matrix. Ici valable une fois et 7 jours (sans option, le jeton est à usage unique et sans expiration) :

    Fenêtre de terminal
    sudo dnf-mas manage issue-user-registration-token --usage-limit 1 --expires-in 604800

    La réponse affiche le jeton, par exemple Created user registration token: 9SwPTGIFGQPa. Le transmettre à l’ami.

  2. L’ami ouvre https://matrix.domain.tld/register et choisit la création par mot de passe.

  3. Il saisit son identifiant et son mot de passe, puis le jeton, réclamé à l’étape suivante.

  4. Il se connecte ensuite avec ce couple identifiant / mot de passe depuis n’importe quel client, Element X compris.

Collision d’identifiant avec un utilisateur déclaré

Section intitulée « Collision d’identifiant avec un utilisateur déclaré »

Amis et utilisateurs SSO partagent un seul espace de noms : @bob:domain.tld n’appartient qu’à un compte, quelle que soit son origine. Et un identifiant Matrix est définitif — la table users de MAS impose son unicité, et désactiver un compte ne libère pas son identifiant. On ne renomme pas, on ne recycle pas.

Le cas typique : un ami s’inscrit comme bob, puis bob est déclaré plus tard comme utilisateur DNF dans etc/config.yaml. Sa première connexion SSO s’arrête alors sur une page d’erreur User exists :

Upstream account provider returned "bob" as username, which is not linked to
that upstream account. Your homeserver does not allow linking an upstream
account to an existing account

Vérifier avant de déclarer un utilisateur, sur l’hôte Matrix :

Fenêtre de terminal
sudo -u postgres psql matrix-authentication-service \
-c "SELECT username FROM users WHERE lower(username) = lower('bob');"

Deux issues, selon ce qu’on veut garder :

Rattache l’identité Kanidm au compte ami existant, qui conserve tout : salons, historique, clés de chiffrement, ponts. C’est presque toujours ce qu’on veut, et l’intéressé le fait lui-même.

  1. Déclarer d’abord bob dans etc/config.yaml et déployer, pour qu’il ait des identifiants Kanidm.

  2. Bob se connecte sur https://matrix.domain.tld/account/ avec son mot de passe d’ami.

  3. Dans le même navigateur, il ouvre le lien de rattachement du fournisseur IDM :

    https://matrix.domain.tld/upstream/authorize/01JDNF0000000000000KAN1DM0
  4. Il s’identifie sur Kanidm ; MAS propose alors « Lier à votre compte existant », qu’il valide.

Bob se connecte ensuite par le SSO comme n’importe quel utilisateur déclaré. Son mot de passe d’ami reste valable : le supprimer se fait depuis le portail de compte.

Les ponts WhatsApp, Signal, Telegram et Messenger sont activés par défaut avec Matrix ; Discord est désactivé par défaut. Réglage par hôte dans la configuration nix de la machine :

# usr/machines/<host>/default.nix
darkone.service.matrix.bridges = {
discord.enable = true; # opt-in
messenger.enable = false; # désactiver un pont par défaut
};

Chaque pont a ses secrets propres :

PontClés sopsRemarque
WhatsAppmautrix-whatsapp-as-token, mautrix-whatsapp-hs-token, mautrix-whatsapp-encryption-pickle-keyopenssl rand -hex 32 chacun
Signalmautrix-signal-as-token, mautrix-signal-hs-token, mautrix-signal-encryption-pickle-keyopenssl rand -hex 32 chacun
Messengermautrix-meta-as-token, mautrix-meta-hs-token, mautrix-meta-encryption-pickle-keyopenssl rand -hex 32 chacun
Telegrammautrix-telegram-api-id, mautrix-telegram-api-hash, mautrix-telegram-as-token, mautrix-telegram-hs-tokenAPI id/hash à créer sur my.telegram.org 🡕
Discordmautrix-discord-as-token, mautrix-discord-hs-tokenopenssl rand -hex 32 chacun

Les tokens as/hs rendent la registration appservice de chaque pont déterministe (voir les tokens d’appservice).

Rien à gérer : tout compte local (@*:domain.tld) peut utiliser chaque pont avec son propre compte distant, sans interférence entre utilisateurs. Le compte matrix.admin dispose des commandes d’administration des bots (help en conversation directe avec le bot pour la liste).

Les ponts partagent un appservice doublepuppet (token mautrix-doublepuppet-as-token) : les messages envoyés depuis le téléphone apparaissent dans Matrix comme venant du vrai compte de l’utilisateur. C’est automatique pour tous les comptes locaux, aucune action par utilisateur.

Deux piles cohabitent, parce qu’aucune ne couvre tous les clients. Elles s’activent séparément et ne se remplacent pas :

WebRTC historiqueMatrixRTC
Optiondarkone.service.turn.enabledarkone.service.matrix.matrixRtc.enable
ComposantscoturnLiveKit (SFU) + service d’autorisation
Appels à deuxouioui
Appels de groupenonoui
Element Classic (Android)ouinon
Element Web / Desktopouioui
Element X (Android, iOS)nonoui

Garder les deux activées : Element Classic ne parle que la première, Element X que la seconde.

  1. Activer la pile MatrixRTC dans la config nix de l’hôte :

    # usr/machines/<host>/default.nix
    darkone.service.matrix = {
    mas.enable = true; # requis par Element X pour se connecter
    matrixRtc.enable = true; # requis par Element X pour appeler
    };
  2. Générer le secret partagé, puis déployer :

    Fenêtre de terminal
    just passwd-livekit
    just apply <host> switch
  3. Vérifier que le serveur annonce bien un transport :

    Fenêtre de terminal
    # doit contenir "org.matrix.msc4143.rtc_foci"
    curl -A Mozilla/5.0 https://domain.tld/.well-known/matrix/client
    # org.matrix.msc4143 doit valoir true
    curl -A Mozilla/5.0 https://matrix.domain.tld/_matrix/client/versions

Aucun sous-domaine ni certificat supplémentaire : le SFU et son service d’autorisation sont servis sur le vhost Matrix, sous /livekit/sfu et /livekit/jwt. Côté client, rien à régler non plus — Element X et Element Web embarquent Element Call et découvrent le transport tout seuls.

Le média ne passe pas par le proxy inverse : les clients joignent l’hôte directement. Ces ports doivent donc être ouverts jusqu’à lui, et redirigés s’il est derrière un NAT :

PortUsage
40000-40100/udpMédia WebRTC, un port par participant
7881/tcpRepli pour les réseaux qui bloquent l’UDP

Par défaut, le serveur fédère avec tous les serveurs Matrix, tout en restant invisible dans les recherches (annuaires non exposés). Pour contrôler la portée, régler darkone.service.matrix.federation dans la config nix de l’hôte :

# usr/machines/<host>/default.nix
darkone.service.matrix.federation = {
enable = true; # false = fédération totalement bloquée
whitelist = [ ]; # vide = ouverte ; sinon liste blanche
};
ObjectifRéglage
Réseau isolé (aucune fédération)enable = false;
Ouvert à tout l’écosystème Matrixenable = true; whitelist = [ ];
Restreint à des serveurs de confianceenable = true; whitelist = [ "ami.org" "matrix.org" ];

Vérifier après déploiement :

  1. Tester la fédération entrante avec federationtester.matrix.org 🡕 sur domain.tld → tous les voyants verts.

  2. En liste blanche, un serveur non listé est refusé (journal Synapse : Federation denied), un domaine listé fonctionne.

Un salon public au sens Matrix se rejoint librement : pas de validation. Pour un salon « joignable mais sur approbation », utiliser la règle d’accès knock (toquer) — réglage par salon, côté client, sans configuration serveur.

  1. Dans Element : salon → Paramètres du salonSécurité & confidentialité.

  2. Accès au salon → « Demander à rejoindre » (knock).

  3. Un utilisateur (local ou distant fédéré) demande l’accès ; un modérateur approuve ou refuse.

ActionCommande
État des servicessystemctl status matrix-synapse mautrix-whatsapp mautrix-signal mautrix-telegram mautrix-meta-messenger
Journaux d’un pontjournalctl -u mautrix-whatsapp -f
Journaux Synapsejournalctl -u matrix-synapse -f
Journaux MASjournalctl -u matrix-authentication-service -f
Journaux MatrixRTCjournalctl -u livekit -u lk-jwt-service -f

dnf-mas enveloppe l’outil officiel mas-cli sur l’hôte Matrix : il reconstitue la configuration et les secrets du service, et fonctionne donc service arrêté comme démarré. Toujours en sudo.

ActionCommande
Créer un jeton d’inscriptionsudo dnf-mas manage issue-user-registration-token
Promouvoir un administrateursudo dnf-mas manage promote-admin alice
Lister les administrateurssudo dnf-mas manage list-admin-users
Créer un compte (bot, service)sudo dnf-mas manage register-user
Changer un mot de passesudo dnf-mas manage set-password alice
Fermer les sessions d’un comptesudo dnf-mas manage kill-sessions alice
Verrouiller un comptesudo dnf-mas manage lock-user alice
Diagnostic completsudo dnf-mas doctor

Après une migration, les sessions déjà ouvertes continuent de fonctionner : une connexion cassée ne se voit donc qu’au premier login neuf. Suivre MAS pendant une tentative :

Fenêtre de terminal
journalctl -u matrix-authentication-service -f
MessageCauseCorrectif
wrong signature alg sur /upstream/callback/…MAS attend un id_token RS256, Kanidm signe en ES256id_token_signed_response_alg du fournisseur amont : réglé sur ES256 par le module
client registration denied by the policy: invalid redirect_uriLe client_uri annoncé par le client ne correspond pas à son schéma de redirectionNe pas forcer oidc_metadata dans la config Element ; ses valeurs par défaut passent
M_UNRECOGNIZED sur /login/sso/redirectLe chemin part vers Synapse au lieu de MASVérifier le matcher @masCompat du vhost Caddy
Page User exists : « returned “bob” as username, which is not linked »Un compte ami occupe déjà l’identifiant de l’utilisateur déclaréFusionner ou renommer (cf. collision d’identifiant)
« Le code QR n’est pas pris en charge » côté clientLe canal de rendez-vous MSC4108 est absentexperimental_features.msc4108_enabled : activé par le module avec MAS
« L’appel n’est pas pris en charge », MISSING_MATRIX_RTC_TRANSPORTAucun backend MatrixRTC annoncéActiver matrixRtc.enable (cf. Appels audio et vidéo)
Appel qui sonne mais reste muet, ou coupe au bout de quelques secondesLes ports média n’atteignent pas l’hôteOuvrir 40000-40100/udp et 7881/tcp jusqu’à lui

Contrôles rapides, sans se connecter :

Fenêtre de terminal
# org.matrix.msc4108 doit valoir true
curl -A Mozilla/5.0 https://matrix.domain.tld/_matrix/client/versions
# doit répondre 201 et une url publique
curl -A Mozilla/5.0 -X POST --data probe \
https://matrix.domain.tld/_matrix/client/unstable/org.matrix.msc4108/rendezvous

Un bot qui ne répond pas à help alors que son service tourne tombe presque toujours dans l’un des deux cas : ses tokens ne correspondent plus à la registration chargée par Synapse, ou il reçoit les messages mais ne peut pas les déchiffrer. Diagnostic dans l’ordre :

  1. Le service tourne-t-il vraiment ?

    Fenêtre de terminal
    systemctl status mautrix-<pont>
    journalctl -u mautrix-<pont> -n 50

    S’il redémarre en boucle avec « The as_token was not accepted », l’enregistrement en mémoire de Synapse ne correspond plus aux tokens du pont (typique après un reset ou une migration, voir l’encart plus haut) : systemctl restart matrix-synapse, puis redémarrer le pont.

  2. Le message arrive-t-il au pont ? Suivre Synapse pendant l’envoi d’un help au bot :

    Fenêtre de terminal
    journalctl -u matrix-synapse -f | grep transactions

    Une ligne PUT .../_matrix/app/v1/transactions/... 200 doit apparaître. Si rien ne part, l’appservice n’est pas chargé : vérifier la présence de l’enregistrement dans /var/lib/mautrix-<pont>/ et redémarrer Synapse.

  3. Le pont peut-il le déchiffrer ? Si la transaction passe (200) mais que le bot reste muet, suivre ses logs pendant un nouvel envoi :

    Fenêtre de terminal
    journalctl -u mautrix-<pont> -f

    Chercher decrypt, session, verification, dropping. Un échec de déchiffrement signifie que la clé de session du salon n’a pas été partagée avec le device actuel du bot (cas classique après un reset du pont : son identité e2ee a changé).

  4. Recréer la conversation. Quitter le message direct avec le bot et en démarrer un nouveau : le client établit une session de chiffrement neuve et partage la clé avec le device actuel du bot. Vérifier au passage que sa propre session Element est vérifiée.

  5. Dernier recours : réinitialiser l’état du pont (encart ci-dessous), puis redémarrer Synapse pour recharger l’enregistrement.