letsencrypt: Remove renewal hooks implementation

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-11 19:30:37 -07:00 committed by Joseph Nuthalapati
parent a134311663
commit d8647aaf18
No known key found for this signature in database
GPG Key ID: 5398F00A2FA43C35
5 changed files with 54 additions and 646 deletions

View File

@ -22,15 +22,11 @@ Configuration helper for Let's Encrypt.
import argparse import argparse
import json import json
import os import os
import shutil import re
import subprocess import subprocess
import sys import sys
import re
import configobj
from plinth import action_utils from plinth import action_utils
from plinth.errors import ActionError
from plinth.modules import config
from plinth.modules import letsencrypt as le from plinth.modules import letsencrypt as le
TEST_MODE = False TEST_MODE = False
@ -69,43 +65,18 @@ def parse_arguments():
delete_parser.add_argument('--domain', required=True, delete_parser.add_argument('--domain', required=True,
help='Domain name to delete certificate of') help='Domain name to delete certificate of')
help_hooks = 'FreedomBox management of certificates (for current domain).' help_hooks = 'Does nothing, kept for compatibility.'
manage_hook_parser = subparsers.add_parser('manage_hooks', help=help_hooks) subparser = subparsers.add_parser('run_pre_hooks', help=help_hooks)
manage_hook_parser.add_argument('command', help=help_hooks, subparser.add_argument('--domain')
choices=('enable', 'disable', 'status')) subparser.add_argument('--modules', nargs='+', default=[])
manage_hook_parser.add_argument('--domain',
help='Domain for hook management command.')
help_module_arg = 'For enable: Also use LE cert with other FreedomBox ' \
'apps.'
manage_hook_parser.add_argument('--modules', help=help_module_arg,
nargs='+', default=[],
choices=le.MODULES_WITH_HOOKS)
help_domain_arg = 'Domain name to run the hook scripts with.' subparser = subparsers.add_parser('run_renew_hooks', help=help_hooks)
help_module_arg = 'Include hooks from the provided module names.' subparser.add_argument('--domain')
help_pre_hooks = 'Maintenance tasks before a cert is obtained or renewed.' subparser.add_argument('--modules', nargs='+', default=[])
run_pre_hooks_parser = subparsers.add_parser('run_pre_hooks',
help=help_pre_hooks)
run_pre_hooks_parser.add_argument('--domain', help=help_domain_arg)
run_pre_hooks_parser.add_argument('--modules', help=help_module_arg,
nargs='+', default=[],
choices=le.MODULES_WITH_HOOKS)
help_renew_hooks = 'Maintenance tasks after a cert is actually renewed.' subparser = subparsers.add_parser('run_post_hooks', help=help_hooks)
run_renew_hooks_parser = subparsers.add_parser('run_renew_hooks', subparser.add_argument('--domain')
help=help_renew_hooks) subparser.add_argument('--modules', nargs='+', default=[])
run_renew_hooks_parser.add_argument('--domain', help=help_domain_arg)
run_renew_hooks_parser.add_argument('--modules', help=help_module_arg,
nargs='+', default=[],
choices=le.MODULES_WITH_HOOKS)
help_post_hooks = 'Maintenance tasks after a cert is obtained or renewed.'
run_post_hooks_parser = subparsers.add_parser('run_post_hooks',
help=help_post_hooks)
run_post_hooks_parser.add_argument('--domain', help=help_domain_arg)
run_post_hooks_parser.add_argument('--modules', help=help_module_arg,
nargs='+', default=[],
choices=le.MODULES_WITH_HOOKS)
subparsers.required = True subparsers.required = True
return parser.parse_args() return parser.parse_args()
@ -231,259 +202,43 @@ def subcommand_obtain(arguments):
setup_webserver_config(domain, webserver_change) setup_webserver_config(domain, webserver_change)
def subcommand_manage_hooks(arguments): def subcommand_run_pre_hooks(_):
"""Do nothing, kept for legacy LE configuration.
If new version of Plinth is deployed and before it can update the Let's
Encrypt configuration and remove these old hooks, if a renew operation is
run, then we don't want it to exit with non-zero error code because this
hook could not be run.
Remove at some point in the future.
""" """
Enable/disable/status of certbot's pre-, renew-, & post-hooks for renewal.
Enable edits renewal config of current domain (RENEWAL_DIR/DOMAIN.config),
and creates a backup beforehand that is restored if disable is called. def subcommand_run_renew_hooks(_):
Commands disable and status work without error on any domain string. """Do nothing, kept for legacy LE configuration.
If new version of Plinth is deployed and before it can update the Let's
Encrypt configuration and remove these old hooks, if a renew operation is
run, then we don't want it to exit with non-zero error code because this
hook could not be run.
Remove at some point in the future.
""" """
if arguments.command not in ('enable', 'disable', 'status'):
print('Aborted: Unknown command "%s".' % arguments.command)
sys.exit(1)
cmd_is_enable = arguments.command == 'enable'
current_domain = config.get_domainname()
if not arguments.domain:
arguments.domain = current_domain
if cmd_is_enable:
if not current_domain:
print('Aborted: No current domain set.')
sys.exit(2)
if not arguments.domain == current_domain:
print('Aborted: Passed domain (%s) is not current domain (%s).' %
(arguments.domain, current_domain))
sys.exit(3)
domain_status = get_status()
if current_domain not in domain_status:
print('Aborted: Current domain (%s) has no LE certificate.' %
current_domain)
sys.exit(4)
config_path = RENEWAL_DIRECTORY + arguments.domain + '.conf'
if not os.path.exists(config_path):
msg, code = ('Aborted', 5) if cmd_is_enable else ('Disabled', 0)
print('%s: No certbot config file at %s.' % (msg, config_path))
sys.exit(code)
config_certbot = configobj.ConfigObj(config_path)
if 'renewalparams' not in config_certbot:
msg, code = ('Aborted', 6) if cmd_is_enable else ('Disabled', 0)
print('%s: No section [renewalparams] in config file at %s.' %
(msg, config_path))
sys.exit(code)
script_path = os.path.realpath(__file__)
config_backup_path = config_path + '_plinth_backup'
call_pre = script_path + ' run_pre_hooks --domain ' + arguments.domain
call_renew = script_path + ' run_renew_hooks --domain ' + arguments.domain
call_post = script_path + ' run_post_hooks --domain ' + arguments.domain
config_plinth = {
'renewalparams': {
'authenticator': AUTHENTICATOR,
# 'webroot_path': [WEB_ROOT_PATH], # removed by renew...
'webroot_map': {
arguments.domain: WEB_ROOT_PATH
},
'installer': 'None',
'pre_hook': call_pre,
'renew_hook': call_renew,
'post_hook': call_post
}
}
comment_plinth = '# This file was edited by Plinth.'
config_edited_by_plinth = any([
'edited by plinth' in line.lower()
for line in config_certbot.initial_comment
])
if arguments.command == 'status':
# check for presence of expected minimal configuration
config_checks = [
(entry in config_certbot['renewalparams'])
and (str(config_plinth['renewalparams'][entry]) in str(
config_certbot['renewalparams'][entry]))
for entry in config_plinth['renewalparams'].keys()
]
if not all(config_checks):
print('disabled')
sys.exit(0)
# is enabled; check for which selected modules (only for renew_hook)
cmd_str = config_certbot['renewalparams']['renew_hook']
module_list = []
for mod_str in le.MODULES_WITH_HOOKS:
mod_pattern = 'letsencrypt .*--modules .*%s.*' % mod_str
match = re.search(mod_pattern, cmd_str)
if match is not None:
module_list.append(mod_str)
if module_list != []:
print('enabled, with modules: ' + ', '.join(module_list))
else:
print('enabled, without modules')
elif arguments.command == 'enable':
if not config_edited_by_plinth:
shutil.copy(config_path, config_backup_path)
config_certbot.initial_comment.append(comment_plinth)
if arguments.modules != []:
call_renew += ' --modules ' + ' '.join(arguments.modules)
config_plinth['renewalparams']['renew_hook'] = call_renew
config_certbot['renewalparams'].update(config_plinth['renewalparams'])
config_certbot.write()
if arguments.modules != []:
print('enabled, with modules: ' + ', '.join(arguments.modules))
else:
print('enabled, without modules')
elif arguments.command == 'disable':
# if changed, restore from backup; refuse disabling if no backup exists
if not config_edited_by_plinth:
print('Disabled: Nothing to do, hook management was not enabled.')
elif os.path.exists(config_backup_path):
shutil.move(config_backup_path, config_path)
print('disabled successfully')
else:
print('Aborted: No backup config file at %s.' % config_backup_path)
sys.exit(7)
sys.exit(0)
def subcommand_run_pre_hooks(arguments): def subcommand_run_post_hooks(_):
"""Do nothing, kept for legacy LE configuration.
If new version of Plinth is deployed and before it can update the Let's
Encrypt configuration and remove these old hooks, if a renew operation is
run, then we don't want it to exit with non-zero error code because this
hook could not be run.
Remove at some point in the future.
""" """
Execute all needed maintenance tasks BEFORE a cert is obtained/renewed.
If registered as certbot's pre-hook, this script gets ALWAYS executed when
certbot attempts a renewal, irrespective of necessity/success (2x per day).
"""
# Require current domain, to avoid confusion (e.g. call from old cron job).
if not arguments.domain:
print('Aborted: You must specify the current domain.')
sys.exit(1)
current_domain = config.get_domainname()
if not arguments.domain == current_domain:
print('Aborted: Current domain is %s, but called for %s.' %
(current_domain, arguments.domain))
sys.exit(2)
sys.exit(0)
def subcommand_run_renew_hooks(arguments):
"""
Execute ALL maintenance tasks when (just after) certbot renews a cert, i.e.
run all tasks of every app/plinth module that supports usage of LE certs.
If registered as certbot's renew-hook, this script gets ONLY executed when
certbot successfully renewed a certificate; with Debian default config,
this means it would run about once every 60 days (renewals get executed
if a cert is <30 days before expiry, and current default is 90 days).
Errors will be logged by certbot to /var/log/letsencrypt/letsencrypt.log.
"""
# Require current domain, to avoid confusion (e.g. call from old cron job).
if not arguments.domain:
print('Aborted: You must specify the current domain.')
sys.exit(1)
current_domain = config.get_domainname()
if not arguments.domain == current_domain:
print('Aborted: Current domain is %s, but called for %s.' %
(current_domain, arguments.domain))
sys.exit(2)
if action_utils.service_is_running('apache2'):
action_utils.service_restart('apache2')
for module in arguments.modules:
# If >1 modules, collect errors and raise just one in the end,
# for certbot to log ALL failed attempts, not just 1st fail.
error_messages = []
try:
_run_action(module, ['letsencrypt', 'add'])
except Exception as err:
error_messages.append(err.message)
if error_messages:
raise ActionError(message="\n".join(error_messages))
sys.exit(3)
sys.exit(0)
def _run_action(action, action_options=None):
"""
Run a specific action from another module, for the run_renew_hooks command.
This function is a simplified version of plinth/actions.py, to enable
somewhat safe calls of other actions from outside the FreedomBox Service
(Plinth) process. The comments about the action contracts refer to
plinth/actions.py.
"""
if action_options is None:
action_options = []
# Contract 3A and 3B: don't call anything outside of the actions directory.
# Assume the current path is the actions directly.
script_path = os.path.realpath(__file__)
actions_dir, _ = os.path.split(script_path)
if os.sep in action:
raise ValueError('Action cannot contain: ' + os.sep)
cmd = os.path.join(actions_dir, action)
if not os.path.realpath(cmd).startswith(actions_dir):
raise ValueError('Action has to be in directory %s' % actions_dir)
# Contract 3C: interpret shell escape sequences as literal file names.
# Contract 3E: fail if the action doesn't exist or exists elsewhere.
if not os.access(cmd, os.F_OK):
raise ValueError('Action must exist in action directory.')
cmd = [cmd]
# Contract: 3C, 3D: don't allow shell special characters in
# options be interpreted by the shell.
if action_options:
if not isinstance(action_options, (list, tuple)):
raise ValueError('Options must be list or tuple.')
cmd += list(action_options) # No escaping necessary
# Contract 3C: don't interpret shell escape sequences.
# Contract 5 (and 6-ish).
proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
stderr=subprocess.PIPE, shell=False)
output, error = proc.communicate()
output, error = output.decode(), error.decode()
if proc.returncode != 0:
raise ActionError(action, output, error)
def subcommand_run_post_hooks(arguments):
"""
Execute all needed maintenance tasks AFTER a cert is obtained/renewed.
If registered as certbot's post-hook, this script gets ALWAYS executed when
certbot attempts a renewal, irrespective of necessity/success (2x per day).
"""
# Require current domain, to avoid confusion (e.g. call from old cron job).
if not arguments.domain:
print('Aborted: You must specify the current domain.')
sys.exit(1)
current_domain = config.get_domainname()
if not arguments.domain == current_domain:
print('Aborted: Current domain is %s, but called for %s.' %
(current_domain, arguments.domain))
sys.exit(2)
sys.exit(0)
def subcommand_delete(arguments): def subcommand_delete(arguments):

View File

@ -25,7 +25,7 @@ from django.utils.translation import ugettext_lazy as _
from plinth import action_utils, actions from plinth import action_utils, actions
from plinth import app as app_module from plinth import app as app_module
from plinth import cfg, menu, module_loader from plinth import cfg, menu
from plinth.errors import ActionError from plinth.errors import ActionError
from plinth.modules import config, names from plinth.modules import config, names
from plinth.signals import domain_added, domain_removed, domainname_change from plinth.signals import domain_added, domain_removed, domainname_change
@ -62,7 +62,6 @@ description = [
manual_page = 'LetsEncrypt' manual_page = 'LetsEncrypt'
MODULES_WITH_HOOKS = ['ejabberd', 'matrixsynapse']
LIVE_DIRECTORY = '/etc/letsencrypt/live/' LIVE_DIRECTORY = '/etc/letsencrypt/live/'
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -120,56 +119,12 @@ def try_action(domain, action):
actions.superuser_run('letsencrypt', [action, '--domain', domain]) actions.superuser_run('letsencrypt', [action, '--domain', domain])
def enable_renewal_management(domain):
if domain == config.get_domainname():
try:
actions.superuser_run('letsencrypt', ['manage_hooks', 'enable'])
logger.info(
_('Certificate renewal management enabled for {domain}.').
format(domain=domain))
except ActionError as exception:
logger.error(
_('Failed to enable certificate renewal management for '
'{domain}: {error}').format(domain=domain,
error=exception.args[2]))
def on_domainname_change(sender, old_domainname, new_domainname, **kwargs): def on_domainname_change(sender, old_domainname, new_domainname, **kwargs):
"""Disable renewal hook management after a domain name change.""" """Drop the certificate after a domain name change."""
del sender # Unused del sender # Unused
del new_domainname # Unused del new_domainname # Unused
del kwargs # Unused del kwargs # Unused
for module in MODULES_WITH_HOOKS:
actions.superuser_run(
module, ['letsencrypt', 'drop', '--domain', old_domainname],
run_in_background=True)
actions.superuser_run(
'letsencrypt', ['manage_hooks', 'disable', '--domain', old_domainname],
run_in_background=True)
def get_manage_hooks_status():
"""Return status of hook management for current domain."""
try:
output = actions.superuser_run('letsencrypt',
['manage_hooks', 'status'])
except ActionError:
return False
return output.strip()
def get_installed_modules():
installed_modules = [
module_name
for module_name, module in module_loader.loaded_modules.items()
if module_name in MODULES_WITH_HOOKS
and module.setup_helper.get_state() == 'up-to-date'
]
return installed_modules
def on_domain_added(sender, domain_type='', name='', description='', def on_domain_added(sender, domain_type='', name='', description='',
services=None, **kwargs): services=None, **kwargs):
@ -189,7 +144,6 @@ def on_domain_added(sender, domain_type='', name='', description='',
if sender != 'test' and name: if sender != 'test' and name:
logger.info("Obtaining a Let\'s Encrypt certificate for " + name) logger.info("Obtaining a Let\'s Encrypt certificate for " + name)
try_action(name, 'obtain') try_action(name, 'obtain')
enable_renewal_management(name)
return True return True
except ActionError as ex: except ActionError as ex:
return False return False
@ -220,8 +174,6 @@ def get_status():
curr_dom, curr_dom,
'has_cert': (curr_dom in status['domains'] 'has_cert': (curr_dom in status['domains']
and status['domains'][curr_dom]['certificate_available']), and status['domains'][curr_dom]['certificate_available']),
'manage_hooks_status':
get_manage_hooks_status()
} }
status['current_domain'] = current_domain status['current_domain'] = current_domain

View File

@ -35,7 +35,7 @@
{% block configuration %} {% block configuration %}
<h3>{% trans "Configuration" %}</h3> <h3>{% trans "Status" %}</h3>
{% if status.domains %} {% if status.domains %}
<table class="table table-bordered table-condensed table-striped"> <table class="table table-bordered table-condensed table-striped">
@ -135,191 +135,6 @@
{% include "diagnostics_button.html" with module="letsencrypt" enabled=True %} {% include "diagnostics_button.html" with module="letsencrypt" enabled=True %}
<h4>{% trans "Certificate renewal management and use by other modules" %}</h4>
<p class="help-block">
{% blocktrans trimmed %}
If you have a Let's Encrypt certificate for your current domain, you may
let {{ box_name }} manage its renewal process. This also enables other apps
to use that certificate, so that users would not prompted with security
warnings when using them.
{% endblocktrans %}
</p>
{% if status.current_domain.name %}
<form class="form" method="post"
action="{% url 'letsencrypt:toggle_hooks' status.current_domain.name %}">
{% csrf_token %}
<div class="checkbox">
<label>
{% if status.current_domain.has_cert %}
<input type="checkbox" name="toggle_hooks" id="id_toggle_hooks"
{% if 'enabled' in status.current_domain.manage_hooks_status %}
checked
{% endif %}
onchange="this.form.submit();">
</input>
<noscript>
<button class="btn btn-sm btn-default" type="submit">
{% trans "Update config" %}</button>
</noscript>
{% else %}
<input type="checkbox" name="toggle_hooks" id="id_toggle_hooks" disabled></input>
<noscript>
<button class="btn btn-sm btn-default disabled" type="submit">
{% trans "Update config" %}</button>
</noscript>
{% endif %}
<span>
{% blocktrans trimmed with current_domain=status.current_domain.name %}
Let {{ box_name }} manage certificate renewal of
<b>{{ current_domain }}</b>
{% endblocktrans %}
</span>
</label>
</div>
<p class="help-block">
{% if status.current_domain.has_cert %}
{% blocktrans trimmed %}
If enabled, {{ box_name }} can make sure that all apps can use the
certificate as soon as it is renewed.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
<b>No certificate available for the current domain.</b>
First obtain a certificate to enable its management.
{% endblocktrans %}
{% endif %}
</p>
</form>
<form class="form" method="post"
action="{% url 'letsencrypt:toggle_module' domain=status.current_domain.name module='ejabberd' %}">
{% csrf_token %}
<div class="checkbox">
<label>
{% if 'ejabberd' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
<input type="checkbox" name="ejabberd" id="ejabberd"
{% if 'ejabberd' in status.current_domain.manage_hooks_status %}
checked
{% endif %}
onchange="this.form.submit();">
</input>
<noscript>
<button class="btn btn-sm btn-default" type="submit">
{% trans "Update config" %}</button>
</noscript>
{% else %}
<input type="checkbox" name="ejabberd" id="ejabberd" disabled></input>
<noscript>
<button class="btn btn-sm btn-default disabled" type="submit">
{% trans "Update config" %}</button>
</noscript>
{% endif %}
<span>
{% if 'ejabberd' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
{% blocktrans trimmed with current_domain=status.current_domain.name %}
Use certificate of {{ current_domain }} for <b>ejabberd</b>
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
Use certificate of the current domain for <b>ejabberd</b>
{% endblocktrans %}
{% endif %}
</span>
</label>
</div>
<p class="help-block">
{% url 'ejabberd:index' as ejabberd_url %}
{% if 'ejabberd' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
{% blocktrans trimmed %}
If enabled, the app <a href="{{ ejabberd_url }}">ejabberd</a>
will also use the Let's Encrypt certificate.
This will reduce warnings about self-signed certificates in
client applications, and enable more wide-spread federation with
other XMPP servers in the Internet.
{% endblocktrans %}
{% elif 'ejabberd' not in installed_modules %}
{% blocktrans trimmed %}
This feature only makes sense if you are using the
<a href="{{ ejabberd_url }}">ejabberd</a> chat server app.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
To use a Let's Encrypt certificate for
<a href="{{ ejabberd_url }}">ejabberd</a> chat server app, you
must first enable certificate renewal of the current domain.
{% endblocktrans %}
{% endif %}
</p>
</form>
<form class="form" method="post"
action="{% url 'letsencrypt:toggle_module' domain=status.current_domain.name module='matrixsynapse' %}">
{% csrf_token %}
<div class="checkbox">
<label>
{% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
<input type="checkbox" name="matrixsynapse" id="matrixsynapse"
{% if 'matrixsynapse' in status.current_domain.manage_hooks_status %}
checked
{% endif %}
onchange="this.form.submit();">
</input>
<noscript>
<button class="btn btn-sm btn-default" type="submit">
{% trans "Update config" %}</button>
</noscript>
{% else %}
<input type="checkbox" name="matrixsynapse" id="matrixsynapse"
disabled></input>
<noscript>
<button class="btn btn-sm btn-default disabled" type="submit">
{% trans "Update config" %}</button>
</noscript>
{% endif %}
<span>
{% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
{% blocktrans trimmed with current_domain=status.current_domain.name %}
Use certificate of {{ current_domain }} for <b>matrixsynapse</b>
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
Use certificate of the current domain for <b>matrixsynapse</b>
{% endblocktrans %}
{% endif %}
</span>
</label>
</div>
<p class="help-block">
{% url 'matrixsynapse:index' as matrixsynapse_url %}
{% if 'matrixsynapse' in installed_modules and 'enabled' in status.current_domain.manage_hooks_status %}
{% blocktrans trimmed %}
If enabled, the app <a href="{{ matrixsynapse_url }}">Matrix Synapse</a>
will also use the same Let's Encrypt certificate.
Other Matrix Synapse instances running version 1.0 or greater expect
your server to produce a valid TLS certificate. Federation with other
instances will not work without this.
{% endblocktrans %}
{% elif 'matrixsynapse' not in installed_modules %}
{% blocktrans trimmed %}
This feature only makes sense if you are using the
<a href="{{ matrixsynapse_url }}">Matrix Synapse</a> chat server app.
{% endblocktrans %}
{% else %}
{% blocktrans trimmed %}
To use a Let's Encrypt certificate for
<a href="{{ matrixsynapse_url }}">Matrix Synapse</a> chat server app, you
must first enable certificate renewal of the current domain.
{% endblocktrans %}
{% endif %}
</p>
</form>
{% else %}
{% blocktrans trimmed %}
<b>No current domain is configured.</b>
First configure a domain to enable management of its certificates.
{% endblocktrans %}
{% endif %}
{% else %} {% else %}
{% url 'config:index' as config_url %} {% url 'config:index' as config_url %}
{% blocktrans trimmed %} {% blocktrans trimmed %}

View File

@ -14,14 +14,13 @@
# You should have received a copy of the GNU Affero General Public License # 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/>. # along with this program. If not, see <http://www.gnu.org/licenses/>.
# #
""" """
URLs for the Let's Encrypt module. URLs for the Let's Encrypt module.
""" """
from django.conf.urls import url from django.conf.urls import url
from . import views
from . import views
urlpatterns = [ urlpatterns = [
url(r'^sys/letsencrypt/$', views.index, name='index'), url(r'^sys/letsencrypt/$', views.index, name='index'),
@ -31,8 +30,4 @@ urlpatterns = [
name='obtain'), name='obtain'),
url(r'^sys/letsencrypt/delete/(?P<domain>[^/]+)/$', views.delete, url(r'^sys/letsencrypt/delete/(?P<domain>[^/]+)/$', views.delete,
name='delete'), name='delete'),
url(r'^sys/letsencrypt/toggle_hooks/(?P<domain>[^/]+)/$',
views.toggle_hooks, name='toggle_hooks'),
url(r'^sys/letsencrypt/toggle_module/(?P<domain>[^/]+)/'
'(?P<module>[^/]+)/$', views.toggle_module, name='toggle_module'),
] ]

View File

@ -29,7 +29,7 @@ from django.views.decorators.http import require_POST
from plinth import actions from plinth import actions
from plinth.errors import ActionError from plinth.errors import ActionError
from plinth.modules import config, letsencrypt from plinth.modules import letsencrypt
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -43,7 +43,6 @@ def index(request):
'description': letsencrypt.description, 'description': letsencrypt.description,
'status': status, 'status': status,
'manual_page': letsencrypt.manual_page, 'manual_page': letsencrypt.manual_page,
'installed_modules': letsencrypt.get_installed_modules()
}) })
@ -60,8 +59,8 @@ def revoke(request, domain):
except ActionError as exception: except ActionError as exception:
messages.error( messages.error(
request, request,
_('Failed to revoke certificate for domain {domain}: {error}') _('Failed to revoke certificate for domain {domain}: {error}').
.format(domain=domain, error=exception.args[2])) format(domain=domain, error=exception.args[2]))
return redirect(reverse_lazy('letsencrypt:index')) return redirect(reverse_lazy('letsencrypt:index'))
@ -75,125 +74,17 @@ def obtain(request, domain):
request, request,
_('Certificate successfully obtained for domain {domain}').format( _('Certificate successfully obtained for domain {domain}').format(
domain=domain)) domain=domain))
enable_renewal_management(request, domain)
except ActionError as exception: except ActionError as exception:
messages.error( messages.error(
request, request,
_('Failed to obtain certificate for domain {domain}: {error}') _('Failed to obtain certificate for domain {domain}: {error}').
.format(domain=domain, error=exception.args[2])) format(domain=domain, error=exception.args[2]))
return redirect(reverse_lazy('letsencrypt:index'))
def enable_renewal_management(request, domain):
if domain == config.get_domainname():
try:
actions.superuser_run('letsencrypt', ['manage_hooks', 'enable'])
messages.success(
request,
_('Certificate renewal management enabled for {domain}.')
.format(domain=domain))
except ActionError as exception:
messages.error(
request,
_('Failed to enable certificate renewal management for '
'{domain}: {error}').format(domain=domain,
error=exception.args[2]))
@require_POST
def toggle_hooks(request, domain):
"""Toggle pointing of certbot's hooks to FreedomBox.
For the current domain.
"""
manage_hooks_status = letsencrypt.get_manage_hooks_status()
subcommand = 'disable' if 'enabled' in manage_hooks_status else 'enable'
try:
if subcommand == 'disable':
enabled_modules = [
module for module in letsencrypt.MODULES_WITH_HOOKS
if module in manage_hooks_status
]
for module in enabled_modules:
actions.superuser_run(module, ['letsencrypt', 'drop'],
run_in_background=True)
actions.superuser_run('letsencrypt', ['manage_hooks', subcommand])
if subcommand == 'enable':
msg = _('Certificate renewal management enabled for {domain}.')\
.format(domain=domain)
else:
msg = _('Certificate renewal management disabled for {domain}.')\
.format(domain=domain)
messages.success(request, msg)
except ActionError as exception:
messages.error(
request,
_('Failed to switch certificate renewal management for {domain}: '
'{error}').format(domain=domain, error=exception.args[2]))
return redirect(reverse_lazy('letsencrypt:index'))
@require_POST
def toggle_module(request, domain, module):
"""Toggle usage of LE cert for a module name, for the current domain."""
manage_hooks_status = letsencrypt.get_manage_hooks_status()
enabled_modules = [
module for module in letsencrypt.MODULES_WITH_HOOKS
if module in manage_hooks_status
]
if module in enabled_modules:
mod_le_arg = 'drop'
enabled_modules.remove(module)
else:
mod_le_arg = 'add'
enabled_modules.append(module)
module_args = ['letsencrypt', mod_le_arg]
le_arguments = ['manage_hooks', 'enable']
if not enabled_modules == []:
le_arguments.extend(['--modules'] + enabled_modules)
try:
actions.superuser_run(module, module_args)
actions.superuser_run('letsencrypt', le_arguments)
messages.success(request,
_('Switched use of certificate for app {module}')
.format(module=module))
except ActionError as exception:
messages.error(
request,
_('Failed to switch certificate use for app {module}: {error}')
.format(module=module, error=exception.args[2]))
return redirect(reverse_lazy('letsencrypt:index')) return redirect(reverse_lazy('letsencrypt:index'))
@require_POST @require_POST
def delete(request, domain): def delete(request, domain):
"""Delete a certificate for a given domain, and cleanup renewal config.""" """Delete a certificate for a given domain, and cleanup renewal config."""
manage_hooks_status = letsencrypt.get_manage_hooks_status()
enabled_modules = [
module for module in letsencrypt.MODULES_WITH_HOOKS
if module in manage_hooks_status
]
try:
for module in enabled_modules:
actions.superuser_run(module, ['letsencrypt', 'drop'],
run_in_background=True)
actions.superuser_run('letsencrypt',
['manage_hooks', 'disable', '--domain', domain])
except ActionError as exception:
messages.error(
request,
_('Failed to disable certificate renewal management for {domain}: '
'{error}').format(domain=domain, error=exception.args[2]))
try: try:
actions.superuser_run('letsencrypt', ['delete', '--domain', domain]) actions.superuser_run('letsencrypt', ['delete', '--domain', domain])
messages.success( messages.success(
@ -203,7 +94,7 @@ def delete(request, domain):
except ActionError as exception: except ActionError as exception:
messages.error( messages.error(
request, request,
_('Failed to delete certificate for domain {domain}: {error}') _('Failed to delete certificate for domain {domain}: {error}').
.format(domain=domain, error=exception.args[2])) format(domain=domain, error=exception.args[2]))
return redirect(reverse_lazy('letsencrypt:index')) return redirect(reverse_lazy('letsencrypt:index'))