diff --git a/.gitignore b/.gitignore index 437c9f0bd..e1dca3a57 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ build/ dist/ venv/ .tox/ +.coverage m3 diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 000000000..1a63f343f --- /dev/null +++ b/.travis.yml @@ -0,0 +1,22 @@ +# To mimic README.md installation and hacking instructions as much as +# possible, this config file instructs Travis CI to create a build +# environment for each supported Python version, and then for each of +# those it runs tox with two environments: lint and pyXX corresponding +# to the currently used Travis CI build Python version. + +language: python + +python: + - "2.6" + - "2.7" + +before_install: > + travis_retry sudo apt-get install python python-setuptools + python-virtualenv python-dev gcc swig dialog libaugeas0 libssl-dev + +install: travis_retry python setup.py dev # installs tox +script: travis_retry tox + +env: + - TOXENV=py${TRAVIS_PYTHON_VERSION//[.]/} + - TOXENV=lint diff --git a/README.md b/README.md index 4af884374..560d8c8e2 100644 --- a/README.md +++ b/README.md @@ -1,25 +1,56 @@ -This is the Let's Encrypt Agent DEVELOPER PREVIEW repository. +# Let's Encrypt -DO NOT RUN THIS CODE ON A PRODUCTION WEBSERVER. IT WILL INSTALL CERTIFICATES -SIGNED BY A TEST CA, AND WILL CAUSE CERT WARNINGS FOR USERS. +[![Build Status] +(https://travis-ci.org/letsencrypt/lets-encrypt-preview.svg?branch=master)] +(https://travis-ci.org/letsencrypt/lets-encrypt-preview) -This code is intended for testing, demonstration, and integration engineering -with OSes and hosting platforms. Currently the code works with Linux and -Apache, though we will be expanding it to other platforms. +## Disclaimer -## Running the demo code on Ubuntu +This is the [Let's Encrypt] Agent **DEVELOPER PREVIEW** repository. + +**DO NOT RUN THIS CODE ON A PRODUCTION WEBSERVER. IT WILL INSTALL +CERTIFICATES SIGNED BY A TEST CA, AND WILL CAUSE CERT WARNINGS FOR +USERS.** + +This code is intended for testing, demonstration, and integration +engineering with OSes and hosting platforms. For the time being +project focuses on Linux and Apache, though we will be expanding +it to other platforms. + +## Running the demo code + +The demo code is supported and known to work on **Ubuntu only** (even +closely related [Debian is known to fail] +(https://github.com/letsencrypt/lets-encrypt-preview/issues/68)). +Therefore, prerequisites for other platforms listed below are provided +mainly for the [developers](#hacking) reference. + +### Prerequisites + +In general: + +* [swig] is required for compiling [m2crypto] +* [augeas] is required for the `python-augeas` bindings + +#### Ubuntu ``` sudo apt-get install python python-setuptools python-virtualenv \ python-dev gcc swig dialog libaugeas0 libssl-dev +``` + +#### Mac OSX + +`sudo brew install augeas swig` + +### Installation + +``` virtualenv --no-site-packages venv ./venv/bin/python setup.py install sudo ./venv/bin/letsencrypt ``` -Note, that letsencrypt does not yet handle Debian unstable's Apache2 -conf layout. - ## Hacking 1. Bootstrap: `./venv/bin/python setup.py dev` @@ -62,14 +93,20 @@ optional arguments: ## More Information -Further Setup, documentation and open projects are available in the [Wiki] -(https://github.com/letsencrypt/lets-encrypt-preview/wiki) +- Further setup, documentation and open projects are available in the + [Wiki]. -Join us at our IRC channel @ freenode.net #letsencrypt +- Join us at our IRC channel: #letsencrypt at [Freenode]. -Client software development can be discussed on this [mailing list] -(https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev) +- Client software development can be discussed on this [mailing + list]. To subscribe without a Google account, send an email to + client-dev+subscribe@letsencrypt.org. -To subscribe without a Google account, send mail to -[client-dev+subscribe@letsencrypt.org] -(mailto:client-dev+subscribe@letsencrypt.org). + +[augeas]: http://augeas.net +[Freenode]: https://freenode.net +[Let's Encrypt]: https://letsencrypt.org +[m2crypto]: https://github.com/M2Crypto/M2Crypto +[mailing list]: https://groups.google.com/a/letsencrypt.org/forum/#!forum/client-dev +[swig]: http://www.swig.org +[wiki]: https://github.com/letsencrypt/lets-encrypt-preview/wiki diff --git a/letsencrypt/client/acme.py b/letsencrypt/client/acme.py index 9384abadb..347b28494 100644 --- a/letsencrypt/client/acme.py +++ b/letsencrypt/client/acme.py @@ -81,7 +81,7 @@ def challenge_request(names): def authorization_request(req_id, name, server_nonce, responses, key_file): - """Create ACME "authoriazationRequest" message. + """Create ACME "authorizationRequest" message. :param req_id: TODO :type req_id: TODO @@ -98,7 +98,7 @@ def authorization_request(req_id, name, server_nonce, responses, key_file): :param key_file: TODO :type key_file: TODO - :returns: ACME "authoriazationRequest" message. + :returns: ACME "authorizationRequest" message. :rtype: dict """ diff --git a/letsencrypt/client/apache_configurator.py b/letsencrypt/client/apache_configurator.py index 080a3b24c..c8dd4df9a 100644 --- a/letsencrypt/client/apache_configurator.py +++ b/letsencrypt/client/apache_configurator.py @@ -1242,9 +1242,9 @@ LogLevel warn \n\ have permissions of root """ - le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0755) - le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0755) - le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0755) + le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0o755) + le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0o755) + le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0o755) def standardize_excl(self): """Standardize the excl arguments for the Httpd lens in Augeas. diff --git a/letsencrypt/client/augeas_configurator.py b/letsencrypt/client/augeas_configurator.py index 89d7277ec..8bfd4c1ff 100644 --- a/letsencrypt/client/augeas_configurator.py +++ b/letsencrypt/client/augeas_configurator.py @@ -257,7 +257,7 @@ class AugeasConfigurator(configurator.Configurator): :type save_files: set """ - le_util.make_or_verify_dir(cp_dir, 0755) + le_util.make_or_verify_dir(cp_dir, 0o755) existing_filepaths = [] op_fd = None diff --git a/letsencrypt/client/client.py b/letsencrypt/client/client.py index f501c10d0..f4b42f9a8 100644 --- a/letsencrypt/client/client.py +++ b/letsencrypt/client/client.py @@ -410,7 +410,7 @@ class Client(object): """ cert_chain_abspath = None - cert_fd, cert_file = le_util.unique_file(CONFIG.CERT_PATH, 644) + cert_fd, cert_file = le_util.unique_file(CONFIG.CERT_PATH, 0o644) cert_fd.write( crypto_util.b64_cert_to_pem(certificate_dict["certificate"])) cert_fd.close() @@ -418,7 +418,7 @@ class Client(object): cert_file) if certificate_dict.get("chain", None): - chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 644) + chain_fd, chain_fn = le_util.unique_file(CONFIG.CHAIN_PATH, 0o644) for cert in certificate_dict.get("chain", []): chain_fd.write(crypto_util.b64_cert_to_pem(cert)) chain_fd.close() @@ -527,7 +527,7 @@ class Client(object): """ list_file = os.path.join(CONFIG.CERT_KEY_BACKUP, "LIST") - le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0700) + le_util.make_or_verify_dir(CONFIG.CERT_KEY_BACKUP, 0o700) idx = 0 if encrypt: @@ -658,10 +658,10 @@ class Client(object): self.privkey = key_pem # Save file - le_util.make_or_verify_dir(CONFIG.KEY_DIR, 0700) - key_f, key_filename = le_util.unique_file( - os.path.join(CONFIG.KEY_DIR, "key-letsencrypt.pem"), 0600) - key_f.write(self.privkey) + le_util.make_or_verify_dir(CONFIG.KEY_DIR, 0o700) + key_f, self.key_file = le_util.unique_file( + os.path.join(CONFIG.KEY_DIR, "key-letsencrypt.pem"), 0o600) + key_f.write(key_pem) key_f.close() logger.info("Generating key: %s" % key_filename) else: @@ -672,10 +672,10 @@ class Client(object): self.csr = csr_pem # Save CSR - le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0755) - csr_f, csr_filename = le_util.unique_file( - os.path.join(CONFIG.CERT_DIR, "csr-letsencrypt.pem"), 0644) - csr_f.write(self.csr) + le_util.make_or_verify_dir(CONFIG.CERT_DIR, 0o755) + csr_f, self.csr_file = le_util.unique_file( + os.path.join(CONFIG.CERT_DIR, "csr-letsencrypt.pem"), 0o644) + csr_f.write(csr_pem) csr_f.close() logger.info("Creating CSR: %s" % csr_filename) else: diff --git a/letsencrypt/client/le_util.py b/letsencrypt/client/le_util.py index e1758c02a..5e8f5414b 100644 --- a/letsencrypt/client/le_util.py +++ b/letsencrypt/client/le_util.py @@ -5,7 +5,7 @@ import os import stat -def make_or_verify_dir(directory, mode=0755, uid=0): +def make_or_verify_dir(directory, mode=0o755, uid=0): """Make sure directory exists with proper permissions. :param directory: Path to a directry. @@ -50,7 +50,7 @@ def check_permissions(filepath, mode, uid=0): return stat.S_IMODE(file_stat.st_mode) == mode and file_stat.st_uid == uid -def unique_file(default_name, mode=0644): +def unique_file(default_name, mode=0o777): """Safely finds a unique file for writing only (by default).""" count = 1 f_parsed = os.path.splitext(default_name) diff --git a/letsencrypt/client/le_util_test.py b/letsencrypt/client/le_util_test.py index 30743c24a..926830602 100644 --- a/letsencrypt/client/le_util_test.py +++ b/letsencrypt/client/le_util_test.py @@ -16,7 +16,7 @@ class MakeOrVerifyDirTest(unittest.TestCase): def setUp(self): self.root_path = tempfile.mkdtemp() self.path = os.path.join(self.root_path, 'foo') - os.mkdir(self.path, 0400) + os.mkdir(self.path, 0o400) self.uid = os.getuid() @@ -29,16 +29,16 @@ class MakeOrVerifyDirTest(unittest.TestCase): def test_creates_dir_when_missing(self): path = os.path.join(self.root_path, 'bar') - self._call(path, 0650) + self._call(path, 0o650) self.assertTrue(os.path.isdir(path)) # TODO: check mode def test_existing_correct_mode_does_not_fail(self): - self._call(self.path, 0400) + self._call(self.path, 0o400) # TODO: check mode def test_existing_wrong_mode_fails(self): - self.assertRaises(Exception, self._call, self.path, 0600) + self.assertRaises(Exception, self._call, self.path, 0o600) class CheckPermissionsTest(unittest.TestCase): @@ -61,12 +61,12 @@ class CheckPermissionsTest(unittest.TestCase): return check_permissions(self.path, mode, self.uid) def test_ok_mode(self): - os.chmod(self.path, 0600) - self.assertTrue(self._call(0600)) + os.chmod(self.path, 0o600) + self.assertTrue(self._call(0o600)) def test_wrong_mode(self): - os.chmod(self.path, 0400) - self.assertFalse(self._call(0600)) + os.chmod(self.path, 0o400) + self.assertFalse(self._call(0o600)) # https://en.wikipedia.org/wiki/Base64#Examples diff --git a/letsencrypt/client/nginx_configurator.py b/letsencrypt/client/nginx_configurator.py index 70d775a8b..5ca0ab68c 100644 --- a/letsencrypt/client/nginx_configurator.py +++ b/letsencrypt/client/nginx_configurator.py @@ -174,9 +174,9 @@ class NginxConfigurator(augeas_configurator.AugeasConfigurator): # permissions. Aim for defensive coding... make sure all input files # have permissions of root # """ - # le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0755) - # le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0755) - # le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0755) + # le_util.make_or_verify_dir(CONFIG.CONFIG_DIR, 0o755) + # le_util.make_or_verify_dir(CONFIG.WORK_DIR, 0o755) + # le_util.make_or_verify_dir(CONFIG.BACKUP_DIR, 0o755) def restart(self, quiet=False): """Restarts nginx server""" diff --git a/setup.cfg b/setup.cfg index a848a847e..2b9124c06 100644 --- a/setup.cfg +++ b/setup.cfg @@ -3,3 +3,8 @@ zip_ok = false [aliases] dev = develop easy_install letsencrypt[testing] + +[nosetests] +nocapture=1 +cover-package=letsencrypt +cover-erase=1 diff --git a/setup.py b/setup.py index 4352b0c25..93e22b4d9 100755 --- a/setup.py +++ b/setup.py @@ -15,6 +15,7 @@ install_requires = [ testing_extras = [ 'coverage', 'nose', + 'nosexcover', 'pylint', 'tox', ] @@ -33,6 +34,7 @@ setup( ], install_requires=install_requires, tests_require=install_requires, + test_suite='letsencrypt', extras_require={ 'testing': testing_extras, }, diff --git a/tox.ini b/tox.ini index 9b6ca773a..75941366e 100644 --- a/tox.ini +++ b/tox.ini @@ -4,14 +4,19 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, lint +envlist = py26,py27,cover,lint [testenv] -commands = nosetests -deps = - nose +commands = + python setup.py dev + python setup.py test -q # -q does not suppress errors + +[testenv:cover] +commands = + python setup.py dev + python setup.py nosetests --with-coverage --cover-min-percentage=100 [testenv:lint] -commands = pylint letsencrypt -deps = - pylint +commands = + python setup.py dev + pylint letsencrypt