diff --git a/CHANGELOG.md b/CHANGELOG.md index 79b3834ea..146db0b8c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,7 +2,34 @@ Certbot adheres to [Semantic Versioning](https://semver.org/). -## 0.31.0 - master +## 0.32.0 - master + +### Added + +* + +### Changed + +* Certbot and its acme module now depend on josepy>=1.1.0 to avoid printing the + warnings described at https://github.com/certbot/josepy/issues/13. +* Apache plugin now respects CERTBOT_DOCS environment variable when adding + command line defaults. + +### Fixed + +* + +Despite us having broken lockstep, we are continuing to release new versions of +all Certbot components during releases for the time being, however, the only +package with changes other than its version number was: + +* acme +* certbot +* certbot-apache + +More details about these changes can be found on our GitHub repo. + +## 0.31.0 - 2019-02-07 ### Added diff --git a/acme/acme/client.py b/acme/acme/client.py index 41338e17e..76b6a7788 100644 --- a/acme/acme/client.py +++ b/acme/acme/client.py @@ -739,8 +739,7 @@ class ClientV2(ClientBase): if body.error is not None: raise errors.IssuanceError(body.error) if body.certificate is not None: - certificate_response = self._post_as_get(body.certificate, - content_type=DER_CONTENT_TYPE).text + certificate_response = self._post_as_get(body.certificate).text return orderr.update(body=body, fullchain_pem=certificate_response) raise errors.TimeoutError() diff --git a/acme/setup.py b/acme/setup.py index eac3974fa..79d6d3389 100644 --- a/acme/setup.py +++ b/acme/setup.py @@ -3,7 +3,7 @@ from setuptools import find_packages from setuptools.command.test import test as TestCommand import sys -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ @@ -11,7 +11,9 @@ install_requires = [ # rsa_recover_prime_factors (>=0.8) 'cryptography>=1.2.3', # formerly known as acme.jose: - 'josepy>=1.0.0', + # 1.1.0+ is required to avoid the warnings described at + # https://github.com/certbot/josepy/issues/13. + 'josepy>=1.1.0', # Connection.set_tlsext_host_name (>=0.13) 'mock', 'PyOpenSSL>=0.13.1', diff --git a/certbot-apache/certbot_apache/configurator.py b/certbot-apache/certbot_apache/configurator.py index efd766e63..f7f4ff925 100644 --- a/certbot-apache/certbot_apache/configurator.py +++ b/certbot-apache/certbot_apache/configurator.py @@ -92,6 +92,11 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): """ description = "Apache Web Server plugin" + if os.environ.get("CERTBOT_DOCS") == "1": + description += ( # pragma: no cover + " (Please note that the default values of the Apache plugin options" + " change depending on the operating system Certbot is run on.)" + ) OS_DEFAULTS = dict( server_root="/etc/apache2", @@ -141,28 +146,36 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): # When adding, modifying or deleting command line arguments, be sure to # include the changes in the list used in method _prepare_options() to # ensure consistent behavior. - add("enmod", default=cls.OS_DEFAULTS["enmod"], + + # Respect CERTBOT_DOCS environment variable and use default values from + # base class regardless of the underlying distribution (overrides). + if os.environ.get("CERTBOT_DOCS") == "1": + DEFAULTS = ApacheConfigurator.OS_DEFAULTS + else: + # cls.OS_DEFAULTS can be distribution specific, see override classes + DEFAULTS = cls.OS_DEFAULTS + add("enmod", default=DEFAULTS["enmod"], help="Path to the Apache 'a2enmod' binary") - add("dismod", default=cls.OS_DEFAULTS["dismod"], + add("dismod", default=DEFAULTS["dismod"], help="Path to the Apache 'a2dismod' binary") - add("le-vhost-ext", default=cls.OS_DEFAULTS["le_vhost_ext"], + add("le-vhost-ext", default=DEFAULTS["le_vhost_ext"], help="SSL vhost configuration extension") - add("server-root", default=cls.OS_DEFAULTS["server_root"], + add("server-root", default=DEFAULTS["server_root"], help="Apache server root directory") add("vhost-root", default=None, help="Apache server VirtualHost configuration root") - add("logs-root", default=cls.OS_DEFAULTS["logs_root"], + add("logs-root", default=DEFAULTS["logs_root"], help="Apache server logs directory") add("challenge-location", - default=cls.OS_DEFAULTS["challenge_location"], + default=DEFAULTS["challenge_location"], help="Directory path for challenge configuration") - add("handle-modules", default=cls.OS_DEFAULTS["handle_modules"], + add("handle-modules", default=DEFAULTS["handle_modules"], help="Let installer handle enabling required modules for you " + "(Only Ubuntu/Debian currently)") - add("handle-sites", default=cls.OS_DEFAULTS["handle_sites"], + add("handle-sites", default=DEFAULTS["handle_sites"], help="Let installer handle enabling sites for you " + "(Only Ubuntu/Debian currently)") - add("ctl", default=cls.OS_DEFAULTS["ctl"], + add("ctl", default=DEFAULTS["ctl"], help="Full path to Apache control script") util.add_deprecated_argument( add, argument_name="init-script", nargs=1) diff --git a/certbot-apache/certbot_apache/tests/configurator_test.py b/certbot-apache/certbot_apache/tests/configurator_test.py index ff93682b6..7c281d707 100644 --- a/certbot-apache/certbot_apache/tests/configurator_test.py +++ b/certbot-apache/certbot_apache/tests/configurator_test.py @@ -115,6 +115,37 @@ class MultipleVhostsTest(util.ApacheTest): # Weak test.. ApacheConfigurator.add_parser_arguments(mock.MagicMock()) + def test_docs_parser_arguments(self): + os.environ["CERTBOT_DOCS"] = "1" + from certbot_apache.configurator import ApacheConfigurator + mock_add = mock.MagicMock() + ApacheConfigurator.add_parser_arguments(mock_add) + parserargs = ["server_root", "enmod", "dismod", "le_vhost_ext", + "vhost_root", "logs_root", "challenge_location", + "handle_modules", "handle_sites", "ctl"] + exp = dict() + + for k in ApacheConfigurator.OS_DEFAULTS: + if k in parserargs: + exp[k.replace("_", "-")] = ApacheConfigurator.OS_DEFAULTS[k] + # Special cases + exp["vhost-root"] = None + exp["init-script"] = None + + found = set() + for call in mock_add.call_args_list: + # init-script is a special case: deprecated argument + if call[0][0] != "init-script": + self.assertEqual(exp[call[0][0]], call[1]['default']) + found.add(call[0][0]) + + # Make sure that all (and only) the expected values exist + self.assertEqual(len(mock_add.call_args_list), len(found)) + for e in exp: + self.assertTrue(e in found) + + del os.environ["CERTBOT_DOCS"] + def test_add_parser_arguments_all_configurators(self): # pylint: disable=no-self-use from certbot_apache.entrypoint import OVERRIDE_CLASSES for cls in OVERRIDE_CLASSES.values(): diff --git a/certbot-apache/setup.py b/certbot-apache/setup.py index 14d6cacb6..52528a536 100644 --- a/certbot-apache/setup.py +++ b/certbot-apache/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-auto b/certbot-auto index a79a9c5ae..03c2994e3 100755 --- a/certbot-auto +++ b/certbot-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="0.30.2" +LE_AUTO_VERSION="0.31.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -333,63 +333,11 @@ BootstrapDebCommon() { fi augeas_pkg="libaugeas0 augeas-lenses" - AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` if [ "$ASSUME_YES" = 1 ]; then YES_FLAG="-y" fi - AddBackportRepo() { - # ARGS: - BACKPORT_NAME="$1" - BACKPORT_SOURCELINE="$2" - say "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME." - if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then - # This can theoretically error if sources.list.d is empty, but in that case we don't care. - if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..." - sleep 1s - add_backports=1 - else - read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response - case $response in - [yY][eE][sS]|[yY]|"") - add_backports=1;; - *) - add_backports=0;; - esac - fi - if [ "$add_backports" = 1 ]; then - sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list" - apt-get $QUIET_FLAG update - fi - fi - fi - if [ "$add_backports" != 0 ]; then - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg - augeas_pkg= - fi - } - - - if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then - if lsb_release -a | grep -q wheezy ; then - AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main" - elif lsb_release -a | grep -q precise ; then - # XXX add ARM case - AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" - else - echo "No libaugeas0 version is available that's new enough to run the" - echo "Certbot apache plugin..." - fi - # XXX add a case for ubuntu PPAs - fi - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \ python \ python-dev \ @@ -1140,9 +1088,9 @@ parsedatetime==2.1 \ pbr==1.8.1 \ --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==16.2.0 \ - --hash=sha256:26ca380ddf272f7556e48064bbcd5bd71f83dfc144f3583501c7ddbd9434ee17 \ - --hash=sha256:7779a3bbb74e79db234af6a08775568c6769b5821faecf6e2f4143edb227516e +pyOpenSSL==18.0.0 \ + --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ + --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 pyparsing==2.1.8 \ --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ @@ -1232,18 +1180,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/certbot-compatibility-test/setup.py b/certbot-compatibility-test/setup.py index f519ed422..e1c82b063 100644 --- a/certbot-compatibility-test/setup.py +++ b/certbot-compatibility-test/setup.py @@ -4,7 +4,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' install_requires = [ 'certbot', diff --git a/certbot-dns-cloudflare/setup.py b/certbot-dns-cloudflare/setup.py index ff33293fe..d6a16f468 100644 --- a/certbot-dns-cloudflare/setup.py +++ b/certbot-dns-cloudflare/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-cloudxns/local-oldest-requirements.txt b/certbot-dns-cloudxns/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-cloudxns/local-oldest-requirements.txt +++ b/certbot-dns-cloudxns/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-cloudxns/setup.py b/certbot-dns-cloudxns/setup.py index 1a6f900d8..f0f2446e1 100644 --- a/certbot-dns-cloudxns/setup.py +++ b/certbot-dns-cloudxns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-digitalocean/setup.py b/certbot-dns-digitalocean/setup.py index 2f7fa37d6..c61f05339 100644 --- a/certbot-dns-digitalocean/setup.py +++ b/certbot-dns-digitalocean/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-dnsimple/local-oldest-requirements.txt b/certbot-dns-dnsimple/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-dnsimple/local-oldest-requirements.txt +++ b/certbot-dns-dnsimple/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-dnsimple/setup.py b/certbot-dns-dnsimple/setup.py index 1a2ce5d92..7d39a10d0 100644 --- a/certbot-dns-dnsimple/setup.py +++ b/certbot-dns-dnsimple/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt +++ b/certbot-dns-dnsmadeeasy/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-dnsmadeeasy/setup.py b/certbot-dns-dnsmadeeasy/setup.py index 0a99f452d..8a5f234b5 100644 --- a/certbot-dns-dnsmadeeasy/setup.py +++ b/certbot-dns-dnsmadeeasy/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-gehirn/local-oldest-requirements.txt b/certbot-dns-gehirn/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-gehirn/local-oldest-requirements.txt +++ b/certbot-dns-gehirn/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-gehirn/setup.py b/certbot-dns-gehirn/setup.py index f4a75379c..67ae1d4eb 100644 --- a/certbot-dns-gehirn/setup.py +++ b/certbot-dns-gehirn/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.1.22', 'mock', 'setuptools', diff --git a/certbot-dns-google/setup.py b/certbot-dns-google/setup.py index c99ad38aa..429702d75 100644 --- a/certbot-dns-google/setup.py +++ b/certbot-dns-google/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-linode/local-oldest-requirements.txt b/certbot-dns-linode/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-linode/local-oldest-requirements.txt +++ b/certbot-dns-linode/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-linode/setup.py b/certbot-dns-linode/setup.py index 31c2c20bc..6797501b9 100644 --- a/certbot-dns-linode/setup.py +++ b/certbot-dns-linode/setup.py @@ -1,12 +1,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', 'mock', 'setuptools', diff --git a/certbot-dns-luadns/local-oldest-requirements.txt b/certbot-dns-luadns/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-luadns/local-oldest-requirements.txt +++ b/certbot-dns-luadns/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-luadns/setup.py b/certbot-dns-luadns/setup.py index 31472e8cf..07d9decdf 100644 --- a/certbot-dns-luadns/setup.py +++ b/certbot-dns-luadns/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-nsone/local-oldest-requirements.txt b/certbot-dns-nsone/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-nsone/local-oldest-requirements.txt +++ b/certbot-dns-nsone/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-nsone/setup.py b/certbot-dns-nsone/setup.py index 41b99cc73..314b0a16c 100644 --- a/certbot-dns-nsone/setup.py +++ b/certbot-dns-nsone/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.2.1', # Support for >1 TXT record per name 'mock', 'setuptools', diff --git a/certbot-dns-ovh/local-oldest-requirements.txt b/certbot-dns-ovh/local-oldest-requirements.txt index 01cbcb317..b7e6072af 100644 --- a/certbot-dns-ovh/local-oldest-requirements.txt +++ b/certbot-dns-ovh/local-oldest-requirements.txt @@ -1,3 +1,3 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 dns-lexicon==2.7.14 diff --git a/certbot-dns-ovh/setup.py b/certbot-dns-ovh/setup.py index 5b3329568..a878eac47 100644 --- a/certbot-dns-ovh/setup.py +++ b/certbot-dns-ovh/setup.py @@ -2,13 +2,13 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.7.14', # Correct proxy use on OVH provider 'mock', 'setuptools', diff --git a/certbot-dns-rfc2136/setup.py b/certbot-dns-rfc2136/setup.py index edf7b6ba6..39f35e953 100644 --- a/certbot-dns-rfc2136/setup.py +++ b/certbot-dns-rfc2136/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-route53/setup.py b/certbot-dns-route53/setup.py index 69c2c7ed3..e915c6b86 100644 --- a/certbot-dns-route53/setup.py +++ b/certbot-dns-route53/setup.py @@ -1,7 +1,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot-dns-sakuracloud/local-oldest-requirements.txt b/certbot-dns-sakuracloud/local-oldest-requirements.txt index 65f5a758e..45b6b2291 100644 --- a/certbot-dns-sakuracloud/local-oldest-requirements.txt +++ b/certbot-dns-sakuracloud/local-oldest-requirements.txt @@ -1,2 +1,2 @@ --e acme[dev] --e .[dev] +acme[dev]==0.31.0 +certbot[dev]==0.31.0 diff --git a/certbot-dns-sakuracloud/setup.py b/certbot-dns-sakuracloud/setup.py index 4ebfc6e1d..429f960f7 100644 --- a/certbot-dns-sakuracloud/setup.py +++ b/certbot-dns-sakuracloud/setup.py @@ -2,12 +2,12 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Please update tox.ini when modifying dependency version requirements install_requires = [ - 'acme>=0.31.0.dev0', - 'certbot>=0.31.0.dev0', + 'acme>=0.31.0', + 'certbot>=0.31.0', 'dns-lexicon>=2.1.23', 'mock', 'setuptools', diff --git a/certbot-nginx/setup.py b/certbot-nginx/setup.py index 70e11f62b..f78473ada 100644 --- a/certbot-nginx/setup.py +++ b/certbot-nginx/setup.py @@ -2,7 +2,7 @@ from setuptools import setup from setuptools import find_packages -version = '0.31.0.dev0' +version = '0.32.0.dev0' # Remember to update local-oldest-requirements.txt when changing the minimum # acme/certbot version. diff --git a/certbot/__init__.py b/certbot/__init__.py index bf68034c8..767448a12 100644 --- a/certbot/__init__.py +++ b/certbot/__init__.py @@ -1,4 +1,4 @@ """Certbot client.""" # version number like 1.2.3a0, must have at least 2 parts, like 1.2 -__version__ = '0.31.0.dev0' +__version__ = '0.32.0.dev0' diff --git a/certbot/compat.py b/certbot/compat.py index 3b5d068a6..f533f5954 100644 --- a/certbot/compat.py +++ b/certbot/compat.py @@ -13,13 +13,6 @@ import stat from certbot import errors -try: - # Linux specific - import fcntl # pylint: disable=import-error -except ImportError: - # Windows specific - import msvcrt # pylint: disable=import-error - UNPRIVILEGED_SUBCOMMANDS_ALLOWED = [ 'certificates', 'enhance', 'revoke', 'delete', 'register', 'unregister', 'config_changes', 'plugins'] @@ -118,55 +111,6 @@ def readline_with_timeout(timeout, prompt): return sys.stdin.readline() -def lock_file(fd): - """ - Lock the file linked to the specified file descriptor. - - :param int fd: The file descriptor of the file to lock. - - """ - if 'fcntl' in sys.modules: - # Linux specific - fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) - else: - # Windows specific - msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) - - -def release_locked_file(fd, path): - """ - Remove, close, and release a lock file specified by its file descriptor and its path. - - :param int fd: The file descriptor of the lock file. - :param str path: The path of the lock file. - - """ - # Linux specific - # - # It is important the lock file is removed before it's released, - # otherwise: - # - # process A: open lock file - # process B: release lock file - # process A: lock file - # process A: check device and inode - # process B: delete file - # process C: open and lock a different file at the same path - try: - os.remove(path) - except OSError as err: - if err.errno == errno.EACCES: - # Windows specific - # We will not be able to remove a file before closing it. - # To avoid race conditions described for Linux, we will not delete the lockfile, - # just close it to be reused on the next Certbot call. - pass - else: - raise - finally: - os.close(fd) - - def compare_file_modes(mode1, mode2): """Return true if the two modes can be considered as equals for this platform""" if os.name != 'nt': diff --git a/certbot/lock.py b/certbot/lock.py index 3ff46518d..760a12b8f 100644 --- a/certbot/lock.py +++ b/certbot/lock.py @@ -1,15 +1,23 @@ -"""Implements file locks for locking files and directories in UNIX.""" +"""Implements file locks compatible with Linux and Windows for locking files and directories.""" import errno import logging import os +try: + import fcntl # pylint: disable=import-error +except ImportError: + import msvcrt # pylint: disable=import-error + POSIX_MODE = False +else: + POSIX_MODE = True -from certbot import compat from certbot import errors +from acme.magic_typing import Optional, Callable # pylint: disable=unused-import, no-name-in-module logger = logging.getLogger(__name__) def lock_dir(dir_path): + # type: (str) -> LockFile """Place a lock file on the directory at dir_path. The lock file is placed in the root of dir_path with the name @@ -27,34 +35,99 @@ def lock_dir(dir_path): class LockFile(object): - """A UNIX lock file. - - This lock file is released when the locked file is closed or the - process exits. It cannot be used to provide synchronization between - threads. It is based on the lock_file package by Martin Horcicka. - + """ + Platform independent file lock system. + LockFile accepts a parameter, the path to a file acting as a lock. Once the LockFile, + instance is created, the associated file is 'locked from the point of view of the OS, + meaning that if another instance of Certbot try at the same time to acquire the same lock, + it will raise an Exception. Calling release method will release the lock, and make it + available to every other instance. + Upon exit, Certbot will also release all the locks. + This allows us to protect a file or directory from being concurrently accessed + or modified by two Certbot instances. + LockFile is platform independent: it will proceed to the appropriate OS lock mechanism + depending on Linux or Windows. """ def __init__(self, path): - """Initialize and acquire the lock file. - - :param str path: path to the file to lock - - :raises errors.LockError: if unable to acquire the lock - + # type: (str) -> None + """ + Create a LockFile instance on the given file path, and acquire lock. + :param str path: the path to the file that will hold a lock """ - super(LockFile, self).__init__() self._path = path - self._fd = None + mechanism = _UnixLockMechanism if POSIX_MODE else _WindowsLockMechanism + self._lock_mechanism = mechanism(path) self.acquire() + def __repr__(self): + # type: () -> str + repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) + if self.is_locked(): + repr_str += 'acquired>' + else: + repr_str += 'released>' + return repr_str + def acquire(self): - """Acquire the lock file. - - :raises errors.LockError: if lock is already held - :raises OSError: if unable to open or stat the lock file - + # type: () -> None """ + Acquire the lock on the file, forbidding any other Certbot instance to acquire it. + :raises errors.LockError: if unable to acquire the lock + """ + self._lock_mechanism.acquire() + + def release(self): + # type: () -> None + """ + Release the lock on the file, allowing any other Certbot instance to acquire it. + """ + self._lock_mechanism.release() + + def is_locked(self): + # type: () -> bool + """ + Check if the file is currently locked. + :return: True if the file is locked, False otherwise + """ + return self._lock_mechanism.is_locked() + + +class _BaseLockMechanism(object): + def __init__(self, path): + # type: (str) -> None + """ + Create a lock file mechanism for Unix. + :param str path: the path to the lock file + """ + self._path = path + self._fd = None # type: Optional[int] + + def is_locked(self): + # type: () -> bool + """Check if lock file is currently locked. + :return: True if the lock file is locked + :rtype: bool + """ + return self._fd is not None + + def acquire(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + def release(self): # pylint: disable=missing-docstring + pass # pragma: no cover + + +class _UnixLockMechanism(_BaseLockMechanism): + """ + A UNIX lock file mechanism. + This lock file is released when the locked file is closed or the + process exits. It cannot be used to provide synchronization between + threads. It is based on the lock_file package by Martin Horcicka. + """ + def acquire(self): + # type: () -> None + """Acquire the lock.""" while self._fd is None: # Open the file fd = os.open(self._path, os.O_CREAT | os.O_WRONLY, 0o600) @@ -68,33 +141,29 @@ class LockFile(object): os.close(fd) def _try_lock(self, fd): - """Try to acquire the lock file without blocking. - + # type: (int) -> None + """ + Try to acquire the lock file without blocking. :param int fd: file descriptor of the opened file to lock - """ try: - compat.lock_file(fd) + fcntl.lockf(fd, fcntl.LOCK_EX | fcntl.LOCK_NB) except IOError as err: if err.errno in (errno.EACCES, errno.EAGAIN): - logger.debug( - "A lock on %s is held by another process.", self._path) - raise errors.LockError( - "Another instance of Certbot is already running.") + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') raise def _lock_success(self, fd): - """Did we successfully grab the lock? - + # type: (int) -> bool + """ + Did we successfully grab the lock? Because this class deletes the locked file when the lock is released, it is possible another process removed and recreated the file between us opening the file and acquiring the lock. - :param int fd: file descriptor of the opened file to lock - :returns: True if the lock was successfully acquired :rtype: bool - """ try: stat1 = os.stat(self._path) @@ -108,17 +177,75 @@ class LockFile(object): # the same device and inode, they're the same file. return stat1.st_dev == stat2.st_dev and stat1.st_ino == stat2.st_ino - def __repr__(self): - repr_str = '{0}({1}) <'.format(self.__class__.__name__, self._path) - if self._fd is None: - repr_str += 'released>' - else: - repr_str += 'acquired>' - return repr_str + def release(self): + # type: () -> None + """Remove, close, and release the lock file.""" + # It is important the lock file is removed before it's released, + # otherwise: + # + # process A: open lock file + # process B: release lock file + # process A: lock file + # process A: check device and inode + # process B: delete file + # process C: open and lock a different file at the same path + try: + os.remove(self._path) + finally: + # Following check is done to make mypy happy: it ensure that self._fd, marked + # as Optional[int] is effectively int to make it compatible with os.close signature. + if self._fd is None: # pragma: no cover + raise TypeError('Error, self._fd is None.') + try: + os.close(self._fd) + finally: + self._fd = None + + +class _WindowsLockMechanism(_BaseLockMechanism): + """ + A Windows lock file mechanism. + By default on Windows, acquiring a file handler gives exclusive access to the process + and results in an effective lock. However, it is possible to explicitly acquire the + file handler in shared access in terms of read and write, and this is done by os.open + and io.open in Python. So an explicit lock needs to be done through the call of + msvcrt.locking, that will lock the first byte of the file. In theory, it is also + possible to access a file in shared delete access, allowing other processes to delete an + opened file. But this needs also to be done explicitly by all processes using the Windows + low level APIs, and Python does not do it. As of Python 3.7 and below, Python developers + state that deleting a file opened by a process from another process is not possible with + os.open and io.open. + Consequently, mscvrt.locking is sufficient to obtain an effective lock, and the race + condition encountered on Linux is not possible on Windows, leading to a simpler workflow. + """ + def acquire(self): + """Acquire the lock""" + open_mode = os.O_RDWR | os.O_CREAT | os.O_TRUNC + + fd = os.open(self._path, open_mode, 0o600) + try: + msvcrt.locking(fd, msvcrt.LK_NBLCK, 1) + except (IOError, OSError) as err: + os.close(fd) + # Anything except EACCES is unexpected. Raise directly the error in that case. + if err.errno != errno.EACCES: + raise + logger.debug('A lock on %s is held by another process.', self._path) + raise errors.LockError('Another instance of Certbot is already running.') + + self._fd = fd def release(self): - """Remove, close, and release the lock file.""" + """Release the lock.""" try: - compat.release_locked_file(self._fd, self._path) + msvcrt.locking(self._fd, msvcrt.LK_UNLCK, 1) + os.close(self._fd) + + try: + os.remove(self._path) + except OSError as e: + # If the lock file cannot be removed, it is not a big deal. + # Likely another instance is acquiring the lock we just released. + logger.debug(str(e)) finally: self._fd = None diff --git a/certbot/tests/lock_test.py b/certbot/tests/lock_test.py index aa82701f3..8658443d0 100644 --- a/certbot/tests/lock_test.py +++ b/certbot/tests/lock_test.py @@ -3,6 +3,12 @@ import functools import multiprocessing import os import unittest +try: + import fcntl # pylint: disable=import-error,unused-import +except ImportError: + POSIX_MODE = False +else: + POSIX_MODE = True import mock @@ -10,7 +16,6 @@ from certbot import errors from certbot.tests import util as test_util -@test_util.broken_on_windows class LockDirTest(test_util.TempDirTestCase): """Tests for certbot.lock.lock_dir.""" @classmethod @@ -25,7 +30,6 @@ class LockDirTest(test_util.TempDirTestCase): test_util.lock_and_call(assert_raises, lock_path) -@test_util.broken_on_windows class LockFileTest(test_util.TempDirTestCase): """Tests for certbot.lock.LockFile.""" @classmethod @@ -71,6 +75,8 @@ class LockFileTest(test_util.TempDirTestCase): self.assertTrue(lock_file.__class__.__name__ in lock_repr) self.assertTrue(self.lock_path in lock_repr) + @test_util.skip_on_windows( + 'Race conditions on lock are specific to the non-blocking file access approach on Linux.') def test_race(self): should_delete = [True, False] stat = os.stat @@ -91,27 +97,36 @@ class LockFileTest(test_util.TempDirTestCase): lock_file.release() self.assertFalse(os.path.exists(self.lock_path)) - @mock.patch('certbot.compat.fcntl.lockf') - def test_unexpected_lockf_err(self, mock_lockf): + def test_unexpected_lockf_or_locking_err(self): + if POSIX_MODE: + mocked_function = 'certbot.lock.fcntl.lockf' + else: + mocked_function = 'certbot.lock.msvcrt.locking' msg = 'hi there' - mock_lockf.side_effect = IOError(msg) - try: - self._call(self.lock_path) - except IOError as err: - self.assertTrue(msg in str(err)) - else: # pragma: no cover - self.fail('IOError not raised') + with mock.patch(mocked_function) as mock_lock: + mock_lock.side_effect = IOError(msg) + try: + self._call(self.lock_path) + except IOError as err: + self.assertTrue(msg in str(err)) + else: # pragma: no cover + self.fail('IOError not raised') - @mock.patch('certbot.lock.os.stat') - def test_unexpected_stat_err(self, mock_stat): + def test_unexpected_os_err(self): + if POSIX_MODE: + mock_function = 'certbot.lock.os.stat' + else: + mock_function = 'certbot.lock.msvcrt.locking' + # The only expected errno are ENOENT and EACCES in lock module. msg = 'hi there' - mock_stat.side_effect = OSError(msg) - try: - self._call(self.lock_path) - except OSError as err: - self.assertTrue(msg in str(err)) - else: # pragma: no cover - self.fail('OSError not raised') + with mock.patch(mock_function) as mock_os: + mock_os.side_effect = OSError(msg) + try: + self._call(self.lock_path) + except OSError as err: + self.assertTrue(msg in str(err)) + else: # pragma: no cover + self.fail('OSError not raised') if __name__ == "__main__": diff --git a/certbot/tests/util.py b/certbot/tests/util.py index 8c5db2c2f..953d36536 100644 --- a/certbot/tests/util.py +++ b/certbot/tests/util.py @@ -3,7 +3,6 @@ .. warning:: This module is not part of the public API. """ -import multiprocessing import os import pkg_resources import shutil @@ -11,6 +10,8 @@ import tempfile import unittest import sys import warnings +import subprocess +import time from cryptography.hazmat.backends import default_backend from cryptography.hazmat.primitives import serialization @@ -23,8 +24,8 @@ from six.moves import reload_module # pylint: disable=import-error from certbot import constants from certbot import interfaces from certbot import storage -from certbot import util from certbot import configuration +from certbot import util from certbot.display import util as display_util @@ -211,7 +212,7 @@ class FreezableMock(object): """ def __init__(self, frozen=False, func=None, return_value=mock.sentinel.DEFAULT): - self._frozen_set = set() if frozen else set(('freeze',)) + self._frozen_set = set() if frozen else {'freeze', } self._func = func self._mock = mock.MagicMock() if return_value != mock.sentinel.DEFAULT: @@ -340,6 +341,7 @@ class TempDirTestCase(unittest.TestCase): warnings.warn(message) shutil.rmtree(self.tempdir, onerror=onerror_handler) + class ConfigTestCase(TempDirTestCase): """Test class which sets up a NamespaceConfig object. @@ -358,47 +360,58 @@ class ConfigTestCase(TempDirTestCase): self.config.chain_path = constants.CLI_DEFAULTS['auth_chain_path'] self.config.server = "https://example.com" -def lock_and_call(func, lock_path): - """Grab a lock for lock_path and call func. - - :param callable func: object to call after acquiring the lock - :param str lock_path: path to file or directory to lock +def lock_and_call(callback, path_to_lock): + """Grab a lock on path_to_lock from a foreign process and call the callback. + :param callable callback: object to call after acquiring the lock + :param str path_to_lock: path to file or directory to lock """ - # Reload module to reset internal _LOCKS dictionary + script = """\ +import os +import sys +import time +from certbot import lock + +path_to_lock = sys.argv[1] +trigger = sys.argv[2] + +if os.path.isdir(path_to_lock): + my_lock = lock.lock_dir(path_to_lock) +else: + my_lock = lock.LockFile(path_to_lock) +try: + open(trigger, 'w').close() + while os.path.exists(trigger): + time.sleep(1) +finally: + my_lock.release() +""" + # Reload certbot.util module to reset internal _LOCKS dictionary. reload_module(util) - # start child and wait for it to grab the lock - cv = multiprocessing.Condition() - cv.acquire() - child_args = (cv, lock_path,) - child = multiprocessing.Process(target=hold_lock, args=child_args) - child.start() - cv.wait() + workspace = tempfile.mkdtemp() + try: + tmp_script = os.path.join(workspace, 'test_script.py') + with open(tmp_script, 'w') as file_handle: + file_handle.write(script) - # call func and terminate the child - func() - cv.notify() - cv.release() - child.join() - assert child.exitcode == 0 + # Trigger file is used to coordinate current process and its subprocess. + trigger = os.path.join(workspace, 'trigger') + process = subprocess.Popen([sys.executable, tmp_script, path_to_lock, trigger]) + try: + # Poll and wait for the lock to be acquired, spotted by the trigger file creation. + while not os.path.exists(trigger): + time.sleep(1) + # Then execute the callback. + callback() + finally: + # This will trigger the lock release in subprocess. + os.remove(trigger) + process.communicate() + assert process.returncode == 0 + finally: + shutil.rmtree(workspace) -def hold_lock(cv, lock_path): # pragma: no cover - """Acquire a file lock at lock_path and wait to release it. - - :param multiprocessing.Condition cv: condition for synchronization - :param str lock_path: path to the file lock - - """ - from certbot import lock - if os.path.isdir(lock_path): - my_lock = lock.lock_dir(lock_path) - else: - my_lock = lock.LockFile(lock_path) - cv.acquire() - cv.notify() - cv.wait() - my_lock.release() def skip_on_windows(reason): """Decorator to skip permanently a test on Windows. A reason is required.""" @@ -407,6 +420,7 @@ def skip_on_windows(reason): return unittest.skipIf(sys.platform == 'win32', reason)(function) return wrapper + def broken_on_windows(function): """Decorator to skip temporarily a broken test on Windows.""" reason = 'Test is broken and ignored on windows but should be fixed.' @@ -415,9 +429,10 @@ def broken_on_windows(function): and os.environ.get('SKIP_BROKEN_TESTS_ON_WINDOWS', 'true') == 'true', reason)(function) + def temp_join(path): """ Return the given path joined to the tempdir path for the current platform Eg.: 'cert' => /tmp/cert (Linux) or 'C:\\Users\\currentuser\\AppData\\Temp\\cert' (Windows) """ - return os.path.join(tempfile.gettempdir(), path) + return os.path.join(tempfile.gettempdir(), path) diff --git a/certbot/tests/util_test.py b/certbot/tests/util_test.py index 6685b88c6..c85009c62 100644 --- a/certbot/tests/util_test.py +++ b/certbot/tests/util_test.py @@ -2,7 +2,6 @@ import argparse import errno import os -import shutil import unittest import mock @@ -88,7 +87,6 @@ class LockDirUntilExit(test_util.TempDirTestCase): import certbot.util reload_module(certbot.util) - @test_util.broken_on_windows @mock.patch('certbot.util.logger') @mock.patch('certbot.util.atexit_register') def test_it(self, mock_register, mock_logger): @@ -100,11 +98,15 @@ class LockDirUntilExit(test_util.TempDirTestCase): self.assertEqual(mock_register.call_count, 1) registered_func = mock_register.call_args[0][0] - shutil.rmtree(subdir) - registered_func() # exception not raised - # logger.debug is only called once because the second call - # to lock subdir was ignored because it was already locked - self.assertEqual(mock_logger.debug.call_count, 1) + + from certbot import util + # Despite lock_dir_until_exit has been called twice to subdir, its lock should have been + # added only once. So we expect to have two lock references: for self.tempdir and subdir + self.assertTrue(len(util._LOCKS) == 2) # pylint: disable=protected-access + registered_func() # Exception should not be raised + # Logically, logger.debug, that would be invoked in case of unlock failure, + # should never been called. + self.assertEqual(mock_logger.debug.call_count, 0) class SetUpCoreDirTest(test_util.TempDirTestCase): diff --git a/docs/cli-help.txt b/docs/cli-help.txt index cd6d431b3..e95b2fcd5 100644 --- a/docs/cli-help.txt +++ b/docs/cli-help.txt @@ -113,7 +113,7 @@ optional arguments: case, and to know when to deprecate support for past Python versions and flags. If you wish to hide this information from the Let's Encrypt server, set this to - "". (default: CertbotACMEClient/0.30.2 + "". (default: CertbotACMEClient/0.31.0 (certbot(-auto); OS_NAME OS_VERSION) Authenticator/XXX Installer/YYY (SUBCOMMAND; flags: FLAGS) Py/major.minor.patchlevel). The flags encoded in the @@ -351,8 +351,9 @@ revoke: Specify reason for revoking certificate. (default: unspecified) --delete-after-revoke - Delete certificates after revoking them. (default: - None) + Delete certificates after revoking them, along with + all previous and later versions of those certificates. + (default: None) --no-delete-after-revoke Do not delete certificates after revoking them. This option should be used with caution because the 'renew' @@ -479,10 +480,9 @@ apache: Apache Web Server plugin --apache-enmod APACHE_ENMOD - Path to the Apache 'a2enmod' binary (default: a2enmod) + Path to the Apache 'a2enmod' binary (default: None) --apache-dismod APACHE_DISMOD - Path to the Apache 'a2dismod' binary (default: - a2dismod) + Path to the Apache 'a2dismod' binary (default: None) --apache-le-vhost-ext APACHE_LE_VHOST_EXT SSL vhost configuration extension (default: -le- ssl.conf) @@ -496,16 +496,16 @@ apache: /var/log/apache2) --apache-challenge-location APACHE_CHALLENGE_LOCATION Directory path for challenge configuration (default: - /etc/apache2) + /etc/apache2/other) --apache-handle-modules APACHE_HANDLE_MODULES Let installer handle enabling required modules for you - (Only Ubuntu/Debian currently) (default: True) + (Only Ubuntu/Debian currently) (default: False) --apache-handle-sites APACHE_HANDLE_SITES Let installer handle enabling sites for you (Only - Ubuntu/Debian currently) (default: True) + Ubuntu/Debian currently) (default: False) --apache-ctl APACHE_CTL Full path to Apache control script (default: - apache2ctl) + apachectl) dns-cloudflare: Obtain certificates using a DNS TXT record (if you are using Cloudflare diff --git a/letsencrypt-auto b/letsencrypt-auto index a79a9c5ae..03c2994e3 100755 --- a/letsencrypt-auto +++ b/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="0.30.2" +LE_AUTO_VERSION="0.31.0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -333,63 +333,11 @@ BootstrapDebCommon() { fi augeas_pkg="libaugeas0 augeas-lenses" - AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2` if [ "$ASSUME_YES" = 1 ]; then YES_FLAG="-y" fi - AddBackportRepo() { - # ARGS: - BACKPORT_NAME="$1" - BACKPORT_SOURCELINE="$2" - say "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME." - if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then - # This can theoretically error if sources.list.d is empty, but in that case we don't care. - if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then - if [ "$ASSUME_YES" = 1 ]; then - /bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..." - sleep 1s - /bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..." - sleep 1s - /bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..." - sleep 1s - add_backports=1 - else - read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response - case $response in - [yY][eE][sS]|[yY]|"") - add_backports=1;; - *) - add_backports=0;; - esac - fi - if [ "$add_backports" = 1 ]; then - sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list" - apt-get $QUIET_FLAG update - fi - fi - fi - if [ "$add_backports" != 0 ]; then - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg - augeas_pkg= - fi - } - - - if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then - if lsb_release -a | grep -q wheezy ; then - AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main" - elif lsb_release -a | grep -q precise ; then - # XXX add ARM case - AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse" - else - echo "No libaugeas0 version is available that's new enough to run the" - echo "Certbot apache plugin..." - fi - # XXX add a case for ubuntu PPAs - fi - apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \ python \ python-dev \ @@ -1140,9 +1088,9 @@ parsedatetime==2.1 \ pbr==1.8.1 \ --hash=sha256:46c8db75ae75a056bd1cc07fa21734fe2e603d11a07833ecc1eeb74c35c72e0c \ --hash=sha256:e2127626a91e6c885db89668976db31020f0af2da728924b56480fc7ccf09649 -pyOpenSSL==16.2.0 \ - --hash=sha256:26ca380ddf272f7556e48064bbcd5bd71f83dfc144f3583501c7ddbd9434ee17 \ - --hash=sha256:7779a3bbb74e79db234af6a08775568c6769b5821faecf6e2f4143edb227516e +pyOpenSSL==18.0.0 \ + --hash=sha256:26ff56a6b5ecaf3a2a59f132681e2a80afcc76b4f902f612f518f92c2a1bf854 \ + --hash=sha256:6488f1423b00f73b7ad5167885312bb0ce410d3312eb212393795b53c8caa580 pyparsing==2.1.8 \ --hash=sha256:2f0f5ceb14eccd5aef809d6382e87df22ca1da583c79f6db01675ce7d7f49c18 \ --hash=sha256:03a4869b9f3493807ee1f1cb405e6d576a1a2ca4d81a982677c0c1ad6177c56b \ @@ -1232,18 +1180,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/certbot-auto.asc b/letsencrypt-auto-source/certbot-auto.asc index a60ccd8bb..d95f9abcb 100644 --- a/letsencrypt-auto-source/certbot-auto.asc +++ b/letsencrypt-auto-source/certbot-auto.asc @@ -1,11 +1,11 @@ -----BEGIN PGP SIGNATURE----- -iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlxLcw8ACgkQTRfJlc2X -dfK35gf+PoxtrJJIjvybNqd3lb8HOg2ntIVmXcYJGuuUo6m09fzai+XI6cOm5Dpu -l2D5OrbLqmez8tYkCkEWHV0OfwyVWw+m8T3sXlcrv14eA1RfgMnZ+cmmlpDskzHU -EOtaXo1/IkLDwBRrsl8IUbwD2XxbjuLsA2Sevoa59NlfTXJUApfAzohl3epRiJjB -gugdqcsfjRRAqQqOz+iJCKBCWSTIrr/g6Y9aZu9V93t/WDSLRFjehxO1GQrLnCnX -17JGlr0/AXd67jOKS1OWmORPPAFfLIXezUMtgrz5hE7T5UviaUu9ySV8UCxq1N79 -cfSBb/HIUxZ0wf1CkTUMRFQpA7cGtw== -=cNcT +iQEzBAABCAAdFiEEos+1H6J1pyhiNOeyTRfJlc2XdfIFAlxcop8ACgkQTRfJlc2X +dfIbZwf/faKu7IjLi0qFQ+kw8zaAnV47JDgfWqbR5GSdwWPqld+QyHlcRfPgwYma +fKj9+g/FvPNPSfjHRRCoFrYvpZ4lZ+f4HPN9+OjydfM77rdDhVDwzs8dbKIk02yU +0IEJhXj5Q9hF3TSDZcyXAJdBU1lz51ohtVIXelMBPmzhYPCZF47iE9/k9pApQi86 +RTji7hxPcF/n7mzXrbyTvk+kDxSdDlE0Eg9syK7XaFDBTa2lqgG8wTnMPVqhc/hm +WM/uwkzbYarjy05ffV1kM683nP0rECnHlYT38pYcT2puw2kn/QthwR5j/jB/DWSc +94Kw7BeMH651V8EaNwYIiouylnVH3A== +=U+Qh -----END PGP SIGNATURE----- diff --git a/letsencrypt-auto-source/letsencrypt-auto b/letsencrypt-auto-source/letsencrypt-auto index 8aaa1e3fb..19aaa4eeb 100755 --- a/letsencrypt-auto-source/letsencrypt-auto +++ b/letsencrypt-auto-source/letsencrypt-auto @@ -31,7 +31,7 @@ if [ -z "$VENV_PATH" ]; then fi VENV_BIN="$VENV_PATH/bin" BOOTSTRAP_VERSION_PATH="$VENV_PATH/certbot-auto-bootstrap-version.txt" -LE_AUTO_VERSION="0.31.0.dev0" +LE_AUTO_VERSION="0.32.0.dev0" BASENAME=$(basename $0) USAGE="Usage: $BASENAME [OPTIONS] A self-updating wrapper script for the Certbot ACME client. When run, updates @@ -1180,18 +1180,18 @@ letsencrypt==0.7.0 \ --hash=sha256:105a5fb107e45bcd0722eb89696986dcf5f08a86a321d6aef25a0c7c63375ade \ --hash=sha256:c36e532c486a7e92155ee09da54b436a3c420813ec1c590b98f635d924720de9 -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 UNLIKELY_EOF # ------------------------------------------------------------------------- diff --git a/letsencrypt-auto-source/letsencrypt-auto.sig b/letsencrypt-auto-source/letsencrypt-auto.sig index f5175187a..3ecca1510 100644 Binary files a/letsencrypt-auto-source/letsencrypt-auto.sig and b/letsencrypt-auto-source/letsencrypt-auto.sig differ diff --git a/letsencrypt-auto-source/pieces/certbot-requirements.txt b/letsencrypt-auto-source/pieces/certbot-requirements.txt index 80249cd9a..514d3271b 100644 --- a/letsencrypt-auto-source/pieces/certbot-requirements.txt +++ b/letsencrypt-auto-source/pieces/certbot-requirements.txt @@ -1,12 +1,12 @@ -certbot==0.30.2 \ - --hash=sha256:e411b72fa86eec1018e6de28e649e8c9c71191a7431dcc77f207b57ca9484c11 \ - --hash=sha256:534487cb552ced8e47948ba3d2e7ca12c3a439133fc609485012b1a02fc7776e -acme==0.30.2 \ - --hash=sha256:68982576492dfa99c7e2be0fce4371adc9344740b05420ce0ab53238d2bb9b3b \ - --hash=sha256:295a5b7fce9f908e6e5cff8c40be1a3daf3e1ebabd2e139a4c87274e68eeb8f2 -certbot-apache==0.30.2 \ - --hash=sha256:3b7fa4e59772da7c9975ef2a49ceff157c9d7cb31eb9475928b5986d89701a3a \ - --hash=sha256:32fa915a8a51810fdfe828ac1361da4425c231d7384891e49e6338e4741464b2 -certbot-nginx==0.30.2 \ - --hash=sha256:7dc785f6f0c0c57b19cea8d98f9ea8feef53945613967b52c9348c81327010e2 \ - --hash=sha256:6ba4dd772d0c7cdfb3383ca325b35639e01ac9e142e4baa6445cd85c7fb59552 +certbot==0.31.0 \ + --hash=sha256:1a1b4b2675daf5266cc2cf2a44ded44de1d83e9541ffa078913c0e4c3231a1c4 \ + --hash=sha256:0c3196f80a102c0f9d82d566ba859efe3b70e9ed4670520224c844fafd930473 +acme==0.31.0 \ + --hash=sha256:a0c851f6b7845a0faa3a47a3e871440eed9ec11b4ab949de0dc4a0fb1201cd24 \ + --hash=sha256:7e5c2d01986e0f34ca08fee58981892704c82c48435dcd3592b424c312d8b2bf +certbot-apache==0.31.0 \ + --hash=sha256:740bb55dd71723a21eebabb16e6ee5d8883f8b8f8cf6956dd1d4873e0cccae21 \ + --hash=sha256:cc4b840b2a439a63e2dce809272c3c3cd4b1aeefc4053cd188935135be137edd +certbot-nginx==0.31.0 \ + --hash=sha256:7a1ffda9d93dc7c2aaf89452ce190250de8932e624d31ebba8e4fa7d950025c5 \ + --hash=sha256:d450d75650384f74baccb7673c89e2f52468afa478ed354eb6d4b99aa33bf865 diff --git a/setup.py b/setup.py index 9e6af2d4f..14fef37f3 100644 --- a/setup.py +++ b/setup.py @@ -38,7 +38,9 @@ install_requires = [ 'ConfigArgParse>=0.9.3', 'configobj', 'cryptography>=1.2.3', # load_pem_x509_certificate - 'josepy', + # 1.1.0+ is required to avoid the warnings described at + # https://github.com/certbot/josepy/issues/13. + 'josepy>=1.1.0', 'mock', 'parsedatetime>=1.3', # Calendar.parseDT 'pyrfc3339', diff --git a/tests/letstest/scripts/test_leauto_upgrades.sh b/tests/letstest/scripts/test_leauto_upgrades.sh index 0c2b374f2..e08a0710c 100755 --- a/tests/letstest/scripts/test_leauto_upgrades.sh +++ b/tests/letstest/scripts/test_leauto_upgrades.sh @@ -23,7 +23,7 @@ fi # started failing on newer distros with newer versions of OpenSSL. INITIAL_VERSION="0.17.0" git checkout -f "v$INITIAL_VERSION" letsencrypt-auto -if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | grep "$INITIAL_VERSION" ; then +if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo initial installation appeared to fail exit 1 fi @@ -85,7 +85,7 @@ if [ $(python -V 2>&1 | cut -d" " -f 2 | cut -d. -f1,2 | sed 's/\.//') -eq 26 ]; fi # Create a 2nd venv at the new path to ensure we properly handle this case export VENV_PATH="/opt/eff.org/certbot/venv" - if ! sudo -E ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | grep "$INITIAL_VERSION" ; then + if ! sudo -E ./letsencrypt-auto -v --debug --version --no-self-upgrade 2>&1 | tail -n1 | grep "^certbot $INITIAL_VERSION$" ; then echo second installation appeared to fail exit 1 fi @@ -98,7 +98,7 @@ if ./letsencrypt-auto -v --debug --version | grep "WARNING: couldn't find Python fi EXPECTED_VERSION=$(grep -m1 LE_AUTO_VERSION certbot-auto | cut -d\" -f2) -if ! /opt/eff.org/certbot/venv/bin/letsencrypt --version 2>&1 | grep "$EXPECTED_VERSION" ; then +if ! /opt/eff.org/certbot/venv/bin/letsencrypt --version 2>&1 | tail -n1 | grep "^certbot $EXPECTED_VERSION$" ; then echo upgrade appeared to fail exit 1 fi diff --git a/tests/letstest/scripts/test_sdists.sh b/tests/letstest/scripts/test_sdists.sh index 0b9a91ffd..260a0acfb 100755 --- a/tests/letstest/scripts/test_sdists.sh +++ b/tests/letstest/scripts/test_sdists.sh @@ -12,6 +12,8 @@ export VENV_ARGS="-p $PYTHON" # setup venv tools/_venv_common.py --requirement letsencrypt-auto-source/pieces/dependency-requirements.txt . ./venv/bin/activate +# pytest is needed to run tests on some of our packages so we install a pinned version here. +tools/pip_install.py pytest # build sdists for pkg_dir in acme . $PLUGINS; do diff --git a/tests/letstest/scripts/test_tests.sh b/tests/letstest/scripts/test_tests.sh index e6ab836b8..d5fd6e14a 100755 --- a/tests/letstest/scripts/test_tests.sh +++ b/tests/letstest/scripts/test_tests.sh @@ -1,18 +1,20 @@ #!/bin/sh -xe -LE_AUTO="letsencrypt/letsencrypt-auto-source/letsencrypt-auto" +REPO_ROOT="letsencrypt" +LE_AUTO="$REPO_ROOT/letsencrypt-auto-source/letsencrypt-auto" LE_AUTO="$LE_AUTO --debug --no-self-upgrade --non-interactive" MODULES="acme certbot certbot_apache certbot_nginx" +PIP_INSTALL="$REPO_ROOT/tools/pip_install.py" VENV_NAME=venv # *-auto respects VENV_PATH $LE_AUTO --os-packages-only LE_AUTO_SUDO="" VENV_PATH="$VENV_NAME" $LE_AUTO --no-bootstrap --version . $VENV_NAME/bin/activate +"$PIP_INSTALL" pytest # change to an empty directory to ensure CWD doesn't affect tests cd $(mktemp -d) -pip install pytest==3.2.5 for module in $MODULES ; do echo testing $module diff --git a/tests/pebble-fetch.sh b/tests/pebble-fetch.sh index b0ba08961..6b562eec2 100755 --- a/tests/pebble-fetch.sh +++ b/tests/pebble-fetch.sh @@ -2,7 +2,7 @@ # Download and run Pebble instance for integration testing set -xe -PEBBLE_VERSION=2018-11-02 +PEBBLE_VERSION=v1.0.1 # We reuse the same GOPATH-style directory than for Boulder. # Pebble does not need it, but it will make the installation consistent with Boulder's one. @@ -13,15 +13,32 @@ mkdir -p ${PEBBLEPATH} cat << UNLIKELY_EOF > "$PEBBLEPATH/docker-compose.yml" version: '3' - services: - pebble: - image: letsencrypt/pebble:${PEBBLE_VERSION} - command: pebble -strict ${PEBBLE_STRICT:-false} -dnsserver 10.77.77.1 - ports: - - 14000:14000 - environment: + pebble: + image: letsencrypt/pebble:${PEBBLE_VERSION} + command: pebble -dnsserver 10.30.50.3:8053 + environment: - PEBBLE_VA_NOSLEEP=1 + ports: + - 14000:14000 + networks: + acmenet: + ipv4_address: 10.30.50.2 + challtestsrv: + image: letsencrypt/pebble-challtestsrv:${PEBBLE_VERSION} + command: pebble-challtestsrv -defaultIPv6 "" -defaultIPv4 10.30.50.1 + ports: + - 8055:8055 + networks: + acmenet: + ipv4_address: 10.30.50.3 +networks: + acmenet: + driver: bridge + ipam: + driver: default + config: + - subnet: 10.30.50.0/24 UNLIKELY_EOF docker-compose -f "$PEBBLEPATH/docker-compose.yml" up -d pebble diff --git a/tox.ini b/tox.ini index b66e330da..b386ebc86 100644 --- a/tox.ini +++ b/tox.ini @@ -166,7 +166,6 @@ passenv = HOME GOPATH PEBBLEPATH - PEBBLE_STRICT setenv = SERVER=https://localhost:14000/dir