ejabberd: Add let's encrypt component for managing certificates

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:45 -07:00 committed by Joseph Nuthalapati
parent 9fd1b95244
commit 49b543599a
No known key found for this signature in database
GPG Key ID: 5398F00A2FA43C35
3 changed files with 51 additions and 99 deletions

View File

@ -21,11 +21,10 @@ Configuration helper for the ejabberd service
import argparse
import os
import pathlib
import shutil
import socket
import stat
import subprocess
import sys
from distutils.version import LooseVersion as LV
import ruamel.yaml
@ -56,7 +55,10 @@ def parse_arguments():
help='The domain name that will be used by the XMPP service.')
# Setup ejabberd configuration
subparsers.add_parser('setup', help='Setup ejabberd configuration')
setup = subparsers.add_parser('setup', help='Setup ejabberd configuration')
setup.add_argument(
'--domainname',
help='The domain name that will be used by the XMPP service.')
# Prepare ejabberd for hostname change
pre_hostname_change = subparsers.add_parser(
@ -103,7 +105,7 @@ def subcommand_pre_install(arguments):
input=b'ejabberd ejabberd/hostname string ' + domainname.encode())
def subcommand_setup(_):
def subcommand_setup(arguments):
"""Enabled LDAP authentication"""
with open(EJABBERD_CONFIG, 'r') as file_handle:
conf = ruamel.yaml.round_trip_load(file_handle, preserve_quotes=True)
@ -122,7 +124,7 @@ def subcommand_setup(_):
with open(EJABBERD_CONFIG, 'w') as file_handle:
ruamel.yaml.round_trip_dump(conf, file_handle)
upgrade_config()
upgrade_config(arguments.domainname)
try:
subprocess.check_output(['ejabberdctl', 'restart'])
@ -130,7 +132,7 @@ def subcommand_setup(_):
print('Failed to restart ejabberd with new configuration: %s', err)
def upgrade_config():
def upgrade_config(domain):
"""Fix the config file by removing deprecated settings"""
current_version = _get_version()
if not current_version:
@ -154,6 +156,14 @@ def upgrade_config():
if listen_port['port'] == 5280:
listen_port['port'] = 5443
cert_dir = pathlib.Path('/etc/ejabberd/letsencrypt') / domain
cert_file = str(cert_dir / 'ejabberd.pem')
cert_file = ruamel.yaml.scalarstring.DoubleQuotedScalarString(cert_file)
conf['s2s_certfile'] = cert_file
for listen_port in conf['listen']:
if 'certfile' in listen_port:
listen_port['certfile'] = cert_file
# Write changes back to the file
with open(EJABBERD_CONFIG, 'w') as file_handle:
ruamel.yaml.round_trip_dump(conf, file_handle)
@ -220,9 +230,6 @@ def subcommand_change_domainname(arguments):
# If new domainname is blank, use hostname instead.
domainname = socket.gethostname()
action_utils.service_stop('ejabberd')
subprocess.call(['pkill', '-u', 'ejabberd'])
# Add updated domainname to ejabberd hosts list.
with open(EJABBERD_CONFIG, 'r') as file_handle:
conf = ruamel.yaml.round_trip_load(file_handle, preserve_quotes=True)
@ -233,8 +240,6 @@ def subcommand_change_domainname(arguments):
with open(EJABBERD_CONFIG, 'w') as file_handle:
ruamel.yaml.round_trip_dump(conf, file_handle)
action_utils.service_start('ejabberd')
def subcommand_mam(argument):
"""Enable, disable, or get status of Message Archive Management (MAM)."""
@ -284,90 +289,6 @@ def subcommand_mam(argument):
subprocess.call(['ejabberdctl', 'reload_config'])
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 ejabberd 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 open(EJABBERD_CONFIG, 'r') as file_handle:
conf = ruamel.yaml.round_trip_load(file_handle, preserve_quotes=True)
if arguments.domain is not None and arguments.domain not in conf['hosts']:
print('Aborted: Current domain "%s" not configured for ejabberd.' %
arguments.domain)
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
cert_folder = '/etc/ejabberd/letsencrypt/' + arguments.domain
cert_file = cert_folder + '/ejabberd.pem'
if arguments.command == 'add':
le_folder = os.path.join(LE_LIVE_DIRECTORY, current_domain)
le_privkey = os.path.join(le_folder, 'privkey.pem')
le_fullchain = os.path.join(le_folder, 'fullchain.pem')
if not os.path.exists(le_folder):
print('Aborted: No certificate directory at %s.' % le_folder)
sys.exit(3)
if not os.path.exists(cert_folder):
os.makedirs(cert_folder)
shutil.chown(cert_folder, 'ejabberd', 'ejabberd')
with open(cert_file, 'w') as outfile:
with open(le_privkey, 'r') as infile:
for line in infile:
if line.strip():
outfile.write(line)
with open(le_fullchain, 'r') as infile:
for line in infile:
if line.strip():
outfile.write(line)
shutil.chown(cert_file, 'ejabberd', 'ejabberd')
os.chmod(cert_file, stat.S_IRUSR | stat.S_IWUSR)
cert_file = ruamel.yaml.scalarstring.DoubleQuotedScalarString(
cert_file)
conf['s2s_certfile'] = cert_file
for listen_port in conf['listen']:
if 'certfile' in listen_port:
listen_port['certfile'] = cert_file
else: # arguments.command == 'drop' (ensured by parser)
orig_cert_file = ruamel.yaml.scalarstring.DoubleQuotedScalarString(
EJABBERD_ORIG_CERT)
for listen_port in conf['listen']:
if 'certfile' in listen_port \
and listen_port['certfile'] == cert_file:
listen_port['certfile'] = orig_cert_file
if conf['s2s_certfile'] == cert_file:
conf['s2s_certfile'] = orig_cert_file
if os.path.exists(cert_folder):
shutil.rmtree(cert_folder)
with open(EJABBERD_CONFIG, 'w') as file_handle:
ruamel.yaml.round_trip_dump(conf, file_handle)
if action_utils.service_is_running('ejabberd'):
action_utils.service_restart('ejabberd')
def _get_version():
""" Get the current ejabberd version """
try:

View File

@ -19,6 +19,7 @@ FreedomBox app to configure ejabberd server.
"""
import logging
import pathlib
from django.urls import reverse_lazy
from django.utils.translation import ugettext_lazy as _
@ -30,6 +31,7 @@ from plinth.daemon import Daemon
from plinth.modules import config
from plinth.modules.apache.components import Webserver
from plinth.modules.firewall.components import Firewall
from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.signals import (domainname_change, post_hostname_change,
pre_hostname_change)
from plinth.utils import format_lazy
@ -42,6 +44,8 @@ managed_services = ['ejabberd']
managed_packages = ['ejabberd']
managed_paths = [pathlib.Path('/etc/ejabberd/')]
name = _('ejabberd')
short_description = _('Chat Server')
@ -104,6 +108,15 @@ class EjabberdApp(app_module.App):
webserver = Webserver('webserver-ejabberd', 'jwchat-plinth')
self.add(webserver)
letsencrypt = LetsEncrypt(
'letsencrypt-ejabberd', domains=get_domains, daemons=['ejabberd'],
should_copy_certificates=True,
private_key_path='/etc/ejabberd/letsencrypt/{domain}/ejabberd.pem',
certificate_path='/etc/ejabberd/letsencrypt/{domain}/ejabberd.pem',
user_owner='ejabberd', group_owner='ejabberd',
managing_app='ejabberd')
self.add(letsencrypt)
daemon = Daemon('daemon-ejabberd', managed_services[0])
self.add(daemon)
@ -129,11 +142,29 @@ def setup(helper, old_version=None):
helper.call('pre', actions.superuser_run, 'ejabberd',
['pre-install', '--domainname', domainname])
# XXX: Configure all other domain names
helper.install(managed_packages)
helper.call('post', actions.superuser_run, 'ejabberd', ['setup'])
helper.call('post',
app.get_component('letsencrypt-ejabberd').setup_certificates,
[domainname])
helper.call('post', actions.superuser_run, 'ejabberd',
['setup', '--domainname', domainname])
helper.call('post', app.enable)
def get_domains():
"""Return the list of domains that ejabberd is interested in.
XXX: Retrieve the list from ejabberd configuration.
"""
setup_helper = globals()['setup_helper']
if setup_helper.get_state() == 'needs-setup':
return []
return [config.get_domainname()]
def on_pre_hostname_change(sender, old_hostname, new_hostname, **kwargs):
"""
Backup ejabberd database before hostname is changed.
@ -165,8 +196,8 @@ def on_domainname_change(sender, old_domainname, new_domainname, **kwargs):
del kwargs # Unused
actions.superuser_run(
'ejabberd', ['change-domainname', '--domainname', new_domainname],
run_in_background=True)
'ejabberd', ['change-domainname', '--domainname', new_domainname])
app.get_component('letsencrypt-ejabberd').setup_certificates()
def diagnose():

View File

@ -47,7 +47,7 @@ class EjabberdAppView(AppView):
def get_context_data(self, *args, **kwargs):
"""Add service to the context data."""
context = super().get_context_data(*args, **kwargs)
context['domainname'] = config.get_domainname()
context['domainname'] = ejabberd.get_domains()[0]
context['clients'] = ejabberd.clients
return context