diff options
author | Brendan Abolivier <babolivier@matrix.org> | 2021-06-17 19:56:48 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-06-17 18:56:48 +0100 |
commit | 08c84693227de9571412fa18a7d82818a370c655 (patch) | |
tree | c9282621794ea74e2a5c764f444b70a48f26470f /synapse | |
parent | Update MSC3083 support per changes in the MSC. (#10189) (diff) | |
download | synapse-08c84693227de9571412fa18a7d82818a370c655.tar.xz |
Remove support for ACME v1 (#10194)
Fixes #9778 ACME v1 has been fully decommissioned for existing installs on June 1st 2021(see https://community.letsencrypt.org/t/end-of-life-plan-for-acmev1/88430/27), so we can now safely remove it from Synapse.
Diffstat (limited to '')
-rw-r--r-- | synapse/app/_base.py | 3 | ||||
-rw-r--r-- | synapse/app/homeserver.py | 48 | ||||
-rw-r--r-- | synapse/config/_base.py | 5 | ||||
-rw-r--r-- | synapse/config/_base.pyi | 1 | ||||
-rw-r--r-- | synapse/config/tls.py | 151 | ||||
-rw-r--r-- | synapse/handlers/acme.py | 117 | ||||
-rw-r--r-- | synapse/handlers/acme_issuing_service.py | 127 | ||||
-rw-r--r-- | synapse/python_dependencies.py | 5 | ||||
-rw-r--r-- | synapse/server.py | 5 |
9 files changed, 7 insertions, 455 deletions
diff --git a/synapse/app/_base.py b/synapse/app/_base.py index 575bd30d27..1dde9d7173 100644 --- a/synapse/app/_base.py +++ b/synapse/app/_base.py @@ -289,8 +289,7 @@ async def start(hs: "synapse.server.HomeServer"): """ Start a Synapse server or worker. - Should be called once the reactor is running and (if we're using ACME) the - TLS certificates are in place. + Should be called once the reactor is running. Will start the main HTTP listeners and do some other startup tasks, and then notify systemd. diff --git a/synapse/app/homeserver.py b/synapse/app/homeserver.py index b2501ee4d7..fb16bceff8 100644 --- a/synapse/app/homeserver.py +++ b/synapse/app/homeserver.py @@ -363,55 +363,7 @@ def setup(config_options): except UpgradeDatabaseException as e: quit_with_error("Failed to upgrade database: %s" % (e,)) - async def do_acme() -> bool: - """ - Reprovision an ACME certificate, if it's required. - - Returns: - Whether the cert has been updated. - """ - acme = hs.get_acme_handler() - - # Check how long the certificate is active for. - cert_days_remaining = hs.config.is_disk_cert_valid(allow_self_signed=False) - - # We want to reprovision if cert_days_remaining is None (meaning no - # certificate exists), or the days remaining number it returns - # is less than our re-registration threshold. - provision = False - - if ( - cert_days_remaining is None - or cert_days_remaining < hs.config.acme_reprovision_threshold - ): - provision = True - - if provision: - await acme.provision_certificate() - - return provision - - async def reprovision_acme(): - """ - Provision a certificate from ACME, if required, and reload the TLS - certificate if it's renewed. - """ - reprovisioned = await do_acme() - if reprovisioned: - _base.refresh_certificate(hs) - async def start(): - # Run the ACME provisioning code, if it's enabled. - if hs.config.acme_enabled: - acme = hs.get_acme_handler() - # Start up the webservices which we will respond to ACME - # challenges with, and then provision. - await acme.start_listening() - await do_acme() - - # Check if it needs to be reprovisioned every day. - hs.get_clock().looping_call(reprovision_acme, 24 * 60 * 60 * 1000) - # Load the OIDC provider metadatas, if OIDC is enabled. if hs.config.oidc_enabled: oidc = hs.get_oidc_handler() diff --git a/synapse/config/_base.py b/synapse/config/_base.py index 08e2c2c543..d6ec618f8f 100644 --- a/synapse/config/_base.py +++ b/synapse/config/_base.py @@ -405,7 +405,6 @@ class RootConfig: listeners=None, tls_certificate_path=None, tls_private_key_path=None, - acme_domain=None, ): """ Build a default configuration file @@ -457,9 +456,6 @@ class RootConfig: tls_private_key_path (str|None): The path to the tls private key. - acme_domain (str|None): The domain acme will try to validate. If - specified acme will be enabled. - Returns: str: the yaml config file """ @@ -477,7 +473,6 @@ class RootConfig: listeners=listeners, tls_certificate_path=tls_certificate_path, tls_private_key_path=tls_private_key_path, - acme_domain=acme_domain, ).values() ) diff --git a/synapse/config/_base.pyi b/synapse/config/_base.pyi index ff9abbc232..4e7bfa8b3b 100644 --- a/synapse/config/_base.pyi +++ b/synapse/config/_base.pyi @@ -111,7 +111,6 @@ class RootConfig: database_conf: Optional[Any] = ..., tls_certificate_path: Optional[str] = ..., tls_private_key_path: Optional[str] = ..., - acme_domain: Optional[str] = ..., ): ... @classmethod def load_or_generate_config(cls, description: Any, argv: Any): ... diff --git a/synapse/config/tls.py b/synapse/config/tls.py index 0e9bba53c9..9a16a8fbae 100644 --- a/synapse/config/tls.py +++ b/synapse/config/tls.py @@ -14,7 +14,6 @@ import logging import os -import warnings from datetime import datetime from typing import List, Optional, Pattern @@ -26,45 +25,12 @@ from synapse.util import glob_to_regex logger = logging.getLogger(__name__) -ACME_SUPPORT_ENABLED_WARN = """\ -This server uses Synapse's built-in ACME support. Note that ACME v1 has been -deprecated by Let's Encrypt, and that Synapse doesn't currently support ACME v2, -which means that this feature will not work with Synapse installs set up after -November 2019, and that it may stop working on June 2020 for installs set up -before that date. - -For more info and alternative solutions, see -https://github.com/matrix-org/synapse/blob/master/docs/ACME.md#deprecation-of-acme-v1 ---------------------------------------------------------------------------------""" - class TlsConfig(Config): section = "tls" def read_config(self, config: dict, config_dir_path: str, **kwargs): - acme_config = config.get("acme", None) - if acme_config is None: - acme_config = {} - - self.acme_enabled = acme_config.get("enabled", False) - - if self.acme_enabled: - logger.warning(ACME_SUPPORT_ENABLED_WARN) - - # hyperlink complains on py2 if this is not a Unicode - self.acme_url = str( - acme_config.get("url", "https://acme-v01.api.letsencrypt.org/directory") - ) - self.acme_port = acme_config.get("port", 80) - self.acme_bind_addresses = acme_config.get("bind_addresses", ["::", "0.0.0.0"]) - self.acme_reprovision_threshold = acme_config.get("reprovision_threshold", 30) - self.acme_domain = acme_config.get("domain", config.get("server_name")) - - self.acme_account_key_file = self.abspath( - acme_config.get("account_key_file", config_dir_path + "/client.key") - ) - self.tls_certificate_file = self.abspath(config.get("tls_certificate_path")) self.tls_private_key_file = self.abspath(config.get("tls_private_key_path")) @@ -229,11 +195,9 @@ class TlsConfig(Config): data_dir_path, tls_certificate_path, tls_private_key_path, - acme_domain, **kwargs, ): - """If the acme_domain is specified acme will be enabled. - If the TLS paths are not specified the default will be certs in the + """If the TLS paths are not specified the default will be certs in the config directory""" base_key_name = os.path.join(config_dir_path, server_name) @@ -243,28 +207,15 @@ class TlsConfig(Config): "Please specify both a cert path and a key path or neither." ) - tls_enabled = ( - "" if tls_certificate_path and tls_private_key_path or acme_domain else "#" - ) + tls_enabled = "" if tls_certificate_path and tls_private_key_path else "#" if not tls_certificate_path: tls_certificate_path = base_key_name + ".tls.crt" if not tls_private_key_path: tls_private_key_path = base_key_name + ".tls.key" - acme_enabled = bool(acme_domain) - acme_domain = "matrix.example.com" - - default_acme_account_file = os.path.join(data_dir_path, "acme_account.key") - - # this is to avoid the max line length. Sorrynotsorry - proxypassline = ( - "ProxyPass /.well-known/acme-challenge " - "http://localhost:8009/.well-known/acme-challenge" - ) - # flake8 doesn't recognise that variables are used in the below string - _ = tls_enabled, proxypassline, acme_enabled, default_acme_account_file + _ = tls_enabled return ( """\ @@ -274,13 +225,9 @@ class TlsConfig(Config): # This certificate, as of Synapse 1.0, will need to be a valid and verifiable # certificate, signed by a recognised Certificate Authority. # - # See 'ACME support' below to enable auto-provisioning this certificate via - # Let's Encrypt. - # - # If supplying your own, be sure to use a `.pem` file that includes the - # full certificate chain including any intermediate certificates (for - # instance, if using certbot, use `fullchain.pem` as your certificate, - # not `cert.pem`). + # Be sure to use a `.pem` file that includes the full certificate chain including + # any intermediate certificates (for instance, if using certbot, use + # `fullchain.pem` as your certificate, not `cert.pem`). # %(tls_enabled)stls_certificate_path: "%(tls_certificate_path)s" @@ -330,80 +277,6 @@ class TlsConfig(Config): # - myCA1.pem # - myCA2.pem # - myCA3.pem - - # ACME support: This will configure Synapse to request a valid TLS certificate - # for your configured `server_name` via Let's Encrypt. - # - # Note that ACME v1 is now deprecated, and Synapse currently doesn't support - # ACME v2. This means that this feature currently won't work with installs set - # up after November 2019. For more info, and alternative solutions, see - # https://github.com/matrix-org/synapse/blob/master/docs/ACME.md#deprecation-of-acme-v1 - # - # Note that provisioning a certificate in this way requires port 80 to be - # routed to Synapse so that it can complete the http-01 ACME challenge. - # By default, if you enable ACME support, Synapse will attempt to listen on - # port 80 for incoming http-01 challenges - however, this will likely fail - # with 'Permission denied' or a similar error. - # - # There are a couple of potential solutions to this: - # - # * If you already have an Apache, Nginx, or similar listening on port 80, - # you can configure Synapse to use an alternate port, and have your web - # server forward the requests. For example, assuming you set 'port: 8009' - # below, on Apache, you would write: - # - # %(proxypassline)s - # - # * Alternatively, you can use something like `authbind` to give Synapse - # permission to listen on port 80. - # - acme: - # ACME support is disabled by default. Set this to `true` and uncomment - # tls_certificate_path and tls_private_key_path above to enable it. - # - enabled: %(acme_enabled)s - - # Endpoint to use to request certificates. If you only want to test, - # use Let's Encrypt's staging url: - # https://acme-staging.api.letsencrypt.org/directory - # - #url: https://acme-v01.api.letsencrypt.org/directory - - # Port number to listen on for the HTTP-01 challenge. Change this if - # you are forwarding connections through Apache/Nginx/etc. - # - port: 80 - - # Local addresses to listen on for incoming connections. - # Again, you may want to change this if you are forwarding connections - # through Apache/Nginx/etc. - # - bind_addresses: ['::', '0.0.0.0'] - - # How many days remaining on a certificate before it is renewed. - # - reprovision_threshold: 30 - - # The domain that the certificate should be for. Normally this - # should be the same as your Matrix domain (i.e., 'server_name'), but, - # by putting a file at 'https://<server_name>/.well-known/matrix/server', - # you can delegate incoming traffic to another server. If you do that, - # you should give the target of the delegation here. - # - # For example: if your 'server_name' is 'example.com', but - # 'https://example.com/.well-known/matrix/server' delegates to - # 'matrix.example.com', you should put 'matrix.example.com' here. - # - # If not set, defaults to your 'server_name'. - # - domain: %(acme_domain)s - - # file to use for the account key. This will be generated if it doesn't - # exist. - # - # If unspecified, we will use CONFDIR/client.key. - # - account_key_file: %(default_acme_account_file)s """ # Lowercase the string representation of boolean values % { @@ -415,8 +288,6 @@ class TlsConfig(Config): def read_tls_certificate(self) -> crypto.X509: """Reads the TLS certificate from the configured file, and returns it - Also checks if it is self-signed, and warns if so - Returns: The certificate """ @@ -425,16 +296,6 @@ class TlsConfig(Config): 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) -> crypto.PKey: diff --git a/synapse/handlers/acme.py b/synapse/handlers/acme.py deleted file mode 100644 index 16ab93f580..0000000000 --- a/synapse/handlers/acme.py +++ /dev/null @@ -1,117 +0,0 @@ -# Copyright 2019 New Vector Ltd -# -# 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. - -import logging -from typing import TYPE_CHECKING - -import twisted -import twisted.internet.error -from twisted.web import server, static -from twisted.web.resource import Resource - -from synapse.app import check_bind_error - -if TYPE_CHECKING: - from synapse.server import HomeServer - -logger = logging.getLogger(__name__) - -ACME_REGISTER_FAIL_ERROR = """ --------------------------------------------------------------------------------- -Failed to register with the ACME provider. This is likely happening because the installation -is new, and ACME v1 has been deprecated by Let's Encrypt and disabled for -new installations since November 2019. -At the moment, Synapse doesn't support ACME v2. For more information and alternative -solutions, please read https://github.com/matrix-org/synapse/blob/master/docs/ACME.md#deprecation-of-acme-v1 ---------------------------------------------------------------------------------""" - - -class AcmeHandler: - def __init__(self, hs: "HomeServer"): - self.hs = hs - self.reactor = hs.get_reactor() - self._acme_domain = hs.config.acme_domain - - async def start_listening(self) -> None: - from synapse.handlers import acme_issuing_service - - # Configure logging for txacme, if you need to debug - # from eliot import add_destinations - # from eliot.twisted import TwistedDestination - # - # add_destinations(TwistedDestination()) - - well_known = Resource() - - self._issuer = acme_issuing_service.create_issuing_service( - self.reactor, - acme_url=self.hs.config.acme_url, - account_key_file=self.hs.config.acme_account_key_file, - well_known_resource=well_known, - ) - - responder_resource = Resource() - responder_resource.putChild(b".well-known", well_known) - responder_resource.putChild(b"check", static.Data(b"OK", b"text/plain")) - srv = server.Site(responder_resource) - - bind_addresses = self.hs.config.acme_bind_addresses - for host in bind_addresses: - logger.info( - "Listening for ACME requests on %s:%i", host, self.hs.config.acme_port - ) - try: - self.reactor.listenTCP( - self.hs.config.acme_port, srv, backlog=50, interface=host - ) - except twisted.internet.error.CannotListenError as e: - check_bind_error(e, host, bind_addresses) - - # Make sure we are registered to the ACME server. There's no public API - # for this, it is usually triggered by startService, but since we don't - # want it to control where we save the certificates, we have to reach in - # and trigger the registration machinery ourselves. - self._issuer._registered = False - - try: - await self._issuer._ensure_registered() - except Exception: - logger.error(ACME_REGISTER_FAIL_ERROR) - raise - - async def provision_certificate(self) -> None: - - logger.warning("Reprovisioning %s", self._acme_domain) - - try: - await self._issuer.issue_cert(self._acme_domain) - except Exception: - logger.exception("Fail!") - raise - logger.warning("Reprovisioned %s, saving.", self._acme_domain) - cert_chain = self._issuer.cert_store.certs[self._acme_domain] - - try: - with open(self.hs.config.tls_private_key_file, "wb") as private_key_file: - for x in cert_chain: - if x.startswith(b"-----BEGIN RSA PRIVATE KEY-----"): - private_key_file.write(x) - - with open(self.hs.config.tls_certificate_file, "wb") as certificate_file: - for x in cert_chain: - if x.startswith(b"-----BEGIN CERTIFICATE-----"): - certificate_file.write(x) - except Exception: - logger.exception("Failed saving!") - raise diff --git a/synapse/handlers/acme_issuing_service.py b/synapse/handlers/acme_issuing_service.py deleted file mode 100644 index a972d3fa0a..0000000000 --- a/synapse/handlers/acme_issuing_service.py +++ /dev/null @@ -1,127 +0,0 @@ -# Copyright 2019 New Vector Ltd -# Copyright 2019 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. - -""" -Utility function to create an ACME issuing service. - -This file contains the unconditional imports on the acme and cryptography bits that we -only need (and may only have available) if we are doing ACME, so is designed to be -imported conditionally. -""" -import logging -from typing import Dict, Iterable, List - -import attr -import pem -from cryptography.hazmat.backends import default_backend -from cryptography.hazmat.primitives import serialization -from josepy import JWKRSA -from josepy.jwa import RS256 -from txacme.challenges import HTTP01Responder -from txacme.client import Client -from txacme.interfaces import ICertificateStore -from txacme.service import AcmeIssuingService -from txacme.util import generate_private_key -from zope.interface import implementer - -from twisted.internet import defer -from twisted.internet.interfaces import IReactorTCP -from twisted.python.filepath import FilePath -from twisted.python.url import URL -from twisted.web.resource import IResource - -logger = logging.getLogger(__name__) - - -def create_issuing_service( - reactor: IReactorTCP, - acme_url: str, - account_key_file: str, - well_known_resource: IResource, -) -> AcmeIssuingService: - """Create an ACME issuing service, and attach it to a web Resource - - Args: - reactor: twisted reactor - acme_url: URL to use to request certificates - account_key_file: where to store the account key - well_known_resource: web resource for .well-known. - we will attach a child resource for "acme-challenge". - - Returns: - AcmeIssuingService - """ - responder = HTTP01Responder() - - well_known_resource.putChild(b"acme-challenge", responder.resource) - - store = ErsatzStore() - - return AcmeIssuingService( - cert_store=store, - client_creator=( - lambda: Client.from_url( - reactor=reactor, - url=URL.from_text(acme_url), - key=load_or_create_client_key(account_key_file), - alg=RS256, - ) - ), - clock=reactor, - responders=[responder], - ) - - -@attr.s(slots=True) -@implementer(ICertificateStore) -class ErsatzStore: - """ - A store that only stores in memory. - """ - - certs = attr.ib(type=Dict[bytes, List[bytes]], default=attr.Factory(dict)) - - def store( - self, server_name: bytes, pem_objects: Iterable[pem.AbstractPEMObject] - ) -> defer.Deferred: - self.certs[server_name] = [o.as_bytes() for o in pem_objects] - return defer.succeed(None) - - -def load_or_create_client_key(key_file: str) -> JWKRSA: - """Load the ACME account key from a file, creating it if it does not exist. - - Args: - key_file: name of the file to use as the account key - """ - # this is based on txacme.endpoint.load_or_create_client_key, but doesn't - # hardcode the 'client.key' filename - acme_key_file = FilePath(key_file) - if acme_key_file.exists(): - logger.info("Loading ACME account key from '%s'", acme_key_file) - key = serialization.load_pem_private_key( - acme_key_file.getContent(), password=None, backend=default_backend() - ) - else: - logger.info("Saving new ACME account key to '%s'", acme_key_file) - key = generate_private_key("rsa") - acme_key_file.setContent( - key.private_bytes( - encoding=serialization.Encoding.PEM, - format=serialization.PrivateFormat.TraditionalOpenSSL, - encryption_algorithm=serialization.NoEncryption(), - ) - ) - return JWKRSA(key=key) diff --git a/synapse/python_dependencies.py b/synapse/python_dependencies.py index bf361c42d6..271c17c226 100644 --- a/synapse/python_dependencies.py +++ b/synapse/python_dependencies.py @@ -96,11 +96,6 @@ CONDITIONAL_REQUIREMENTS = { "psycopg2cffi>=2.8 ; platform_python_implementation == 'PyPy'", "psycopg2cffi-compat==1.1 ; platform_python_implementation == 'PyPy'", ], - # ACME support is required to provision TLS certificates from authorities - # that use the protocol, such as Let's Encrypt. - "acme": [ - "txacme>=0.9.2", - ], "saml2": [ "pysaml2>=4.5.0", ], diff --git a/synapse/server.py b/synapse/server.py index fec0024c89..e8dd2fa9f2 100644 --- a/synapse/server.py +++ b/synapse/server.py @@ -66,7 +66,6 @@ from synapse.groups.attestations import GroupAttestationSigning, GroupAttestionR from synapse.groups.groups_server import GroupsServerHandler, GroupsServerWorkerHandler from synapse.handlers.account_data import AccountDataHandler from synapse.handlers.account_validity import AccountValidityHandler -from synapse.handlers.acme import AcmeHandler from synapse.handlers.admin import AdminHandler from synapse.handlers.appservice import ApplicationServicesHandler from synapse.handlers.auth import AuthHandler, MacaroonGenerator @@ -495,10 +494,6 @@ class HomeServer(metaclass=abc.ABCMeta): return E2eRoomKeysHandler(self) @cache_in_self - def get_acme_handler(self) -> AcmeHandler: - return AcmeHandler(self) - - @cache_in_self def get_admin_handler(self) -> AdminHandler: return AdminHandler(self) |