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

ACME Reprovisioning #4522

Merged
merged 51 commits into from
Feb 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
e3e159d
fix typo
hawkowl Jan 24, 2019
f6a7149
fix
hawkowl Jan 24, 2019
e148691
fix
hawkowl Jan 24, 2019
3ca24d5
fix
hawkowl Jan 24, 2019
8aa6d71
fix
hawkowl Jan 24, 2019
7ea34b0
fix
hawkowl Jan 24, 2019
70170e0
fix
hawkowl Jan 24, 2019
92e893e
fix
hawkowl Jan 24, 2019
f8c4258
fix
hawkowl Jan 24, 2019
9a66c51
fix
hawkowl Jan 24, 2019
7c99df8
fix
hawkowl Jan 24, 2019
0f0187e
fix
hawkowl Jan 24, 2019
4b9fd2b
fix
hawkowl Jan 24, 2019
e2615be
fix
hawkowl Jan 24, 2019
abc4e7d
fix
hawkowl Jan 24, 2019
666fc90
fix
hawkowl Jan 24, 2019
f6b58aa
fix
hawkowl Jan 24, 2019
26f4f5a
Merge remote-tracking branch 'origin/develop' into hawkowl/sighup-tls
hawkowl Jan 28, 2019
aaf9220
fix
hawkowl Jan 28, 2019
7df3114
changelog
hawkowl Jan 28, 2019
3fe07f7
fix
hawkowl Jan 29, 2019
2340ae5
fix
hawkowl Jan 29, 2019
08de6c9
fix
hawkowl Jan 29, 2019
cd78e7e
Merge remote-tracking branch 'origin/develop' into hawkowl/sighup-tls
hawkowl Jan 29, 2019
14e4c4f
fix
hawkowl Jan 29, 2019
d08ef7b
fix
hawkowl Jan 29, 2019
45f9d6c
reprovisioning code
hawkowl Jan 29, 2019
97eec0a
reprovisioning code
hawkowl Jan 30, 2019
edd51a1
Merge remote-tracking branch 'origin/develop' into hawkowl/acme-repro…
hawkowl Jan 30, 2019
62b4e01
pep8 fixes
hawkowl Jan 30, 2019
965921c
pep8 fixes
hawkowl Jan 30, 2019
d21f762
fixes
hawkowl Jan 30, 2019
67c30d3
Merge remote-tracking branch 'origin/develop' into hawkowl/acme-repro…
hawkowl Jan 30, 2019
14274eb
fixes
hawkowl Jan 30, 2019
12696ab
fixes
hawkowl Jan 30, 2019
fe36f24
fixes
hawkowl Jan 30, 2019
9c9d261
changelog
hawkowl Jan 30, 2019
e9e4c52
Merge remote-tracking branch 'origin/develop' into hawkowl/acme-repro…
hawkowl Jan 30, 2019
45810a4
Merge remote-tracking branch 'origin/develop' into hawkowl/acme-repro…
hawkowl Jan 30, 2019
512dfeb
fixes
hawkowl Jan 30, 2019
5fdaf5c
port over
hawkowl Feb 5, 2019
3fa83ab
changelog
hawkowl Feb 5, 2019
1779696
fix
hawkowl Feb 5, 2019
89fba92
some cleanup
hawkowl Feb 8, 2019
b232c17
some cleanup
hawkowl Feb 8, 2019
ffdac50
some cleanup
hawkowl Feb 8, 2019
54ea80c
Merge branch 'hawkowl/dedupe-start' into hawkowl/acme-reprovision
hawkowl Feb 8, 2019
759ceb8
Merge remote-tracking branch 'origin/develop' into hawkowl/acme-repro…
hawkowl Feb 8, 2019
fcb2fe1
Update _base.py
hawkowl Feb 8, 2019
565b3d2
some docs
hawkowl Feb 11, 2019
50046cc
fix changelog
richvdh Feb 11, 2019
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/4522.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Synapse's ACME support will now correctly reprovision a certificate that approaches its expiry while Synapse is running.
19 changes: 19 additions & 0 deletions synapse/app/_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
from daemonize import Daemonize

from twisted.internet import error, reactor
from twisted.protocols.tls import TLSMemoryBIOFactory

from synapse.app import check_bind_error
from synapse.crypto import context_factory
Expand Down Expand Up @@ -220,6 +221,24 @@ def refresh_certificate(hs):
)
logging.info("Certificate loaded.")

if hs._listening_services:
logging.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
# requests. This factory attribute is public but missing from
# Twisted's documentation.
if isinstance(i.factory, TLSMemoryBIOFactory):
# We want to replace TLS factories with a new one, with the new
# TLS configuration. We do this by reaching in and pulling out
# the wrappedFactory, and then re-wrapping it.
i.factory = TLSMemoryBIOFactory(
hs.tls_server_context_factory,
False,
i.factory.wrappedFactory
)
logging.info("Context factories updated.")


def start(hs, listeners=None):
"""
Expand Down
79 changes: 55 additions & 24 deletions synapse/app/homeserver.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ def gz_wrap(r):

class SynapseHomeServer(HomeServer):
DATASTORE_CLASS = DataStore
_listening_services = []

def _listener_http(self, config, listener_config):
port = listener_config["port"]
Expand Down Expand Up @@ -365,42 +364,73 @@ def setup(config_options):

hs.setup()

@defer.inlineCallbacks
def do_acme():
"""
Reprovision an ACME certificate, if it's required.

Returns:
Deferred[bool]: 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):
provision = True

if cert_days_remaining > hs.config.acme_reprovision_threshold:
provision = True

if provision:
yield acme.provision_certificate()

defer.returnValue(provision)

@defer.inlineCallbacks
def reprovision_acme():
"""
Provision a certificate from ACME, if required, and reload the TLS
certificate if it's renewed.
"""
reprovisioned = yield do_acme()
if reprovisioned:
_base.refresh_certificate(hs)

@defer.inlineCallbacks
def start():
try:
# Check if the certificate is still valid.
cert_days_remaining = hs.config.is_disk_cert_valid()

# Run the ACME provisioning code, if it's enabled.
if hs.config.acme_enabled:
# If ACME is enabled, we might need to provision a certificate
# before starting.
acme = hs.get_acme_handler()

# Start up the webservices which we will respond to ACME
# challenges with.
# challenges with, and then provision.
yield acme.start_listening()
yield do_acme()

# 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.
if (cert_days_remaining is None) or (
not cert_days_remaining > hs.config.acme_reprovision_threshold
):
yield acme.provision_certificate()
# Check if it needs to be reprovisioned every day.
hs.get_clock().looping_call(
reprovision_acme,
24 * 60 * 60 * 1000
)

_base.start(hs, config.listeners)

hs.get_pusherpool().start()
hs.get_datastore().start_doing_background_updates()
except Exception as e:
# If a DeferredList failed (like in listening on the ACME listener),
# we need to print the subfailure explicitly.
if isinstance(e, defer.FirstError):
e.subFailure.printTraceback(sys.stderr)
sys.exit(1)

# Something else went wrong when starting. Print it and bail out.
except Exception:
# Print the exception and bail out.
traceback.print_exc(file=sys.stderr)
if reactor.running:
reactor.stop()
sys.exit(1)

reactor.callWhenRunning(start)
Expand All @@ -409,7 +439,8 @@ def start():


class SynapseService(service.Service):
"""A twisted Service class that will start synapse. Used to run synapse
"""
A twisted Service class that will start synapse. Used to run synapse
via twistd and a .tac.
"""
def __init__(self, config):
Expand Down
12 changes: 11 additions & 1 deletion synapse/config/tls.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,14 @@ def read_config(self, 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.
Expand All @@ -84,6 +88,12 @@ def is_disk_cert_valid(self):
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"
Expand Down
3 changes: 3 additions & 0 deletions synapse/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ def build_DEPENDENCY(self)

Attributes:
config (synapse.config.homeserver.HomeserverConfig):
_listening_services (list[twisted.internet.tcp.Port]): TCP ports that
we are listening on to provide HTTP services.
"""

__metaclass__ = abc.ABCMeta
Expand Down Expand Up @@ -196,6 +198,7 @@ def __init__(self, hostname, reactor=None, **kwargs):
self._reactor = reactor
self.hostname = hostname
self._building = {}
self._listening_services = []

self.clock = Clock(reactor)
self.distributor = Distributor()
Expand Down