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

letsencyrpt: Implement action to copy certificates

Signed-off-by: Sunil Mohan Adapa's avatarSunil Mohan Adapa <>
Reviewed-by: Joseph Nuthalapati's avatarJoseph Nuthalapati <>
parent c042ff5a
......@@ -21,18 +21,23 @@ Configuration helper for Let's Encrypt.
import argparse
import glob
import importlib
import json
import os
import pathlib
import re
import shutil
import subprocess
import sys
import configobj
from plinth import action_utils
from plinth import action_utils, cfg
from plinth.modules import letsencrypt as le
LE_DIRECTORY = '/etc/letsencrypt/'
ETC_SSL_DIRECTORY = '/etc/ssl/'
RENEWAL_DIRECTORY = '/etc/letsencrypt/renewal/'
WEB_ROOT_PATH = '/var/www/html'
......@@ -68,6 +73,25 @@ def parse_arguments():
delete_parser.add_argument('--domain', required=True,
help='Domain name to delete certificate of')
subparser = subparsers.add_parser(
help='Copy LE certificate to a daemon\'s directory')
subparser.add_argument('--managing-app', required=True,
help='App needing the certificate')
subparser.add_argument('--user-owner', required=True,
help='User who should own the certificate')
subparser.add_argument('--group-owner', required=True,
help='Group that should own the certificate')
subparser.add_argument('--source-private-key-path', required=True,
help='Path to the source private key')
'--source certificate-path', required=True,
help='Path to the source certificate with public key')
subparser.add_argument('--private-key-path', required=True,
help='Path to the private key')
subparser.add_argument('--certificate-path', required=True,
help='Path to the certificate with public key')
help_hooks = 'Does nothing, kept for compatibility.'
subparser = subparsers.add_parser('run_pre_hooks', help=help_hooks)
......@@ -253,6 +277,67 @@ def _remove_old_hooks_from_file(file_path):
def subcommand_copy_certificate(arguments):
"""Copy certificate from LE directory to daemon's directory.
Set ownership and permissions as requested needed by the daemon.
source_private_key_path = pathlib.Path(
source_certificate_path = pathlib.Path(
private_key_path = pathlib.Path(arguments.private_key_path).resolve()
_assert_managed_path(arguments.managing_app, private_key_path)
certificate_path = pathlib.Path(arguments.certificate_path).resolve()
_assert_managed_path(arguments.managing_app, certificate_path)
# Create directories, owned by root
private_key_path.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
certificate_path.parent.mkdir(mode=0o755, parents=True, exist_ok=True)
# Private key is only accessible to the user owner
old_mask = os.umask(0o177)
shutil.copyfile(source_private_key_path, private_key_path)
if certificate_path != private_key_path:
# Certificate is only writable by the user owner
shutil.copyfile(source_certificate_path, certificate_path)
# If private key and certificate are the same file, append one after
# the other.
source_certificate = source_certificate_path.read_bytes()
with'a+b') as file_handle:
shutil.chown(certificate_path, user=arguments.user_owner,
shutil.chown(private_key_path, user=arguments.user_owner,
def _assert_source_directory(path):
"""Assert that a path is a valid source of a certificates."""
assert (str(path).startswith(LE_DIRECTORY)
or str(path).startswith(ETC_SSL_DIRECTORY))
def _assert_managed_path(module, path):
"""Check that path is in fact managed by module."""
module_file = pathlib.Path(cfg.config_dir) / 'modules-enabled' / module
module_path = module_file.read_text().strip()
module = importlib.import_module(module_path)
assert set(path.parents).intersection(set(module.managed_paths))
def subcommand_run_pre_hooks(_):
"""Do nothing, kept for legacy LE configuration.
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