From 0e1f6b24f3a543140dc97928a50cdf69ffab61a7 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Wed, 27 May 2015 19:29:06 -0400 Subject: [PATCH 01/11] Added basic notifier --- letsencrypt/cli.py | 7 +++++ letsencrypt/interfaces.py | 27 +++++++++++++++++++ letsencrypt/notifier.py | 57 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 91 insertions(+) create mode 100644 letsencrypt/notifier.py diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index defa7633d..8cb8ec84f 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1,5 +1,6 @@ """Let's Encrypt CLI.""" # TODO: Sanity check all input. Be sure to avoid shell code etc... +import atexit import argparse import logging import os @@ -20,6 +21,7 @@ from letsencrypt import errors from letsencrypt import interfaces from letsencrypt import le_util from letsencrypt import log +from letsencrypt import notifier from letsencrypt.display import util as display_util from letsencrypt.display import ops as display_ops @@ -347,6 +349,11 @@ def main(args=sys.argv[1:]): displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) + # Notifier + notify = notifier.Notifier() + zope.component.provideUtility(notify) + atexit.register(notify.print_messages) + # Logging level = -args.verbose_count * 10 logger = logging.getLogger() diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 609b9410a..b39737070 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -346,3 +346,30 @@ class IValidator(zope.interface.Interface): def hsts(name): """Verify HSTS header is enabled.""" + + +class INotify(zope.interface.Interface): + """Interface to collect and display information to the user.""" + + HIGH_PRIORITY = zope.interface.Attribute( + "Used to denote high priority messages") + MEDIUM_PRIORITY = zope.interface.Attribute( + "Used to denote medium priority messages") + LOW_PRIORITY = zope.interface.Attribute( + "Used to denote low priority messages") + + def add_message(self, msg, priority, on_crash=False): + """Adds msg to the list of messages to be printed. + + :param str msg: Message to be displayed to the user. + + :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or + LOW_PRIORITY. + + :param bool on_crash: Whether or not the message should be printed if + the program exits abnormally. + + """ + + def print_messages(self): + """Prints messages to the user and clears the message queue.""" diff --git a/letsencrypt/notifier.py b/letsencrypt/notifier.py new file mode 100644 index 000000000..fa795673c --- /dev/null +++ b/letsencrypt/notifier.py @@ -0,0 +1,57 @@ +"""Collects and displays information to the user.""" +import collections +import Queue +import sys +import textwrap + +import zope.interface + +from letsencrypt import interfaces + + +class Notifier(object): + """Collects and displays information to the user. + + :ivar `Queue.PriorityQueue` messages: Messages to be displayed to the user. + + """ + + zope.interface.implements(interfaces.INotify) + + HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY = xrange(3) + _msg_type = collections.namedtuple('Msg', 'priority, text, on_crash') + + def __init__(self): + self.messages = Queue.PriorityQueue() + + def add_message(self, msg, priority, on_crash=False): + """Adds msg to the list of messages to be printed. + + :param str msg: Message to be displayed to the user. + + :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or + LOW_PRIORITY. + + :param bool on_crash: Whether or not the message should be printed if + the program exits abnormally. + + """ + assert priority >= self.HIGH_PRIORITY and priority <= self.LOW_PRIORITY + self.messages.put(self._msg_type(priority, msg, on_crash)) + + def print_messages(self): + """Prints messages to the user and clears the message queue. + + If there is an unhandled exception, only messages for which on_crash is + True are printed. + + """ + if not self.messages.empty(): + no_exception = sys.exc_info()[0] is None + print 'IMPORTANT NOTES:' + wrapper = textwrap.TextWrapper(initial_indent=' - ', + subsequent_indent=' '*3) + while not self.messages.empty(): + msg = self.messages.get() + if no_exception or msg.on_crash: + print wrapper.fill(msg.text) From e520efe41e412688393c5130dbb7d2f935a5a655 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Fri, 29 May 2015 11:24:11 +0000 Subject: [PATCH 02/11] Add continer badge in README --- README.rst | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.rst b/README.rst index 28034406e..db32889db 100644 --- a/README.rst +++ b/README.rst @@ -1,7 +1,7 @@ About the Let's Encrypt Client ============================== -|build-status| |coverage| |docs| +|build-status| |coverage| |docs| |container| In short: getting and installing SSL/TLS certificates made easy (`watch demo video`_). @@ -43,6 +43,10 @@ server automatically!:: :target: https://readthedocs.org/projects/letsencrypt/ :alt: Documentation status +.. |container| image:: https://quay.io/repository/letsencrypt/lets-encrypt-preview/status + :target: https://quay.io/repository/letsencrypt/lets-encrypt-preview + :alt: Docker Repository on Quay.io + .. _watch demo video: https://www.youtube.com/watch?v=Gas_sSB-5SU From 8bcc8f802431a89a8752f09d28ea114b88e3cbec Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 29 May 2015 11:33:11 -0700 Subject: [PATCH 03/11] Finished basic reporter --- letsencrypt/cli.py | 8 +-- letsencrypt/interfaces.py | 2 +- letsencrypt/{notifier.py => reporter.py} | 15 ++++- letsencrypt/tests/reporter_test.py | 73 ++++++++++++++++++++++++ 4 files changed, 91 insertions(+), 7 deletions(-) rename letsencrypt/{notifier.py => reporter.py} (78%) create mode 100644 letsencrypt/tests/reporter_test.py diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 0b8ef5761..85c2d2536 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -21,7 +21,7 @@ from letsencrypt import errors from letsencrypt import interfaces from letsencrypt import le_util from letsencrypt import log -from letsencrypt import notifier +from letsencrypt import reporter from letsencrypt.display import util as display_util from letsencrypt.display import ops as display_ops @@ -363,9 +363,9 @@ def main(args=sys.argv[1:]): zope.component.provideUtility(displayer) # Notifier - notify = notifier.Notifier() - zope.component.provideUtility(notify) - atexit.register(notify.print_messages) + report = reporter.Reporter() + zope.component.provideUtility(report) + atexit.register(report.print_messages) # Logging level = -args.verbose_count * 10 diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 60cf358e5..1eea6b2d3 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -353,7 +353,7 @@ class IValidator(zope.interface.Interface): """Verify HSTS header is enabled.""" -class INotify(zope.interface.Interface): +class IReporter(zope.interface.Interface): """Interface to collect and display information to the user.""" HIGH_PRIORITY = zope.interface.Attribute( diff --git a/letsencrypt/notifier.py b/letsencrypt/reporter.py similarity index 78% rename from letsencrypt/notifier.py rename to letsencrypt/reporter.py index fa795673c..26269e3f8 100644 --- a/letsencrypt/notifier.py +++ b/letsencrypt/reporter.py @@ -9,16 +9,18 @@ import zope.interface from letsencrypt import interfaces -class Notifier(object): +class Reporter(object): """Collects and displays information to the user. :ivar `Queue.PriorityQueue` messages: Messages to be displayed to the user. """ - zope.interface.implements(interfaces.INotify) + zope.interface.implements(interfaces.IReporter) HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY = xrange(3) + _RESET = '\033[0m' + _BOLD = '\033[1m' _msg_type = collections.namedtuple('Msg', 'priority, text, on_crash') def __init__(self): @@ -46,12 +48,21 @@ class Notifier(object): True are printed. """ + bold_on = False if not self.messages.empty(): no_exception = sys.exc_info()[0] is None + bold_on = sys.stdout.isatty() + if bold_on: + sys.stdout.write(self._BOLD) print 'IMPORTANT NOTES:' wrapper = textwrap.TextWrapper(initial_indent=' - ', subsequent_indent=' '*3) while not self.messages.empty(): msg = self.messages.get() if no_exception or msg.on_crash: + if bold_on and msg.priority > self.HIGH_PRIORITY: + sys.stdout.write(self._RESET) + bold_on = False print wrapper.fill(msg.text) + if bold_on: + sys.stdout.write(self._RESET) diff --git a/letsencrypt/tests/reporter_test.py b/letsencrypt/tests/reporter_test.py new file mode 100644 index 000000000..df9790256 --- /dev/null +++ b/letsencrypt/tests/reporter_test.py @@ -0,0 +1,73 @@ +"""Tests for letsencrypt/reporter.py""" +import StringIO +import sys +import unittest + + +class ReporterTest(unittest.TestCase): + def setUp(self): + from letsencrypt import reporter + self.reporter = reporter.Reporter() + + self.old_stdout = sys.stdout + sys.stdout = StringIO.StringIO() + + def tearDown(self): + sys.stdout = self.old_stdout + + def test_tty_print_empty(self): + sys.stdout.isatty = lambda: True + self.test_no_tty_print_empty() + + def test_no_tty_print_empty(self): + self.reporter.print_messages() + self.assertEqual(sys.stdout.getvalue(), "") + try: + raise ValueError + except ValueError: + self.reporter.print_messages() + self.assertEqual(sys.stdout.getvalue(), "") + + def test_tty_successful_exit(self): + sys.stdout.isatty = lambda: True + self._successful_exit_common() + + def test_no_tty_successful_exit(self): + self._successful_exit_common() + + def test_tty_unsuccessful_exit(self): + sys.stdout.isatty = lambda: True + self._unsuccessful_exit_common() + + def test_no_tty_unsuccessful_exit(self): + self._unsuccessful_exit_common() + + def _successful_exit_common(self): + self._add_messages() + self.reporter.print_messages() + output = sys.stdout.getvalue() + self.assertTrue("IMPORTANT NOTES:" in output) + self.assertTrue("High" in output) + self.assertTrue("Med" in output) + self.assertTrue("Low" in output) + + def _unsuccessful_exit_common(self): + self._add_messages() + try: + raise ValueError + except ValueError: + self.reporter.print_messages() + output = sys.stdout.getvalue() + self.assertTrue("IMPORTANT NOTES:" in output) + self.assertTrue("High" in output) + self.assertTrue("Med" not in output) + self.assertTrue("Low" not in output) + + def _add_messages(self): + self.reporter.add_message("High", self.reporter.HIGH_PRIORITY, True) + self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY) + self.reporter.add_message("Low", self.reporter.LOW_PRIORITY) + + +if __name__ == "__main__": + unittest.main() # pragma: no cover From 4f7d83d274c7b80aa6650cbede54fe4fea9a49fb Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 29 May 2015 11:45:06 -0700 Subject: [PATCH 04/11] Corrected comment in cli.py --- letsencrypt/cli.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 8600ecc4f..59e99648d 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -361,7 +361,7 @@ def main(args=sys.argv[1:]): displayer = display_util.NcursesDisplay() zope.component.provideUtility(displayer) - # Notifier + # Reporter report = reporter.Reporter() zope.component.provideUtility(report) atexit.register(report.print_messages) From 20271105128ad59b73ada9878babfb9b0d99bb01 Mon Sep 17 00:00:00 2001 From: Seth Schoen Date: Mon, 1 Jun 2015 11:34:12 -0700 Subject: [PATCH 05/11] Add a single performance-related TODO comment --- letsencrypt/renewer.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index d95fb7d95..6e61fd893 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -117,6 +117,14 @@ def main(config=None): rc_config = configobj.ConfigObj( os.path.join(config["renewal_configs_dir"], i)) try: + # TODO: Before trying to initialize the RenewableCert object, + # we could check here whether the combination of the config + # and the rc_config together disables all autorenewal and + # autodeployment applicable to this cert. In that case, we + # can simply continue and don't need to instantiate a + # RenewableCert object for this cert at all, which could + # dramatically improve performance for large deployments + # where autorenewal is widely turned off. cert = storage.RenewableCert(rc_config) except ValueError: # This indicates an invalid renewal configuration file, such From e5dd4ba70ccc0ad59feaec50ae59a8f95ffc705c Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 19:06:15 +0000 Subject: [PATCH 06/11] Minor fixes for #453 and reporter API docs. --- docs/api/reporter.rst | 5 +++++ letsencrypt/cli.py | 2 +- letsencrypt/reporter.py | 22 +++++++++++----------- letsencrypt/tests/reporter_test.py | 6 ++++-- 4 files changed, 21 insertions(+), 14 deletions(-) create mode 100644 docs/api/reporter.rst diff --git a/docs/api/reporter.rst b/docs/api/reporter.rst new file mode 100644 index 000000000..03260f9cd --- /dev/null +++ b/docs/api/reporter.rst @@ -0,0 +1,5 @@ +:mod:`letsencrypt.reporter` +--------------------------- + +.. automodule:: letsencrypt.reporter + :members: diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 59e99648d..70a0d82ee 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -1,7 +1,7 @@ """Let's Encrypt CLI.""" # TODO: Sanity check all input. Be sure to avoid shell code etc... -import atexit import argparse +import atexit import logging import os import sys diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 26269e3f8..99586d7cf 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -12,16 +12,16 @@ from letsencrypt import interfaces class Reporter(object): """Collects and displays information to the user. - :ivar `Queue.PriorityQueue` messages: Messages to be displayed to the user. + :ivar `Queue.PriorityQueue` messages: Messages to be displayed to + the user. """ - zope.interface.implements(interfaces.IReporter) HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY = xrange(3) _RESET = '\033[0m' _BOLD = '\033[1m' - _msg_type = collections.namedtuple('Msg', 'priority, text, on_crash') + _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') def __init__(self): self.messages = Queue.PriorityQueue() @@ -31,21 +31,21 @@ class Reporter(object): :param str msg: Message to be displayed to the user. - :param int priority: One of HIGH_PRIORITY, MEDIUM_PRIORITY, or - LOW_PRIORITY. + :param int priority: One of `HIGH_PRIORITY`, `MEDIUM_PRIORITY`, + or `LOW_PRIORITY`. - :param bool on_crash: Whether or not the message should be printed if - the program exits abnormally. + :param bool on_crash: Whether or not the message should be + printed if the program exits abnormally. """ - assert priority >= self.HIGH_PRIORITY and priority <= self.LOW_PRIORITY + assert self.HIGH_PRIORITY <= priority <= self.LOW_PRIORITY self.messages.put(self._msg_type(priority, msg, on_crash)) def print_messages(self): """Prints messages to the user and clears the message queue. - If there is an unhandled exception, only messages for which on_crash is - True are printed. + If there is an unhandled exception, only messages for which + ``on_crash`` is ``True`` are printed. """ bold_on = False @@ -56,7 +56,7 @@ class Reporter(object): sys.stdout.write(self._BOLD) print 'IMPORTANT NOTES:' wrapper = textwrap.TextWrapper(initial_indent=' - ', - subsequent_indent=' '*3) + subsequent_indent=(' ' * 3)) while not self.messages.empty(): msg = self.messages.get() if no_exception or msg.on_crash: diff --git a/letsencrypt/tests/reporter_test.py b/letsencrypt/tests/reporter_test.py index df9790256..e6a8c9005 100644 --- a/letsencrypt/tests/reporter_test.py +++ b/letsencrypt/tests/reporter_test.py @@ -1,10 +1,12 @@ -"""Tests for letsencrypt/reporter.py""" +"""Tests for letsencrypt.reporter.""" import StringIO import sys import unittest class ReporterTest(unittest.TestCase): + """Tests for letsencrypt.reporter.Reporter.""" + def setUp(self): from letsencrypt import reporter self.reporter = reporter.Reporter() @@ -70,4 +72,4 @@ class ReporterTest(unittest.TestCase): if __name__ == "__main__": - unittest.main() # pragma: no cover + unittest.main() # pragma: no cover From c4195c6cdf0fb437a3a87a3efe2d05a94876cd8a Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 19:07:00 +0000 Subject: [PATCH 07/11] Unify test docstrings. --- letsencrypt/tests/acme_util.py | 2 +- letsencrypt/tests/continuity_auth_test.py | 2 +- letsencrypt/tests/notify_test.py | 2 +- letsencrypt/tests/proof_of_possession_test.py | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/letsencrypt/tests/acme_util.py b/letsencrypt/tests/acme_util.py index 93cc35e47..48e3beba7 100644 --- a/letsencrypt/tests/acme_util.py +++ b/letsencrypt/tests/acme_util.py @@ -1,4 +1,4 @@ -"""Class helps construct valid ACME messages for testing.""" +"""ACME utilities for testing.""" import datetime import itertools import os diff --git a/letsencrypt/tests/continuity_auth_test.py b/letsencrypt/tests/continuity_auth_test.py index 1b14718fe..829af736d 100644 --- a/letsencrypt/tests/continuity_auth_test.py +++ b/letsencrypt/tests/continuity_auth_test.py @@ -1,4 +1,4 @@ -"""Test the ContinuityAuthenticator dispatcher.""" +"""Test for letsencrypt.continuity_auth.""" import unittest import mock diff --git a/letsencrypt/tests/notify_test.py b/letsencrypt/tests/notify_test.py index c6f76c42e..1ccfdbf87 100644 --- a/letsencrypt/tests/notify_test.py +++ b/letsencrypt/tests/notify_test.py @@ -1,4 +1,4 @@ -"""Tests for letsencrypt/notify.py""" +"""Tests for letsencrypt.notify.""" import mock import socket diff --git a/letsencrypt/tests/proof_of_possession_test.py b/letsencrypt/tests/proof_of_possession_test.py index b420c972c..3635b5ead 100644 --- a/letsencrypt/tests/proof_of_possession_test.py +++ b/letsencrypt/tests/proof_of_possession_test.py @@ -1,4 +1,4 @@ -"""Tests for proof_of_possession.py""" +"""Tests for letsencrypt.proof_of_possession.""" import Crypto.PublicKey.RSA import os import pkg_resources From 3ba41de2ba854311ecd03451abdf95eb13d87513 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 19:16:58 +0000 Subject: [PATCH 08/11] Split Reporter priority constants (improves docs readability). --- letsencrypt/reporter.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/letsencrypt/reporter.py b/letsencrypt/reporter.py index 99586d7cf..7dfacaf14 100644 --- a/letsencrypt/reporter.py +++ b/letsencrypt/reporter.py @@ -18,7 +18,13 @@ class Reporter(object): """ zope.interface.implements(interfaces.IReporter) - HIGH_PRIORITY, MEDIUM_PRIORITY, LOW_PRIORITY = xrange(3) + HIGH_PRIORITY = 0 + """High priority constant. See `add_message`.""" + MEDIUM_PRIORITY = 1 + """Medium priority constant. See `add_message`.""" + LOW_PRIORITY = 2 + """Low priority constant. See `add_message`.""" + _RESET = '\033[0m' _BOLD = '\033[1m' _msg_type = collections.namedtuple('ReporterMsg', 'priority text on_crash') From 2929039cf442536c9efec39b1cc93751e3ca73db Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 20:56:58 +0000 Subject: [PATCH 09/11] Split --test-mode into --no-verify-ssl and --dvsni-port (fixes #462). --- letsencrypt/cli.py | 15 +++++++-- letsencrypt/client.py | 2 +- letsencrypt/constants.py | 7 ++--- letsencrypt/interfaces.py | 7 +++-- .../plugins/standalone/authenticator.py | 18 +++++------ .../standalone/tests/authenticator_test.py | 31 ++++++++++--------- 6 files changed, 46 insertions(+), 34 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 59e99648d..66e98550e 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -249,8 +249,19 @@ def create_parser(plugins): help="Skip the end user license agreement screen.") add("-t", "--text", dest="text_mode", action="store_true", help="Use the text output instead of the curses UI.") - add("--test-mode", action="store_true", help=config_help("test_mode"), - default=flag_default("test_mode")) + + testing_group = parser.add_argument_group( + "testing", description="The following flags are meant for " + "testing purposes only! Do NOT change them, unless you " + "really know what you're doing!") + testing_group.add_argument( + "--no-verify-ssl", action="store_true", + help=config_help("no_verify_ssl"), + default=flag_default("no_verify_ssl")) + # TODO: apache and nginx plugins do NOT respect it + testing_group.add_argument( + "--dvsni-port", type=int, help=config_help("dvsni_port"), + default=flag_default("dvsni_port")) subparsers = parser.add_subparsers(metavar="SUBCOMMAND") def add_subparser(name, func): # pylint: disable=missing-docstring diff --git a/letsencrypt/client.py b/letsencrypt/client.py index 34347959d..c4b8ef40c 100644 --- a/letsencrypt/client.py +++ b/letsencrypt/client.py @@ -64,7 +64,7 @@ class Client(object): # TODO: Allow for other alg types besides RS256 self.network = network2.Network( config.server, jwk.JWKRSA.load(self.account.key.pem), - verify_ssl=(not config.test_mode)) + verify_ssl=config.no_verify_ssl) self.config = config diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index eb367e064..dacbe9040 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -22,7 +22,8 @@ CLI_DEFAULTS = dict( cert_path="/etc/letsencrypt/certs/cert-letsencrypt.pem", chain_path="/etc/letsencrypt/certs/chain-letsencrypt.pem", renewer_config_file="/etc/letsencrypt/renewer.conf", - test_mode=False, + no_verify_ssl=False, + dvsni_port=challenges.DVSNI.PORT, ) """Defaults for CLI flags and `.IConfig` attributes.""" @@ -84,7 +85,3 @@ IConfig.work_dir).""" NETSTAT = "/bin/netstat" """Location of netstat binary for checking whether a listener is already running on the specified port (Linux-specific).""" - -BOULDER_TEST_MODE_CHALLENGE_PORT = 5001 -"""Port that Boulder will connect on for validations in test mode.""" - diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py index 575332f3b..e47eea6cc 100644 --- a/letsencrypt/interfaces.py +++ b/letsencrypt/interfaces.py @@ -182,8 +182,11 @@ class IConfig(zope.interface.Interface): cert_path = zope.interface.Attribute("Let's Encrypt certificate file path.") chain_path = zope.interface.Attribute("Let's Encrypt chain file path.") - test_mode = zope.interface.Attribute( - "Test mode. Disables certificate verification.") + no_verify_ssl = zope.interface.Attribute( + "Disable SSL certificate verification.") + dvsni_port = zope.interface.Attribute( + "Port number to perform DVSNI challenge. " + "Boulder in testing mode defaults to 5001.") class IInstaller(IPlugin): diff --git a/letsencrypt/plugins/standalone/authenticator.py b/letsencrypt/plugins/standalone/authenticator.py index 3947c1e3e..599c34395 100644 --- a/letsencrypt/plugins/standalone/authenticator.py +++ b/letsencrypt/plugins/standalone/authenticator.py @@ -15,7 +15,6 @@ import zope.interface from acme import challenges from letsencrypt import achallenges -from letsencrypt import constants from letsencrypt import interfaces from letsencrypt.plugins import common @@ -379,10 +378,8 @@ class StandaloneAuthenticator(common.Plugin): results_if_failure.append(False) if not self.tasks: raise ValueError("nothing for .perform() to do") - port = challenges.DVSNI.PORT - if self.config and self.config.test_mode: - port = constants.BOULDER_TEST_MODE_CHALLENGE_PORT - if self.already_listening(port): + + if self.already_listening(self.config.port): # If we know a process is already listening on this port, # tell the user, and don't even attempt to bind it. (This # test is Linux-specific and won't indicate that the port @@ -390,7 +387,7 @@ class StandaloneAuthenticator(common.Plugin): return results_if_failure # Try to do the authentication; note that this creates # the listener subprocess via os.fork() - if self.start_listener(port, key): + if self.start_listener(self.config.port, key): return results_if_success else: # TODO: This should probably raise a DVAuthError exception @@ -427,8 +424,9 @@ class StandaloneAuthenticator(common.Plugin): def more_info(self): # pylint: disable=no-self-use """Human-readable string that describes the Authenticator.""" return ("The Standalone Authenticator uses PyOpenSSL to listen " - "on port 443 and perform DVSNI challenges. Once a certificate " + "on port {port} and perform DVSNI challenges. Once a certificate " "is attained, it will be saved in the " - "(TODO) current working directory.{0}{0}" - "TCP port 443 must be available in order to use the " - "Standalone Authenticator.".format(os.linesep)) + "(TODO) current working directory.{linesep}{linesep}" + "TCP port {port} must be available in order to use the " + "Standalone Authenticator.".format( + linesep=os.linesep, port=self.config.port)) diff --git a/letsencrypt/plugins/standalone/tests/authenticator_test.py b/letsencrypt/plugins/standalone/tests/authenticator_test.py index 802431536..005ba5682 100644 --- a/letsencrypt/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/plugins/standalone/tests/authenticator_test.py @@ -22,6 +22,7 @@ KEY = le_util.Key("foo", pkg_resources.resource_string( "acme.jose", os.path.join("testdata", "rsa512_key.pem"))) PRIVATE_KEY = OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, KEY.pem) +CONFIG = mock.Mock(port=5001) # Classes based on to allow interrupting infinite loop under test @@ -59,7 +60,7 @@ class ChallPrefTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) def test_chall_pref(self): self.assertEqual(self.authenticator.get_chall_pref("example.com"), @@ -71,7 +72,7 @@ class SNICallbackTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) self.cert = achallenges.DVSNI( challb=acme_util.DVSNI_P, domain="example.com", key=KEY).gen_cert_and_response()[0] @@ -109,7 +110,7 @@ class ClientSignalHandlerTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) self.authenticator.tasks = {"foononce.acme.invalid": "stuff"} self.authenticator.child_pid = 12345 @@ -138,7 +139,7 @@ class SubprocSignalHandlerTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) self.authenticator.tasks = {"foononce.acme.invalid": "stuff"} self.authenticator.child_pid = 12345 self.authenticator.parent_pid = 23456 @@ -190,7 +191,7 @@ class AlreadyListeningTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) @mock.patch("letsencrypt.plugins.standalone.authenticator.psutil." "net_connections") @@ -293,7 +294,7 @@ class PerformTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) self.achall1 = achallenges.DVSNI( challb=acme_util.chall_to_challb( @@ -327,7 +328,8 @@ class PerformTest(unittest.TestCase): self.assertTrue(isinstance(result[0], challenges.ChallengeResponse)) self.assertTrue(isinstance(result[1], challenges.ChallengeResponse)) self.assertFalse(result[2]) - self.authenticator.start_listener.assert_called_once_with(443, KEY) + self.authenticator.start_listener.assert_called_once_with( + CONFIG.port, KEY) def test_cannot_perform(self): """What happens if start_listener() returns False.""" @@ -342,7 +344,8 @@ class PerformTest(unittest.TestCase): self.assertTrue(isinstance(result, list)) self.assertEqual(len(result), 3) self.assertEqual(result, [None, None, False]) - self.authenticator.start_listener.assert_called_once_with(443, KEY) + self.authenticator.start_listener.assert_called_once_with( + CONFIG.port, KEY) def test_perform_with_pending_tasks(self): self.authenticator.tasks = {"foononce.acme.invalid": "cert_data"} @@ -367,7 +370,7 @@ class StartListenerTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) @mock.patch("letsencrypt.plugins.standalone.authenticator." "Crypto.Random.atfork") @@ -402,7 +405,7 @@ class DoParentProcessTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) @mock.patch("letsencrypt.plugins.standalone.authenticator." "zope.component.getUtility") @@ -444,7 +447,7 @@ class DoChildProcessTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) self.cert = achallenges.DVSNI( challb=acme_util.chall_to_challb( challenges.DVSNI(r=("x" * 32), nonce="abcdef"), "pending"), @@ -530,7 +533,7 @@ class CleanupTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import \ StandaloneAuthenticator - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) self.achall = achallenges.DVSNI( challb=acme_util.chall_to_challb( challenges.DVSNI(r="whee", nonce="foononce"), "pending"), @@ -562,7 +565,7 @@ class MoreInfoTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import ( StandaloneAuthenticator) - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) def test_more_info(self): """Make sure exceptions aren't raised.""" @@ -574,7 +577,7 @@ class InitTest(unittest.TestCase): def setUp(self): from letsencrypt.plugins.standalone.authenticator import ( StandaloneAuthenticator) - self.authenticator = StandaloneAuthenticator(config=None, name=None) + self.authenticator = StandaloneAuthenticator(config=CONFIG, name=None) def test_prepare(self): """Make sure exceptions aren't raised. From f5613ee07499530a1e9c3acd001ca9d4f41bd699 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 21:08:42 +0000 Subject: [PATCH 10/11] Add help for the --verbose flag. --- letsencrypt/cli.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 59e99648d..419064aab 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -242,7 +242,9 @@ def create_parser(plugins): add("--version", action="version", version="%(prog)s {0}".format( letsencrypt.__version__)) add("-v", "--verbose", dest="verbose_count", action="count", - default=flag_default("verbose_count")) + default=flag_default("verbose_count"), help="This flag can be used " + "multiple times to incrementally increase the verbosity of output, " + "e.g. -vvv.") add("--no-confirm", dest="no_confirm", action="store_true", help="Turn off confirmation screens, currently used for --revoke") add("-e", "--agree-tos", dest="tos", action="store_true", From 5b957f27d67b07bbffc292f1229e4b37c99ca98c Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Mon, 1 Jun 2015 21:38:32 +0000 Subject: [PATCH 11/11] Fix typo: config.port -> config.dvsni_port. --- letsencrypt/plugins/standalone/authenticator.py | 10 +++++----- .../plugins/standalone/tests/authenticator_test.py | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/letsencrypt/plugins/standalone/authenticator.py b/letsencrypt/plugins/standalone/authenticator.py index 599c34395..8c06d1139 100644 --- a/letsencrypt/plugins/standalone/authenticator.py +++ b/letsencrypt/plugins/standalone/authenticator.py @@ -379,7 +379,7 @@ class StandaloneAuthenticator(common.Plugin): if not self.tasks: raise ValueError("nothing for .perform() to do") - if self.already_listening(self.config.port): + if self.already_listening(self.config.dvsni_port): # If we know a process is already listening on this port, # tell the user, and don't even attempt to bind it. (This # test is Linux-specific and won't indicate that the port @@ -387,7 +387,7 @@ class StandaloneAuthenticator(common.Plugin): return results_if_failure # Try to do the authentication; note that this creates # the listener subprocess via os.fork() - if self.start_listener(self.config.port, key): + if self.start_listener(self.config.dvsni_port, key): return results_if_success else: # TODO: This should probably raise a DVAuthError exception @@ -424,9 +424,9 @@ class StandaloneAuthenticator(common.Plugin): def more_info(self): # pylint: disable=no-self-use """Human-readable string that describes the Authenticator.""" return ("The Standalone Authenticator uses PyOpenSSL to listen " - "on port {port} and perform DVSNI challenges. Once a certificate " - "is attained, it will be saved in the " + "on port {port} and perform DVSNI challenges. Once a " + "certificate is attained, it will be saved in the " "(TODO) current working directory.{linesep}{linesep}" "TCP port {port} must be available in order to use the " "Standalone Authenticator.".format( - linesep=os.linesep, port=self.config.port)) + linesep=os.linesep, port=self.config.dvsni_port)) diff --git a/letsencrypt/plugins/standalone/tests/authenticator_test.py b/letsencrypt/plugins/standalone/tests/authenticator_test.py index 005ba5682..841285750 100644 --- a/letsencrypt/plugins/standalone/tests/authenticator_test.py +++ b/letsencrypt/plugins/standalone/tests/authenticator_test.py @@ -22,7 +22,7 @@ KEY = le_util.Key("foo", pkg_resources.resource_string( "acme.jose", os.path.join("testdata", "rsa512_key.pem"))) PRIVATE_KEY = OpenSSL.crypto.load_privatekey( OpenSSL.crypto.FILETYPE_PEM, KEY.pem) -CONFIG = mock.Mock(port=5001) +CONFIG = mock.Mock(dvsni_port=5001) # Classes based on to allow interrupting infinite loop under test @@ -329,7 +329,7 @@ class PerformTest(unittest.TestCase): self.assertTrue(isinstance(result[1], challenges.ChallengeResponse)) self.assertFalse(result[2]) self.authenticator.start_listener.assert_called_once_with( - CONFIG.port, KEY) + CONFIG.dvsni_port, KEY) def test_cannot_perform(self): """What happens if start_listener() returns False.""" @@ -345,7 +345,7 @@ class PerformTest(unittest.TestCase): self.assertEqual(len(result), 3) self.assertEqual(result, [None, None, False]) self.authenticator.start_listener.assert_called_once_with( - CONFIG.port, KEY) + CONFIG.dvsni_port, KEY) def test_perform_with_pending_tasks(self): self.authenticator.tasks = {"foononce.acme.invalid": "cert_data"}