Commit 49b54359 authored by Sunil Mohan Adapa's avatar Sunil Mohan Adapa Committed by Joseph Nuthalapati

ejabberd: Add let's encrypt component for managing certificates

Signed-off-by: Sunil Mohan Adapa's avatarSunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Joseph Nuthalapati's avatarJoseph Nuthalapati <njoseph@thoughtworks.com>
parent 9fd1b952
......@@ -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:
......
......@@ -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():
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment