Merge remote-tracking branch 'origin/master' into issue_2347

This commit is contained in:
Peter Eckersley 2016-02-09 10:30:44 -08:00
commit d148c0c9e2
11 changed files with 233 additions and 125 deletions

View file

@ -337,6 +337,7 @@ def _handle_identical_cert_request(config, cert):
else:
assert False, "This is impossible"
def _handle_subset_cert_request(config, domains, cert):
"""Figure out what to do if a previous cert had a subset of the names now requested
@ -415,10 +416,12 @@ def _suggest_donation_if_appropriate(config):
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
def _report_successful_dry_run():
def _report_successful_dry_run(config):
reporter_util = zope.component.getUtility(interfaces.IReporter)
reporter_util.add_message("The dry run was successful.",
reporter_util.HIGH_PRIORITY, on_crash=False)
if config.verb != "renew":
reporter_util.add_message("The dry run was successful.",
reporter_util.HIGH_PRIORITY, on_crash=False)
def _auth_from_domains(le_client, config, domains, lineage=None):
@ -438,7 +441,7 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
else:
# Renewal, where we already know the specific lineage we're
# interested in
action = "renew" if _should_renew(config, lineage) else "reinstall"
action = "renew"
if action == "reinstall":
# The lineage already exists; allow the caller to try installing
@ -470,7 +473,7 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
if lineage is False:
raise errors.Error("Certificate could not be obtained")
if not config.dry_run:
if not config.dry_run and not config.verb == "renew":
_report_new_cert(lineage.cert, lineage.fullchain)
return lineage, action
@ -487,7 +490,7 @@ def _avoid_invalidating_lineage(config, lineage, original_server):
open(lineage.cert).read())
# all our test certs are from happy hacker fake CA, though maybe one day
# we should test more methodically
now_valid = not "fake" in repr(latest_cert.get_issuer()).lower()
now_valid = "fake" not in repr(latest_cert.get_issuer()).lower()
if _is_staging(config.server):
if not _is_staging(original_server) or now_valid:
@ -546,6 +549,7 @@ def set_configurator(previously, now):
raise errors.PluginSelectionError(msg.format(repr(previously), repr(now)))
return now
def cli_plugin_requests(config):
"""
Figure out which plugins the user requested with CLI and config options
@ -574,6 +578,7 @@ def cli_plugin_requests(config):
noninstaller_plugins = ["webroot", "manual", "standalone"]
def choose_configurator_plugins(config, plugins, verb):
"""
Figure out which configurator we're going to use, modifies
@ -596,7 +601,7 @@ def choose_configurator_plugins(config, plugins, verb):
'{1} {2} certonly --{0}{1}{1}'
'(Alternatively, add a --installer flag. See https://eff.org/letsencrypt-plugins'
'{1} and "--help plugins" for more information.)'.format(
req_auth, os.linesep, cli_command))
req_auth, os.linesep, cli_command))
raise errors.MissingCommandlineFlag(msg)
else:
@ -671,16 +676,12 @@ def run(config, plugins): # pylint: disable=too-many-branches,too-many-locals
def obtain_cert(config, plugins, lineage=None):
"""Implements "certonly": authenticate & obtain cert, but do not install it."""
if config.domains and config.csr is not None:
# TODO: --csr could have a priority, when --domains is
# supplied, check if CSR matches given domains?
return "--domains and --csr are mutually exclusive"
try:
# installers are used in auth mode to determine domain names
installer, authenticator = choose_configurator_plugins(config, plugins, "certonly")
except errors.PluginSelectionError as e:
return e.message
logger.info("Could not choose appropriate plugin: %s", e)
raise
# TODO: Handle errors from _init_le_client?
le_client = _init_le_client(config, authenticator, installer)
@ -688,8 +689,8 @@ def obtain_cert(config, plugins, lineage=None):
# This is a special case; cert and chain are simply saved
if config.csr is not None:
assert lineage is None, "Did not expect a CSR with a RenewableCert"
certr, chain = le_client.obtain_certificate_from_csr(le_util.CSR(
file=config.csr[0], data=config.csr[1], form="der"))
csr, typ = config.actual_csr
certr, chain = le_client.obtain_certificate_from_csr(config.domains, csr, typ)
if config.dry_run:
logger.info(
"Dry run: skipping saving certificate to %s", config.cert_path)
@ -702,13 +703,19 @@ def obtain_cert(config, plugins, lineage=None):
_auth_from_domains(le_client, config, domains, lineage)
if config.dry_run:
_report_successful_dry_run()
elif config.verb == "renew" and installer is not None:
# In case of a renewal, reload server to pick up new certificate.
# In principle we could have a configuration option to inhibit this
# from happening.
installer.restart()
print("reloaded")
_report_successful_dry_run(config)
elif config.verb == "renew":
if installer is None:
# Tell the user that the server was not restarted.
print("new certificate deployed without reload, fullchain is",
lineage.fullchain)
else:
# In case of a renewal, reload server to pick up new certificate.
# In principle we could have a configuration option to inhibit this
# from happening.
installer.restart()
print("new certificate deployed with reload of",
config.installer, "server; fullchain is", lineage.fullchain)
_suggest_donation_if_appropriate(config)
@ -793,6 +800,7 @@ def _restore_required_config_elements(config, renewalparams):
raise errors.Error(
"Expected a numeric value for {0}".format(config_item))
def _restore_plugin_configs(config, renewalparams):
"""Sets plugin specific values in config from renewalparams
@ -825,8 +833,7 @@ def _restore_plugin_configs(config, renewalparams):
if config_value == "None":
setattr(config.namespace, config_item, None)
continue
for action in _parser.parser._actions: # pylint: disable=protected-access
for action in _parser.parser._actions: # pylint: disable=protected-access
if action.type is not None and action.dest == config_item:
setattr(config.namespace, config_item,
action.type(config_value))
@ -905,6 +912,42 @@ def _renewal_conf_files(config):
return glob.glob(os.path.join(config.renewal_configs_dir, "*.conf"))
def _renew_describe_results(config, renew_successes, renew_failures,
renew_skipped, parse_failures):
status = lambda x, msg: " " + "\n ".join(i + " (" + msg +")" for i in x)
if config.dry_run:
print("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry")
print("** (The test certificates below have not been saved.)")
print()
if renew_skipped:
print("The following certs are not due for renewal yet:")
print(status(renew_skipped, "skipped"))
if not renew_successes and not renew_failures:
print("No renewals were attempted.")
elif renew_successes and not renew_failures:
print("Congratulations, all renewals succeeded. The following certs "
"have been renewed:")
print(status(renew_successes, "success"))
elif renew_failures and not renew_successes:
print("All renewal attempts failed. The following certs could not be "
"renewed:")
print(status(renew_failures, "failure"))
elif renew_failures and renew_successes:
print("The following certs were successfully renewed:")
print(status(renew_successes, "success"))
print("\nThe following certs could not be renewed:")
print(status(renew_failures, "failure"))
if parse_failures:
print("\nAdditionally, the following renewal configuration files "
"were invalid: ")
print(status(parse_failures, "parsefail"))
if config.dry_run:
print("** DRY RUN: simulating 'letsencrypt renew' close to cert expiry")
print("** (The test certificates above have not been saved.)")
def renew(config, unused_plugins):
"""Renew previously-obtained certificates."""
@ -921,44 +964,47 @@ def renew(config, unused_plugins):
"specifying a CSR file. Please try the certonly "
"command instead.")
renewer_config = configuration.RenewerConfiguration(config)
renew_successes = []
renew_failures = []
renew_skipped = []
parse_failures = []
for renewal_file in _renewal_conf_files(renewer_config):
print("Processing " + renewal_file)
# XXX: does this succeed in making a fully independent config object
# each time?
lineage_config = copy.deepcopy(config)
# 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)
except Exception as e: # pylint: disable=broad-except
# reconstitute encountered an unanticipated problem.
except Exception as e: # pylint: disable=broad-except
logger.warning("Renewal configuration file %s produced an "
"unexpected error: %s. Skipping.", renewal_file, e)
logger.debug("Traceback was:\n%s", traceback.format_exc())
parse_failures.append(renewal_file)
continue
try:
if renewal_candidate is not None:
# _reconstitute succeeded in producing a RenewableCert, so we
# have something to work with from this particular config file.
if renewal_candidate is None:
parse_failures.append(renewal_file)
else:
# XXX: ensure that each call here replaces the previous one
zope.component.provideUtility(lineage_config)
print("Trying...")
# Because obtain_cert itself indirectly decides whether to renew
# or not, we couldn't currently make a UI/logging distinction at
# this stage to indicate whether renewal was actually attempted
# (or successful).
obtain_cert(lineage_config,
plugins_disco.PluginsRegistry.find_all(),
renewal_candidate)
except Exception as e: # pylint: disable=broad-except
if _should_renew(lineage_config, renewal_candidate):
plugins = plugins_disco.PluginsRegistry.find_all()
obtain_cert(lineage_config, plugins, renewal_candidate)
renew_successes.append(renewal_candidate.fullchain)
else:
renew_skipped.append(renewal_candidate.fullchain)
except Exception as e: # pylint: disable=broad-except
# obtain_cert (presumably) encountered an unanticipated problem.
logger.warning("Attempting to renew cert from %s produced an "
"unexpected error: %s. Skipping.", renewal_file, e)
logger.debug("Traceback was:\n%s", traceback.format_exc())
renew_failures.append(renewal_candidate.fullchain)
# Describe all the results
_renew_describe_results(config, renew_successes, renew_failures,
renew_skipped, parse_failures)
def revoke(config, unused_plugins): # TODO: coop with renewal config
@ -1155,11 +1201,56 @@ class HelpfulArgumentParser(object):
"'certonly' or 'renew' subcommands (%r)" % self.verb)
parsed_args.break_my_certs = parsed_args.staging = True
if parsed_args.csr:
self.handle_csr(parsed_args)
if self.detect_defaults: # plumbing
parsed_args.store_false_vars = self.store_false_vars
return parsed_args
def handle_csr(self, parsed_args):
"""
Process a --csr flag. This needs to happen early enough that the
webroot plugin can know about the calls to _process_domain
"""
try:
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="der")
typ = OpenSSL.crypto.FILETYPE_ASN1
domains = crypto_util.get_sans_from_csr(csr.data, OpenSSL.crypto.FILETYPE_ASN1)
except OpenSSL.crypto.Error:
try:
e1 = traceback.format_exc()
typ = OpenSSL.crypto.FILETYPE_PEM
csr = le_util.CSR(file=parsed_args.csr[0], data=parsed_args.csr[1], form="pem")
domains = crypto_util.get_sans_from_csr(csr.data, typ)
except OpenSSL.crypto.Error:
logger.debug("DER CSR parse error %s", e1)
logger.debug("PEM CSR parse error %s", traceback.format_exc())
raise errors.Error("Failed to parse CSR file: {0}".format(parsed_args.csr[0]))
for d in domains:
_process_domain(parsed_args, d)
for d in domains:
sanitised = le_util.enforce_domain_sanity(d)
if d.lower() != sanitised:
raise errors.ConfigurationError(
"CSR domain {0} needs to be sanitised to {1}.".format(d, sanitised))
if not domains:
# TODO: add CN to domains instead:
raise errors.Error(
"Unfortunately, your CSR %s needs to have a SubjectAltName for every domain"
% parsed_args.csr[0])
parsed_args.actual_csr = (csr, typ)
csr_domains, config_domains = set(domains), set(parsed_args.domains)
if csr_domains != config_domains:
raise errors.ConfigurationError(
"Inconsistent domain requests:\nFrom the CSR: {0}\nFrom command line/config: {1}"
.format(", ".join(csr_domains), ", ".join(config_domains)))
def determine_verb(self):
"""Determines the verb/subcommand provided by the user.
@ -1617,14 +1708,17 @@ def _plugins_parsing(helpful, plugins):
"www.example.com -w /var/www/thing -d thing.net -d m.thing.net`")
# --webroot-map still has some awkward properties, so it is undocumented
helpful.add("webroot", "--webroot-map", default={}, action=WebrootMapProcessor,
help="JSON dictionary mapping domains to webroot paths; this implies -d "
"for each entry. You may need to escape this from your shell. "
"""Eg: --webroot-map '{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' """
"This option is merged with, but takes precedence over, -w / -d entries."
" At present, if you put webroot-map in a config file, it needs to be "
' on a single line, like: webroot-map = {"example.com":"/var/www"}.')
help="JSON dictionary mapping domains to webroot paths; this "
"implies -d for each entry. You may need to escape this "
"from your shell. E.g.: --webroot-map "
"""'{"eg1.is,m.eg1.is":"/www/eg1/", "eg2.is":"/www/eg2"}' """
"This option is merged with, but takes precedence over, "
"-w / -d entries. At present, if you put webroot-map in "
"a config file, it needs to be on a single line, like: "
'webroot-map = {"example.com":"/var/www"}.')
class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring
class WebrootPathProcessor(argparse.Action): # pylint: disable=missing-docstring
def __init__(self, *args, **kwargs):
self.domain_before_webroot = False
argparse.Action.__init__(self, *args, **kwargs)
@ -1673,14 +1767,14 @@ def _process_domain(args_or_config, domain_arg, webroot_path=None):
args_or_config.webroot_map.setdefault(domain, webroot_path[-1])
class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring
class WebrootMapProcessor(argparse.Action): # pylint: disable=missing-docstring
def __call__(self, parser, args, webroot_map_arg, option_string=None):
webroot_map = json.loads(webroot_map_arg)
for domains, webroot_path in webroot_map.iteritems():
_process_domain(args, domains, [webroot_path])
class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring
class DomainFlagProcessor(argparse.Action): # pylint: disable=missing-docstring
def __call__(self, parser, args, domain_arg, option_string=None):
"""Just wrap _process_domain in argparseese."""
_process_domain(args, domain_arg)
@ -1771,8 +1865,8 @@ def _handle_exception(exc_type, exc_value, trace, config):
# acme.messages.Error: urn:acme:error:malformed :: The request message was
# malformed :: Error creating new registration :: Validation of contact
# mailto:none@longrandomstring.biz failed: Server failure at resolver
if ("urn:acme" in err and ":: " in err
and config.verbose_count <= flag_default("verbose_count")):
if (("urn:acme" in err and ":: " in err and
config.verbose_count <= flag_default("verbose_count"))):
# prune ACME error code, we have a human description
_code, _sep, err = err.partition(":: ")
msg = "An unexpected error occurred:\n" + err + "Please see the "

View file

@ -195,7 +195,8 @@ class Client(object):
else:
self.auth_handler = None
def _obtain_certificate(self, domains, csr):
def obtain_certificate_from_csr(self, domains, csr,
typ=OpenSSL.crypto.FILETYPE_ASN1):
"""Obtain certificate.
Internal function with precondition that `domains` are
@ -223,26 +224,11 @@ class Client(object):
authzr = self.auth_handler.get_authorizations(domains)
certr = self.acme.request_issuance(
jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, csr.data)),
jose.ComparableX509(
OpenSSL.crypto.load_certificate_request(typ, csr.data)),
authzr)
return certr, self.acme.fetch_chain(certr)
def obtain_certificate_from_csr(self, csr):
"""Obtain certficiate from CSR.
:param .le_util.CSR csr: DER-encoded Certificate Signing
Request.
:returns: `.CertificateResource` and certificate chain (as
returned by `.fetch_chain`).
:rtype: tuple
"""
return self._obtain_certificate(
# TODO: add CN to domains?
crypto_util.get_sans_from_csr(
csr.data, OpenSSL.crypto.FILETYPE_ASN1), csr)
def obtain_certificate(self, domains):
"""Obtains a certificate from the ACME server.
@ -263,7 +249,7 @@ class Client(object):
self.config.rsa_key_size, self.config.key_dir)
csr = crypto_util.init_save_csr(key, domains, self.config.csr_dir)
return self._obtain_certificate(domains, csr) + (key, csr)
return self.obtain_certificate_from_csr(domains, csr) + (key, csr)
def obtain_and_enroll_certificate(self, domains):
"""Obtain and enroll certificate.

View file

@ -308,7 +308,7 @@ def enforce_domain_sanity(domain):
# Unicode
try:
domain = domain.encode('ascii')
domain = domain.encode('ascii').lower()
except UnicodeDecodeError:
raise errors.ConfigurationError(
"Internationalized domain names are not presently supported: {0}"

View file

@ -91,7 +91,7 @@ s.serve_forever()" """
help="Automatically allows public IP logging.")
def prepare(self): # pylint: disable=missing-docstring,no-self-use
if self.config.noninteractive_mode:
if self.config.noninteractive_mode and not self.conf("test-mode"):
raise errors.PluginError("Running manual mode non-interactively is not supported")
def more_info(self): # pylint: disable=missing-docstring,no-self-use

View file

@ -23,16 +23,21 @@ class AuthenticatorTest(unittest.TestCase):
def setUp(self):
from letsencrypt.plugins.manual import Authenticator
self.config = mock.MagicMock(
http01_port=8080, manual_test_mode=False, manual_public_ip_logging_ok=False)
http01_port=8080, manual_test_mode=False,
manual_public_ip_logging_ok=False, noninteractive_mode=True)
self.auth = Authenticator(config=self.config, name="manual")
self.achalls = [achallenges.KeyAuthorizationAnnotatedChallenge(
challb=acme_util.HTTP01_P, domain="foo.com", account_key=KEY)]
config_test_mode = mock.MagicMock(
http01_port=8080, manual_test_mode=True)
http01_port=8080, manual_test_mode=True, noninteractive_mode=True)
self.auth_test_mode = Authenticator(
config=config_test_mode, name="manual")
def test_prepare(self):
self.assertRaises(errors.PluginError, self.auth.prepare)
self.auth_test_mode.prepare() # error not raised
def test_more_info(self):
self.assertTrue(isinstance(self.auth.more_info(), str))

View file

@ -90,6 +90,9 @@ class ServerManager(object):
logger.debug("Stopping server at %s:%d...",
*instance.server.socket.getsockname()[:2])
instance.server.shutdown()
# Not calling server_close causes problems when renewing multiple
# certs with `letsencrypt renew` using TLSSNI01 and PyOpenSSL 0.13
instance.server.server_close()
instance.thread.join()
del self._instances[port]

View file

@ -49,8 +49,10 @@ to serve all files under specified web root ({0})."""
path_map = self.conf("map")
if not path_map:
raise errors.PluginError("--{0} must be set".format(
self.option_name("path")))
raise errors.PluginError(
"Missing parts of webroot configuration; please set either "
"--webroot-path and --domains, or --webroot-map. Run with "
" --help webroot for examples.")
for name, path in path_map.items():
if not os.path.isdir(path):
raise errors.PluginError(path + " does not exist or is not a directory")

View file

@ -227,8 +227,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertTrue("MisconfigurationError" in ret)
args = ["certonly", "--webroot"]
ret, _, _, _ = self._call(args)
self.assertTrue("--webroot-path must be set" in ret)
try:
self._call(args)
assert False, "Exception should have been raised"
except errors.PluginSelectionError as e:
self.assertTrue("please set either --webroot-path" in e.message)
self._cli_missing_flag(["--standalone"], "With the standalone plugin, you probably")
@ -323,11 +326,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertEqual(config.fullchain_path, os.path.abspath(fullchain))
def test_certonly_bad_args(self):
ret, _, _, _ = self._call(['-d', 'foo.bar', 'certonly', '--csr', CSR])
self.assertEqual(ret, '--domains and --csr are mutually exclusive')
ret, _, _, _ = self._call(['-a', 'bad_auth', 'certonly'])
self.assertEqual(ret, 'The requested bad_auth plugin does not appear to be installed')
try:
self._call(['-a', 'bad_auth', 'certonly'])
assert False, "Exception should have been raised"
except errors.PluginSelectionError as e:
self.assertTrue('The requested bad_auth plugin does not appear' in e.message)
def test_check_config_sanity_domain(self):
# Punycode
@ -633,6 +636,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._make_dummy_renewal_config()
with mock.patch('letsencrypt.storage.RenewableCert') as mock_rc:
mock_lineage = mock.MagicMock()
mock_lineage.fullchain = "somepath/fullchain.pem"
if renewalparams is not None:
mock_lineage.configuration = {'renewalparams': renewalparams}
if names is not None:
@ -682,6 +686,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._make_dummy_renewal_config()
with mock.patch('letsencrypt.storage.RenewableCert') as mock_rc:
mock_lineage = mock.MagicMock()
mock_lineage.fullchain = "somewhere/fullchain.pem"
mock_rc.return_value = mock_lineage
mock_lineage.configuration = {
'renewalparams': {'authenticator': 'webroot'}}

View file

@ -82,6 +82,7 @@ class ClientTest(unittest.TestCase):
no_verify_ssl=False, config_dir="/etc/letsencrypt")
# pylint: disable=star-args
self.account = mock.MagicMock(**{"key.pem": KEY})
self.eg_domains = ["example.com", "www.example.com"]
from letsencrypt.client import Client
with mock.patch("letsencrypt.client.acme_client.Client") as acme:
@ -101,21 +102,40 @@ class ClientTest(unittest.TestCase):
self.acme.fetch_chain.return_value = mock.sentinel.chain
def _check_obtain_certificate(self):
self.client.auth_handler.get_authorizations.assert_called_once_with(
["example.com", "www.example.com"])
self.client.auth_handler.get_authorizations.assert_called_once_with(self.eg_domains)
self.acme.request_issuance.assert_called_once_with(
jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
OpenSSL.crypto.FILETYPE_ASN1, CSR_SAN)),
self.client.auth_handler.get_authorizations())
self.acme.fetch_chain.assert_called_once_with(mock.sentinel.certr)
def test_obtain_certificate_from_csr(self):
# FIXME move parts of this to test_cli.py...
@mock.patch("letsencrypt.cli._process_domain")
def test_obtain_certificate_from_csr(self, mock_process_domain):
self._mock_obtain_certificate()
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(le_util.CSR(
form="der", file=None, data=CSR_SAN)))
self._check_obtain_certificate()
from letsencrypt import cli
test_csr = le_util.CSR(form="der", file=None, data=CSR_SAN)
mock_parsed_args = mock.MagicMock()
with mock.patch("letsencrypt.client.le_util.CSR") as mock_CSR:
mock_CSR.return_value = test_csr
mock_parsed_args.domains = self.eg_domains[:]
mock_parser = mock.MagicMock(cli.HelpfulArgumentParser)
cli.HelpfulArgumentParser.handle_csr(mock_parser, mock_parsed_args)
# make sure cli processing occurred
cli_processed = (call[0][1] for call in mock_process_domain.call_args_list)
self.assertEqual(set(cli_processed), set(("example.com", "www.example.com")))
# Now provoke an inconsistent domains error...
mock_parsed_args.domains.append("hippopotamus.io")
self.assertRaises(errors.ConfigurationError,
cli.HelpfulArgumentParser.handle_csr, mock_parser, mock_parsed_args)
self.assertEqual(
(mock.sentinel.certr, mock.sentinel.chain),
self.client.obtain_certificate_from_csr(self.eg_domains, test_csr))
# and that the cert was obtained correctly
self._check_obtain_certificate()
@mock.patch("letsencrypt.client.crypto_util")
def test_obtain_certificate(self, mock_crypto_util):

View file

@ -20,17 +20,16 @@ else
readlink="readlink"
fi
common() {
letsencrypt_test \
common_no_force_renew() {
letsencrypt_test_no_force_renew \
--authenticator standalone \
--installer null \
"$@"
}
common_no_force_renew() {
letsencrypt_test_no_force_renew \
--authenticator standalone \
--installer null \
common() {
common_no_force_renew \
--renew-by-default \
"$@"
}
@ -51,21 +50,27 @@ common --domains le3.wtf install \
--cert-path "${root}/csr/cert.pem" \
--key-path "${root}/csr/key.pem"
CheckCertCount() {
CERTCOUNT=`ls "${root}/conf/archive/le.wtf/cert"* | wc -l`
if [ "$CERTCOUNT" -ne "$1" ] ; then
echo Wrong cert count, not "$1" `ls "${root}/conf/archive/le.wtf/"*`
exit 1
fi
}
CheckCertCount 1
# This won't renew (because it's not time yet)
letsencrypt_test_no_force_renew --authenticator standalone --installer null renew
common_no_force_renew renew
CheckCertCount 1
# --renew-by-default is used, so renewal should occur
common renew
CheckCertCount 2
# This will renew because the expiry is less than 10 years from now
sed -i "4arenew_before_expiry = 10 years" "$root/conf/renewal/le1.wtf.conf"
letsencrypt_test_no_force_renew --authenticator standalone --installer null renew
ls "$root/conf/archive/le1.wtf"
# dir="$root/conf/archive/le1.wtf"
# for x in cert chain fullchain privkey;
# do
# latest="$(ls -1t $dir/ | grep -e "^${x}" | head -n1)"
# live="$($readlink -f "$root/conf/live/le1.wtf/${x}.pem")"
# [ "${dir}/${latest}" = "$live" ] # renewer fails this test
# done
sed -i "4arenew_before_expiry = 10 years" "$root/conf/renewal/le.wtf.conf"
common_no_force_renew renew
CheckCertCount 3
# revoke by account key
common revoke --cert-path "$root/conf/live/le.wtf/cert.pem"

View file

@ -12,20 +12,8 @@ store_flags="$store_flags --logs-dir $root/logs"
export root store_flags
letsencrypt_test () {
letsencrypt \
--server "${SERVER:-http://localhost:4000/directory}" \
--no-verify-ssl \
--tls-sni-01-port 5001 \
--http-01-port 5002 \
--manual-test-mode \
$store_flags \
--text \
--no-redirect \
--agree-tos \
--register-unsafely-without-email \
letsencrypt_test_no_force_renew \
--renew-by-default \
--debug \
-vvvvvvv \
"$@"
}
@ -37,7 +25,7 @@ letsencrypt_test_no_force_renew () {
--http-01-port 5002 \
--manual-test-mode \
$store_flags \
--text \
--non-interactive \
--no-redirect \
--agree-tos \
--register-unsafely-without-email \