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

Infer no_tls from presence of TLS listeners #4613

Merged
merged 7 commits into from
Feb 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions changelog.d/4613.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
There is no longer any need to specify `no_tls`: it is inferred from the absence of TLS listeners
1 change: 1 addition & 0 deletions changelog.d/4615.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
There is no longer any need to specify `no_tls`: it is inferred from the absence of TLS listeners
1 change: 1 addition & 0 deletions changelog.d/4616.misc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fail cleanly if listener config lacks a 'port'
1 change: 1 addition & 0 deletions changelog.d/4617.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
There is no longer any need to specify `no_tls`: it is inferred from the absence of TLS listeners
11 changes: 7 additions & 4 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -213,13 +213,16 @@ def refresh_certificate(hs):
Refresh the TLS certificates that Synapse is using by re-reading them from
disk and updating the TLS context factories to use them.
"""
logging.info("Loading certificate from disk...")
hs.config.read_certificate_from_disk()

if not hs.config.has_tls_listener():
# nothing else to do here
return

hs.tls_server_context_factory = context_factory.ServerContextFactory(hs.config)
logging.info("Certificate loaded.")

if hs._listening_services:
logging.info("Updating context factories...")
logger.info("Updating context factories...")
for i in hs._listening_services:
# When you listenSSL, it doesn't make an SSL port but a TCP one with
# a TLS wrapping factory around the factory you actually want to get
Expand All @@ -234,7 +237,7 @@ def refresh_certificate(hs):
False,
i.factory.wrappedFactory
)
logging.info("Context factories updated.")
logger.info("Context factories updated.")


def start(hs, listeners=None):
Expand Down
5 changes: 0 additions & 5 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,11 +90,6 @@ def _listener_http(self, config, listener_config):
tls = listener_config.get("tls", False)
site_tag = listener_config.get("tag", port)

if tls and config.no_tls:
raise ConfigError(
"Listener on port %i has TLS enabled, but no_tls is set" % (port,),
)

resources = {}
for res in listener_config["resources"]:
for name in res["names"]:
Expand Down
2 changes: 1 addition & 1 deletion synapse/config/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
from .workers import WorkerConfig


class HomeServerConfig(TlsConfig, ServerConfig, DatabaseConfig, LoggingConfig,
class HomeServerConfig(ServerConfig, TlsConfig, DatabaseConfig, LoggingConfig,
RatelimitConfig, ContentRepositoryConfig, CaptchaConfig,
VoipConfig, RegistrationConfig, MetricsConfig, ApiConfig,
AppServiceConfig, KeyConfig, SAML2Config, CasConfig,
Expand Down
26 changes: 24 additions & 2 deletions synapse/config/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -126,9 +126,22 @@ def read_config(self, config):
self.public_baseurl += '/'
self.start_pushers = config.get("start_pushers", True)

self.listeners = config.get("listeners", [])
self.listeners = []
for listener in config.get("listeners", []):
if not isinstance(listener.get("port", None), int):
raise ConfigError(
"Listener configuration is lacking a valid 'port' option"
)

if listener.setdefault("tls", False):
# no_tls is not really supported any more, but let's grandfather it in
# here.
if config.get("no_tls", False):
logger.info(
"Ignoring TLS-enabled listener on port %i due to no_tls"
)
continue

for listener in self.listeners:
bind_address = listener.pop("bind_address", None)
bind_addresses = listener.setdefault("bind_addresses", [])

Expand All @@ -140,13 +153,18 @@ def read_config(self, config):
if not bind_addresses:
bind_addresses.extend(DEFAULT_BIND_ADDRESSES)

self.listeners.append(listener)

if not self.web_client_location:
_warn_if_webclient_configured(self.listeners)

self.gc_thresholds = read_gc_thresholds(config.get("gc_thresholds", None))

bind_port = config.get("bind_port")
if bind_port:
if config.get("no_tls", False):
raise ConfigError("no_tls is incompatible with bind_port")

self.listeners = []
bind_host = config.get("bind_host", "")
gzip_responses = config.get("gzip_responses", True)
Expand Down Expand Up @@ -193,6 +211,7 @@ def read_config(self, config):
"port": manhole,
"bind_addresses": ["127.0.0.1"],
"type": "manhole",
"tls": False,
})

metrics_port = config.get("metrics_port")
Expand All @@ -218,6 +237,9 @@ def read_config(self, config):

_check_resource_config(self.listeners)

def has_tls_listener(self):
return any(l["tls"] for l in self.listeners)

def default_config(self, server_name, data_dir_path, **kwargs):
_, bind_port = parse_and_validate_server_name(server_name)
if bind_port is not None:
Expand Down
66 changes: 39 additions & 27 deletions synapse/config/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@

from synapse.config._base import Config

logger = logging.getLogger()
logger = logging.getLogger(__name__)


class TlsConfig(Config):
Expand All @@ -51,7 +51,6 @@ def read_config(self, config):
self._original_tls_fingerprints = []

self.tls_fingerprints = list(self._original_tls_fingerprints)
self.no_tls = config.get("no_tls", False)

# This config option applies to non-federation HTTP clients
# (e.g. for talking to recaptcha, identity servers, and such)
Expand Down Expand Up @@ -110,20 +109,10 @@ def read_certificate_from_disk(self):
"""
Read the certificates from disk.
"""
self.tls_certificate = self.read_tls_certificate(self.tls_certificate_file)
self.tls_certificate = self.read_tls_certificate()

# Check if it is self-signed, and issue a warning if so.
if self.tls_certificate.get_issuer() == self.tls_certificate.get_subject():
warnings.warn(
(
"Self-signed TLS certificates will not be accepted by Synapse 1.0. "
"Please either provide a valid certificate, or use Synapse's ACME "
"support to provision one."
)
)

if not self.no_tls:
self.tls_private_key = self.read_tls_private_key(self.tls_private_key_file)
if self.has_tls_listener():
self.tls_private_key = self.read_tls_private_key()

self.tls_fingerprints = list(self._original_tls_fingerprints)

Expand Down Expand Up @@ -151,6 +140,8 @@ def default_config(self, config_dir_path, server_name, **kwargs):

return (
"""\
## TLS ##

# PEM-encoded X509 certificate for TLS.
# This certificate, as of Synapse 1.0, will need to be a valid and verifiable
# certificate, signed by a recognised Certificate Authority.
Expand Down Expand Up @@ -211,13 +202,6 @@ def default_config(self, config_dir_path, server_name, **kwargs):
#
# reprovision_threshold: 30

# If your server runs behind a reverse-proxy which terminates TLS connections
# (for both client and federation connections), it may be useful to disable
# All TLS support for incoming connections. Setting no_tls to True will
# do so (and avoid the need to give synapse a TLS private key).
#
# no_tls: True

# List of allowed TLS fingerprints for this server to publish along
# with the signing keys for this server. Other matrix servers that
# make HTTPS requests to this server will check that the TLS
Expand Down Expand Up @@ -250,10 +234,38 @@ def default_config(self, config_dir_path, server_name, **kwargs):
% locals()
)

def read_tls_certificate(self, cert_path):
cert_pem = self.read_file(cert_path, "tls_certificate")
return crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)
def read_tls_certificate(self):
"""Reads the TLS certificate from the configured file, and returns it

def read_tls_private_key(self, private_key_path):
private_key_pem = self.read_file(private_key_path, "tls_private_key")
Also checks if it is self-signed, and warns if so

Returns:
OpenSSL.crypto.X509: the certificate
"""
cert_path = self.tls_certificate_file
logger.info("Loading TLS certificate from %s", cert_path)
cert_pem = self.read_file(cert_path, "tls_certificate_path")
cert = crypto.load_certificate(crypto.FILETYPE_PEM, cert_pem)

# Check if it is self-signed, and issue a warning if so.
if cert.get_issuer() == cert.get_subject():
warnings.warn(
(
"Self-signed TLS certificates will not be accepted by Synapse 1.0. "
"Please either provide a valid certificate, or use Synapse's ACME "
"support to provision one."
)
)

return cert

def read_tls_private_key(self):
"""Reads the TLS private key from the configured file, and returns it

Returns:
OpenSSL.crypto.PKey: the private key
"""
private_key_path = self.tls_private_key_file
logger.info("Loading TLS key from %s", private_key_path)
private_key_pem = self.read_file(private_key_path, "tls_private_key_path")
return crypto.load_privatekey(crypto.FILETYPE_PEM, private_key_pem)
4 changes: 1 addition & 3 deletions synapse/crypto/context_factory.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,7 @@ def configure_context(context, config):
logger.exception("Failed to enable elliptic curve for TLS")
context.set_options(SSL.OP_NO_SSLv2 | SSL.OP_NO_SSLv3)
context.use_certificate_chain_file(config.tls_certificate_file)

if not config.no_tls:
context.use_privatekey(config.tls_private_key)
context.use_privatekey(config.tls_private_key)

# https://hynek.me/articles/hardening-your-web-servers-ssl-ciphers/
context.set_cipher_list(
Expand Down
8 changes: 6 additions & 2 deletions tests/config/test_tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,11 @@
from tests.unittest import TestCase


class TestConfig(TlsConfig):
def has_tls_listener(self):
return False


class TLSConfigTests(TestCase):

def test_warn_self_signed(self):
Expand Down Expand Up @@ -55,11 +60,10 @@ def test_warn_self_signed(self):

config = {
"tls_certificate_path": os.path.join(config_dir, "cert.pem"),
"no_tls": True,
"tls_fingerprints": []
}

t = TlsConfig()
t = TestConfig()
t.read_config(config)
t.read_certificate_from_disk()

Expand Down