Add optional Cloudflare TURN credential fetching#19650
Add optional Cloudflare TURN credential fetching#19650kakahu2015 wants to merge 6 commits intoelement-hq:developfrom
Conversation
Add a new voip.turn_mode config option that allows explicit selection of the TURN credential source: - coturn: Direct local CoTURN (default, backward compatible) - cf: Cloudflare TURN with CoTURN fallback - broker: Federation broker with CoTURN fallback Previously the routing was implicit based on turn_federation_deployment and turn_cloudflare_enabled flags in an if/elif relationship, which meant enabling federation mode silently disabled Cloudflare TURN. The new turn_mode flag makes the intent explicit and ensures each mode has a clear fallback to local CoTURN on failure.
- turn-howto.md: document turn_mode (coturn/cf/broker) with examples - config_documentation.md: add turn_mode reference entry - clarify that turn_cloudflare_enabled/turn_federation_deployment must be set when using cf/broker modes respectively
Add turn_mode to override_config in CF and broker tests so they exercise the correct code path with the new three-way routing.
reivilibre
left a comment
There was a problem hiding this comment.
Thank you for sending this in
| - `cf`: fetch credentials from Cloudflare Realtime TURN, falling back to local CoTURN on failure. | ||
| - `broker`: fetch credentials from a federated TURN broker, falling back to local CoTURN on failure. |
There was a problem hiding this comment.
This fallback logic feels a bit surprising and maybe unnecessarily complicated; is it likely to be a real use case when people want to have both?
| --- | ||
| ### `turn_broker_url` | ||
|
|
||
| *(string|null)* The URL of a TURN broker endpoint that returns Matrix-style TURN credentials (`username`, `password`, `ttl`, `uris`). This is only used when `turn_federation_deployment` is true. Defaults to `null`. |
There was a problem hiding this comment.
I am somewhat wondering what the point of a TURN broker is; what does it provide over configuring a different route in your reverse proxy to handle the TURN server credentials endpoint?
| * [coturn](setup/turn/coturn.md) | ||
| * [eturnal](setup/turn/eturnal.md) | ||
|
|
||
| Synapse can also fetch short-lived credentials from Cloudflare Realtime TURN. |
There was a problem hiding this comment.
should we link to some docs about this service?
| If you operate multiple federated homeservers and want them to fetch the same | ||
| shared TURN credentials for a short time window, configure those homeservers to | ||
| use a TURN broker instead of calling Cloudflare directly: |
There was a problem hiding this comment.
I am a bit confused about this; what would be the reason you'd want multiple servers with the same credentials?
| Cloudflare may return alternate port 53 TURN URLs in addition to the primary | ||
| ports. Synapse filters those `:53` TURN URLs before returning credentials to | ||
| clients, since browsers often time out on that port. |
There was a problem hiding this comment.
This sounds fairly suspicious; it'd be really good to have some justification and references to back this up, if you don't mind
| from tests.unittest import override_config | ||
|
|
||
|
|
||
| class ParseCloudflareTurnResponseTestCase(unittest.TestCase): |
There was a problem hiding this comment.
these tests should have docstrings explaining what they are testing, please :)
| self.auth = hs.get_auth() | ||
| self.http_client: SimpleHttpClient = hs.get_proxied_http_client() | ||
|
|
||
| async def _get_turn_broker_credentials(self, ttl: int) -> JsonDict | None: |
There was a problem hiding this comment.
these new methods should have docstrings; imagining I'm 100% unfamiliar with what these things are, right now it's a bit hard to understand what these are for
| default: true | ||
| examples: | ||
| - false | ||
| turn_cloudflare_enabled: |
There was a problem hiding this comment.
I wonder if nesting all the config under turn_cloudflare: would make a more sensible config. I don't blame you for matching what was already there, but over the years we've started organising config a bit more hieararchically. Should make it easier to discover the relevant options, particularly in the config manual
|
Thanks, that’s a fair point. The code path is intended to leave the existing coturn behavior unchanged, and treat Cloudflare TURN / broker as optional best-effort fetch modes. If the fetch fails, Synapse falls back to the locally configured TURN server. I’ll rewrite the docs and config descriptions to make that explicit, especially around when each mode is active and what the fallback behavior is. I’ll also add a clearer justification for filtering Cloudflare’s TURN URLs. |
|
Let me clarify the broker use case, since I did not explain it well in the docs. The TURN credential problem in federation comes down to ICE candidate matching:
The broker solves case 2b: it acts as a single credential authority that fetches ONE set of CF TURN credentials and serves the same credentials to all federated homeservers. Both sides relay through the same TURN infrastructure, ICE matches, the call works. A reverse proxy cannot do this because it has no concept of credential synchronization — it just routes requests, it cannot ensure two independent Synapse instances receive identical credentials within the same time window. I will rewrite the docs to make this explicit. |
…icate test key - Replace substring-based port 53 check with urlparse exact port match to avoid false-matching port 5349 (TURNS) - Add test case for port 5349 preservation - Add docstrings to all new methods and tests per review feedback - Remove duplicate turn_cloudflare_enabled key in fallback test config
Summary
This adds optional support for fetching short-lived TURN credentials from Cloudflare Realtime TURN via Synapse's existing
/_matrix/client/*/voip/turnServerendpoint.Clients do not need any Cloudflare-specific changes: Synapse performs the Cloudflare API request server-side and returns the usual Matrix TURN response shape (
username,password,ttl,uris).Why
Today Synapse supports:
turn_shared_secret)turn_username/turn_password)That works well for self-hosted TURN servers such as coturn or eturnal, but it does not cover providers like Cloudflare Realtime TURN, where Synapse needs to call an API to mint short-lived credentials.
This is useful for deployments that want:
What this PR changes
New configuration
Adds optional Cloudflare-specific settings:
turn_cloudflare_enabledturn_cloudflare_key_idturn_cloudflare_api_tokenturn_cloudflare_api_token_pathturn_cloudflare_api_base_urlAdds optional broker-specific settings for federated deployments:
turn_federation_deploymentturn_broker_urlturn_broker_api_tokenturn_broker_api_token_pathAdds a
turn_modesetting to explicitly select the TURN credential source:coturn(default): use local CoTURN directly. Backward compatible — no behaviour change if unset.cf: fetch credentials from Cloudflare Realtime TURN.broker: fetch credentials from a federated TURN broker.Request flow
When
turn_mode: cfis configured:/_matrix/client/*/voip/turnServerWhen
turn_mode: brokeris configured:/_matrix/client/*/voip/turnServerusername,password,ttl,uris)When
turn_mode: coturn(default) is configured:turn_uris/turn_shared_secretorturn_username/turn_passwordflow as before.Port 53 filtering
Cloudflare documents that alternate port 53 TURN URLs may time out in browsers.
This PR filters
turn:/turns:URLs on port53before returning credentials to clients, while preserving the primary ports (3478,80,443,5349).Security / operational notes
turn_cloudflare_api_token_path, which is the recommended deployment mode.turn_broker_api_token_path, which is the recommended deployment mode for broker deployments.turn_user_lifetimecontinues to control the returned TTL and is reused for Cloudflare or broker credential requests.Documentation
This PR updates:
The docs explicitly describe:
turn_cloudflare_api_token_pathTests
Adds coverage for:
Example config
Cloudflare TURN mode
Federation broker mode
Local CoTURN only (default, backward compatible)
Reference TURN broker implementations
Two reference implementations are provided:
VPS / Docker: https://github.com/kakahu2015/synapse-turn-broker
Python + Redis, self-hosted on a VPS behind Caddy/nginx.
Cloudflare Workers: https://github.com/kakahu2015/synapse-turn-broker-worker
JavaScript + Cloudflare KV, serverless deployment on Cloudflare edge. Zero server maintenance, free tier friendly.