Dokumentation aus dem Wiki uebernommen und um die Dienste auf dem Server
erweitert. Alle Anleitungen sind so geschrieben, dass sich der jeweilige
Dienst allein daraus neu aufsetzen laesst.
docs/ Dienste im Heimnetz (aus dem Wiki, anonymisiert)
services/ Dienste auf dem Server mit Konfigurationsvorlagen
- matrix-synapse: Homeserver, Postgres, Well-Known-Delegation
- coturn: TURN-Relay fuer Anrufe
- element-web: Web-Client
- wikijs, gitea, nginx
Durchgehend anonymisiert: echte Domain durch example.com ersetzt, IP-Adressen
und E-Mail-Adressen durch Platzhalter. Konfigurationsdateien liegen nur als
.example mit Platzhaltern statt echter Secrets vor.
Die Anleitungen halten die Stolpersteine fest, die beim Aufbau tatsaechlich
aufgetreten sind, unter anderem:
- Synapse verlangt LC_COLLATE=C, sonst startet es nicht
- server_name ist nachtraeglich nicht aenderbar -> Delegation noetig
- register_new_matrix_user liest conf.d nicht, Secret muss per -k kommen
- coturn braucht external-ip, sonst kommen keine Medien durch
- Gruppenmitgliedschaft fuer den Zertifikatszugriff wirkt erst beim Neustart
- nginx vererbt add_header nicht in location-Bloecke mit eigenen Direktiven
241 lines
7.3 KiB
Markdown
241 lines
7.3 KiB
Markdown
# TURN-Server (coturn)
|
||
|
||
TURN-Relay für Sprach- und Videoanrufe in Matrix/Element.
|
||
|
||
| Punkt | Wert |
|
||
|---|---|
|
||
| Software | `coturn` |
|
||
| Öffentlich | `turn.example.com` |
|
||
| Ports | 3478 TCP/UDP (STUN/TURN), 5349 TCP/UDP (TURNS), 49152–65535 UDP (Relay) |
|
||
| Authentifizierung | Zeitbegrenzt über gemeinsames Secret |
|
||
|
||
## Wozu überhaupt?
|
||
|
||
WebRTC verbindet zwei Teilnehmer möglichst direkt. Sitzen beide hinter NAT
|
||
oder einer restriktiven Firewall, klappt das nicht – der Anruf klingelt, aber
|
||
es kommt kein Ton an. Der TURN-Server leitet den Medienstrom dann weiter.
|
||
|
||
Er ist damit ein **Relay für fremden Datenverkehr**. Ohne Absicherung ist das
|
||
ein offener Proxy, mit dem sich das interne Netz des Servers erreichen lässt.
|
||
Der Abschnitt „Absicherung“ unten ist deshalb nicht optional.
|
||
|
||
## Kein festes Passwort
|
||
|
||
coturn und Synapse teilen sich ein Secret (`static-auth-secret` bzw.
|
||
`turn_shared_secret`). Fragt ein Client Zugangsdaten an, erzeugt Synapse daraus
|
||
per HMAC ein kurzlebiges Paar:
|
||
|
||
- Benutzername: `<ablaufzeitpunkt>:<benutzer-id>`
|
||
- Passwort: HMAC-SHA1 über den Benutzernamen mit dem Secret, Base64-kodiert
|
||
|
||
coturn rechnet dasselbe nach. Es gibt also **keine angelegten Benutzer** – beide
|
||
Seiten müssen nur exakt dasselbe Secret kennen.
|
||
|
||
## 1. Installation
|
||
|
||
```bash
|
||
sudo apt-get install -y coturn
|
||
|
||
# Das Debian-Paket startet ohne diesen Schalter nicht.
|
||
sudo sed -i 's/^#\?TURNSERVER_ENABLED=.*/TURNSERVER_ENABLED=1/' /etc/default/coturn
|
||
```
|
||
|
||
## 2. Secret erzeugen
|
||
|
||
```bash
|
||
openssl rand -hex 32
|
||
```
|
||
|
||
Derselbe Wert muss in `/etc/turnserver.conf` (`static-auth-secret`) **und** in
|
||
der Synapse-Konfiguration (`turn_shared_secret`) stehen.
|
||
|
||
## 3. Konfiguration
|
||
|
||
`turnserver.conf.example` nach `/etc/turnserver.conf` kopieren und anpassen:
|
||
|
||
```bash
|
||
sudo cp turnserver.conf.example /etc/turnserver.conf
|
||
sudo chown root:turnserver /etc/turnserver.conf
|
||
sudo chmod 640 /etc/turnserver.conf
|
||
```
|
||
|
||
Zwingend zu ersetzen:
|
||
|
||
- `static-auth-secret` – der oben erzeugte Wert
|
||
- `external-ip` – die **öffentliche** IP des Servers
|
||
- `realm` / `server-name` – die eigene Domain
|
||
|
||
### external-ip nicht vergessen
|
||
|
||
Ohne `external-ip` kündigt coturn hinter NAT seine private Adresse als
|
||
Relay-Kandidat an. Der Anruf baut sich dann scheinbar auf, aber es fließen keine
|
||
Medien. Zu ermitteln mit:
|
||
|
||
```bash
|
||
curl -s -4 ifconfig.me
|
||
```
|
||
|
||
## 4. Zertifikat lesbar machen
|
||
|
||
`turns:` auf Port 5349 braucht Zertifikat und Key. coturn läuft als Benutzer
|
||
`turnserver` und kommt an Let's-Encrypt-Material standardmäßig nicht heran.
|
||
|
||
Statt die Rechte global aufzuweichen, eine eigene Gruppe verwenden:
|
||
|
||
```bash
|
||
sudo groupadd -f ssl-cert
|
||
sudo usermod -aG ssl-cert turnserver
|
||
|
||
sudo chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive
|
||
sudo chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive
|
||
sudo find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \;
|
||
```
|
||
|
||
> Gruppenmitgliedschaften greifen erst beim **Start eines Prozesses**. Läuft
|
||
> coturn schon, ist danach ein `systemctl restart coturn` nötig – ein `reload`
|
||
> reicht nicht.
|
||
|
||
Prüfen, ob es wirklich klappt:
|
||
|
||
```bash
|
||
sudo -u turnserver head -c 30 /etc/letsencrypt/live/matrix.example.com/privkey.pem
|
||
```
|
||
|
||
### Erneuerung berücksichtigen
|
||
|
||
coturn liest das Zertifikat nur beim Start. Nach einer Erneuerung würde es mit
|
||
dem alten weiterarbeiten, bis es irgendwann abläuft. Deshalb ein Deploy-Hook
|
||
nach `/etc/letsencrypt/renewal-hooks/deploy/10-reload-services.sh`:
|
||
|
||
```sh
|
||
#!/bin/sh
|
||
set -e
|
||
chgrp -R ssl-cert /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||
chmod -R g+rX /etc/letsencrypt/live /etc/letsencrypt/archive || true
|
||
find /etc/letsencrypt/archive -name 'privkey*.pem' -exec chmod 640 {} \; || true
|
||
systemctl reload nginx || true
|
||
systemctl restart coturn || true
|
||
```
|
||
|
||
Ausführbar machen: `sudo chmod +x …/10-reload-services.sh`
|
||
|
||
## 5. Absicherung
|
||
|
||
Die `denied-peer-ip`-Zeilen in der Beispielkonfiguration sperren alle privaten
|
||
und reservierten Netze. **Ohne sie kann jeder, der Zugangsdaten bekommt, über
|
||
den TURN-Server auf `localhost` und das interne Netz zugreifen** – inklusive
|
||
Diensten, die nur lokal lauschen.
|
||
|
||
Weitere gesetzte Schalter:
|
||
|
||
| Schalter | Wirkung |
|
||
|---|---|
|
||
| `no-tcp-relay` | Kein TCP-Relay (RFC 6062). WebRTC braucht es nicht. |
|
||
| `no-multicast-peers` | Kein Relay an Multicast-Adressen |
|
||
| `no-cli` | Telnet-Verwaltungsschnittstelle aus |
|
||
| `no-tlsv1`, `no-tlsv1_1`, `no-sslv3` | Veraltete Protokolle aus |
|
||
| `user-quota`, `total-quota` | Begrenzt den Missbrauch |
|
||
|
||
## 6. Start und Prüfung
|
||
|
||
```bash
|
||
sudo systemctl enable --now coturn
|
||
sudo systemctl status coturn
|
||
```
|
||
|
||
Lauschen sollte er auf beiden Ports:
|
||
|
||
```bash
|
||
ss -tuln | grep -E '3478|5349'
|
||
```
|
||
|
||
Im Log darf **nicht** stehen „cannot start TLS and DTLS listeners“. Das heißt,
|
||
dass coturn das Zertifikat nicht lesen kann:
|
||
|
||
```bash
|
||
sudo journalctl -u coturn | grep -iE 'certificate|private key|TLS'
|
||
```
|
||
|
||
### Relay tatsächlich testen
|
||
|
||
Ein Blick auf offene Ports genügt nicht – erst eine echte Allocation beweist,
|
||
dass Authentifizierung und Relay funktionieren. Zugangsdaten selbst berechnen:
|
||
|
||
```bash
|
||
python3 - 'HIER-DAS-TURN-SECRET' <<'PY'
|
||
import hashlib, hmac, base64, time, sys
|
||
secret = sys.argv[1]
|
||
user = f"{int(time.time()) + 3600}:testuser"
|
||
pw = base64.b64encode(
|
||
hmac.new(secret.encode(), user.encode(), hashlib.sha1).digest()).decode()
|
||
print(user); print(pw)
|
||
PY
|
||
```
|
||
|
||
Damit eine Allocation anfordern:
|
||
|
||
```bash
|
||
turnutils_uclient -u '<benutzername>' -w '<passwort>' -y -n 2 turn.example.com -p 3478
|
||
```
|
||
|
||
Erfolg sieht so aus – gleich viele gesendete und empfangene Nachrichten, keine
|
||
verlorenen Pakete:
|
||
|
||
```
|
||
start_mclient: tot_send_msgs=8, tot_recv_msgs=8
|
||
Total lost packets 0 (0.000000%)
|
||
```
|
||
|
||
Gegenprobe mit falschem Passwort muss `Cannot complete Allocation` liefern.
|
||
|
||
> `turnutils_uclient -T …` schlägt mit `error 442` fehl. Das ist **kein**
|
||
> Fehler: `-T` fordert ein TCP-Relay an, das per `no-tcp-relay` gesperrt ist.
|
||
|
||
TLS-Listener separat prüfen:
|
||
|
||
```bash
|
||
openssl s_client -connect turn.example.com:5349 -servername turn.example.com </dev/null
|
||
```
|
||
|
||
Der Domainname muss im Zertifikat stehen:
|
||
|
||
```bash
|
||
openssl s_client -connect turn.example.com:5349 </dev/null 2>/dev/null \
|
||
| openssl x509 -noout -ext subjectAltName
|
||
```
|
||
|
||
## 7. Verbindung zu Synapse
|
||
|
||
In `/etc/matrix-synapse/conf.d/homelab.yaml` stehen `turn_uris` und
|
||
`turn_shared_secret`. Nach dem Neustart lässt sich prüfen, was ein Client
|
||
wirklich ausgeliefert bekommt:
|
||
|
||
```bash
|
||
curl -s -H "Authorization: Bearer <access-token>" \
|
||
https://matrix.example.com/_matrix/client/v3/voip/turnServer
|
||
```
|
||
|
||
Die zurückgegebenen Zugangsdaten sollten mit `turnutils_uclient` funktionieren –
|
||
das ist der eigentliche Ende-zu-Ende-Nachweis.
|
||
|
||
## Firewall
|
||
|
||
Freizugeben sind:
|
||
|
||
- 3478 TCP **und** UDP
|
||
- 5349 TCP **und** UDP
|
||
- 49152–65535 UDP (Relay-Bereich)
|
||
|
||
Der Relay-Bereich wird oft vergessen. Ohne ihn kommt die Allocation zustande,
|
||
aber es fließen keine Medien.
|
||
|
||
## Stolpersteine
|
||
|
||
| Symptom | Ursache |
|
||
|---|---|
|
||
| Port 5349 fehlt, Log meldet „certificate file is not set properly“ | `turnserver` darf den Key nicht lesen, oder Dienst lief schon vor der Gruppenänderung |
|
||
| Anruf klingelt, kein Ton/Bild | `external-ip` fehlt oder Relay-Ports gesperrt |
|
||
| `error 442` im Test | `-T` verwendet; TCP-Relay ist absichtlich aus |
|
||
| Authentifizierung schlägt fehl | Secret in coturn und Synapse verschieden |
|
||
| Nach Zertifikatserneuerung bricht TURNS ab | Deploy-Hook fehlt |
|