Skip to content
This repository has been archived by the owner on Apr 26, 2024. It is now read-only.

Commit

Permalink
Support using SSL on worker endpoints. (#14128)
Browse files Browse the repository at this point in the history
* Fix missing SSL support in worker endpoints.

* Add changelog

* SSL for Replication endpoint

* Remove unit test change

* Refactor listener creation to reduce duplicated code

* Fix the logger message

* Update synapse/app/_base.py

Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>

* Update synapse/app/_base.py

Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>

* Update synapse/app/_base.py

Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>

* Add config documentation for new TLS option

Co-authored-by: Tuomas Ojamies <tojamies@palantir.com>
Co-authored-by: Patrick Cloke <clokep@users.noreply.github.com>
Co-authored-by: Olivier Wilkinson (reivilibre) <oliverw@matrix.org>
  • Loading branch information
4 people authored Nov 15, 2022
1 parent 634359b commit b5ab2c4
Show file tree
Hide file tree
Showing 7 changed files with 100 additions and 53 deletions.
1 change: 1 addition & 0 deletions changelog.d/14128.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Add TLS support for generic worker endpoints.
20 changes: 20 additions & 0 deletions docs/usage/configuration/config_documentation.md
Original file line number Diff line number Diff line change
Expand Up @@ -3893,6 +3893,26 @@ Example configuration:
worker_replication_http_port: 9093
```
---
### `worker_replication_http_tls`

Whether TLS should be used for talking to the HTTP replication port on the main
Synapse process.
The main Synapse process defines this with the `tls` option on its [listener](#listeners) that
has the `replication` resource enabled.

**Please note:** by default, it is not safe to expose replication ports to the
public Internet, even with TLS enabled.
See [`worker_replication_secret`](#worker_replication_secret).

Defaults to `false`.

*Added in Synapse 1.72.0.*

Example configuration:
```yaml
worker_replication_http_tls: true
```
---
### `worker_listeners`

A worker can handle HTTP requests. To do so, a `worker_listeners` option
Expand Down
53 changes: 52 additions & 1 deletion synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
from twisted.logger import LoggingFile, LogLevel
from twisted.protocols.tls import TLSMemoryBIOFactory
from twisted.python.threadpool import ThreadPool
from twisted.web.resource import Resource

import synapse.util.caches
from synapse.api.constants import MAX_PDU_SIZE
Expand All @@ -55,12 +56,13 @@
from synapse.config import ConfigError
from synapse.config._base import format_config_error
from synapse.config.homeserver import HomeServerConfig
from synapse.config.server import ManholeConfig
from synapse.config.server import ListenerConfig, ManholeConfig
from synapse.crypto import context_factory
from synapse.events.presence_router import load_legacy_presence_router
from synapse.events.spamcheck import load_legacy_spam_checkers
from synapse.events.third_party_rules import load_legacy_third_party_event_rules
from synapse.handlers.auth import load_legacy_password_auth_providers
from synapse.http.site import SynapseSite
from synapse.logging.context import PreserveLoggingContext
from synapse.logging.opentracing import init_tracer
from synapse.metrics import install_gc_manager, register_threadpool
Expand Down Expand Up @@ -357,6 +359,55 @@ def listen_tcp(
return r # type: ignore[return-value]


def listen_http(
listener_config: ListenerConfig,
root_resource: Resource,
version_string: str,
max_request_body_size: int,
context_factory: IOpenSSLContextFactory,
reactor: IReactorSSL = reactor,
) -> List[Port]:
port = listener_config.port
bind_addresses = listener_config.bind_addresses
tls = listener_config.tls

assert listener_config.http_options is not None

site_tag = listener_config.http_options.tag
if site_tag is None:
site_tag = str(port)

site = SynapseSite(
"synapse.access.%s.%s" % ("https" if tls else "http", site_tag),
site_tag,
listener_config,
root_resource,
version_string,
max_request_body_size=max_request_body_size,
reactor=reactor,
)
if tls:
# refresh_certificate should have been called before this.
assert context_factory is not None
ports = listen_ssl(
bind_addresses,
port,
site,
context_factory,
reactor=reactor,
)
logger.info("Synapse now listening on TCP port %d (TLS)", port)
else:
ports = listen_tcp(
bind_addresses,
port,
site,
reactor=reactor,
)
logger.info("Synapse now listening on TCP port %d", port)
return ports


def listen_ssl(
bind_addresses: Collection[str],
port: int,
Expand Down
28 changes: 7 additions & 21 deletions synapse/app/generic_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
from synapse.federation.transport.server import TransportLayerServer
from synapse.http.server import JsonResource, OptionsResource
from synapse.http.servlet import RestServlet, parse_json_object_from_request
from synapse.http.site import SynapseRequest, SynapseSite
from synapse.http.site import SynapseRequest
from synapse.logging.context import LoggingContext
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
Expand Down Expand Up @@ -288,15 +288,9 @@ class GenericWorkerServer(HomeServer):
DATASTORE_CLASS = GenericWorkerSlavedStore # type: ignore

def _listen_http(self, listener_config: ListenerConfig) -> None:
port = listener_config.port
bind_addresses = listener_config.bind_addresses

assert listener_config.http_options is not None

site_tag = listener_config.http_options.tag
if site_tag is None:
site_tag = str(port)

# We always include a health resource.
resources: Dict[str, Resource] = {"/health": HealthResource()}

Expand Down Expand Up @@ -395,23 +389,15 @@ def _listen_http(self, listener_config: ListenerConfig) -> None:

root_resource = create_resource_tree(resources, OptionsResource())

_base.listen_tcp(
bind_addresses,
port,
SynapseSite(
"synapse.access.http.%s" % (site_tag,),
site_tag,
listener_config,
root_resource,
self.version_string,
max_request_body_size=max_request_body_size(self.config),
reactor=self.get_reactor(),
),
_base.listen_http(
listener_config,
root_resource,
self.version_string,
max_request_body_size(self.config),
self.tls_server_context_factory,
reactor=self.get_reactor(),
)

logger.info("Synapse worker now listening on port %d", port)

def start_listening(self) -> None:
for listener in self.config.worker.worker_listeners:
if listener.type == "http":
Expand Down
34 changes: 4 additions & 30 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
from synapse.app import _base
from synapse.app._base import (
handle_startup_exception,
listen_ssl,
listen_tcp,
listen_http,
max_request_body_size,
redirect_stdio_to_logs,
register_start,
Expand All @@ -53,7 +52,6 @@
RootOptionsRedirectResource,
StaticResource,
)
from synapse.http.site import SynapseSite
from synapse.logging.context import LoggingContext
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
Expand Down Expand Up @@ -83,8 +81,6 @@ def _listener_http(
self, config: HomeServerConfig, listener_config: ListenerConfig
) -> Iterable[Port]:
port = listener_config.port
bind_addresses = listener_config.bind_addresses
tls = listener_config.tls
# Must exist since this is an HTTP listener.
assert listener_config.http_options is not None
site_tag = listener_config.http_options.tag
Expand Down Expand Up @@ -140,37 +136,15 @@ def _listener_http(
else:
root_resource = OptionsResource()

site = SynapseSite(
"synapse.access.%s.%s" % ("https" if tls else "http", site_tag),
site_tag,
ports = listen_http(
listener_config,
create_resource_tree(resources, root_resource),
self.version_string,
max_request_body_size=max_request_body_size(self.config),
max_request_body_size(self.config),
self.tls_server_context_factory,
reactor=self.get_reactor(),
)

if tls:
# refresh_certificate should have been called before this.
assert self.tls_server_context_factory is not None
ports = listen_ssl(
bind_addresses,
port,
site,
self.tls_server_context_factory,
reactor=self.get_reactor(),
)
logger.info("Synapse now listening on TCP port %d (TLS)", port)

else:
ports = listen_tcp(
bind_addresses,
port,
site,
reactor=self.get_reactor(),
)
logger.info("Synapse now listening on TCP port %d", port)

return ports

def _configure_named_resource(
Expand Down
7 changes: 7 additions & 0 deletions synapse/config/workers.py
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ class InstanceLocationConfig:

host: str
port: int
tls: bool = False


@attr.s
Expand Down Expand Up @@ -149,6 +150,12 @@ def read_config(self, config: JsonDict, **kwargs: Any) -> None:
# The port on the main synapse for HTTP replication endpoint
self.worker_replication_http_port = config.get("worker_replication_http_port")

# The tls mode on the main synapse for HTTP replication endpoint.
# For backward compatibility this defaults to False.
self.worker_replication_http_tls = config.get(
"worker_replication_http_tls", False
)

# The shared secret used for authentication when connecting to the main synapse.
self.worker_replication_secret = config.get("worker_replication_secret", None)

Expand Down
10 changes: 9 additions & 1 deletion synapse/replication/http/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,8 +184,10 @@ def make_client(cls, hs: "HomeServer") -> Callable:
client = hs.get_simple_http_client()
local_instance_name = hs.get_instance_name()

# The value of these option should match the replication listener settings
master_host = hs.config.worker.worker_replication_host
master_port = hs.config.worker.worker_replication_http_port
master_tls = hs.config.worker.worker_replication_http_tls

instance_map = hs.config.worker.instance_map

Expand All @@ -205,9 +207,11 @@ async def send_request(*, instance_name: str = "master", **kwargs: Any) -> Any:
if instance_name == "master":
host = master_host
port = master_port
tls = master_tls
elif instance_name in instance_map:
host = instance_map[instance_name].host
port = instance_map[instance_name].port
tls = instance_map[instance_name].tls
else:
raise Exception(
"Instance %r not in 'instance_map' config" % (instance_name,)
Expand Down Expand Up @@ -238,7 +242,11 @@ async def send_request(*, instance_name: str = "master", **kwargs: Any) -> Any:
"Unknown METHOD on %s replication endpoint" % (cls.NAME,)
)

uri = "http://%s:%s/_synapse/replication/%s/%s" % (
# Here the protocol is hard coded to be http by default or https in case the replication
# port is set to have tls true.
scheme = "https" if tls else "http"
uri = "%s://%s:%s/_synapse/replication/%s/%s" % (
scheme,
host,
port,
cls.NAME,
Expand Down

0 comments on commit b5ab2c4

Please sign in to comment.