mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-03 10:50:20 +00:00
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>
This commit is contained in:
parent
a821517e91
commit
a918f9a885
@ -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)
|
||||
|
||||
|
||||
@ -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('-', '_')
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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 %}
|
||||
</p>
|
||||
@ -252,6 +252,67 @@
|
||||
{% endif %}
|
||||
</p>
|
||||
</form>
|
||||
<form class="form" method="post"
|
||||
action="{% url 'letsencrypt:toggle_module' domain=status.current_domain.name module='matrixsynapse' %}">
|
||||
{% csrf_token %}
|
||||
<div class="checkbox">
|
||||
<label>
|
||||
{% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
|
||||
<input type="checkbox" name="matrixsynapse" id="matrixsynapse"
|
||||
{% if 'matrixsynapse' in status.current_domain.manage_hooks_status %}
|
||||
checked
|
||||
{% endif %}
|
||||
onchange="this.form.submit();">
|
||||
</input>
|
||||
<noscript>
|
||||
<button class="btn btn-sm btn-default" type="submit">
|
||||
{% trans "Update config" %}</button>
|
||||
</noscript>
|
||||
{% else %}
|
||||
<input type="checkbox" name="matrixsynapse" id="matrixsynapse"
|
||||
class="disabled"></input>
|
||||
<noscript>
|
||||
<button class="btn btn-sm btn-default disabled" type="submit">
|
||||
{% trans "Update config" %}</button>
|
||||
</noscript>
|
||||
{% endif %}
|
||||
<span>
|
||||
{% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
|
||||
{% blocktrans trimmed with current_domain=status.current_domain.name %}
|
||||
Use certificate of {{ current_domain }} for <b>matrixsynapse</b>
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
Use certificate of the current domain for <b>matrixsynapse</b>
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
<p class="help-block">
|
||||
{% url 'matrixsynapse:index' as matrixsynapse_url %}
|
||||
{% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
|
||||
{% blocktrans trimmed %}
|
||||
If enabled, the app <a href="{{ matrixsynapse_url }}">Matrix Synapse</a>
|
||||
will also use the same Let's Encrypt certificate.
|
||||
Other Matrix Synapse instances running version 1.0 or greater expect
|
||||
your server to produce a valid TLS certificate. Federation with other
|
||||
instances will not work without this.
|
||||
{% endblocktrans %}
|
||||
{% elif 'matrixsynapse' not in installed_modules %}
|
||||
{% blocktrans trimmed %}
|
||||
This feature only makes sense if you are using the
|
||||
<a href="{{ matrixsynapse_url }}">Matrix Synapse</a> chat server app.
|
||||
{% endblocktrans %}
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
To use a Let's Encrypt certificate for
|
||||
<a href="{{ matrixsynapse_url }}">Matrix Synapse</a> chat server app, you
|
||||
must first enable certificate renewal of the current domain.
|
||||
{% endblocktrans %}
|
||||
{% endif %}
|
||||
</p>
|
||||
</form>
|
||||
{% else %}
|
||||
{% blocktrans trimmed %}
|
||||
<b>No current domain is configured.</b>
|
||||
|
||||
@ -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
|
||||
|
||||
@ -33,4 +33,15 @@
|
||||
{% endblocktrans %}
|
||||
</p>
|
||||
<p>New users can be registered from any client if public registration is enabled.</p>
|
||||
|
||||
{% if not has_valid_certificate %}
|
||||
<div class="alert alert-warning" role="alert">
|
||||
{% blocktrans %}
|
||||
The configured domain name is using a self-signed certificate.
|
||||
Federation with other Matrix Synapse instances requires a valid TLS certificate.
|
||||
Please go to <a href="/plinth/sys/letsencrypt">Let's Encrypt</a> to obtain one.
|
||||
{% endblocktrans %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
|
||||
@ -30,7 +30,7 @@ from plinth.modules import matrixsynapse
|
||||
from plinth.utils import get_domain_names
|
||||
from plinth.views import ServiceView
|
||||
|
||||
from . import get_public_registration_status
|
||||
from . import get_public_registration_status, has_valid_certificate
|
||||
from .forms import MatrixSynapseForm
|
||||
|
||||
|
||||
@ -86,7 +86,8 @@ class MatrixSynapseServiceView(ServiceView):
|
||||
"""Return the values to fill in the form."""
|
||||
initial = super().get_initial()
|
||||
initial.update({
|
||||
'enable_public_registration': get_public_registration_status()
|
||||
'enable_public_registration': get_public_registration_status(),
|
||||
'has_valid_certificate': has_valid_certificate(),
|
||||
})
|
||||
return initial
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user