fix: read LiveKit credentials from env file instead of hardcoding
LIVEKIT_KEY/LIVEKIT_SECRET were committed in the unit since 68a6acf
(2026-06-12). That secret signs re-issued SFU join tokens, so it
controlled who could publish. Rotated and moved to /etc/matrix-deploy.env
(0600); livekit-server, lk-jwt-service and voice-limit-guard all updated
and verified against the new value.
Environment= overrides EnvironmentFile= regardless of order, so the
hardcoded lines had to be removed rather than shadowed.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -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`).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user