Merge branch 'master' into renew_verb

Conflicts:
	letsencrypt/cli.py
This commit is contained in:
Seth Schoen 2016-02-02 13:42:55 -08:00
commit 8f26e69cfe
9 changed files with 297 additions and 126 deletions

View file

@ -107,7 +107,7 @@ and the version implemented by the Let's Encrypt client will be the
version that was most current as of the release date of each client
version. Mozilla offers three separate sets of cryptographic options,
which trade off security and compatibility differently. These are
referred to as as the "Modern", "Intermediate", and "Old" configurations
referred to as the "Modern", "Intermediate", and "Old" configurations
(in order from most secure to least secure, and least-backwards compatible
to most-backwards compatible). The client will follow the Mozilla defaults
for the *Intermediate* configuration by default, at least with regards to

View file

@ -644,11 +644,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
"""
if self.conf("handle-modules"):
if "ssl_module" not in self.parser.modules:
self.enable_mod("ssl", temp=temp)
if self.version >= (2, 4) and ("socache_shmcb_module" not in
self.parser.modules):
self.enable_mod("socache_shmcb", temp=temp)
if "ssl_module" not in self.parser.modules:
self.enable_mod("ssl", temp=temp)
def make_addrs_sni_ready(self, addrs):
"""Checks to see if the server is ready for SNI challenges.

View file

@ -429,9 +429,15 @@ class TwoVhost80Test(util.ApacheTest):
self.config.parser.add_dir_to_ifmodssl = mock_add_dir
self.config.prepare_server_https("443")
# Changing the order these modules are enabled breaks the reverter
self.assertEqual(mock_enable.call_args_list[0][0][0], "socache_shmcb")
self.assertEqual(mock_enable.call_args[0][0], "ssl")
self.assertEqual(mock_enable.call_args[1], {"temp": False})
self.config.prepare_server_https("8080", temp=True)
# Changing the order these modules are enabled breaks the reverter
self.assertEqual(mock_enable.call_args_list[2][0][0], "socache_shmcb")
self.assertEqual(mock_enable.call_args[0][0], "ssl")
# Enable mod is temporary
self.assertEqual(mock_enable.call_args[1], {"temp": True})

View file

@ -438,8 +438,8 @@ if [ "$NO_SELF_UPGRADE" = 1 ]; then
# -------------------------------------------------------------------------
cat << "UNLIKELY_EOF" > "$TEMP_DIR/letsencrypt-auto-requirements.txt"
# This is the flattened list of packages letsencrypt-auto installs. To generate
# this, do `pip install -e acme -e . -e letsencrypt-apache`, `pip freeze`,
# and then gather the hashes.
# this, do `pip install --no-cache-dir -e acme -e . -e letsencrypt-apache`, and
# then use `hashin` or a more secure method to gather the hashes.
# sha256: wxZH7baf09RlqEfqMVfTe-0flfGXYLEaR6qRwEtmYxQ
# sha256: YrCJpVvh2JSc0rx-DfC9254Cj678jDIDjMhIYq791uQ
@ -508,6 +508,10 @@ idna==2.0
# sha256: WjGCsyKnBlJcRigspvBk0noCz_vUSfn0dBbx3JaqcbA
ipaddress==1.0.16
# sha256: 54vpwKDfy6xxL-BPv5K5bN2ugLG4QvJCSCFMhJbwBu8
# sha256: Syb_TnEQ23butvWntkqCYjg51ZXCA47tpmLyott46Xw
linecache2==1.0.0
# sha256: 6MFV_evZxLywgQtO0BrhmHVUse4DTddTLXuP2uOKYnQ
ndg-httpsclient==0.4.0
@ -599,6 +603,14 @@ requests==2.9.1
# sha256: EF-NaGFvgkjiS_DpNy7wTTzBAQTxmA9U1Xss5zpa1Wo
six==1.10.0
# sha256: glPOvsSxkJTWfMXtWvmb8duhKFKSIm6Yoxkp-HpdayM
# sha256: BazGegmYDC7P7dNCP3rgEEg57MtV_GRXc-HKoJUcMDA
traceback2==1.4.0
# sha256: E_d9CHXbbZtDXh1PQedK1MwutuHVyCSZYJKzQw8Ii7g
# sha256: IogqDkGMKE4fcYqCKzsCKUTVPS2QjhaQsxmp0-ssBXk
unittest2==1.1.0
# sha256: aUkbUwUVfDxuDwSnAZhNaud_1yn8HJrNJQd_HfOFMms
# sha256: 619wCpv8lkILBVY1r5AC02YuQ9gMP_0x8iTCW8DV9GI
Werkzeug==0.11.3

View file

@ -1,6 +1,6 @@
# This is the flattened list of packages letsencrypt-auto installs. To generate
# this, do `pip install -e acme -e . -e letsencrypt-apache`, `pip freeze`,
# and then gather the hashes.
# this, do `pip install --no-cache-dir -e acme -e . -e letsencrypt-apache`, and
# then use `hashin` or a more secure method to gather the hashes.
# sha256: wxZH7baf09RlqEfqMVfTe-0flfGXYLEaR6qRwEtmYxQ
# sha256: YrCJpVvh2JSc0rx-DfC9254Cj678jDIDjMhIYq791uQ
@ -69,6 +69,10 @@ idna==2.0
# sha256: WjGCsyKnBlJcRigspvBk0noCz_vUSfn0dBbx3JaqcbA
ipaddress==1.0.16
# sha256: 54vpwKDfy6xxL-BPv5K5bN2ugLG4QvJCSCFMhJbwBu8
# sha256: Syb_TnEQ23butvWntkqCYjg51ZXCA47tpmLyott46Xw
linecache2==1.0.0
# sha256: 6MFV_evZxLywgQtO0BrhmHVUse4DTddTLXuP2uOKYnQ
ndg-httpsclient==0.4.0
@ -160,6 +164,14 @@ requests==2.9.1
# sha256: EF-NaGFvgkjiS_DpNy7wTTzBAQTxmA9U1Xss5zpa1Wo
six==1.10.0
# sha256: glPOvsSxkJTWfMXtWvmb8duhKFKSIm6Yoxkp-HpdayM
# sha256: BazGegmYDC7P7dNCP3rgEEg57MtV_GRXc-HKoJUcMDA
traceback2==1.4.0
# sha256: E_d9CHXbbZtDXh1PQedK1MwutuHVyCSZYJKzQw8Ii7g
# sha256: IogqDkGMKE4fcYqCKzsCKUTVPS2QjhaQsxmp0-ssBXk
unittest2==1.1.0
# sha256: aUkbUwUVfDxuDwSnAZhNaud_1yn8HJrNJQd_HfOFMms
# sha256: 619wCpv8lkILBVY1r5AC02YuQ9gMP_0x8iTCW8DV9GI
Werkzeug==0.11.3

View file

@ -380,13 +380,21 @@ def _report_new_cert(cert_path, fullchain_path):
.format(and_chain, path, expiry))
reporter_util.add_message(msg, reporter_util.MEDIUM_PRIORITY)
def _suggest_donate():
"Suggest a donation to support Let's Encrypt"
def _suggest_donation_if_appropriate(config):
"""Potentially suggest a donation to support Let's Encrypt."""
if not config.staging: # --dry-run implies --staging
reporter_util = zope.component.getUtility(interfaces.IReporter)
msg = ("If you like Let's Encrypt, please consider supporting our work by:\n\n"
"Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n"
"Donating to EFF: https://eff.org/donate-le\n\n")
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
def _report_successful_dry_run():
reporter_util = zope.component.getUtility(interfaces.IReporter)
msg = ("If you like Let's Encrypt, please consider supporting our work by:\n\n"
"Donating to ISRG / Let's Encrypt: https://letsencrypt.org/donate\n"
"Donating to EFF: https://eff.org/donate-le\n\n")
reporter_util.add_message(msg, reporter_util.LOW_PRIORITY)
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):
@ -408,6 +416,11 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
# interested in
action = "renew" if lineage.should_autorenew() else "reinstall"
if config.dry_run and action == "reinstall":
logger.info(
"Cert not due for renewal, but simulating renewal for dry run")
action = "renew"
if action == "reinstall":
# The lineage already exists; allow the caller to try installing
# it without getting a new certificate at all.
@ -419,25 +432,31 @@ def _auth_from_domains(le_client, config, domains, lineage=None):
# https://github.com/letsencrypt/letsencrypt/pull/777/files#r40498574
new_certr, new_chain, new_key, _ = le_client.obtain_certificate(domains)
# TODO: Check whether it worked! <- or make sure errors are thrown (jdk)
lineage.save_successor(
lineage.latest_common_version(), OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
if config.dry_run:
logger.info("Dry run: skipping updating lineage at %s",
os.path.dirname(lineage.cert))
else:
lineage.save_successor(
lineage.latest_common_version(), OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, new_certr.body.wrapped),
new_key.pem, crypto_util.dump_pyopenssl_chain(new_chain))
lineage.update_all_links_to(lineage.latest_common_version())
lineage.update_all_links_to(lineage.latest_common_version())
# TODO: Check return value of save_successor
# TODO: Also update lineage renewal config with any relevant
# configuration values from this attempt? <- Absolutely (jdkasten)
elif action == "newcert":
# TREAT AS NEW REQUEST
lineage = le_client.obtain_and_enroll_certificate(domains)
if not lineage:
if lineage is False:
raise errors.Error("Certificate could not be obtained")
_report_new_cert(lineage.cert, lineage.fullchain)
if not config.dry_run:
_report_new_cert(lineage.cert, lineage.fullchain)
return lineage, action
def _avoid_invalidating_lineage(config, lineage, original_server):
"Do not renew a valid cert with one from a staging server!"
def _is_staging(srv):
@ -623,7 +642,7 @@ def run(args, config, plugins): # pylint: disable=too-many-branches,too-many-lo
else:
display_ops.success_renewal(domains, action)
_suggest_donate()
_suggest_donation_if_appropriate(config)
def obtain_cert(args, config, plugins, lineage=None):
@ -648,14 +667,20 @@ def obtain_cert(args, config, plugins, lineage=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=args.csr[0], data=args.csr[1], form="der"))
cert_path, _, cert_fullchain = le_client.save_certificate(
certr, chain, args.cert_path, args.chain_path, args.fullchain_path)
_report_new_cert(cert_path, cert_fullchain)
if args.dry_run:
logger.info(
"Dry run: skipping saving certificate to %s", args.cert_path)
else:
cert_path, _, cert_fullchain = le_client.save_certificate(
certr, chain, args.cert_path, args.chain_path, args.fullchain_path)
_report_new_cert(cert_path, cert_fullchain)
else:
domains = _find_domains(config, installer)
_auth_from_domains(le_client, config, domains, lineage)
_suggest_donate()
if args.dry_run:
_report_successful_dry_run()
_suggest_donation_if_appropriate(config)
def install(args, config, plugins):
@ -939,14 +964,23 @@ class HelpfulArgumentParser(object):
if domain not in parsed_args.domains:
parsed_args.domains.append(domain)
# argparse seemingly isn't flexible enough to give us this behaviour easily...
if parsed_args.staging:
if parsed_args.server not in (flag_default("server"), constants.STAGING_URI):
raise errors.Error("--server value conflicts with --staging")
if parsed_args.staging or parsed_args.dry_run:
if (parsed_args.server not in
(flag_default("server"), constants.STAGING_URI)):
conflicts = ["--staging"] if parsed_args.staging else []
conflicts += ["--dry-run"] if parsed_args.dry_run else []
raise errors.Error("--server value conflicts with {0}".format(
" and ".join(conflicts)))
parsed_args.server = constants.STAGING_URI
return parsed_args
if parsed_args.dry_run:
if self.verb != "certonly":
raise errors.Error("--dry-run currently only works with the "
"'certonly' subcommand")
parsed_args.break_my_certs = parsed_args.staging = True
return parsed_args
def determine_verb(self):
"""Determines the verb/subcommand provided by the user.
@ -1318,6 +1352,10 @@ def _paths_parser(helpful):
add("testing", "--test-cert", "--staging", action='store_true', dest='staging',
help='Use the staging server to obtain test (invalid) certs; equivalent'
' to --server ' + constants.STAGING_URI)
add("testing", "--dry-run", action="store_true", dest="dry_run",
help="Perform a test run of the client, obtaining test (invalid) certs"
" but not saving them to disk. This can currently only be used"
" with the 'certonly' subcommand.")
def _plugins_parsing(helpful, plugins):
@ -1366,7 +1404,9 @@ def _plugins_parsing(helpful, plugins):
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")
"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
def __init__(self, *args, **kwargs):

View file

@ -276,8 +276,8 @@ class Client(object):
:param plugins: A PluginsFactory object.
:returns: A new :class:`letsencrypt.storage.RenewableCert` instance
referred to the enrolled cert lineage, or False if the cert could
not be obtained.
referred to the enrolled cert lineage, False if the cert could not
be obtained, or None if doing a successful dry run.
"""
certr, chain, key, _ = self.obtain_certificate(domains)
@ -298,12 +298,16 @@ class Client(object):
"Non-standard path(s), might not work with crontab installed "
"by your operating system package manager")
lineage = storage.RenewableCert.new_lineage(
domains[0], OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped),
key.pem, crypto_util.dump_pyopenssl_chain(chain),
params, config, cli_config)
return lineage
if cli_config.dry_run:
logger.info("Dry run: Skipping creating new lineage for %s",
domains[0])
return None
else:
return storage.RenewableCert.new_lineage(
domains[0], OpenSSL.crypto.dump_certificate(
OpenSSL.crypto.FILETYPE_PEM, certr.body.wrapped),
key.pem, crypto_util.dump_pyopenssl_chain(chain),
params, config, cli_config)
def save_certificate(self, certr, chain_cert,
cert_path, chain_path, fullchain_path):

View file

@ -1,5 +1,6 @@
"""Tests for letsencrypt.cli."""
import argparse
import functools
import itertools
import os
import shutil
@ -49,18 +50,16 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
def _call(self, args):
"Run the cli with output streams and actual client mocked out"
with mock.patch('letsencrypt.cli._suggest_donate'):
with mock.patch('letsencrypt.cli.client') as client:
ret, stdout, stderr = self._call_no_clientmock(args)
return ret, stdout, stderr, client
with mock.patch('letsencrypt.cli.client') as client:
ret, stdout, stderr = self._call_no_clientmock(args)
return ret, stdout, stderr, client
def _call_no_clientmock(self, args):
"Run the client with output streams mocked out"
args = self.standard_args + args
with mock.patch('letsencrypt.cli._suggest_donate'):
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
with mock.patch('letsencrypt.cli.sys.stdout') as stdout:
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
return ret, stdout, stderr
def _call_stdout(self, args):
@ -69,10 +68,9 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
caller.
"""
args = self.standard_args + args
with mock.patch('letsencrypt.cli._suggest_donate'):
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client:
ret = cli.main(args[:]) # NOTE: parser can alter its args!
return ret, None, stderr, client
def test_no_flags(self):
@ -349,50 +347,91 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._call,
['-d', '*.wildcard.tld'])
def test_parse_domains(self):
def _get_argument_parser(self):
plugins = disco.PluginsRegistry.find_all()
return functools.partial(cli.prepare_and_parse_args, plugins)
def test_parse_domains(self):
parse = self._get_argument_parser()
short_args = ['-d', 'example.com']
namespace = cli.prepare_and_parse_args(plugins, short_args)
namespace = parse(short_args)
self.assertEqual(namespace.domains, ['example.com'])
short_args = ['-d', 'trailing.period.com.']
namespace = cli.prepare_and_parse_args(plugins, short_args)
namespace = parse(short_args)
self.assertEqual(namespace.domains, ['trailing.period.com'])
short_args = ['-d', 'example.com,another.net,third.org,example.com']
namespace = cli.prepare_and_parse_args(plugins, short_args)
namespace = parse(short_args)
self.assertEqual(namespace.domains, ['example.com', 'another.net',
'third.org'])
long_args = ['--domains', 'example.com']
namespace = cli.prepare_and_parse_args(plugins, long_args)
namespace = parse(long_args)
self.assertEqual(namespace.domains, ['example.com'])
long_args = ['--domains', 'trailing.period.com.']
namespace = cli.prepare_and_parse_args(plugins, long_args)
namespace = parse(long_args)
self.assertEqual(namespace.domains, ['trailing.period.com'])
long_args = ['--domains', 'example.com,another.net,example.com']
namespace = cli.prepare_and_parse_args(plugins, long_args)
namespace = parse(long_args)
self.assertEqual(namespace.domains, ['example.com', 'another.net'])
def test_parse_server(self):
plugins = disco.PluginsRegistry.find_all()
short_args = ['--server', 'example.com']
namespace = cli.prepare_and_parse_args(plugins, short_args)
def test_server_flag(self):
parse = self._get_argument_parser()
namespace = parse('--server example.com'.split())
self.assertEqual(namespace.server, 'example.com')
def _check_server_conflict_message(self, parser_args, conflicting_args):
parse = self._get_argument_parser()
try:
parse(parser_args)
self.fail( # pragma: no cover
"The following flags didn't conflict with "
'--server: {0}'.format(', '.join(conflicting_args)))
except errors.Error as error:
self.assertTrue('--server' in error.message)
for arg in conflicting_args:
self.assertTrue(arg in error.message)
def test_staging_flag(self):
parse = self._get_argument_parser()
short_args = ['--staging']
namespace = cli.prepare_and_parse_args(plugins, short_args)
namespace = parse(short_args)
self.assertTrue(namespace.staging)
self.assertEqual(namespace.server, constants.STAGING_URI)
short_args = ['--staging', '--server', 'example.com']
self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, short_args)
short_args += '--server example.com'.split()
self._check_server_conflict_message(short_args, '--staging')
def _assert_dry_run_flag_worked(self, namespace):
self.assertTrue(namespace.dry_run)
self.assertTrue(namespace.break_my_certs)
self.assertTrue(namespace.staging)
self.assertEqual(namespace.server, constants.STAGING_URI)
def test_dry_run_flag(self):
parse = self._get_argument_parser()
short_args = ['--dry-run']
self.assertRaises(errors.Error, parse, short_args)
self._assert_dry_run_flag_worked(parse(short_args + ['auth']))
short_args += ['certonly']
self._assert_dry_run_flag_worked(parse(short_args))
short_args += '--server example.com'.split()
conflicts = ['--dry-run']
self._check_server_conflict_message(short_args, '--dry-run')
short_args += ['--staging']
conflicts += ['--staging']
self._check_server_conflict_message(short_args, conflicts)
def _webroot_map_test(self, map_arg, path_arg, domains_arg, # pylint: disable=too-many-arguments
expected_map, expectect_domains, extra_args=None):
plugins = disco.PluginsRegistry.find_all()
parse = self._get_argument_parser()
webroot_map_args = extra_args if extra_args else []
if map_arg:
webroot_map_args.extend(["--webroot-map", map_arg])
@ -400,17 +439,17 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
webroot_map_args.extend(["-w", path_arg])
if domains_arg:
webroot_map_args.extend(["-d", domains_arg])
namespace = cli.prepare_and_parse_args(plugins, webroot_map_args)
namespace = parse(webroot_map_args)
domains = cli._find_domains(namespace, mock.MagicMock()) # pylint: disable=protected-access
self.assertEqual(namespace.webroot_map, expected_map)
self.assertEqual(set(domains), set(expectect_domains))
def test_parse_webroot(self):
plugins = disco.PluginsRegistry.find_all()
parse = self._get_argument_parser()
webroot_args = ['--webroot', '-w', '/var/www/example',
'-d', 'example.com,www.example.com', '-w', '/var/www/superfluous',
'-d', 'superfluo.us', '-d', 'www.superfluo.us.']
namespace = cli.prepare_and_parse_args(plugins, webroot_args)
'-d', 'superfluo.us', '-d', 'www.superfluo.us']
namespace = parse(webroot_args)
self.assertEqual(namespace.webroot_map, {
'example.com': '/var/www/example',
'www.example.com': '/var/www/example',
@ -418,7 +457,7 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
'superfluo.us': '/var/www/superfluous'})
webroot_args = ['-d', 'stray.example.com'] + webroot_args
self.assertRaises(errors.Error, cli.prepare_and_parse_args, plugins, webroot_args)
self.assertRaises(errors.Error, parse, webroot_args)
simple_map = '{"eg.com" : "/tmp"}'
expected_map = {"eg.com": "/tmp"}
@ -440,14 +479,35 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
webroot_map_args = ['--webroot-map',
'{"eg.com.,www.eg.com": "/tmp", "eg.is.": "/tmp2"}']
namespace = cli.prepare_and_parse_args(plugins, webroot_map_args)
namespace = parse(webroot_map_args)
self.assertEqual(namespace.webroot_map,
{"eg.com": "/tmp", "www.eg.com": "/tmp", "eg.is": "/tmp2"})
@mock.patch('letsencrypt.cli._suggest_donate')
def _certonly_new_request_common(self, mock_client, args=None):
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
mock_renewal.return_value = ("newcert", None)
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
mock_init.return_value = mock_client
if args is None:
args = []
args += '-d foo.bar -a standalone certonly'.split()
self._call(args)
@mock.patch('letsencrypt.cli.zope.component.getUtility')
def test_certonly_dry_run_new_request_success(self, mock_get_utility):
mock_client = mock.MagicMock()
mock_client.obtain_and_enroll_certificate.return_value = None
self._certonly_new_request_common(mock_client, ['--dry-run'])
self.assertEqual(
mock_client.obtain_and_enroll_certificate.call_count, 1)
self.assertTrue(
'dry run' in mock_get_utility().add_message.call_args[0][0])
# Asserts we don't suggest donating after a successful dry run
self.assertEqual(mock_get_utility().add_message.call_count, 1)
@mock.patch('letsencrypt.crypto_util.notAfter')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter, _suggest):
def test_certonly_new_request_success(self, mock_get_utility, mock_notAfter):
cert_path = '/etc/letsencrypt/live/foo.bar'
date = '1970-01-01'
mock_notAfter().date.return_value = date
@ -458,10 +518,11 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self._certonly_new_request_common(mock_client)
self.assertEqual(
mock_client.obtain_and_enroll_certificate.call_count, 1)
cert_msg = mock_get_utility().add_message.call_args_list[0][0][0]
self.assertTrue(cert_path in cert_msg)
self.assertTrue(date in cert_msg)
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
self.assertTrue(
date in mock_get_utility().add_message.call_args[0][0])
'donate' in mock_get_utility().add_message.call_args[0][0])
def test_certonly_new_request_failure(self):
mock_client = mock.MagicMock()
@ -469,69 +530,101 @@ class CLITest(unittest.TestCase): # pylint: disable=too-many-public-methods
self.assertRaises(errors.Error,
self._certonly_new_request_common, mock_client)
def _certonly_new_request_common(self, mock_client):
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
mock_renewal.return_value = ("newcert", None)
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
mock_init.return_value = mock_client
self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly'])
@mock.patch('letsencrypt.cli._suggest_donate')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')
@mock.patch('letsencrypt.cli._init_le_client')
def test_certonly_renewal(self, mock_init, mock_renewal, mock_get_utility, _suggest):
def _test_certonly_renewal_common(self, renewal_verb, extra_args=None):
cert_path = 'letsencrypt/tests/testdata/cert.pem'
chain_path = '/etc/letsencrypt/live/foo.bar/fullchain.pem'
mock_lineage = mock.MagicMock(cert=cert_path, fullchain=chain_path)
mock_certr = mock.MagicMock()
mock_key = mock.MagicMock(pem='pem_key')
mock_renewal.return_value = ("renew", mock_lineage)
mock_client = mock.MagicMock()
mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
mock_key, 'csr')
mock_init.return_value = mock_client
with mock.patch('letsencrypt.cli.OpenSSL'):
with mock.patch('letsencrypt.cli.crypto_util'):
self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly'])
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
mock_renewal.return_value = (renewal_verb, mock_lineage)
mock_client = mock.MagicMock()
mock_client.obtain_certificate.return_value = (mock_certr, 'chain',
mock_key, 'csr')
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
mock_init.return_value = mock_client
get_utility_path = 'letsencrypt.cli.zope.component.getUtility'
with mock.patch(get_utility_path) as mock_get_utility:
with mock.patch('letsencrypt.cli.OpenSSL'):
with mock.patch('letsencrypt.cli.crypto_util'):
args = ['-d', 'foo.bar', '-a',
'standalone', 'certonly']
if extra_args:
args += extra_args
self._call(args)
mock_client.obtain_certificate.assert_called_once_with(['foo.bar'])
self.assertEqual(mock_lineage.save_successor.call_count, 1)
mock_lineage.update_all_links_to.assert_called_once_with(
mock_lineage.latest_common_version())
self.assertTrue(
chain_path in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli._suggest_donate')
@mock.patch('letsencrypt.crypto_util.notAfter')
@mock.patch('letsencrypt.cli.display_ops.pick_installer')
return mock_lineage, mock_get_utility
def test_certonly_renewal(self):
lineage, get_utility = self._test_certonly_renewal_common('renew')
self.assertEqual(lineage.save_successor.call_count, 1)
lineage.update_all_links_to.assert_called_once_with(
lineage.latest_common_version())
cert_msg = get_utility().add_message.call_args_list[0][0][0]
self.assertTrue('fullchain.pem' in cert_msg)
self.assertTrue('donate' in get_utility().add_message.call_args[0][0])
def test_certonly_dry_run_reinstall_is_renewal(self):
_, get_utility = self._test_certonly_renewal_common('reinstall',
['--dry-run'])
self.assertEqual(get_utility().add_message.call_count, 1)
self.assertTrue('dry run' in get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')
@mock.patch('letsencrypt.cli._init_le_client')
@mock.patch('letsencrypt.cli.record_chosen_plugins')
def test_certonly_csr(self, _rec, mock_init, mock_get_utility,
mock_pick_installer, mock_notAfter, _suggest):
cert_path = '/etc/letsencrypt/live/blahcert.pem'
date = '1970-01-01'
mock_notAfter().date.return_value = date
def test_certonly_reinstall(self, mock_init, mock_renewal, mock_get_utility):
mock_renewal.return_value = ('reinstall', mock.MagicMock())
mock_init.return_value = mock_client = mock.MagicMock()
self._call(['-d', 'foo.bar', '-a', 'standalone', 'certonly'])
self.assertFalse(mock_client.obtain_certificate.called)
self.assertFalse(mock_client.obtain_and_enroll_certificate.called)
self.assertTrue(
'donate' in mock_get_utility().add_message.call_args[0][0])
def _test_certonly_csr_common(self, extra_args=None):
certr = 'certr'
chain = 'chain'
mock_client = mock.MagicMock()
mock_client.obtain_certificate_from_csr.return_value = ('certr',
'chain')
mock_client.obtain_certificate_from_csr.return_value = (certr, chain)
cert_path = '/etc/letsencrypt/live/example.com/cert.pem'
mock_client.save_certificate.return_value = cert_path, None, None
mock_init.return_value = mock_client
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
mock_init.return_value = mock_client
get_utility_path = 'letsencrypt.cli.zope.component.getUtility'
with mock.patch(get_utility_path) as mock_get_utility:
chain_path = '/etc/letsencrypt/live/example.com/chain.pem'
full_path = '/etc/letsencrypt/live/example.com/fullchain.pem'
args = ('-a standalone certonly --csr {0} --cert-path {1} '
'--chain-path {2} --fullchain-path {3}').format(
CSR, cert_path, chain_path, full_path).split()
if extra_args:
args += extra_args
with mock.patch('letsencrypt.cli.crypto_util'):
self._call(args)
installer = 'installer'
self._call(
['-a', 'standalone', '-i', installer, 'certonly', '--csr', CSR,
'--cert-path', cert_path, '--fullchain-path', '/',
'--chain-path', '/'])
self.assertEqual(mock_pick_installer.call_args[0][1], installer)
mock_client.save_certificate.assert_called_once_with(
'certr', 'chain', cert_path, '/', '/')
if '--dry-run' in args:
self.assertFalse(mock_client.save_certificate.called)
else:
mock_client.save_certificate.assert_called_once_with(
certr, chain, cert_path, chain_path, full_path)
return mock_get_utility
def test_certonly_csr(self):
mock_get_utility = self._test_certonly_csr_common()
cert_msg = mock_get_utility().add_message.call_args_list[0][0][0]
self.assertTrue('cert.pem' in cert_msg)
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
'donate' in mock_get_utility().add_message.call_args[0][0])
def test_certonly_csr_dry_run(self):
mock_get_utility = self._test_certonly_csr_common(['--dry-run'])
self.assertEqual(mock_get_utility().add_message.call_count, 1)
self.assertTrue(
date in mock_get_utility().add_message.call_args[0][0])
'dry run' in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.client.acme_client')
def test_revoke_with_key(self, mock_acme_client):

View file

@ -133,8 +133,12 @@ virtualenv --no-site-packages ../venv
. ../venv/bin/activate
pip install -U setuptools
pip install -U pip
# Now, use our local PyPI
# Now, use our local PyPI. Disable cache so we get the correct KGS even if we
# (or our dependencies) have conditional dependencies implemented with if
# statements in setup.py and we have cached wheels lying around that would
# cause those ifs to not be evaluated.
pip install \
--no-cache-dir \
--extra-index-url http://localhost:$PORT \
letsencrypt $SUBPKGS
# stop local PyPI