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.
"""
import pathlib
from django.urls import reverse_lazy
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 cfg, frontpage, menu
from plinth.daemon import Daemon
from plinth.modules import names
from plinth.modules.firewall.components import Firewall
from plinth.modules.letsencrypt.components import LetsEncrypt
from plinth.utils import format_lazy
from plinth.views import AppView
from .manifest import backup, clients # noqa, pylint: disable=unused-import
@ -37,6 +40,8 @@ managed_services = ['quasselcore']
managed_packages = ['quassel-core']
managed_paths = [pathlib.Path('/var/lib/quassel/')]
name = _('Quassel')
short_description = _('IRC Client')
@ -92,6 +97,15 @@ class QuasselApp(app_module.App):
is_external=True)
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])
self.add(daemon)
@ -106,20 +120,11 @@ def init():
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):
"""Install and configure the module."""
helper.install(managed_packages)
helper.call('post', app.enable)
app.get_component('letsencrypt-quassel').setup_certificates()
def diagnose():
@ -130,3 +135,40 @@ def diagnose():
results.append(action_utils.diagnose_port_listening(4242, 'tcp6'))
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 plinth.modules.quassel import QuasselAppView
from .views import QuasselAppView
urlpatterns = [
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)