FreedomBox/actions/matrixsynapse
Joseph Nuthalapati a918f9a885
matrix-synapse: Use Let's Encrypt certificates
Matrix requires valid certificates for federation with other servers from
version 1.0 onward. If the FreedomBox server already has LE cert and private
key, copy them into /etc/matrix-synapse

- Add certificate renewal hooks for Matrix Synapse. Reusing the certificate
  renewal mechanism built for ejabberd with matrix-synapse as well. One notable
  difference is that Matrix Synapse doesn't support switching the domain name or
  dropping the Let's Encrypt certificate.

- Use self-signed certificate if there is no LE certificate. Matrix Synapse
  server startup fails if the files homeserver.tls.crt and homeserver.tls.key
  are missing.

- Copy Apache's snakeoil certificates to /etc/matrix-synapse when LE
  certificates are not available. Prefer LE certificates if available.

- Display warning if no valid LE certificate is found.

Signed-off-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
2019-02-13 11:29:36 -08:00

228 lines
8.1 KiB
Python
Executable File

#!/usr/bin/python3
#
# This file is part of FreedomBox.
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
#
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
"""
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():
"""Return parsed command line arguments as dictionary"""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparsers.add_parser('post-install', help='Perform post install steps')
subparsers.add_parser('enable', help='Enable matrix-synapse service')
subparsers.add_parser('disable', help='Disable matrix-synapse service')
help_pubreg = 'Enable/Disable/Status public user registration.'
pubreg = subparsers.add_parser('public-registration', help=help_pubreg)
pubreg.add_argument('command', choices=('enable', 'disable', 'status'),
help=help_pubreg)
setup = subparsers.add_parser('setup', help='Set domain name for Matrix')
setup.add_argument(
'--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:
config = yaml.load(config_file)
config['max_upload_size'] = '100M'
for listener in config['listeners']:
if listener['port'] == 8448:
listener['bind_addresses'] = ['::', '0.0.0.0']
listener.pop('bind_address', None)
# Setup ldap parameters
config['password_providers'] = [{}]
config['password_providers'][0][
'module'] = 'ldap_auth_provider.LdapAuthProvider'
ldap_config = {
'enabled': True,
'uri': 'ldap://localhost:389',
'start_tls': False,
'base': 'ou=users,dc=thisbox',
'attributes': {
'uid': 'uid',
'name': 'uid',
'mail': None
}
}
config['password_providers'][0]['config'] = ldap_config
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})
subcommand_enable(arguments)
def subcommand_enable(_):
"""Enable service."""
action_utils.service_enable('matrix-synapse')
action_utils.webserver_enable('matrix-synapse-plinth')
def subcommand_disable(_):
"""Disable service."""
action_utils.webserver_disable('matrix-synapse-plinth')
action_utils.service_disable('matrix-synapse')
def subcommand_public_registration(argument):
"""Enable/Disable/Status public user registration."""
with open(CONFIG_FILE_PATH) as config_file:
config = yaml.load(config_file)
if argument.command == 'status':
if config['enable_registration']:
print('enabled')
return
else:
print('disabled')
return
elif argument.command == 'enable':
config['enable_registration'] = True
elif argument.command == 'disable':
config['enable_registration'] = False
with open(CONFIG_FILE_PATH, 'w') as config_file:
yaml.dump(config, config_file)
if action_utils.service_is_running('matrix-synapse'):
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('-', '_')
sub_command_method = globals()['subcommand_' + sub_command]
sub_command_method(arguments)
if __name__ == '__main__':
main()