Skip to content

feat(mumble): security, VX auth, direction enforcement, channel sync#297

Open
TX-RX wants to merge 2 commits intobrian7704:masterfrom
TX-RX:feature/mumble-ice-security-auth
Open

feat(mumble): security, VX auth, direction enforcement, channel sync#297
TX-RX wants to merge 2 commits intobrian7704:masterfrom
TX-RX:feature/mumble-ice-security-auth

Conversation

@TX-RX
Copy link
Copy Markdown
Contributor

@TX-RX TX-RX commented Apr 28, 2026

What this PR does

Four things, all needed to make Mumble work for the official ATAK VX voice plugin:

  1. Security fix — Ice secret was hardcoded as "", so any process on localhost could administer Murmur over Ice without authentication. Now read from OTS_ICE_SECRET (added to DefaultConfig).

  2. 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:

    OTS username
      -> EUD callsign
      -> base callsign with `---<uuid>` stripped
      -> underscore -> space variant
      -> cert CN (= EUD UID, immutable; survives mid-session callsign renames)
    
  3. Direction enforcementDirectionEnforcementCallback applies Murmur's suppress flag from each user's OTS group direction (IN=speak, OUT=listen-only).

  4. OTS group → Mumble channel sync — auto-creates a root-level Mumble channel for every OTS group (channel name == group name, which DirectionEnforcementCallback already requires for its lookup). Sync runs on startup per booted virtual server, and again from group_api after POST/DELETE /api/groups so 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 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. Implementing nameToId / idToName / getInfo to 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.callsign column 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() returns user.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()). 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.

Channel sync surface

The Mumble Ice daemon publishes itself as app.extensions["mumble_ice_app"] on init, exposing request_sync() to other blueprints. request_sync() spawns a daemon thread that calls getBootedServers() and runs sync_channels_from_groups(server) on each — Ice proxy calls from a worker thread are fine (same pattern used by DirectionEnforcementCallback._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:

  • The device is enrolled with OTS (data package installed)
  • The EUD is linked to an OTS user (euds.user_id populated)
  • The user is a member of the OTS groups that gate the Mumble channels they should access

PC / Mumble desktop client — username + password. Standard Mumble client connecting to the server's Mumble port:

  • Username: OTS username (not the callsign)
  • Password: OTS password
  • (Optional) install an OTS-issued client cert to skip the password on subsequent connects

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 by authenticate() 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:

Change When it takes effect
Direction flip (IN ↔ OUT) Next channel move (subject to 30s session cache) or on reconnect
Group add/remove On reconnect (Mumble caches the group list returned by authenticate())
Channel ACL edits in Mumble client Immediate (Murmur enforces)
Group → channel sync Immediate on add; channels for deleted groups are not auto-removed

A future improvement would be one of:

  1. Kick-on-change — when PR feat: add PATCH /api/groups/members for atomic membership level management #298's PATCH endpoint mutates groups_users, look up the user's active Mumble sessions via Ice and call Server::kickUser() so they reconnect with the new permissions.
  2. ACL provisioning — drop the callback entirely and have OTS write Murmur ACL rules directly when groups change. This is the cleanest long-term path; deletes the 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

  • 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
  • Removed getRegistration (it's a Server method, not on ServerUpdatingAuthenticator — Murmur never called it)

Test logs

Posted in a follow-up comment on this PR.

🤖 Generated with Claude Code

@brian7704
Copy link
Copy Markdown
Owner

Thanks for this PR. Can you post an OTS debug log where the callsign has the ---<uuid> suffix? I haven't seen that before and I want to see where it's coming from. Could it be from the VX plugin?

@TX-RX
Copy link
Copy Markdown
Contributor Author

TX-RX commented Apr 28, 2026

@brian7704 PR Updated the description with a few other issues I found during teseting. Log messages included as requested.

@brian7704
Copy link
Copy Markdown
Owner

Thank you. Could you also post OTS's log in debug mode?

@TX-RX
Copy link
Copy Markdown
Contributor Author

TX-RX commented Apr 29, 2026

[2026-04-29 15:27:06,711] - OpenTAKServer[78311] - mumble_ice_app - userDisconnected - 384 - INFO - User disconnected: USER-A (session=68)
INFO:OpenTAKServer:User disconnected: USER-A (session=68)
[2026-04-29 15:31:10,041] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 42 - INFO - Mumble auth request for USER-B
INFO:OpenTAKServer:Mumble auth request for USER-B
[2026-04-29 15:31:10,050] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user USER-B to group CHAN-1
INFO:OpenTAKServer:Mumble auth: Adding user USER-B to group CHAN-1
[2026-04-29 15:31:10,156] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 106 - INFO - Mumble auth: USER-B has been authenticated
INFO:OpenTAKServer:Mumble auth: USER-B has been authenticated
[2026-04-29 15:31:10,156] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 108 - INFO - Mumble auth: ID=4000 name=USER-B groups=['CHAN-1']
INFO:OpenTAKServer:Mumble auth: ID=4000 name=USER-B groups=['CHAN-1']
[2026-04-29 15:31:10,198] - OpenTAKServer[78311] - mumble_ice_app - userConnected - 372 - INFO - User connected: USER-B (session=69, userid=4000) channel=6
INFO:OpenTAKServer:User connected: USER-B (session=69, userid=4000) channel=6
[2026-04-29 15:31:10,205] - OpenTAKServer[78311] - mumble_ice_app - _apply_direction - 349 - INFO - LISTEN ONLY: USER-B in CHAN-1 (direction=OUT)
INFO:OpenTAKServer:LISTEN ONLY: USER-B in CHAN-1 (direction=OUT)
[2026-04-29 15:31:35,919] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 42 - INFO - Mumble auth request for DEVICE-1---UUID-BETA
INFO:OpenTAKServer:Mumble auth request for DEVICE-1---UUID-BETA
[2026-04-29 15:31:35,924] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 60 - INFO - Mumble auth: Trying base callsign: DEVICE-1
INFO:OpenTAKServer:Mumble auth: Trying base callsign: DEVICE-1
[2026-04-29 15:31:35,929] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 67 - INFO - Mumble auth: Matched callsign DEVICE-1---UUID-BETA to user USER-A
INFO:OpenTAKServer:Mumble auth: Matched callsign DEVICE-1---UUID-BETA to user USER-A
[2026-04-29 15:31:35,930] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-2
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-2
[2026-04-29 15:31:35,930] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-3
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-3
[2026-04-29 15:31:35,930] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-1
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-1
[2026-04-29 15:31:35,930] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-4
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-BETA to group CHAN-4
[2026-04-29 15:31:35,931] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 84 - INFO - Mumble auth: User DEVICE-1---UUID-BETA is admin
INFO:OpenTAKServer:Mumble auth: User DEVICE-1---UUID-BETA is admin
[2026-04-29 15:31:36,043] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 106 - INFO - Mumble auth: DEVICE-1---UUID-BETA has been authenticated
INFO:OpenTAKServer:Mumble auth: DEVICE-1---UUID-BETA has been authenticated
[2026-04-29 15:31:36,043] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 108 - INFO - Mumble auth: ID=2409 name=DEVICE-1---UUID-BETA groups=['CHAN-2', 'CHAN-3', 'CHAN-1', 'CHAN-4', 'admin']
INFO:OpenTAKServer:Mumble auth: ID=2409 name=DEVICE-1---UUID-BETA groups=['CHAN-2', 'CHAN-3', 'CHAN-1', 'CHAN-4', 'admin']
[2026-04-29 15:31:36,074] - OpenTAKServer[78311] - mumble_ice_app - userConnected - 372 - INFO - User connected: DEVICE-1---UUID-BETA (session=70, userid=2409) channel=0
INFO:OpenTAKServer:User connected: DEVICE-1---UUID-BETA (session=70, userid=2409) channel=0
[2026-04-29 15:31:36,091] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 42 - INFO - Mumble auth request for DEVICE-1---UUID-ALPHA
INFO:OpenTAKServer:Mumble auth request for DEVICE-1---UUID-ALPHA
[2026-04-29 15:31:36,095] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 60 - INFO - Mumble auth: Trying base callsign: DEVICE-1
INFO:OpenTAKServer:Mumble auth: Trying base callsign: DEVICE-1
[2026-04-29 15:31:36,100] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 67 - INFO - Mumble auth: Matched callsign DEVICE-1---UUID-ALPHA to user USER-A
INFO:OpenTAKServer:Mumble auth: Matched callsign DEVICE-1---UUID-ALPHA to user USER-A
[2026-04-29 15:31:36,104] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-2
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-2
[2026-04-29 15:31:36,104] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-3
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-3
[2026-04-29 15:31:36,104] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-1
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-1
[2026-04-29 15:31:36,104] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-4
INFO:OpenTAKServer:Mumble auth: Adding user DEVICE-1---UUID-ALPHA to group CHAN-4
[2026-04-29 15:31:36,104] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 84 - INFO - Mumble auth: User DEVICE-1---UUID-ALPHA is admin
INFO:OpenTAKServer:Mumble auth: User DEVICE-1---UUID-ALPHA is admin
[2026-04-29 15:31:36,212] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 106 - INFO - Mumble auth: DEVICE-1---UUID-ALPHA has been authenticated
INFO:OpenTAKServer:Mumble auth: DEVICE-1---UUID-ALPHA has been authenticated
[2026-04-29 15:31:36,212] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 108 - INFO - Mumble auth: ID=2620 name=DEVICE-1---UUID-ALPHA groups=['CHAN-2', 'CHAN-3', 'CHAN-1', 'CHAN-4', 'admin']
INFO:OpenTAKServer:Mumble auth: ID=2620 name=DEVICE-1---UUID-ALPHA groups=['CHAN-2', 'CHAN-3', 'CHAN-1', 'CHAN-4', 'admin']
[2026-04-29 15:31:36,247] - OpenTAKServer[78311] - mumble_ice_app - userConnected - 372 - INFO - User connected: DEVICE-1---UUID-ALPHA (session=71, userid=2620) channel=0
INFO:OpenTAKServer:User connected: DEVICE-1---UUID-ALPHA (session=71, userid=2620) channel=0
[2026-04-29 15:32:09,580] - OpenTAKServer[78311] - mumble_ice_app - _apply_direction - 349 - INFO - LISTEN ONLY: USER-B in CHAN-1 (direction=OUT)
INFO:OpenTAKServer:LISTEN ONLY: USER-B in CHAN-1 (direction=OUT)
[2026-04-29 15:33:33,178] - OpenTAKServer[78311] - mumble_ice_app - userDisconnected - 384 - INFO - User disconnected: USER-B (session=69)
INFO:OpenTAKServer:User disconnected: USER-B (session=69)
[2026-04-29 15:33:33,320] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 42 - INFO - Mumble auth request for USER-A
INFO:OpenTAKServer:Mumble auth request for USER-A
[2026-04-29 15:33:33,325] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user USER-A to group CHAN-2
INFO:OpenTAKServer:Mumble auth: Adding user USER-A to group CHAN-2
[2026-04-29 15:33:33,325] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user USER-A to group CHAN-3
INFO:OpenTAKServer:Mumble auth: Adding user USER-A to group CHAN-3
[2026-04-29 15:33:33,325] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user USER-A to group CHAN-1
INFO:OpenTAKServer:Mumble auth: Adding user USER-A to group CHAN-1
[2026-04-29 15:33:33,325] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user USER-A to group CHAN-4
INFO:OpenTAKServer:Mumble auth: Adding user USER-A to group CHAN-4
[2026-04-29 15:33:33,325] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 84 - INFO - Mumble auth: User USER-A is admin
INFO:OpenTAKServer:Mumble auth: User USER-A is admin
[2026-04-29 15:33:33,430] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 106 - INFO - Mumble auth: USER-A has been authenticated
INFO:OpenTAKServer:Mumble auth: USER-A has been authenticated
[2026-04-29 15:33:33,430] - OpenTAKServer[78311] - mumble_authenticator - authenticate - 108 - INFO - Mumble auth: ID=2000 name=USER-A groups=['CHAN-2', 'CHAN-3', 'CHAN-1', 'CHAN-4', 'admin']
INFO:OpenTAKServer:Mumble auth: ID=2000 name=USER-A groups=['CHAN-2', 'CHAN-3', 'CHAN-1', 'CHAN-4', 'admin']
[2026-04-29 15:33:33,463] - OpenTAKServer[78311] - mumble_ice_app - userConnected - 372 - INFO - User connected: USER-A (session=72, userid=2000) channel=2
INFO:OpenTAKServer:User connected: USER-A (session=72, userid=2000) channel=2
[2026-04-29 15:34:48,076] - OpenTAKServer[80550] - app - init_extensions - 74 - INFO - OpenTAKServer 1.7.10
[2026-04-29 15:34:48,076] - OpenTAKServer[80550] - app - init_extensions - 75 - INFO - Loading the database...
[2026-04-29 15:34:48,130] - OpenTAKServer[80550] - certificate_authority - create_ca - 166 - DEBUG - CA already exists
[2026-04-29 15:34:48,788] - OpenTAKServer[80550] - app - main - 452 - DEBUG - Starting in debug mode
[2026-04-29 15:34:48,795] - OpenTAKServer[80550] - meshtastic_controller - init - 31 - INFO - Starting Meshtastic controller...
[2026-04-29 15:34:48,804] - OpenTAKServer[80550] - app - main - 487 - INFO - Starting Mumble authentication handler
[2026-04-29 15:34:48,804] - OpenTAKServer[80550] - mumble_ice_app - init - 26 - INFO - mumble daemon init
[2026-04-29 15:34:48,805] - OpenTAKServer[80550] - mumble_ice_app - run - 46 - INFO - Set Ice secret in run() method
[2026-04-29 15:34:48,806] - OpenTAKServer[80550] - mumble_ice_app - initialize_ice_connection - 97 - INFO - Ice secret from config: 64 chars, type: <class 'str'>
[2026-04-29 15:34:48,806] - OpenTAKServer[80550] - mumble_ice_app - initialize_ice_connection - 100 - INFO - Ice secret set in ImplicitContext
[2026-04-29 15:34:48,806] - OpenTAKServer[80550] - mumble_ice_app - initialize_ice_connection - 104 - DEBUG - Connecting to Ice server (127.0.0.1:6502)
[2026-04-29 15:34:48,806] - OpenTAKServer[80550] - mumble_ice_app - attach_callbacks - 126 - DEBUG - Attaching meta callback
[2026-04-29 15:34:48,807] - OpenTAKServer[80550] - mumble_ice_app - attach_callbacks - 131 - DEBUG - Setting mumble authenticator for virtual server 1
[2026-04-29 15:34:48,840] - OpenTAKServer[80550] - mumble_ice_app - attach_server_callback - 179 - INFO - Direction enforcement callback attached to server 1
[2026-04-29 15:34:48,840] - OpenTAKServer[80550] - mumble_ice_app - attach_callbacks - 126 - DEBUG - Attaching meta callback
[2026-04-29 15:34:48,841] - OpenTAKServer[80550] - mumble_ice_app - attach_callbacks - 131 - DEBUG - Setting mumble authenticator for virtual server 1
[2026-04-29 15:34:51,589] - OpenTAKServer[80550] - pubsub_manager - initialize - 38 - INFO - kombu backend initialized.
[2026-04-29 15:34:51,647] - OpenTAKServer[80550] - ots_socketio - connect - 39 - DEBUG - got a socketio connection from USER-A
[2026-04-29 15:34:59,267] - OpenTAKServer[80550] - pubsub_manager - _thread - 213 - DEBUG - pubsub message: emit
[2026-04-29 15:36:07,823] - OpenTAKServer[80550] - mumble_ice_app - userDisconnected - 384 - INFO - User disconnected: USER-A (session=72)
[2026-04-29 15:36:11,633] - OpenTAKServer[80550] - mumble_authenticator - authenticate - 42 - INFO - Mumble auth request for USER-B
[2026-04-29 15:36:11,640] - OpenTAKServer[80550] - mumble_authenticator - authenticate - 80 - INFO - Mumble auth: Adding user USER-B to group CHAN-1
[2026-04-29 15:36:11,743] - OpenTAKServer[80550] - mumble_authenticator - authenticate - 106 - INFO - Mumble auth: USER-B has been authenticated
[2026-04-29 15:36:11,744] - OpenTAKServer[80550] - mumble_authenticator - authenticate - 108 - INFO - Mumble auth: ID=4000 name=USER-B groups=['CHAN-1']
[2026-04-29 15:36:11,774] - OpenTAKServer[80550] - mumble_ice_app - userConnected - 372 - INFO - User connected: USER-B (session=73, userid=4000) channel=6
[2026-04-29 15:36:11,781] - OpenTAKServer[80550] - mumble_ice_app - _apply_direction - 349 - INFO - LISTEN ONLY: USER-B in CHAN-1 (direction=OUT)
[2026-04-29 15:37:59,748] - OpenTAKServer[80550] - pubsub_manager - _thread - 213 - DEBUG - pubsub message: emit
[2026-04-29 15:38:40,750] - OpenTAKServer[80550] - pubsub_manager - _thread - 213 - DEBUG - pubsub message: emit
[2026-04-29 15:39:05,771] - OpenTAKServer[80550] - pubsub_manager - _thread - 213 - DEBUG - pubsub message: emit
[2026-04-29 15:39:50,788] - OpenTAKServer[80550] - pubsub_manager - _thread - 213 - DEBUG - pubsub message: emit

@brian7704

…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>
@TX-RX TX-RX force-pushed the feature/mumble-ice-security-auth branch from 4c16372 to 85deb99 Compare May 1, 2026 02:52
@TX-RX TX-RX changed the title fix(mumble): Ice secret security fix, ATAK callsign auth, and direction enforcement fix(mumble): security fix, ATAK VX voice plugin auth, and direction enforcement May 1, 2026
@TX-RX
Copy link
Copy Markdown
Contributor Author

TX-RX commented May 1, 2026

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 -> ots_admin, ots_user_2; EUDs -> EUD-Alpha/-Bravo/-Charlie/-Delta; channels -> CHAN-A..CHAN-E; uuids -> <uuid-1>..; cert UID -> ANDROID-xxxxxxxx.

1. ATAK VX dual-connection: both sockets auth, get unique Mumble ids

Mumble auth request for EUD-Alpha---<uuid-1>
Mumble auth: id=2622 display=EUD-Alpha---<uuid-1> groups=['CHAN-A','CHAN-B','CHAN-C','admin']
User connected: EUD-Alpha---<uuid-1> (session=23, userid=2622) channel=5

Mumble auth request for EUD-Alpha---<uuid-2>     # second socket from same physical device
Mumble auth: id=2734 display=EUD-Alpha---<uuid-2> groups=['CHAN-A','CHAN-B','CHAN-C','admin']
User connected: EUD-Alpha---<uuid-2> (session=24, userid=2734) channel=6

This is what answers the ---<uuid> question — it's the official VX voice plugin opening two connections per device, each with its own UUID. The deterministic-hash offset on user.id*1000 gives both sockets unique Mumble user ids despite mapping to the same OTS account.

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 Wrong certificate or password for existing user before Ice was even called. After making nameToId/idToName/getInfo authoritative:

Mumble auth request for EUD-Alpha---<uuid-1>
Mumble auth: id=2622 display=EUD-Alpha---<uuid-1> groups=['CHAN-A',...]
User connected: EUD-Alpha---<uuid-1> (session=15, userid=2622) channel=0
User disconnected: EUD-Alpha---<uuid-1> (session=15)

Mumble auth request for EUD-Alpha---<uuid-1>     # immediate reconnect
Mumble auth: id=2622 display=EUD-Alpha---<uuid-1> groups=['CHAN-A',...]
User connected: EUD-Alpha---<uuid-1> (session=23, userid=2622) channel=5

3. Cert-CN fallback for callsign renames (rebooted device, OTS EUD callsign stale)

I renamed the callsign in ATAK from EUD-Charlie to EUD Charlie MXVX (with spaces). VX immediately auths with the new name (EUD_Charlie_MXVX---<uuid>) but the OTS EUDs table still has the old EUD-Charlie (CoT update hadn't landed yet). Without the cert-CN fallback, this would fail every 10s until a CoT update arrived.

Mumble auth request for EUD_Charlie_MXVX---<uuid-3>
Mumble auth: Matched cert CN to EUD uid=ANDROID-xxxxxxxx (db callsign='EUD-Charlie', presented='EUD_Charlie_MXVX---<uuid-3>')
Mumble auth: id=4882 display=EUD_Charlie_MXVX---<uuid-3> groups=['CHAN-A','CHAN-B']
User connected: EUD_Charlie_MXVX---<uuid-3> (session=25, userid=4882) channel=6

4. PC client (Mumble Windows) - happy path unchanged

Mumble auth request for ots_admin
Mumble auth: id=2000 display=ots_admin groups=['CHAN-A','CHAN-B','CHAN-C','CHAN-D','CHAN-E','admin']
User connected: ots_admin (session=17, userid=2000) channel=5

5. Direction enforcement (OUT = listen-only)

ots_user_2 is direction=OUT for CHAN-A. Suppress flag applied within ~7ms of joining the channel:

User connected: ots_user_2 (session=66, userid=4112) channel=0
LISTEN ONLY: ots_user_2 in CHAN-A (direction=OUT)

Service state during testing

  • 0 active bans throughout (autobanAttempts bumped to 1000 during testing as a safety net)
  • No ERROR lines in OTS log
  • No Wrong certificate or password rejections after the patch
  • Direction callback applies suppress flag in <10ms on connect

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>
@TX-RX TX-RX changed the title fix(mumble): security fix, ATAK VX voice plugin auth, and direction enforcement feat(mumble): security, VX auth, direction enforcement, channel sync May 1, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants