Support using SSL on worker endpoints. (#14128)

* 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>
This commit is contained in:
Tuomas Ojamies 2022-11-15 13:55:00 +01:00 committed by GitHub
parent 634359b083
commit b5ab2c428a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 100 additions and 53 deletions

1
changelog.d/14128.misc Normal file
View File

@ -0,0 +1 @@
Add TLS support for generic worker endpoints.

View File

@ -3893,6 +3893,26 @@ Example configuration:
worker_replication_http_port: 9093 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` ### `worker_listeners`
A worker can handle HTTP requests. To do so, a `worker_listeners` option A worker can handle HTTP requests. To do so, a `worker_listeners` option

View File

@ -47,6 +47,7 @@ from twisted.internet.tcp import Port
from twisted.logger import LoggingFile, LogLevel from twisted.logger import LoggingFile, LogLevel
from twisted.protocols.tls import TLSMemoryBIOFactory from twisted.protocols.tls import TLSMemoryBIOFactory
from twisted.python.threadpool import ThreadPool from twisted.python.threadpool import ThreadPool
from twisted.web.resource import Resource
import synapse.util.caches import synapse.util.caches
from synapse.api.constants import MAX_PDU_SIZE from synapse.api.constants import MAX_PDU_SIZE
@ -55,12 +56,13 @@ from synapse.app.phone_stats_home import start_phone_stats_home
from synapse.config import ConfigError from synapse.config import ConfigError
from synapse.config._base import format_config_error from synapse.config._base import format_config_error
from synapse.config.homeserver import HomeServerConfig 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.crypto import context_factory
from synapse.events.presence_router import load_legacy_presence_router from synapse.events.presence_router import load_legacy_presence_router
from synapse.events.spamcheck import load_legacy_spam_checkers from synapse.events.spamcheck import load_legacy_spam_checkers
from synapse.events.third_party_rules import load_legacy_third_party_event_rules 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.handlers.auth import load_legacy_password_auth_providers
from synapse.http.site import SynapseSite
from synapse.logging.context import PreserveLoggingContext from synapse.logging.context import PreserveLoggingContext
from synapse.logging.opentracing import init_tracer from synapse.logging.opentracing import init_tracer
from synapse.metrics import install_gc_manager, register_threadpool from synapse.metrics import install_gc_manager, register_threadpool
@ -357,6 +359,55 @@ def listen_tcp(
return r # type: ignore[return-value] 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( def listen_ssl(
bind_addresses: Collection[str], bind_addresses: Collection[str],
port: int, port: int,

View File

@ -44,7 +44,7 @@ from synapse.config.server import ListenerConfig
from synapse.federation.transport.server import TransportLayerServer from synapse.federation.transport.server import TransportLayerServer
from synapse.http.server import JsonResource, OptionsResource from synapse.http.server import JsonResource, OptionsResource
from synapse.http.servlet import RestServlet, parse_json_object_from_request 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.logging.context import LoggingContext
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
@ -288,15 +288,9 @@ class GenericWorkerServer(HomeServer):
DATASTORE_CLASS = GenericWorkerSlavedStore # type: ignore DATASTORE_CLASS = GenericWorkerSlavedStore # type: ignore
def _listen_http(self, listener_config: ListenerConfig) -> None: 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 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. # We always include a health resource.
resources: Dict[str, Resource] = {"/health": HealthResource()} resources: Dict[str, Resource] = {"/health": HealthResource()}
@ -395,23 +389,15 @@ class GenericWorkerServer(HomeServer):
root_resource = create_resource_tree(resources, OptionsResource()) root_resource = create_resource_tree(resources, OptionsResource())
_base.listen_tcp( _base.listen_http(
bind_addresses, listener_config,
port, root_resource,
SynapseSite( self.version_string,
"synapse.access.http.%s" % (site_tag,), max_request_body_size(self.config),
site_tag, self.tls_server_context_factory,
listener_config,
root_resource,
self.version_string,
max_request_body_size=max_request_body_size(self.config),
reactor=self.get_reactor(),
),
reactor=self.get_reactor(), reactor=self.get_reactor(),
) )
logger.info("Synapse worker now listening on port %d", port)
def start_listening(self) -> None: def start_listening(self) -> None:
for listener in self.config.worker.worker_listeners: for listener in self.config.worker.worker_listeners:
if listener.type == "http": if listener.type == "http":

View File

@ -37,8 +37,7 @@ from synapse.api.urls import (
from synapse.app import _base from synapse.app import _base
from synapse.app._base import ( from synapse.app._base import (
handle_startup_exception, handle_startup_exception,
listen_ssl, listen_http,
listen_tcp,
max_request_body_size, max_request_body_size,
redirect_stdio_to_logs, redirect_stdio_to_logs,
register_start, register_start,
@ -53,7 +52,6 @@ from synapse.http.server import (
RootOptionsRedirectResource, RootOptionsRedirectResource,
StaticResource, StaticResource,
) )
from synapse.http.site import SynapseSite
from synapse.logging.context import LoggingContext from synapse.logging.context import LoggingContext
from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy from synapse.metrics import METRICS_PREFIX, MetricsResource, RegistryProxy
from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource from synapse.replication.http import REPLICATION_PREFIX, ReplicationRestResource
@ -83,8 +81,6 @@ class SynapseHomeServer(HomeServer):
self, config: HomeServerConfig, listener_config: ListenerConfig self, config: HomeServerConfig, listener_config: ListenerConfig
) -> Iterable[Port]: ) -> Iterable[Port]:
port = listener_config.port port = listener_config.port
bind_addresses = listener_config.bind_addresses
tls = listener_config.tls
# Must exist since this is an HTTP listener. # Must exist since this is an HTTP listener.
assert listener_config.http_options is not None assert listener_config.http_options is not None
site_tag = listener_config.http_options.tag site_tag = listener_config.http_options.tag
@ -140,37 +136,15 @@ class SynapseHomeServer(HomeServer):
else: else:
root_resource = OptionsResource() root_resource = OptionsResource()
site = SynapseSite( ports = listen_http(
"synapse.access.%s.%s" % ("https" if tls else "http", site_tag),
site_tag,
listener_config, listener_config,
create_resource_tree(resources, root_resource), create_resource_tree(resources, root_resource),
self.version_string, 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(), 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 return ports
def _configure_named_resource( def _configure_named_resource(

View File

@ -67,6 +67,7 @@ class InstanceLocationConfig:
host: str host: str
port: int port: int
tls: bool = False
@attr.s @attr.s
@ -149,6 +150,12 @@ class WorkerConfig(Config):
# The port on the main synapse for HTTP replication endpoint # The port on the main synapse for HTTP replication endpoint
self.worker_replication_http_port = config.get("worker_replication_http_port") 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. # The shared secret used for authentication when connecting to the main synapse.
self.worker_replication_secret = config.get("worker_replication_secret", None) self.worker_replication_secret = config.get("worker_replication_secret", None)

View File

@ -184,8 +184,10 @@ class ReplicationEndpoint(metaclass=abc.ABCMeta):
client = hs.get_simple_http_client() client = hs.get_simple_http_client()
local_instance_name = hs.get_instance_name() 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_host = hs.config.worker.worker_replication_host
master_port = hs.config.worker.worker_replication_http_port 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 instance_map = hs.config.worker.instance_map
@ -205,9 +207,11 @@ class ReplicationEndpoint(metaclass=abc.ABCMeta):
if instance_name == "master": if instance_name == "master":
host = master_host host = master_host
port = master_port port = master_port
tls = master_tls
elif instance_name in instance_map: elif instance_name in instance_map:
host = instance_map[instance_name].host host = instance_map[instance_name].host
port = instance_map[instance_name].port port = instance_map[instance_name].port
tls = instance_map[instance_name].tls
else: else:
raise Exception( raise Exception(
"Instance %r not in 'instance_map' config" % (instance_name,) "Instance %r not in 'instance_map' config" % (instance_name,)
@ -238,7 +242,11 @@ class ReplicationEndpoint(metaclass=abc.ABCMeta):
"Unknown METHOD on %s replication endpoint" % (cls.NAME,) "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, host,
port, port,
cls.NAME, cls.NAME,