mirror of
https://github.com/freedombox/FreedomBox.git
synced 2026-06-17 11:10:23 +00:00
letsencrypt: Handling certificate renewals when daemon is offline
During boot or in other situations when FreedomBox Service is offline, Let's Encrypt certificate renewals might happen. When FreedomBox Service starts, check on such certificates and run certificate setup mechanism in each app to use the latest renewed certificate. Signed-off-by: Sunil Mohan Adapa <sunil@medhas.org> Reviewed-by: Joseph Nuthalapati <njoseph@thoughtworks.com>
This commit is contained in:
parent
9c6efad55d
commit
254b8a98a9
@ -61,6 +61,11 @@ def parse_arguments():
|
||||
|
||||
subparsers.add_parser('get-status',
|
||||
help='Return the status of configured domains.')
|
||||
subparser = subparsers.add_parser(
|
||||
'get-modified-time',
|
||||
help='Return the modified time for a certificate.')
|
||||
subparser.add_argument('--domain', required=True,
|
||||
help='Domain name to get modified time for')
|
||||
revoke_parser = subparsers.add_parser(
|
||||
'revoke', help='Revoke certificate of a domain and disable website.')
|
||||
revoke_parser.add_argument('--domain', required=True,
|
||||
@ -133,6 +138,12 @@ def get_certificate_expiry(domain):
|
||||
return output.decode().strip().split('=')[1]
|
||||
|
||||
|
||||
def get_modified_time(domain):
|
||||
"""Return the last modified time of a certificate."""
|
||||
certificate_file = pathlib.Path(le.LIVE_DIRECTORY) / domain / 'cert.pem'
|
||||
return int(certificate_file.stat().st_mtime)
|
||||
|
||||
|
||||
def get_validity_status(domain):
|
||||
"""Return validity status of a certificate, e.g. valid, revoked, expired"""
|
||||
output = subprocess.check_output(['certbot', 'certificates', '-d', domain])
|
||||
@ -176,7 +187,9 @@ def get_status():
|
||||
'validity':
|
||||
get_validity_status(domain),
|
||||
'lineage':
|
||||
str(pathlib.Path(le.LIVE_DIRECTORY) / domain)
|
||||
str(pathlib.Path(le.LIVE_DIRECTORY) / domain),
|
||||
'modified_time':
|
||||
get_modified_time(domain)
|
||||
}
|
||||
return domain_status
|
||||
|
||||
@ -205,6 +218,11 @@ def subcommand_get_status(_):
|
||||
print(json.dumps({'domains': domain_status}))
|
||||
|
||||
|
||||
def subcommand_get_modified_time(arguments):
|
||||
"""Print the modified time of a certificate as integer."""
|
||||
print(get_modified_time(arguments.domain))
|
||||
|
||||
|
||||
def subcommand_revoke(arguments):
|
||||
"""Disable a domain and revoke the certificate."""
|
||||
domain = arguments.domain
|
||||
|
||||
@ -20,6 +20,7 @@ FreedomBox app for using Let's Encrypt.
|
||||
|
||||
import json
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
@ -28,7 +29,8 @@ from plinth import app as app_module
|
||||
from plinth import cfg, menu
|
||||
from plinth.errors import ActionError
|
||||
from plinth.modules import names
|
||||
from plinth.signals import domain_added, domain_removed, domainname_change
|
||||
from plinth.signals import (domain_added, domain_removed, domainname_change,
|
||||
post_module_loading)
|
||||
from plinth.utils import format_lazy
|
||||
|
||||
from . import components
|
||||
@ -64,6 +66,7 @@ description = [
|
||||
manual_page = 'LetsEncrypt'
|
||||
|
||||
LIVE_DIRECTORY = '/etc/letsencrypt/live/'
|
||||
CERTIFICATE_CHECK_DELAY = 120
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
app = None
|
||||
@ -93,6 +96,8 @@ def init():
|
||||
domain_added.connect(on_domain_added)
|
||||
domain_removed.connect(on_domain_removed)
|
||||
|
||||
post_module_loading.connect(_certificate_handle_modified)
|
||||
|
||||
|
||||
def setup(helper, old_version=None):
|
||||
"""Install and configure the module."""
|
||||
@ -207,3 +212,65 @@ def get_status():
|
||||
status['domains'].setdefault(domain, {})
|
||||
|
||||
return status
|
||||
|
||||
|
||||
def _certificate_handle_modified(**kwargs):
|
||||
"""Generate events for certificates that got modified during downtime.
|
||||
|
||||
This runs as a synchronous method soon after initializing the apps. After
|
||||
this is done, remaining initialization happens.
|
||||
|
||||
This method is a wrapper over the read method to catch and print
|
||||
exceptions.
|
||||
|
||||
"""
|
||||
logger.info('Checking if any Let\'s Encrypt certificates got renewed.')
|
||||
try:
|
||||
_certificate_handle_modified_internal()
|
||||
except Exception:
|
||||
logger.exception('Error triggering certificate events.')
|
||||
|
||||
|
||||
def _certificate_handle_modified_internal():
|
||||
"""Generate events for certificates that got modified during downtime."""
|
||||
status = get_status()
|
||||
for domain, domain_status in status['domains'].items():
|
||||
if not domain_status:
|
||||
continue
|
||||
|
||||
lineage = domain_status['lineage']
|
||||
modified_time = domain_status['modified_time']
|
||||
if certificate_get_last_seen_modified_time(lineage) < modified_time:
|
||||
logger.info('Certificate for %s got renewed offline.', domain)
|
||||
components.on_certificate_event_sync('renewed', domain, lineage)
|
||||
else:
|
||||
logger.info('Certificate for %s is already the latest known.',
|
||||
domain)
|
||||
|
||||
|
||||
def certificate_get_last_seen_modified_time(lineage):
|
||||
"""Return the last seen expiry date of a certificate."""
|
||||
from plinth import kvstore
|
||||
info = kvstore.get_default('letsencrypt_certificate_info', '{}')
|
||||
info = json.loads(info)
|
||||
try:
|
||||
return info[str(lineage)]['last_seen_modified_time']
|
||||
except KeyError:
|
||||
return 0
|
||||
|
||||
|
||||
def certificate_set_last_seen_modified_time(lineage):
|
||||
"""Write to store a certificate's last seen expiry date."""
|
||||
lineage = pathlib.Path(lineage)
|
||||
output = actions.superuser_run(
|
||||
'letsencrypt', ['get-modified-time', '--domain', lineage.name])
|
||||
modified_time = int(output)
|
||||
|
||||
from plinth import kvstore
|
||||
info = kvstore.get_default('letsencrypt_certificate_info', '{}')
|
||||
info = json.loads(info)
|
||||
|
||||
certificate_info = info.setdefault(str(lineage), {})
|
||||
certificate_info['last_seen_modified_time'] = modified_time
|
||||
|
||||
kvstore.set('letsencrypt_certificate_info', json.dumps(info))
|
||||
|
||||
@ -398,3 +398,7 @@ def on_certificate_event_sync(event, domains, lineage):
|
||||
logger.exception(
|
||||
'Error executing certificate hook for %s: %s, %s, %s: %s',
|
||||
component.component_id, event, domains, lineage, exception)
|
||||
|
||||
if event in ('obtained', 'renewed'):
|
||||
from plinth.modules import letsencrypt
|
||||
letsencrypt.certificate_set_last_seen_modified_time(lineage)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user