generic_worker.py 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516
  1. # Copyright 2016 OpenMarket Ltd
  2. # Copyright 2020 The Matrix.org Foundation C.I.C.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import logging
  16. import sys
  17. from typing import Dict, List, Optional, Tuple
  18. from matrix_common.versionstring import get_distribution_version_string
  19. from twisted.internet import address
  20. from twisted.web.resource import Resource
  21. import synapse
  22. import synapse.events
  23. from synapse.api.errors import HttpResponseException, RequestSendFailed, SynapseError
  24. from synapse.api.urls import (
  25. CLIENT_API_PREFIX,
  26. FEDERATION_PREFIX,
  27. LEGACY_MEDIA_PREFIX,
  28. MEDIA_R0_PREFIX,
  29. MEDIA_V3_PREFIX,
  30. SERVER_KEY_V2_PREFIX,
  31. )
  32. from synapse.app import _base
  33. from synapse.app._base import (
  34. handle_startup_exception,
  35. max_request_body_size,
  36. redirect_stdio_to_logs,
  37. register_start,
  38. )
  39. from synapse.config._base import ConfigError
  40. from synapse.config.homeserver import HomeServerConfig
  41. from synapse.config.logger import setup_logging
  42. from synapse.config.server import ListenerConfig
  43. from synapse.federation.transport.server import TransportLayerServer
  44. from synapse.http.server import JsonResource, OptionsResource
  45. from synapse.http.servlet import RestServlet, parse_json_object_from_request
  46. from synapse.http.site import SynapseRequest, SynapseSite
  47. from synapse.logging.context import LoggingContext
  48. from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
  49. from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
  50. from synapse.replication.slave.storage._base import BaseSlavedStore
  51. from synapse.replication.slave.storage.account_data import SlavedAccountDataStore
  52. from synapse.replication.slave.storage.appservice import SlavedApplicationServiceStore
  53. from synapse.replication.slave.storage.client_ips import SlavedClientIpStore
  54. from synapse.replication.slave.storage.deviceinbox import SlavedDeviceInboxStore
  55. from synapse.replication.slave.storage.devices import SlavedDeviceStore
  56. from synapse.replication.slave.storage.directory import DirectoryStore
  57. from synapse.replication.slave.storage.events import SlavedEventStore
  58. from synapse.replication.slave.storage.filtering import SlavedFilteringStore
  59. from synapse.replication.slave.storage.groups import SlavedGroupServerStore
  60. from synapse.replication.slave.storage.keys import SlavedKeyStore
  61. from synapse.replication.slave.storage.profile import SlavedProfileStore
  62. from synapse.replication.slave.storage.push_rule import SlavedPushRuleStore
  63. from synapse.replication.slave.storage.pushers import SlavedPusherStore
  64. from synapse.replication.slave.storage.receipts import SlavedReceiptsStore
  65. from synapse.replication.slave.storage.registration import SlavedRegistrationStore
  66. from synapse.rest.admin import register_servlets_for_media_repo
  67. from synapse.rest.client import (
  68. account_data,
  69. events,
  70. groups,
  71. initial_sync,
  72. login,
  73. presence,
  74. profile,
  75. push_rule,
  76. read_marker,
  77. receipts,
  78. room,
  79. room_keys,
  80. sendtodevice,
  81. sync,
  82. tags,
  83. user_directory,
  84. versions,
  85. voip,
  86. )
  87. from synapse.rest.client._base import client_patterns
  88. from synapse.rest.client.account import ThreepidRestServlet
  89. from synapse.rest.client.devices import DevicesRestServlet
  90. from synapse.rest.client.keys import (
  91. KeyChangesServlet,
  92. KeyQueryServlet,
  93. OneTimeKeyServlet,
  94. )
  95. from synapse.rest.client.register import (
  96. RegisterRestServlet,
  97. RegistrationTokenValidityRestServlet,
  98. )
  99. from synapse.rest.health import HealthResource
  100. from synapse.rest.key.v2 import KeyApiV2Resource
  101. from synapse.rest.synapse.client import build_synapse_client_resource_tree
  102. from synapse.rest.well_known import well_known_resource
  103. from synapse.server import HomeServer
  104. from synapse.storage.databases.main.censor_events import CensorEventsStore
  105. from synapse.storage.databases.main.client_ips import ClientIpWorkerStore
  106. from synapse.storage.databases.main.e2e_room_keys import EndToEndRoomKeyStore
  107. from synapse.storage.databases.main.lock import LockStore
  108. from synapse.storage.databases.main.media_repository import MediaRepositoryStore
  109. from synapse.storage.databases.main.metrics import ServerMetricsStore
  110. from synapse.storage.databases.main.monthly_active_users import (
  111. MonthlyActiveUsersWorkerStore,
  112. )
  113. from synapse.storage.databases.main.presence import PresenceStore
  114. from synapse.storage.databases.main.room import RoomWorkerStore
  115. from synapse.storage.databases.main.room_batch import RoomBatchStore
  116. from synapse.storage.databases.main.search import SearchStore
  117. from synapse.storage.databases.main.session import SessionStore
  118. from synapse.storage.databases.main.stats import StatsStore
  119. from synapse.storage.databases.main.transactions import TransactionWorkerStore
  120. from synapse.storage.databases.main.ui_auth import UIAuthWorkerStore
  121. from synapse.storage.databases.main.user_directory import UserDirectoryStore
  122. from synapse.types import JsonDict
  123. from synapse.util.httpresourcetree import create_resource_tree
  124. logger = logging.getLogger("synapse.app.generic_worker")
  125. class KeyUploadServlet(RestServlet):
  126. """An implementation of the `KeyUploadServlet` that responds to read only
  127. requests, but otherwise proxies through to the master instance.
  128. """
  129. PATTERNS = client_patterns("/keys/upload(/(?P<device_id>[^/]+))?$")
  130. def __init__(self, hs: HomeServer):
  131. """
  132. Args:
  133. hs: server
  134. """
  135. super().__init__()
  136. self.auth = hs.get_auth()
  137. self.store = hs.get_datastores().main
  138. self.http_client = hs.get_simple_http_client()
  139. self.main_uri = hs.config.worker.worker_main_http_uri
  140. async def on_POST(
  141. self, request: SynapseRequest, device_id: Optional[str]
  142. ) -> Tuple[int, JsonDict]:
  143. requester = await self.auth.get_user_by_req(request, allow_guest=True)
  144. user_id = requester.user.to_string()
  145. body = parse_json_object_from_request(request)
  146. if device_id is not None:
  147. # passing the device_id here is deprecated; however, we allow it
  148. # for now for compatibility with older clients.
  149. if requester.device_id is not None and device_id != requester.device_id:
  150. logger.warning(
  151. "Client uploading keys for a different device "
  152. "(logged in as %s, uploading for %s)",
  153. requester.device_id,
  154. device_id,
  155. )
  156. else:
  157. device_id = requester.device_id
  158. if device_id is None:
  159. raise SynapseError(
  160. 400, "To upload keys, you must pass device_id when authenticating"
  161. )
  162. if body:
  163. # They're actually trying to upload something, proxy to main synapse.
  164. # Proxy headers from the original request, such as the auth headers
  165. # (in case the access token is there) and the original IP /
  166. # User-Agent of the request.
  167. headers = {
  168. header: request.requestHeaders.getRawHeaders(header, [])
  169. for header in (b"Authorization", b"User-Agent")
  170. }
  171. # Add the previous hop to the X-Forwarded-For header.
  172. x_forwarded_for = request.requestHeaders.getRawHeaders(
  173. b"X-Forwarded-For", []
  174. )
  175. # we use request.client here, since we want the previous hop, not the
  176. # original client (as returned by request.getClientAddress()).
  177. if isinstance(request.client, (address.IPv4Address, address.IPv6Address)):
  178. previous_host = request.client.host.encode("ascii")
  179. # If the header exists, add to the comma-separated list of the first
  180. # instance of the header. Otherwise, generate a new header.
  181. if x_forwarded_for:
  182. x_forwarded_for = [x_forwarded_for[0] + b", " + previous_host]
  183. x_forwarded_for.extend(x_forwarded_for[1:])
  184. else:
  185. x_forwarded_for = [previous_host]
  186. headers[b"X-Forwarded-For"] = x_forwarded_for
  187. # Replicate the original X-Forwarded-Proto header. Note that
  188. # XForwardedForRequest overrides isSecure() to give us the original protocol
  189. # used by the client, as opposed to the protocol used by our upstream proxy
  190. # - which is what we want here.
  191. headers[b"X-Forwarded-Proto"] = [
  192. b"https" if request.isSecure() else b"http"
  193. ]
  194. try:
  195. result = await self.http_client.post_json_get_json(
  196. self.main_uri + request.uri.decode("ascii"), body, headers=headers
  197. )
  198. except HttpResponseException as e:
  199. raise e.to_synapse_error() from e
  200. except RequestSendFailed as e:
  201. raise SynapseError(502, "Failed to talk to master") from e
  202. return 200, result
  203. else:
  204. # Just interested in counts.
  205. result = await self.store.count_e2e_one_time_keys(user_id, device_id)
  206. return 200, {"one_time_key_counts": result}
  207. class GenericWorkerSlavedStore(
  208. # FIXME(#3714): We need to add UserDirectoryStore as we write directly
  209. # rather than going via the correct worker.
  210. UserDirectoryStore,
  211. StatsStore,
  212. UIAuthWorkerStore,
  213. EndToEndRoomKeyStore,
  214. PresenceStore,
  215. SlavedDeviceInboxStore,
  216. SlavedDeviceStore,
  217. SlavedReceiptsStore,
  218. SlavedPushRuleStore,
  219. SlavedGroupServerStore,
  220. SlavedAccountDataStore,
  221. SlavedPusherStore,
  222. CensorEventsStore,
  223. ClientIpWorkerStore,
  224. SlavedEventStore,
  225. SlavedKeyStore,
  226. RoomWorkerStore,
  227. RoomBatchStore,
  228. DirectoryStore,
  229. SlavedApplicationServiceStore,
  230. SlavedRegistrationStore,
  231. SlavedProfileStore,
  232. SlavedClientIpStore,
  233. SlavedFilteringStore,
  234. MonthlyActiveUsersWorkerStore,
  235. MediaRepositoryStore,
  236. ServerMetricsStore,
  237. SearchStore,
  238. TransactionWorkerStore,
  239. LockStore,
  240. SessionStore,
  241. BaseSlavedStore,
  242. ):
  243. # Properties that multiple storage classes define. Tell mypy what the
  244. # expected type is.
  245. server_name: str
  246. config: HomeServerConfig
  247. class GenericWorkerServer(HomeServer):
  248. DATASTORE_CLASS = GenericWorkerSlavedStore # type: ignore
  249. def _listen_http(self, listener_config: ListenerConfig) -> None:
  250. port = listener_config.port
  251. bind_addresses = listener_config.bind_addresses
  252. assert listener_config.http_options is not None
  253. site_tag = listener_config.http_options.tag
  254. if site_tag is None:
  255. site_tag = str(port)
  256. # We always include a health resource.
  257. resources: Dict[str, Resource] = {"/health": HealthResource()}
  258. for res in listener_config.http_options.resources:
  259. for name in res.names:
  260. if name == "metrics":
  261. resources[METRICS_PREFIX] = MetricsResource(RegistryProxy)
  262. elif name == "client":
  263. resource = JsonResource(self, canonical_json=False)
  264. RegisterRestServlet(self).register(resource)
  265. RegistrationTokenValidityRestServlet(self).register(resource)
  266. login.register_servlets(self, resource)
  267. ThreepidRestServlet(self).register(resource)
  268. DevicesRestServlet(self).register(resource)
  269. # Read-only
  270. KeyUploadServlet(self).register(resource)
  271. KeyQueryServlet(self).register(resource)
  272. KeyChangesServlet(self).register(resource)
  273. OneTimeKeyServlet(self).register(resource)
  274. voip.register_servlets(self, resource)
  275. push_rule.register_servlets(self, resource)
  276. versions.register_servlets(self, resource)
  277. profile.register_servlets(self, resource)
  278. sync.register_servlets(self, resource)
  279. events.register_servlets(self, resource)
  280. room.register_servlets(self, resource, is_worker=True)
  281. room.register_deprecated_servlets(self, resource)
  282. initial_sync.register_servlets(self, resource)
  283. room_keys.register_servlets(self, resource)
  284. tags.register_servlets(self, resource)
  285. account_data.register_servlets(self, resource)
  286. receipts.register_servlets(self, resource)
  287. read_marker.register_servlets(self, resource)
  288. sendtodevice.register_servlets(self, resource)
  289. user_directory.register_servlets(self, resource)
  290. presence.register_servlets(self, resource)
  291. if self.config.experimental.groups_enabled:
  292. groups.register_servlets(self, resource)
  293. resources.update({CLIENT_API_PREFIX: resource})
  294. resources.update(build_synapse_client_resource_tree(self))
  295. resources.update({"/.well-known": well_known_resource(self)})
  296. elif name == "federation":
  297. resources.update({FEDERATION_PREFIX: TransportLayerServer(self)})
  298. elif name == "media":
  299. if self.config.media.can_load_media_repo:
  300. media_repo = self.get_media_repository_resource()
  301. # We need to serve the admin servlets for media on the
  302. # worker.
  303. admin_resource = JsonResource(self, canonical_json=False)
  304. register_servlets_for_media_repo(self, admin_resource)
  305. resources.update(
  306. {
  307. MEDIA_R0_PREFIX: media_repo,
  308. MEDIA_V3_PREFIX: media_repo,
  309. LEGACY_MEDIA_PREFIX: media_repo,
  310. "/_synapse/admin": admin_resource,
  311. }
  312. )
  313. else:
  314. logger.warning(
  315. "A 'media' listener is configured but the media"
  316. " repository is disabled. Ignoring."
  317. )
  318. if name == "openid" and "federation" not in res.names:
  319. # Only load the openid resource separately if federation resource
  320. # is not specified since federation resource includes openid
  321. # resource.
  322. resources.update(
  323. {
  324. FEDERATION_PREFIX: TransportLayerServer(
  325. self, servlet_groups=["openid"]
  326. )
  327. }
  328. )
  329. if name in ["keys", "federation"]:
  330. resources[SERVER_KEY_V2_PREFIX] = KeyApiV2Resource(self)
  331. if name == "replication":
  332. resources[REPLICATION_PREFIX] = ReplicationRestResource(self)
  333. # Attach additional resources registered by modules.
  334. resources.update(self._module_web_resources)
  335. self._module_web_resources_consumed = True
  336. root_resource = create_resource_tree(resources, OptionsResource())
  337. _base.listen_tcp(
  338. bind_addresses,
  339. port,
  340. SynapseSite(
  341. "synapse.access.http.%s" % (site_tag,),
  342. site_tag,
  343. listener_config,
  344. root_resource,
  345. self.version_string,
  346. max_request_body_size=max_request_body_size(self.config),
  347. reactor=self.get_reactor(),
  348. ),
  349. reactor=self.get_reactor(),
  350. )
  351. logger.info("Synapse worker now listening on port %d", port)
  352. def start_listening(self) -> None:
  353. for listener in self.config.worker.worker_listeners:
  354. if listener.type == "http":
  355. self._listen_http(listener)
  356. elif listener.type == "manhole":
  357. _base.listen_manhole(
  358. listener.bind_addresses,
  359. listener.port,
  360. manhole_settings=self.config.server.manhole_settings,
  361. manhole_globals={"hs": self},
  362. )
  363. elif listener.type == "metrics":
  364. if not self.config.metrics.enable_metrics:
  365. logger.warning(
  366. "Metrics listener configured, but "
  367. "enable_metrics is not True!"
  368. )
  369. else:
  370. _base.listen_metrics(listener.bind_addresses, listener.port)
  371. else:
  372. logger.warning("Unsupported listener type: %s", listener.type)
  373. self.get_replication_command_handler().start_replication(self)
  374. def start(config_options: List[str]) -> None:
  375. try:
  376. config = HomeServerConfig.load_config("Synapse worker", config_options)
  377. except ConfigError as e:
  378. sys.stderr.write("\n" + str(e) + "\n")
  379. sys.exit(1)
  380. # For backwards compatibility let any of the old app names.
  381. assert config.worker.worker_app in (
  382. "synapse.app.appservice",
  383. "synapse.app.client_reader",
  384. "synapse.app.event_creator",
  385. "synapse.app.federation_reader",
  386. "synapse.app.federation_sender",
  387. "synapse.app.frontend_proxy",
  388. "synapse.app.generic_worker",
  389. "synapse.app.media_repository",
  390. "synapse.app.pusher",
  391. "synapse.app.synchrotron",
  392. "synapse.app.user_dir",
  393. )
  394. if config.worker.worker_app == "synapse.app.appservice":
  395. if config.appservice.notify_appservices:
  396. sys.stderr.write(
  397. "\nThe appservices must be disabled in the main synapse process"
  398. "\nbefore they can be run in a separate worker."
  399. "\nPlease add ``notify_appservices: false`` to the main config"
  400. "\n"
  401. )
  402. sys.exit(1)
  403. # Force the appservice to start since they will be disabled in the main config
  404. config.appservice.notify_appservices = True
  405. else:
  406. # For other worker types we force this to off.
  407. config.appservice.notify_appservices = False
  408. if config.worker.worker_app == "synapse.app.user_dir":
  409. if config.server.update_user_directory:
  410. sys.stderr.write(
  411. "\nThe update_user_directory must be disabled in the main synapse process"
  412. "\nbefore they can be run in a separate worker."
  413. "\nPlease add ``update_user_directory: false`` to the main config"
  414. "\n"
  415. )
  416. sys.exit(1)
  417. # Force the pushers to start since they will be disabled in the main config
  418. config.server.update_user_directory = True
  419. else:
  420. # For other worker types we force this to off.
  421. config.server.update_user_directory = False
  422. synapse.events.USE_FROZEN_DICTS = config.server.use_frozen_dicts
  423. synapse.util.caches.TRACK_MEMORY_USAGE = config.caches.track_memory_usage
  424. if config.server.gc_seconds:
  425. synapse.metrics.MIN_TIME_BETWEEN_GCS = config.server.gc_seconds
  426. hs = GenericWorkerServer(
  427. config.server.server_name,
  428. config=config,
  429. version_string="Synapse/" + get_distribution_version_string("matrix-synapse"),
  430. )
  431. setup_logging(hs, config, use_worker_options=True)
  432. try:
  433. hs.setup()
  434. # Ensure the replication streamer is always started in case we write to any
  435. # streams. Will no-op if no streams can be written to by this worker.
  436. hs.get_replication_streamer()
  437. except Exception as e:
  438. handle_startup_exception(e)
  439. register_start(_base.start, hs)
  440. # redirect stdio to the logs, if configured.
  441. if not hs.config.logging.no_redirect_stdio:
  442. redirect_stdio_to_logs()
  443. _base.start_worker_reactor("synapse-generic-worker", config)
  444. def main() -> None:
  445. with LoggingContext("main"):
  446. start(sys.argv[1:])
  447. if __name__ == "__main__":
  448. main()