matrixsynapse: Add let's encrypt component for certficiates

Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
This commit is contained in:
Sunil Mohan Adapa 2019-06-21 11:11:17 -07:00 committed by Joseph Nuthalapati
parent c47a99b25e
commit 9fd1b95244
No known key found for this signature in database
GPG Key ID: 5398F00A2FA43C35
4 changed files with 43 additions and 117 deletions

View File

@ -20,10 +20,6 @@ Configuration helper for Matrix-Synapse server.
"""
import argparse
import filecmp
import os
import shutil
import sys
import yaml
@ -59,67 +55,6 @@ def parse_arguments():
return parser.parse_args()
def _get_certificate_status():
"""Return if the current certificate is an up-to-date LE certificate."""
configured_domain = get_configured_domain_name()
if not configured_domain:
return False
if not os.path.exists(letsencrypt.LIVE_DIRECTORY):
return False
source_dir = os.path.join(letsencrypt.LIVE_DIRECTORY, configured_domain)
source_certificate_path = os.path.join(source_dir, 'fullchain.pem')
source_private_key_path = os.path.join(source_dir, 'privkey.pem')
dest_dir = '/etc/matrix-synapse'
dest_certificate_path = os.path.join(dest_dir, 'homeserver.tls.crt')
dest_private_key_path = os.path.join(dest_dir, 'homeserver.tls.key')
if filecmp.cmp(source_certificate_path, dest_certificate_path) and \
filecmp.cmp(source_private_key_path, dest_private_key_path):
return True
return False
def _update_tls_certificate():
"""Update the TLS certificate and private key used by Matrix Synapse.
A valid certificate is necessary for federation with other instances
starting with version 1.0.
"""
configured_domain = get_configured_domain_name()
if os.path.exists(letsencrypt.LIVE_DIRECTORY) and configured_domain:
# Copy the latest Let's Encrypt certs into Synapse's directory.
src_dir = os.path.join(letsencrypt.LIVE_DIRECTORY, configured_domain)
source_certificate_path = os.path.join(src_dir, 'fullchain.pem')
source_private_key_path = os.path.join(src_dir, 'privkey.pem')
else:
# Copy Apache's snake-oil certificate into Synapse's config directory.
# The self-signed certificate doesn't really work (other Matrix
# Synapse instances do not accept it). It is merely to prevent the
# server from failing to startup because the files are missing.
source_certificate_path = '/etc/ssl/certs/ssl-cert-snakeoil.pem'
source_private_key_path = '/etc/ssl/private/ssl-cert-snakeoil.key'
dest_dir = '/etc/matrix-synapse'
dest_certificate_path = os.path.join(dest_dir, 'homeserver.tls.crt')
dest_private_key_path = os.path.join(dest_dir, 'homeserver.tls.key')
# Private key is only accessible to the user "matrix-synapse"
# Group access is prohibited since it is "nogroup"
old_mask = os.umask(0o133)
shutil.copyfile(source_certificate_path, dest_certificate_path)
os.umask(0o177)
shutil.copyfile(source_private_key_path, dest_private_key_path)
os.umask(old_mask)
shutil.chown(dest_certificate_path, user='matrix-synapse', group='nogroup')
shutil.chown(dest_private_key_path, user='matrix-synapse', group='nogroup')
def subcommand_post_install(_):
"""Perform post installation configuration."""
with open(CONFIG_FILE_PATH) as config_file:
@ -152,18 +87,12 @@ def subcommand_post_install(_):
with open(CONFIG_FILE_PATH, 'w') as config_file:
yaml.dump(config, config_file)
_update_tls_certificate()
if action_utils.service_is_running('matrix-synapse'):
action_utils.service_restart('matrix-synapse')
def subcommand_setup(arguments):
"""Configure the domain name for matrix-synapse package."""
domain_name = arguments.domain_name
action_utils.dpkg_reconfigure('matrix-synapse',
{'server-name': domain_name})
_update_tls_certificate()
def subcommand_public_registration(argument):
@ -190,40 +119,6 @@ def subcommand_public_registration(argument):
action_utils.service_restart('matrix-synapse')
def subcommand_letsencrypt(arguments):
"""Add/drop usage of Let's Encrypt cert or show status.
The command 'add' applies only to current domain, will be called by action
'letsencrypt run_renew_hooks', when certbot renews the cert (if
matrix-synapse is selected for cert use). Drop of a cert must be possible
for any domain to respond to domain change.
"""
if arguments.command == 'drop':
print('Dropping certificates is not supported for Matrix Synapse.')
return
if arguments.command == 'get-status':
print('valid' if _get_certificate_status() else 'invalid')
return
configured_domain = get_configured_domain_name()
if arguments.domain is not None and \
arguments.domain != configured_domain:
print('Aborted: Current domain "{}" is not configured.'.format(
arguments.domain))
sys.exit(1)
le_folder = os.path.join(letsencrypt.LIVE_DIRECTORY, configured_domain)
if not os.path.exists(le_folder):
print('Aborted: No certificate directory at %s.' % le_folder)
sys.exit(2)
_update_tls_certificate()
action_utils.service_try_restart('matrix-synapse')
def main():
arguments = parse_arguments()
sub_command = arguments.subcommand.replace('-', '_')

View File

@ -20,6 +20,7 @@ FreedomBox app to configure matrix-synapse server.
import logging
import os
import pathlib
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
@ -31,6 +32,7 @@ from plinth import frontpage, menu
from plinth.daemon import Daemon
from plinth.modules.apache.components import Webserver
from plinth.modules.firewall.components import Firewall
from plinth.modules.letsencrypt.components import LetsEncrypt
from .manifest import backup, clients
@ -40,6 +42,8 @@ managed_services = ['matrix-synapse']
managed_packages = ['matrix-synapse', 'matrix-synapse-ldap3']
managed_paths = [pathlib.Path('/etc/matrix-synapse/')]
name = _('Matrix Synapse')
short_description = _('Chat Server')
@ -101,6 +105,16 @@ class MatrixSynapseApp(app_module.App):
'matrix-synapse-plinth')
self.add(webserver)
letsencrypt = LetsEncrypt(
'letsencrypt-matrixsynapse', domains=get_domains, daemons=[
managed_services[0]
], should_copy_certificates=True,
private_key_path='/etc/matrix-synapse/homeserver.tls.key',
certificate_path='/etc/matrix-synapse/homeserver.tls.crt',
user_owner='matrix-synapse', group_owner='nogroup',
managing_app='matrixsynapse')
self.add(letsencrypt)
daemon = Daemon('daemon-matrixsynapse', managed_services[0])
self.add(daemon)
@ -121,6 +135,15 @@ def setup(helper, old_version=None):
helper.call('post', actions.superuser_run, 'matrixsynapse',
['post-install'])
helper.call('post', app.enable)
app.get_component('letsencrypt-matrixsynapse').setup_certificates()
def setup_domain(domain_name):
"""Configure a domain name for matrixsynapse."""
app.get_component('letsencrypt-matrixsynapse').setup_certificates(
[domain_name])
actions.superuser_run('matrixsynapse',
['setup', '--domain-name', domain_name])
def is_setup():
@ -141,6 +164,15 @@ def diagnose():
return results
def get_domains():
"""Return a list of domains this app is interested in."""
domain = get_configured_domain_name()
if domain:
return [domain]
return []
def get_configured_domain_name():
"""Return the currently configured domain name."""
if not is_setup():
@ -159,8 +191,10 @@ def get_public_registration_status():
return output.strip() == 'enabled'
def has_valid_certificate():
"""Return whether the configured domain name has a valid certificate."""
status = actions.superuser_run('matrixsynapse',
['letsencrypt', 'get-status'])
return status.startswith('valid')
def get_certificate_status():
"""Return the status of certificate for the configured domain."""
status = app.get_component('letsencrypt-matrixsynapse').get_status()
if not status:
return 'no-domains'
return list(status.values())[0]

View File

@ -39,7 +39,7 @@
{% endblocktrans %}
</p>
{% if not has_valid_certificate %}
{% if certificate_status != "valid" %}
<div class="alert alert-warning" role="alert">
{% url 'letsencrypt:index' as letsencrypt_url %}
{% blocktrans %}

View File

@ -30,7 +30,7 @@ from plinth.modules import matrixsynapse
from plinth.utils import get_domain_names
from plinth.views import AppView
from . import get_public_registration_status, has_valid_certificate
from . import get_public_registration_status
from .forms import MatrixSynapseForm
@ -42,10 +42,7 @@ class SetupView(FormView):
def form_valid(self, form):
"""Handle valid form submission."""
domain_name = form.cleaned_data['domain_name']
actions.superuser_run('matrixsynapse',
['setup', '--domain-name', domain_name])
matrixsynapse.setup_domain(form.cleaned_data['domain_name'])
return super().form_valid(form)
def get_context_data(self, *args, **kwargs):
@ -82,7 +79,7 @@ class MatrixSynapseAppView(AppView):
context['domain_name'] = matrixsynapse.get_configured_domain_name()
context['clients'] = matrixsynapse.clients
context['manual_page'] = matrixsynapse.manual_page
context['has_valid_certificate'] = has_valid_certificate()
context['certificate_status'] = matrixsynapse.get_certificate_status()
return context
def get_initial(self):