diff --git a/actions/letsencrypt b/actions/letsencrypt index 17b9201fb..877e280b8 100755 --- a/actions/letsencrypt +++ b/actions/letsencrypt @@ -402,13 +402,17 @@ def subcommand_run_renew_hooks(arguments): action_utils.service_restart('apache2') for module in arguments.modules: - _run_action(module, ['letsencrypt', 'add']) # OK if only 1 module - # TODO: If >1 modules, collect errors and raise just one in the end, - # for certbot to log ALL failed attempts, not just 1st fail. - # try: - # _run_action(module, ['letsencrypt', 'add']) - # except Exception as err: - # pass + # If >1 modules, collect errors and raise just one in the end, + # for certbot to log ALL failed attempts, not just 1st fail. + error_messages = [] + try: + _run_action(module, ['letsencrypt', 'add']) + except Exception as err: + error_messages.append(err.message) + + if error_messages: + raise ActionError(message="\n".join(error_messages)) + sys.exit(3) sys.exit(0) diff --git a/actions/matrixsynapse b/actions/matrixsynapse index c50f213d5..697ab7d22 100755 --- a/actions/matrixsynapse +++ b/actions/matrixsynapse @@ -21,9 +21,15 @@ Configuration helper for Matrix-Synapse server. import argparse +import os +import shutil +import sys import yaml + from plinth import action_utils +from plinth.modules import config, letsencrypt from plinth.modules.matrixsynapse import CONFIG_FILE_PATH +from plinth.utils import YAMLFile def parse_arguments(): @@ -43,10 +49,50 @@ def parse_arguments(): '--domain-name', help='The domain name that will be used by Matrix Synapse') + help_LE = "Add/drop Let's Encrypt certificate if configured domain matches" + letsencrypt = subparsers.add_parser('letsencrypt', help=help_LE) + letsencrypt.add_argument('command', choices=('add', 'drop'), help=help_LE) + letsencrypt.add_argument('--domain', + help='Domain name to renew certificates for.') + subparsers.required = True return parser.parse_args() +def _update_TLS_certificate(): + """Update the TLS certificate and private key used by Matrix Synapse for + federation with other instances.""" + if os.path.exists(letsencrypt.LIVE_DIRECTORY): + # Copy the latest Let's Encrypt certs into Synapse's directory. + with YAMLFile('/etc/matrix-synapse/conf.d/server_name.yaml') as conf: + src_dir = os.path.join(letsencrypt.LIVE_DIRECTORY, + conf['server_name']) + + 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') + + shutil.copyfile(source_certificate_path, dest_certificate_path) + shutil.copyfile(source_private_key_path, dest_private_key_path) + + shutil.chown(dest_certificate_path, user='matrix-synapse', group='nogroup') + shutil.chown(dest_private_key_path, user='matrix-synapse', group='nogroup') + + # Private key is only accessible to the user "matrix-synapse" + # Group access is prohibited since it is "nogroup" + os.chmod(dest_private_key_path, 0o600) + + def subcommand_post_install(_): """Perform post installation configuration.""" with open(CONFIG_FILE_PATH) as config_file: @@ -79,6 +125,8 @@ 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') @@ -127,6 +175,47 @@ def subcommand_public_registration(argument): action_utils.service_restart('matrix-synapse') +def subcommand_letsencrypt(arguments): + """ + Add/drop usage of Let's Encrypt cert. 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. + """ + current_domain = config.get_domainname() + + with YAMLFile('/etc/matrix-synapse/conf.d/server_name.yaml') as conf: + if arguments.domain is not None and \ + arguments.domain != conf['server_name']: + print('Aborted: Current domain "{}"'.format(arguments.domain), + 'is not configured for matrix-synapse.') + sys.exit(1) + + if arguments.command == 'add' and arguments.domain is not None \ + and arguments.domain != current_domain: + print('Aborted: Only certificate of current domain "%s" can be added.' + % current_domain) + sys.exit(2) + + if arguments.domain is None: + arguments.domain = current_domain + + if arguments.command == 'add': + le_folder = os.path.join(letsencrypt.LIVE_DIRECTORY, current_domain) + + if not os.path.exists(le_folder): + print('Aborted: No certificate directory at %s.' % le_folder) + sys.exit(3) + + _update_TLS_certificate() + + else: + print("Dropping certificates is not supported for Matrix Synapse.") + + if action_utils.service_is_running('matrix-synapse'): + action_utils.service_restart('matrix-synapse') + + def main(): arguments = parse_arguments() sub_command = arguments.subcommand.replace('-', '_') diff --git a/plinth/modules/letsencrypt/__init__.py b/plinth/modules/letsencrypt/__init__.py index 30da59a34..9c9fd5ea4 100644 --- a/plinth/modules/letsencrypt/__init__.py +++ b/plinth/modules/letsencrypt/__init__.py @@ -63,7 +63,7 @@ service = None manual_page = 'LetsEncrypt' -MODULES_WITH_HOOKS = ['ejabberd'] +MODULES_WITH_HOOKS = ['ejabberd', 'matrixsynapse'] LIVE_DIRECTORY = '/etc/letsencrypt/live/' logger = logging.getLogger(__name__) @@ -71,8 +71,7 @@ logger = logging.getLogger(__name__) def init(): """Intialize the module.""" menu = main_menu.get('system') - menu.add_urlname(name, 'fa-lock', 'letsencrypt:index', - short_description) + menu.add_urlname(name, 'fa-lock', 'letsencrypt:index', short_description) domainname_change.connect(on_domainname_change) domain_added.connect(on_domain_added) domain_removed.connect(on_domain_removed) diff --git a/plinth/modules/letsencrypt/templates/letsencrypt.html b/plinth/modules/letsencrypt/templates/letsencrypt.html index fc0c5f641..408b3c1d2 100644 --- a/plinth/modules/letsencrypt/templates/letsencrypt.html +++ b/plinth/modules/letsencrypt/templates/letsencrypt.html @@ -140,7 +140,7 @@ {% blocktrans trimmed %} If you have a Let's Encrypt certificate for your current domain, you may let {{ box_name }} manage its renewal process. This also enables other apps - to use that certificate, so most users would not prompted with security + to use that certificate, so that users would not prompted with security warnings when using them. {% endblocktrans %}
@@ -252,6 +252,67 @@ {% endif %} + {% else %} {% blocktrans trimmed %} No current domain is configured. diff --git a/plinth/modules/matrixsynapse/__init__.py b/plinth/modules/matrixsynapse/__init__.py index f008252d8..5e0e939d4 100644 --- a/plinth/modules/matrixsynapse/__init__.py +++ b/plinth/modules/matrixsynapse/__init__.py @@ -18,6 +18,7 @@ FreedomBox app to configure matrix-synapse server. """ +import json import logging import os @@ -28,10 +29,11 @@ from ruamel.yaml.util import load_yaml_guess_indent from plinth import service as service_module from plinth import action_utils, actions, frontpage from plinth.menu import main_menu +from plinth.utils import YAMLFile from .manifest import backup, clients -version = 3 +version = 4 managed_services = ['matrix-synapse'] @@ -159,3 +161,15 @@ def get_public_registration_status(): output = actions.superuser_run('matrixsynapse', ['public-registration', 'status']) return output.strip() == 'enabled' + + +def has_valid_certificate(): + """Return whether the configured domain name has a valid + Let's Encrypt certificate.""" + domain_name = get_configured_domain_name() + status = actions.superuser_run('letsencrypt', ['get-status']) + status = json.loads(status) + if domain_name in status['domains']: + return status['domains'][domain_name][ + 'certificate_available'] == "true" + return False diff --git a/plinth/modules/matrixsynapse/templates/matrix-synapse.html b/plinth/modules/matrixsynapse/templates/matrix-synapse.html index 9206abe2d..55323839d 100644 --- a/plinth/modules/matrixsynapse/templates/matrix-synapse.html +++ b/plinth/modules/matrixsynapse/templates/matrix-synapse.html @@ -33,4 +33,15 @@ {% endblocktrans %}New users can be registered from any client if public registration is enabled.
+ + {% if not has_valid_certificate %} +