feat(matrix): 4 synapse quick wins applied + 6 follow-up issues
Server-side homeserver.yaml on organic-machine VPS: - encryption_enabled_by_default_for_room_type: invite -> all - presence.enabled: false (block EDU metadata leak) - url_preview_enabled: false (block SSRF + IP leak) - msc4108 rendezvous endpoint uncommented (QR login) Synapse restarted, /versions shows e2ee_forced.* + msc4108 unstable features active. Backup at synapse_data/homeserver.yaml.bak.1779659423. Issues opened for remaining gaps: - 0165 LUKS for media_store (at-rest encryption) - 0166 LiveKit TURN deploy (NAT traversal gap) - 0167 STUN leak to Google (hardcode external_ip) - 0168 UDP range expand 200 -> 500 - 0169 LIVEKIT_SECRET rotation (audit exposure) - 0170 livekit.example.yaml rename hygiene Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
---
|
||||
id: "0165"
|
||||
title: "Cifrar media_store/ Synapse con LUKS at-rest"
|
||||
status: pendiente
|
||||
type: infra
|
||||
domain:
|
||||
- matrix
|
||||
scope: app:element_matrix_chat
|
||||
priority: media
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0162"]
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, synapse, encryption, security, luks]
|
||||
---
|
||||
# 0165 — Cifrar media_store/ Synapse con LUKS at-rest
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-24
|
||||
**Type:** infra
|
||||
**Priority:** media
|
||||
**Domain:** matrix
|
||||
**Scope:** app:element_matrix_chat
|
||||
**Depends:** —
|
||||
**Blocks:** —
|
||||
|
||||
## Problema
|
||||
|
||||
`synapse_data/media_store/` contiene archivos subidos (fotos, voice messages, attachments) + thumbnails. Rooms NO-E2EE: media cleartext en disco. Tabla `media_repository` Postgres: filename/mime/uploader/room_id siempre cleartext. Riesgo: VPS provider snapshot disk, backups desencriptados, disco fisico.
|
||||
|
||||
## Objetivo
|
||||
|
||||
`media_store/` cifrado at-rest. Synapse arranca y sirve media normal. Decrypt automatico via keyfile en TPM o passphrase al boot.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Decidir estrategia: LUKS container file-based (loop device) vs LUKS sobre volumen Docker dedicado.
|
||||
2. Crear LUKS container 50GB (ajustar segun crecimiento previsto).
|
||||
3. Montar como `/home/ubuntu/CodeProyects/element_matrix_chat/synapse_data/media_store_encrypted/`.
|
||||
4. Stop Synapse → rsync `media_store/` → `media_store_encrypted/` → swap mountpoint.
|
||||
5. Verificar Synapse sirve thumbnails + uploads OK.
|
||||
6. Configurar auto-unlock via keyfile en `/root/.luks-media.key` con permisos 0400.
|
||||
7. Documentar recovery passphrase en `pass` (entry `matrix/luks-media-passphrase`).
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `media_store/` montado sobre LUKS, `lsblk -f` muestra crypto_LUKS.
|
||||
- [ ] Synapse arranca tras reboot completo del VPS sin intervencion manual.
|
||||
- [ ] Test: subir imagen via Element, verificar thumb generado.
|
||||
- [ ] Test: leer media_store via `dd if=/dev/sdX` directo retorna basura cifrada.
|
||||
- [ ] Passphrase backed up en `pass`.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Repetibilidad: reboot VPS, media accesible sin intervencion.
|
||||
- [ ] Observabilidad: log entry en `journalctl -u systemd-cryptsetup@*`.
|
||||
- [ ] User-facing: clientes Element no notan diferencia.
|
||||
- [ ] Recovery probado: detach LUKS y reattach con passphrase.
|
||||
|
||||
## Notas
|
||||
|
||||
LUKS solo protege at-rest. VPS provider con acceso a RAM viva ve plaintext via memory dump. Sin TPM atestado, utilidad real = anti-snapshot/anti-backup-leak/anti-physical-theft.
|
||||
|
||||
Caveat: si keyfile vive en mismo disco que LUKS device, no protege contra disk theft. Mover keyfile a USB removible o TPM2 (`systemd-cryptenroll`).
|
||||
@@ -0,0 +1,70 @@
|
||||
---
|
||||
id: "0166"
|
||||
title: "Desplegar TURN para LiveKit (coturn o integrado)"
|
||||
status: pendiente
|
||||
type: infra
|
||||
domain:
|
||||
- matrix
|
||||
scope: app:element_matrix_chat
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0167", "0168"]
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, livekit, webrtc, turn, nat]
|
||||
---
|
||||
# 0166 — Desplegar TURN para LiveKit (coturn o integrado)
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-24
|
||||
**Type:** infra
|
||||
**Priority:** alta
|
||||
**Domain:** matrix
|
||||
**Scope:** app:element_matrix_chat
|
||||
**Depends:** —
|
||||
**Blocks:** —
|
||||
|
||||
## Problema
|
||||
|
||||
LiveKit corre sin TURN (`turn.enabled: false` en `configs/livekit/livekit.yaml`). Usuarios detras de NAT simetrico (CGNAT movil 4G/5G, redes corporativas con firewall estricto, hotel WiFi) NO pueden establecer call — WebRTC ICE direct/reflexive falla. Calls fallan silenciosos para ~10-20% usuarios.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Calls funcionan en cualquier red. Element X movil sobre 4G CGNAT completa handshake.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Decidir: coturn standalone vs LiveKit TURN integrado (recomendado: integrado, menos moving parts).
|
||||
2. Anadir subdominio `turn.organic-machine.com` con Let's Encrypt cert (Traefik).
|
||||
3. Activar bloque `turn:` en `livekit.yaml`:
|
||||
```yaml
|
||||
turn:
|
||||
enabled: true
|
||||
domain: "turn.organic-machine.com"
|
||||
tls_port: 5349
|
||||
udp_port: 443
|
||||
external_tls: true
|
||||
```
|
||||
4. Abrir puertos VPS firewall: TCP+UDP 443 (best practice — bypassea firewalls corp), TCP 5349.
|
||||
5. Rotar shared secret TURN.
|
||||
6. Test: navegador en red corp con `force-tcp` flag → call establecida.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `nc -vz turn.organic-machine.com 443` UDP+TCP OK.
|
||||
- [ ] Test call Element Web detras de NAT simetrico (movil hotspot tethering) → audio/video pasa.
|
||||
- [ ] LiveKit logs muestran `TURN allocation` requests servidas.
|
||||
- [ ] `.well-known/matrix/client` sigue apuntando al `livekit_service_url` JWT correcto.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Repetibilidad: 5 calls consecutivas desde 5 redes distintas (incluido CGNAT) sin fallo.
|
||||
- [ ] Observabilidad: dashboard LiveKit muestra TURN vs direct ratio.
|
||||
- [ ] User-facing: usuario movil 4G inicia call → conecta < 3s.
|
||||
|
||||
## Notas
|
||||
|
||||
UDP 443 es trick conocido: la mayoria de firewalls corporativos solo dejan 443 (HTTPS) — TURN sobre UDP 443 bypassea sin requerir TCP relay que aumenta latencia.
|
||||
|
||||
Alternativa coturn standalone si LiveKit integrado tiene gaps de gestion: `docker run -d coturn/coturn` + config compartida con shared secret de LiveKit.
|
||||
@@ -0,0 +1,62 @@
|
||||
---
|
||||
id: "0167"
|
||||
title: "Eliminar STUN leak a Google en LiveKit (hardcode external_ip)"
|
||||
status: pendiente
|
||||
type: infra
|
||||
domain:
|
||||
- matrix
|
||||
scope: app:element_matrix_chat
|
||||
priority: baja
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0166"]
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, livekit, privacy, stun]
|
||||
---
|
||||
# 0167 — Eliminar STUN leak a Google en LiveKit (hardcode external_ip)
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-24
|
||||
**Type:** infra
|
||||
**Priority:** baja
|
||||
**Domain:** matrix
|
||||
**Scope:** app:element_matrix_chat
|
||||
**Depends:** —
|
||||
**Blocks:** —
|
||||
|
||||
## Problema
|
||||
|
||||
`rtc.use_external_ip: true` con `external_ip` vacio → LiveKit hace STUN query a `stun.l.google.com:19302` cada arranque para descubrir IP publica. Leak metadata server (IP del VPS) a Google. Contradice premisa "self-host privacy first".
|
||||
|
||||
## Objetivo
|
||||
|
||||
LiveKit conoce su IP publica sin contactar STUN externos.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Determinar IP publica VPS: `curl -s ifconfig.me`.
|
||||
2. Editar `configs/livekit/livekit.yaml`:
|
||||
```yaml
|
||||
rtc:
|
||||
use_external_ip: false
|
||||
node_ip: "<IP_PUBLICA>"
|
||||
```
|
||||
3. Si TURN propio desplegado (issue 0166), usar coturn como STUN propio.
|
||||
4. Restart `element_matrix_chat-livekit-1`.
|
||||
5. Test: call funciona igual.
|
||||
6. Auditar: `docker logs element_matrix_chat-livekit-1 | grep -i stun` no muestra queries a google.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `tcpdump -i eth0 dst stun.l.google.com` no captura paquetes tras restart.
|
||||
- [ ] Calls Element Call siguen funcionando 1:1 y grupo.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Repetibilidad: reboot VPS, 0 paquetes a stun.l.google.com.
|
||||
- [ ] Observabilidad: log LiveKit confirma IP hardcoded.
|
||||
|
||||
## Notas
|
||||
|
||||
Bajo impacto operacional pero alta consistencia con doctrina self-host. Si IP del VPS cambia (rara vez con VPS estatico), actualizar config manual o automatizar con script de healthcheck.
|
||||
@@ -0,0 +1,58 @@
|
||||
---
|
||||
id: "0168"
|
||||
title: "Ampliar UDP range LiveKit de 200 a 500 ports"
|
||||
status: pendiente
|
||||
type: infra
|
||||
domain:
|
||||
- matrix
|
||||
scope: app:element_matrix_chat
|
||||
priority: baja
|
||||
depends: []
|
||||
blocks: []
|
||||
related: ["0166"]
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, livekit, scaling, webrtc]
|
||||
---
|
||||
# 0168 — Ampliar UDP range LiveKit de 200 a 500 ports
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-24
|
||||
**Type:** infra
|
||||
**Priority:** baja
|
||||
**Domain:** matrix
|
||||
**Scope:** app:element_matrix_chat
|
||||
**Depends:** —
|
||||
**Blocks:** —
|
||||
|
||||
## Problema
|
||||
|
||||
LiveKit configurado con `port_range_start: 50000`, `port_range_end: 50200` (200 ports UDP). Cada participante usa ~2 ports → cap **~100 participantes concurrentes** sumando TODAS las calls del server. OK para uso personal hoy, justo si se anaden grupos simultaneos o reuniones >10 personas.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Sostener al menos 250 participantes concurrentes sin port exhaustion.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Editar `configs/livekit/livekit.yaml`: `port_range_end: 50500`.
|
||||
2. Actualizar `docker-compose.yml` para exponer rango ampliado (300 puertos UDP adicionales).
|
||||
3. Abrir rango en firewall VPS (UFW/iptables).
|
||||
4. Restart stack LiveKit.
|
||||
5. Smoke test: call funciona.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `docker port element_matrix_chat-livekit-1` muestra 50000-50500 UDP.
|
||||
- [ ] `ss -lun | grep -c "0.0.0.0:50"` >= 500 tras restart.
|
||||
- [ ] Call test OK.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Repetibilidad: stack reinicia limpio.
|
||||
|
||||
## Notas
|
||||
|
||||
`docker-compose.yml` actualmente lista los 200 ports uno a uno (verboso pero explicito). Considerar usar sintaxis `"50000-50500:50000-50500/udp"` para legibilidad.
|
||||
|
||||
NO incrementar a >1000 sin medir consumo memoria LiveKit — cada port asignado tiene overhead minimo pero acumula.
|
||||
@@ -0,0 +1,60 @@
|
||||
---
|
||||
id: "0169"
|
||||
title: "Rotar LIVEKIT_SECRET (expuesto en sesion auditoria)"
|
||||
status: pendiente
|
||||
type: bugfix
|
||||
domain:
|
||||
- matrix
|
||||
scope: app:element_matrix_chat
|
||||
priority: alta
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, livekit, security, secret-rotation]
|
||||
---
|
||||
# 0169 — Rotar LIVEKIT_SECRET (expuesto en sesion auditoria)
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-24
|
||||
**Type:** bugfix
|
||||
**Priority:** alta
|
||||
**Domain:** matrix
|
||||
**Scope:** app:element_matrix_chat
|
||||
**Depends:** —
|
||||
**Blocks:** —
|
||||
|
||||
## Problema
|
||||
|
||||
Durante auditoria 2026-05-24 (sesion Claude), `docker inspect element_matrix_chat-livekit-jwt-1` volco `LIVEKIT_SECRET=b00e98f70722bc...` cleartext en stdout de la sesion. Aunque la sesion es del operador, el secret quedo en log de conversacion + potencialmente en backups del log + transcripts. Rotacion necesaria por higiene.
|
||||
|
||||
## Objetivo
|
||||
|
||||
Nuevo secret 32 bytes hex, mismo `api_key` (o regenerar ambos), stack restart sin perdida sesion.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Generar nuevo secret: `openssl rand -hex 32`.
|
||||
2. Editar `configs/livekit/livekit.yaml` → bloque `keys:` con nuevo valor.
|
||||
3. Editar `.env` de docker-compose (var `LIVEKIT_SECRET` consumida por `livekit-jwt`).
|
||||
4. Restart `element_matrix_chat-livekit-1` y `element_matrix_chat-livekit-jwt-1` en orden.
|
||||
5. Test call Element Call → handshake JWT OK.
|
||||
6. Guardar secret antiguo + nuevo en `pass` con timestamp rotacion.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `docker inspect ... --format "{{.Config.Env}}"` muestra secret nuevo.
|
||||
- [ ] Element Call inicia call sin error "invalid token".
|
||||
- [ ] Entry `pass matrix/livekit-secret` actualizada.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] Repetibilidad: rotacion documentada como funcion del registry (candidato `livekit_secret_rotate_bash_infra`).
|
||||
- [ ] Observabilidad: rotation log con timestamp.
|
||||
|
||||
## Notas
|
||||
|
||||
Considerar promover el procedimiento a funcion del registry: `livekit_secret_rotate_bash_infra(ssh_host, compose_dir)` que automatiza pasos 1-5 y guarda en pass via `gpg_pass_write`.
|
||||
|
||||
Patron similar para otros secrets del stack (Synapse macaroon, MAS encryption key, postgres passwords) → capability group nuevo `secret-rotation`.
|
||||
@@ -0,0 +1,55 @@
|
||||
---
|
||||
id: "0170"
|
||||
title: "Renombrar livekit.example.yaml -> livekit.yaml en bind mount"
|
||||
status: pendiente
|
||||
type: chore
|
||||
domain:
|
||||
- matrix
|
||||
scope: app:element_matrix_chat
|
||||
priority: baja
|
||||
depends: []
|
||||
blocks: []
|
||||
related: []
|
||||
created: 2026-05-24
|
||||
updated: 2026-05-24
|
||||
tags: [matrix, livekit, hygiene]
|
||||
---
|
||||
# 0170 — Renombrar livekit.example.yaml -> livekit.yaml en bind mount
|
||||
|
||||
**Status:** pendiente
|
||||
**Created:** 2026-05-24
|
||||
**Type:** chore
|
||||
**Priority:** baja
|
||||
**Domain:** matrix
|
||||
**Scope:** app:element_matrix_chat
|
||||
**Depends:** —
|
||||
**Blocks:** —
|
||||
|
||||
## Problema
|
||||
|
||||
`configs/livekit/livekit.yaml` mantiene los comentarios "Copy this file..." del template original. Funciona pero confunde: parece config sin completar. El bind mount apunta directo a este archivo, asi que renombrar limpiamente el archivo template y mantener `livekit.yaml` limpio para mantenimiento.
|
||||
|
||||
## Objetivo
|
||||
|
||||
`livekit.yaml` limpio sin comentarios de "example", `livekit.example.yaml` separado como referencia template inicial en repo.
|
||||
|
||||
## Plan
|
||||
|
||||
1. Crear `configs/livekit/livekit.example.yaml` con plantilla limpia (placeholders).
|
||||
2. Eliminar comentarios "Copy this file..." del `livekit.yaml` actual.
|
||||
3. Verificar `.gitignore` cubre `livekit.yaml` real pero no `livekit.example.yaml`.
|
||||
4. Commit en `egutierrez/element_matrix_chat`.
|
||||
|
||||
## Acceptance
|
||||
|
||||
- [ ] `head -3 configs/livekit/livekit.yaml` NO menciona "example".
|
||||
- [ ] `configs/livekit/livekit.example.yaml` versionado.
|
||||
- [ ] Stack restart sin cambios funcionales.
|
||||
|
||||
## Definition of Done
|
||||
|
||||
- [ ] PR mergeado en `dataforge/element_matrix_chat`.
|
||||
|
||||
## Notas
|
||||
|
||||
Tarea de higiene puro. Cero impacto runtime. Mejora onboarding futuro si otro operador clona el repo.
|
||||
Reference in New Issue
Block a user