You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

497 lines
20 KiB

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