diff --git a/synapse/config/_base.py b/synapse/config/_base.py
index 5858fb92b4..5aec43b702 100644
--- a/synapse/config/_base.py
+++ b/synapse/config/_base.py
@@ -257,7 +257,7 @@ class Config(object):
"--keys-directory",
metavar="DIRECTORY",
help="Used with 'generate-*' options to specify where files such as"
- " certs and signing keys should be stored in, unless explicitly"
+ " signing keys should be stored, unless explicitly"
" specified in the config.",
)
config_parser.add_argument(
@@ -313,16 +313,11 @@ class Config(object):
print(
(
"A config file has been generated in %r for server name"
- " %r with corresponding SSL keys and self-signed"
- " certificates. Please review this file and customise it"
+ " %r. Please review this file and customise it"
" to your needs."
)
% (config_path, server_name)
)
- print(
- "If this server name is incorrect, you will need to"
- " regenerate the SSL certificates"
- )
return
else:
print(
diff --git a/synapse/config/api.py b/synapse/config/api.py
index 403d96ba76..9f25bbc5cb 100644
--- a/synapse/config/api.py
+++ b/synapse/config/api.py
@@ -24,6 +24,7 @@ class ApiConfig(Config):
EventTypes.JoinRules,
EventTypes.CanonicalAlias,
EventTypes.RoomAvatar,
+ EventTypes.RoomEncryption,
EventTypes.Name,
])
@@ -36,5 +37,6 @@ class ApiConfig(Config):
- "{JoinRules}"
- "{CanonicalAlias}"
- "{RoomAvatar}"
+ - "{RoomEncryption}"
- "{Name}"
""".format(**vars(EventTypes))
diff --git a/synapse/config/homeserver.py b/synapse/config/homeserver.py
index 5aad062c36..727fdc54d8 100644
--- a/synapse/config/homeserver.py
+++ b/synapse/config/homeserver.py
@@ -42,7 +42,7 @@ from .voip import VoipConfig
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,
diff --git a/synapse/config/logger.py b/synapse/config/logger.py
index a795e39b1a..4b938053fb 100644
--- a/synapse/config/logger.py
+++ b/synapse/config/logger.py
@@ -15,7 +15,6 @@
import logging
import logging.config
import os
-import signal
import sys
from string import Template
@@ -24,6 +23,7 @@ import yaml
from twisted.logger import STDLibLogObserver, globalLogBeginner
import synapse
+from synapse.app import _base as appbase
from synapse.util.logcontext import LoggingContextFilter
from synapse.util.versionstring import get_version_string
@@ -127,7 +127,7 @@ class LoggingConfig(Config):
)
-def setup_logging(config, use_worker_options=False, register_sighup=None):
+def setup_logging(config, use_worker_options=False):
""" Set up python logging
Args:
@@ -140,12 +140,6 @@ def setup_logging(config, use_worker_options=False, register_sighup=None):
register_sighup (func | None): Function to call to register a
sighup handler.
"""
- if not register_sighup:
- if getattr(signal, "SIGHUP"):
- register_sighup = lambda x: signal.signal(signal.SIGHUP, x)
- else:
- register_sighup = lambda x: None
-
log_config = (config.worker_log_config if use_worker_options
else config.log_config)
log_file = (config.worker_log_file if use_worker_options
@@ -187,7 +181,7 @@ def setup_logging(config, use_worker_options=False, register_sighup=None):
else:
handler = logging.StreamHandler()
- def sighup(signum, stack):
+ def sighup(*args):
pass
handler.setFormatter(formatter)
@@ -200,14 +194,14 @@ def setup_logging(config, use_worker_options=False, register_sighup=None):
with open(log_config, 'r') as f:
logging.config.dictConfig(yaml.load(f))
- def sighup(signum, stack):
+ def sighup(*args):
# it might be better to use a file watcher or something for this.
load_log_config()
logging.info("Reloaded log config from %s due to SIGHUP", log_config)
load_log_config()
- register_sighup(sighup)
+ appbase.register_sighup(sighup)
# make sure that the first thing we log is a thing we can grep backwards
# for
diff --git a/synapse/config/server.py b/synapse/config/server.py
index 268a43ff00..93a30e4cfa 100644
--- a/synapse/config/server.py
+++ b/synapse/config/server.py
@@ -24,6 +24,14 @@ from ._base import Config, ConfigError
logger = logging.Logger(__name__)
+# by default, we attempt to listen on both '::' *and* '0.0.0.0' because some OSes
+# (Windows, macOS, other BSD/Linux where net.ipv6.bindv6only is set) will only listen
+# on IPv6 when '::' is set.
+#
+# We later check for errors when binding to 0.0.0.0 and ignore them if :: is also in
+# in the list.
+DEFAULT_BIND_ADDRESSES = ['::', '0.0.0.0']
+
class ServerConfig(Config):
@@ -118,16 +126,38 @@ class ServerConfig(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", [])
+ # if bind_address was specified, add it to the list of addresses
if bind_address:
bind_addresses.append(bind_address)
- elif not bind_addresses:
- bind_addresses.append('')
+
+ # if we still have an empty list of addresses, use the default list
+ if not bind_addresses:
+ if listener['type'] == 'metrics':
+ # the metrics listener doesn't support IPv6
+ bind_addresses.append('0.0.0.0')
+ else:
+ bind_addresses.extend(DEFAULT_BIND_ADDRESSES)
+
+ self.listeners.append(listener)
if not self.web_client_location:
_warn_if_webclient_configured(self.listeners)
@@ -136,6 +166,9 @@ class ServerConfig(Config):
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)
@@ -182,6 +215,7 @@ class ServerConfig(Config):
"port": manhole,
"bind_addresses": ["127.0.0.1"],
"type": "manhole",
+ "tls": False,
})
metrics_port = config.get("metrics_port")
@@ -207,6 +241,9 @@ class ServerConfig(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:
@@ -295,75 +332,106 @@ class ServerConfig(Config):
# List of ports that Synapse should listen on, their purpose and their
# configuration.
+ #
+ # Options for each listener include:
+ #
+ # port: the TCP port to bind to
+ #
+ # bind_addresses: a list of local addresses to listen on. The default is
+ # 'all local interfaces'.
+ #
+ # type: the type of listener. Normally 'http', but other valid options are:
+ # 'manhole' (see docs/manhole.md),
+ # 'metrics' (see docs/metrics-howto.rst),
+ # 'replication' (see docs/workers.rst).
+ #
+ # tls: set to true to enable TLS for this listener. Will use the TLS
+ # key/cert specified in tls_private_key_path / tls_certificate_path.
+ #
+ # x_forwarded: Only valid for an 'http' listener. Set to true to use the
+ # X-Forwarded-For header as the client IP. Useful when Synapse is
+ # behind a reverse-proxy.
+ #
+ # resources: Only valid for an 'http' listener. A list of resources to host
+ # on this port. Options for each resource are:
+ #
+ # names: a list of names of HTTP resources. See below for a list of
+ # valid resource names.
+ #
+ # compress: set to true to enable HTTP comression for this resource.
+ #
+ # additional_resources: Only valid for an 'http' listener. A map of
+ # additional endpoints which should be loaded via dynamic modules.
+ #
+ # Valid resource names are:
+ #
+ # client: the client-server API (/_matrix/client). Also implies 'media' and
+ # 'static'.
+ #
+ # consent: user consent forms (/_matrix/consent). See
+ # docs/consent_tracking.md.
+ #
+ # federation: the server-server API (/_matrix/federation). Also implies
+ # 'media', 'keys', 'openid'
+ #
+ # keys: the key discovery API (/_matrix/keys).
+ #
+ # media: the media API (/_matrix/media).
+ #
+ # metrics: the metrics interface. See docs/metrics-howto.rst.
+ #
+ # openid: OpenID authentication.
+ #
+ # replication: the HTTP replication API (/_synapse/replication). See
+ # docs/workers.rst.
+ #
+ # static: static resources under synapse/static (/_matrix/static). (Mostly
+ # useful for 'fallback authentication'.)
+ #
+ # webclient: A web client. Requires web_client_location to be set.
+ #
listeners:
- # Main HTTPS listener
- # For when matrix traffic is sent directly to synapse.
- -
- # The port to listen for HTTPS requests on.
- port: %(bind_port)s
-
- # Local addresses to listen on.
- # On Linux and Mac OS, `::` will listen on all IPv4 and IPv6
- # addresses by default. For most other OSes, this will only listen
- # on IPv6.
- bind_addresses:
- - '::'
- - '0.0.0.0'
-
- # This is a 'http' listener, allows us to specify 'resources'.
+ # TLS-enabled listener: for when matrix traffic is sent directly to synapse.
+ #
+ # Disabled by default. To enable it, uncomment the following. (Note that you
+ # will also need to give Synapse a TLS key and certificate: see the TLS section
+ # below.)
+ #
+ # - port: %(bind_port)s
+ # type: http
+ # tls: true
+ # resources:
+ # - names: [client, federation]
+
+ # Unsecure HTTP listener: for when matrix traffic passes through a reverse proxy
+ # that unwraps TLS.
+ #
+ # If you plan to use a reverse proxy, please see
+ # https://github.com/matrix-org/synapse/blob/master/docs/reverse_proxy.rst.
+ #
+ - port: %(unsecure_port)s
+ tls: false
+ bind_addresses: ['::1', '127.0.0.1']
type: http
+ x_forwarded: true
- tls: true
-
- # Use the X-Forwarded-For (XFF) header as the client IP and not the
- # actual client IP.
- x_forwarded: false
-
- # List of HTTP resources to serve on this listener.
resources:
- -
- # List of resources to host on this listener.
- names:
- - client # The client-server APIs, both v1 and v2
- # - webclient # A web client. Requires web_client_location to be set.
-
- # Should synapse compress HTTP responses to clients that support it?
- # This should be disabled if running synapse behind a load balancer
- # that can do automatic compression.
- compress: true
-
- - names: [federation] # Federation APIs
+ - names: [client, federation]
compress: false
- # optional list of additional endpoints which can be loaded via
- # dynamic modules
+ # example additonal_resources:
+ #
# additional_resources:
# "/_matrix/my/custom/endpoint":
# module: my_module.CustomRequestHandler
# config: {}
- # Unsecure HTTP listener,
- # For when matrix traffic passes through loadbalancer that unwraps TLS.
- - port: %(unsecure_port)s
- tls: false
- bind_addresses: ['::', '0.0.0.0']
- type: http
-
- x_forwarded: false
-
- resources:
- - names: [client]
- compress: true
- - names: [federation]
- compress: false
-
# Turn on the twisted ssh manhole service on localhost on the given
# port.
# - port: 9000
# bind_addresses: ['::1', '127.0.0.1']
# type: manhole
-
# Homeserver blocking
#
# How to reach the server admin, used in ResourceLimitError
@@ -480,6 +548,7 @@ KNOWN_RESOURCES = (
'keys',
'media',
'metrics',
+ 'openid',
'replication',
'static',
'webclient',
diff --git a/synapse/config/tls.py b/synapse/config/tls.py
index b5f2cfd9b7..5fb3486db1 100644
--- a/synapse/config/tls.py
+++ b/synapse/config/tls.py
@@ -23,9 +23,9 @@ from unpaddedbase64 import encode_base64
from OpenSSL import crypto
-from synapse.config._base import Config
+from synapse.config._base import Config, ConfigError
-logger = logging.getLogger()
+logger = logging.getLogger(__name__)
class TlsConfig(Config):
@@ -45,9 +45,25 @@ class TlsConfig(Config):
self.tls_certificate_file = self.abspath(config.get("tls_certificate_path"))
self.tls_private_key_file = self.abspath(config.get("tls_private_key_path"))
- self._original_tls_fingerprints = config["tls_fingerprints"]
+
+ if self.has_tls_listener():
+ if not self.tls_certificate_file:
+ raise ConfigError(
+ "tls_certificate_path must be specified if TLS-enabled listeners are "
+ "configured."
+ )
+ if not self.tls_private_key_file:
+ raise ConfigError(
+ "tls_certificate_path must be specified if TLS-enabled listeners are "
+ "configured."
+ )
+
+ self._original_tls_fingerprints = config.get("tls_fingerprints", [])
+
+ if self._original_tls_fingerprints is None:
+ 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)
@@ -60,10 +76,14 @@ class TlsConfig(Config):
self.tls_certificate = None
self.tls_private_key = None
- def is_disk_cert_valid(self):
+ def is_disk_cert_valid(self, allow_self_signed=True):
"""
Is the certificate we have on disk valid, and if so, for how long?
+ Args:
+ allow_self_signed (bool): Should we allow the certificate we
+ read to be self signed?
+
Returns:
int: Days remaining of certificate validity.
None: No certificate exists.
@@ -84,6 +104,12 @@ class TlsConfig(Config):
logger.exception("Failed to parse existing certificate off disk!")
raise
+ if not allow_self_signed:
+ if tls_certificate.get_subject() == tls_certificate.get_issuer():
+ raise ValueError(
+ "TLS Certificate is self signed, and this is not permitted"
+ )
+
# YYYYMMDDhhmmssZ -- in UTC
expires_on = datetime.strptime(
tls_certificate.get_notAfter().decode('ascii'), "%Y%m%d%H%M%SZ"
@@ -92,36 +118,40 @@ class TlsConfig(Config):
days_remaining = (expires_on - now).days
return days_remaining
- def read_certificate_from_disk(self):
+ def read_certificate_from_disk(self, require_cert_and_key):
"""
- Read the certificates from disk.
- """
- self.tls_certificate = self.read_tls_certificate(self.tls_certificate_file)
+ Read the certificates and private key from disk.
- # 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."
+ Args:
+ require_cert_and_key (bool): set to True to throw an error if the certificate
+ and key file are not given
+ """
+ if require_cert_and_key:
+ self.tls_private_key = self.read_tls_private_key()
+ self.tls_certificate = self.read_tls_certificate()
+ elif self.tls_certificate_file:
+ # we only need the certificate for the tls_fingerprints. Reload it if we
+ # can, but it's not a fatal error if we can't.
+ try:
+ self.tls_certificate = self.read_tls_certificate()
+ except Exception as e:
+ logger.info(
+ "Unable to read TLS certificate (%s). Ignoring as no "
+ "tls listeners enabled.", e,
)
- )
-
- if not self.no_tls:
- self.tls_private_key = self.read_tls_private_key(self.tls_private_key_file)
self.tls_fingerprints = list(self._original_tls_fingerprints)
- # Check that our own certificate is included in the list of fingerprints
- # and include it if it is not.
- x509_certificate_bytes = crypto.dump_certificate(
- crypto.FILETYPE_ASN1, self.tls_certificate
- )
- sha256_fingerprint = encode_base64(sha256(x509_certificate_bytes).digest())
- sha256_fingerprints = set(f["sha256"] for f in self.tls_fingerprints)
- if sha256_fingerprint not in sha256_fingerprints:
- self.tls_fingerprints.append({u"sha256": sha256_fingerprint})
+ if self.tls_certificate:
+ # Check that our own certificate is included in the list of fingerprints
+ # and include it if it is not.
+ x509_certificate_bytes = crypto.dump_certificate(
+ crypto.FILETYPE_ASN1, self.tls_certificate
+ )
+ sha256_fingerprint = encode_base64(sha256(x509_certificate_bytes).digest())
+ sha256_fingerprints = set(f["sha256"] for f in self.tls_fingerprints)
+ if sha256_fingerprint not in sha256_fingerprints:
+ self.tls_fingerprints.append({u"sha256": sha256_fingerprint})
def default_config(self, config_dir_path, server_name, **kwargs):
base_key_name = os.path.join(config_dir_path, server_name)
@@ -137,6 +167,8 @@ class TlsConfig(Config):
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.
@@ -144,10 +176,10 @@ class TlsConfig(Config):
# See 'ACME support' below to enable auto-provisioning this certificate via
# Let's Encrypt.
#
- tls_certificate_path: "%(tls_certificate_path)s"
+ # tls_certificate_path: "%(tls_certificate_path)s"
# PEM-encoded private key for TLS
- tls_private_key_path: "%(tls_private_key_path)s"
+ # tls_private_key_path: "%(tls_private_key_path)s"
# ACME support: This will configure Synapse to request a valid TLS certificate
# for your configured `server_name` via Let's Encrypt.
@@ -172,7 +204,7 @@ class TlsConfig(Config):
#
acme:
# ACME support is disabled by default. Uncomment the following line
- # to enable it.
+ # (and tls_certificate_path and tls_private_key_path above) to enable it.
#
# enabled: true
@@ -197,13 +229,6 @@ class TlsConfig(Config):
#
# 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
@@ -236,10 +261,38 @@ class TlsConfig(Config):
% 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
+
+ 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
- def read_tls_private_key(self, private_key_path):
- private_key_pem = self.read_file(private_key_path, "tls_private_key")
+ 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)
|