diff --git a/.gitignore b/.gitignore index e1dca3a57..ae5116fcb 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,6 @@ venv/ .tox/ .coverage m3 +*~ +.vagrant +*.swp diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 000000000..bf19b18e1 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,18 @@ + + +https://letsencrypt.readthedocs.org/en/latest/contributing.html diff --git a/CONTRIBUTING.rst b/CONTRIBUTING.rst deleted file mode 100644 index 9cb73a654..000000000 --- a/CONTRIBUTING.rst +++ /dev/null @@ -1,72 +0,0 @@ -.. _hacking: - -Hacking -======= - -In order to start hacking, you will first have to create a development -environment: - -:: - - ./venv/bin/python setup.py dev - -The code base, including your pull requests, **must** have 100% test statement -coverage **and** be compliant with the :ref:`coding-style`. - -The following tools are there to help you: - -- ``./venv/bin/tox`` starts a full set of tests. Please make sure you - run it before submitting a new pull request. - -- ``./venv/bin/tox -e cover`` checks the test coverage only. - -- ``./venv/bin/tox -e lint`` checks the style of the whole project, - while ``./venv/bin/pylint --rcfile=.pylintrc file`` will check a single `file` only. - - -.. _coding-style: - -Coding style -============ - -Please: - -1. **Be consistent with the rest of the code**. - -2. Read `PEP 8 - Style Guide for Python Code`_. - -3. Follow the `Google Python Style Guide`_, with the exception that we - use `Sphinx-style`_ documentation: - - :: - - def foo(arg): - """Short description. - - :param int arg: Some number. - - :returns: Argument - :rtype: int - - """ - return arg - -4. Remember to use ``./venv/bin/pylint``. - -.. _Google Python Style Guide: https://google-styleguide.googlecode.com/svn/trunk/pyguide.html -.. _Sphinx-style: http://sphinx-doc.org/ -.. _PEP 8 - Style Guide for Python Code: https://www.python.org/dev/peps/pep-0008 - - -Updating the Documentation -========================== - -In order to generate the Sphinx documentation, run the following commands. - -:: - - cd docs - make clean html SPHINXBUILD=../venv/bin/sphinx-build - - -This should generate documentation in the ``docs/_build/html`` directory. diff --git a/MANIFEST.in b/MANIFEST.in index bea6fd9bb..3bd657b87 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,6 +1,6 @@ include README.rst include CHANGES.rst -include CONTRIBUTING.rst +include CONTRIBUTING.md include linter_plugin.py include letsencrypt/EULA recursive-include letsencrypt *.json diff --git a/README.rst b/README.rst index 712eb3b22..86d85ed1d 100644 --- a/README.rst +++ b/README.rst @@ -57,6 +57,7 @@ Current Features * web servers supported: - apache2.x (tested and working on Ubuntu Linux) + - standalone (runs its own webserver to prove you control the domain) * the private key is generated locally on your system * can talk to the Let's Encrypt (demo) CA or optionally to other ACME @@ -79,6 +80,8 @@ Documentation: https://letsencrypt.readthedocs.org/ Software project: https://github.com/letsencrypt/lets-encrypt-preview +Notes for developers: CONTRIBUTING.rst_ + Main Website: https://letsencrypt.org/ IRC Channel: #letsencrypt on `Freenode`_ @@ -88,3 +91,4 @@ email to client-dev+subscribe@letsencrypt.org) .. _Freenode: https://freenode.net .. _client-dev: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev +.. _CONTRIBUTING.rst: https://github.com/letsencrypt/lets-encrypt-preview/blob/master/CONTRIBUTING.rst diff --git a/Vagrantfile b/Vagrantfile new file mode 100644 index 000000000..a9e5494ac --- /dev/null +++ b/Vagrantfile @@ -0,0 +1,32 @@ +# -*- mode: ruby -*- +# vi: set ft=ruby : + +# Vagrantfile API/syntax version. Don't touch unless you know what you're doing! +VAGRANTFILE_API_VERSION = "2" + +# Setup instructions from docs/using.rst +$ubuntu_setup_script = <`. + +Now you can install the development packages: + +.. code-block:: shell + + ./venv/bin/python setup.py dev + +The code base, including your pull requests, **must** have 100% test +statement coverage **and** be compliant with the :ref:`coding style +`. + +The following tools are there to help you: + +- ``./venv/bin/tox`` starts a full set of tests. Please make sure you + run it before submitting a new pull request. + +- ``./venv/bin/tox -e cover`` checks the test coverage only. + +- ``./venv/bin/tox -e lint`` checks the style of the whole project, + while ``./venv/bin/pylint --rcfile=.pylintrc file`` will check a + single ``file`` only. + +.. _installing dependencies and setting up Let's Encrypt: + https://letsencrypt.readthedocs.org/en/latest/using.html + + +Vagrant +------- + +If you are a Vagrant user, Let's Encrypt comes with a Vagrantfile that +automates setting up a development environment in an Ubuntu 14.04 +LTS VM. To set it up, simply run ``vagrant up``. The repository is +synced to ``/vagrant``, so you can get started with: + +.. code-block:: shell + + vagrant ssh + cd /vagrant + ./venv/bin/python setup.py install + sudo ./venv/bin/letsencrypt + +Support for other Linux distributions coming soon. + +.. note:: + Unfortunately, Python distutils and, by extension, setup.py and + tox, use hard linking quite extensively. Hard linking is not + supported by the default sync filesystem in Vagrant. As a result, + all actions with these commands are *significantly slower* in + Vagrant. One potential fix is to `use NFS`_ (`related issue`_). + +.. _use NFS: http://docs.vagrantup.com/v2/synced-folders/nfs.html +.. _related issue: https://github.com/ClusterHQ/flocker/issues/516 + + +Code components and layout +========================== + +letsencrypt/acme + contains all protocol specific code +letsencrypt/client + all client code +letsencrypt/scripts + just the starting point of the code, main.py + + +Plugin-architecture +------------------- + +Let's Encrypt has a plugin architecture to facilitate support for +different webservers, other TLS servers, and operating systems. + +The most common kind of plugin is a "Configurator", which is likely to +implement the `~letsencrypt.client.interfaces.IAuthenticator` and +`~letsencrypt.client.interfaces.IInstaller` interfaces (though some +Configurators may implement just one of those). + +There are also `~letsencrypt.client.interfaces.IDisplay` plugins, +which implement bindings to alternative UI libraries. + + +Authenticators +-------------- + +Authenticators are plugins designed to solve challenges received from +the ACME server. From the protocol, there are essentially two +different types of challenges. Challenges that must be solved by +individual plugins in order to satisfy domain validation (subclasses +of `~.DVChallenge`, i.e. `~.challenges.DVSNI`, +`~.challenges.SimpleHTTPS`, `~.challenges.DNS`) and client specific +challenges (subclasses of `~.ClientChallenge`, +i.e. `~.challenges.RecoveryToken`, `~.challenges.RecoveryContact`, +`~.challenges.ProofOfPossession`). Client specific challenges are +always handled by the `~.ClientAuthenticator`. Right now we have two +DV Authenticators, `~.ApacheConfigurator` and the +`~.StandaloneAuthenticator`. The Standalone and Apache authenticators +only solve the `~.challenges.DVSNI` challenge currently. (You can set +which challenges your authenticator can handle through the +:meth:`~.IAuthenticator.get_chall_pref`. + +(FYI: We also have a partial implementation for a `~.DNSAuthenticator` +in a separate branch). + + +Installer +--------- + +Installers classes exist to actually setup the certificate and be able +to enhance the configuration. (Turn on HSTS, redirect to HTTPS, +etc). You can indicate your abilities through the +:meth:`~.IInstaller.supported_enhancements` call. We currently only +have one Installer written (still developing), `~.ApacheConfigurator`. + +Installers and Authenticators will oftentimes be the same +class/object. Installers and Authenticators are kept separate because +it should be possible to use the `~.StandaloneAuthenticator` (it sets +up its own Python server to perform challenges) with a program that +cannot solve challenges itself. (I am imagining MTA installers). + + +Display +~~~~~~~ + +We currently offer a pythondialog and "text" mode for displays. I have +rewritten the interface which should be merged within the next day +(the rewrite is in the revoker branch of the repo and should be merged +within the next day). Display plugins implement +`~letsencrypt.client.interfaces.IDisplay` interface. + + +Augeas +------ + +Some plugins, especially those designed to reconfigure UNIX servers, +can take inherit from `~.AugeasConfigurator` class in order to more +efficiently handle common operations on UNIX server configuration +files. + + +.. _coding-style: + +Coding style +============ + +Please: + +1. **Be consistent with the rest of the code**. + +2. Read `PEP 8 - Style Guide for Python Code`_. + +3. Follow the `Google Python Style Guide`_, with the exception that we + use `Sphinx-style`_ documentation:: + + def foo(arg): + """Short description. + + :param int arg: Some number. + + :returns: Argument + :rtype: int + + """ + return arg + +4. Remember to use ``./venv/bin/pylint``. + +.. _Google Python Style Guide: + https://google-styleguide.googlecode.com/svn/trunk/pyguide.html +.. _Sphinx-style: http://sphinx-doc.org/ +.. _PEP 8 - Style Guide for Python Code: + https://www.python.org/dev/peps/pep-0008 + + +Updating the documentation +========================== + +In order to generate the Sphinx documentation, run the following +commands: + +.. code-block:: shell + + cd docs + make clean html SPHINXBUILD=../venv/bin/sphinx-build + +This should generate documentation in the ``docs/_build/html`` +directory. diff --git a/docs/index.rst b/docs/index.rst index b290b2231..34615168c 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -6,7 +6,7 @@ Welcome to the Let's Encrypt client documentation! intro using - project + contributing .. toctree:: :maxdepth: 1 diff --git a/docs/project.rst b/docs/project.rst deleted file mode 100644 index 421f0b062..000000000 --- a/docs/project.rst +++ /dev/null @@ -1,5 +0,0 @@ -================================ -The Let's Encrypt Client Project -================================ - -.. include:: ../CONTRIBUTING.rst diff --git a/docs/using.rst b/docs/using.rst index 9b09833e4..362b75d81 100644 --- a/docs/using.rst +++ b/docs/using.rst @@ -21,30 +21,30 @@ In general: Ubuntu ------ -:: +.. code-block:: shell - sudo apt-get install python python-setuptools python-virtualenv python-dev \ - gcc swig dialog libaugeas0 libssl-dev libffi-dev \ - ca-certificates + sudo apt-get install python python-setuptools python-virtualenv python-dev \ + gcc swig dialog libaugeas0 libssl-dev libffi-dev \ + ca-certificates .. Please keep the above command in sync with .travis.yml (before_install) Mac OSX ------- -:: +.. code-block:: shell - sudo brew install augeas swig + sudo brew install augeas swig Installation ============ -:: +.. code-block:: shell - virtualenv --no-site-packages -p python2 venv - ./venv/bin/python setup.py install - sudo ./venv/bin/letsencrypt + virtualenv --no-site-packages -p python2 venv + ./venv/bin/python setup.py install + sudo ./venv/bin/letsencrypt Usage @@ -52,7 +52,7 @@ Usage The letsencrypt commandline tool has a builtin help: -:: +.. code-block:: shell ./venv/bin/letsencrypt --help diff --git a/letsencrypt.py b/letsencrypt.py deleted file mode 120000 index 77b93ee70..000000000 --- a/letsencrypt.py +++ /dev/null @@ -1 +0,0 @@ -letsencrypt/scripts/main.py \ No newline at end of file diff --git a/letsencrypt/acme/jose/json_util.py b/letsencrypt/acme/jose/json_util.py index 8abcf5e32..01eada89c 100644 --- a/letsencrypt/acme/jose/json_util.py +++ b/letsencrypt/acme/jose/json_util.py @@ -30,13 +30,7 @@ class Field(object): :class:`~letsencrypt.acme.jose.errors.SerializationError` (:class:`~letsencrypt.acme.jose.errors.DeserializationError`). - For greater flexibility, ``encoder2`` and ``decoder2`` accept two - parameters: the whole object ("``self``" in case of encoding, and - JSON serialized object ``jobj`` in case of decoding) and the value - to be encoded/decoded. - - Note, that ``decoder`` and ``decoder2`` should perform partial - serialization only. + Note, that ``decoder`` should perform partial serialization only. :ivar str json_name: Name of the field when encoded to JSON. :ivar default: Default value (used when not present in JSON object). @@ -48,18 +42,15 @@ class Field(object): deserializing. """ - __slots__ = ('json_name', 'default', 'omitempty', - 'fdec', 'fenc', 'fdec2', 'fenc2') + __slots__ = ('json_name', 'default', 'omitempty', 'fdec', 'fenc') def __init__(self, json_name, default=None, omitempty=False, - decoder=None, encoder=None, decoder2=None, encoder2=None): + decoder=None, encoder=None): # pylint: disable=too-many-arguments self.json_name = json_name self.default = default self.omitempty = omitempty - self.fdec2 = decoder2 - self.fenc2 = encoder2 self.fdec = self.default_decoder if decoder is None else decoder self.fenc = self.default_encoder if encoder is None else encoder @@ -80,37 +71,24 @@ class Field(object): def _update_params(self, **kwargs): current = dict(json_name=self.json_name, default=self.default, omitempty=self.omitempty, - decoder=self.fdec, encoder=self.fenc, - decoder2=self.fdec2, encoder2=self.fenc2) + decoder=self.fdec, encoder=self.fenc) current.update(kwargs) return type(self)(**current) # pylint: disable=star-args def decoder(self, fdec): """Descriptor to change the decoder on JSON object field.""" - return self._update_params(decoder=fdec, decoder2=None) + return self._update_params(decoder=fdec) def encoder(self, fenc): """Descriptor to change the encoder on JSON object field.""" - return self._update_params(encoder=fenc, encoder2=None) + return self._update_params(encoder=fenc) - def decoder2(self, fdec2): - """Descriptor to change the decoder2 on JSON object field.""" - return self._update_params(decoder2=fdec2, decoder=None) - - def encoder2(self, fenc2): - """Descriptor to change the encoder2 on JSON object field.""" - return self._update_params(encoder2=fenc2, encoder=None) - - def decode(self, value, jobj=None): + def decode(self, value): """Decode a value, optionally with context JSON object.""" - if self.fdec2 is not None: - return self.fdec2(jobj, value) return self.fdec(value) - def encode(self, value, obj=None): + def encode(self, value): """Encode a value, optionally with context JSON object.""" - if self.fenc2 is not None: - return self.fenc2(obj, value) return self.fenc(value) @classmethod @@ -241,7 +219,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable): logging.debug('Ommiting empty field "%s" (%s)', slot, value) else: try: - jobj[field.json_name] = field.encode(value, self) + jobj[field.json_name] = field.encode(value) except errors.SerializationError as error: raise errors.SerializationError( 'Could not encode {0} ({1}): {2}'.format( @@ -274,7 +252,7 @@ class JSONObjectWithFields(util.ImmutableMap, interfaces.JSONDeSerializable): else: value = jobj[field.json_name] try: - fields[slot] = field.decode(value, jobj) + fields[slot] = field.decode(value) except errors.DeserializationError as error: raise errors.DeserializationError( 'Could not decode {0!r} ({1!r}): {2}'.format( diff --git a/letsencrypt/acme/jose/json_util_test.py b/letsencrypt/acme/jose/json_util_test.py index da548aaee..e5bffd294 100644 --- a/letsencrypt/acme/jose/json_util_test.py +++ b/letsencrypt/acme/jose/json_util_test.py @@ -21,8 +21,6 @@ class FieldTest(unittest.TestCase): """Tests for letsencrypt.acme.jose.json_util.Field.""" def test_descriptors(self): - mock_jobj = mock.MagicMock() - mock_obj = mock.MagicMock() mock_value = mock.MagicMock() # pylint: disable=missing-docstring @@ -33,36 +31,15 @@ class FieldTest(unittest.TestCase): def encoder(unused_value): return 'e' - def decoder2(jobj, unused_value): - self.assertTrue(jobj is mock_jobj) - return 'd2' - - def encoder2(obj, unused_value): - self.assertTrue(obj is mock_obj) - return 'e2' - from letsencrypt.acme.jose.json_util import Field - field = Field('foo', decoder=decoder, encoder=encoder, - decoder2=decoder2, encoder2=encoder2) - - self.assertEqual('e2', field.encode(mock_value, mock_obj)) - self.assertEqual('d2', field.decode(mock_value, mock_jobj)) + field = Field('foo') field = field.encoder(encoder) - self.assertEqual('e', field.encode(mock_value, mock_obj)) - self.assertEqual('d2', field.decode(mock_value, mock_jobj)) + self.assertEqual('e', field.encode(mock_value)) field = field.decoder(decoder) - self.assertEqual('e', field.encode(mock_value, mock_obj)) - self.assertEqual('d', field.decode(mock_value, mock_jobj)) - - field = field.encoder2(encoder2) - self.assertEqual('e2', field.encode(mock_value, mock_obj)) - self.assertEqual('d', field.decode(mock_value, mock_jobj)) - - field = field.decoder2(decoder2) - self.assertEqual('e2', field.encode(mock_value, mock_obj)) - self.assertEqual('d2', field.decode(mock_value, mock_jobj)) + self.assertEqual('e', field.encode(mock_value)) + self.assertEqual('d', field.decode(mock_value)) def test_default_encoder_is_partial(self): class MockField(interfaces.JSONDeSerializable): diff --git a/letsencrypt/acme/jose/jwa.py b/letsencrypt/acme/jose/jwa.py index 99c9a8631..984a10f41 100644 --- a/letsencrypt/acme/jose/jwa.py +++ b/letsencrypt/acme/jose/jwa.py @@ -71,7 +71,12 @@ class _JWAHS(JWASignature): return HMAC.new(key, msg, self.digestmod).digest() def verify(self, key, msg, sig): - # TODO: use constant compare to mitigate timing attack? + """Verify the signature. + + .. warning:: + Does not protect against timing attack (no constant compare). + + """ return self.sign(key, msg) == sig diff --git a/letsencrypt/acme/jose/jws.py b/letsencrypt/acme/jose/jws.py index 81106dd2c..3b962aede 100644 --- a/letsencrypt/acme/jose/jws.py +++ b/letsencrypt/acme/jose/jws.py @@ -3,7 +3,7 @@ import argparse import base64 import sys -import M2Crypto.X509 +import M2Crypto from letsencrypt.acme.jose import b64 from letsencrypt.acme.jose import errors diff --git a/letsencrypt/acme/jose/jws_test.py b/letsencrypt/acme/jose/jws_test.py index 28dfd0c2f..215960e15 100644 --- a/letsencrypt/acme/jose/jws_test.py +++ b/letsencrypt/acme/jose/jws_test.py @@ -5,7 +5,7 @@ import pkg_resources import unittest import Crypto.PublicKey.RSA -import M2Crypto.X509 +import M2Crypto import mock from letsencrypt.acme.jose import b64 @@ -205,29 +205,33 @@ class CLITest(unittest.TestCase): def test_unverified(self): from letsencrypt.acme.jose.jws import CLI - with mock.patch('sys.stdin') as sin, mock.patch('sys.stdout'): + with mock.patch('sys.stdin') as sin: sin.read.return_value = '{"payload": "foo", "signature": "xxx"}' - self.assertEqual(-1, CLI.run(['verify'])) + with mock.patch('sys.stdout'): + self.assertEqual(-1, CLI.run(['verify'])) def test_json(self): from letsencrypt.acme.jose.jws import CLI - with mock.patch('sys.stdin') as sin, mock.patch('sys.stdout') as sout: + with mock.patch('sys.stdin') as sin: sin.read.return_value = 'foo' - CLI.run(['sign', '-k', self.key_path, '-a', 'RS256', - '-p', 'jwk']) - sin.read.return_value = sout.write.mock_calls[0][1][0] - self.assertEqual(0, CLI.run(['verify'])) + with mock.patch('sys.stdout') as sout: + CLI.run(['sign', '-k', self.key_path, '-a', 'RS256', + '-p', 'jwk']) + sin.read.return_value = sout.write.mock_calls[0][1][0] + self.assertEqual(0, CLI.run(['verify'])) def test_compact(self): from letsencrypt.acme.jose.jws import CLI - with mock.patch('sys.stdin') as sin, mock.patch('sys.stdout') as sout: + with mock.patch('sys.stdin') as sin: sin.read.return_value = 'foo' - CLI.run(['--compact', 'sign', '-k', self.key_path]) - sin.read.return_value = sout.write.mock_calls[0][1][0] - self.assertEqual(0, CLI.run([ - '--compact', 'verify', '--kty', 'RSA', '-k', self.key_path])) + with mock.patch('sys.stdout') as sout: + CLI.run(['--compact', 'sign', '-k', self.key_path]) + sin.read.return_value = sout.write.mock_calls[0][1][0] + self.assertEqual(0, CLI.run([ + '--compact', 'verify', '--kty', 'RSA', + '-k', self.key_path])) if __name__ == '__main__': diff --git a/letsencrypt/client/apache/configurator.py b/letsencrypt/client/apache/configurator.py index 93db689f8..89a2ff4e2 100644 --- a/letsencrypt/client/apache/configurator.py +++ b/letsencrypt/client/apache/configurator.py @@ -548,8 +548,9 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator): def _enable_redirect(self, ssl_vhost, unused_options): """Redirect all equivalent HTTP traffic to ssl_vhost. - .. todo:: This enhancement should be rewritten and will unfortunately - require lots of debugging by hand. + .. todo:: This enhancement should be rewritten and will + unfortunately require lots of debugging by hand. + Adds Redirect directive to the port 80 equivalent of ssl_vhost First the function attempts to find the vhost with equivalent ip addresses that serves on non-ssl ports diff --git a/letsencrypt/client/apache/dvsni.py b/letsencrypt/client/apache/dvsni.py index b980fdb36..033bcde20 100644 --- a/letsencrypt/client/apache/dvsni.py +++ b/letsencrypt/client/apache/dvsni.py @@ -50,7 +50,7 @@ class ApacheDvsni(object): def perform(self): """Peform a DVSNI challenge.""" if not self.achalls: - return None + return [] # Save any changes to the configuration as a precaution # About to make temporary changes to the config self.configurator.save() @@ -160,19 +160,19 @@ class ApacheDvsni(object): ips = " ".join(str(i) for i in ip_addrs) document_root = os.path.join( self.configurator.config.config_dir, "dvsni_page/") - return ("\n" - "ServerName " + achall.nonce_domain + "\n" - "UseCanonicalName on\n" - "SSLStrictSNIVHostCheck on\n" - "\n" - "LimitRequestBody 1048576\n" - "\n" - "Include " + self.configurator.parser.loc["ssl_options"] + "\n" - "SSLCertificateFile " + self.get_cert_file(achall) + "\n" - "SSLCertificateKeyFile " + achall.key.file + "\n" - "\n" - "DocumentRoot " + document_root + "\n" - "\n\n") + return ("{0}" + "ServerName " + achall.nonce_domain + "{0}" + "UseCanonicalName on{0}" + "SSLStrictSNIVHostCheck on{0}" + "{0}" + "LimitRequestBody 1048576{0}" + "{0}" + "Include " + self.configurator.parser.loc["ssl_options"] + "{0}" + "SSLCertificateFile " + self.get_cert_file(achall) + "{0}" + "SSLCertificateKeyFile " + achall.key.file + "{0}" + "{0}" + "DocumentRoot " + document_root + "{0}" + "{0}{0}".format(os.linesep)) def get_cert_file(self, achall): """Returns standardized name for challenge certificate. diff --git a/letsencrypt/client/auth_handler.py b/letsencrypt/client/auth_handler.py index 7ded4322c..05f3722cf 100644 --- a/letsencrypt/client/auth_handler.py +++ b/letsencrypt/client/auth_handler.py @@ -203,7 +203,8 @@ class AuthHandler(object): # pylint: disable=too-many-instance-attributes def _path_satisfied(self, dom): """Returns whether a path has been completely satisfied.""" - return all(self.responses[dom][i] is not None for i in self.paths[dom]) + # Make sure that there are no 'None' or 'False' entries along path. + return all(self.responses[dom][i] for i in self.paths[dom]) def _get_chall_pref(self, domain): """Return list of challenge preferences. diff --git a/letsencrypt/client/network.py b/letsencrypt/client/network.py index de6db575b..2719583c3 100644 --- a/letsencrypt/client/network.py +++ b/letsencrypt/client/network.py @@ -11,6 +11,9 @@ from letsencrypt.acme import messages from letsencrypt.client import errors +# https://urllib3.readthedocs.org/en/latest/security.html#insecureplatformwarning +requests.packages.urllib3.contrib.pyopenssl.inject_into_urllib3() + logging.getLogger("requests").setLevel(logging.WARNING) diff --git a/letsencrypt/client/standalone_authenticator.py b/letsencrypt/client/standalone_authenticator.py old mode 100755 new mode 100644 diff --git a/letsencrypt/client/tests/auth_handler_test.py b/letsencrypt/client/tests/auth_handler_test.py index abf7032b9..478d4c0ac 100644 --- a/letsencrypt/client/tests/auth_handler_test.py +++ b/letsencrypt/client/tests/auth_handler_test.py @@ -481,7 +481,7 @@ class PathSatisfiedTest(unittest.TestCase): self.handler.responses[dom[0]] = [None, "sat", "sat2", None] self.handler.paths[dom[1]] = [0] - self.handler.responses[dom[1]] = ["sat", None, None, None] + self.handler.responses[dom[1]] = ["sat", None, None, False] self.handler.paths[dom[2]] = [0] self.handler.responses[dom[2]] = ["sat"] @@ -496,7 +496,7 @@ class PathSatisfiedTest(unittest.TestCase): self.assertTrue(self.handler._path_satisfied(dom[i])) def test_not_satisfied(self): - dom = ["0", "1", "2"] + dom = ["0", "1", "2", "3"] self.handler.paths[dom[0]] = [1, 2] self.handler.responses[dom[0]] = ["sat1", None, "sat2", None] @@ -506,6 +506,9 @@ class PathSatisfiedTest(unittest.TestCase): self.handler.paths[dom[2]] = [0] self.handler.responses[dom[2]] = [None] + self.handler.paths[dom[3]] = [0] + self.handler.responses[dom[3]] = [False] + for i in xrange(3): self.assertFalse(self.handler._path_satisfied(dom[i])) diff --git a/letsencrypt/scripts/main.py b/letsencrypt/scripts/main.py old mode 100755 new mode 100644 index 0d76a1581..11caf944a --- a/letsencrypt/scripts/main.py +++ b/letsencrypt/scripts/main.py @@ -1,4 +1,3 @@ -#!/usr/bin/env python """Parse command line and call the appropriate functions. .. todo:: Sanity check all input. Be sure to avoid shell code etc... diff --git a/setup.py b/setup.py old mode 100755 new mode 100644 index 436fcf0ea..fac3eef90 --- a/setup.py +++ b/setup.py @@ -1,10 +1,15 @@ -#!/usr/bin/env python import codecs import os import re from setuptools import setup +# Workaround for http://bugs.python.org/issue8876, see +# http://bugs.python.org/issue8876#msg208792 +# This can be removed when using Python 2.7.9 or later: +# https://hg.python.org/cpython/raw-file/v2.7.9/Misc/NEWS +if os.path.abspath(__file__).split(os.path.sep)[1] == 'vagrant': + del os.link def read_file(filename, encoding='utf8'): """Read unicode from given file.""" @@ -26,7 +31,9 @@ install_requires = [ 'ConfArgParse', 'jsonschema', 'mock', + 'ndg-httpsclient', # urllib3 InsecurePlatformWarning (#304) 'psutil>=2.1.0', # net_connections introduced in 2.1.0 + 'pyasn1', # urllib3 InsecurePlatformWarning (#304) 'pycrypto', 'PyOpenSSL', 'python-augeas', @@ -40,7 +47,9 @@ install_requires = [ ] dev_extras = [ - 'pylint>=1.4.0', # upstream #248 + # Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289 + 'astroid==1.3.5', + 'pylint==1.4.2', # upstream #248 ] docs_extras = [ @@ -85,6 +94,7 @@ setup( packages=[ 'letsencrypt', 'letsencrypt.acme', + 'letsencrypt.acme.jose', 'letsencrypt.client', 'letsencrypt.client.apache', 'letsencrypt.client.display', diff --git a/tox.ini b/tox.ini index 485816d45..bb5ac1bb7 100644 --- a/tox.ini +++ b/tox.ini @@ -19,7 +19,7 @@ setenv = basepython = python2.7 commands = pip install -e .[testing] - python setup.py nosetests --with-coverage --cover-min-percentage=85 + python setup.py nosetests --with-coverage --cover-min-percentage=86 [testenv:lint] # recent versions of pylint do not support Python 2.6 (#97, #187)