Matrix mit OPNsense und TrueNAS / FreeBSD

Im folgenden schauen wir uns eine Matrix Installation auf einem eigenen Server an. Für dieses Setup nutzen wir zwei Systeme, die in jeden ambitionierten Haushalt gehören:

  • TrueNAS als Server. Da TrueNAS auf FreeBSD basiert, bieten sich die FreeBSD typischen Jail als perfekte Grundlage an, um eine gesicherte Umgebung für Matrix zu schaffen. Wir installieren hier exemplarisch alle benötigten Dienste in ein Jail (Synapse, Element-web, NGINX und PostgreSQL). Die TrueNAS typischen Funktionen wie ZFS Snapshots schaffen eine Grundlage, die sehr einfach zu sichern ist.
  • OPNsense als Firewall davor. Durch zwei Erweiterungen übernimmt die OPNsense aber auch die Erstellung der Zertifikate und nimmt mit HAProxy die HTTPS Anfragen entgegen, um diese dann an das interne Jail zu leiten. Das vereinfacht insbesondere den Aufbau im Jail enorm. Die Zertifikate werden bei Lets Encrypt automatisch signiert und dem HAProxy zur Verfügung gestellt.

Anstelle von TrueNAS kann natürlich auch ein normaler FreeBSD Server genutzt werden, dann sind die TrueNAS spezifischen Teile zu ignorieren. Als Jail Verwaltung bietet sich unter FreeBSD BastilleBSD an, was eher in diesem Artikel als Grundlage dient.

Zusammengefasst sind folgende Schritte nötig, um das Ziel dieser Anleitung zu erreichen:

In jedem Falle wird dringend empfohlen, alle Dienste in eigene Jails einzusperren, auch wenn wir hier exemplarisch nur eines nutzen.

Fehler oder Verbesserungsvorschläge bitte hier hin.

Zeitaufwand: ca 60 Minuten.
Letzte Aktualisierung:

  • 19.11.2022: Erweiterung um Diagramm und kleinere Anpassungen.
  • 12.09.2022: Initiales Dokument.

Voraussetzungen für dieses Tutorial

  • OPNsense Firewall
    • Wichtig! Die Adminoberfläche wurde von Port 443 auf 4433 umgelegt (System: Settings: Administration: TCP port).
    • Mit den installierten Plugins os-haproxy und os-acme-client (System: Firmware: Plugins).
    • Der Zugriff auf TCP Port 443 und 8448 ist an der WAN Schnittstelle erlaubt/freigegeben (Firewall: Rules: WAN)
    • Intern sollte der MATRIXHOSTNAME im Unbound Dienst auf die IP der OPNsense gesetzt werden, damit auch interne Aufrufe an den HAProxy geleitet werden (Services: Unbound DNS: Overrides)
  • TrueNAS Core oder reiner FreeBSD Server
    • Ein Jail (z.B. mit dem Namen MATRIX) ist eingerichtet und gestartet.
      • IP Adresse des MATRIX Jails ist bekannt, im DHCP reserviert oder statisch vergeben (Services: DHCPv4: [LAN]: DHCP Static Mappings).
  • Eigenes Webhosting
    • Zugriff auf die DNS Verwaltung der Domain.
    • Zugriff auf die Datei Verwaltung der Domain (z.B. per FTP oder SSH).
    • die eigene Domain die bereits per htttps erreichbar ist (z.B. https://DOMAIN.DE).
    • eine neue Subdomain mit fester IP-Adresse als A Record oder alternativ ein Hostname im CNAME im DNS hinterlegen (z.B. MATRIX.DOMAIN.DE).
    • das Anlegen von zwei Ordnerns und Dateien auf der Domain mit den Inhalten:
      • "https://DOMAIN.DE/.well-known/matrix/server = {"m.server": "MATRIX.DOMAIN.DE:8448"}';
      • "https://DOMAIN.DE/.well-known/matrix/client" = {"m.homeserver":{"base_url": "https://MATRIX.DOMAIN.DE"}}';

Begrifflichkeiten

  • USERNAME = SSH Benutzeraccount (root Login ist nicht direkt möglich) mit SU Rechten
  • MATRIXIP = IP Adresse des lokalen Vaultwarden Servers
  • MATRIXHOSTNAME = Hostname es des Matrix Servers (z.B. MATRIX.DOMAIN.DE)
  • MATRIXDOMAIN = Domain Name des des Matrix Servers (z.B. DOMAIN.DE)
  • ACCOUNTNAME = Name des Let's Encrypt Accounts
  • EMAILADRRESSE = Email Adresse des Let's Encrypt Accounts
  • DATENBANKNAME = Name der Datenbank im PostgreSQL Server
  • DATENBANKBENUTZER = Name des Vaultwarden Benutzers im PostgreSQL Server
  • DATENBANKKENNWORT = Kennwort des Vaultwarden Benutzers im PostgreSQL Server

Diagramm

Damit sieht das Setup so aus:

                                                                                     ┌─────────────────────────────┐
                                                                                     │ TrueNAS                     │
                                                                                     │   192.168.178.100           │
                           ┌────────────────────────────────┐                        │ ┌─────────────────────────┐ │
                           │ OPNsense                       │                        │ │ Matrix                  │ │
                           │   192.168.178.1                │                        │ │   192.168.178.101       │ │
                           │                                │                        │ │                         │ │
WAN: 0.0.0.0:80   ─────────┼─► acme.sh:80                   │                        │ │   postgresql13-server   │ │
                           │                              ┐ │                        │ │     ▲                   │ │
WAN: 0.0.0.0:8448 ┌────────┼─► HAProxy:8448               │ │                        │ │     │                   │ │
WAN: 0.0.0.0:443  ┘        │      "   :443/_matrix        ├─┼─ 192.168.178.101:8008 ─┼─┼─► py39-matrix-synapse   │ │
                           │      "   :443/_synapse/client│ │                        │ │                         │ │
                           │                              ┘ │                        │ │                         │ │
                           │      "   :443 ─────────────────┼─ 192.168.178.101:80   ─┼─┼─► nginx                 │ │
                           │                                │                        │ │     │                   │ │
                           │                                │                        │ │     ▼                   │ │
                           │                                │                        │ │   element-web           │ │
                           │                                │                        │ │                         │ │
                           └────────────────────────────────┘                        │ └─────────────────────────┘ │
                                                                                     └─────────────────────────────┘

Matrix Jail

Pakete installieren

Login per SSH in das MATRIX Jail: ssh USERNAME@MATRIXIP oder ssh USERNAME@MATRIXHOSTNAME um root Rechte zu erlangen: su.

Zuvor passen wir die Paketquellen auf die "latest" an, damit auch wirklich die neueste Pakete bezogen werden:

mkdir -p /usr/local/etc/pkg/repos 
cp /etc/pkg/FreeBSD.conf /usr/local/etc/pkg/repos/FreeBSD.conf
sed -i '' 's/quarterly/latest/' /usr/local/etc/pkg/repos/FreeBSD.conf

Paketquelle aktualisieren mit pkg update und dann die benötigte Pakete installieren:
postgresql13-server py39-matrix-synapse nginx element-web

Dienste aktivieren und beim Start des Jails automatisch starten:
sysrc postgresql_enable=YES
sysrc synapse_enable=YES
sysrc nginx_enable=YES

PostgreSQL

Datenbank initialisieren: /usr/local/etc/rc.d/postgresql initdb
PostgreSQL Dienst starten: service postgresql start

Folgende Daten ggf. ändern und merken:

  • DATENBANKNAME: synapse
  • DATENBANKBENUTZER: synapse_user
  • DATENBANKKENNWORT: PASSWORT (Bitte was eigenes erstellen und merken!)

Im folgenden Schritt legen wir den Datenbankbenutzer und die Datenbank an.
Das wird als Postgres Benutzer ausgeführt: su - postgres

createuser --pwprompt DATENBANKBENUTZER 
createdb --encoding=UTF8 --locale=C --template=template0 --owner=DATENBANKBENUTZER DATENBANKNAME
exit

Synapse

Synapse ist in der Email Analogie der IMAP und SMTP Server, der alle Nachrichten speichert und an andere Server übermittelt oder entgegennimmt.

MATRIXDOMAIN ist der Domainname. Hier wird die Domain angegeben, also z.B. "DOMAIN.DE" und nicht "MATRIX.DOMAIN.DE"! Das wird der sichtbare Teil der Benutzeradresse auf dem Server, z.B. @BENUTZERNAME:DOMAIN.DE

Konfigration anpassen

mkdir -p /usr/home/synapse/media_store
mkdir /var/run/matrix-synapse/
chown -R synapse:synapse /usr/home/synapse/
chown -R synapse:synapse /var/run/matrix-synapse/
/usr/local/bin/python3.9 -B -m synapse.app.homeserver -c /usr/local/etc/matrix-synapse/homeserver.yaml --generate-config -H MATRIXNAME --report-stats no

Nun konfigurieren wir die Synapse mit ee /usr/local/etc/matrix-synapse/homeserver.yaml die bereits einige Inhalte hat.
Die bereits vorhandenen Inhalte sind entsprechend anzupassen.

server_name: MATRIXDOMAIN
pid_file: /var/run/matrix-synapse/homeserver.pid
log_config: "/usr/local/etc/matrix-synapse/log.config"
media_store_path: /usr/home/synapse/media_store

listeners:
  - port: 8008
    tls: false
    type: http
    x_forwarded: true
    bind_addresses: ['0.0.0.0']
    resources:
      - names: [client, federation]
        compress: false

database:
 name: psycopg2
 txn_limit: 10000
 args:
  user: DATENBANKBENUTZER
  password: DATENBANKKENNWORT
  database: DATENBANKNAME
  host: 127.0.0.1
  port: 5432
  cp_min: 5
  cp_max: 10

report_stats: false

signing_key_path: "/usr/local/etc/matrix-synapse/MATRIXDOMAIN.signing.key"
trusted_key_servers:
  - server_name: "matrix.org"

Synapse starten und testen

... und den Synapse Dienst starten: service synapse start

Synapse ist dann unter http://MATRIXIP:8008 bereits intern erreichbar. Es wird eine einfache Statusseite angezeigt.

Admin Benutzer erstellen

Wenn alles gut gegangen ist, dann kann der erste Admin-Benutzer erstellt werden:
register_new_matrix_user -c /usr/local/etc/matrix-synapse/homeserver.yaml http://127.0.0.1:8008
Hier vergeben wir einen Benutzernamen und ein Passwort; beides notieren wir uns selbstverständlich wieder!

NGINX und Element-Web

Element ist in der Email Analogie der Email Client, mit dem Nachrichten gelesen und erstellt werden können.
Element ist eine Web Anwendung, die wie jede andere Webseite mittels eines Webserver ausgeliefert wird. Wir benötigen also einen Webserver, der uns diese Aufgabe abnimmt.

Konfigration anpassen

Das macht NGINX. Da wir den weiter oben bereits installiert hatten, editieren wir die Konfigurationsdatei mit
ee /usr/local/etc/nginx/nginx.conf und ersetzen den existierenden Inhalt mit:

user www;
worker_processes 1;
events {
 worker_connections 1024;
}
http {
 include mime.types;
 default_type application/octet-stream;
 sendfile on;
 keepalive_timeout  65;
 gzip on;
 server {
  listen 80 default_server;
  #listen [::]:80;
   location / {
   root  /usr/local/www/element;
   index index.html index.htm;
  }
  error_page 500 502 503 504 /50x.html;
  location = /50x.html {
   root /usr/local/www/nginx-dist;
  }
 }
}

Nun passen wir die wir die Element Konfiguration an, die bereits einige Inhalte hat. Die bereits vorhandenen Inhalte sind entsprechend anzupassen.

cp /usr/local/www/element/config.sample.json /usr/local/www/element/config.json
ee /usr/local/www/element/config.json
    "base_url": "https://MATRIXHOSTNAME",
    "server_name": "MATRIXDOMAIN"
    "disable_custom_urls": true,
    "disable_guests": true,
    "brand": "MATRIXDOMAIN Element",
    "room_directory": {
        "servers": [
            "MATRIXDOMAIN",
            "matrix.org"

NGINX/Element starten und testen

... und letztendlich den NGINX Dienst starten: service nginx start

Element-Web ist dann unter http://MATRIXIP bereits intern erreichbar. Es wird ein Anmeldedialog angezeigt.

Lets Encrypt Zertifikate

OPNsense ist mit dem ACME CLient Plugin (os-acme-client) in der Lage Let's Encrypt Zertifikate zu erstellen und auch automatisch zu erneuern. Der riesen Vorteil ist, das wir eine zentrale Zertifikatsverwaltung haben, nicht mühselig auf den internen Zielsystemen jeweils eine eigene Verwaltung benötigen und keine NAT oder anderen Firewall Einstellungen vornehmen müssen.

Services: ACME Client

Settings

Um ein Zertifikat für die eigene Domain zu erhalten sind nur wenige Schritte notwendig. Hierfür wechseln wir in den Bereich "Services / ACME Client / Settings" der OPNsense und setzen folgende Haken:

Enable Plugin: JA  
Auto Renewal: JA  

Hierdurch wird das Plugin aktiviert und die automatische Erneuerung der Zertifikate festgelegt.

7

Accounts:

Danach wechseln wir in den Bereich "Services / ACME Client / Accounts" und erstellen mit dem "+"-Zeichen einen neuen Account.

Enabled: JA  
Name: ACCOUNTNAME  
E-Mail Address: EMAILADRRESSE  
ACME CA: Let's Encrypt [default]  

Der Account muss einmalig registiert werden. Ganz Rechts gibt es dazu den Knopf "Register Account". Bei Erfolg wird der Account dann bei Status mit "OK (registered)" gekennzeichnet.

Challenge Types

Im Anschluss legen wir fest, wie die Überprüfung (Challenge) stattfinden soll. Dies geschieht im Bereich "Services / ACME Client / Challenge Types". Auch hier erzeugen wir durch Betätigung des "+"-Zeichens eine neue Challenge mit den folgenden Einstellungen:

Enabled: JA  
Name: VAULTHOSTNAME  
Challenge Type: HTTP-01  
HTTP Service: OPNsense Web Service (automatic port forward)  
Interface: WAN  

Certificates

Im letzten Schritt geht es an die Erstellung des Zertifikates. Hierfür wechseln wir nach "Services / ACME Client / Certificates" und erstellen erneut einen neuen Eintrag durch das "+"-Zeichen. Im Feld "ACME Account" wählen wir den soeben erstellen Account aus, in dem Feld "Challange Type" die erstellte Challange. Die übrigen Einstellungen wählen wir wie folgt:

Enabled: JA  
Common Name: MATRIXHOSTNAME
ACME Account: ACCOUNTNAME (aus Accounts)  
Challenge Type: VAULTHOSTNAME (aus Challenge Types)  
Key Length: ec-384  

Damit sind alle Einstellungen vorgenommen und das Zertifikat kann ganz Rechts mit dem Knopf "Issue or renew certificate" erstellt werden. Bei Erfolg (das dauert ein paar Minuten) wird das Zertifikat bei "Last ACME Status" mit "OK" gekennzeichnet.

11

Fertig! Die Erneuerung des Zertifikates erfolgt nun gemäß den Einstellungen alle 90 Tage vollautomatisch.

HAProxy

HAProxy nimmt von außen die Aufrufe für den Port 443 und 8448 entgegen, verschlüsselt die Verbindung und leitet diese dann an den interen Matrix Server weiter. Das schöne ist, das auch hier die Verwaltung wieder zentral stattfindet, die Zertifikate ohne Aufwand von Let's Encrypt genutzt und mehrere Dienste gleichzeitig bereitgestellt werden können. Dieser übernimmt vereinfacht gesprochen die Weiterleitung der eingehenden Verbindungen (z.B. Aufruf https://matrix.domain.de) an das Jail bzw. dem Element oder Synapse Service.

Aufgabenstellung des HAProxy ist hier:

  • eingehende Verbindungen von MATRIX.DOMAIN.DE:8448 an den Synapse Dienst (MATRIXIP:8008) weiterzuleiten.
  • eingehende Verbindungen von MATRIX.DOMAIN.DE:443 an den nginx-Webserver (MATRIXIP:80) weiterzuleiten,
    • es sei denn der eingehende Aufruf beginnt mit dem Pfad "/_matrix" oder "/_synapse/client",
      dann erfolgt die Weiterleitung an MATRIXIP:8008

Um nun eine Weiterleitung der MATRIXHOSTNAME auf den interen Matris Server zu erhalten, sind nur wenige Schritte unter "Services - HAProxy" notwendig:

Services: HAProxy

Settings

Enable HAProxy: YES

Real Servers:

Im ersten Schritt definieren wir unter "Services / HAProxy / Settings" in der Lasche "Real Servers" zwei Server. Dies geschieht wieder jeweils durch Betätigung der "+"-Schaltfläche. Diese Einträge verweisen auf die beiden unterschiedlichen Ports unsere Matrix Installation.

Name: element_MATRIXHOSTNAME  
Description: IP Adresse des lokalen Matrix Servers auf Port 80
Type: static  
FQDN or IP: MATRIXIP  
Port: 80" 

Name: synapse_MATRIXHOSTNAME  
Description: IP Adresse des lokalen Matrix Servers auf Port 8008
Type: static  
FQDN or IP: MATRIXIP  
Port: 8008  

Danach sollten unter den "Real Servers" zwei Einträge zu sehen sein. Wichtig, auch hier wieder nach dem Anlegen der beiden Server die "Apply"-Schaltfläche betätigen!

Virtual Services: Backend Pools

Im zweiten Schritt erstellen wir die "Backend Pools", dies erledigen wir unter "Services / HAProxy / Settings" in der Lasche "Virtual Services" den Eintrag "Backend Pools". Hier erstellen wir ebenfalls durch die "+"-Schaltfläche zwei Backend Pools (einen für Element und einen für Synapse). Einstellungen des Element-Pools:

Name: poolelement_MATRIXHOSTNAME  
Description: Pool an lokalen Element Servers (hier gibts nur einen)  
Servers: element_MATRIXHOSTNAME 

Unter "Servers" wählen wir den zuvor erstellten Server "element_MATRIXHOSTNAME" aus!

Einstellungen des Synapse-Pools:

Name: poolsynapse_MATRIXHOSTNAME  
Description: Pool an lokalen Element Servers (hier gibts nur einen)  
Servers: synapse_MATRIXHOSTNAME  

Unter "Servers" wählen wir den zuvor erstellten Server "synapse_MATRIXHOSTNAME" aus! Wichtig, auch hier wieder nach dem Anlegen der beiden Backend Pools die "Apply"-Schaltfläche betätigen!

Rules & Checks: Conditions

Nun hinterlegen wir das Regelwerk, welche bestimmen, welche eingehenden Verbindungen an welchen Service weitergeleitet werden. Hierfür wählen wir unter "Services / HAProxy / Settings" in der Lasche "Rules & Checks" den Eintrag "Conditions" aus und erstellen zwei Bedingungen:

Name: synapse-path  
Description: Prüfung ob /_matrix aufgerufen wurde  
Condition type: Path starts with  
Path Prefix: /_matrix

Name: matrix-path  
Description: Prüfung ob /_synapse/client aufgerufen wurde  
Condition type: Path starts with  
Path Prefix: /_synapse/client  

Rules & Checks: Rules

Diese Bedingungen werden durch einer Regel überprüft. Regeln erzeugen wir wie folgt unter "Services / HAProxy / Settings" in der Lasche "Rules & Checks" im Eintrag "Rules" durch die Schaltfläche "+":

Name: synapse  
Description: Zugriffe auf synapse leiten  
Select conditions: synapse-path, matrix-path
Logical operator for conditions: OR
Execute function: Use specific Backend Pool 
Use backend pool: poolsynapse_MATRIXHOSTNAME

Wichtig, auch hier wieder nach dem Anlegen die "Apply"-Schaltfläche betätigen!

Virtual Services: Public Services

Abschließend erstellen wir zwei "Public Services" unter "Services / HAProxy / Settings" in der Lasche "Virtual Services" den Eintrag "Public Services"

Name: element_MATRIXHOSTNAME  
Description: Server auf Port 443 der alle externen Anfragen entgegenimmt  
Listen Addresses: 0.0.0.0:443
Default Backend Pool: none  
Certificates: MATRIXHOSTNAME (ACME Client)  
Default certificate: MATRIXHOSTNAME (ACME Client Zertifikat auswählen)  
Cipher List: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384  
Cipher Suites: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256  
Enable HSTS: YES  
HSTS includeSubDomains: YES  
Select Rules: synapse  

Name: synapse_MATRIXHOSTNAME  
Description: Server auf Port 8448 der alle externen Anfragen für Synapse entgegenimmt  
Listen Addresses: 0.0.0.0:8448
Default Backend Pool: poolsynapse_MATRIXHOSTNAME  
Certificates: MATRIXHOSTNAME (ACME Client)  
Default certificate: MATRIXHOSTNAME (ACME Client Zertifikat auswählen)  
Cipher List: ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384  
Cipher Suites: TLS_AES_128_GCM_SHA256:TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256  
Enable HSTS: YES  
HSTS includeSubDomains: YES  
Select Rules: 

Zum Schluss noch ein "Apply" und fertig ist die Installation!

Damit ist nun von außen der Zugriff auf den Matrix Server eingerichtet und erlaubt.

Mit dem Matrix Federation Tester, kann das gesamte Setup überprüft werden.
Als Servername sollte MATRIXDOMAIN genutzt werden, nicht MATRIXHOSTNAME.