mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-09-19 04:59:01 +00:00
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:
parent
c47a99b25e
commit
9fd1b95244
@ -20,10 +20,6 @@ Configuration helper for Matrix-Synapse server.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import filecmp
|
|
||||||
import os
|
|
||||||
import shutil
|
|
||||||
import sys
|
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
@ -59,67 +55,6 @@ def parse_arguments():
|
|||||||
return parser.parse_args()
|
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(_):
|
def subcommand_post_install(_):
|
||||||
"""Perform post installation configuration."""
|
"""Perform post installation configuration."""
|
||||||
with open(CONFIG_FILE_PATH) as config_file:
|
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:
|
with open(CONFIG_FILE_PATH, 'w') as config_file:
|
||||||
yaml.dump(config, 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):
|
def subcommand_setup(arguments):
|
||||||
"""Configure the domain name for matrix-synapse package."""
|
"""Configure the domain name for matrix-synapse package."""
|
||||||
domain_name = arguments.domain_name
|
domain_name = arguments.domain_name
|
||||||
action_utils.dpkg_reconfigure('matrix-synapse',
|
action_utils.dpkg_reconfigure('matrix-synapse',
|
||||||
{'server-name': domain_name})
|
{'server-name': domain_name})
|
||||||
_update_tls_certificate()
|
|
||||||
|
|
||||||
|
|
||||||
def subcommand_public_registration(argument):
|
def subcommand_public_registration(argument):
|
||||||
@ -190,40 +119,6 @@ def subcommand_public_registration(argument):
|
|||||||
action_utils.service_restart('matrix-synapse')
|
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():
|
def main():
|
||||||
arguments = parse_arguments()
|
arguments = parse_arguments()
|
||||||
sub_command = arguments.subcommand.replace('-', '_')
|
sub_command = arguments.subcommand.replace('-', '_')
|
||||||
|
|||||||
@ -20,6 +20,7 @@ FreedomBox app to configure matrix-synapse server.
|
|||||||
|
|
||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
import pathlib
|
||||||
|
|
||||||
from django.urls import reverse_lazy
|
from django.urls import reverse_lazy
|
||||||
from django.utils.translation import ugettext_lazy as _
|
from django.utils.translation import ugettext_lazy as _
|
||||||
@ -31,6 +32,7 @@ from plinth import frontpage, menu
|
|||||||
from plinth.daemon import Daemon
|
from plinth.daemon import Daemon
|
||||||
from plinth.modules.apache.components import Webserver
|
from plinth.modules.apache.components import Webserver
|
||||||
from plinth.modules.firewall.components import Firewall
|
from plinth.modules.firewall.components import Firewall
|
||||||
|
from plinth.modules.letsencrypt.components import LetsEncrypt
|
||||||
|
|
||||||
from .manifest import backup, clients
|
from .manifest import backup, clients
|
||||||
|
|
||||||
@ -40,6 +42,8 @@ managed_services = ['matrix-synapse']
|
|||||||
|
|
||||||
managed_packages = ['matrix-synapse', 'matrix-synapse-ldap3']
|
managed_packages = ['matrix-synapse', 'matrix-synapse-ldap3']
|
||||||
|
|
||||||
|
managed_paths = [pathlib.Path('/etc/matrix-synapse/')]
|
||||||
|
|
||||||
name = _('Matrix Synapse')
|
name = _('Matrix Synapse')
|
||||||
|
|
||||||
short_description = _('Chat Server')
|
short_description = _('Chat Server')
|
||||||
@ -101,6 +105,16 @@ class MatrixSynapseApp(app_module.App):
|
|||||||
'matrix-synapse-plinth')
|
'matrix-synapse-plinth')
|
||||||
self.add(webserver)
|
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])
|
daemon = Daemon('daemon-matrixsynapse', managed_services[0])
|
||||||
self.add(daemon)
|
self.add(daemon)
|
||||||
|
|
||||||
@ -121,6 +135,15 @@ def setup(helper, old_version=None):
|
|||||||
helper.call('post', actions.superuser_run, 'matrixsynapse',
|
helper.call('post', actions.superuser_run, 'matrixsynapse',
|
||||||
['post-install'])
|
['post-install'])
|
||||||
helper.call('post', app.enable)
|
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():
|
def is_setup():
|
||||||
@ -141,6 +164,15 @@ def diagnose():
|
|||||||
return results
|
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():
|
def get_configured_domain_name():
|
||||||
"""Return the currently configured domain name."""
|
"""Return the currently configured domain name."""
|
||||||
if not is_setup():
|
if not is_setup():
|
||||||
@ -159,8 +191,10 @@ def get_public_registration_status():
|
|||||||
return output.strip() == 'enabled'
|
return output.strip() == 'enabled'
|
||||||
|
|
||||||
|
|
||||||
def has_valid_certificate():
|
def get_certificate_status():
|
||||||
"""Return whether the configured domain name has a valid certificate."""
|
"""Return the status of certificate for the configured domain."""
|
||||||
status = actions.superuser_run('matrixsynapse',
|
status = app.get_component('letsencrypt-matrixsynapse').get_status()
|
||||||
['letsencrypt', 'get-status'])
|
if not status:
|
||||||
return status.startswith('valid')
|
return 'no-domains'
|
||||||
|
|
||||||
|
return list(status.values())[0]
|
||||||
|
|||||||
@ -39,7 +39,7 @@
|
|||||||
{% endblocktrans %}
|
{% endblocktrans %}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
{% if not has_valid_certificate %}
|
{% if certificate_status != "valid" %}
|
||||||
<div class="alert alert-warning" role="alert">
|
<div class="alert alert-warning" role="alert">
|
||||||
{% url 'letsencrypt:index' as letsencrypt_url %}
|
{% url 'letsencrypt:index' as letsencrypt_url %}
|
||||||
{% blocktrans %}
|
{% blocktrans %}
|
||||||
|
|||||||
@ -30,7 +30,7 @@ from plinth.modules import matrixsynapse
|
|||||||
from plinth.utils import get_domain_names
|
from plinth.utils import get_domain_names
|
||||||
from plinth.views import AppView
|
from plinth.views import AppView
|
||||||
|
|
||||||
from . import get_public_registration_status, has_valid_certificate
|
from . import get_public_registration_status
|
||||||
from .forms import MatrixSynapseForm
|
from .forms import MatrixSynapseForm
|
||||||
|
|
||||||
|
|
||||||
@ -42,10 +42,7 @@ class SetupView(FormView):
|
|||||||
|
|
||||||
def form_valid(self, form):
|
def form_valid(self, form):
|
||||||
"""Handle valid form submission."""
|
"""Handle valid form submission."""
|
||||||
domain_name = form.cleaned_data['domain_name']
|
matrixsynapse.setup_domain(form.cleaned_data['domain_name'])
|
||||||
actions.superuser_run('matrixsynapse',
|
|
||||||
['setup', '--domain-name', domain_name])
|
|
||||||
|
|
||||||
return super().form_valid(form)
|
return super().form_valid(form)
|
||||||
|
|
||||||
def get_context_data(self, *args, **kwargs):
|
def get_context_data(self, *args, **kwargs):
|
||||||
@ -82,7 +79,7 @@ class MatrixSynapseAppView(AppView):
|
|||||||
context['domain_name'] = matrixsynapse.get_configured_domain_name()
|
context['domain_name'] = matrixsynapse.get_configured_domain_name()
|
||||||
context['clients'] = matrixsynapse.clients
|
context['clients'] = matrixsynapse.clients
|
||||||
context['manual_page'] = matrixsynapse.manual_page
|
context['manual_page'] = matrixsynapse.manual_page
|
||||||
context['has_valid_certificate'] = has_valid_certificate()
|
context['certificate_status'] = matrixsynapse.get_certificate_status()
|
||||||
return context
|
return context
|
||||||
|
|
||||||
def get_initial(self):
|
def get_initial(self):
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user