diff --git a/README.md b/README.md index fab3fcf..295a0ff 100644 --- a/README.md +++ b/README.md @@ -204,7 +204,8 @@ Every Matrix client must fetch a LiveKit JWT from lk-jwt-service before it can j - **Live (mid-call) enforcement** — the JWT re-sign covers anyone *joining* after a policy change. For people **already in the call**, a background **reconcile loop** (every `GUARD_RECONCILE_INTERVAL`, default 3 s) calls LiveKit `UpdateParticipant` to narrow their `canPublishSources`, which **unpublishes an in-progress screenshare/camera server-side for all clients** and blocks re-publish (confirmed LiveKit 1.9.11 behavior: reducing `can_publish_sources` removes the offending live track). So flipping a room to audio-only kills existing cameras/screenshares within ~one interval. The loop learns each LiveKit room's Matrix id from tokens it issues, only ever **removes** forbidden sources (never grants), preserves every other permission flag (full-replace safety), and no-ops once compliant. Disable with `GUARD_RECONCILE=0`. - **Fail-open:** any error (admin API down, bad/absent token, LiveKit unreachable, unparseable room id, unexpected JWT shape) returns the upstream response **unchanged**, so calls keep working even if enforcement is degraded. The limit check and the source-policy re-sign are **independent** (a LiveKit-admin outage during the limit count can't skip the source restriction, and vice-versa). Before re-signing, the guard **verifies its own secret actually signed the token** — on a `LIVEKIT_SECRET` mismatch it skips the restriction and passes the original token through (so a secret drift can never emit a token the SFU rejects). A room with no policy set takes a zero-overhead fast path (token untouched). -> **Security note:** `LIVEKIT_KEY`/`LIVEKIT_SECRET` are currently hardcoded in `systemd/voice-limit-guard.service` (pre-existing). Since this secret now also signs re-issued join tokens, it should be moved into `/etc/matrix-deploy.env` (already an `EnvironmentFile` on LXC 151) and the exposed value rotated. Not changed automatically to avoid a deploy breaking before the env file carries it. +> `LIVEKIT_KEY`/`LIVEKIT_SECRET` live in `/etc/matrix-deploy.env` (0600, not in git). The same pair must +> match in `/etc/livekit/config.yaml` and the `lk-jwt-service` unit — change all three together. Pure logic (limit decision, source narrowing, JWT re-sign/verify roundtrip, tamper detection) is unit-tested in `livekit/test_voice_limit_guard.py` (`python3 -m unittest livekit.test_voice_limit_guard`). diff --git a/systemd/voice-limit-guard.service b/systemd/voice-limit-guard.service index 89b0622..4e6d9ce 100644 --- a/systemd/voice-limit-guard.service +++ b/systemd/voice-limit-guard.service @@ -8,15 +8,17 @@ Type=simple ExecStart=/usr/bin/env python3 /opt/voice-limit-guard/voice-limit-guard.py Restart=on-failure RestartSec=5 -# MATRIX_TOKEN (server-admin) is read from the existing deploy env file. +# Secrets come from the deploy env file, never from this unit — it is in git. +# Provides MATRIX_TOKEN (server-admin), LIVEKIT_KEY and LIVEKIT_SECRET. +# NOTE: LIVEKIT_KEY/SECRET must match /etc/livekit/config.yaml and +# lk-jwt-service (which reads the same env file). Rotating means changing +# all three together, or every call fails to get a token. EnvironmentFile=/etc/matrix-deploy.env Environment=GUARD_BIND_HOST=0.0.0.0 Environment=GUARD_BIND_PORT=8070 Environment=GUARD_UPSTREAM=http://127.0.0.1:8071 Environment=LIVEKIT_API=http://127.0.0.1:7880 Environment=SYNAPSE_API=http://127.0.0.1:8008 -Environment=LIVEKIT_KEY=lotuskey -Environment=LIVEKIT_SECRET=GoI5PPLbNXZlQHlfdAzLFy0B/QoqA9uXiyb/p6dQEtc= [Install] WantedBy=multi-user.target