Merge remote-tracking branch 'github/letsencrypt/master' into config

This commit is contained in:
Jakub Warmuz 2015-02-07 22:32:38 +00:00
commit 4b6baae8b8
No known key found for this signature in database
GPG key ID: 2A7BAD3A489B52EA
11 changed files with 161 additions and 65 deletions

View file

@ -12,13 +12,22 @@
# All configuration values have a default; values that are commented out
# serve to show the default.
import sys
import codecs
import os
import re
import sys
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()))
# 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
# documentation root, use os.path.abspath to make it absolute, like shown here.
#sys.path.insert(0, os.path.abspath('.'))
sys.path.insert(0, os.path.abspath(os.path.join(here, '..')))
# -- General configuration ------------------------------------------------
@ -58,9 +67,9 @@ copyright = u'2014, Let\'s Encrypt Project'
# built documents.
#
# The short X.Y version.
version = '0.1'
version = '.'.join(meta['version'].split('.')[:2])
# The full version, including alpha/beta/rc tags.
release = '0.1'
release = meta['version']
# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.

View file

@ -69,7 +69,6 @@ In order to generate the Sphinx documentation, run the following commands.
::
./venv/bin/python setup.py docs
cd docs
make clean html SPHINXBUILD=../venv/bin/sphinx-build

View file

@ -1,2 +1,3 @@
"""Let's Encrypt."""
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
__version__ = "0.1"

View file

@ -125,7 +125,12 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
self._cleanup_challenges(domain)
def _satisfy_challenges(self):
"""Attempt to satisfy all saved challenge messages."""
"""Attempt to satisfy all saved challenge messages.
.. todo:: It might be worth it to try different challenges to
find one that doesn't throw an exception
"""
logging.info("Performing the following challenges:")
for dom in self.domains:
self.paths[dom] = gen_challenge_path(
@ -145,8 +150,19 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
flat_client.extend(ichall.chall for ichall in self.client_c[dom])
flat_auth.extend(ichall.chall for ichall in self.dv_c[dom])
client_resp = self.client_auth.perform(flat_client)
dv_resp = self.dv_auth.perform(flat_auth)
try:
client_resp = self.client_auth.perform(flat_client)
dv_resp = self.dv_auth.perform(flat_auth)
# This will catch both specific types of errors.
except errors.LetsEncryptAuthHandlerError as err:
logging.critical("Failure in setting up challenges:")
logging.critical(str(err))
logging.info("Attempting to clean up outstanding challenges...")
for dom in self.domains:
self._cleanup_challenges(dom)
raise errors.LetsEncryptAuthHandlerError(
"Unable to perform challenges")
logging.info("Ready for verification...")
@ -193,8 +209,12 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
"""
logging.info("Cleaning up challenges for %s", domain)
self.dv_auth.cleanup(self.dv_c[domain])
self.client_auth.cleanup(self.client_c[domain])
# These are indexed challenges... give just the challenges to the auth
# Chose to make these lists instead of a generator to make it easier to
# work with...
self.dv_auth.cleanup([ichall.chall for ichall in self.dv_c[domain]])
self.client_auth.cleanup(
[ichall.chall for ichall in self.client_c[domain]])
def _cleanup_state(self, delete_list):
"""Cleanup state after an authorization is received.
@ -281,8 +301,7 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes
elif chall["type"] == "dns":
logging.info(" DNS challenge for name %s.", domain)
return challenge_util.DnsChall(
domain, str(chall["token"]), self.authkey[domain])
return challenge_util.DnsChall(domain, str(chall["token"]))
else:
raise errors.LetsEncryptClientError(

View file

@ -13,7 +13,7 @@ from letsencrypt.client import le_util
DvsniChall = collections.namedtuple("DvsniChall", "domain, r_b64, nonce, key")
SimpleHttpsChall = collections.namedtuple(
"SimpleHttpsChall", "domain, token, key")
DnsChall = collections.namedtuple("DnsChall", "domain, token, key")
DnsChall = collections.namedtuple("DnsChall", "domain, token")
# Client Challenges
RecContactChall = collections.namedtuple(

View file

@ -9,6 +9,7 @@ class LetsEncryptReverterError(LetsEncryptClientError):
"""Let's Encrypt Reverter error."""
# Auth Handler Errors
class LetsEncryptAuthHandlerError(LetsEncryptClientError):
"""Let's Encrypt Auth Handler error."""
@ -17,6 +18,16 @@ class LetsEncryptClientAuthError(LetsEncryptAuthHandlerError):
"""Let's Encrypt Client Authenticator error."""
class LetsEncryptDvAuthError(LetsEncryptAuthHandlerError):
"""Let's Encrypt DV Authenticator error."""
# Authenticator - Challenge specific errors
class LetsEncryptDvsniError(LetsEncryptDvAuthError):
"""Let's Encrypt DVSNI error."""
# Configurator Errors
class LetsEncryptConfiguratorError(LetsEncryptClientError):
"""Let's Encrypt Configurator error."""
@ -28,6 +39,3 @@ class LetsEncryptNoInstallationError(LetsEncryptConfiguratorError):
class LetsEncryptMisconfigurationError(LetsEncryptConfiguratorError):
"""Let's Encrypt Misconfiguration error."""
class LetsEncryptDvsniError(LetsEncryptConfiguratorError):
"""Let's Encrypt DVSNI error."""

View file

@ -1,8 +1,10 @@
"""Tests for letsencrypt.client.auth_handler."""
import logging
import unittest
import mock
from letsencrypt.client import challenge_util
from letsencrypt.client import errors
from letsencrypt.client.tests import acme_util
@ -35,6 +37,11 @@ class SatisfyChallengesTest(unittest.TestCase):
self.handler = AuthHandler(
self.mock_dv_auth, self.mock_client_auth, None)
logging.disable(logging.CRITICAL)
def tearDown(self):
logging.disable(logging.NOTSET)
def test_name1_dvsni1(self):
dom = "0"
challenge = [acme_util.CHALLENGES["dvsni"]]
@ -54,7 +61,7 @@ class SatisfyChallengesTest(unittest.TestCase):
def test_name5_dvsni5(self):
challenge = [acme_util.CHALLENGES["dvsni"]]
for i in range(5):
for i in xrange(5):
self.handler.add_chall_msg(
str(i),
acme_util.get_chall_msg(str(i), "nonce%d" % i, challenge),
@ -67,14 +74,14 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.client_c), 5)
# Each message contains 1 auth, 0 client
for i in range(5):
for i in xrange(5):
dom = str(i)
self.assertEqual(len(self.handler.responses[dom]), 1)
self.assertEqual(self.handler.responses[dom][0], "DvsniChall%d" % i)
self.assertEqual(len(self.handler.dv_c[dom]), 1)
self.assertEqual(len(self.handler.client_c[dom]), 0)
self.assertEqual(
type(self.handler.dv_c[dom][0].chall).__name__, "DvsniChall")
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
challenge_util.DvsniChall))
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
def test_name1_auth(self, mock_chall_path):
@ -102,8 +109,8 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.dv_c[dom]), 1)
self.assertEqual(len(self.handler.client_c[dom]), 0)
self.assertEqual(
type(self.handler.dv_c[dom][0].chall).__name__, "SimpleHttpsChall")
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
challenge_util.SimpleHttpsChall))
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
def test_name1_all(self, mock_chall_path):
@ -131,16 +138,16 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(
self.handler.responses[dom],
self._get_exp_response(dom, path, challenges))
self.assertEqual(
type(self.handler.dv_c[dom][0].chall).__name__, "SimpleHttpsChall")
self.assertEqual(
type(self.handler.client_c[dom][0].chall).__name__, "RecTokenChall")
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
challenge_util.SimpleHttpsChall))
self.assertTrue(isinstance(self.handler.client_c[dom][0].chall,
challenge_util.RecTokenChall))
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
def test_name5_all(self, mock_chall_path):
challenges = acme_util.get_challenges()
combos = acme_util.gen_combos(challenges)
for i in range(5):
for i in xrange(5):
self.handler.add_chall_msg(
str(i),
acme_util.get_chall_msg(
@ -153,7 +160,7 @@ class SatisfyChallengesTest(unittest.TestCase):
self.handler._satisfy_challenges() # pylint: disable=protected-access
self.assertEqual(len(self.handler.responses), 5)
for i in range(5):
for i in xrange(5):
self.assertEqual(
len(self.handler.responses[str(i)]), len(challenges))
self.assertEqual(len(self.handler.dv_c), 5)
@ -167,11 +174,10 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.dv_c[dom]), 1)
self.assertEqual(len(self.handler.client_c[dom]), 1)
self.assertEqual(
type(self.handler.dv_c[dom][0].chall).__name__, "DvsniChall")
self.assertEqual(
type(self.handler.client_c[dom][0].chall).__name__,
"RecContactChall")
self.assertTrue(isinstance(self.handler.dv_c[dom][0].chall,
challenge_util.DvsniChall))
self.assertTrue(isinstance(self.handler.client_c[dom][0].chall,
challenge_util.RecContactChall))
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
def test_name5_mix(self, mock_chall_path):
@ -188,7 +194,7 @@ class SatisfyChallengesTest(unittest.TestCase):
acme_util.get_challenges()]
# Combos doesn't matter since I am overriding the gen_path function
for i in range(5):
for i in xrange(5):
dom = str(i)
paths.append(gen_path(chosen_chall[i], challenge_list[i]))
self.handler.add_chall_msg(
@ -205,7 +211,7 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(len(self.handler.dv_c), 5)
self.assertEqual(len(self.handler.client_c), 5)
for i in range(5):
for i in xrange(5):
dom = str(i)
resp = self._get_exp_response(i, paths[i], challenge_list[i])
self.assertEqual(self.handler.responses[dom], resp)
@ -213,21 +219,66 @@ class SatisfyChallengesTest(unittest.TestCase):
self.assertEqual(
len(self.handler.client_c[dom]), len(chosen_chall[i]) - 1)
self.assertEqual(
type(self.handler.dv_c["0"][0].chall).__name__, "DnsChall")
self.assertEqual(
type(self.handler.dv_c["1"][0].chall).__name__, "DvsniChall")
self.assertEqual(
type(self.handler.dv_c["2"][0].chall).__name__, "SimpleHttpsChall")
self.assertEqual(
type(self.handler.dv_c["3"][0].chall).__name__, "SimpleHttpsChall")
self.assertEqual(
type(self.handler.dv_c["4"][0].chall).__name__, "DnsChall")
self.assertTrue(isinstance(self.handler.dv_c["0"][0].chall,
challenge_util.DnsChall))
self.assertTrue(isinstance(self.handler.dv_c["1"][0].chall,
challenge_util.DvsniChall))
self.assertTrue(isinstance(self.handler.dv_c["2"][0].chall,
challenge_util.SimpleHttpsChall))
self.assertTrue(isinstance(self.handler.dv_c["3"][0].chall,
challenge_util.SimpleHttpsChall))
self.assertTrue(isinstance(self.handler.dv_c["4"][0].chall,
challenge_util.DnsChall))
self.assertTrue(isinstance(self.handler.client_c["2"][0].chall,
challenge_util.PopChall))
self.assertTrue(isinstance(self.handler.client_c["4"][0].chall,
challenge_util.RecTokenChall))
@mock.patch("letsencrypt.client.auth_handler.gen_challenge_path")
def test_perform_exception_cleanup(self, mock_chall_path):
"""3 Challenge messages... fail perform... clean up."""
# pylint: disable=protected-access
self.mock_dv_auth.perform.side_effect = errors.LetsEncryptDvsniError
challenges = acme_util.get_challenges()
combos = acme_util.gen_combos(challenges)
for i in xrange(3):
self.handler.add_chall_msg(
str(i),
acme_util.get_chall_msg(
str(i), "nonce%d" % i, challenges, combos),
"dummy_key")
mock_chall_path.return_value = gen_path(
["dvsni", "proofOfPossession"], challenges)
# This may change in the future... but for now catch the error
self.assertRaises(errors.LetsEncryptAuthHandlerError,
self.handler._satisfy_challenges)
# Verify cleanup is actually run correctly
self.assertEqual(self.mock_dv_auth.cleanup.call_count, 3)
self.assertEqual(self.mock_client_auth.cleanup.call_count, 3)
# Check DV cleanup
mock_cleanup_args = self.mock_dv_auth.cleanup.call_args_list
for i in xrange(3):
# Assert length of arg list was 1
arg_chall_list = mock_cleanup_args[i][0][0]
self.assertEqual(len(arg_chall_list), 1)
self.assertTrue(isinstance(arg_chall_list[0],
challenge_util.DvsniChall))
# Check Auth cleanup
mock_cleanup_args = self.mock_client_auth.cleanup.call_args_list
for i in xrange(3):
arg_chall_list = mock_cleanup_args[i][0][0]
self.assertEqual(len(arg_chall_list), 1)
self.assertTrue(isinstance(arg_chall_list[0],
challenge_util.PopChall))
self.assertEqual(
type(self.handler.client_c["2"][0].chall).__name__, "PopChall")
self.assertEqual(
type(self.handler.client_c["4"][0].chall).__name__, "RecTokenChall")
def _get_exp_response(self, domain, path, challenges): # pylint: disable=no-self-use
exp_resp = ["null"] * len(challenges)
@ -259,7 +310,7 @@ class GetAuthorizationsTest(unittest.TestCase):
def test_solved3_at_once(self):
# Set 3 DVSNI challenges
challenge = [acme_util.CHALLENGES["dvsni"]]
for i in range(3):
for i in xrange(3):
self.handler.add_chall_msg(
str(i),
acme_util.get_chall_msg(str(i), "nonce%d" % i, challenge),
@ -277,7 +328,7 @@ class GetAuthorizationsTest(unittest.TestCase):
self._test_finished()
def _sat_solved_at_once(self):
for i in range(3):
for i in xrange(3):
dom = str(i)
self.handler.responses[dom] = ["DvsniChall%d" % i]
self.handler.paths[dom] = [0]
@ -314,7 +365,7 @@ class GetAuthorizationsTest(unittest.TestCase):
challs = []
challs.append(acme_util.get_challenges())
challs.append(acme_util.get_dv_challenges())
for i in range(2):
for i in xrange(2):
dom = str(i)
self.handler.add_chall_msg(
dom,
@ -388,7 +439,7 @@ class PathSatisfiedTest(unittest.TestCase):
self.handler.paths[dom[4]] = []
self.handler.responses[dom[4]] = ["respond... sure"]
for i in range(5):
for i in xrange(5):
self.assertTrue(self.handler._path_satisfied(dom[i]))
def test_not_satisfied(self):
@ -405,16 +456,25 @@ class PathSatisfiedTest(unittest.TestCase):
self.handler.paths[dom[3]] = [0]
self.handler.responses[dom[3]] = ["null"]
for i in range(4):
for i in xrange(4):
self.assertFalse(self.handler._path_satisfied(dom[i]))
def gen_auth_resp(chall_list): # pylint: disable=missing-docstring
def gen_auth_resp(chall_list):
"""Generate a dummy authorization response."""
return ["%s%s" % (type(chall).__name__, chall.domain)
for chall in chall_list]
def gen_path(str_list, challenges): # pylint: disable=missing-docstring
def gen_path(str_list, challenges):
"""Generate a path for challenge messages
:param list str_list: challenge message types (:class:`str`)
:param dict challenges: ACME challenge messages
:return: :class:`list` of :class:`int`
"""
path = []
for i, chall in enumerate(challenges):
for str_chall in str_list:

View file

@ -1,6 +1,7 @@
"""Tests for letsencrypt.client.le_util."""
import os
import shutil
import stat
import tempfile
import unittest
@ -33,11 +34,11 @@ class MakeOrVerifyDirTest(unittest.TestCase):
path = os.path.join(self.root_path, 'bar')
self._call(path, 0o650)
self.assertTrue(os.path.isdir(path))
# TODO: check mode
self.assertEqual(stat.S_IMODE(os.stat(path).st_mode), 0o650)
def test_existing_correct_mode_does_not_fail(self):
self._call(self.path, 0o400)
# TODO: check mode
self.assertEqual(stat.S_IMODE(os.stat(self.path).st_mode), 0o400)
def test_existing_wrong_mode_fails(self):
self.assertRaises(Exception, self._call, self.path, 0o600)

View file

@ -2,8 +2,7 @@
zip_ok = false
[aliases]
dev = develop easy_install letsencrypt[testing]
docs = develop easy_install letsencrypt[docs]
dev = develop easy_install letsencrypt[testing,dev]
[nosetests]
nocapture=1

View file

@ -37,7 +37,8 @@ install_requires = [
'M2Crypto',
]
docs_extras = [
dev_extras = [
'pylint>=1.4.0', # upstream #248
'repoze.sphinx.autointerface',
'Sphinx',
]
@ -46,7 +47,6 @@ testing_extras = [
'coverage',
'nose',
'nosexcover',
'pylint>=1.4.0', # upstream #248
'tox',
]
@ -70,7 +70,7 @@ setup(
tests_require=install_requires,
test_suite='letsencrypt',
extras_require={
'docs': docs_extras,
'dev': dev_extras,
'testing': testing_extras,
},
entry_points={

View file

@ -7,13 +7,13 @@ envlist = py26,py27,cover,lint
[testenv]
commands =
python setup.py dev
python setup.py develop easy_install letsencrypt[testing]
python setup.py test -q # -q does not suppress errors
[testenv:cover]
basepython = python2.7
commands =
python setup.py dev
python setup.py develop easy_install letsencrypt[testing]
python setup.py nosetests --with-coverage --cover-min-percentage=66
[testenv:lint]