1
0
mirror of https://github.com/certbot/certbot.git synced 2026-01-21 19:01:07 +03:00

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

This commit is contained in:
Jakub Warmuz
2015-10-05 21:38:06 +00:00
73 changed files with 1817 additions and 246 deletions

6
.gitignore vendored
View File

@@ -2,9 +2,9 @@
*.egg-info/
.eggs/
build/
dist/
/venv/
/venv3/
dist*/
/venv*/
/kgs/
/.tox/
letsencrypt.log

View File

@@ -38,7 +38,7 @@ load-plugins=linter_plugin
# --enable=similarities". If you want to run only the classes checker, but have
# no Warning level messages displayed, use"--disable=all --enable=classes
# --disable=W"
disable=fixme,locally-disabled,abstract-class-not-used,bad-continuation,too-few-public-methods,no-self-use
disable=fixme,locally-disabled,abstract-class-not-used,bad-continuation,too-few-public-methods,no-self-use,invalid-name
# abstract-class-not-used cannot be disabled locally (at least in pylint 1.4.1)

View File

@@ -62,5 +62,5 @@ RUN virtualenv --no-site-packages -p python2 /opt/letsencrypt/venv && \
# bash" and investigate, apply patches, etc.
ENV PATH /opt/letsencrypt/venv/bin:$PATH
# TODO: is --text really necessary?
ENTRYPOINT [ "letsencrypt", "--text" ]
ENTRYPOINT [ "letsencrypt" ]

View File

@@ -1,5 +1,5 @@
Let's Encrypt:
Copyright (c) Internet Security Research Group
Let's Encrypt Python Client
Copyright (c) Electronic Frontier Foundation and others
Licensed Apache Version 2.0
Incorporating code from nginxparser

View File

@@ -2,6 +2,7 @@ include requirements.txt
include README.rst
include CHANGES.rst
include CONTRIBUTING.md
include LICENSE.txt
include linter_plugin.py
include letsencrypt/EULA
recursive-include letsencrypt/tests/testdata *

View File

@@ -79,7 +79,7 @@ Current Features
* web servers supported:
- apache/2.x (tested and working on Ubuntu Linux)
- nginx/0.8.48+ (tested and mostly working on Ubuntu Linux)
- nginx/0.8.48+ (under development)
- standalone (runs its own webserver to prove you control the domain)
* the private key is generated locally on your system

190
acme/LICENSE.txt Normal file
View File

@@ -0,0 +1,190 @@
Copyright 2015 Electronic Frontier Foundation and others
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1 +1,3 @@
include LICENSE.txt
include README.rst
recursive-include acme/testdata *

1
acme/README.rst Normal file
View File

@@ -0,0 +1 @@
ACME protocol implementation for Python

View File

@@ -25,6 +25,14 @@ class Challenge(jose.TypedJSONObjectWithFields):
"""ACME challenge."""
TYPES = {}
@classmethod
def from_json(cls, jobj):
try:
return super(Challenge, cls).from_json(jobj)
except jose.UnrecognizedTypeError as error:
logger.debug(error)
return UnrecognizedChallenge.from_json(jobj)
class ContinuityChallenge(Challenge): # pylint: disable=abstract-method
"""Client validation challenges."""
@@ -42,6 +50,32 @@ class ChallengeResponse(jose.TypedJSONObjectWithFields):
resource = fields.Resource(resource_type)
class UnrecognizedChallenge(Challenge):
"""Unrecognized challenge.
ACME specification defines a generic framework for challenges and
defines some standard challenges that are implemented in this
module. However, other implementations (including peers) might
define additional challenge types, which should be ignored if
unrecognized.
:ivar jobj: Original JSON decoded object.
"""
def __init__(self, jobj):
super(UnrecognizedChallenge, self).__init__()
object.__setattr__(self, "jobj", jobj)
def to_partial_json(self):
# pylint: disable=no-member
return self.jobj
@classmethod
def from_json(cls, jobj):
return cls(jobj)
@Challenge.register
class SimpleHTTP(DVChallenge):
"""ACME "simpleHttp" challenge.
@@ -542,7 +576,7 @@ class DNS(DVChallenge):
def check_validation(self, validation, account_public_key):
"""Check validation.
:param validation
:param JWS validation:
:type account_public_key:
`~cryptography.hazmat.primitives.asymmetric.rsa.RSAPublicKey`
or

View File

@@ -17,6 +17,32 @@ CERT = test_util.load_cert('cert.pem')
KEY = test_util.load_rsa_private_key('rsa512_key.pem')
class ChallengeTest(unittest.TestCase):
def test_from_json_unrecognized(self):
from acme.challenges import Challenge
from acme.challenges import UnrecognizedChallenge
chall = UnrecognizedChallenge({"type": "foo"})
# pylint: disable=no-member
self.assertEqual(chall, Challenge.from_json(chall.jobj))
class UnrecognizedChallengeTest(unittest.TestCase):
def setUp(self):
from acme.challenges import UnrecognizedChallenge
self.jobj = {"type": "foo"}
self.chall = UnrecognizedChallenge(self.jobj)
def test_to_partial_json(self):
self.assertEqual(self.jobj, self.chall.to_partial_json())
def test_from_json(self):
from acme.challenges import UnrecognizedChallenge
self.assertEqual(
self.chall, UnrecognizedChallenge.from_json(self.jobj))
class SimpleHTTPTest(unittest.TestCase):
def setUp(self):

View File

@@ -274,6 +274,7 @@ class AuthorizationTest(unittest.TestCase):
def setUp(self):
from acme.messages import ChallengeBody
from acme.messages import STATUS_VALID
self.challbs = (
ChallengeBody(
uri='http://challb1', status=STATUS_VALID,

View File

@@ -1,5 +1,3 @@
# Symlinked in letsencrypt/tests/test_util.py, causes duplicate-code
# warning that cannot be disabled locally.
"""Test utilities.
.. warning:: This module is not part of the public API.

View File

@@ -4,6 +4,8 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
install_requires = [
# load_pem_private/public_key (>=0.6)
# rsa_recover_prime_factors (>=0.8)
@@ -34,7 +36,25 @@ testing_extras = [
setup(
name='acme',
version=version,
description='ACME protocol implementation',
url='https://github.com/letsencrypt/letsencrypt',
author="Let's Encrypt Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
extras_require={
'testing': testing_extras,

2
bootstrap/archlinux.sh Executable file
View File

@@ -0,0 +1,2 @@
#!/bin/sh
pacman -S git python2 python2-virtualenv gcc dialog augeas openssl libffi ca-certificates

View File

@@ -21,9 +21,3 @@
.. automodule:: letsencrypt.display.enhancements
:members:
:mod:`letsencrypt.display.revocation`
=====================================
.. automodule:: letsencrypt.display.revocation
:members:

View File

@@ -1,5 +0,0 @@
:mod:`letsencrypt.recovery_token`
--------------------------------------------------
.. automodule:: letsencrypt.recovery_token
:members:

View File

@@ -1,5 +0,0 @@
:mod:`letsencrypt.revoker`
--------------------------
.. automodule:: letsencrypt.revoker
:members:

View File

@@ -68,8 +68,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

View File

@@ -0,0 +1,53 @@
:mod:`letsencrypt_compatibility_test`
-------------------------------------
.. automodule:: letsencrypt_compatibility_test
:members:
:mod:`letsencrypt_compatibility_test.errors`
============================================
.. automodule:: letsencrypt_compatibility_test.errors
:members:
:mod:`letsencrypt_compatibility_test.interfaces`
================================================
.. automodule:: letsencrypt_compatibility_test.interfaces
:members:
:mod:`letsencrypt_compatibility_test.test_driver`
=================================================
.. automodule:: letsencrypt_compatibility_test.test_driver
:members:
:mod:`letsencrypt_compatibility_test.util`
==========================================
.. automodule:: letsencrypt_compatibility_test.util
:members:
:mod:`letsencrypt_compatibility_test.configurators`
===================================================
.. automodule:: letsencrypt_compatibility_test.configurators
:members:
:mod:`letsencrypt_compatibility_test.configurators.apache`
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. automodule:: letsencrypt_compatibility_test.configurators.apache
:members:
:mod:`letsencrypt_compatibility_test.configurators.apache.apache24`
-------------------------------------------------------------------
.. automodule:: letsencrypt_compatibility_test.configurators.apache.apache24
:members:
:mod:`letsencrypt_compatibility_test.configurators.apache.common`
-------------------------------------------------------------------
.. automodule:: letsencrypt_compatibility_test.configurators.apache.common
:members:

View File

@@ -0,0 +1,11 @@
:mod:`letshelp_letsencrypt`
---------------------------
.. automodule:: letshelp_letsencrypt
:members:
:mod:`letshelp_letsencrypt.apache`
==================================
.. automodule:: letshelp_letsencrypt.apache
:members:

View File

@@ -0,0 +1,190 @@
Copyright 2015 Electronic Frontier Foundation and others
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1,2 +1,4 @@
include LICENSE.txt
include README.rst
recursive-include letsencrypt_apache/tests/testdata *
include letsencrypt_apache/options-ssl-apache.conf

View File

@@ -0,0 +1 @@
Apache plugin for Let's Encrypt client

View File

@@ -137,6 +137,12 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
:raises .errors.PluginError: If there is any other error
"""
# Verify Apache is installed
for exe in (self.conf("ctl"), self.conf("enmod"),
self.conf("dismod"), self.conf("init-script")):
if not le_util.exe_exists(exe):
raise errors.NoInstallationError
# Make sure configuration is valid
self.config_test()
@@ -1162,7 +1168,7 @@ def _get_mod_deps(mod_name):
changes.
.. warning:: If all deps are not included, it may cause incorrect parsing
behavior, due to enable_mod's shortcut for updating the parser's
currently defined modules (:method:`.ApacheConfigurator._add_parser_mod`)
currently defined modules (`.ApacheConfigurator._add_parser_mod`)
This would only present a major problem in extremely atypical
configs that use ifmod for the missing deps.

View File

@@ -37,8 +37,16 @@ class TwoVhost80Test(util.ApacheTest):
shutil.rmtree(self.config_dir)
shutil.rmtree(self.work_dir)
@mock.patch("letsencrypt_apache.configurator.le_util.exe_exists")
def test_prepare_no_install(self, mock_exe_exists):
mock_exe_exists.return_value = False
self.assertRaises(
errors.NoInstallationError, self.config.prepare)
@mock.patch("letsencrypt_apache.parser.ApacheParser")
def test_prepare_version(self, _):
@mock.patch("letsencrypt_apache.configurator.le_util.exe_exists")
def test_prepare_version(self, mock_exe_exists, _):
mock_exe_exists.return_value = True
self.config.version = None
self.config.config_test = mock.Mock()
self.config.get_version = mock.Mock(return_value=(1, 1))

View File

@@ -66,31 +66,34 @@ def get_apache_configurator(
"""
backups = os.path.join(work_dir, "backups")
mock_le_config = mock.MagicMock(
apache_server_root=config_path,
apache_le_vhost_ext=constants.CLI_DEFAULTS["le_vhost_ext"],
backup_dir=backups,
config_dir=config_dir,
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
work_dir=work_dir)
with mock.patch("letsencrypt_apache.configurator."
"subprocess.Popen") as mock_popen:
with mock.patch("letsencrypt_apache.parser.ApacheParser."
"update_runtime_variables"):
# This indicates config_test passes
mock_popen().communicate.return_value = ("Fine output", "No problems")
mock_popen().returncode = 0
# This indicates config_test passes
mock_popen().communicate.return_value = ("Fine output", "No problems")
mock_popen().returncode = 0
with mock.patch("letsencrypt_apache.configurator.le_util."
"exe_exists") as mock_exe_exists:
mock_exe_exists.return_value = True
with mock.patch("letsencrypt_apache.parser.ApacheParser."
"update_runtime_variables"):
config = configurator.ApacheConfigurator(
config=mock_le_config,
name="apache",
version=version)
# This allows testing scripts to set it a bit more quickly
if conf is not None:
config.conf = conf # pragma: no cover
config = configurator.ApacheConfigurator(
config=mock.MagicMock(
apache_server_root=config_path,
apache_le_vhost_ext=constants.CLI_DEFAULTS["le_vhost_ext"],
backup_dir=backups,
config_dir=config_dir,
temp_checkpoint_dir=os.path.join(work_dir, "temp_checkpoints"),
in_progress_dir=os.path.join(backups, "IN_PROGRESS"),
work_dir=work_dir),
name="apache",
version=version)
# This allows testing scripts to set it a bit more quickly
if conf is not None:
config.conf = conf # pragma: no cover
config.prepare()
config.prepare()
return config

View File

@@ -2,9 +2,11 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
install_requires = [
'acme',
'letsencrypt',
'acme=={0}'.format(version),
'letsencrypt=={0}'.format(version),
'mock<1.1.0', # py26
'python-augeas',
'setuptools', # pkg_resources
@@ -14,12 +16,35 @@ install_requires = [
setup(
name='letsencrypt-apache',
version=version,
description="Apache plugin for Let's Encrypt client",
url='https://github.com/letsencrypt/letsencrypt',
author="Let's Encrypt Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Plugins',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',
'Topic :: System :: Networking',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
entry_points={
'letsencrypt.plugins': [
'apache = letsencrypt_apache.configurator:ApacheConfigurator',
],
},
include_package_data=True,
)

View File

@@ -0,0 +1,190 @@
Copyright 2015 Electronic Frontier Foundation and others
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1 +1,3 @@
include LICENSE.txt
include README.rst
recursive-include letsencrypt_compatibility_test/testdata *

View File

@@ -0,0 +1 @@
Compatibility tests for Let's Encrypt client

View File

@@ -2,10 +2,12 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
install_requires = [
'letsencrypt',
'letsencrypt-apache',
'letsencrypt-nginx',
'letsencrypt=={0}'.format(version),
'letsencrypt-apache=={0}'.format(version),
'letsencrypt-nginx=={0}'.format(version),
'docker-py',
'mock<1.1.0', # py26
'zope.interface',
@@ -13,7 +15,25 @@ install_requires = [
setup(
name='letsencrypt-compatibility-test',
version=version,
description="Compatibility tests for Let's Encrypt client",
url='https://github.com/letsencrypt/letsencrypt',
author="Let's Encrypt Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: Developers',
'License :: OSI Approved :: Apache Software License',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
entry_points={
'console_scripts': [

View File

@@ -0,0 +1,190 @@
Copyright 2015 Electronic Frontier Foundation and others
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1,2 +1,4 @@
include LICENSE.txt
include README.rst
recursive-include letsencrypt_nginx/tests/testdata *
include letsencrypt_nginx/options-ssl-nginx.conf

View File

@@ -0,0 +1 @@
Nginx plugin for Let's Encrypt client

View File

@@ -56,7 +56,7 @@ class NginxConfigurator(common.Plugin):
zope.interface.implements(interfaces.IAuthenticator, interfaces.IInstaller)
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Nginx Web Server"
description = "Nginx Web Server - currently doesn't work"
@classmethod
def add_parser_arguments(cls, add):

View File

@@ -2,9 +2,11 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
install_requires = [
'acme',
'letsencrypt',
'acme=={0}'.format(version),
'letsencrypt=={0}'.format(version),
'mock<1.1.0', # py26
'PyOpenSSL',
'pyparsing>=1.5.5', # Python3 support; perhaps unnecessary?
@@ -14,12 +16,35 @@ install_requires = [
setup(
name='letsencrypt-nginx',
version=version,
description="Nginx plugin for Let's Encrypt client",
url='https://github.com/letsencrypt/letsencrypt',
author="Let's Encrypt Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Plugins',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',
'Topic :: System :: Networking',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
entry_points={
'letsencrypt.plugins': [
'nginx = letsencrypt_nginx.configurator:NginxConfigurator',
],
},
include_package_data=True,
)

View File

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

View File

@@ -54,7 +54,7 @@ class Account(object): # pylint: disable=too-few-public-methods
tz=pytz.UTC).replace(microsecond=0),
creation_host=socket.getfqdn()) if meta is None else meta
self.id = hashlib.md5( # pylint: disable=invalid-name
self.id = hashlib.md5(
self.key.key.public_key().public_bytes(
encoding=serialization.Encoding.DER,
format=serialization.PublicFormat.SubjectPublicKeyInfo)
@@ -92,13 +92,13 @@ def report_new_account(acc, config):
"contain certificates and private keys obtained by Let's Encrypt "
"so making regular backups of this folder is ideal.".format(
config.config_dir),
reporter.MEDIUM_PRIORITY, True)
reporter.MEDIUM_PRIORITY)
if acc.regr.body.emails:
recovery_msg = ("If you lose your account credentials, you can "
"recover through e-mails sent to {0}.".format(
", ".join(acc.regr.body.emails)))
reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY, True)
reporter.add_message(recovery_msg, reporter.HIGH_PRIORITY)
class AccountMemoryStorage(interfaces.AccountStorage):

View File

@@ -531,7 +531,7 @@ def _report_failed_challs(failed_achalls):
reporter = zope.component.getUtility(interfaces.IReporter)
for achalls in problems.itervalues():
reporter.add_message(
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY, True)
_generate_failed_chall_msg(achalls), reporter.MEDIUM_PRIORITY)
def _generate_failed_chall_msg(failed_achalls):

View File

@@ -80,8 +80,8 @@ More detailed help:
-h, --help [topic] print this message, or detailed help on a topic;
the available topics are:
all, apache, automation, nginx, paths, security, testing, or any of the
subcommands
all, apache, automation, manual, nginx, paths, security, testing, or any of
the subcommands
"""
@@ -267,6 +267,14 @@ def _treat_as_renewal(config, domains):
return None
def _report_new_cert(cert_path):
"""Reports the creation of a new certificate to the user."""
reporter_util = zope.component.getUtility(interfaces.IReporter)
reporter_util.add_message("Congratulations! Your certificate has been "
"saved at {0}.".format(cert_path),
reporter_util.MEDIUM_PRIORITY)
def _auth_from_domains(le_client, config, domains, plugins):
"""Authenticate and enroll certificate."""
# Note: This can raise errors... caught above us though.
@@ -292,6 +300,8 @@ def _auth_from_domains(le_client, config, domains, plugins):
if not lineage:
raise errors.Error("Certificate could not be obtained")
_report_new_cert(lineage.cert)
return lineage
@@ -365,6 +375,7 @@ def auth(args, config, plugins):
file=args.csr[0], data=args.csr[1], form="der"))
le_client.save_certificate(
certr, chain, args.cert_path, args.chain_path)
_report_new_cert(args.cert_path)
else:
domains = _find_domains(args, installer)
_auth_from_domains(le_client, config, domains, plugins)
@@ -420,7 +431,7 @@ def plugins_cmd(args, config, plugins): # TODO: Use IDisplay rather than print
logger.debug("Expected interfaces: %s", args.ifaces)
ifaces = [] if args.ifaces is None else args.ifaces
filtered = plugins.ifaces(ifaces)
filtered = plugins.visible().ifaces(ifaces)
logger.debug("Filtered plugins: %r", filtered)
if not args.init and not args.prepare:
@@ -489,9 +500,6 @@ class SilentParser(object): # pylint: disable=too-few-public-methods
self.parser.add_argument(*args, **kwargs)
HELP_TOPICS = ["all", "security", "paths", "automation", "testing", "plugins"]
class HelpfulArgumentParser(object):
"""Argparse Wrapper.
@@ -513,12 +521,13 @@ class HelpfulArgumentParser(object):
self.parser._add_config_file_help = False # pylint: disable=protected-access
self.silent_parser = SilentParser(self.parser)
self.verb = None
self.args = self.preprocess_args(args)
help1 = self.prescan_for_flag("-h", self.help_topics)
help2 = self.prescan_for_flag("--help", self.help_topics)
assert max(True, "a") == "a", "Gravity changed direction"
help_arg = max(help1, help2)
if help_arg == True:
if help_arg is True:
# just --help with no topic; avoid argparse altogether
print USAGE
sys.exit(0)
@@ -529,13 +538,22 @@ class HelpfulArgumentParser(object):
def preprocess_args(self, args):
"""Work around some limitations in argparse.
Currently, add the default verb "run" as a default.
Currently: add the default verb "run" as a default, and ensure that the
subcommand / verb comes last.
"""
if "-h" in args or "--help" in args:
# all verbs double as help arguments; don't get them confused
self.verb = "help"
return args
for token in args:
for i, token in enumerate(args):
if token in VERBS:
return args
return ["run"] + args
reordered = args[:i] + args[i+1:] + [args[i]]
self.verb = token
return reordered
self.verb = "run"
return args + ["run"]
def prescan_for_flag(self, flag, possible_arguments):
"""Checks cli input for flags.
@@ -711,79 +729,84 @@ 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"]
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")
def add_subparser(name, func): # pylint: disable=missing-docstring
subparser = subparsers.add_parser(
name, help=func.__doc__.splitlines()[0], description=func.__doc__)
def add_subparser(name): # pylint: disable=missing-docstring
if name == "plugins":
func = plugins_cmd
else:
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)
return subparser
# the order of add_subparser() calls is important: it defines the
# order in which subparser names will be displayed in --help
add_subparser("run", run)
parser_auth = add_subparser("auth", auth)
parser_install = add_subparser("install", install)
parser_revoke = add_subparser("revoke", revoke)
parser_rollback = add_subparser("rollback", rollback)
add_subparser("config_changes", config_changes)
parser_plugins = add_subparser("plugins", plugins_cmd)
# these add_subparser objects return objects to which arguments could be
# attached, but they have annoying arg ordering constrains so we use
# groups instead: https://github.com/letsencrypt/letsencrypt/issues/820
for v in VERBS:
add_subparser(v)
parser_auth.add_argument(
"--csr", type=read_file, help="Path to a Certificate Signing "
"Request (CSR) in DER format.")
parser_auth.add_argument(
"--cert-path", default=flag_default("auth_cert_path"),
help="When using --csr this is where certificate is saved.")
parser_auth.add_argument(
"--chain-path", default=flag_default("auth_chain_path"),
help="When using --csr this is where certificate chain is saved.")
helpful.add_group("auth", description="Options for modifying how a cert is obtained")
helpful.add_group("install", description="Options for modifying how a cert is deployed")
helpful.add_group("revoke", description="Options for revocation of certs")
helpful.add_group("rollback", description="Options for reverting config changes")
helpful.add_group("plugins", description="Plugin options")
parser_install.add_argument(
"--cert-path", required=True, help="Path to a certificate that "
"is going to be installed.")
parser_install.add_argument(
"--key-path", required=True, help="Accompanying private key")
parser_install.add_argument(
"--chain-path", help="Accompanying path to a certificate chain.")
parser_revoke.add_argument(
"--cert-path", type=read_file, help="Revoke a specific certificate.",
required=True)
parser_revoke.add_argument(
"--key-path", type=read_file,
help="Revoke certificate using its accompanying key. Useful if "
"Account Key is lost.")
helpful.add("auth",
"--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.")
parser_rollback.add_argument(
"--checkpoints", type=int, metavar="N",
default=flag_default("rollback_checkpoints"),
help="Revert configuration N number of checkpoints.")
parser_plugins.add_argument(
"--init", action="store_true", help="Initialize plugins.")
parser_plugins.add_argument(
"--prepare", action="store_true",
help="Initialize and prepare plugins.")
parser_plugins.add_argument(
"--authenticators", action="append_const", dest="ifaces",
const=interfaces.IAuthenticator,
help="Limit to authenticator plugins only.")
parser_plugins.add_argument(
"--installers", action="append_const", dest="ifaces",
const=interfaces.IInstaller, help="Limit to installer plugins only.")
helpful.add("plugins",
"--init", action="store_true", help="Initialize plugins.")
helpful.add("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.")
helpful.add("plugins",
"--installers", action="append_const", dest="ifaces",
const=interfaces.IInstaller, help="Limit to installer plugins only.")
def _paths_parser(helpful):
add = helpful.add
verb = helpful.verb
helpful.add_group(
"paths", description="Arguments changing execution paths & servers")
cph = "Path to where cert is saved (with auth), installed (with install --csr) or revoked."
if verb == "auth":
add("paths", "--cert-path", default=flag_default("auth_cert_path"), help=cph)
elif verb == "revoke":
add("paths", "--cert-path", type=read_file, required=True, help=cph)
else:
add("paths", "--cert-path", help=cph, required=(verb == "install"))
# revoke --key-path reads a file, install --key-path takes a string
add("paths", "--key-path", type=((verb == "revoke" and read_file) or str),
required=(verb == "install"),
help="Path to private key for cert creation or revocation (if account key is missing)")
default_cp = None
if verb == "auth":
default_cp = flag_default("auth_chain_path")
add("paths", "--chain-path", default=default_cp,
help="Accompanying path to a certificate chain.")
add("paths", "--config-dir", default=flag_default("config_dir"),
help=config_help("config_dir"))
add("paths", "--work-dir", default=flag_default("work_dir"),

View File

@@ -286,7 +286,7 @@ class Client(object):
"configured in the directories under {0}.").format(
cert.cli_config.renewal_configs_dir)
reporter = zope.component.getUtility(interfaces.IReporter)
reporter.add_message(msg, reporter.LOW_PRIORITY, True)
reporter.add_message(msg, reporter.LOW_PRIORITY)
def save_certificate(self, certr, chain_cert, cert_path, chain_path):
# pylint: disable=no-self-use

View File

@@ -4,7 +4,6 @@
is capable of handling the signatures.
"""
import datetime
import logging
import os
@@ -201,29 +200,26 @@ def valid_privkey(privkey):
return False
def _pyopenssl_load(data, method, types=(
OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)):
openssl_errors = []
for filetype in types:
try:
return method(filetype, data), filetype
except OpenSSL.crypto.Error as error: # TODO: anything else?
openssl_errors.append(error)
raise errors.Error("Unable to load: {0}".format(",".join(
str(error) for error in openssl_errors)))
def pyopenssl_load_certificate(data):
"""Load PEM/DER certificate.
:raises errors.Error:
"""
return _pyopenssl_load(data, OpenSSL.crypto.load_certificate)
openssl_errors = []
for file_type in (OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1):
try:
return OpenSSL.crypto.load_certificate(file_type, data), file_type
except OpenSSL.crypto.Error as error: # TODO: other errors?
openssl_errors.append(error)
raise errors.Error("Unable to load: {0}".format(",".join(
str(error) for error in openssl_errors)))
def _get_sans_from_cert_or_req(
cert_or_req_str, load_func, typ=OpenSSL.crypto.FILETYPE_PEM):
def _get_sans_from_cert_or_req(cert_or_req_str, load_func,
typ=OpenSSL.crypto.FILETYPE_PEM):
try:
cert_or_req = load_func(typ, cert_or_req_str)
except OpenSSL.crypto.Error as error:
@@ -261,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.

View File

@@ -11,7 +11,7 @@ from letsencrypt.display import util as display_util
logger = logging.getLogger(__name__)
# Define a helper function to avoid verbose code
util = zope.component.getUtility # pylint: disable=invalid-name
util = zope.component.getUtility
def ask(enhancement):

View File

@@ -12,7 +12,7 @@ from letsencrypt.display import util as display_util
logger = logging.getLogger(__name__)
# Define a helper function to avoid verbose code
util = zope.component.getUtility # pylint: disable=invalid-name
util = zope.component.getUtility
def choose_plugin(prepared, question):
@@ -65,7 +65,7 @@ def pick_plugin(config, default, plugins, question, ifaces):
# throw more UX-friendly error if default not in plugins
filtered = plugins.filter(lambda p_ep: p_ep.name == default)
else:
filtered = plugins.ifaces(ifaces)
filtered = plugins.visible().ifaces(ifaces)
filtered.init(config)
verified = filtered.verify(ifaces)

View File

@@ -15,15 +15,15 @@ logger = logging.getLogger(__name__)
# immediately.
_SIGNALS = ([signal.SIGTERM] if os.name == "nt" else
[signal.SIGTERM, signal.SIGHUP, signal.SIGQUIT,
signal.SIGXCPU, signal.SIGXFSZ, signal.SIGPWR])
signal.SIGXCPU, signal.SIGXFSZ])
class ErrorHandler(object):
"""Registers functions to be called if an exception or signal occurs.
This class allows you to register functions that will be called when
an exception or signal is encountered. The class works best as a
context manager. For example:
an exception (excluding SystemExit) or signal is encountered. The
class works best as a context manager. For example:
with ErrorHandler(cleanup_func):
do_something()
@@ -50,7 +50,8 @@ class ErrorHandler(object):
self.set_signal_handlers()
def __exit__(self, exec_type, exec_value, trace):
if exec_value is not None:
# SystemExit is ignored to properly handle forks that don't exec
if exec_type not in (None, SystemExit):
logger.debug("Encountered exception:\n%s", "".join(
traceback.format_exception(exec_type, exec_value, trace)))
self.call_registered()

View File

@@ -478,7 +478,7 @@ class IReporter(zope.interface.Interface):
LOW_PRIORITY = zope.interface.Attribute(
"Used to denote low priority messages")
def add_message(self, msg, priority, on_crash=False):
def add_message(self, msg, priority, on_crash=True):
"""Adds msg to the list of messages to be printed.
:param str msg: Message to be displayed to the user.

View File

@@ -25,7 +25,7 @@ class DialogHandler(logging.Handler): # pylint: disable=too-few-public-methods
logging.Handler.__init__(self, level)
self.height = height
self.width = width
# "dialog" collides with module name... pylint: disable=invalid-name
# "dialog" collides with module name...
self.d = dialog.Dialog() if d is None else d
self.lines = []

View File

@@ -23,10 +23,10 @@ def dest_namespace(name):
"""ArgumentParser dest namespace (prefix of all destinations)."""
return name.replace("-", "_") + "_"
private_ips_regex = re.compile( # pylint: disable=invalid-name
private_ips_regex = re.compile(
r"(^127\.0\.0\.1)|(^10\.)|(^172\.1[6-9]\.)|"
r"(^172\.2[0-9]\.)|(^172\.3[0-1]\.)|(^192\.168\.)")
hostname_regex = re.compile( # pylint: disable=invalid-name
hostname_regex = re.compile(
r"^(([a-z0-9]|[a-z0-9][a-z0-9\-]*[a-z0-9])\.)*[a-z]+$", re.IGNORECASE)
@@ -173,7 +173,7 @@ class Dvsni(object):
achall.chall.encode("token") + '.pem')
def _setup_challenge_cert(self, achall, s=None):
# pylint: disable=invalid-name
"""Generate and write out challenge certificate."""
cert_path = self.get_cert_path(achall)
key_path = self.get_key_path(achall)

View File

@@ -50,6 +50,11 @@ class PluginEntryPoint(object):
"""Description with name. Handy for UI."""
return "{0} ({1})".format(self.description, self.name)
@property
def hidden(self):
"""Should this plugin be hidden from UI?"""
return getattr(self.plugin_cls, "hidden", False)
def ifaces(self, *ifaces_groups):
"""Does plugin implements specified interface groups?"""
return not ifaces_groups or any(
@@ -183,6 +188,10 @@ class PluginsRegistry(collections.Mapping):
return type(self)(dict((name, plugin_ep) for name, plugin_ep
in self._plugins.iteritems() if pred(plugin_ep)))
def visible(self):
"""Filter plugins based on visibility."""
return self.filter(lambda plugin_ep: not plugin_ep.hidden)
def ifaces(self, *ifaces_groups):
"""Filter plugins based on interfaces."""
# pylint: disable=star-args

View File

@@ -182,6 +182,8 @@ binary for temporary key/certificate generation.""".replace("\n", "")
achall.account_key.public_key(), self.config.simple_http_port):
return response
else:
logger.error(
"Self-verify of challenge failed, authorization abandoned.")
if self.conf("test-mode") and self._httpd.poll() is not None:
# simply verify cause command failure...
return False

View File

@@ -17,6 +17,7 @@ class Installer(common.Plugin):
zope.interface.classProvides(interfaces.IPluginFactory)
description = "Null Installer"
hidden = True
# pylint: disable=missing-docstring,no-self-use

View File

@@ -36,7 +36,7 @@ class Reporter(object):
def __init__(self):
self.messages = Queue.PriorityQueue()
def add_message(self, msg, priority, on_crash=False):
def add_message(self, msg, priority, on_crash=True):
"""Adds msg to the list of messages to be printed.
:param str msg: Message to be displayed to the user.

View File

@@ -520,7 +520,7 @@ class RenewableCert(object): # pylint: disable=too-many-instance-attributes
remaining = expiry - now
if remaining < autorenew_interval:
return True
return False
return False
@classmethod
def new_lineage(cls, lineagename, cert, privkey, chain,

View File

@@ -355,7 +355,7 @@ class GenChallengePathTest(unittest.TestCase):
class MutuallyExclusiveTest(unittest.TestCase):
"""Tests for letsencrypt.auth_handler.mutually_exclusive."""
# pylint: disable=invalid-name,missing-docstring,too-few-public-methods
# pylint: disable=missing-docstring,too-few-public-methods
class A(object):
pass

View File

@@ -2,6 +2,7 @@
import itertools
import os
import shutil
import StringIO
import traceback
import tempfile
import unittest
@@ -16,6 +17,9 @@ from letsencrypt.tests import renewer_test
from letsencrypt.tests import test_util
CSR = test_util.vector_path('csr.der')
class CLITest(unittest.TestCase):
"""Tests for different commands."""
@@ -39,12 +43,52 @@ class CLITest(unittest.TestCase):
ret = cli.main(args)
return ret, stdout, stderr, client
def _call_stdout(self, args):
"""
Variant of _call that preserves stdout so that it can be mocked by the
caller.
"""
from letsencrypt import cli
args = ['--text', '--config-dir', self.config_dir,
'--work-dir', self.work_dir, '--logs-dir', self.logs_dir,
'--agree-eula'] + args
with mock.patch('letsencrypt.cli.sys.stderr') as stderr:
with mock.patch('letsencrypt.cli.client') as client:
ret = cli.main(args)
return ret, None, stderr, client
def test_no_flags(self):
self.assertRaises(SystemExit, self._call, [])
with mock.patch('letsencrypt.cli.run') as mock_run:
self._call([])
self.assertEqual(1, mock_run.call_count)
def test_help(self):
self.assertRaises(SystemExit, self._call, ['--help'])
self.assertRaises(SystemExit, self._call, ['--help all'])
self.assertRaises(SystemExit, self._call, ['--help', 'all'])
output = StringIO.StringIO()
with mock.patch('letsencrypt.cli.sys.stdout', new=output):
self.assertRaises(SystemExit, self._call_stdout, ['--help', 'all'])
out = output.getvalue()
self.assertTrue("--configurator" in out)
self.assertTrue("how a cert is deployed" in out)
self.assertTrue("--manual-test-mode" in out)
output.truncate(0)
self.assertRaises(SystemExit, self._call_stdout, ['-h', 'nginx'])
out = output.getvalue()
self.assertTrue("--nginx-ctl" in out)
self.assertTrue("--manual-test-mode" not in out)
self.assertTrue("--checkpoints" not in out)
output.truncate(0)
self.assertRaises(SystemExit, self._call_stdout, ['--help', 'plugins'])
out = output.getvalue()
self.assertTrue("--manual-test-mode" not in out)
self.assertTrue("--prepare" in out)
self.assertTrue("Plugin options" in out)
output.truncate(0)
self.assertRaises(SystemExit, self._call_stdout, ['-h'])
out = output.getvalue()
from letsencrypt import cli
self.assertTrue(cli.USAGE in out)
def test_rollback(self):
_, _, _, client = self._call(['rollback'])
@@ -65,40 +109,114 @@ class CLITest(unittest.TestCase):
for r in xrange(len(flags)))):
self._call(['plugins'] + list(args))
@mock.patch("letsencrypt.cli.sys")
def test_auth_bad_args(self):
ret, _, _, _ = self._call(['-d', 'foo.bar', 'auth', '--csr', CSR])
self.assertEqual(ret, '--domains and --csr are mutually exclusive')
ret, _, _, _ = self._call(['-a', 'bad_auth', 'auth'])
self.assertEqual(ret, 'Authenticator could not be determined')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
def test_auth_new_request_success(self, mock_get_utility):
cert_path = '/etc/letsencrypt/live/foo.bar'
mock_lineage = mock.MagicMock(cert=cert_path)
mock_client = mock.MagicMock()
mock_client.obtain_and_enroll_certificate.return_value = mock_lineage
self._auth_new_request_common(mock_client)
self.assertEqual(
mock_client.obtain_and_enroll_certificate.call_count, 1)
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
def test_auth_new_request_failure(self):
mock_client = mock.MagicMock()
mock_client.obtain_and_enroll_certificate.return_value = False
self.assertRaises(errors.Error,
self._auth_new_request_common, mock_client)
def _auth_new_request_common(self, mock_client):
with mock.patch('letsencrypt.cli._treat_as_renewal') as mock_renewal:
mock_renewal.return_value = None
with mock.patch('letsencrypt.cli._init_le_client') as mock_init:
mock_init.return_value = mock_client
self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth'])
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._treat_as_renewal')
@mock.patch('letsencrypt.cli._init_le_client')
def test_auth_renewal(self, mock_init, mock_renewal, mock_get_utility):
cert_path = '/etc/letsencrypt/live/foo.bar'
mock_lineage = mock.MagicMock(cert=cert_path)
mock_cert = mock.MagicMock(body='body')
mock_key = mock.MagicMock(pem='pem_key')
mock_renewal.return_value = mock_lineage
mock_client = mock.MagicMock()
mock_client.obtain_certificate.return_value = (mock_cert, 'chain',
mock_key, 'csr')
mock_init.return_value = mock_client
with mock.patch('letsencrypt.cli.OpenSSL'):
with mock.patch('letsencrypt.cli.crypto_util'):
self._call(['-d', 'foo.bar', '-a', 'standalone', 'auth'])
mock_client.obtain_certificate.assert_called_once_with(['foo.bar'])
self.assertEqual(mock_lineage.save_successor.call_count, 1)
mock_lineage.update_all_links_to.assert_called_once_with(
mock_lineage.latest_common_version())
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.display_ops.pick_installer')
@mock.patch('letsencrypt.cli.zope.component.getUtility')
@mock.patch('letsencrypt.cli._init_le_client')
def test_auth_csr(self, mock_init, mock_get_utility, mock_pick_installer):
cert_path = '/etc/letsencrypt/live/foo.bar'
mock_client = mock.MagicMock()
mock_client.obtain_certificate_from_csr.return_value = ('certr',
'chain')
mock_init.return_value = mock_client
installer = 'installer'
self._call(
['-a', 'standalone', '-i', installer, 'auth', '--csr', CSR,
'--cert-path', cert_path, '--chain-path', '/'])
self.assertEqual(mock_pick_installer.call_args[0][1], installer)
mock_client.save_certificate.assert_called_once_with(
'certr', 'chain', cert_path, '/')
self.assertTrue(
cert_path in mock_get_utility().add_message.call_args[0][0])
@mock.patch('letsencrypt.cli.sys')
def test_handle_exception(self, mock_sys):
# pylint: disable=protected-access
from letsencrypt import cli
mock_open = mock.mock_open()
with mock.patch("letsencrypt.cli.open", mock_open, create=True):
exception = Exception("detail")
with mock.patch('letsencrypt.cli.open', mock_open, create=True):
exception = Exception('detail')
cli._handle_exception(
Exception, exc_value=exception, trace=None, args=None)
mock_open().write.assert_called_once_with("".join(
mock_open().write.assert_called_once_with(''.join(
traceback.format_exception_only(Exception, exception)))
error_msg = mock_sys.exit.call_args_list[0][0][0]
self.assertTrue("unexpected error" in error_msg)
self.assertTrue('unexpected error' in error_msg)
with mock.patch("letsencrypt.cli.open", mock_open, create=True):
with mock.patch('letsencrypt.cli.open', mock_open, create=True):
mock_open.side_effect = [KeyboardInterrupt]
error = errors.Error("detail")
error = errors.Error('detail')
cli._handle_exception(
errors.Error, exc_value=error, trace=None, args=None)
# assert_any_call used because sys.exit doesn't exit in cli.py
mock_sys.exit.assert_any_call("".join(
mock_sys.exit.assert_any_call(''.join(
traceback.format_exception_only(errors.Error, error)))
args = mock.MagicMock(debug=False)
cli._handle_exception(
Exception, exc_value=Exception("detail"), trace=None, args=args)
Exception, exc_value=Exception('detail'), trace=None, args=args)
error_msg = mock_sys.exit.call_args_list[-1][0][0]
self.assertTrue("unexpected error" in error_msg)
self.assertTrue('unexpected error' in error_msg)
interrupt = KeyboardInterrupt("detail")
interrupt = KeyboardInterrupt('detail')
cli._handle_exception(
KeyboardInterrupt, exc_value=interrupt, trace=None, args=None)
mock_sys.exit.assert_called_with("".join(
mock_sys.exit.assert_called_with(''.join(
traceback.format_exception_only(KeyboardInterrupt, interrupt)))
@@ -108,13 +226,13 @@ class DetermineAccountTest(unittest.TestCase):
def setUp(self):
self.args = mock.MagicMock(account=None, email=None)
self.config = configuration.NamespaceConfig(self.args)
self.accs = [mock.MagicMock(id="x"), mock.MagicMock(id="y")]
self.accs = [mock.MagicMock(id='x'), mock.MagicMock(id='y')]
self.account_storage = account.AccountMemoryStorage()
def _call(self):
# pylint: disable=protected-access
from letsencrypt.cli import _determine_account
with mock.patch("letsencrypt.cli.account.AccountFileStorage") as mock_storage:
with mock.patch('letsencrypt.cli.account.AccountFileStorage') as mock_storage:
mock_storage.return_value = self.account_storage
return _determine_account(self.args, self.config)
@@ -131,7 +249,7 @@ class DetermineAccountTest(unittest.TestCase):
self.assertEqual(self.accs[0].id, self.args.account)
self.assertTrue(self.args.email is None)
@mock.patch("letsencrypt.client.display_ops.choose_account")
@mock.patch('letsencrypt.client.display_ops.choose_account')
def test_multiple_accounts(self, mock_choose_accounts):
for acc in self.accs:
self.account_storage.save(acc)
@@ -142,11 +260,11 @@ class DetermineAccountTest(unittest.TestCase):
self.assertEqual(self.accs[1].id, self.args.account)
self.assertTrue(self.args.email is None)
@mock.patch("letsencrypt.client.display_ops.get_email")
@mock.patch('letsencrypt.client.display_ops.get_email')
def test_no_accounts_no_email(self, mock_get_email):
mock_get_email.return_value = "foo@bar.baz"
mock_get_email.return_value = 'foo@bar.baz'
with mock.patch("letsencrypt.cli.client") as client:
with mock.patch('letsencrypt.cli.client') as client:
client.register.return_value = (
self.accs[0], mock.sentinel.acme)
self.assertEqual((self.accs[0], mock.sentinel.acme), self._call())
@@ -154,15 +272,15 @@ class DetermineAccountTest(unittest.TestCase):
self.config, self.account_storage, tos_cb=mock.ANY)
self.assertEqual(self.accs[0].id, self.args.account)
self.assertEqual("foo@bar.baz", self.args.email)
self.assertEqual('foo@bar.baz', self.args.email)
def test_no_accounts_email(self):
self.args.email = "other email"
with mock.patch("letsencrypt.cli.client") as client:
self.args.email = 'other email'
with mock.patch('letsencrypt.cli.client') as client:
client.register.return_value = (self.accs[1], mock.sentinel.acme)
self._call()
self.assertEqual(self.accs[1].id, self.args.account)
self.assertEqual("other email", self.args.email)
self.assertEqual('other email', self.args.email)
class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest):
@@ -176,36 +294,36 @@ class DuplicativeCertsTest(renewer_test.BaseRenewableCertTest):
def tearDown(self):
shutil.rmtree(self.tempdir)
@mock.patch("letsencrypt.le_util.make_or_verify_dir")
@mock.patch('letsencrypt.le_util.make_or_verify_dir')
def test_find_duplicative_names(self, unused_makedir):
from letsencrypt.cli import _find_duplicative_certs
test_cert = test_util.load_vector("cert-san.pem")
with open(self.test_rc.cert, "w") as f:
test_cert = test_util.load_vector('cert-san.pem')
with open(self.test_rc.cert, 'w') as f:
f.write(test_cert)
# No overlap at all
result = _find_duplicative_certs(["wow.net", "hooray.org"],
result = _find_duplicative_certs(['wow.net', 'hooray.org'],
self.config, self.cli_config)
self.assertEqual(result, (None, None))
# Totally identical
result = _find_duplicative_certs(["example.com", "www.example.com"],
result = _find_duplicative_certs(['example.com', 'www.example.com'],
self.config, self.cli_config)
self.assertTrue(result[0].configfile.filename.endswith("example.org.conf"))
self.assertTrue(result[0].configfile.filename.endswith('example.org.conf'))
self.assertEqual(result[1], None)
# Superset
result = _find_duplicative_certs(["example.com", "www.example.com",
"something.new"], self.config,
result = _find_duplicative_certs(['example.com', 'www.example.com',
'something.new'], self.config,
self.cli_config)
self.assertEqual(result[0], None)
self.assertTrue(result[1].configfile.filename.endswith("example.org.conf"))
self.assertTrue(result[1].configfile.filename.endswith('example.org.conf'))
# Partial overlap doesn't count
result = _find_duplicative_certs(["example.com", "something.new"],
result = _find_duplicative_certs(['example.com', 'something.new'],
self.config, self.cli_config)
self.assertEqual(result, (None, None))
if __name__ == "__main__":
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -8,6 +8,7 @@ import OpenSSL
import mock
import zope.component
from letsencrypt import errors
from letsencrypt import interfaces
from letsencrypt.tests import test_util
@@ -213,5 +214,23 @@ class GetSANsFromCSRTest(unittest.TestCase):
[], self._call(test_util.load_vector('csr-nosans.pem')))
class CertLoaderTest(unittest.TestCase):
"""Tests for letsencrypt.crypto_util.pyopenssl_load_certificate"""
def test_load_valid_cert(self):
from letsencrypt.crypto_util import pyopenssl_load_certificate
cert, file_type = pyopenssl_load_certificate(CERT)
self.assertEqual(cert.digest('sha1'),
OpenSSL.crypto.load_certificate(file_type, CERT).digest('sha1'))
def test_load_invalid_cert(self):
from letsencrypt.crypto_util import pyopenssl_load_certificate
bad_cert_data = CERT.replace("BEGIN CERTIFICATE", "ASDFASDFASDF!!!")
with self.assertRaises(errors.Error):
pyopenssl_load_certificate(bad_cert_data)
if __name__ == '__main__':
unittest.main() # pragma: no cover

View File

@@ -84,7 +84,7 @@ class PickPluginTest(unittest.TestCase):
def test_no_default(self):
self._call()
self.assertEqual(1, self.reg.ifaces.call_count)
self.assertEqual(1, self.reg.visible().ifaces.call_count)
def test_no_candidate(self):
self.assertTrue(self._call() is None)
@@ -94,7 +94,8 @@ class PickPluginTest(unittest.TestCase):
plugin_ep.init.return_value = "foo"
plugin_ep.misconfigured = False
self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep}
self.reg.visible().ifaces().verify().available.return_value = {
"bar": plugin_ep}
self.assertEqual("foo", self._call())
def test_single_misconfigured(self):
@@ -102,13 +103,14 @@ class PickPluginTest(unittest.TestCase):
plugin_ep.init.return_value = "foo"
plugin_ep.misconfigured = True
self.reg.ifaces().verify().available.return_value = {"bar": plugin_ep}
self.reg.visible().ifaces().verify().available.return_value = {
"bar": plugin_ep}
self.assertTrue(self._call() is None)
def test_multiple(self):
plugin_ep = mock.MagicMock()
plugin_ep.init.return_value = "foo"
self.reg.ifaces().verify().available.return_value = {
self.reg.visible().ifaces().verify().available.return_value = {
"bar": plugin_ep,
"baz": plugin_ep,
}
@@ -119,7 +121,7 @@ class PickPluginTest(unittest.TestCase):
[plugin_ep, plugin_ep], self.question)
def test_choose_plugin_none(self):
self.reg.ifaces().verify().available.return_value = {
self.reg.visible().ifaces().verify().available.return_value = {
"bar": None,
"baz": None,
}

View File

@@ -1,5 +1,6 @@
"""Tests for letsencrypt.error_handler."""
import signal
import sys
import unittest
import mock
@@ -50,6 +51,14 @@ class ErrorHandlerTest(unittest.TestCase):
self.init_func.assert_called_once_with()
bad_func.assert_called_once_with()
def test_sysexit_ignored(self):
try:
with self.handler:
sys.exit(0)
except SystemExit:
pass
self.assertFalse(self.init_func.called)
if __name__ == "__main__":
unittest.main() # pragma: no cover

View File

@@ -8,7 +8,7 @@ import mock
class DialogHandlerTest(unittest.TestCase):
def setUp(self):
self.d = mock.MagicMock() # pylint: disable=invalid-name
self.d = mock.MagicMock()
from letsencrypt.log import DialogHandler
self.handler = DialogHandler(height=2, width=6, d=self.d)

View File

@@ -82,9 +82,11 @@ class ReporterTest(unittest.TestCase):
self.assertTrue("Low" not in output)
def _add_messages(self):
self.reporter.add_message("High", self.reporter.HIGH_PRIORITY, True)
self.reporter.add_message("Med", self.reporter.MEDIUM_PRIORITY)
self.reporter.add_message("Low", self.reporter.LOW_PRIORITY)
self.reporter.add_message("High", self.reporter.HIGH_PRIORITY)
self.reporter.add_message(
"Med", self.reporter.MEDIUM_PRIORITY, on_crash=False)
self.reporter.add_message(
"Low", self.reporter.LOW_PRIORITY, on_crash=False)
if __name__ == "__main__":

View File

@@ -85,7 +85,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
self.assertEqual(read_in(self.config1), "directive-dir1")
def test_multiple_registration_fail_and_revert(self):
# pylint: disable=invalid-name
config3 = os.path.join(self.dir1, "config3.txt")
update_file(config3, "Config3")
config4 = os.path.join(self.dir2, "config4.txt")
@@ -173,7 +173,7 @@ class ReverterCheckpointLocalTest(unittest.TestCase):
self.assertRaises(errors.ReverterError, self.reverter.recovery_routine)
def test_recover_checkpoint_revert_temp_failures(self):
# pylint: disable=invalid-name
mock_recover = mock.MagicMock(
side_effect=errors.ReverterError("e"))
@@ -291,7 +291,7 @@ class TestFullCheckpointsReverter(unittest.TestCase):
errors.ReverterError, self.reverter.rollback_checkpoints, "one")
def test_rollback_finalize_checkpoint_valid_inputs(self):
# pylint: disable=invalid-name
config3 = self._setup_three_checkpoints()
# Check resulting backup directory
@@ -334,7 +334,7 @@ class TestFullCheckpointsReverter(unittest.TestCase):
@mock.patch("letsencrypt.reverter.os.rename")
def test_finalize_checkpoint_no_rename_directory(self, mock_rename):
# pylint: disable=invalid-name
self.reverter.add_to_checkpoint(self.sets[0], "perm save")
mock_rename.side_effect = OSError

View File

@@ -1 +0,0 @@
../../acme/acme/test_util.py

View File

@@ -0,0 +1,67 @@
"""Test utilities.
.. warning:: This module is not part of the public API.
"""
import os
import pkg_resources
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives import serialization
import OpenSSL
from acme import jose
def vector_path(*names):
"""Path to a test vector."""
return pkg_resources.resource_filename(
__name__, os.path.join('testdata', *names))
def load_vector(*names):
"""Load contents of a test vector."""
# luckily, resource_string opens file in binary mode
return pkg_resources.resource_string(
__name__, os.path.join('testdata', *names))
def _guess_loader(filename, loader_pem, loader_der):
_, ext = os.path.splitext(filename)
if ext.lower() == '.pem':
return loader_pem
elif ext.lower() == '.der':
return loader_der
else: # pragma: no cover
raise ValueError("Loader could not be recognized based on extension")
def load_cert(*names):
"""Load certificate."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate(
loader, load_vector(*names)))
def load_csr(*names):
"""Load certificate request."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return jose.ComparableX509(OpenSSL.crypto.load_certificate_request(
loader, load_vector(*names)))
def load_rsa_private_key(*names):
"""Load RSA private key."""
loader = _guess_loader(names[-1], serialization.load_pem_private_key,
serialization.load_der_private_key)
return jose.ComparableRSAKey(loader(
load_vector(*names), password=None, backend=default_backend()))
def load_pyopenssl_private_key(*names):
"""Load pyOpenSSL private key."""
loader = _guess_loader(
names[-1], OpenSSL.crypto.FILETYPE_PEM, OpenSSL.crypto.FILETYPE_ASN1)
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))

View File

@@ -0,0 +1,190 @@
Copyright 2015 Electronic Frontier Foundation and others
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS

View File

@@ -1 +1,3 @@
recursive-include letshelp-letsencrypt/testdata *
include LICENSE.txt
include README.rst
recursive-include letshelp_letsencrypt/testdata *

View File

@@ -0,0 +1 @@
Let's help Let's Encrypt client

View File

@@ -4,22 +4,46 @@ from setuptools import setup
from setuptools import find_packages
version = '0.1.0.dev0'
install_requires = [
'setuptools', # pkg_resources
]
if sys.version_info < (2, 7):
install_requires.append("mock<1.1.0")
install_requires.append('mock<1.1.0')
else:
install_requires.append("mock")
install_requires.append('mock')
setup(
name="letshelp-letsencrypt",
name='letshelp-letsencrypt',
version=version,
description="Let's help Let's Encrypt client",
url='https://github.com/letsencrypt/letsencrypt',
author="Let's Encrypt Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
classifiers=[
'Development Status :: 3 - Alpha',
'Intended Audience :: System Administrators',
'License :: OSI Approved :: Apache Software License',
'Operating System :: POSIX :: Linux',
'Programming Language :: Python',
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.7',
'Topic :: Internet :: WWW/HTTP',
'Topic :: Security',
'Topic :: System :: Installation/Setup',
'Topic :: System :: Networking',
'Topic :: System :: Systems Administration',
'Topic :: Utilities',
],
packages=find_packages(),
include_package_data=True,
install_requires=install_requires,
entry_points={
'console_scripts': [
"letshelp-letsencrypt-apache = letshelp_letsencrypt.apache:main",
'letshelp-letsencrypt-apache = letshelp_letsencrypt.apache:main',
],
},
include_package_data=True,
)

View File

@@ -11,3 +11,5 @@
-e .[docs]
-e letsencrypt-apache
-e letsencrypt-nginx
-e letsencrypt-compatibility-test
-e letshelp-letsencrypt

View File

@@ -24,13 +24,14 @@ 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')
meta = dict(re.findall(r"""__([a-z]+)__ = "([^"]+)""", read_file(init_fn)))
meta = dict(re.findall(r"""__([a-z]+)__ = '([^']+)""", read_file(init_fn)))
readme = read_file(os.path.join(here, 'README.rst'))
changes = read_file(os.path.join(here, 'CHANGES.rst'))
version = meta['version']
install_requires = [
'acme',
'acme=={0}'.format(version),
'ConfigArgParse',
'configobj',
'cryptography>=0.7', # load_pem_x509_certificate
@@ -56,6 +57,8 @@ dev_extras = [
# Pin astroid==1.3.5, pylint==1.4.2 as a workaround for #289
'astroid==1.3.5',
'pylint==1.4.2', # upstream #248
'twine',
'wheel',
]
docs_extras = [
@@ -75,13 +78,15 @@ testing_extras = [
setup(
name='letsencrypt',
version=meta['version'],
description="Let's Encrypt",
version=version,
description="Let's Encrypt client",
long_description=readme, # later: + '\n\n' + changes
url='https://github.com/letsencrypt/letsencrypt',
author="Let's Encrypt Project",
author_email='client-dev@letsencrypt.org',
license='Apache License 2.0',
url='https://letsencrypt.org',
classifiers=[
'Development Status :: 3 - Alpha',
'Environment :: Console',
'Environment :: Console :: Curses',
'Intended Audience :: System Administrators',
@@ -99,6 +104,8 @@ setup(
],
packages=find_packages(exclude=['docs', 'examples', 'tests', 'venv']),
include_package_data=True,
install_requires=install_requires,
extras_require={
'dev': dev_extras,
@@ -118,13 +125,9 @@ setup(
],
'letsencrypt.plugins': [
'manual = letsencrypt.plugins.manual:Authenticator',
# TODO: null should probably not be presented to the user
'null = letsencrypt.plugins.null:Installer',
'standalone = letsencrypt.plugins.standalone.authenticator'
':StandaloneAuthenticator',
],
},
zip_safe=False, # letsencrypt/tests/test_util.py is a symlink!
include_package_data=True,
)

View File

@@ -4,11 +4,17 @@
# ugh, go version output is like:
# go version go1.4.2 linux/amd64
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
GOVER=`go version | cut -d" " -f3 | cut -do -f2`
# version comparison
function verlte {
#OS X doesn't support version sorting; emulate with sed
if [ `uname` == 'Darwin' ]; then
[ "$1" = "`echo -e \"$1\n$2\" | sed 's/\b\([0-9]\)\b/0\1/g' \
| sort | sed 's/\b0\([0-9]\)/\1/g' | head -n1`" ]
else
[ "$1" = "`echo -e "$1\n$2" | sort -V | head -n1`" ]
fi
}
if ! verlte 1.5 "$GOVER" ; then

26
tests/mac-bootstrap.sh Executable file
View File

@@ -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

96
tools/dev-release.sh Executable file
View File

@@ -0,0 +1,96 @@
#!/bin/sh -xe
# Release dev packages to PyPI
version="0.0.0.dev$(date +%Y%m%d)"
DEV_RELEASE_BRANCH="dev-release"
# TODO: create a real release key instead of using Kuba's personal one
RELEASE_GPG_KEY="${RELEASE_GPG_KEY:-148C30F6F7E429337A72D992B00B9CC82D7ADF2C}"
# port for a local Python Package Index (used in testing)
PORT=${PORT:-1234}
# subpackages to be released
SUBPKGS=${SUBPKGS:-"acme letsencrypt_apache letsencrypt_nginx letshelp_letsencrypt"}
subpkgs_dirs="$(echo $SUBPKGS | sed s/_/-/g)"
# letsencrypt_compatibility_test is not packaged because:
# - it is not meant to be used by anyone else than Let's Encrypt devs
# - it causes problems when running nosetests - the latter tries to
# run everything that matches test*, while there are no unittests
# there
tag="v$version"
mv "dist.$version" "dist.$version.$(date +%s).bak" || true
git tag --delete "$tag" || true
root="$(mktemp -d -t le.$version.XXX)"
echo "Cloning into fresh copy at $root" # clean repo = no artificats
git clone . $root
cd $root
git branch -f "$DEV_RELEASE_BRANCH"
git checkout "$DEV_RELEASE_BRANCH"
for pkg_dir in $subpkgs_dirs
do
sed -i $x "s/^version.*/version = '$version'/" $pkg_dir/setup.py
done
sed -i "s/^__version.*/__version__ = '$version'/" letsencrypt/__init__.py
git add -p # interactive user input
git -c commit.gpgsign=true commit -m "Release $version"
git tag --local-user "$RELEASE_GPG_KEY" \
--sign --message "Release $version" "$tag"
echo "Preparing sdists and wheels"
for pkg_dir in . $subpkgs_dirs
do
cd $pkg_dir
python setup.py clean
rm -rf build dist
python setup.py sdist
python setup.py bdist_wheel
echo "Signing ($pkg_dir)"
for x in dist/*.tar.gz dist/*.whl
do
gpg2 --detach-sign --armor --sign $x
done
cd -
done
mkdir "dist.$version"
mv dist "dist.$version/letsencrypt"
for pkg_dir in $subpkgs_dirs
do
mv $pkg_dir/dist "dist.$version/$pkg_dir/"
done
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
# installed from local PyPI rather than current directory (repo root)
virtualenv --no-site-packages ../venv
. ../venv/bin/activate
# Now, use our local PyPI. --pre allows installation of pre-release (incl. dev)
pip install \
--pre \
--extra-index-url http://localhost:$PORT \
letsencrypt $SUBPKGS
# stop local PyPI
kill $!
# freeze before installing anythin else, so that we know end-user 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 "In order to upload packages run the following command:"
echo twine upload "$root/dist.$version/*/*"

View File

@@ -16,7 +16,7 @@ fi
cover () {
if [ "$1" = "letsencrypt" ]; then
min=96
min=97
elif [ "$1" = "acme" ]; then
min=100
elif [ "$1" = "letsencrypt_apache" ]; then