feat(mumble): security, VX auth, direction enforcement, channel sync#297
feat(mumble): security, VX auth, direction enforcement, channel sync#297TX-RX wants to merge 2 commits intobrian7704:masterfrom
Conversation
|
Thanks for this PR. Can you post an OTS debug log where the callsign has the |
|
@brian7704 PR Updated the description with a few other issues I found during teseting. Log messages included as requested. |
|
Thank you. Could you also post OTS's log in debug mode? |
|
[2026-04-29 15:27:06,711] - OpenTAKServer[78311] - mumble_ice_app - userDisconnected - 384 - INFO - User disconnected: USER-A (session=68) |
…nforcement
## Security
* Read Ice secret from OTS_ICE_SECRET (was hardcoded as empty string, so any
process on localhost could administer Murmur over Ice without authentication).
* Add OTS_ICE_SECRET = "" to DefaultConfig so the setting is discoverable.
## ATAK VX voice plugin auth
The official ATAK VX voice plugin connects to Murmur as the device's callsign
(not the OTS username), appends `---<uuid>` per connection, and converts spaces
in callsigns to underscores. Authenticator now resolves this via a single
lookup chain: OTS username -> EUD callsign -> base callsign with `---<uuid>`
stripped -> underscore->space -> cert CN (EUD UID).
The cert-CN fallback survives mid-session callsign renames: ATAK auths with the
new callsign immediately, but the OTS EUDs.callsign column only updates on the
next CoT.
ATAK clients are validated by client cert (TLS layer), not password, so
verify_password() is skipped when the lookup matched via callsign or cert.
## Murmur authority (Murmur 1.3 cred-check bug)
Murmur 1.3 caches Ice-authed users in its local users table on first connect.
On reconnect it demands a cert hash or password BEFORE calling Ice
authenticate(), and rejects with "Wrong certificate or password for existing
user" — so every reconnect failed. Implementing nameToId/idToName/getInfo to
return authoritative answers tells Murmur to defer to Ice instead of its local
table, which fixes both reconnects and direct-call/whisper lookups.
## VX dual-connection support
The VX plugin opens two simultaneous Mumble sockets per device, each with a
different `---<uuid>` suffix. mumble_identity() returns base_id + a
deterministic offset per callsign so both sessions get unique Mumble user ids.
## Direction enforcement
DirectionEnforcementCallback applies Murmur's suppress flag from each user's
OTS group direction (IN=speak, OUT=listen-only). DB queries run in the Ice
dispatch thread (same as authenticate()); only getState/setState are dispatched
to a background daemon thread to avoid Ice thread-pool deadlock.
Admins skip the suppress check entirely (calling getState() for an admin
session blocks for 30s and crashes the Ice connection).
Duplicate callback registration is guarded per server_id and cleared by
on_server_stopped() so virtual-server restarts re-register correctly.
## Bug fixes folded in
* Linux import crash from `from opentakserver.models.User` (correct case is
`user`)
* Admin detection via `role.name == 'administrator'` instead of fragile
`len(user.groups) >= 6` heuristic
* Ice method stubs now return correct sentinels (-2, -1, 0, b"", {}) instead
of None which serialization in some Murmur versions doesn't accept
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
4c16372 to
85deb99
Compare
Test logs (anonymized)Live-tested on my Azure server with PC client (Mumble Windows 1.5.857) + ATAK VX voice plugin on multiple Android devices. Anonymized: users -> 1. ATAK VX dual-connection: both sockets auth, get unique Mumble idsThis is what answers the 2. Reconnect after disconnect (the rejection-loop bug, now fixed)Before this PR, every reconnect hit Murmur's local cred check and was rejected with 3. Cert-CN fallback for callsign renames (rebooted device, OTS EUD callsign stale)I renamed the callsign in ATAK from 4. PC client (Mumble Windows) - happy path unchanged5. Direction enforcement (OUT = listen-only)
Service state during testing
|
DirectionEnforcementCallback keys IN/OUT speak permissions on channel_name == group_name, so a new OTS group with no matching Mumble channel had no voice room for users to join. Channels had to be created manually in Murmur and could drift from the group list. - mumble_ice_app: add sync_channels_from_groups(server) and request_sync(). Sync runs on startup per booted virtual server (after attach_server_callback) and on demand from blueprints via app.extensions["mumble_ice_app"]. - group_api: trigger request_sync() after add_group / delete_group success. One-way (create-only): channels for deleted groups are logged as warnings but not removed, to avoid kicking active voice users. Operators can prune manually. __ANON__ is skipped. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
What this PR does
Four things, all needed to make Mumble work for the official ATAK VX voice plugin:
Security fix — Ice secret was hardcoded as
"", so any process on localhost could administer Murmur over Ice without authentication. Now read fromOTS_ICE_SECRET(added toDefaultConfig).ATAK VX voice plugin auth — VX connects to Murmur as the device's callsign (not OTS username), appends
---<uuid>per connection, replaces spaces in callsigns with underscores, and validates by client cert (no password). The authenticator now resolves all of this:Direction enforcement —
DirectionEnforcementCallbackapplies Murmur'ssuppressflag from each user's OTS groupdirection(IN=speak, OUT=listen-only).OTS group → Mumble channel sync — auto-creates a root-level Mumble channel for every OTS group (channel name == group name, which
DirectionEnforcementCallbackalready requires for its lookup). Sync runs on startup per booted virtual server, and again fromgroup_apiafterPOST/DELETE /api/groupsso new groups get a voice channel without a service restart.__ANON__is skipped. One-way (create-only): channels for deleted groups are logged as warnings but not removed, to avoid kicking active voice users — operators can prune manually.Two architectural fixes that were the hardest to find
Murmur 1.3 cred-check bug. Murmur caches Ice-authed users in its local
userstable on first connect. On reconnect it demands a cert hash or password before calling Iceauthenticate(), and rejects withWrong certificate or password for existing user. ImplementingnameToId/idToName/getInfoto return authoritative answers tells Murmur to defer to Ice instead of its local table — fixes both reconnects and direct-call/whisper lookups.Cert-CN fallback for callsign renames. When a user renames their callsign in ATAK, the VX plugin authenticates with the new name immediately, but the OTS
EUDs.callsigncolumn only updates on the next CoT location update. During that window, callsign-based lookup misses. Parsing the client cert's CN (which is the immutable EUD UID for ATAK certs) lets us resolve the EUD anyway.VX dual-connection
The VX plugin opens two simultaneous Mumble sockets per device, each with a different
---<uuid>suffix.mumble_identity()returnsuser.id * 1000 + deterministic_offset(callsign)so both sessions get unique Mumble user ids. (This answers your earlier question about---<uuid>— yes, it's the VX plugin's protocol, confirmed against ATAK CIV which uses the bare callsign.)Direction enforcement threading
DB queries run in the Ice dispatch thread (same context as
authenticate()). OnlygetState/setStateare dispatched to a background daemon thread to avoid Ice thread-pool deadlock. Admins skip the suppress check entirely — callinggetState()for an admin session blocks for 30s and crashes the Ice connection.Channel sync surface
The Mumble Ice daemon publishes itself as
app.extensions["mumble_ice_app"]on init, exposingrequest_sync()to other blueprints.request_sync()spawns a daemon thread that callsgetBootedServers()and runssync_channels_from_groups(server)on each — Ice proxy calls from a worker thread are fine (same pattern used byDirectionEnforcementCallback._dispatch_apply). The hook is best-effort: if Mumble Ice isn't running, group adds still succeed and the call no-ops with a warning.How users authenticate
ATAK (VX voice plugin) — no password needed. The plugin uses the device's existing TAK enrollment cert and the device callsign. Just make sure:
euds.user_idpopulated)PC / Mumble desktop client — username + password. Standard Mumble client connecting to the server's Mumble port:
Channel ACLs are still one-time and manual. Sync creates the channels, but Murmur ACLs (who can
Enter/Speak) are still set up once in the Mumble desktop client (right-click channel → Edit → ACL, grant against OTS group names). After that, OTS group membership returned byauthenticate()flows through automatically — confirmed working: non-admins see lock icons on channels they don't belong to, admins see and enter all channels.Known limitations
Permission changes don't fully propagate to active Mumble sessions:
authenticate())A future improvement would be one of:
groups_users, look up the user's active Mumble sessions via Ice and callServer::kickUser()so they reconnect with the new permissions.DirectionEnforcementCallback, the 30s cache, and all the threading workarounds. Would also be the natural place to extend the channel sync into a true two-way sync (delete channel when group is deleted, only if no one is in it).For now I think the workflow is acceptable — admins changing memberships can ask the affected user to reconnect, which takes a few seconds in VX.
Bug fixes folded in
from opentakserver.models.User(correct case isuser)role.name == 'administrator'instead of fragilelen(user.groups) >= 6heuristic-2,-1,0,b"",{}) instead ofNonegetRegistration(it's a Server method, not onServerUpdatingAuthenticator— Murmur never called it)Test logs
Posted in a follow-up comment on this PR.
🤖 Generated with Claude Code