Compare commits

...

69 Commits

Author SHA1 Message Date
Erica Portnoy
e6ceb1fc50 file contents are accessed twice now 2023-01-09 16:09:09 -08:00
Erica Portnoy
edd78d7c1c used webroot in integration test since it's already installed 2023-01-09 15:57:40 -08:00
Erica Portnoy
3c9973b4d6 Just set -a rather than redoing the whole testing infrastructure 2023-01-09 14:42:27 -08:00
Erica Portnoy
7427daaa6c Add basic integration tests 2023-01-09 14:29:18 -08:00
Erica Portnoy
44154708d9 update changelog 2023-01-09 13:53:27 -08:00
Erica Portnoy
87c143d9ca Merge branch 'master' into update-config 2023-01-09 13:52:11 -08:00
Erica Portnoy
232e8625af Update error message about modifying domains on the certificate 2023-01-09 13:51:56 -08:00
Erica Portnoy
62556486a1 update changelog 2022-12-01 15:12:07 -08:00
Erica Portnoy
ad8d49f9dc Add test for reporting results when there is a webroot map 2022-12-01 13:44:07 -08:00
Erica Portnoy
8098506e51 Merge branch 'master' into update-config 2022-12-01 13:02:17 -08:00
Erica Portnoy
2ce70a32bc pluralize run deploy hook(s) 2022-12-01 13:01:47 -08:00
Erica Portnoy
0ab42fd3c2 disable lint and remove new from language asking about running a deploy hook 2022-12-01 12:58:03 -08:00
Erica Portnoy
4336547979 mention that the deploy hook will use the active cert not the test one 2022-11-29 17:08:22 -08:00
Erica Portnoy
16365e95e2 use renewal configuration instead of configuration information 2022-11-29 17:02:49 -08:00
Erica Portnoy
80d5c6a18b refer to --deploy-hook instead of deploy hook 2022-11-29 17:01:39 -08:00
Erica Portnoy
15d25ff257 test if the two dicts are equal instead of finding the actual changes, thus avoiding having to deal with webroot_map being a list 2022-11-29 17:00:37 -08:00
Erica Portnoy
68781a9813 missing () around multi-line string 2022-11-29 16:40:18 -08:00
Erica Portnoy
eb932e1a41 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 2022-11-23 12:04:38 -08:00
Erica Portnoy
610b9aec5c Merge branch 'master' into update-config 2022-11-23 11:23:12 -08:00
Erica Portnoy
034ba23269 Remove reporting of which values were changed 2022-11-17 16:45:25 -08:00
Erica Portnoy
5467dd72e1 Merge branch 'master' into update-config 2022-11-17 16:29:44 -08:00
Erica Portnoy
c6efe45e78 factor out reporting results code 2022-10-28 17:19:59 -07:00
Erica Portnoy
928a7ec531 patch list_hooks call for tests 2022-10-28 17:07:45 -07:00
Erica Portnoy
adcd6e60ab turn off dry run to test deploy hook 2022-10-28 17:00:43 -07:00
Erica Portnoy
de082ee398 run the deploy hook 2022-10-28 16:47:50 -07:00
Erica Portnoy
9d686c3110 lint really likes dict.items() for some reason 2022-10-28 16:42:36 -07:00
Erica Portnoy
720836a7c1 lint 2022-10-28 16:24:48 -07:00
Erica Portnoy
ecf6d2e81a Add changes will apply at the next renewal message 2022-10-28 16:18:19 -07:00
Erica Portnoy
8746454851 syntax 2022-10-28 15:59:19 -07:00
Erica Portnoy
0c11c00da7 improve changed message to show before as well 2022-10-28 15:58:07 -07:00
Erica Portnoy
80d60d6fbf add message for no changes have been made 2022-10-28 15:48:14 -07:00
Erica Portnoy
e4470134c7 Improve success message 2022-10-28 15:45:33 -07:00
Erica Portnoy
cc7dc32b71 print success message with updated parameters 2022-10-28 15:32:59 -07:00
Erica Portnoy
1fbfe1a64e mock validate_hooks for windows 2022-10-14 17:47:23 -07:00
Erica Portnoy
9f99ad0201 lint and clean up intermediate artifacts 2022-10-14 14:15:12 -07:00
Erica Portnoy
97bc3516c5 add webroot to reconfigure common options 2022-10-14 14:09:10 -07:00
Erica Portnoy
d4e731e632 improve documentation 2022-10-14 14:00:57 -07:00
Erica Portnoy
0069961c83 test that we don't modify the config if the dry run fails 2022-10-14 13:56:16 -07:00
Erica Portnoy
cad3019a53 add tests for adding and modifying hooks 2022-10-14 13:42:40 -07:00
Erica Portnoy
ea946f376c test functionality is working! 2022-10-13 20:22:00 -07:00
Erica Portnoy
5f4123141c mock everything out for tests 2022-10-13 14:54:32 -07:00
Erica Portnoy
11d5f0c090 improve comment 2022-10-12 18:26:39 -07:00
Erica Portnoy
45606c270a add error message 2022-10-12 18:25:15 -07:00
Erica Portnoy
8b1b389cbb only load cert once 2022-10-12 18:21:09 -07:00
Erica Portnoy
1ca7220177 always reconstitute 2022-10-12 18:12:54 -07:00
Erica Portnoy
9e99a893f4 import copy 2022-10-12 18:08:35 -07:00
Erica Portnoy
f6a7fcd484 get plugin from lineage if not set on cli 2022-10-12 18:07:45 -07:00
Erica Portnoy
e957a7e25a get certname before setting up plugins 2022-10-12 17:48:24 -07:00
Erica Portnoy
e377a30b80 remove test code 2022-10-12 17:39:17 -07:00
Erica Portnoy
bd81191dc4 start adding tests 2022-10-12 17:02:03 -07:00
Erica Portnoy
8eb46706c1 add cli information 2022-10-12 15:43:20 -07:00
Erica Portnoy
5fd18ce285 Add comments and messages 2022-10-12 15:31:27 -07:00
Erica Portnoy
92ae44ad44 remove unused imports 2022-10-12 15:19:55 -07:00
Erica Portnoy
c51a48e20e Merge branch 'master' into update-config 2022-10-12 15:18:39 -07:00
Erica Portnoy
81ceb6adfe remove unused cert_manager.reconfigure 2022-10-12 15:18:17 -07:00
Erica Portnoy
5cefba1fa1 do not allow domains to be set while reconfiguring 2022-09-07 13:28:25 -07:00
Erica Portnoy
e5a658f009 rewrite by piggybacking on existing side effects of a dry run instead 2022-09-07 13:10:56 -07:00
Erica Portnoy
ddf141c0d4 Set certonly in choose config plugins as we do for renew 2022-09-07 12:57:30 -07:00
Erica Portnoy
7c7fde35c7 call choose_configurator_plugins for its side effect of writing to config 2022-09-06 16:45:43 -07:00
Erica Portnoy
d5c2358ae4 fix lint errors 2022-08-31 15:34:21 -07:00
Erica Portnoy
439a67e081 import cli 2022-07-18 16:58:46 -07:00
Erica Portnoy
d7e32a0e84 import renewal 2022-07-18 16:58:19 -07:00
Erica Portnoy
ae2947b259 reconsitute is in renewal 2022-07-18 16:57:26 -07:00
Erica Portnoy
d9f100b4a7 import copy 2022-07-18 16:56:10 -07:00
Erica Portnoy
204eaac19d consistent name, no zope 2022-07-18 16:55:02 -07:00
Erica Portnoy
0bc84dfff7 set certname in config 2022-07-18 16:52:53 -07:00
Erica Portnoy
2b5c957dd1 remove duplicated code 2022-07-18 16:50:11 -07:00
Erica Portnoy
8584d5f736 toss up a vague untested skeleton 2022-07-18 16:48:14 -07:00
Erica Portnoy
4172158c31 Add command to update config files without issuing/renewing cert 2022-07-18 15:58:38 -07:00
15 changed files with 361 additions and 21 deletions

View File

@@ -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')

View File

@@ -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

View File

@@ -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'

View File

@@ -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

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:

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

View File

@@ -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",

View File

@@ -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"
}),
]

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,

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

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
@@ -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,

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.
@@ -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.",

View File

@@ -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)

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)

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)