Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/certbot/certbot.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorPeter Eckersley <pde@eff.org>2015-10-08 21:32:19 +0300
committerPeter Eckersley <pde@eff.org>2015-10-08 21:32:19 +0300
commit53d532cfe39873d5fb0700e61822b7c47eb00c85 (patch)
tree34e99b656442f4e6277ce50f0ff8eab2ebd38869
parent0ecfec56e26f40333879d17ec8755ae75b7798f2 (diff)
parent52a45d158ab2dbc01956b4b772ce5884c1297931 (diff)
Merge branch 'master' of ssh://github.com/letsencrypt/letsencrypt
-rw-r--r--MANIFEST.in1
-rw-r--r--acme/acme/jose/interfaces.py2
-rw-r--r--acme/acme/jose/interfaces_test.py2
-rw-r--r--acme/acme/jose/jwk.py41
-rw-r--r--acme/acme/jose/jwk_test.py23
-rw-r--r--acme/setup.py10
-rwxr-xr-xbootstrap/archlinux.sh15
-rw-r--r--bootstrap/dev/README1
-rwxr-xr-xbootstrap/dev/_venv_common.sh25
-rwxr-xr-xbootstrap/dev/venv.sh13
-rwxr-xr-xbootstrap/dev/venv3.sh8
-rw-r--r--docs/conf.py2
-rw-r--r--docs/contributing.rst47
-rw-r--r--docs/using.rst6
-rw-r--r--letsencrypt-apache/setup.py8
-rw-r--r--letsencrypt-compatibility-test/MANIFEST.in3
-rw-r--r--letsencrypt-compatibility-test/setup.py8
-rw-r--r--letsencrypt-nginx/setup.py8
-rw-r--r--letsencrypt/cli.py27
-rw-r--r--letsencrypt/client.py14
-rw-r--r--letsencrypt/crypto_util.py19
-rw-r--r--letsencrypt/interfaces.py2
-rw-r--r--letsencrypt/plugins/manual.py34
-rw-r--r--letsencrypt/plugins/manual_test.py12
-rw-r--r--letsencrypt/storage.py38
-rw-r--r--letsencrypt/tests/cli_test.py2
-rw-r--r--letsencrypt/tests/client_test.py15
-rw-r--r--letsencrypt/tests/renewer_test.py48
-rw-r--r--setup.py10
-rwxr-xr-xtests/boulder-integration.sh1
-rwxr-xr-xtests/mac-bootstrap.sh26
-rwxr-xr-xtools/dev-release.sh11
-rwxr-xr-xtox.cover.sh2
33 files changed, 321 insertions, 163 deletions
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 *
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
diff --git a/acme/acme/jose/jwk.py b/acme/acme/jose/jwk.py
index 7a976f189..74fa72319 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.
@@ -60,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):
@@ -81,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
@@ -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(
@@ -204,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)
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(
diff --git a/acme/setup.py b/acme/setup.py
index 2a3a123c5..6448b7fe9 100644
--- a/acme/setup.py
+++ b/acme/setup.py
@@ -10,7 +10,6 @@ install_requires = [
# load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8)
'cryptography>=0.8',
- 'mock<1.1.0', # py26
'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304)
'pyasn1', # urllib3 InsecurePlatformWarning (#304)
# Connection.set_tlsext_host_name (>=0.13), X509Req.get_extensions (>=0.15)
@@ -25,8 +24,13 @@ install_requires = [
# env markers in extras_require cause problems with older pip: #517
if sys.version_info < (2, 7):
- # only some distros recognize stdlib argparse as already satisfying
- install_requires.append('argparse')
+ install_requires.extend([
+ # only some distros recognize stdlib argparse as already satisfying
+ 'argparse',
+ 'mock<1.1.0',
+ ])
+else:
+ install_requires.append('mock')
testing_extras = [
'nose',
diff --git a/bootstrap/archlinux.sh b/bootstrap/archlinux.sh
index fbe0987fe..6de7c23d4 100755
--- a/bootstrap/archlinux.sh
+++ b/bootstrap/archlinux.sh
@@ -1,2 +1,15 @@
#!/bin/sh
-pacman -S git python2 python2-virtualenv gcc dialog augeas openssl libffi ca-certificates \ No newline at end of file
+
+# "python-virtualenv" is Python3, but "python2-virtualenv" provides
+# only "virtualenv2" binary, not "virtualenv" necessary in
+# ./bootstrap/dev/_common_venv.sh
+pacman -S \
+ git \
+ python2 \
+ python-virtualenv \
+ gcc \
+ dialog \
+ augeas \
+ openssl \
+ libffi \
+ ca-certificates \
diff --git a/bootstrap/dev/README b/bootstrap/dev/README
new file mode 100644
index 000000000..759496187
--- /dev/null
+++ b/bootstrap/dev/README
@@ -0,0 +1 @@
+This directory contains developer setup.
diff --git a/bootstrap/dev/_venv_common.sh b/bootstrap/dev/_venv_common.sh
new file mode 100755
index 000000000..2d84dc39b
--- /dev/null
+++ b/bootstrap/dev/_venv_common.sh
@@ -0,0 +1,25 @@
+#!/bin/sh -xe
+
+VENV_NAME=${VENV_NAME:-venv}
+
+# .egg-info directories tend to cause bizzaire problems (e.g. `pip -e
+# .` might unexpectedly install letshelp-letsencrypt only, in case
+# `python letshelp-letsencrypt/setup.py build` has been called
+# earlier)
+rm -rf *.egg-info
+
+# virtualenv setup is NOT idempotent: shutil.Error:
+# `/home/jakub/dev/letsencrypt/letsencrypt/venv/bin/python2` and
+# `venv/bin/python2` are the same file
+mv $VENV_NAME "$VENV_NAME.$(date +%s).bak" || true
+virtualenv --no-site-packages $VENV_NAME $VENV_ARGS
+. ./$VENV_NAME/bin/activate
+
+# Separately install setuptools and pip to make sure following
+# invocations use latest
+pip install -U setuptools
+pip install -U pip
+pip install "$@"
+
+echo "Please run the following command to activate developer environment:"
+echo "source $VENV_NAME/bin/activate"
diff --git a/bootstrap/dev/venv.sh b/bootstrap/dev/venv.sh
new file mode 100755
index 000000000..d6cf95bb5
--- /dev/null
+++ b/bootstrap/dev/venv.sh
@@ -0,0 +1,13 @@
+#!/bin/sh -xe
+# Developer virtualenv setup for Let's Encrypt client
+
+export VENV_ARGS="--python python2"
+
+./bootstrap/dev/_venv_common.sh \
+ -r requirements.txt \
+ -e acme[testing] \
+ -e .[dev,docs,testing] \
+ -e letsencrypt-apache \
+ -e letsencrypt-nginx \
+ -e letshelp-letsencrypt \
+ -e letsencrypt-compatibility-test
diff --git a/bootstrap/dev/venv3.sh b/bootstrap/dev/venv3.sh
new file mode 100755
index 000000000..ccffffb83
--- /dev/null
+++ b/bootstrap/dev/venv3.sh
@@ -0,0 +1,8 @@
+#!/bin/sh -xe
+# Developer Python3 virtualenv setup for Let's Encrypt
+
+export VENV_NAME="${VENV_NAME:-venv3}"
+export VENV_ARGS="--python python3"
+
+./bootstrap/dev/_venv_common.sh \
+ -e acme[testing] \
diff --git a/docs/conf.py b/docs/conf.py
index 2b4b2cd43..e2b360a6e 100644
--- a/docs/conf.py
+++ b/docs/conf.py
@@ -30,7 +30,7 @@ here = os.path.abspath(os.path.dirname(__file__))
# read version number (and other metadata) from package init
init_fn = os.path.join(here, '..', 'letsencrypt', '__init__.py')
with codecs.open(init_fn, encoding='utf8') as fd:
- meta = dict(re.findall(r"""__([a-z]+)__ = "([^"]+)""", fd.read()))
+ meta = dict(re.findall(r"""__([a-z]+)__ = '([^']+)""", fd.read()))
# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
diff --git a/docs/contributing.rst b/docs/contributing.rst
index c6443e3b2..3277d321a 100644
--- a/docs/contributing.rst
+++ b/docs/contributing.rst
@@ -7,38 +7,37 @@ Contributing
Hacking
=======
-Start by :doc:`installing dependencies and setting up Let's Encrypt
-<using>`.
+All changes in your pull request **must** have 100% unit test coverage, pass
+our `integration`_ tests, **and** be compliant with the
+:ref:`coding style <coding-style>`.
-When you're done activate the virtualenv:
-.. code-block:: shell
+Bootstrap
+---------
- source ./venv/bin/activate
+Start by :ref:`installing Let's Encrypt prerequisites
+<prerequisites>`. Then run:
-This step should prepend you prompt with ``(venv)`` and save you from
-typing ``./venv/bin/...``. It is also required to run some of the
-`testing`_ tools. Virtualenv can be disabled at any time by typing
-``deactivate``. More information can be found in `virtualenv
-documentation`_.
+.. code-block:: shell
-Install the development packages:
+ ./bootstrap/dev/venv.sh
-.. code-block:: shell
+Activate the virtualenv:
- pip install -r requirements.txt -e acme -e .[dev,docs,testing] -e letsencrypt-apache -e letsencrypt-nginx -e letshelp-letsencrypt
+.. code-block:: shell
-.. note:: `-e` (short for `--editable`) turns on *editable mode* in
- which any source code changes in the current working
- directory are "live" and no further `pip install ...`
- invocations are necessary while developing.
+ source ./$VENV_NAME/bin/activate
- This is roughly equivalent to `python setup.py develop`. For
- more info see `man pip`.
+This step should prepend you prompt with ``($VENV_NAME)`` and save you
+from typing ``./$VENV_NAME/bin/...``. It is also required to run some
+of the `testing`_ tools. Virtualenv can be disabled at any time by
+typing ``deactivate``. More information can be found in `virtualenv
+documentation`_.
-The code base, including your pull requests, **must** have 100% unit
-test coverage, pass our `integration`_ tests **and** be compliant with
-the :ref:`coding style <coding-style>`.
+Note that packages are installed in so called *editable mode*, in
+which any source code changes in the current working directory are
+"live" and no further ``./bootstrap/dev/venv.sh`` or ``pip install
+...`` invocations are necessary while developing.
.. _`virtualenv documentation`: https://virtualenv.pypa.io
@@ -67,8 +66,10 @@ The following tools are there to help you:
Integration
~~~~~~~~~~~
+Mac OS X users: Run `./tests/mac-bootstrap.sh` instead of `boulder-start.sh` to
+install dependencies, configure the environment, and start boulder.
-First, install `Go`_ 1.5, libtool-ltdl, mariadb-server and
+Otherwise, install `Go`_ 1.5, libtool-ltdl, mariadb-server and
rabbitmq-server and then start Boulder_, an ACME CA server::
./tests/boulder-start.sh
diff --git a/docs/using.rst b/docs/using.rst
index cfce29bae..9611f37c0 100644
--- a/docs/using.rst
+++ b/docs/using.rst
@@ -42,6 +42,8 @@ above method instead.
https://github.com/letsencrypt/letsencrypt/archive/master.zip
+.. _prerequisites:
+
Prerequisites
=============
@@ -121,11 +123,13 @@ Installation
============
.. "pip install acme" doesn't search for "acme" in cwd, just like "pip
- install -e acme" does
+ install -e acme" does; `-U setuptools pip` necessary for #722
.. code-block:: shell
virtualenv --no-site-packages -p python2 venv
+ ./venv/bin/pip install -U setuptools
+ ./venv/bin/pip install -U pip
./venv/bin/pip install -r requirements.txt acme/ . letsencrypt-apache/ letsencrypt-nginx/
.. warning:: Please do **not** use ``python setup.py install``. Please
diff --git a/letsencrypt-apache/setup.py b/letsencrypt-apache/setup.py
index ee1457131..626e700b2 100644
--- a/letsencrypt-apache/setup.py
+++ b/letsencrypt-apache/setup.py
@@ -1,3 +1,5 @@
+import sys
+
from setuptools import setup
from setuptools import find_packages
@@ -7,13 +9,17 @@ version = '0.1.0.dev0'
install_requires = [
'acme=={0}'.format(version),
'letsencrypt=={0}'.format(version),
- 'mock<1.1.0', # py26
'python-augeas',
'setuptools', # pkg_resources
'zope.component',
'zope.interface',
]
+if sys.version_info < (2, 7):
+ install_requires.append('mock<1.1.0')
+else:
+ install_requires.append('mock')
+
setup(
name='letsencrypt-apache',
version=version,
diff --git a/letsencrypt-compatibility-test/MANIFEST.in b/letsencrypt-compatibility-test/MANIFEST.in
index 52bbb3c65..4d346a5d0 100644
--- a/letsencrypt-compatibility-test/MANIFEST.in
+++ b/letsencrypt-compatibility-test/MANIFEST.in
@@ -1,3 +1,6 @@
include LICENSE.txt
include README.rst
+include letsencrypt_compatibility_test/configurators/apache/a2enmod.sh
+include letsencrypt_compatibility_test/configurators/apache/a2dismod.sh
+include letsencrypt_compatibility_test/configurators/apache/Dockerfile
recursive-include letsencrypt_compatibility_test/testdata *
diff --git a/letsencrypt-compatibility-test/setup.py b/letsencrypt-compatibility-test/setup.py
index 745b49bb5..2e70fd1d7 100644
--- a/letsencrypt-compatibility-test/setup.py
+++ b/letsencrypt-compatibility-test/setup.py
@@ -1,3 +1,5 @@
+import sys
+
from setuptools import setup
from setuptools import find_packages
@@ -9,10 +11,14 @@ install_requires = [
'letsencrypt-apache=={0}'.format(version),
'letsencrypt-nginx=={0}'.format(version),
'docker-py',
- 'mock<1.1.0', # py26
'zope.interface',
]
+if sys.version_info < (2, 7):
+ install_requires.append('mock<1.1.0')
+else:
+ install_requires.append('mock')
+
setup(
name='letsencrypt-compatibility-test',
version=version,
diff --git a/letsencrypt-nginx/setup.py b/letsencrypt-nginx/setup.py
index 4e770c8cb..a37b8222b 100644
--- a/letsencrypt-nginx/setup.py
+++ b/letsencrypt-nginx/setup.py
@@ -1,3 +1,5 @@
+import sys
+
from setuptools import setup
from setuptools import find_packages
@@ -7,13 +9,17 @@ version = '0.1.0.dev0'
install_requires = [
'acme=={0}'.format(version),
'letsencrypt=={0}'.format(version),
- 'mock<1.1.0', # py26
'PyOpenSSL',
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
'setuptools', # pkg_resources
'zope.interface',
]
+if sys.version_info < (2, 7):
+ install_requires.append('mock<1.1.0')
+else:
+ install_requires.append('mock')
+
setup(
name='letsencrypt-nginx',
version=version,
diff --git a/letsencrypt/cli.py b/letsencrypt/cli.py
index 73dd24bdb..64cba508d 100644
--- a/letsencrypt/cli.py
+++ b/letsencrypt/cli.py
@@ -702,8 +702,6 @@ def create_parser(plugins, args):
help=config_help("dvsni_port"))
helpful.add("testing", "--simple-http-port", type=int,
help=config_help("simple_http_port"))
- helpful.add("testing", "--no-simple-http-tls", action="store_true",
- help=config_help("no_simple_http_tls"))
helpful.add_group(
"security", description="Security parameters & server settings")
@@ -729,11 +727,13 @@ def create_parser(plugins, args):
return helpful.parser, helpful.args
+
# For now unfortunately this constant just needs to match the code below;
# there isn't an elegant way to autogenerate it in time.
VERBS = ["run", "auth", "install", "revoke", "rollback", "config_changes", "plugins"]
HELP_TOPICS = ["all", "security", "paths", "automation", "testing"] + VERBS
+
def _create_subparsers(helpful):
subparsers = helpful.parser.add_subparsers(metavar="SUBCOMMAND")
@@ -741,7 +741,7 @@ def _create_subparsers(helpful):
if name == "plugins":
func = plugins_cmd
else:
- func = eval(name) # pylint: disable=eval-used
+ func = eval(name) # pylint: disable=eval-used
h = func.__doc__.splitlines()[0]
subparser = subparsers.add_parser(name, help=h, description=func.__doc__)
subparser.set_defaults(func=func)
@@ -762,22 +762,23 @@ def _create_subparsers(helpful):
helpful.add_group("plugins", description="Plugin options")
helpful.add("auth",
- "--csr", type=read_file, help="Path to a Certificate Signing Request (CSR) in DER format.")
+ "--csr", type=read_file,
+ help="Path to a Certificate Signing Request (CSR) in DER format.")
helpful.add("rollback",
- "--checkpoints", type=int, metavar="N",
- default=flag_default("rollback_checkpoints"),
- help="Revert configuration N number of checkpoints.")
+ "--checkpoints", type=int, metavar="N",
+ default=flag_default("rollback_checkpoints"),
+ help="Revert configuration N number of checkpoints.")
helpful.add("plugins",
- "--init", action="store_true", help="Initialize plugins.")
+ "--init", action="store_true", help="Initialize plugins.")
helpful.add("plugins",
- "--prepare", action="store_true", help="Initialize and prepare plugins.")
+ "--prepare", action="store_true", help="Initialize and prepare plugins.")
helpful.add("plugins",
- "--authenticators", action="append_const", dest="ifaces",
- const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.")
+ "--authenticators", action="append_const", dest="ifaces",
+ const=interfaces.IAuthenticator, help="Limit to authenticator plugins only.")
helpful.add("plugins",
- "--installers", action="append_const", dest="ifaces",
- const=interfaces.IInstaller, help="Limit to installer plugins only.")
+ "--installers", action="append_const", dest="ifaces",
+ const=interfaces.IInstaller, help="Limit to installer plugins only.")
def _paths_parser(helpful):
diff --git a/letsencrypt/client.py b/letsencrypt/client.py
index c82131af3..7a78add38 100644
--- a/letsencrypt/client.py
+++ b/letsencrypt/client.py
@@ -268,19 +268,15 @@ class Client(object):
:param .RenewableCert cert: Newly issued certificate
"""
- if ("autorenew" not in cert.configuration or
- cert.configuration.as_bool("autorenew")):
- if ("autodeploy" not in cert.configuration or
- cert.configuration.as_bool("autodeploy")):
+ if cert.autorenewal_is_enabled():
+ if cert.autodeployment_is_enabled():
msg = "Automatic renewal and deployment has "
else:
msg = "Automatic renewal but not automatic deployment has "
+ elif cert.autodeployment_is_enabled():
+ msg = "Automatic deployment but not automatic renewal has "
else:
- if ("autodeploy" not in cert.configuration or
- cert.configuration.as_bool("autodeploy")):
- msg = "Automatic deployment but not automatic renewal has "
- else:
- msg = "Automatic renewal and deployment has not "
+ msg = "Automatic renewal and deployment has not "
msg += ("been enabled for your certificate. These settings can be "
"configured in the directories under {0}.").format(
diff --git a/letsencrypt/crypto_util.py b/letsencrypt/crypto_util.py
index 777b4d006..61aa8b0db 100644
--- a/letsencrypt/crypto_util.py
+++ b/letsencrypt/crypto_util.py
@@ -4,7 +4,6 @@
is capable of handling the signatures.
"""
-import datetime
import logging
import os
@@ -258,24 +257,6 @@ def get_sans_from_csr(csr, typ=OpenSSL.crypto.FILETYPE_PEM):
csr, OpenSSL.crypto.load_certificate_request, typ)
-def asn1_generalizedtime_to_dt(timestamp):
- """Convert ASN.1 GENERALIZEDTIME to datetime.
-
- Useful for deserialization of `OpenSSL.crypto.X509.get_notAfter` and
- `OpenSSL.crypto.X509.get_notAfter` outputs.
-
- .. todo:: This function support only one format: `%Y%m%d%H%M%SZ`.
- Implement remaining two.
-
- """
- return datetime.datetime.strptime(timestamp, '%Y%m%d%H%M%SZ')
-
-
-def pyopenssl_x509_name_as_text(x509name):
- """Convert `OpenSSL.crypto.X509Name` to text."""
- return "/".join("{0}={1}" for key, value in x509name.get_components())
-
-
def dump_pyopenssl_chain(chain, filetype=OpenSSL.crypto.FILETYPE_PEM):
"""Dump certificate chain into a bundle.
diff --git a/letsencrypt/interfaces.py b/letsencrypt/interfaces.py
index 1f51645ab..5e82d61aa 100644
--- a/letsencrypt/interfaces.py
+++ b/letsencrypt/interfaces.py
@@ -223,8 +223,6 @@ class IConfig(zope.interface.Interface):
"Port number to perform DVSNI challenge. "
"Boulder in testing mode defaults to 5001.")
- no_simple_http_tls = zope.interface.Attribute(
- "Do not use TLS when solving SimpleHTTP challenges.")
simple_http_port = zope.interface.Attribute(
"Port used in the SimpleHttp challenge.")
diff --git a/letsencrypt/plugins/manual.py b/letsencrypt/plugins/manual.py
index 3f7276725..9d5ef87e9 100644
--- a/letsencrypt/plugins/manual.py
+++ b/letsencrypt/plugins/manual.py
@@ -53,7 +53,7 @@ command on the target server (as root):
# served and makes it more obvious that Python command will serve
# anything recursively under the cwd
- HTTP_TEMPLATE = """\
+ CMD_TEMPLATE = """\
mkdir -p {root}/public_html/{response.URI_ROOT_PATH}
cd {root}/public_html
echo -n {validation} > {response.URI_ROOT_PATH}/{encoded_token}
@@ -63,33 +63,10 @@ $(command -v python2 || command -v python2.7 || command -v python2.6) -c \\
SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map = {{'': '{ct}'}}; \\
s = BaseHTTPServer.HTTPServer(('', {port}), SimpleHTTPServer.SimpleHTTPRequestHandler); \\
s.serve_forever()" """
- """Non-TLS command template."""
-
- # https://www.piware.de/2011/01/creating-an-https-server-in-python/
- HTTPS_TEMPLATE = """\
-mkdir -p {root}/public_html/{response.URI_ROOT_PATH}
-cd {root}/public_html
-echo -n {validation} > {response.URI_ROOT_PATH}/{encoded_token}
-# run only once per server:
-openssl req -new -newkey rsa:4096 -subj "/" -days 1 -nodes -x509 -keyout ../key.pem -out ../cert.pem
-$(command -v python2 || command -v python2.7 || command -v python2.6) -c \\
-"import BaseHTTPServer, SimpleHTTPServer, ssl; \\
-SimpleHTTPServer.SimpleHTTPRequestHandler.extensions_map = {{'': '{ct}'}}; \\
-s = BaseHTTPServer.HTTPServer(('', {port}), SimpleHTTPServer.SimpleHTTPRequestHandler); \\
-s.socket = ssl.wrap_socket(s.socket, keyfile='../key.pem', certfile='../cert.pem'); \\
-s.serve_forever()" """
- """TLS command template.
-
- According to the ACME specification, "the ACME server MUST ignore
- the certificate provided by the HTTPS server", so the first command
- generates temporary self-signed certificate.
-
- """
+ """Command template."""
def __init__(self, *args, **kwargs):
super(Authenticator, self).__init__(*args, **kwargs)
- self.template = (self.HTTP_TEMPLATE if self.config.no_simple_http_tls
- else self.HTTPS_TEMPLATE)
self._root = (tempfile.mkdtemp() if self.conf("test-mode")
else "/tmp/letsencrypt")
self._httpd = None
@@ -97,8 +74,7 @@ s.serve_forever()" """
@classmethod
def add_parser_arguments(cls, add):
add("test-mode", action="store_true",
- help="Test mode. Executes the manual command in subprocess. "
- "Requires openssl to be installed unless --no-simple-http-tls.")
+ help="Test mode. Executes the manual command in subprocess.")
def prepare(self): # pylint: disable=missing-docstring,no-self-use
pass # pragma: no cover
@@ -142,11 +118,11 @@ binary for temporary key/certificate generation.""".replace("\n", "")
# users, but will not work if multiple domains point at the
# same server: default command doesn't support virtual hosts
response, validation = achall.gen_response_and_validation(
- tls=(not self.config.no_simple_http_tls))
+ tls=False) # SimpleHTTP TLS is dead: ietf-wg-acme/acme#7
port = (response.port if self.config.simple_http_port is None
else int(self.config.simple_http_port))
- command = self.template.format(
+ command = self.CMD_TEMPLATE.format(
root=self._root, achall=achall, response=response,
validation=pipes.quote(validation.json_dumps()),
encoded_token=achall.chall.encode("token"),
diff --git a/letsencrypt/plugins/manual_test.py b/letsencrypt/plugins/manual_test.py
index cfe47b833..8cfff1cc5 100644
--- a/letsencrypt/plugins/manual_test.py
+++ b/letsencrypt/plugins/manual_test.py
@@ -23,15 +23,13 @@ class AuthenticatorTest(unittest.TestCase):
def setUp(self):
from letsencrypt.plugins.manual import Authenticator
self.config = mock.MagicMock(
- no_simple_http_tls=True, simple_http_port=4430,
- manual_test_mode=False)
+ simple_http_port=8080, manual_test_mode=False)
self.auth = Authenticator(config=self.config, name="manual")
self.achalls = [achallenges.SimpleHTTP(
challb=acme_util.SIMPLE_HTTP_P, domain="foo.com", account_key=KEY)]
config_test_mode = mock.MagicMock(
- no_simple_http_tls=True, simple_http_port=4430,
- manual_test_mode=True)
+ simple_http_port=8080, manual_test_mode=True)
self.auth_test_mode = Authenticator(
config=config_test_mode, name="manual")
@@ -55,7 +53,7 @@ class AuthenticatorTest(unittest.TestCase):
self.assertEqual([resp], self.auth.perform(self.achalls))
self.assertEqual(1, mock_raw_input.call_count)
mock_verify.assert_called_with(
- self.achalls[0].challb.chall, "foo.com", KEY.public_key(), 4430)
+ self.achalls[0].challb.chall, "foo.com", KEY.public_key(), 8080)
message = mock_stdout.write.mock_calls[0][1][0]
self.assertTrue(self.achalls[0].chall.encode("token") in message)
@@ -68,7 +66,7 @@ class AuthenticatorTest(unittest.TestCase):
mock_popen.side_effect = OSError
self.assertEqual([False], self.auth_test_mode.perform(self.achalls))
- @mock.patch("letsencrypt.plugins.manual.socket.socket", autospec=True)
+ @mock.patch("letsencrypt.plugins.manual.socket.socket")
@mock.patch("letsencrypt.plugins.manual.time.sleep", autospec=True)
@mock.patch("letsencrypt.plugins.manual.subprocess.Popen", autospec=True)
def test_perform_test_command_run_failure(
@@ -78,7 +76,7 @@ class AuthenticatorTest(unittest.TestCase):
self.assertRaises(
errors.Error, self.auth_test_mode.perform, self.achalls)
- @mock.patch("letsencrypt.plugins.manual.socket.socket", autospec=True)
+ @mock.patch("letsencrypt.plugins.manual.socket.socket")
@mock.patch("letsencrypt.plugins.manual.time.sleep", autospec=True)
@mock.patch("acme.challenges.SimpleHTTPResponse.simple_verify",
autospec=True)
diff --git a/letsencrypt/storage.py b/letsencrypt/storage.py
index be270a762..8a0f4829e 100644
--- a/letsencrypt/storage.py
+++ b/letsencrypt/storage.py
@@ -129,7 +129,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
self.chain = self.configuration["chain"]
self.fullchain = self.configuration["fullchain"]
- def consistent(self):
+ def _consistent(self):
"""Are the files associated with this lineage self-consistent?
:returns: Whether the files stored in connection with this
@@ -187,7 +187,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
# for x in ALL_FOUR))) == 1
return True
- def fix(self):
+ def _fix(self):
"""Attempt to fix defects or inconsistencies in this lineage.
.. todo:: Currently unimplemented.
@@ -347,7 +347,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
smallest_current = min(self.current_version(x) for x in ALL_FOUR)
return smallest_current < self.latest_common_version()
- def update_link_to(self, kind, version):
+ def _update_link_to(self, kind, version):
"""Make the specified item point at the specified version.
(Note that this method doesn't verify that the specified version
@@ -379,7 +379,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
:param int version: the desired version"""
for kind in ALL_FOUR:
- self.update_link_to(kind, version)
+ self._update_link_to(kind, version)
def _notafterbefore(self, method, version):
"""Internal helper function for finding notbefore/notafter."""
@@ -439,6 +439,18 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
with open(target) as f:
return crypto_util.get_sans_from_cert(f.read())
+ def autodeployment_is_enabled(self):
+ """Is automatic deployment enabled for this cert?
+
+ If autodeploy is not specified, defaults to True.
+
+ :returns: True if automatic deployment is enabled
+ :rtype: bool
+
+ """
+ return ("autodeploy" not in self.configuration or
+ self.configuration.as_bool("autodeploy"))
+
def should_autodeploy(self):
"""Should this lineage now automatically deploy a newer version?
@@ -453,8 +465,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
:rtype: bool
"""
- if ("autodeploy" not in self.configuration or
- self.configuration.as_bool("autodeploy")):
+ if self.autodeployment_is_enabled():
if self.has_pending_deployment():
interval = self.configuration.get("deploy_before_expiry",
"5 days")
@@ -488,6 +499,18 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
# certificate is not revoked).
return False
+ def autorenewal_is_enabled(self):
+ """Is automatic renewal enabled for this cert?
+
+ If autorenew is not specified, defaults to True.
+
+ :returns: True if automatic renewal is enabled
+ :rtype: bool
+
+ """
+ return ("autorenew" not in self.configuration or
+ self.configuration.as_bool("autorenew"))
+
def should_autorenew(self):
"""Should we now try to autorenew the most recent cert version?
@@ -504,8 +527,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
:rtype: bool
"""
- if ("autorenew" not in self.configuration or
- self.configuration.as_bool("autorenew")):
+ if self.autorenewal_is_enabled():
# Consider whether to attempt to autorenew this cert now
# Renewals on the basis of revocation
diff --git a/letsencrypt/tests/cli_test.py b/letsencrypt/tests/cli_test.py
index 0a92aba62..d0fae370d 100644
--- a/letsencrypt/tests/cli_test.py
+++ b/letsencrypt/tests/cli_test.py
@@ -57,7 +57,6 @@ class CLITest(unittest.TestCase):
ret = cli.main(args)
return ret, None, stderr, client
-
def test_no_flags(self):
with mock.patch('letsencrypt.cli.run') as mock_run:
self._call([])
@@ -91,7 +90,6 @@ class CLITest(unittest.TestCase):
from letsencrypt import cli
self.assertTrue(cli.USAGE in out)
-
def test_rollback(self):
_, _, _, client = self._call(['rollback'])
self.assertEqual(1, client.rollback.call_count)
diff --git a/letsencrypt/tests/client_test.py b/letsencrypt/tests/client_test.py
index 1a232bccb..1e63bdbb6 100644
--- a/letsencrypt/tests/client_test.py
+++ b/letsencrypt/tests/client_test.py
@@ -4,14 +4,12 @@ import shutil
import tempfile
import unittest
-import configobj
import OpenSSL
import mock
from acme import jose
from letsencrypt import account
-from letsencrypt import configuration
from letsencrypt import errors
from letsencrypt import le_util
@@ -120,29 +118,28 @@ class ClientTest(unittest.TestCase):
def test_report_renewal_status(self, mock_zope):
# pylint: disable=protected-access
cert = mock.MagicMock()
- cert.configuration = configobj.ConfigObj()
- cert.cli_config = configuration.RenewerConfiguration(self.config)
+ cert.cli_config.renewal_configs_dir = "/foo/bar/baz"
- cert.configuration["autorenew"] = "True"
- cert.configuration["autodeploy"] = "True"
+ cert.autorenewal_is_enabled.return_value = True
+ cert.autodeployment_is_enabled.return_value = True
self.client._report_renewal_status(cert)
msg = mock_zope().add_message.call_args[0][0]
self.assertTrue("renewal and deployment has been" in msg)
self.assertTrue(cert.cli_config.renewal_configs_dir in msg)
- cert.configuration["autorenew"] = "False"
+ cert.autorenewal_is_enabled.return_value = False
self.client._report_renewal_status(cert)
msg = mock_zope().add_message.call_args[0][0]
self.assertTrue("deployment but not automatic renewal" in msg)
self.assertTrue(cert.cli_config.renewal_configs_dir in msg)
- cert.configuration["autodeploy"] = "False"
+ cert.autodeployment_is_enabled.return_value = False
self.client._report_renewal_status(cert)
msg = mock_zope().add_message.call_args[0][0]
self.assertTrue("renewal and deployment has not" in msg)
self.assertTrue(cert.cli_config.renewal_configs_dir in msg)
- cert.configuration["autorenew"] = "True"
+ cert.autorenewal_is_enabled.return_value = True
self.client._report_renewal_status(cert)
msg = mock_zope().add_message.call_args[0][0]
self.assertTrue("renewal but not automatic deployment" in msg)
diff --git a/letsencrypt/tests/renewer_test.py b/letsencrypt/tests/renewer_test.py
index e67631605..6f115abf9 100644
--- a/letsencrypt/tests/renewer_test.py
+++ b/letsencrypt/tests/renewer_test.py
@@ -123,46 +123,47 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertRaises(
errors.CertStorageError, storage.RenewableCert, config, defaults)
- def test_consistent(self): # pylint: disable=too-many-statements
+ def test_consistent(self):
+ # pylint: disable=too-many-statements,protected-access
oldcert = self.test_rc.cert
self.test_rc.cert = "relative/path"
# Absolute path for item requirement
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
self.test_rc.cert = oldcert
# Items must exist requirement
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
# Items must be symlinks requirements
fill_with_sample_data(self.test_rc)
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
unlink_all(self.test_rc)
# Items must point to desired place if they are relative
for kind in ALL_FOUR:
os.symlink(os.path.join("..", kind + "17.pem"),
getattr(self.test_rc, kind))
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
unlink_all(self.test_rc)
# Items must point to desired place if they are absolute
for kind in ALL_FOUR:
os.symlink(os.path.join(self.tempdir, kind + "17.pem"),
getattr(self.test_rc, kind))
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
unlink_all(self.test_rc)
# Items must point to things that exist
for kind in ALL_FOUR:
os.symlink(os.path.join("..", "..", "archive", "example.org",
kind + "17.pem"),
getattr(self.test_rc, kind))
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
# This version should work
fill_with_sample_data(self.test_rc)
- self.assertTrue(self.test_rc.consistent())
+ self.assertTrue(self.test_rc._consistent())
# Items must point to things that follow the naming convention
os.unlink(self.test_rc.fullchain)
os.symlink(os.path.join("..", "..", "archive", "example.org",
"fullchain_17.pem"), self.test_rc.fullchain)
with open(self.test_rc.fullchain, "w") as f:
f.write("wrongly-named fullchain")
- self.assertFalse(self.test_rc.consistent())
+ self.assertFalse(self.test_rc._consistent())
def test_current_target(self):
# Relative path logic
@@ -259,14 +260,15 @@ class RenewableCertTests(BaseRenewableCertTest):
with open(where, "w") as f:
f.write(kind)
self.assertEqual(ver, self.test_rc.current_version(kind))
- self.test_rc.update_link_to("cert", 3)
- self.test_rc.update_link_to("privkey", 2)
+ # pylint: disable=protected-access
+ self.test_rc._update_link_to("cert", 3)
+ self.test_rc._update_link_to("privkey", 2)
self.assertEqual(3, self.test_rc.current_version("cert"))
self.assertEqual(2, self.test_rc.current_version("privkey"))
self.assertEqual(5, self.test_rc.current_version("chain"))
self.assertEqual(5, self.test_rc.current_version("fullchain"))
# Currently we are allowed to update to a version that doesn't exist
- self.test_rc.update_link_to("chain", 3000)
+ self.test_rc._update_link_to("chain", 3000)
# However, current_version doesn't allow querying the resulting
# version (because it's a broken link).
self.assertEqual(os.path.basename(os.readlink(self.test_rc.chain)),
@@ -405,6 +407,14 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertEqual(self.test_rc.should_autodeploy(), result)
self.assertEqual(self.test_rc.should_autorenew(), result)
+ def test_autodeployment_is_enabled(self):
+ self.assertTrue(self.test_rc.autodeployment_is_enabled())
+ self.test_rc.configuration["autodeploy"] = "1"
+ self.assertTrue(self.test_rc.autodeployment_is_enabled())
+
+ self.test_rc.configuration["autodeploy"] = "0"
+ self.assertFalse(self.test_rc.autodeployment_is_enabled())
+
def test_should_autodeploy(self):
"""Test should_autodeploy() on the basis of reasons other than
expiry time window."""
@@ -425,6 +435,14 @@ class RenewableCertTests(BaseRenewableCertTest):
f.write(kind)
self.assertFalse(self.test_rc.should_autodeploy())
+ def test_autorenewal_is_enabled(self):
+ self.assertTrue(self.test_rc.autorenewal_is_enabled())
+ self.test_rc.configuration["autorenew"] = "1"
+ self.assertTrue(self.test_rc.autorenewal_is_enabled())
+
+ self.test_rc.configuration["autorenew"] = "0"
+ self.assertFalse(self.test_rc.autorenewal_is_enabled())
+
@mock.patch("letsencrypt.storage.RenewableCert.ocsp_revoked")
def test_should_autorenew(self, mock_ocsp):
"""Test should_autorenew on the basis of reasons other than
@@ -507,7 +525,8 @@ class RenewableCertTests(BaseRenewableCertTest):
self.defaults, self.cli_config)
# This consistency check tests most relevant properties about the
# newly created cert lineage.
- self.assertTrue(result.consistent())
+ # pylint: disable=protected-access
+ self.assertTrue(result._consistent())
self.assertTrue(os.path.exists(os.path.join(
self.cli_config.renewal_configs_dir, "the-lineage.com.conf")))
with open(result.fullchain) as f:
@@ -578,9 +597,10 @@ class RenewableCertTests(BaseRenewableCertTest):
self.assertRaises(
errors.CertStorageError,
self.test_rc.newest_available_version, "elephant")
+ # pylint: disable=protected-access
self.assertRaises(
errors.CertStorageError,
- self.test_rc.update_link_to, "elephant", 17)
+ self.test_rc._update_link_to, "elephant", 17)
def test_ocsp_revoked(self):
# XXX: This is currently hardcoded to False due to a lack of an
diff --git a/setup.py b/setup.py
index f3ef07f8d..016dc146e 100644
--- a/setup.py
+++ b/setup.py
@@ -35,7 +35,6 @@ install_requires = [
'ConfigArgParse',
'configobj',
'cryptography>=0.7', # load_pem_x509_certificate
- 'mock<1.1.0', # py26
'parsedatetime',
'psutil>=2.1.0', # net_connections introduced in 2.1.0
'PyOpenSSL',
@@ -50,8 +49,13 @@ install_requires = [
# env markers in extras_require cause problems with older pip: #517
if sys.version_info < (2, 7):
- # only some distros recognize stdlib argparse as already satisfying
- install_requires.append('argparse')
+ install_requires.extend([
+ # only some distros recognize stdlib argparse as already satisfying
+ 'argparse',
+ 'mock<1.1.0',
+ ])
+else:
+ install_requires.append('mock')
dev_extras = [
# Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289
diff --git a/tests/boulder-integration.sh b/tests/boulder-integration.sh
index ed877d136..25db8ba6d 100755
--- a/tests/boulder-integration.sh
+++ b/tests/boulder-integration.sh
@@ -24,7 +24,6 @@ common() {
common --domains le1.wtf auth
common --domains le2.wtf run
common -a manual -d le.wtf auth
-common -a manual -d le.wtf --no-simple-http-tls auth
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
OPENSSL_CNF=examples/openssl.cnf
diff --git a/tests/mac-bootstrap.sh b/tests/mac-bootstrap.sh
new file mode 100755
index 000000000..66036ce56
--- /dev/null
+++ b/tests/mac-bootstrap.sh
@@ -0,0 +1,26 @@
+#!/bin/sh
+
+#Check Homebrew
+if ! hash brew 2>/dev/null; then
+ echo "Homebrew Not Installed\nDownloading..."
+ ruby -e "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/master/install)"
+fi
+
+brew install libtool mariadb rabbitmq coreutils go
+
+mysql.server start
+
+rabbit_pid=`ps | grep rabbitmq | grep -v grep | awk '{ print $1}'`
+if [ -n "$rabbit_pid" ]; then
+ echo "RabbitMQ already running"
+else
+ rabbitmq-server &
+fi
+
+hosts_entry=`cat /etc/hosts | grep "127.0.0.1 le.wtf"`
+if [ -z "$hosts_entry" ]; then
+ echo "Adding hosts entry for le.wtf..."
+ sudo sh -c "echo 127.0.0.1 le.wtf >> /etc/hosts"
+fi
+
+./tests/boulder-start.sh
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/*/*"
diff --git a/tox.cover.sh b/tox.cover.sh
index edfd9b81a..8418de9a8 100755
--- a/tox.cover.sh
+++ b/tox.cover.sh
@@ -16,7 +16,7 @@ fi
cover () {
if [ "$1" = "letsencrypt" ]; then
- min=97
+ min=98
elif [ "$1" = "acme" ]; then
min=100
elif [ "$1" = "letsencrypt_apache" ]; then