From 2611433b70fc30c436f6b9b950a3bcc533b3df5b Mon Sep 17 00:00:00 2001 From: Roel ter Maat Date: Thu, 11 May 2023 14:02:51 +0200 Subject: [PATCH] Add redis SSL configuration options (#15312) * Add SSL options to redis config * fix lint issues * Add documentation and changelog file * add missing . at the end of the changelog * Move client context factory to new file * Rename ssl to tls and fix typo * fix lint issues * Added when redis attributes were added --- changelog.d/15312.feature | 1 + contrib/docker_compose_workers/README.md | 4 +++ .../configuration/config_documentation.md | 11 ++++++ synapse/config/redis.py | 6 ++++ synapse/replication/tcp/context.py | 34 +++++++++++++++++++ synapse/replication/tcp/handler.py | 29 ++++++++++++---- synapse/replication/tcp/redis.py | 27 +++++++++++---- 7 files changed, 98 insertions(+), 14 deletions(-) create mode 100644 changelog.d/15312.feature create mode 100644 synapse/replication/tcp/context.py diff --git a/changelog.d/15312.feature b/changelog.d/15312.feature new file mode 100644 index 000000000..e4e972cfe --- /dev/null +++ b/changelog.d/15312.feature @@ -0,0 +1 @@ +Add redis TLS configuration options. \ No newline at end of file diff --git a/contrib/docker_compose_workers/README.md b/contrib/docker_compose_workers/README.md index d3cdfe561..ebb225fba 100644 --- a/contrib/docker_compose_workers/README.md +++ b/contrib/docker_compose_workers/README.md @@ -70,6 +70,10 @@ redis: port: 6379 # dbid: # password: + # use_tls: True + # certificate_file: + # private_key_file: + # ca_file: ``` This assumes that your Redis service is called `redis` in your Docker Compose file. diff --git a/docs/usage/configuration/config_documentation.md b/docs/usage/configuration/config_documentation.md index dc965b411..93b132b6e 100644 --- a/docs/usage/configuration/config_documentation.md +++ b/docs/usage/configuration/config_documentation.md @@ -3981,9 +3981,16 @@ This setting has the following sub-options: localhost and 6379 * `password`: Optional password if configured on the Redis instance. * `dbid`: Optional redis dbid if needs to connect to specific redis logical db. +* `use_tls`: Whether to use tls connection. Defaults to false. +* `certificate_file`: Optional path to the certificate file +* `private_key_file`: Optional path to the private key file +* `ca_file`: Optional path to the CA certificate file. Use this one or: +* `ca_path`: Optional path to the folder containing the CA certificate file _Added in Synapse 1.78.0._ + _Changed in Synapse 1.84.0: Added use\_tls, certificate\_file, private\_key\_file, ca\_file and ca\_path attributes_ + Example configuration: ```yaml redis: @@ -3992,6 +3999,10 @@ redis: port: 6379 password: dbid: + #use_tls: True + #certificate_file: + #private_key_file: + #ca_file: ``` --- ## Individual worker configuration diff --git a/synapse/config/redis.py b/synapse/config/redis.py index e6a75be43..636cb450b 100644 --- a/synapse/config/redis.py +++ b/synapse/config/redis.py @@ -35,3 +35,9 @@ class RedisConfig(Config): self.redis_port = redis_config.get("port", 6379) self.redis_dbid = redis_config.get("dbid", None) self.redis_password = redis_config.get("password") + + self.redis_use_tls = redis_config.get("use_tls", False) + self.redis_certificate = redis_config.get("certificate_file", None) + self.redis_private_key = redis_config.get("private_key_file", None) + self.redis_ca_file = redis_config.get("ca_file", None) + self.redis_ca_path = redis_config.get("ca_path", None) diff --git a/synapse/replication/tcp/context.py b/synapse/replication/tcp/context.py new file mode 100644 index 000000000..4688b2200 --- /dev/null +++ b/synapse/replication/tcp/context.py @@ -0,0 +1,34 @@ +# Copyright 2023 The Matrix.org Foundation C.I.C. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from OpenSSL.SSL import Context +from twisted.internet import ssl + +from synapse.config.redis import RedisConfig + + +class ClientContextFactory(ssl.ClientContextFactory): + def __init__(self, redis_config: RedisConfig): + self.redis_config = redis_config + + def getContext(self) -> Context: + ctx = super().getContext() + if self.redis_config.redis_certificate: + ctx.use_certificate_file(self.redis_config.redis_certificate) + if self.redis_config.redis_private_key: + ctx.use_privatekey_file(self.redis_config.redis_private_key) + if self.redis_config.redis_ca_file: + ctx.load_verify_locations(cafile=self.redis_config.redis_ca_file) + elif self.redis_config.redis_ca_path: + ctx.load_verify_locations(capath=self.redis_config.redis_ca_path) + return ctx diff --git a/synapse/replication/tcp/handler.py b/synapse/replication/tcp/handler.py index 2290b3e6f..233ad61d4 100644 --- a/synapse/replication/tcp/handler.py +++ b/synapse/replication/tcp/handler.py @@ -46,6 +46,7 @@ from synapse.replication.tcp.commands import ( UserIpCommand, UserSyncCommand, ) +from synapse.replication.tcp.context import ClientContextFactory from synapse.replication.tcp.protocol import IReplicationConnection from synapse.replication.tcp.streams import ( STREAMS_MAP, @@ -348,13 +349,27 @@ class ReplicationCommandHandler: outbound_redis_connection, channel_names=self._channels_to_subscribe_to, ) - hs.get_reactor().connectTCP( - hs.config.redis.redis_host, - hs.config.redis.redis_port, - self._factory, - timeout=30, - bindAddress=None, - ) + + reactor = hs.get_reactor() + redis_config = hs.config.redis + if hs.config.redis.redis_use_tls: + ssl_context_factory = ClientContextFactory(hs.config.redis) + reactor.connectSSL( + redis_config.redis_host, + redis_config.redis_port, + self._factory, + ssl_context_factory, + timeout=30, + bindAddress=None, + ) + else: + reactor.connectTCP( + redis_config.redis_host, + redis_config.redis_port, + self._factory, + timeout=30, + bindAddress=None, + ) def get_streams(self) -> Dict[str, Stream]: """Get a map from stream name to all streams.""" diff --git a/synapse/replication/tcp/redis.py b/synapse/replication/tcp/redis.py index dfc061eb5..c8f4bf8b2 100644 --- a/synapse/replication/tcp/redis.py +++ b/synapse/replication/tcp/redis.py @@ -35,6 +35,7 @@ from synapse.replication.tcp.commands import ( ReplicateCommand, parse_command_from_line, ) +from synapse.replication.tcp.context import ClientContextFactory from synapse.replication.tcp.protocol import ( IReplicationConnection, tcp_inbound_commands_counter, @@ -386,12 +387,24 @@ def lazyConnection( factory.continueTrying = reconnect reactor = hs.get_reactor() - reactor.connectTCP( - host, - port, - factory, - timeout=30, - bindAddress=None, - ) + + if hs.config.redis.redis_use_tls: + ssl_context_factory = ClientContextFactory(hs.config.redis) + reactor.connectSSL( + host, + port, + factory, + ssl_context_factory, + timeout=30, + bindAddress=None, + ) + else: + reactor.connectTCP( + host, + port, + factory, + timeout=30, + bindAddress=None, + ) return factory.handler