quassel: Add let's encrypt component for certficiates

Signed-off-by: Matthias Dellweg <2500@gmx.de>
[sunil@medhas.org Implement set domain as superuser action to make it succeed]
[sunil@medhas.org Minor cosmetic changes]
Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org>
Reviewed-by: Sunil Mohan Adapa <sunil@medhas.org>
This commit is contained in:
Matthias Dellweg 2019-09-21 19:52:11 +02:00 committed by Sunil Mohan Adapa
parent 4c13ef24bb
commit ebd3af340c
No known key found for this signature in database
GPG Key ID: 43EA1CFF0AA7C5F2
5 changed files with 200 additions and 13 deletions

55
actions/quassel Executable file
View File

@ -0,0 +1,55 @@
#!/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 Quassel.
"""
import argparse
import pathlib
def parse_arguments():
"""Return parsed command line arguments as dictionary."""
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers(dest='subcommand', help='Sub command')
subparser = subparsers.add_parser('set-domain',
help='Setup Cockpit configuration')
subparser.add_argument('domain_name', help='Domain name to be allowed')
subparsers.required = True
return parser.parse_args()
def subcommand_set_domain(arguments):
"""Write a file containing domain name."""
domain_file = pathlib.Path('/var/lib/quassel/domain-freedombox')
domain_file.write_text(arguments.domain_name)
def main():
"""Parse arguments and perform all duties."""
arguments = parse_arguments()
subcommand = arguments.subcommand.replace('-', '_')
subcommand_method = globals()['subcommand_' + subcommand]
subcommand_method(arguments)
if __name__ == '__main__':
main()

View File

@ -18,16 +18,19 @@
FreedomBox app for Quassel. FreedomBox app for Quassel.
""" """
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 _
from plinth import action_utils from plinth import action_utils, actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg, frontpage, menu from plinth import cfg, frontpage, menu
from plinth.daemon import Daemon from plinth.daemon import Daemon
from plinth.modules import names
from plinth.modules.firewall.components import Firewall from plinth.modules.firewall.components import Firewall
from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.utils import format_lazy from plinth.utils import format_lazy
from plinth.views import AppView
from .manifest import backup, clients # noqa, pylint: disable=unused-import from .manifest import backup, clients # noqa, pylint: disable=unused-import
@ -37,6 +40,8 @@ managed_services = ['quasselcore']
managed_packages = ['quassel-core'] managed_packages = ['quassel-core']
managed_paths = [pathlib.Path('/var/lib/quassel/')]
name = _('Quassel') name = _('Quassel')
short_description = _('IRC Client') short_description = _('IRC Client')
@ -92,6 +97,15 @@ class QuasselApp(app_module.App):
is_external=True) is_external=True)
self.add(firewall) self.add(firewall)
letsencrypt = LetsEncrypt(
'letsencrypt-quassel', domains=get_domains,
daemons=managed_services, should_copy_certificates=True,
private_key_path='/var/lib/quassel/quasselCert.pem',
certificate_path='/var/lib/quassel/quasselCert.pem',
user_owner='quasselcore', group_owner='quassel',
managing_app='quassel')
self.add(letsencrypt)
daemon = Daemon('daemon-quassel', managed_services[0]) daemon = Daemon('daemon-quassel', managed_services[0])
self.add(daemon) self.add(daemon)
@ -106,20 +120,11 @@ def init():
app.set_enabled(True) app.set_enabled(True)
class QuasselAppView(AppView):
app_id = 'quassel'
diagnostics_module_name = 'quassel'
name = name
description = description
clients = clients
manual_page = manual_page
port_forwarding_info = port_forwarding_info
def setup(helper, old_version=None): def setup(helper, old_version=None):
"""Install and configure the module.""" """Install and configure the module."""
helper.install(managed_packages) helper.install(managed_packages)
helper.call('post', app.enable) helper.call('post', app.enable)
app.get_component('letsencrypt-quassel').setup_certificates()
def diagnose(): def diagnose():
@ -130,3 +135,40 @@ def diagnose():
results.append(action_utils.diagnose_port_listening(4242, 'tcp6')) results.append(action_utils.diagnose_port_listening(4242, 'tcp6'))
return results return results
def get_available_domains():
"""Return an iterator with all domains able to have a certificate."""
return (domain.name for domain in names.components.DomainName.list()
if domain.domain_type.can_have_certificate)
def set_domain(domain):
"""Set the TLS domain by writing a file to data directory."""
if domain:
actions.superuser_run('quassel', ['set-domain', domain])
def get_domain():
"""Read TLS domain from config file select first available if none."""
domain = None
try:
with open('/var/lib/quassel/domain-freedombox') as file_handle:
domain = file_handle.read().strip()
except FileNotFoundError:
pass
if not domain:
domain = next(get_available_domains(), None)
set_domain(domain)
return domain
def get_domains():
"""Return a list with the configured domain for quassel."""
domain = get_domain()
if domain:
return [domain]
return []

View File

@ -0,0 +1,42 @@
#
# 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/>.
#
"""
Forms for Quassel app.
"""
from django import forms
from django.utils.translation import ugettext_lazy as _
from plinth.forms import AppForm
from plinth.modules import quassel
def get_domain_choices():
"""Double domain entries for inclusion in the choice field."""
return ((domain, domain) for domain in quassel.get_available_domains())
class QuasselForm(AppForm):
"""Form to select a TLS domain for Quassel."""
domain = forms.ChoiceField(
choices=get_domain_choices,
label=_('TLS domain'),
help_text=_(
'Select a domain to use TLS with. If the list is empty, please '
'configure at least one domain with certificates.'),
)

View File

@ -20,7 +20,7 @@ URLs for the quassel module.
from django.conf.urls import url from django.conf.urls import url
from plinth.modules.quassel import QuasselAppView from .views import QuasselAppView
urlpatterns = [ urlpatterns = [
url(r'^apps/quassel/$', QuasselAppView.as_view(), name='index'), url(r'^apps/quassel/$', QuasselAppView.as_view(), name='index'),

View File

@ -0,0 +1,48 @@
#
# 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/>.
#
from plinth.modules import quassel
from plinth.views import AppView
from .forms import QuasselForm
class QuasselAppView(AppView):
app_id = 'quassel'
diagnostics_module_name = 'quassel'
name = quassel.name
description = quassel.description
clients = quassel.clients
manual_page = quassel.manual_page
port_forwarding_info = quassel.port_forwarding_info
form_class = QuasselForm
def get_initial(self):
"""Return the values to fill in the form."""
initial = super().get_initial()
initial['domain'] = quassel.get_domain()
return initial
def form_valid(self, form):
"""Change the access control of Radicale service."""
data = form.cleaned_data
if quassel.get_domain() != data['domain']:
quassel.set_domain(data['domain'])
quassel.app.get_component(
'letsencrypt-quassel').setup_certificates()
return super().form_valid(form)