From 33f1c301924cc6b5c022bf8c9113b04290d2da0a Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Tue, 22 Sep 2015 16:49:07 -0700 Subject: [PATCH 01/36] Updated a2enmod.sh --- .../configurators/apache/a2enmod.sh | 34 +++++-------------- 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh index f822a1f7b..0a8ade4c2 100755 --- a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh +++ b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh @@ -3,31 +3,15 @@ # httpd docker image. First argument is the server_root and the second is the # module to be enabled. -APACHE_CONFDIR=$1 +confdir=$1 +module=$2 -enable () { - echo "LoadModule "$1"_module /usr/local/apache2/modules/mod_"$1".so" >> \ - $APACHE_CONFDIR"/test.conf" - available_base="/mods-available/"$1".conf" - available_conf=$APACHE_CONFDIR$available_base - enabled_dir=$APACHE_CONFDIR"/mods-enabled" - enabled_conf=$enabled_dir"/"$1".conf" - if [ -e "$available_conf" -a -d "$enabled_dir" -a ! -e "$enabled_conf" ] - then - ln -s "..$available_base" $enabled_conf - fi -} - -if [ $2 == "ssl" ] +echo "LoadModule ${module}_module " \ + "/usr/local/apache2/modules/mod_${module}.so" >> "${confdir}/test.conf" +available_conf=$APACHE_CONFDIR"/mods-available/${module}.conf" +enabled_dir=$APACHE_CONFDIR"/mods-enabled" +enabled_conf=$enabled_dir"/"$1".conf" +if [ -e "$available_conf" -a -d "$enabled_dir" -a ! -e "$enabled_conf" ] then - # Enables ssl and all its dependencies - enable "setenvif" - enable "mime" - enable "socache_shmcb" - enable "ssl" -elif [ $2 == "rewrite" ] -then - enable "rewrite" -else - exit 1 + ln -s "..$available_base" $enabled_conf fi From 7a7971ff8d04c87219e31e27e152eb712209cd91 Mon Sep 17 00:00:00 2001 From: Peter Eckersley Date: Wed, 30 Sep 2015 19:06:16 -0700 Subject: [PATCH 02/36] Make sure the LICENSE file is accurate for first pre-relase - In general copyrights remain with their respective authors or authors' organizations, but with license granted by clause 5 of the Apache License. - Presently the plurality of the copyright in the client is held by EFF as a result of work-for-hire by jdkasten, jdkasten, bmw, schoen, pde, rolandshoemaker and jsha; or by Jakub Warmuz or his employer, Google. --- LICENSE.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE.txt b/LICENSE.txt index 5a9f6fa55..2ed752521 100644 --- a/LICENSE.txt +++ b/LICENSE.txt @@ -1,5 +1,5 @@ -Let's Encrypt: -Copyright (c) Internet Security Research Group +Let's Encrypt Python Client +Copyright (c) Electronic Frontier Foundation and others Licensed Apache Version 2.0 Incorporating code from nginxparser From a4d0188d215e6394edce152b750105e284d911e2 Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sat, 3 Oct 2015 12:50:18 -0400 Subject: [PATCH 03/36] Add Mac compatibility to integration tests --- letsencrypt/plugins/manual.py | 1 + letsencrypt/plugins/standalone/authenticator.py | 11 +++++++---- tests/boulder-integration.sh | 6 +++++- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index 3f7276725..f532b8fbc 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -159,6 +159,7 @@ binary for temporary key/certificate generation.""".replace("\n", "") # don't care about setting stdout and stderr, # we're in test mode anyway shell=True, + executable="/bin/bash", # "preexec_fn" is UNIX specific, but so is "command" preexec_fn=os.setsid) except OSError as error: # ValueError should not happen! diff --git a/letsencrypt/plugins/standalone/authenticator.py b/letsencrypt/plugins/standalone/authenticator.py index 968063781..f7c24f5e5 100644 --- a/letsencrypt/plugins/standalone/authenticator.py +++ b/letsencrypt/plugins/standalone/authenticator.py @@ -301,11 +301,14 @@ class StandaloneAuthenticator(common.Plugin): :param int port: The TCP port in question. :returns: True or False.""" - listeners = [conn.pid for conn in psutil.net_connections() - if conn.status == 'LISTEN' and - conn.type == socket.SOCK_STREAM and - conn.laddr[1] == port] try: + + # net_connections() can raise AccessDenied on certain OSs + listeners = [conn.pid for conn in psutil.net_connections() + if conn.status == 'LISTEN' and + conn.type == socket.SOCK_STREAM and + conn.laddr[1] == port] + if listeners and listeners[0] is not None: # conn.pid may be None if the current process doesn't have # permission to identify the listening process! Additionally, diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index ed877d136..efd16ef6c 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -50,7 +50,11 @@ 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")" + if [ `uname` == 'Darwin' ]; then + live="$(greadlink -f "$root/conf/live/le1.wtf/${x}.pem")" + else + live="$(readlink -f "$root/conf/live/le1.wtf/${x}.pem")" + fi [ "${dir}/${latest}" = "$live" ] # renewer fails this test done From 034af2003c45b50e4047b332f7bcdff725fe8fec Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sun, 4 Oct 2015 20:07:00 -0400 Subject: [PATCH 04/36] Only ask for bash on OS X --- letsencrypt/plugins/manual.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index f532b8fbc..179a7b49f 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -154,12 +154,18 @@ binary for temporary key/certificate generation.""".replace("\n", "") if self.conf("test-mode"): logger.debug("Test mode. Executing the manual command: %s", command) try: + # sh shipped with OS X does't support echo -n + if sys.platform == "darwin": + executable = "/bin/bash" + else: + executable = "/bin/sh" + self._httpd = subprocess.Popen( command, # don't care about setting stdout and stderr, # we're in test mode anyway shell=True, - executable="/bin/bash", + executable=executable, # "preexec_fn" is UNIX specific, but so is "command" preexec_fn=os.setsid) except OSError as error: # ValueError should not happen! From 7420a78296371c63e61c242d75e0761eb153ffd6 Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sun, 4 Oct 2015 20:08:15 -0400 Subject: [PATCH 05/36] Shrink AccessDenied error handler and inform --- .../plugins/standalone/authenticator.py | 22 ++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/letsencrypt/plugins/standalone/authenticator.py b/letsencrypt/plugins/standalone/authenticator.py index f7c24f5e5..cdf9a6f04 100644 --- a/letsencrypt/plugins/standalone/authenticator.py +++ b/letsencrypt/plugins/standalone/authenticator.py @@ -1,4 +1,5 @@ """Standalone authenticator.""" +import logging import os import psutil import signal @@ -19,6 +20,9 @@ from letsencrypt import interfaces from letsencrypt.plugins import common +logger = logging.getLogger(__name__) + + class StandaloneAuthenticator(common.Plugin): # pylint: disable=too-many-instance-attributes """Standalone authenticator. @@ -302,13 +306,21 @@ class StandaloneAuthenticator(common.Plugin): :returns: True or False.""" try: + net_connections = psutil.net_connections() + except psutil.AccessDenied as error: + logger.info("Access denied when trying to list network " + "connections: %s. Are you root?", error) + # this function is just a pre-check that often causes false + # positives and problems in testing (c.f. #680 on Mac, #255 + # generally); we will fail later in bind() anyway + return False - # net_connections() can raise AccessDenied on certain OSs - listeners = [conn.pid for conn in psutil.net_connections() - if conn.status == 'LISTEN' and - conn.type == socket.SOCK_STREAM and - conn.laddr[1] == port] + listeners = [conn.pid for conn in net_connections + if conn.status == 'LISTEN' and + conn.type == socket.SOCK_STREAM and + conn.laddr[1] == port] + try: if listeners and listeners[0] is not None: # conn.pid may be None if the current process doesn't have # permission to identify the listening process! Additionally, From c9e28309ed2d3efb3c2c21301f88796fe87169ab Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sun, 4 Oct 2015 20:08:38 -0400 Subject: [PATCH 06/36] Define constants for OS specific executables --- tests/boulder-integration.sh | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh index efd16ef6c..633d426e3 100755 --- a/tests/boulder-integration.sh +++ b/tests/boulder-integration.sh @@ -14,6 +14,12 @@ export PATH="/usr/sbin:$PATH" # /usr/sbin/nginx export GOPATH="${GOPATH:-/tmp/go}" export PATH="$GOPATH/bin:$PATH" +if [ `uname` == 'Darwin' ]; then + readlink="greadlink" +else + readlink="readlink" +fi + common() { letsencrypt_test \ --authenticator standalone \ @@ -50,11 +56,7 @@ dir="$root/conf/archive/le1.wtf" for x in cert chain fullchain privkey; do latest="$(ls -1t $dir/ | grep -e "^${x}" | head -n1)" - if [ `uname` == 'Darwin' ]; then - live="$(greadlink -f "$root/conf/live/le1.wtf/${x}.pem")" - else - live="$(readlink -f "$root/conf/live/le1.wtf/${x}.pem")" - fi + live="$($readlink -f "$root/conf/live/le1.wtf/${x}.pem")" [ "${dir}/${latest}" = "$live" ] # renewer fails this test done From 2737c3299d41cf8dd1a386626846e09316e27bab Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sun, 4 Oct 2015 20:13:07 -0400 Subject: [PATCH 07/36] That shouldn't be in the try block --- letsencrypt/plugins/manual.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index 179a7b49f..f1170eea7 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -153,13 +153,13 @@ binary for temporary key/certificate generation.""".replace("\n", "") ct=response.CONTENT_TYPE, port=port) if self.conf("test-mode"): logger.debug("Test mode. Executing the manual command: %s", command) + # sh shipped with OS X does't support echo -n + if sys.platform == "darwin": + executable = "/bin/bash" + else: + executable = "/bin/sh" + try: - # sh shipped with OS X does't support echo -n - if sys.platform == "darwin": - executable = "/bin/bash" - else: - executable = "/bin/sh" - self._httpd = subprocess.Popen( command, # don't care about setting stdout and stderr, From 973cd6ce42b837175087f4e2a4af4f978fbe9df2 Mon Sep 17 00:00:00 2001 From: Liam Marshall Date: Mon, 5 Oct 2015 18:19:36 -0500 Subject: [PATCH 08/36] Add instructions for submitting a PR. --- docs/contributing.rst | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/contributing.rst b/docs/contributing.rst index 3959ccee1..d2bb2ebd5 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -267,6 +267,17 @@ Please: .. _PEP 8 - Style Guide for Python Code: https://www.python.org/dev/peps/pep-0008 +Submitting a pull request +========================= +Steps: + +1. Write your code! +2. Make sure your environment is set up properly and that you're in your virtualenv (this is a **very important** step). +3. Run ``./pep8.travis.sh`` to do a cursory check of your code style. Fix any errors. +4. Run ``tox -e lint`` to check for pylint errors. Fix any errors. +5. Run ``tox`` to run the unit tests. Fix any errors. +6. :ref:`Run the integration tests `. +7. Submit the PR. Updating the documentation ========================== From a4e5f298565e9b21c4d551bcd3122a404f3fc729 Mon Sep 17 00:00:00 2001 From: Liam Marshall Date: Mon, 5 Oct 2015 18:25:33 -0500 Subject: [PATCH 09/36] Add link to instructions for running integration tests --- docs/contributing.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index d2bb2ebd5..a76d76cd8 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -276,7 +276,7 @@ Steps: 3. Run ``./pep8.travis.sh`` to do a cursory check of your code style. Fix any errors. 4. Run ``tox -e lint`` to check for pylint errors. Fix any errors. 5. Run ``tox`` to run the unit tests. Fix any errors. -6. :ref:`Run the integration tests `. +6. Run the integration tests, see `integration`_. 7. Submit the PR. Updating the documentation From e8118c862b3b9d9dbe34b36a52c1d6990226652e Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 6 Oct 2015 18:54:05 +0000 Subject: [PATCH 10/36] Renewer logging setup (fixes #897) --- letsencrypt/cli.py | 50 ++++++++++++++++++------------- letsencrypt/renewer.py | 29 +++++++++++++++--- letsencrypt/tests/renewer_test.py | 34 +++++++++++++-------- 3 files changed, 76 insertions(+), 37 deletions(-) diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py index 0bd5f537e..66254d2b0 100644 --- a/letsencrypt/cli.py +++ b/letsencrypt/cli.py @@ -841,9 +841,23 @@ def _plugins_parsing(helpful, plugins): helpful.add_plugin_args(plugins) -def _setup_logging(args): - level = -args.verbose_count * 10 - fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" +def setup_log_file_handler(args, logfile, fmt): + """Setup file debug logging.""" + log_file_path = os.path.join(args.logs_dir, logfile) + handler = logging.handlers.RotatingFileHandler( + log_file_path, maxBytes=2 ** 20, backupCount=10) + # rotate on each invocation, rollover only possible when maxBytes + # is nonzero and backupCount is nonzero, so we set maxBytes as big + # as possible not to overrun in single CLI invocation (1MB). + handler.doRollover() # TODO: creates empty letsencrypt.log.1 file + handler.setLevel(logging.DEBUG) + handler_formatter = logging.Formatter(fmt=fmt) + handler_formatter.converter = time.gmtime # don't use localtime + handler.setFormatter(handler_formatter) + return handler, log_file_path + + +def _cli_log_handler(args, level, fmt): if args.text_mode: handler = colored_logging.StreamHandler() handler.setFormatter(logging.Formatter(fmt)) @@ -852,30 +866,26 @@ def _setup_logging(args): # dialog box is small, display as less as possible handler.setFormatter(logging.Formatter("%(message)s")) handler.setLevel(level) + return handler + + +def setup_logging(args, cli_handler_factory, logfile): + """Setup logging.""" + fmt = "%(asctime)s:%(levelname)s:%(name)s:%(message)s" + level = -args.verbose_count * 10 + file_handler, log_file_path = setup_log_file_handler( + args, logfile=logfile, fmt=fmt) + cli_handler = cli_handler_factory(args, level, fmt) # TODO: use fileConfig? - # unconditionally log to file for debugging purposes - # TODO: change before release? - log_file_name = os.path.join(args.logs_dir, 'letsencrypt.log') - file_handler = logging.handlers.RotatingFileHandler( - log_file_name, maxBytes=2 ** 20, backupCount=10) - # rotate on each invocation, rollover only possible when maxBytes - # is nonzero and backupCount is nonzero, so we set maxBytes as big - # as possible not to overrun in single CLI invocation (1MB). - file_handler.doRollover() # TODO: creates empty letsencrypt.log.1 file - file_handler.setLevel(logging.DEBUG) - file_handler_formatter = logging.Formatter(fmt=fmt) - file_handler_formatter.converter = time.gmtime # don't use localtime - file_handler.setFormatter(file_handler_formatter) - root_logger = logging.getLogger() root_logger.setLevel(logging.DEBUG) # send all records to handlers - root_logger.addHandler(handler) + root_logger.addHandler(cli_handler) root_logger.addHandler(file_handler) logger.debug("Root logging level set at %d", level) - logger.info("Saving debug log to %s", log_file_name) + logger.info("Saving debug log to %s", log_file_path) def _handle_exception(exc_type, exc_value, trace, args): @@ -944,7 +954,7 @@ def main(cli_args=sys.argv[1:]): # private key! #525 le_util.make_or_verify_dir( args.logs_dir, 0o700, os.geteuid(), "--strict-permissions" in cli_args) - _setup_logging(args) + setup_logging(args, _cli_log_handler, logfile='letsencrypt.log') # do not log `args`, as it contains sensitive data (e.g. revoke --key)! logger.debug("Arguments: %r", cli_args) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 1c9cddc95..98ecc83b3 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -8,6 +8,7 @@ within lineages of successor certificates, according to configuration. """ import argparse +import logging import os import sys @@ -17,10 +18,12 @@ import zope.component from letsencrypt import account from letsencrypt import configuration +from letsencrypt import colored_logging from letsencrypt import cli from letsencrypt import client from letsencrypt import crypto_util from letsencrypt import errors +from letsencrypt import le_util from letsencrypt import notify from letsencrypt import storage @@ -28,6 +31,9 @@ from letsencrypt.display import util as display_util from letsencrypt.plugins import disco as plugins_disco +logger = logging.getLogger(__name__) + + class _AttrDict(dict): """Attribute dictionary. @@ -104,6 +110,12 @@ def renew(cert, old_version): # (where fewer than all names were renewed) +def _cli_log_handler(args, level, fmt): # pylint: disable=unused-argument + handler = colored_logging.StreamHandler() + handler.setFormatter(logging.Formatter(fmt)) + return handler + + def _paths_parser(parser): add = parser.add_argument_group("paths").add_argument add("--config-dir", default=cli.flag_default("config_dir"), @@ -119,11 +131,16 @@ def _paths_parser(parser): def _create_parser(): parser = argparse.ArgumentParser() #parser.add_argument("--cron", action="store_true", help="Run as cronjob.") - # pylint: disable=protected-access + parser.add_argument( + "-v", "--verbose", dest="verbose_count", action="count", + default=cli.flag_default("verbose_count"), help="This flag can be used " + "multiple times to incrementally increase the verbosity of output, " + "e.g. -vvv.") + return _paths_parser(parser) -def main(config=None, args=sys.argv[1:]): +def main(config=None, cli_args=sys.argv[1:]): """Main function for autorenewer script.""" # TODO: Distinguish automated invocation from manual invocation, # perhaps by looking at sys.argv[0] and inhibiting automated @@ -133,8 +150,12 @@ def main(config=None, args=sys.argv[1:]): zope.component.provideUtility(display_util.FileDisplay(sys.stdout)) - cli_config = configuration.RenewerConfiguration( - _create_parser().parse_args(args)) + args = _create_parser().parse_args(cli_args) + + le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) + cli.setup_logging(args, _cli_log_handler, logfile='renewer.log') + + cli_config = configuration.RenewerConfiguration(args) config = storage.config_with_defaults(config) # Now attempt to read the renewer config file and augment or replace diff --git a/letsencrypt/tests/renewer_test.py b/letsencrypt/tests/renewer_test.py index 6f115abf9..bc8afbcb5 100644 --- a/letsencrypt/tests/renewer_test.py +++ b/letsencrypt/tests/renewer_test.py @@ -44,8 +44,15 @@ class BaseRenewableCertTest(unittest.TestCase): self.tempdir = tempfile.mkdtemp() self.cli_config = configuration.RenewerConfiguration( - namespace=mock.MagicMock(config_dir=self.tempdir)) + namespace=mock.MagicMock( + config_dir=self.tempdir, + work_dir=self.tempdir, + logs_dir=self.tempdir, + ) + ) + # TODO: maybe provide RenewerConfiguration.make_dirs? + # TODO: main() should create those dirs, c.f. #902 os.makedirs(os.path.join(self.tempdir, "live", "example.org")) os.makedirs(os.path.join(self.tempdir, "archive", "example.org")) os.makedirs(os.path.join(self.tempdir, "renewal")) @@ -62,6 +69,9 @@ class BaseRenewableCertTest(unittest.TestCase): self.test_rc = storage.RenewableCert( self.config, self.defaults, self.cli_config) + def tearDown(self): + shutil.rmtree(self.tempdir) + def _write_out_ex_kinds(self): for kind in ALL_FOUR: where = getattr(self.test_rc, kind) @@ -79,11 +89,6 @@ class BaseRenewableCertTest(unittest.TestCase): class RenewableCertTests(BaseRenewableCertTest): # pylint: disable=too-many-public-methods """Tests for letsencrypt.renewer.*.""" - def setUp(self): - super(RenewableCertTests, self).setUp() - - def tearDown(self): - shutil.rmtree(self.tempdir) def test_initialization(self): self.assertEqual(self.test_rc.lineagename, "example.org") @@ -665,11 +670,17 @@ class RenewableCertTests(BaseRenewableCertTest): # This should fail because the renewal itself appears to fail self.assertFalse(renewer.renew(self.test_rc, 1)) + def _common_cli_args(self): + return [ + "--config-dir", self.cli_config.config_dir, + "--work-dir", self.cli_config.work_dir, + "--logs-dir", self.cli_config.logs_dir, + ] + @mock.patch("letsencrypt.renewer.notify") @mock.patch("letsencrypt.storage.RenewableCert") @mock.patch("letsencrypt.renewer.renew") def test_main(self, mock_renew, mock_rc, mock_notify): - """Test for main() function.""" from letsencrypt import renewer mock_rc_instance = mock.MagicMock() mock_rc_instance.should_autodeploy.return_value = True @@ -691,8 +702,7 @@ class RenewableCertTests(BaseRenewableCertTest): "example.com.conf"), "w") as f: f.write("cert = cert.pem\nprivkey = privkey.pem\n") f.write("chain = chain.pem\nfullchain = fullchain.pem\n") - renewer.main(self.defaults, args=[ - '--config-dir', self.cli_config.config_dir]) + renewer.main(self.defaults, cli_args=self._common_cli_args()) self.assertEqual(mock_rc.call_count, 2) self.assertEqual(mock_rc_instance.update_all_links_to.call_count, 2) self.assertEqual(mock_notify.notify.call_count, 4) @@ -705,8 +715,7 @@ class RenewableCertTests(BaseRenewableCertTest): mock_happy_instance.should_autorenew.return_value = False mock_happy_instance.latest_common_version.return_value = 10 mock_rc.return_value = mock_happy_instance - renewer.main(self.defaults, args=[ - '--config-dir', self.cli_config.config_dir]) + renewer.main(self.defaults, cli_args=self._common_cli_args()) self.assertEqual(mock_rc.call_count, 4) self.assertEqual(mock_happy_instance.update_all_links_to.call_count, 0) self.assertEqual(mock_notify.notify.call_count, 4) @@ -717,8 +726,7 @@ class RenewableCertTests(BaseRenewableCertTest): with open(os.path.join(self.cli_config.renewal_configs_dir, "bad.conf"), "w") as f: f.write("incomplete = configfile\n") - renewer.main(self.defaults, args=[ - '--config-dir', self.cli_config.config_dir]) + renewer.main(self.defaults, cli_args=self._common_cli_args()) # The errors.CertStorageError is caught inside and nothing happens. From ae66253ddfe621d3b6ef9da005373d6a67453e5f Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 6 Oct 2015 19:07:20 +0000 Subject: [PATCH 11/36] Don't save KGS in dist dir in dev release script (fixes #908). --- tools/dev-release.sh | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/tools/dev-release.sh b/tools/dev-release.sh index 06f49f0a5..d93a6d21f 100755 --- a/tools/dev-release.sh +++ b/tools/dev-release.sh @@ -70,7 +70,7 @@ echo "Testing packages" cd "dist.$version" # start local PyPI python -m SimpleHTTPServer $PORT & -# cd .. is NOT done on purpose: we make sure that all subpacakges are +# cd .. is NOT done on purpose: we make sure that all subpackages are # installed from local PyPI rather than current directory (repo root) virtualenv --no-site-packages ../venv . ../venv/bin/activate @@ -82,15 +82,16 @@ pip install \ # stop local PyPI kill $! -# freeze before installing anythin else, so that we know end-user KGS -mkdir kgs -kgs="kgs/$version" +# freeze before installing anything else, so that we know end-user KGS +# make sure "twine upload" doesn't catch "kgs" +mkdir ../kgs +kgs="../kgs/$version" pip freeze | tee $kgs pip install nose # TODO: letsencrypt_apache fails due to symlink, c.f. #838 nosetests letsencrypt $SUBPKGS || true echo "New root: $root" -echo "KGS is at $root/$kgs" +echo "KGS is at $root/kgs" echo "In order to upload packages run the following command:" echo twine upload "$root/dist.$version/*/*" From 7e1b7ff7ae0d6f8b187b1ff19e5a94c04cb72bfd Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 6 Oct 2015 20:56:03 +0000 Subject: [PATCH 12/36] Add naive JWK Thumbprint implementation --- acme/acme/jose/jwk.py | 31 +++++++++++++++++++++++++++++++ acme/acme/jose/jwk_test.py | 23 +++++++++++++++++++++-- 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/acme/acme/jose/jwk.py b/acme/acme/jose/jwk.py index 7a976f189..2b8fa0a34 100644 --- a/acme/acme/jose/jwk.py +++ b/acme/acme/jose/jwk.py @@ -1,10 +1,12 @@ """JSON Web Key.""" import abc import binascii +import json import logging import cryptography.exceptions from cryptography.hazmat.backends import default_backend +from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.asymmetric import rsa @@ -27,6 +29,32 @@ class JWK(json_util.TypedJSONObjectWithFields): cryptography_key_types = () """Subclasses should override.""" + required = NotImplemented + """Required members of public key's representation as defined by JWK/JWA.""" + + _thumbprint_json_dumps_params = { + # "no whitespace or line breaks before or after any syntactic + # elements" + 'indent': 0, + 'separators': (',', ':'), + # "members ordered lexicographically by the Unicode [UNICODE] + # code points of the member names" + 'sort_keys': True, + } + + def thumbprint(self, hash_function=hashes.SHA256): + """Compute JWK Thumbprint. + + https://tools.ietf.org/html/rfc7638 + + """ + digest = hashes.Hash(hash_function(), backend=default_backend()) + digest.update(json.dumps( + dict((k, v) for k, v in six.iteritems(self.to_json()) + if k in self.required), + **self._thumbprint_json_dumps_params).encode()) + return digest.finalize() + @abc.abstractmethod def public_key(self): # pragma: no cover """Generate JWK with public key. @@ -105,6 +133,7 @@ class JWKES(JWK): # pragma: no cover typ = 'ES' cryptography_key_types = ( ec.EllipticCurvePublicKey, ec.EllipticCurvePrivateKey) + required = ('crv', JWK.type_field_name, 'x', 'y') def fields_to_partial_json(self): raise NotImplementedError() @@ -122,6 +151,7 @@ class JWKOct(JWK): """Symmetric JWK.""" typ = 'oct' __slots__ = ('key',) + required = ('k', JWK.type_field_name) def fields_to_partial_json(self): # TODO: An "alg" member SHOULD also be present to identify the @@ -150,6 +180,7 @@ class JWKRSA(JWK): typ = 'RSA' cryptography_key_types = (rsa.RSAPublicKey, rsa.RSAPrivateKey) __slots__ = ('key',) + required = ('e', JWK.type_field_name, 'n') def __init__(self, *args, **kwargs): if 'key' in kwargs and not isinstance( diff --git a/acme/acme/jose/jwk_test.py b/acme/acme/jose/jwk_test.py index 5462af6b0..d8a7410e8 100644 --- a/acme/acme/jose/jwk_test.py +++ b/acme/acme/jose/jwk_test.py @@ -25,9 +25,24 @@ class JWKTest(unittest.TestCase): self.assertRaises(errors.Error, JWKRSA.load, DSA_PEM) -class JWKOctTest(unittest.TestCase): +class JWKTestBaseMixin(object): + """Mixin test for JWK subclass tests.""" + + thumbprint = NotImplemented + + def test_thumbprint_private(self): + self.assertEqual(self.thumbprint, self.jwk.thumbprint()) + + def test_thumbprint_public(self): + self.assertEqual(self.thumbprint, self.jwk.public_key().thumbprint()) + + +class JWKOctTest(unittest.TestCase, JWKTestBaseMixin): """Tests for acme.jose.jwk.JWKOct.""" + thumbprint = (b"=,\xdd;I\x1a+i\x02x\x8a\x12?06IM\xc2\x80" + b"\xe4\xc3\x1a\xfc\x89\xf3)'\xce\xccm\xfd5") + def setUp(self): from acme.jose.jwk import JWKOct self.jwk = JWKOct(key=b'foo') @@ -52,10 +67,13 @@ class JWKOctTest(unittest.TestCase): self.assertTrue(self.jwk.public_key() is self.jwk) -class JWKRSATest(unittest.TestCase): +class JWKRSATest(unittest.TestCase, JWKTestBaseMixin): """Tests for acme.jose.jwk.JWKRSA.""" # pylint: disable=too-many-instance-attributes + thumbprint = (b'\x08\xfa1\x87\x1d\x9b6H/*\x1eW\xc2\xe3\xf6P' + b'\xefs\x0cKB\x87\xcf\x85yO\x045\x0e\x91\x80\x0b') + def setUp(self): from acme.jose.jwk import JWKRSA self.jwk256 = JWKRSA(key=RSA256_KEY.public_key()) @@ -87,6 +105,7 @@ class JWKRSATest(unittest.TestCase): 'dq': 'bHh2u7etM8LKKCF2pY2UdQ', 'qi': 'oi45cEkbVoJjAbnQpFY87Q', }) + self.jwk = self.private def test_init_auto_comparable(self): self.assertTrue(isinstance( From 3dac62f20e8dbfacea32df6b1748d5f560b559e3 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 6 Oct 2015 20:56:31 +0000 Subject: [PATCH 13/36] json_dumps_pretty: prettier separators. --- acme/acme/jose/interfaces.py | 2 +- acme/acme/jose/interfaces_test.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/acme/acme/jose/interfaces.py b/acme/acme/jose/interfaces.py index f841848b3..f85777a30 100644 --- a/acme/acme/jose/interfaces.py +++ b/acme/acme/jose/interfaces.py @@ -194,7 +194,7 @@ class JSONDeSerializable(object): :rtype: str """ - return self.json_dumps(sort_keys=True, indent=4, separators=(',', ': ')) + return self.json_dumps(sort_keys=True, indent=4) @classmethod def json_dump_default(cls, python_object): diff --git a/acme/acme/jose/interfaces_test.py b/acme/acme/jose/interfaces_test.py index 380c3a2a5..91e6f4416 100644 --- a/acme/acme/jose/interfaces_test.py +++ b/acme/acme/jose/interfaces_test.py @@ -91,7 +91,7 @@ class JSONDeSerializableTest(unittest.TestCase): def test_json_dumps_pretty(self): self.assertEqual( - self.seq.json_dumps_pretty(), '[\n "foo1",\n "foo2"\n]') + self.seq.json_dumps_pretty(), '[\n "foo1", \n "foo2"\n]') def test_json_dump_default(self): from acme.jose.interfaces import JSONDeSerializable From c6ebfae15e91a232648e41c4a50ea93d01a15d19 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Tue, 6 Oct 2015 20:57:33 +0000 Subject: [PATCH 14/36] Unify quotes --- acme/acme/jose/jwk.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/acme/acme/jose/jwk.py b/acme/acme/jose/jwk.py index 2b8fa0a34..74fa72319 100644 --- a/acme/acme/jose/jwk.py +++ b/acme/acme/jose/jwk.py @@ -88,7 +88,7 @@ class JWK(json_util.TypedJSONObjectWithFields): exceptions[loader] = error # no luck - raise errors.Error("Unable to deserialize key: {0}".format(exceptions)) + raise errors.Error('Unable to deserialize key: {0}'.format(exceptions)) @classmethod def load(cls, data, password=None, backend=None): @@ -109,17 +109,17 @@ class JWK(json_util.TypedJSONObjectWithFields): try: key = cls._load_cryptography_key(data, password, backend) except errors.Error as error: - logger.debug("Loading symmetric key, assymentric failed: %s", error) + logger.debug('Loading symmetric key, assymentric failed: %s', error) return JWKOct(key=data) if cls.typ is not NotImplemented and not isinstance( key, cls.cryptography_key_types): - raise errors.Error("Unable to deserialize {0} into {1}".format( + raise errors.Error('Unable to deserialize {0} into {1}'.format( key.__class__, cls.__class__)) for jwk_cls in six.itervalues(cls.TYPES): if isinstance(key, jwk_cls.cryptography_key_types): return jwk_cls(key=key) - raise errors.Error("Unsupported algorithm: {0}".format(key.__class__)) + raise errors.Error('Unsupported algorithm: {0}'.format(key.__class__)) @JWK.register @@ -235,7 +235,7 @@ class JWKRSA(JWK): jobj.get(x) for x in ('p', 'q', 'dp', 'dq', 'qi')) if tuple(param for param in all_params if param is None): raise errors.Error( - "Some private parameters are missing: {0}".format( + 'Some private parameters are missing: {0}'.format( all_params)) p, q, dp, dq, qi = tuple( cls._decode_param(x) for x in all_params) From 0034a8fae45131e46a27128575eff02f51f79f2c Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Wed, 7 Oct 2015 22:40:02 +0000 Subject: [PATCH 15/36] Add docs to tarballs (fixes #884). --- MANIFEST.in | 1 + 1 file changed, 1 insertion(+) diff --git a/MANIFEST.in b/MANIFEST.in index 80fd8777e..e421e0cd7 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -5,4 +5,5 @@ include CONTRIBUTING.md include LICENSE.txt include linter_plugin.py include letsencrypt/EULA +recursive-include docs * recursive-include letsencrypt/tests/testdata * From 9e1477faa48148ad5b2c3664375a3520d589eb94 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 8 Oct 2015 19:28:55 +0000 Subject: [PATCH 16/36] Release 0.0.0.dev20151008 --- acme/setup.py | 2 +- letsencrypt-apache/setup.py | 2 +- letsencrypt-nginx/setup.py | 2 +- letsencrypt/__init__.py | 2 +- letshelp-letsencrypt/setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 6448b7fe9..36a724f97 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.1.0.dev0' +version = '0.0.0.dev20151008' install_requires = [ # load_pem_private/public_key (>=0.6) diff --git a/letsencrypt-apache/setup.py b/letsencrypt-apache/setup.py index 626e700b2..ef9299b2c 100644 --- a/letsencrypt-apache/setup.py +++ b/letsencrypt-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.1.0.dev0' +version = '0.0.0.dev20151008' install_requires = [ 'acme=={0}'.format(version), diff --git a/letsencrypt-nginx/setup.py b/letsencrypt-nginx/setup.py index a37b8222b..dde7243a9 100644 --- a/letsencrypt-nginx/setup.py +++ b/letsencrypt-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.1.0.dev0' +version = '0.0.0.dev20151008' install_requires = [ 'acme=={0}'.format(version), diff --git a/letsencrypt/__init__.py b/letsencrypt/__init__.py index 1155a5b0c..d7ec9c5e3 100644 --- a/letsencrypt/__init__.py +++ b/letsencrypt/__init__.py @@ -1,4 +1,4 @@ """Let's Encrypt client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.1.0.dev0' +__version__ = '0.0.0.dev20151008' diff --git a/letshelp-letsencrypt/setup.py b/letshelp-letsencrypt/setup.py index a83fc8843..ad517c74e 100644 --- a/letshelp-letsencrypt/setup.py +++ b/letshelp-letsencrypt/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.1.0.dev0' +version = '0.0.0.dev20151008' install_requires = [ 'setuptools', # pkg_resources From 744afe9cea0be2fbb5f8c155e65060d4277de524 Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Thu, 8 Oct 2015 16:15:09 -0400 Subject: [PATCH 17/36] PEP8 E128 up in here. Don't assume sh exists --- letsencrypt/plugins/manual.py | 2 +- letsencrypt/plugins/standalone/authenticator.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index f19861d9d..65d7d6876 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -133,7 +133,7 @@ binary for temporary key/certificate generation.""".replace("\n", "") if sys.platform == "darwin": executable = "/bin/bash" else: - executable = "/bin/sh" + executable = None try: self._httpd = subprocess.Popen( diff --git a/letsencrypt/plugins/standalone/authenticator.py b/letsencrypt/plugins/standalone/authenticator.py index cdf9a6f04..a9972fba2 100644 --- a/letsencrypt/plugins/standalone/authenticator.py +++ b/letsencrypt/plugins/standalone/authenticator.py @@ -309,7 +309,7 @@ class StandaloneAuthenticator(common.Plugin): net_connections = psutil.net_connections() except psutil.AccessDenied as error: logger.info("Access denied when trying to list network " - "connections: %s. Are you root?", error) + "connections: %s. Are you root?", error) # this function is just a pre-check that often causes false # positives and problems in testing (c.f. #680 on Mac, #255 # generally); we will fail later in bind() anyway From 6dfb75b96f6e5126e590b38e608ebedb06c7b2bc Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Thu, 8 Oct 2015 20:32:15 +0000 Subject: [PATCH 18/36] Fix #928 test_json_dumps_pretty py3 compat. --- acme/acme/jose/interfaces_test.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/acme/acme/jose/interfaces_test.py b/acme/acme/jose/interfaces_test.py index 91e6f4416..84dc2a1be 100644 --- a/acme/acme/jose/interfaces_test.py +++ b/acme/acme/jose/interfaces_test.py @@ -1,6 +1,8 @@ """Tests for acme.jose.interfaces.""" import unittest +import six + class JSONDeSerializableTest(unittest.TestCase): # pylint: disable=too-many-instance-attributes @@ -90,8 +92,9 @@ class JSONDeSerializableTest(unittest.TestCase): self.assertEqual('["foo1", "foo2"]', self.seq.json_dumps()) def test_json_dumps_pretty(self): - self.assertEqual( - self.seq.json_dumps_pretty(), '[\n "foo1", \n "foo2"\n]') + filler = ' ' if six.PY2 else '' + self.assertEqual(self.seq.json_dumps_pretty(), + '[\n "foo1",{0}\n "foo2"\n]'.format(filler)) def test_json_dump_default(self): from acme.jose.interfaces import JSONDeSerializable From 454a661d444dfc3e97db826159edc2b35ee820e7 Mon Sep 17 00:00:00 2001 From: Liam Marshall Date: Fri, 9 Oct 2015 15:46:03 -0500 Subject: [PATCH 19/36] contributing.rst: fix nits pointed out by @kuba --- docs/contributing.rst | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/docs/contributing.rst b/docs/contributing.rst index a76d76cd8..b4b1619ce 100644 --- a/docs/contributing.rst +++ b/docs/contributing.rst @@ -269,14 +269,19 @@ Please: Submitting a pull request ========================= + Steps: 1. Write your code! -2. Make sure your environment is set up properly and that you're in your virtualenv (this is a **very important** step). -3. Run ``./pep8.travis.sh`` to do a cursory check of your code style. Fix any errors. +2. Make sure your environment is set up properly and that you're in your + virtualenv. You can do this by running ``./bootstrap/dev/venv.sh``. + (this is a **very important** step) +3. Run ``./pep8.travis.sh`` to do a cursory check of your code style. + Fix any errors. 4. Run ``tox -e lint`` to check for pylint errors. Fix any errors. -5. Run ``tox`` to run the unit tests. Fix any errors. -6. Run the integration tests, see `integration`_. +5. Run ``tox`` to run the entire test suite including coverage. Fix any errors. +6. If your code touches communication with an ACME server/Boulder, you + should run the integration tests, see `integration`_. 7. Submit the PR. Updating the documentation From d1ee8311379dd1c102ed54ed1ae827b527cd255f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 9 Oct 2015 16:55:34 -0700 Subject: [PATCH 20/36] Fixed version string --- letsencrypt-compatibility-test/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-compatibility-test/setup.py b/letsencrypt-compatibility-test/setup.py index 2e70fd1d7..5a54239dd 100644 --- a/letsencrypt-compatibility-test/setup.py +++ b/letsencrypt-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.1.0.dev0' +version = '0.0.0.dev20151008' install_requires = [ 'letsencrypt=={0}'.format(version), From ecf82c4bcf3c3e37ab8ba5746cd3902541d6a9e3 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Fri, 9 Oct 2015 16:56:28 -0700 Subject: [PATCH 21/36] Add 'strict_permissions' when creating config --- .../letsencrypt_compatibility_test/util.py | 1 + 1 file changed, 1 insertion(+) diff --git a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py index 6181da16b..816f04398 100644 --- a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py +++ b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py @@ -25,6 +25,7 @@ IP_REGEX = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") def create_le_config(parent_dir): """Sets up LE dirs in parent_dir and returns the config dict""" config = copy.deepcopy(constants.CLI_DEFAULTS) + config["strict_permissions"] = False le_dir = os.path.join(parent_dir, "letsencrypt") config["config_dir"] = os.path.join(le_dir, "config") From 84418516c9a6e1ecbbc3387fc6bd3e9a8f60cc61 Mon Sep 17 00:00:00 2001 From: Jacob Hoffman-Andrews Date: Sat, 10 Oct 2015 13:31:35 -0700 Subject: [PATCH 22/36] Limit Travis runs to master and PRs. --- .travis.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.travis.yml b/.travis.yml index 46b14fe63..3041fdd82 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,15 @@ env: - TOXENV=lint - TOXENV=cover + +# Only build pushes to the master branch, PRs, and branches beginning with +# `test-`. This reduces the number of simultaneous Travis runs, which speeds +# turnaround time on review since there is a cap of 5 simultaneous runs. +branches: + only: + - master + - /^test-.*$/ + sudo: false # containers addons: # make sure simplehttp simple verification works (custom /etc/hosts) From f96c34546e91be25429a8a1c2f99710a9539f402 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sat, 10 Oct 2015 18:33:44 -0400 Subject: [PATCH 23/36] Fixes #902 Fix for #902 If the directory does't exist, it will create the directory before proceeding. --- letsencrypt/renewer.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 1c9cddc95..953b372f5 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -146,6 +146,10 @@ def main(config=None, args=sys.argv[1:]): # take precedence over this one. config.merge(configobj.ConfigObj(cli_config.renewer_config_file)) + # If the folder does not exist we need to create it. + if not os.path.isdir(cli_config.renewal_configs_dir): + os.makedirs(cli_config.renewal_configs_dir) + for i in os.listdir(cli_config.renewal_configs_dir): print "Processing", i if not i.endswith(".conf"): From 7a153ebf50893cc314b1536c6bb314af4ccfc817 Mon Sep 17 00:00:00 2001 From: Jakub Warmuz Date: Sun, 11 Oct 2015 07:05:35 +0000 Subject: [PATCH 24/36] Revert "Release 0.0.0.dev20151008" This reverts commit 9e1477faa48148ad5b2c3664375a3520d589eb94. --- acme/setup.py | 2 +- letsencrypt-apache/setup.py | 2 +- letsencrypt-nginx/setup.py | 2 +- letsencrypt/__init__.py | 2 +- letshelp-letsencrypt/setup.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/acme/setup.py b/acme/setup.py index 36a724f97..6448b7fe9 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.0.0.dev20151008' +version = '0.1.0.dev0' install_requires = [ # load_pem_private/public_key (>=0.6) diff --git a/letsencrypt-apache/setup.py b/letsencrypt-apache/setup.py index ef9299b2c..626e700b2 100644 --- a/letsencrypt-apache/setup.py +++ b/letsencrypt-apache/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.0.0.dev20151008' +version = '0.1.0.dev0' install_requires = [ 'acme=={0}'.format(version), diff --git a/letsencrypt-nginx/setup.py b/letsencrypt-nginx/setup.py index dde7243a9..a37b8222b 100644 --- a/letsencrypt-nginx/setup.py +++ b/letsencrypt-nginx/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.0.0.dev20151008' +version = '0.1.0.dev0' install_requires = [ 'acme=={0}'.format(version), diff --git a/letsencrypt/__init__.py b/letsencrypt/__init__.py index d7ec9c5e3..1155a5b0c 100644 --- a/letsencrypt/__init__.py +++ b/letsencrypt/__init__.py @@ -1,4 +1,4 @@ """Let's Encrypt client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.0.0.dev20151008' +__version__ = '0.1.0.dev0' diff --git a/letshelp-letsencrypt/setup.py b/letshelp-letsencrypt/setup.py index ad517c74e..a83fc8843 100644 --- a/letshelp-letsencrypt/setup.py +++ b/letshelp-letsencrypt/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.0.0.dev20151008' +version = '0.1.0.dev0' install_requires = [ 'setuptools', # pkg_resources From f8daf5f09411ed9d317d310b8efc97d3f39b495e Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 11 Oct 2015 08:53:38 -0400 Subject: [PATCH 25/36] Fixed failing lint test and explicitly created all needed folders --- letsencrypt/renewer.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 953b372f5..077af6a68 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -23,6 +23,8 @@ from letsencrypt import crypto_util from letsencrypt import errors from letsencrypt import notify from letsencrypt import storage +from letsencrypt import constants +from letsencrypt import le_util from letsencrypt.display import util as display_util from letsencrypt.plugins import disco as plugins_disco @@ -145,10 +147,16 @@ def main(config=None, args=sys.argv[1:]): # specify a config file on the command line, which, if provided, should # take precedence over this one. config.merge(configobj.ConfigObj(cli_config.renewer_config_file)) - - # If the folder does not exist we need to create it. - if not os.path.isdir(cli_config.renewal_configs_dir): - os.makedirs(cli_config.renewal_configs_dir) + # Ensure that all of the needed folders have been created before continuing + uid = os.geteuid() + le_util.make_or_verify_dir( + cli_config.renewal_configs_dir, constants.CONFIG_DIRS_MODE, uid) + le_util.make_or_verify_dir( + cli_config.config_dir, constants.CONFIG_DIRS_MODE, uid) + le_util.make_or_verify_dir( + cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) + le_util.make_or_verify_dir( + cli_config.logs_dir, constants.CONFIG_DIRS_MODE, uid) for i in os.listdir(cli_config.renewal_configs_dir): print "Processing", i @@ -189,4 +197,4 @@ def main(config=None, args=sys.argv[1:]): cert.update_all_links_to(cert.latest_common_version()) # TODO: restart web server (invoke IInstaller.restart() method) notify.notify("Autodeployed a cert!!!", "root", "It worked!") - # TODO: explain what happened + # TODO: explain what happened \ No newline at end of file From ef9312817e230f9d579c673685acae8547d7e5f4 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 11 Oct 2015 11:39:55 -0400 Subject: [PATCH 26/36] Alphabetized imports and added newline at end of file --- letsencrypt/renewer.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 077af6a68..81a557419 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -17,14 +17,14 @@ import zope.component from letsencrypt import account from letsencrypt import configuration +from letsencrypt import constants from letsencrypt import cli from letsencrypt import client from letsencrypt import crypto_util from letsencrypt import errors +from letsencrypt import le_util from letsencrypt import notify from letsencrypt import storage -from letsencrypt import constants -from letsencrypt import le_util from letsencrypt.display import util as display_util from letsencrypt.plugins import disco as plugins_disco @@ -197,4 +197,4 @@ def main(config=None, args=sys.argv[1:]): cert.update_all_links_to(cert.latest_common_version()) # TODO: restart web server (invoke IInstaller.restart() method) notify.notify("Autodeployed a cert!!!", "root", "It worked!") - # TODO: explain what happened \ No newline at end of file + # TODO: explain what happened From 4c73db9aa1c792a831599a69fa6991a636ef501b Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Sun, 11 Oct 2015 10:34:32 -0700 Subject: [PATCH 27/36] Revert "Fixed version string" This reverts commit d1ee8311379dd1c102ed54ed1ae827b527cd255f. --- letsencrypt-compatibility-test/setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-compatibility-test/setup.py b/letsencrypt-compatibility-test/setup.py index 5a54239dd..2e70fd1d7 100644 --- a/letsencrypt-compatibility-test/setup.py +++ b/letsencrypt-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.0.0.dev20151008' +version = '0.1.0.dev0' install_requires = [ 'letsencrypt=={0}'.format(version), From a809a059f0512159279b5dcbc100374393327e4b Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 11 Oct 2015 15:57:57 -0400 Subject: [PATCH 28/36] Don't create renewal_configs_dir and config_dir, instead just print a helpful error message and fail --- letsencrypt/renewer.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index ebd9a42d1..76922e6fd 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -169,14 +169,14 @@ def main(config=None, cli_args=sys.argv[1:]): config.merge(configobj.ConfigObj(cli_config.renewer_config_file)) # Ensure that all of the needed folders have been created before continuing uid = os.geteuid() - le_util.make_or_verify_dir( - cli_config.renewal_configs_dir, constants.CONFIG_DIRS_MODE, uid) - le_util.make_or_verify_dir( - cli_config.config_dir, constants.CONFIG_DIRS_MODE, uid) - le_util.make_or_verify_dir( - cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) - le_util.make_or_verify_dir( - cli_config.logs_dir, constants.CONFIG_DIRS_MODE, uid) + if (not os.path.isdir(cli_config.renewal_configs_dir) or + not os.path.isdir(cli_config.config_dir)): + print "Could not find config directory. Exiting. " + else: + le_util.make_or_verify_dir( + cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) + le_util.make_or_verify_dir( + cli_config.logs_dir, constants.CONFIG_DIRS_MODE, uid) for i in os.listdir(cli_config.renewal_configs_dir): print "Processing", i From 8ec7fdd323b6e814ae54629e507ca23f0e736beb Mon Sep 17 00:00:00 2001 From: David Dworken Date: Sun, 11 Oct 2015 16:20:18 -0400 Subject: [PATCH 29/36] Always create the folders --- letsencrypt/renewer.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index 76922e6fd..bec868483 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -172,11 +172,10 @@ def main(config=None, cli_args=sys.argv[1:]): if (not os.path.isdir(cli_config.renewal_configs_dir) or not os.path.isdir(cli_config.config_dir)): print "Could not find config directory. Exiting. " - else: - le_util.make_or_verify_dir( - cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) - le_util.make_or_verify_dir( - cli_config.logs_dir, constants.CONFIG_DIRS_MODE, uid) + le_util.make_or_verify_dir( + cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) + le_util.make_or_verify_dir( + cli_config.logs_dir, constants.CONFIG_DIRS_MODE, uid) for i in os.listdir(cli_config.renewal_configs_dir): print "Processing", i From 52f7a64b8442f099ea581b407ffaa9cae4a4cefb Mon Sep 17 00:00:00 2001 From: Brandon Kreisel Date: Sun, 11 Oct 2015 17:56:30 -0400 Subject: [PATCH 30/36] lint newline --- letsencrypt/plugins/manual.py | 1 - 1 file changed, 1 deletion(-) diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py index 65d7d6876..99463c362 100644 --- a/letsencrypt/plugins/manual.py +++ b/letsencrypt/plugins/manual.py @@ -134,7 +134,6 @@ binary for temporary key/certificate generation.""".replace("\n", "") executable = "/bin/bash" else: executable = None - try: self._httpd = subprocess.Popen( command, From 90f3b26bcff975f87a26357659543b69dad2492f Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Sun, 11 Oct 2015 17:29:19 -0700 Subject: [PATCH 31/36] Fixed a2enmod --- .../configurators/apache/a2enmod.sh | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh index 0a8ade4c2..60a62407a 100755 --- a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh +++ b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh @@ -1,6 +1,6 @@ #!/bin/bash # An extremely simplified version of `a2enmod` for enabling modules in the -# httpd docker image. First argument is the server_root and the second is the +# httpd docker image. First argument is the ServerRoot and the second is the # module to be enabled. confdir=$1 @@ -8,10 +8,11 @@ module=$2 echo "LoadModule ${module}_module " \ "/usr/local/apache2/modules/mod_${module}.so" >> "${confdir}/test.conf" -available_conf=$APACHE_CONFDIR"/mods-available/${module}.conf" -enabled_dir=$APACHE_CONFDIR"/mods-enabled" -enabled_conf=$enabled_dir"/"$1".conf" -if [ -e "$available_conf" -a -d "$enabled_dir" -a ! -e "$enabled_conf" ] +availbase="/mods-available/${module}.conf" +availconf=$confdir$availbase +enabldir="$confdir/mods-enabled" +enablconf="$enabldir/${module}.conf" +if [ -e $availconf -a -d $enabldir -a ! -e $enablconf ] then - ln -s "..$available_base" $enabled_conf + ln -s "..$availbase" $enablconf fi From 9c59b5089408e9e93d5436def0f747a8202ee913 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Sun, 11 Oct 2015 17:33:00 -0700 Subject: [PATCH 32/36] Reverted incorrect change from 18adec0 --- .../letsencrypt_compatibility_test/util.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py index 816f04398..dd4fd3b48 100644 --- a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py +++ b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py @@ -40,7 +40,7 @@ def create_le_config(parent_dir): def extract_configs(configs, parent_dir): """Extracts configs to a new dir under parent_dir and returns it""" - config_dir = os.path.join(parent_dir, "renewal") + config_dir = os.path.join(parent_dir, "configs") if os.path.isdir(configs): shutil.copytree(configs, config_dir, symlinks=True) From 67ee9bf930b95c9975920a6f3079d336bbfc5520 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 12 Oct 2015 10:00:35 -0700 Subject: [PATCH 33/36] Added 'strict_permissions' to constants.py --- .../letsencrypt_compatibility_test/util.py | 1 - letsencrypt/constants.py | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py index dd4fd3b48..43070cf03 100644 --- a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py +++ b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/util.py @@ -25,7 +25,6 @@ IP_REGEX = re.compile(r"^\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}$") def create_le_config(parent_dir): """Sets up LE dirs in parent_dir and returns the config dict""" config = copy.deepcopy(constants.CLI_DEFAULTS) - config["strict_permissions"] = False le_dir = os.path.join(parent_dir, "letsencrypt") config["config_dir"] = os.path.join(le_dir, "config") diff --git a/letsencrypt/constants.py b/letsencrypt/constants.py index 762409d25..362009ec6 100644 --- a/letsencrypt/constants.py +++ b/letsencrypt/constants.py @@ -27,6 +27,7 @@ CLI_DEFAULTS = dict( auth_cert_path="./cert.pem", auth_chain_path="./chain.pem", + strict_permissions=False, ) """Defaults for CLI flags and `.IConfig` attributes.""" From 7defdb18193500cfb287bde092a089c087887b33 Mon Sep 17 00:00:00 2001 From: Brad Warren Date: Mon, 12 Oct 2015 10:08:35 -0700 Subject: [PATCH 34/36] Updated a2enmod comments --- .../configurators/apache/a2enmod.sh | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh index 60a62407a..4da9288a2 100755 --- a/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh +++ b/letsencrypt-compatibility-test/letsencrypt_compatibility_test/configurators/apache/a2enmod.sh @@ -1,7 +1,7 @@ #!/bin/bash # An extremely simplified version of `a2enmod` for enabling modules in the -# httpd docker image. First argument is the ServerRoot and the second is the -# module to be enabled. +# httpd docker image. First argument is the Apache ServerRoot which should be +# an absolute path. The second is the module to be enabled, such as `ssl`. confdir=$1 module=$2 From 589145686fd4b3f92b90357f39f05fcfa1900b5b Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 12 Oct 2015 15:02:07 -0400 Subject: [PATCH 35/36] Don't print error message and only call os.geteuid() once --- letsencrypt/renewer.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index bec868483..ea1eb5ef7 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -153,7 +153,8 @@ def main(config=None, cli_args=sys.argv[1:]): args = _create_parser().parse_args(cli_args) - le_util.make_or_verify_dir(args.logs_dir, 0o700, os.geteuid()) + uid = os.geteuid() + le_util.make_or_verify_dir(args.logs_dir, 0o700, uid) cli.setup_logging(args, _cli_log_handler, logfile='renewer.log') cli_config = configuration.RenewerConfiguration(args) @@ -168,10 +169,6 @@ def main(config=None, cli_args=sys.argv[1:]): # take precedence over this one. config.merge(configobj.ConfigObj(cli_config.renewer_config_file)) # Ensure that all of the needed folders have been created before continuing - uid = os.geteuid() - if (not os.path.isdir(cli_config.renewal_configs_dir) or - not os.path.isdir(cli_config.config_dir)): - print "Could not find config directory. Exiting. " le_util.make_or_verify_dir( cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) le_util.make_or_verify_dir( From 20d7576f662999c2e53a6bd48d353fcd41a8b126 Mon Sep 17 00:00:00 2001 From: David Dworken Date: Mon, 12 Oct 2015 15:14:13 -0400 Subject: [PATCH 36/36] Deleted duplicate line caused by #912 --- letsencrypt/renewer.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/letsencrypt/renewer.py b/letsencrypt/renewer.py index ea1eb5ef7..b62e31bce 100644 --- a/letsencrypt/renewer.py +++ b/letsencrypt/renewer.py @@ -171,8 +171,6 @@ def main(config=None, cli_args=sys.argv[1:]): # Ensure that all of the needed folders have been created before continuing le_util.make_or_verify_dir( cli_config.work_dir, constants.CONFIG_DIRS_MODE, uid) - le_util.make_or_verify_dir( - cli_config.logs_dir, constants.CONFIG_DIRS_MODE, uid) for i in os.listdir(cli_config.renewal_configs_dir): print "Processing", i