generic_worker.py 20 KB

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