Add whoami-based sync worker routing for user-level sticky sessions

This adds a new routing mechanism for sync workers that resolves access tokens
to usernames via Synapse's whoami endpoint, enabling true user-level sticky
routing regardless of which device or token is used.

Previously, sticky routing relied on parsing the username from native Synapse
tokens (`syt_<base64 username>_...`), which only works with native Synapse auth
and provides device-level stickiness at best. This new approach works with any
auth system (native Synapse, MAS, etc.) because Synapse handles token validation
internally.

Implementation uses nginx's auth_request module with an njs script because:
- The whoami lookup requires an async HTTP subrequest (ngx.fetch)
- js_set handlers must return synchronously and don't support async operations
- auth_request allows the async lookup to complete, then captures the result
  via response headers into nginx variables

The njs script:
- Extracts access tokens from Authorization header or query parameter
- Calls Synapse's whoami endpoint to resolve token -> username
- Caches results in a shared memory zone to minimize latency
- Returns the username via a `X-User-Identifier` header

The username is then used by nginx's upstream hash directive for consistent
worker selection. This leverages nginx's built-in health checking and failover.
This commit is contained in:
Slavi Pantaleev
2026-02-04 03:14:47 +02:00
parent 81f815d19b
commit 5cc69ca7eb
6 changed files with 368 additions and 13 deletions

View File

@@ -8,6 +8,12 @@
# - various temp paths are changed to `/tmp`, so that a non-root user can write to them
# - the `user` directive was removed, as we don't want nginx to switch users
# load_module directives must be first or nginx will choke with:
# > [emerg] "load_module" directive is specified too late.
{% if matrix_synapse_reverse_proxy_companion_njs_enabled %}
load_module modules/ngx_http_js_module.so;
{% endif %}
worker_processes {{ matrix_synapse_reverse_proxy_companion_worker_processes }};
error_log /var/log/nginx/error.log warn;
pid /tmp/nginx.pid;
@@ -22,7 +28,6 @@ events {
{% endfor %}
}
http {
proxy_temp_path /tmp/proxy_temp;
client_body_temp_path /tmp/client_temp;
@@ -33,6 +38,16 @@ http {
include /etc/nginx/mime.types;
default_type application/octet-stream;
{% if matrix_synapse_reverse_proxy_companion_njs_enabled %}
js_path /njs/;
{% endif %}
{% if matrix_synapse_reverse_proxy_companion_whoami_sync_worker_router_enabled %}
# njs module for whoami-based sync worker routing
js_import whoami_sync_worker_router from whoami_sync_worker_router.js;
js_shared_dict_zone zone=whoami_sync_worker_router_cache:{{ matrix_synapse_reverse_proxy_companion_whoami_sync_worker_router_cache_size_mb }}m;
{% endif %}
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';