Compare commits
69 Commits
master
...
test-updat
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e6ceb1fc50 | ||
|
|
edd78d7c1c | ||
|
|
3c9973b4d6 | ||
|
|
7427daaa6c | ||
|
|
44154708d9 | ||
|
|
87c143d9ca | ||
|
|
232e8625af | ||
|
|
62556486a1 | ||
|
|
ad8d49f9dc | ||
|
|
8098506e51 | ||
|
|
2ce70a32bc | ||
|
|
0ab42fd3c2 | ||
|
|
4336547979 | ||
|
|
16365e95e2 | ||
|
|
80d5c6a18b | ||
|
|
15d25ff257 | ||
|
|
68781a9813 | ||
|
|
eb932e1a41 | ||
|
|
610b9aec5c | ||
|
|
034ba23269 | ||
|
|
5467dd72e1 | ||
|
|
c6efe45e78 | ||
|
|
928a7ec531 | ||
|
|
adcd6e60ab | ||
|
|
de082ee398 | ||
|
|
9d686c3110 | ||
|
|
720836a7c1 | ||
|
|
ecf6d2e81a | ||
|
|
8746454851 | ||
|
|
0c11c00da7 | ||
|
|
80d60d6fbf | ||
|
|
e4470134c7 | ||
|
|
cc7dc32b71 | ||
|
|
1fbfe1a64e | ||
|
|
9f99ad0201 | ||
|
|
97bc3516c5 | ||
|
|
d4e731e632 | ||
|
|
0069961c83 | ||
|
|
cad3019a53 | ||
|
|
ea946f376c | ||
|
|
5f4123141c | ||
|
|
11d5f0c090 | ||
|
|
45606c270a | ||
|
|
8b1b389cbb | ||
|
|
1ca7220177 | ||
|
|
9e99a893f4 | ||
|
|
f6a7fcd484 | ||
|
|
e957a7e25a | ||
|
|
e377a30b80 | ||
|
|
bd81191dc4 | ||
|
|
8eb46706c1 | ||
|
|
5fd18ce285 | ||
|
|
92ae44ad44 | ||
|
|
c51a48e20e | ||
|
|
81ceb6adfe | ||
|
|
5cefba1fa1 | ||
|
|
e5a658f009 | ||
|
|
ddf141c0d4 | ||
|
|
7c7fde35c7 | ||
|
|
d5c2358ae4 | ||
|
|
439a67e081 | ||
|
|
d7e32a0e84 | ||
|
|
ae2947b259 | ||
|
|
d9f100b4a7 | ||
|
|
204eaac19d | ||
|
|
0bc84dfff7 | ||
|
|
2b5c957dd1 | ||
|
|
8584d5f736 | ||
|
|
4172158c31 |
@@ -813,6 +813,25 @@ def test_revoke_multiple_lineages(context: IntegrationTestsContext) -> None:
|
||||
assert 'Not deleting revoked certificates due to overlapping archive dirs' in f.read()
|
||||
|
||||
|
||||
def test_reconfigure(context: IntegrationTestsContext) -> None:
|
||||
"""Test the reconfigure verb"""
|
||||
certname = context.get_domain()
|
||||
context.certbot(['-d', certname])
|
||||
conf_path = join(context.config_dir, 'renewal', '{}.conf'.format(certname))
|
||||
|
||||
with misc.create_http_server(context.http_01_port) as webroot:
|
||||
context.certbot(['reconfigure', '--cert-name', certname,
|
||||
'-a', 'webroot', '--webroot-path', webroot])
|
||||
with open(conf_path, 'r') as f:
|
||||
file_contents = f.read()
|
||||
# Check changed value
|
||||
assert 'authenticator = webroot' in file_contents, \
|
||||
'Expected authenticator to be changed to webroot in renewal config'
|
||||
# Check added value
|
||||
assert f'webroot_path = {webroot}' in file_contents, \
|
||||
'Expected new webroot path to be added to renewal config'
|
||||
|
||||
|
||||
def test_wildcard_certificates(context: IntegrationTestsContext) -> None:
|
||||
"""Test wildcard certificate issuance."""
|
||||
certname = context.get_domain('wild')
|
||||
|
||||
@@ -6,7 +6,7 @@ Certbot adheres to [Semantic Versioning](https://semver.org/).
|
||||
|
||||
### Added
|
||||
|
||||
*
|
||||
* Allow a user to modify the configuration of a certificate without renewing it using the new `reconfigure` subcommand. See `certbot help reconfigure` for details.
|
||||
|
||||
### Changed
|
||||
|
||||
@@ -181,7 +181,7 @@ More details about these changes can be found on our GitHub repo.
|
||||
|
||||
### Added
|
||||
|
||||
* Updated Windows installer to be signed and trusted in Windows
|
||||
* Updated Windows installer to be signed and trusted in Windows
|
||||
|
||||
### Changed
|
||||
|
||||
|
||||
@@ -140,7 +140,7 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st
|
||||
)
|
||||
helpful.add(
|
||||
[None, "run", "certonly", "manage", "delete", "certificates",
|
||||
"renew", "enhance"], "--cert-name", dest="certname",
|
||||
"renew", "enhance", "reconfigure"], "--cert-name", dest="certname",
|
||||
metavar="CERTNAME", default=flag_default("certname"),
|
||||
help="Certificate name to apply. This name is used by Certbot for housekeeping "
|
||||
"and in file paths; it doesn't affect the content of the certificate itself. "
|
||||
@@ -162,6 +162,17 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st
|
||||
" roll back those changes. It also calls --pre-hook and --post-hook commands"
|
||||
" if they are defined because they may be necessary to accurately simulate"
|
||||
" renewal. --deploy-hook commands are not called.")
|
||||
helpful.add(
|
||||
["testing", "renew", "certonly", "reconfigure"],
|
||||
"--run-deploy-hooks", action="store_true", dest="run_deploy_hooks",
|
||||
default=flag_default("run_deploy_hooks"),
|
||||
help="When performing a test run using `--dry-run` or `reconfigure`, run any applicable"
|
||||
" deploy hooks. This includes hooks set on the command line, saved in the"
|
||||
" certificate's renewal configuration file, or present in the renewal-hooks directory."
|
||||
" To exclude direcory hooks, use --no-directory-hooks. The hook(s) will only"
|
||||
" be run if the dry run succeeds, and will use the current active certificate, not"
|
||||
" the temporary test certificate acquired during the dry run. This flag is recommended"
|
||||
" when modifying the deploy hook using `reconfigure`.")
|
||||
helpful.add(
|
||||
["register", "automation"], "--register-unsafely-without-email", action="store_true",
|
||||
default=flag_default("register_unsafely_without_email"),
|
||||
@@ -379,7 +390,7 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st
|
||||
default=flag_default("issuance_timeout"),
|
||||
help=config_help("issuance_timeout"))
|
||||
helpful.add(
|
||||
"renew", "--pre-hook",
|
||||
["renew", "reconfigure"], "--pre-hook",
|
||||
help="Command to be run in a shell before obtaining any certificates."
|
||||
" Intended primarily for renewal, where it can be used to temporarily"
|
||||
" shut down a webserver that might conflict with the standalone"
|
||||
@@ -387,21 +398,21 @@ def prepare_and_parse_args(plugins: plugins_disco.PluginsRegistry, args: List[st
|
||||
" obtained/renewed. When renewing several certificates that have"
|
||||
" identical pre-hooks, only the first will be executed.")
|
||||
helpful.add(
|
||||
"renew", "--post-hook",
|
||||
["renew", "reconfigure"], "--post-hook",
|
||||
help="Command to be run in a shell after attempting to obtain/renew"
|
||||
" certificates. Can be used to deploy renewed certificates, or to"
|
||||
" restart any servers that were stopped by --pre-hook. This is only"
|
||||
" run if an attempt was made to obtain/renew a certificate. If"
|
||||
" multiple renewed certificates have identical post-hooks, only"
|
||||
" one will be run.")
|
||||
helpful.add("renew", "--renew-hook",
|
||||
helpful.add(["renew", "reconfigure"], "--renew-hook",
|
||||
action=_RenewHookAction, help=argparse.SUPPRESS)
|
||||
helpful.add(
|
||||
"renew", "--no-random-sleep-on-renew", action="store_false",
|
||||
default=flag_default("random_sleep_on_renew"), dest="random_sleep_on_renew",
|
||||
help=argparse.SUPPRESS)
|
||||
helpful.add(
|
||||
"renew", "--deploy-hook", action=_DeployHookAction,
|
||||
["renew", "reconfigure"], "--deploy-hook", action=_DeployHookAction,
|
||||
help='Command to be run in a shell once for each successfully'
|
||||
' issued certificate. For this command, the shell variable'
|
||||
' $RENEWED_LINEAGE will point to the config live subdirectory'
|
||||
|
||||
@@ -38,6 +38,7 @@ manage certificates:
|
||||
certificates Display information about certificates you have from Certbot
|
||||
revoke Revoke a certificate (supply --cert-name or --cert-path)
|
||||
delete Delete a certificate (supply --cert-name)
|
||||
reconfigure Update a certificate's configuration (supply --cert-name)
|
||||
|
||||
manage your account:
|
||||
register Create an ACME account
|
||||
|
||||
@@ -16,7 +16,7 @@ def _add_all_groups(helpful: "helpful.HelpfulArgumentParser") -> None:
|
||||
helpful.add_group("paths", description="Flags for changing execution paths & servers")
|
||||
helpful.add_group("manage",
|
||||
description="Various subcommands and flags are available for managing your certificates:",
|
||||
verbs=["certificates", "delete", "renew", "revoke", "update_symlinks"])
|
||||
verbs=["certificates", "delete", "renew", "revoke", "update_symlinks", "reconfigure"])
|
||||
|
||||
# VERBS
|
||||
for verb, docs in VERB_HELP:
|
||||
|
||||
@@ -66,6 +66,7 @@ class HelpfulArgumentParser:
|
||||
"certificates": main.certificates,
|
||||
"delete": main.delete,
|
||||
"enhance": main.enhance,
|
||||
"reconfigure": main.reconfigure,
|
||||
}
|
||||
|
||||
# Get notification function for printing
|
||||
|
||||
@@ -22,9 +22,9 @@ def _plugins_parsing(helpful: "helpful.HelpfulArgumentParser",
|
||||
help="Name of the plugin that is both an authenticator and an installer."
|
||||
" Should not be used together with --authenticator or --installer. "
|
||||
"(default: Ask)")
|
||||
helpful.add("plugins", "-a", "--authenticator", default=flag_default("authenticator"),
|
||||
help="Authenticator plugin name.")
|
||||
helpful.add("plugins", "-i", "--installer", default=flag_default("installer"),
|
||||
helpful.add(["plugins", "reconfigure"], "-a", "--authenticator",
|
||||
default=flag_default("authenticator"), help="Authenticator plugin name.")
|
||||
helpful.add(["plugins", "reconfigure"], "-i", "--installer", default=flag_default("installer"),
|
||||
help="Installer plugin name (also used to find domains).")
|
||||
helpful.add(["plugins", "certonly", "run", "install"],
|
||||
"--apache", action="store_true", default=flag_default("apache"),
|
||||
@@ -38,7 +38,7 @@ def _plugins_parsing(helpful: "helpful.HelpfulArgumentParser",
|
||||
helpful.add(["plugins", "certonly"], "--manual", action="store_true",
|
||||
default=flag_default("manual"),
|
||||
help="Provide laborious manual instructions for obtaining a certificate")
|
||||
helpful.add(["plugins", "certonly"], "--webroot", action="store_true",
|
||||
helpful.add(["plugins", "certonly", "reconfigure"], "--webroot", action="store_true",
|
||||
default=flag_default("webroot"),
|
||||
help="Obtain certificates by placing files in a webroot directory.")
|
||||
helpful.add(["plugins", "certonly"], "--dns-cloudflare", action="store_true",
|
||||
|
||||
@@ -102,6 +102,11 @@ VERB_HELP = [
|
||||
"opts": 'Options useful for the "show_account" subcommand:',
|
||||
"usage": "\n\n certbot show_account [options]\n\n"
|
||||
}),
|
||||
("reconfigure", {
|
||||
"short": "Update renewal configuration for a certificate specified by --cert-name",
|
||||
"opts": 'Common options that may be updated with the "reconfigure" subcommand:',
|
||||
"usage": "\n\n certbot reconfigure --cert-name CERTNAME [options]\n\n"
|
||||
}),
|
||||
]
|
||||
|
||||
|
||||
|
||||
@@ -80,6 +80,7 @@ CLI_DEFAULTS: Dict[str, Any] = dict( # noqa
|
||||
eab_hmac_key=None,
|
||||
eab_kid=None,
|
||||
issuance_timeout=90,
|
||||
run_deploy_hooks=False,
|
||||
|
||||
# Subparsers
|
||||
num=None,
|
||||
|
||||
@@ -166,7 +166,7 @@ def deploy_hook(config: configuration.NamespaceConfig, domains: List[str],
|
||||
"""
|
||||
if config.deploy_hook:
|
||||
_run_deploy_hook(config.deploy_hook, domains,
|
||||
lineage_path, config.dry_run)
|
||||
lineage_path, config.dry_run, config.run_deploy_hooks)
|
||||
|
||||
|
||||
def renew_hook(config: configuration.NamespaceConfig, domains: List[str],
|
||||
@@ -190,7 +190,7 @@ def renew_hook(config: configuration.NamespaceConfig, domains: List[str],
|
||||
executed_dir_hooks = set()
|
||||
if config.directory_hooks:
|
||||
for hook in list_hooks(config.renewal_deploy_hooks_dir):
|
||||
_run_deploy_hook(hook, domains, lineage_path, config.dry_run)
|
||||
_run_deploy_hook(hook, domains, lineage_path, config.dry_run, config.run_deploy_hooks)
|
||||
executed_dir_hooks.add(hook)
|
||||
|
||||
if config.renew_hook:
|
||||
@@ -199,10 +199,11 @@ def renew_hook(config: configuration.NamespaceConfig, domains: List[str],
|
||||
config.renew_hook)
|
||||
else:
|
||||
_run_deploy_hook(config.renew_hook, domains,
|
||||
lineage_path, config.dry_run)
|
||||
lineage_path, config.dry_run, config.run_deploy_hooks)
|
||||
|
||||
|
||||
def _run_deploy_hook(command: str, domains: List[str], lineage_path: str, dry_run: bool) -> None:
|
||||
def _run_deploy_hook(command: str, domains: List[str], lineage_path: str, dry_run: bool,
|
||||
run_deploy_hooks: bool) -> None:
|
||||
"""Run the specified deploy-hook (if not doing a dry run).
|
||||
|
||||
If dry_run is True, command is not run and a message is logged
|
||||
@@ -214,9 +215,10 @@ def _run_deploy_hook(command: str, domains: List[str], lineage_path: str, dry_ru
|
||||
:type domains: `list` of `str`
|
||||
:param str lineage_path: live directory path for the new cert
|
||||
:param bool dry_run: True iff Certbot is doing a dry run
|
||||
:param bool run_deploy_hooks: True if deploy hooks should run despite Certbot doing a dry run
|
||||
|
||||
"""
|
||||
if dry_run:
|
||||
if dry_run and not run_deploy_hooks:
|
||||
logger.info("Dry run: skipping deploy hook command: %s",
|
||||
command)
|
||||
return
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
"""Certbot main entry point."""
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
import copy
|
||||
from contextlib import contextmanager
|
||||
import functools
|
||||
import logging.handlers
|
||||
@@ -1654,6 +1655,127 @@ def make_or_verify_needed_dirs(config: configuration.NamespaceConfig) -> None:
|
||||
util.make_or_verify_dir(hook_dir, strict=config.strict_permissions)
|
||||
|
||||
|
||||
def _report_reconfigure_results(renewal_file: str, orig_renewal_conf: configobj.ConfigObj) -> None:
|
||||
"""Reports the outcome of certificate renewal reconfiguration to the user.
|
||||
|
||||
:param renewal_file: Path to the cert's renewal file
|
||||
:type renewal_file: str
|
||||
|
||||
:param orig_renewal_conf: Loaded original renewal configuration
|
||||
:type orig_renewal_conf: configobj.ConfigObj
|
||||
|
||||
:returns: `None`
|
||||
:rtype: None
|
||||
|
||||
"""
|
||||
try:
|
||||
final_renewal_conf = configobj.ConfigObj(
|
||||
renewal_file, encoding='utf-8', default_encoding='utf-8')
|
||||
except configobj.ConfigObjError:
|
||||
raise errors.CertStorageError(
|
||||
f'error parsing {renewal_file}')
|
||||
|
||||
orig_renewal_params = orig_renewal_conf['renewalparams']
|
||||
final_renewal_params = final_renewal_conf['renewalparams']
|
||||
|
||||
if final_renewal_params == orig_renewal_params:
|
||||
success_message = '\nNo changes were made to the renewal configuration.'
|
||||
else:
|
||||
success_message = '\nSuccessfully updated configuration.' + \
|
||||
'\nChanges will apply when the certificate renews.'
|
||||
|
||||
display_util.notify(success_message)
|
||||
|
||||
|
||||
def reconfigure(config: configuration.NamespaceConfig,
|
||||
plugins: plugins_disco.PluginsRegistry) -> None:
|
||||
"""Allow the user to set new configuration options for an existing certificate without
|
||||
forcing renewal. This can be used for things like authenticator, installer, and hooks,
|
||||
but not for the domains on the cert, since those are only saved in the cert.
|
||||
|
||||
:param config: Configuration object
|
||||
:type config: configuration.NamespaceConfig
|
||||
|
||||
:param plugins: List of plugins
|
||||
:type plugins: plugins_disco.PluginsRegistry
|
||||
|
||||
:raises errors.Error: if the dry run fails
|
||||
:raises errors.ConfigurationError: if certificate could not be loaded
|
||||
|
||||
"""
|
||||
|
||||
if config.domains:
|
||||
raise errors.ConfigurationError("You have specified domains, but this function cannot "
|
||||
"be used to modify the domains in a certificate. If you would like to do so, follow "
|
||||
"the instructions at https://certbot.org/change-cert-domain. Otherwise, remove the "
|
||||
"domains from the command to continue reconfiguring. You can specify which certificate "
|
||||
"you want on the command line with flag --cert-name instead.")
|
||||
# While we could technically allow domains to be used to specify the certificate in addition to
|
||||
# --cert-name, there's enough complexity with matching certs to domains that it's not worth it,
|
||||
# to say nothing of the difficulty in explaining what exactly this subcommand can modify
|
||||
|
||||
|
||||
# To make sure that the requested changes work, do a dry run. While setting up the dry run,
|
||||
# we will set all the needed fields in config, which will then be saved upon success.
|
||||
config.dry_run = True
|
||||
|
||||
if not config.certname:
|
||||
certname_question = "Which certificate would you like to reconfigure?"
|
||||
config.certname = cert_manager.get_certnames(
|
||||
config, "reconfigure", allow_multiple=False,
|
||||
custom_prompt=certname_question)[0]
|
||||
|
||||
certname = config.certname
|
||||
|
||||
try:
|
||||
renewal_file = storage.renewal_file_for_certname(config, certname)
|
||||
except errors.CertStorageError:
|
||||
raise errors.ConfigurationError(f"An existing certificate with name {certname} could not "
|
||||
"be found. Run `certbot certificates` to list available certificates.")
|
||||
|
||||
# figure this out before we modify config
|
||||
if config.deploy_hook and not config.run_deploy_hooks:
|
||||
msg = ("You are attempting to set a --deploy-hook. Would you like Certbot to run deploy "
|
||||
"hooks when it performs a dry run with the new settings? This will run all "
|
||||
"relevant deploy hooks, including directory hooks, unless --no-directory-hooks "
|
||||
"is set. This will use the current active certificate, and not the temporary test "
|
||||
"certificate acquired during the dry run.")
|
||||
config.run_deploy_hooks = display_util.yesno(msg,"Run deploy hooks",
|
||||
"Do not run deploy hooks", default=False)
|
||||
|
||||
# cache previous version for later comparison
|
||||
try:
|
||||
orig_renewal_conf = configobj.ConfigObj(
|
||||
renewal_file, encoding='utf-8', default_encoding='utf-8')
|
||||
except configobj.ConfigObjError:
|
||||
raise errors.CertStorageError(
|
||||
f"error parsing {renewal_file}")
|
||||
|
||||
lineage_config = copy.deepcopy(config)
|
||||
try:
|
||||
renewal_candidate = renewal.reconstitute(lineage_config, renewal_file)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
raise errors.ConfigurationError(f"Renewal configuration file {renewal_file} "
|
||||
f"(cert: {certname}) produced an unexpected error: {e}.")
|
||||
if not renewal_candidate:
|
||||
raise errors.ConfigurationError("Could not load certificate. See logs for errors.")
|
||||
|
||||
# this is where lineage_config gets fully filled out (e.g. --apache will set auth and installer)
|
||||
installer, auth = plug_sel.choose_configurator_plugins(lineage_config, plugins, "certonly")
|
||||
le_client = _init_le_client(lineage_config, auth, installer)
|
||||
|
||||
# renews cert as dry run to test that the new values are ok
|
||||
# at this point, renewal_candidate.configuration has the old values, but will use
|
||||
# the values from lineage_config when doing the dry run
|
||||
_get_and_save_cert(le_client, lineage_config, certname=certname,
|
||||
lineage=renewal_candidate)
|
||||
|
||||
# this function will update lineage.configuration with the new values, and save it to disk
|
||||
renewal_candidate.save_new_config_values(lineage_config)
|
||||
|
||||
_report_reconfigure_results(renewal_file, orig_renewal_conf)
|
||||
|
||||
|
||||
@contextmanager
|
||||
def make_displayer(config: configuration.NamespaceConfig
|
||||
) -> Generator[Union[display_obj.NoninteractiveDisplay,
|
||||
|
||||
@@ -53,7 +53,7 @@ CONFIG_ITEMS = set(itertools.chain(
|
||||
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',)))
|
||||
|
||||
|
||||
def _reconstitute(config: configuration.NamespaceConfig,
|
||||
def reconstitute(config: configuration.NamespaceConfig,
|
||||
full_path: str) -> Optional[storage.RenewableCert]:
|
||||
"""Try to instantiate a RenewableCert, updating config with relevant items.
|
||||
|
||||
@@ -490,7 +490,7 @@ def handle_renewal_request(config: configuration.NamespaceConfig) -> None:
|
||||
# Note that this modifies config (to add back the configuration
|
||||
# elements from within the renewal configuration file).
|
||||
try:
|
||||
renewal_candidate = _reconstitute(lineage_config, renewal_file)
|
||||
renewal_candidate = reconstitute(lineage_config, renewal_file)
|
||||
except Exception as e: # pylint: disable=broad-except
|
||||
logger.error("Renewal configuration file %s (cert: %s) "
|
||||
"produced an unexpected error: %s. Skipping.",
|
||||
|
||||
@@ -45,6 +45,8 @@ README = "README"
|
||||
CURRENT_VERSION = pkg_resources.parse_version(certbot.__version__)
|
||||
BASE_PRIVKEY_MODE = 0o600
|
||||
|
||||
# pylint: disable=too-many-lines
|
||||
|
||||
|
||||
def renewal_conf_files(config: configuration.NamespaceConfig) -> List[str]:
|
||||
"""Build a list of all renewal configuration files.
|
||||
@@ -1243,3 +1245,17 @@ class RenewableCert(interfaces.RenewableCert):
|
||||
self.configuration = config_with_defaults(self.configfile)
|
||||
|
||||
return target_version
|
||||
|
||||
|
||||
def save_new_config_values(self, cli_config: configuration.NamespaceConfig) -> None:
|
||||
"""Save only the config information without writing the new cert.
|
||||
|
||||
:param .NamespaceConfig cli_config: parsed command line
|
||||
arguments
|
||||
"""
|
||||
self.cli_config = cli_config
|
||||
symlinks = {kind: self.configuration[kind] for kind in ALL_FOUR}
|
||||
# Update renewal config file
|
||||
self.configfile = update_configuration(
|
||||
self.lineagename, self.archive_dir, symlinks, cli_config)
|
||||
self.configuration = config_with_defaults(self.configfile)
|
||||
|
||||
@@ -14,6 +14,7 @@ from typing import List
|
||||
import unittest
|
||||
from unittest import mock
|
||||
|
||||
import configobj
|
||||
import josepy as jose
|
||||
import pytz
|
||||
|
||||
@@ -530,6 +531,167 @@ class RevokeTest(test_util.TempDirTestCase):
|
||||
self._call()
|
||||
self.assertIs(mock_delete.called, False)
|
||||
|
||||
|
||||
class ReconfigureTest(test_util.TempDirTestCase):
|
||||
"""Tests for certbot._internal.main.reconfigure"""
|
||||
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
self.get_utility_patch = test_util.patch_display_util()
|
||||
self.mock_get_utility = self.get_utility_patch.start()
|
||||
self.patchers = {
|
||||
'check_symlinks': mock.patch('certbot._internal.storage.RenewableCert._check_symlinks'),
|
||||
'cert_names': mock.patch('certbot._internal.storage.RenewableCert.names'),
|
||||
'pick_installer': mock.patch('certbot._internal.plugins.selection.pick_installer'),
|
||||
'pick_auth': mock.patch('certbot._internal.plugins.selection.pick_authenticator'),
|
||||
'find_init': mock.patch('certbot._internal.plugins.disco.PluginsRegistry.find_init'),
|
||||
'_get_and_save_cert': mock.patch('certbot._internal.main._get_and_save_cert'),
|
||||
'_init_le_client': mock.patch('certbot._internal.main._init_le_client'),
|
||||
'list_hooks': mock.patch('certbot._internal.hooks.list_hooks'),
|
||||
}
|
||||
self.mocks = {k: v.start() for k, v in self.patchers.items()}
|
||||
self.mocks['cert_names'].return_value = ['example.com']
|
||||
|
||||
self.config_dir = os.path.join(self.tempdir, 'config')
|
||||
renewal_configs_dir = os.path.join(self.config_dir, 'renewal')
|
||||
if not os.path.exists(renewal_configs_dir):
|
||||
filesystem.makedirs(renewal_configs_dir)
|
||||
self.renewal_file = os.path.join(renewal_configs_dir, 'example.com.conf')
|
||||
original_config = """
|
||||
version = 1.32.0
|
||||
archive_dir = /etc/letsencrypt/archive/example.com
|
||||
cert = /etc/letsencrypt/live/example.com/cert.pem
|
||||
privkey = /etc/letsencrypt/live/example.com/privkey.pem
|
||||
chain = /etc/letsencrypt/live/example.com/chain.pem
|
||||
fullchain = /etc/letsencrypt/live/example.com/fullchain.pem
|
||||
|
||||
# Options used in the renewal process
|
||||
[renewalparams]
|
||||
account = ee43634db0aa4e6804f152be39990e6a
|
||||
server = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
authenticator = nginx
|
||||
installer = nginx
|
||||
key_type = rsa
|
||||
"""
|
||||
with open(self.renewal_file, 'w') as f:
|
||||
f.write(original_config)
|
||||
with open(self.renewal_file, 'r') as f:
|
||||
self.original_config = configobj.ConfigObj(f,
|
||||
encoding='utf-8', default_encoding='utf-8')
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
super().tearDown()
|
||||
self.get_utility_patch.stop()
|
||||
for patch in self.patchers.values():
|
||||
patch.stop()
|
||||
|
||||
def _call(self, passed_args):
|
||||
full_args = passed_args + ['--config-dir', self.config_dir]
|
||||
cli.set_by_cli.detector = None # required to reset set_by_cli state
|
||||
plugins = disco.PluginsRegistry.find_all()
|
||||
config = configuration.NamespaceConfig(
|
||||
cli.prepare_and_parse_args(plugins, full_args))
|
||||
|
||||
from certbot._internal.main import reconfigure
|
||||
reconfigure(config, plugins)
|
||||
|
||||
with open(self.renewal_file, 'r') as f:
|
||||
updated_conf = configobj.ConfigObj(f, encoding='utf-8', default_encoding='utf-8')
|
||||
|
||||
return updated_conf
|
||||
|
||||
def test_domains_set(self):
|
||||
self.assertRaises(errors.ConfigurationError,
|
||||
self._call, '--cert-name cert1 -d one.cert.com'.split())
|
||||
|
||||
@mock.patch('certbot._internal.cert_manager.get_certnames')
|
||||
def test_asks_for_certname(self, mock_cert_manager):
|
||||
mock_cert_manager.return_value = ['example.com']
|
||||
self._call('--nginx'.split())
|
||||
self.assertEqual(mock_cert_manager.call_count, 1)
|
||||
|
||||
def test_update_configurator(self):
|
||||
named_mock = mock.Mock()
|
||||
named_mock.name = 'apache'
|
||||
|
||||
self.mocks['pick_installer'].return_value = named_mock
|
||||
self.mocks['pick_auth'].return_value = named_mock
|
||||
self.mocks['find_init'].return_value = named_mock
|
||||
|
||||
new_config = self._call('--cert-name example.com --apache'.split())
|
||||
self.assertEqual(new_config['renewalparams']['authenticator'], 'apache')
|
||||
|
||||
@mock.patch('certbot._internal.hooks.validate_hooks')
|
||||
def test_update_hooks(self, unused_validate_hooks):
|
||||
self.assertNotIn('pre_hook', self.original_config)
|
||||
# test set
|
||||
new_config = self._call('--cert-name example.com --pre-hook'.split() + ['echo pre'])
|
||||
self.assertEqual(new_config['renewalparams']['pre_hook'], 'echo pre')
|
||||
# test update
|
||||
new_config = self._call('--cert-name example.com --pre-hook'.split() + ['echo pre2'])
|
||||
self.assertEqual(new_config['renewalparams']['pre_hook'], 'echo pre2')
|
||||
|
||||
# test deploy hook is set even though we did a dry run
|
||||
self.assertNotIn('renew_hook', self.original_config)
|
||||
new_config = self._call('--cert-name example.com --deploy-hook'.split() + ['echo deploy'])
|
||||
self.assertEqual(new_config['renewalparams']['renew_hook'], 'echo deploy')
|
||||
|
||||
def test_dry_run_fails(self):
|
||||
# set side effect of raising error
|
||||
self.mocks['_get_and_save_cert'].side_effect = errors.Error
|
||||
|
||||
try:
|
||||
self._call('--cert-name example.com --apache'.split())
|
||||
except errors.Error:
|
||||
pass
|
||||
|
||||
# check that config isn't modified
|
||||
with open(self.renewal_file, 'r') as f:
|
||||
new_config = configobj.ConfigObj(f, encoding='utf-8', default_encoding='utf-8')
|
||||
self.assertEqual(new_config['renewalparams']['authenticator'], 'nginx')
|
||||
|
||||
@mock.patch('certbot._internal.main.display_util.notify')
|
||||
def test_report_results(self, mock_notify):
|
||||
# make sure report results works when config has a webroot map
|
||||
original_config = """
|
||||
version = 2.0.0
|
||||
archive_dir = /etc/letsencrypt/archive/example.com
|
||||
cert = /etc/letsencrypt/live/example.com/cert.pem
|
||||
privkey = /etc/letsencrypt/live/example.com/privkey.pem
|
||||
chain = /etc/letsencrypt/live/example.com/chain.pem
|
||||
fullchain = /etc/letsencrypt/live/example.com/fullchain.pem
|
||||
|
||||
# Options used in the renewal process
|
||||
[renewalparams]
|
||||
account = ee43634db0aa4e6804f152be39990e6a
|
||||
server = https://acme-staging-v02.api.letsencrypt.org/directory
|
||||
authenticator = webroot
|
||||
installer = nginx
|
||||
key_type = ecdsa
|
||||
webroot_path = /var/www/html,
|
||||
[[webroot_map]]
|
||||
example.com = /var/www/html
|
||||
"""
|
||||
with open(self.renewal_file, 'w') as f:
|
||||
f.write(original_config)
|
||||
with open(self.renewal_file, 'r') as f:
|
||||
self.original_config = configobj.ConfigObj(f,
|
||||
encoding='utf-8', default_encoding='utf-8')
|
||||
|
||||
named_mock = mock.Mock()
|
||||
named_mock.name = 'nginx'
|
||||
|
||||
self.mocks['pick_auth'].return_value = named_mock
|
||||
self.mocks['find_init'].return_value = named_mock
|
||||
|
||||
new_config = self._call('--cert-name example.com --nginx'.split())
|
||||
self.assertEqual(new_config['renewalparams']['authenticator'], 'nginx')
|
||||
mock_notify.assert_called_with(
|
||||
'\nSuccessfully updated configuration.'+
|
||||
'\nChanges will apply when the certificate renews.')
|
||||
|
||||
|
||||
class DeleteIfAppropriateTest(test_util.ConfigTestCase):
|
||||
"""Tests for certbot._internal.main._delete_if_appropriate """
|
||||
|
||||
@@ -1492,7 +1654,7 @@ class MainTest(test_util.ConfigTestCase):
|
||||
|
||||
def test_renew_reconstitute_error(self):
|
||||
# pylint: disable=protected-access
|
||||
with mock.patch('certbot._internal.main.renewal._reconstitute') as mock_reconstitute:
|
||||
with mock.patch('certbot._internal.main.renewal.reconstitute') as mock_reconstitute:
|
||||
mock_reconstitute.side_effect = Exception
|
||||
self._test_renew_common(assert_oc_called=False, error_expected=True)
|
||||
|
||||
|
||||
@@ -168,7 +168,7 @@ class RenewalTest(test_util.ConfigTestCase):
|
||||
|
||||
from certbot._internal import renewal
|
||||
lineage_config = copy.deepcopy(self.config)
|
||||
renewal_candidate = renewal._reconstitute(lineage_config, rc_path)
|
||||
renewal_candidate = renewal.reconstitute(lineage_config, rc_path)
|
||||
# This means that manual_public_ip_logging_ok was not modified in the config based on its
|
||||
# value in the renewal conf file
|
||||
self.assertIsInstance(lineage_config.manual_public_ip_logging_ok, mock.MagicMock)
|
||||
|
||||
Reference in New Issue
Block a user