Configuration File Update w/o Certificate Issuance (#9355)

* Add command to update config files without issuing/renewing cert

* toss up a vague untested skeleton

* remove duplicated code

* set certname in config

* consistent name, no zope

* import copy

* reconsitute is in renewal

* import renewal

* import cli

* fix lint errors

* call choose_configurator_plugins for its side effect of writing to config

* Set certonly in choose config plugins as we do for renew

* rewrite by piggybacking on existing side effects of a dry run instead

* do not allow domains to be set while reconfiguring

* remove unused cert_manager.reconfigure

* remove unused imports

* Add comments and messages

* add cli information

* start adding tests

* remove test code

* get certname before setting up plugins

* get plugin from lineage if not set on cli

* import copy

* always reconstitute

* only load cert once

* add error message

* improve comment

* mock everything out for tests

* test functionality is working!

* add tests for adding and modifying hooks

* test that we don't modify the config if the dry run fails

* improve documentation

* add webroot to reconfigure common options

* lint and clean up intermediate artifacts

* mock validate_hooks for windows

* print success message with updated parameters

* Improve success message

* add message for no changes have been made

* improve changed message to show before as well

* syntax

* Add changes will apply at the next renewal message

* lint

* lint really likes dict.items() for some reason

* run the deploy hook

* turn off dry run to test deploy hook

* patch list_hooks call for tests

* factor out reporting results code

* Remove reporting of which values were changed

* add flag to run deploy hook despite doing a dry run, and recommend setting that to yes when running reconfigure and modifying the deploy hook

* missing () around multi-line string

* test if the two dicts are equal instead of finding the actual changes, thus avoiding having to deal with webroot_map being a list

* refer to --deploy-hook instead of deploy hook

* use renewal configuration instead of configuration information

* mention that the deploy hook will use the active cert not the test one

* disable lint and remove new from language asking about running a deploy hook

* pluralize run deploy hook(s)

* Add test for reporting results when there is a webroot map

* update changelog

* Update error message about modifying domains on the certificate

* update changelog

* Add basic integration tests

* Just set -a rather than redoing the whole testing infrastructure

* used webroot in integration test since it's already installed

* file contents are accessed twice now

---------

Co-authored-by: Alex Zorin <alex@zorin.au>
This commit is contained in:
ohemorange
2023-02-04 08:46:08 +11:00
committed by GitHub
co-authored by Alex Zorin
parent 724635bbbd
commit 23090198bf
15 changed files with 360 additions and 20 deletions
@@ -808,6 +808,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')
+2 -1
View File
@@ -6,6 +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.
* `certbot show_account` now displays the [ACME Account Thumbprint](https://datatracker.ietf.org/doc/html/rfc8555#section-8.1).
### Changed
@@ -201,7 +202,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
+16 -5
View File
@@ -135,7 +135,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. "
@@ -157,6 +157,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"),
@@ -374,7 +385,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"
@@ -382,21 +393,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
+1 -1
View File
@@ -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:
+1
View File
@@ -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"
}),
]
+1
View File
@@ -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,
+7 -5
View File
@@ -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
+122
View File
@@ -1,6 +1,7 @@
"""Certbot main entry point."""
# pylint: disable=too-many-lines
import copy
from contextlib import contextmanager
import functools
import logging.handlers
@@ -1658,6 +1659,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,
+2 -2
View File
@@ -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.
@@ -491,7 +491,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.",
+15
View File
@@ -46,6 +46,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.
@@ -1245,6 +1247,19 @@ class RenewableCert(interfaces.RenewableCert):
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)
def truncate(self, num_prior_certs_to_keep: int = 5) -> None:
"""Delete unused historical certificate, chain and key items from the lineage.
+163 -1
View File
@@ -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)
+1 -1
View File
@@ -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)