mirror of
https://github.com/certbot/certbot.git
synced 2026-06-13 18:50:20 -04:00
Merge remote-tracking branch 'upstream/master' into no-sites-available
This commit is contained in:
commit
2401ac522e
88 changed files with 1089 additions and 1168 deletions
|
|
@ -94,11 +94,13 @@ matrix:
|
|||
|
||||
|
||||
# Only build pushes to the master branch, PRs, and branches beginning with
|
||||
# `test-`. This reduces the number of simultaneous Travis runs, which speeds
|
||||
# turnaround time on review since there is a cap of 5 simultaneous runs.
|
||||
# `test-` or of the form `digit(s).digit(s).x`. This reduces the number of
|
||||
# simultaneous Travis runs, which speeds turnaround time on review since there
|
||||
# is a cap of on the number of simultaneous runs.
|
||||
branches:
|
||||
only:
|
||||
- master
|
||||
- /^\d+\.\d+\.x$/
|
||||
- /^test-.*$/
|
||||
|
||||
# container-based infrastructure
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@ from cryptography.hazmat.primitives import hashes
|
|||
import OpenSSL
|
||||
import requests
|
||||
|
||||
from acme import dns_resolver
|
||||
from acme import errors
|
||||
from acme import crypto_util
|
||||
from acme import fields
|
||||
|
|
@ -183,7 +182,7 @@ class KeyAuthorizationChallenge(_TokenChallenge):
|
|||
|
||||
Subclasses must implement this method, but they are likely to
|
||||
return completely different data structures, depending on what's
|
||||
necessary to complete the challenge. Interepretation of that
|
||||
necessary to complete the challenge. Interpretation of that
|
||||
return value must be known to the caller.
|
||||
|
||||
:param JWK account_key:
|
||||
|
|
@ -214,36 +213,24 @@ class DNS01Response(KeyAuthorizationChallengeResponse):
|
|||
def simple_verify(self, chall, domain, account_public_key):
|
||||
"""Simple verify.
|
||||
|
||||
This method no longer checks DNS records and is a simple wrapper
|
||||
around `KeyAuthorizationChallengeResponse.verify`.
|
||||
|
||||
:param challenges.DNS01 chall: Corresponding challenge.
|
||||
:param unicode domain: Domain name being verified.
|
||||
:param JWK account_public_key: Public key for the key pair
|
||||
being authorized.
|
||||
|
||||
:returns: ``True`` iff validation with the TXT records resolved from a
|
||||
DNS server is successful.
|
||||
:return: ``True`` iff verification of the key authorization was
|
||||
successful.
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
if not self.verify(chall, account_public_key):
|
||||
# pylint: disable=unused-argument
|
||||
verified = self.verify(chall, account_public_key)
|
||||
if not verified:
|
||||
logger.debug("Verification of key authorization in response failed")
|
||||
return False
|
||||
|
||||
validation_domain_name = chall.validation_domain_name(domain)
|
||||
validation = chall.validation(account_public_key)
|
||||
logger.debug("Verifying %s at %s...", chall.typ, validation_domain_name)
|
||||
|
||||
try:
|
||||
txt_records = dns_resolver.txt_records_for_name(
|
||||
validation_domain_name)
|
||||
except errors.DependencyError:
|
||||
raise errors.DependencyError("Local validation for 'dns-01' "
|
||||
"challenges requires 'dnspython'")
|
||||
exists = validation in txt_records
|
||||
if not exists:
|
||||
logger.debug("Key authorization from response (%r) doesn't match "
|
||||
"any DNS response in %r", self.key_authorization,
|
||||
txt_records)
|
||||
return exists
|
||||
return verified
|
||||
|
||||
|
||||
@Challenge.register # pylint: disable=too-many-ancestors
|
||||
|
|
|
|||
|
|
@ -10,7 +10,6 @@ from six.moves.urllib import parse as urllib_parse # pylint: disable=import-err
|
|||
from acme import errors
|
||||
from acme import jose
|
||||
from acme import test_util
|
||||
from acme.dns_resolver import DNS_REQUIREMENT
|
||||
|
||||
CERT = test_util.load_comparable_cert('cert.pem')
|
||||
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.pem'))
|
||||
|
|
@ -92,7 +91,6 @@ class DNS01ResponseTest(unittest.TestCase):
|
|||
from acme.challenges import DNS01
|
||||
self.chall = DNS01(token=(b'x' * 16))
|
||||
self.response = self.chall.response(KEY)
|
||||
self.records_for_name_path = "acme.dns_resolver.txt_records_for_name"
|
||||
|
||||
def test_to_partial_json(self):
|
||||
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
||||
|
|
@ -105,45 +103,16 @@ class DNS01ResponseTest(unittest.TestCase):
|
|||
from acme.challenges import DNS01Response
|
||||
hash(DNS01Response.from_json(self.jmsg))
|
||||
|
||||
def test_simple_verify_bad_key_authorization(self):
|
||||
def test_simple_verify_failure(self):
|
||||
key2 = jose.JWKRSA.load(test_util.load_vector('rsa256_key.pem'))
|
||||
self.response.simple_verify(self.chall, "local", key2.public_key())
|
||||
public_key = key2.public_key()
|
||||
verified = self.response.simple_verify(self.chall, "local", public_key)
|
||||
self.assertFalse(verified)
|
||||
|
||||
@mock.patch('acme.dns_resolver.DNS_AVAILABLE', False)
|
||||
def test_simple_verify_without_dns(self):
|
||||
self.assertRaises(
|
||||
errors.DependencyError, self.response.simple_verify,
|
||||
self.chall, 'local', KEY.public_key())
|
||||
|
||||
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
|
||||
"optional dependency dnspython is not available")
|
||||
def test_simple_verify_good_validation(self): # pragma: no cover
|
||||
with mock.patch(self.records_for_name_path) as mock_resolver:
|
||||
mock_resolver.return_value = [
|
||||
self.chall.validation(KEY.public_key())]
|
||||
self.assertTrue(self.response.simple_verify(
|
||||
self.chall, "local", KEY.public_key()))
|
||||
mock_resolver.assert_called_once_with(
|
||||
self.chall.validation_domain_name("local"))
|
||||
|
||||
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
|
||||
"optional dependency dnspython is not available")
|
||||
def test_simple_verify_good_validation_multitxts(self): # pragma: no cover
|
||||
with mock.patch(self.records_for_name_path) as mock_resolver:
|
||||
mock_resolver.return_value = [
|
||||
"!", self.chall.validation(KEY.public_key())]
|
||||
self.assertTrue(self.response.simple_verify(
|
||||
self.chall, "local", KEY.public_key()))
|
||||
mock_resolver.assert_called_once_with(
|
||||
self.chall.validation_domain_name("local"))
|
||||
|
||||
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
|
||||
"optional dependency dnspython is not available")
|
||||
def test_simple_verify_bad_validation(self): # pragma: no cover
|
||||
with mock.patch(self.records_for_name_path) as mock_resolver:
|
||||
mock_resolver.return_value = ["!"]
|
||||
self.assertFalse(self.response.simple_verify(
|
||||
self.chall, "local", KEY.public_key()))
|
||||
def test_simple_verify_success(self):
|
||||
public_key = KEY.public_key()
|
||||
verified = self.response.simple_verify(self.chall, "local", public_key)
|
||||
self.assertTrue(verified)
|
||||
|
||||
|
||||
class DNS01Test(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -674,8 +674,23 @@ class ClientNetwork(object): # pylint: disable=too-many-instance-attributes
|
|||
self._add_nonce(self.head(url))
|
||||
return self._nonces.pop()
|
||||
|
||||
def post(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
|
||||
"""POST object wrapped in `.JWS` and check response."""
|
||||
def post(self, *args, **kwargs):
|
||||
"""POST object wrapped in `.JWS` and check response.
|
||||
|
||||
If the server responded with a badNonce error, the request will
|
||||
be retried once.
|
||||
|
||||
"""
|
||||
try:
|
||||
return self._post_once(*args, **kwargs)
|
||||
except messages.Error as error:
|
||||
if error.code == 'badNonce':
|
||||
logger.debug('Retrying request after error:\n%s', error)
|
||||
return self._post_once(*args, **kwargs)
|
||||
else:
|
||||
raise
|
||||
|
||||
def _post_once(self, url, obj, content_type=JOSE_CONTENT_TYPE, **kwargs):
|
||||
data = self._wrap_in_jws(obj, self._get_nonce(url))
|
||||
kwargs.setdefault('headers', {'Content-Type': content_type})
|
||||
response = self._send_request('POST', url, data=data, **kwargs)
|
||||
|
|
|
|||
|
|
@ -170,7 +170,7 @@ class ClientTest(unittest.TestCase):
|
|||
self.directory.new_authz,
|
||||
messages.NewAuthorization(identifier=self.identifier))
|
||||
|
||||
def test_requets_challenges_custom_uri(self):
|
||||
def test_request_challenges_custom_uri(self):
|
||||
self._prepare_response_for_request_challenges()
|
||||
self.client.request_challenges(self.identifier, 'URI')
|
||||
self.net.post.assert_called_once_with('URI', mock.ANY)
|
||||
|
|
@ -388,7 +388,7 @@ class ClientTest(unittest.TestCase):
|
|||
errors.PollError, self.client.poll_and_request_issuance,
|
||||
csr, authzrs=(invalid_authzr,), mintime=mintime)
|
||||
|
||||
# exceeded max_attemps | TODO: move to a separate test
|
||||
# exceeded max_attempts | TODO: move to a separate test
|
||||
self.assertRaises(
|
||||
errors.PollError, self.client.poll_and_request_issuance,
|
||||
csr, authzrs, mintime=mintime, max_attempts=2)
|
||||
|
|
@ -642,7 +642,9 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
|
|||
self.wrapped_obj = mock.MagicMock()
|
||||
self.content_type = mock.sentinel.content_type
|
||||
|
||||
self.all_nonces = [jose.b64encode(b'Nonce'), jose.b64encode(b'Nonce2')]
|
||||
self.all_nonces = [
|
||||
jose.b64encode(b'Nonce'),
|
||||
jose.b64encode(b'Nonce2'), jose.b64encode(b'Nonce3')]
|
||||
self.available_nonces = self.all_nonces[:]
|
||||
|
||||
def send_request(*args, **kwargs):
|
||||
|
|
@ -690,7 +692,7 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
|
|||
self.net._wrap_in_jws.assert_called_once_with(
|
||||
self.obj, jose.b64decode(self.all_nonces.pop()))
|
||||
|
||||
assert not self.available_nonces
|
||||
self.available_nonces = []
|
||||
self.assertRaises(errors.MissingNonce, self.net.post,
|
||||
'uri', self.obj, content_type=self.content_type)
|
||||
self.net._wrap_in_jws.assert_called_with(
|
||||
|
|
@ -706,6 +708,35 @@ class ClientNetworkWithMockedResponseTest(unittest.TestCase):
|
|||
self.assertRaises(errors.BadNonce, self.net.post, 'uri',
|
||||
self.obj, content_type=self.content_type)
|
||||
|
||||
def test_post_failed_retry(self):
|
||||
check_response = mock.MagicMock()
|
||||
check_response.side_effect = messages.Error.with_code('badNonce')
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.net._check_response = check_response
|
||||
self.assertRaises(messages.Error, self.net.post, 'uri',
|
||||
self.obj, content_type=self.content_type)
|
||||
|
||||
def test_post_not_retried(self):
|
||||
check_response = mock.MagicMock()
|
||||
check_response.side_effect = [messages.Error.with_code('malformed'),
|
||||
self.checked_response]
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.net._check_response = check_response
|
||||
self.assertRaises(messages.Error, self.net.post, 'uri',
|
||||
self.obj, content_type=self.content_type)
|
||||
|
||||
def test_post_successful_retry(self):
|
||||
check_response = mock.MagicMock()
|
||||
check_response.side_effect = [messages.Error.with_code('badNonce'),
|
||||
self.checked_response]
|
||||
|
||||
# pylint: disable=protected-access
|
||||
self.net._check_response = check_response
|
||||
self.assertEqual(self.checked_response, self.net.post(
|
||||
'uri', self.obj, content_type=self.content_type))
|
||||
|
||||
def test_head_get_post_error_passthrough(self):
|
||||
self.send_request.side_effect = requests.exceptions.RequestException
|
||||
for method in self.net.head, self.net.get:
|
||||
|
|
|
|||
|
|
@ -59,7 +59,7 @@ class SSLSocketAndProbeSNITest(unittest.TestCase):
|
|||
def test_probe_not_recognized_name(self):
|
||||
self.assertRaises(errors.Error, self._probe, b'bar')
|
||||
|
||||
# TODO: py33/py34 tox hangs forever on do_hendshake in second probe
|
||||
# TODO: py33/py34 tox hangs forever on do_handshake in second probe
|
||||
#def probe_connection_error(self):
|
||||
# self._probe(b'foo')
|
||||
# #time.sleep(1) # TODO: avoid race conditions in other way
|
||||
|
|
|
|||
|
|
@ -1,45 +0,0 @@
|
|||
"""DNS Resolver for ACME client.
|
||||
Required only for local validation of 'dns-01' challenges.
|
||||
"""
|
||||
import logging
|
||||
|
||||
from acme import errors
|
||||
from acme import util
|
||||
|
||||
DNS_REQUIREMENT = 'dnspython>=1.12'
|
||||
|
||||
try:
|
||||
util.activate(DNS_REQUIREMENT)
|
||||
# pragma: no cover
|
||||
import dns.exception
|
||||
import dns.resolver
|
||||
DNS_AVAILABLE = True
|
||||
except errors.DependencyError: # pragma: no cover
|
||||
DNS_AVAILABLE = False
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def txt_records_for_name(name):
|
||||
"""Resolve the name and return the TXT records.
|
||||
|
||||
:param unicode name: Domain name being verified.
|
||||
|
||||
:returns: A list of txt records, if empty the name could not be resolved
|
||||
:rtype: list of unicode
|
||||
|
||||
"""
|
||||
if not DNS_AVAILABLE:
|
||||
raise errors.DependencyError(
|
||||
'{0} is required to use this function'.format(DNS_REQUIREMENT))
|
||||
try:
|
||||
dns_response = dns.resolver.query(name, 'TXT')
|
||||
except dns.resolver.NXDOMAIN as error:
|
||||
return []
|
||||
except dns.exception.DNSException as error:
|
||||
logger.error("Error resolving %s: %s", name, str(error))
|
||||
return []
|
||||
|
||||
return [txt_rec.decode("utf-8") for rdata in dns_response
|
||||
for txt_rec in rdata.strings]
|
||||
|
|
@ -1,77 +0,0 @@
|
|||
"""Tests for acme.dns_resolver."""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
from six.moves import reload_module # pylint: disable=import-error
|
||||
|
||||
from acme import errors
|
||||
from acme import test_util
|
||||
from acme.dns_resolver import DNS_REQUIREMENT
|
||||
|
||||
|
||||
if test_util.requirement_available(DNS_REQUIREMENT):
|
||||
import dns
|
||||
|
||||
|
||||
def create_txt_response(name, txt_records):
|
||||
"""
|
||||
Returns an RRSet containing the 'txt_records' as the result of a DNS
|
||||
query for 'name'.
|
||||
|
||||
This takes advantage of the fact that an Answer object mostly behaves
|
||||
like an RRset.
|
||||
"""
|
||||
return dns.rrset.from_text_list(name, 60, "IN", "TXT", txt_records)
|
||||
|
||||
|
||||
class TxtRecordsForNameTest(unittest.TestCase):
|
||||
"""Tests for acme.dns_resolver.txt_records_for_name."""
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from acme.dns_resolver import txt_records_for_name
|
||||
return txt_records_for_name(*args, **kwargs)
|
||||
|
||||
|
||||
@test_util.skip_unless(test_util.requirement_available(DNS_REQUIREMENT),
|
||||
"optional dependency dnspython is not available")
|
||||
class TxtRecordsForNameWithDnsTest(TxtRecordsForNameTest):
|
||||
"""Tests for acme.dns_resolver.txt_records_for_name with dns."""
|
||||
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||
def test_txt_records_for_name_with_single_response(self, mock_dns):
|
||||
mock_dns.return_value = create_txt_response('name', ['response'])
|
||||
self.assertEqual(['response'], self._call('name'))
|
||||
|
||||
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||
def test_txt_records_for_name_with_multiple_responses(self, mock_dns):
|
||||
mock_dns.return_value = create_txt_response(
|
||||
'name', ['response1', 'response2'])
|
||||
self.assertEqual(['response1', 'response2'], self._call('name'))
|
||||
|
||||
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||
def test_txt_records_for_name_domain_not_found(self, mock_dns):
|
||||
mock_dns.side_effect = dns.resolver.NXDOMAIN
|
||||
self.assertEquals([], self._call('name'))
|
||||
|
||||
@mock.patch("acme.dns_resolver.dns.resolver.query")
|
||||
def test_txt_records_for_name_domain_other_error(self, mock_dns):
|
||||
mock_dns.side_effect = dns.exception.DNSException
|
||||
self.assertEquals([], self._call('name'))
|
||||
|
||||
|
||||
class TxtRecordsForNameWithoutDnsTest(TxtRecordsForNameTest):
|
||||
"""Tests for acme.dns_resolver.txt_records_for_name without dns."""
|
||||
def setUp(self):
|
||||
from acme import dns_resolver
|
||||
dns_resolver.DNS_AVAILABLE = False
|
||||
|
||||
def tearDown(self):
|
||||
from acme import dns_resolver
|
||||
reload_module(dns_resolver)
|
||||
|
||||
def test_exception_raised(self):
|
||||
self.assertRaises(
|
||||
errors.DependencyError, self._call, "example.org")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -1,6 +1,6 @@
|
|||
"""Javascript Object Signing and Encryption (jose).
|
||||
|
||||
This package is a Python implementation of the stadards developed by
|
||||
This package is a Python implementation of the standards developed by
|
||||
IETF `Javascript Object Signing and Encryption (Active WG)`_, in
|
||||
particular the following RFCs:
|
||||
|
||||
|
|
|
|||
|
|
@ -60,7 +60,7 @@ class Field(object):
|
|||
|
||||
@classmethod
|
||||
def _empty(cls, value):
|
||||
"""Is the provided value cosidered "empty" for this field?
|
||||
"""Is the provided value considered "empty" for this field?
|
||||
|
||||
This is useful for subclasses that might want to override the
|
||||
definition of being empty, e.g. for some more exotic data types.
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ class JWK(json_util.TypedJSONObjectWithFields):
|
|||
try:
|
||||
key = cls._load_cryptography_key(data, password, backend)
|
||||
except errors.Error as error:
|
||||
logger.debug('Loading symmetric key, assymentric failed: %s', error)
|
||||
logger.debug('Loading symmetric key, asymmetric failed: %s', error)
|
||||
return JWKOct(key=data)
|
||||
|
||||
if cls.typ is not NotImplemented and not isinstance(
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ class ErrorTest(unittest.TestCase):
|
|||
'type': ERROR_PREFIX + 'malformed',
|
||||
}
|
||||
self.error_custom = Error(typ='custom', detail='bar')
|
||||
self.jobj_cusom = {'type': 'custom', 'detail': 'bar'}
|
||||
self.jobj_custom = {'type': 'custom', 'detail': 'bar'}
|
||||
|
||||
def test_default_typ(self):
|
||||
from acme.messages import Error
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ from cryptography.hazmat.backends import default_backend
|
|||
from cryptography.hazmat.primitives import serialization
|
||||
import OpenSSL
|
||||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
from acme import util
|
||||
|
||||
|
||||
def vector_path(*names):
|
||||
|
|
@ -78,20 +76,6 @@ def load_pyopenssl_private_key(*names):
|
|||
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
|
||||
|
||||
|
||||
def requirement_available(requirement):
|
||||
"""Checks if requirement can be imported.
|
||||
|
||||
:rtype: bool
|
||||
:returns: ``True`` iff requirement can be imported
|
||||
|
||||
"""
|
||||
try:
|
||||
util.activate(requirement)
|
||||
except errors.DependencyError: # pragma: no cover
|
||||
return False
|
||||
return True # pragma: no cover
|
||||
|
||||
|
||||
def skip_unless(condition, reason): # pragma: no cover
|
||||
"""Skip tests unless a condition holds.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,25 +1,7 @@
|
|||
"""ACME utilities."""
|
||||
import pkg_resources
|
||||
import six
|
||||
|
||||
from acme import errors
|
||||
|
||||
|
||||
def map_keys(dikt, func):
|
||||
"""Map dictionary keys."""
|
||||
return dict((func(key), value) for key, value in six.iteritems(dikt))
|
||||
|
||||
|
||||
def activate(requirement):
|
||||
"""Make requirement importable.
|
||||
|
||||
:param str requirement: the distribution and version to activate
|
||||
|
||||
:raises acme.errors.DependencyError: if cannot activate requirement
|
||||
|
||||
"""
|
||||
try:
|
||||
for distro in pkg_resources.require(requirement): # pylint: disable=not-callable
|
||||
distro.activate()
|
||||
except (pkg_resources.DistributionNotFound, pkg_resources.VersionConflict):
|
||||
raise errors.DependencyError('{0} is unavailable'.format(requirement))
|
||||
|
|
|
|||
|
|
@ -1,8 +1,6 @@
|
|||
"""Tests for acme.util."""
|
||||
import unittest
|
||||
|
||||
from acme import errors
|
||||
|
||||
|
||||
class MapKeysTest(unittest.TestCase):
|
||||
"""Tests for acme.util.map_keys."""
|
||||
|
|
@ -14,21 +12,5 @@ class MapKeysTest(unittest.TestCase):
|
|||
self.assertEqual({2: 2, 4: 4}, map_keys({1: 2, 3: 4}, lambda x: x + 1))
|
||||
|
||||
|
||||
class ActivateTest(unittest.TestCase):
|
||||
"""Tests for acme.util.activate."""
|
||||
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from acme.util import activate
|
||||
return activate(*args, **kwargs)
|
||||
|
||||
def test_failure(self):
|
||||
self.assertRaises(errors.DependencyError, self._call, 'acme>99.0.0')
|
||||
|
||||
def test_success(self):
|
||||
self._call('acme')
|
||||
import acme as unused_acme
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.11.0.dev0'
|
||||
version = '0.12.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
@ -33,11 +33,6 @@ if sys.version_info < (2, 7):
|
|||
else:
|
||||
install_requires.append('mock')
|
||||
|
||||
# dnspython 1.12 is required to support both Python 2 and Python 3.
|
||||
dns_extras = [
|
||||
'dnspython>=1.12',
|
||||
]
|
||||
|
||||
dev_extras = [
|
||||
'nose',
|
||||
'tox',
|
||||
|
|
@ -77,7 +72,6 @@ setup(
|
|||
include_package_data=True,
|
||||
install_requires=install_requires,
|
||||
extras_require={
|
||||
'dns': dns_extras,
|
||||
'dev': dev_extras,
|
||||
'docs': docs_extras,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -472,7 +472,7 @@ class ApacheConfigurator(augeas_configurator.AugeasConfigurator):
|
|||
"\n\nUnfortunately mod_macro is not yet supported".format(
|
||||
"\n ".join(vhost_macro)), force_interactive=True)
|
||||
|
||||
return all_names
|
||||
return util.get_filtered_names(all_names)
|
||||
|
||||
def get_name_from_ip(self, addr): # pylint: disable=no-self-use
|
||||
"""Returns a reverse dns name if available.
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ class Addr(common.Addr):
|
|||
"""Represents an Apache address."""
|
||||
|
||||
def __eq__(self, other):
|
||||
"""This is defined as equalivalent within Apache.
|
||||
"""This is defined as equivalent within Apache.
|
||||
|
||||
ip_addr:* == ip_addr
|
||||
|
||||
|
|
|
|||
|
|
@ -263,7 +263,7 @@
|
|||
#
|
||||
# Set the following policy settings here and they will be propagated to the 30 rules
|
||||
# file (modsecurity_crs_30_http_policy.conf) by using macro expansion.
|
||||
# If you run into false positves, you can adjust the settings here.
|
||||
# If you run into false positives, you can adjust the settings here.
|
||||
#
|
||||
#SecAction \
|
||||
"id:'900012', \
|
||||
|
|
@ -349,7 +349,7 @@
|
|||
|
||||
|
||||
#
|
||||
# -- [[ Check UTF enconding ]] -----------------------------------------------------------
|
||||
# -- [[ Check UTF encoding ]] -----------------------------------------------------------
|
||||
#
|
||||
# We only want to apply this check if UTF-8 encoding is actually used by the site, otherwise
|
||||
# it will result in false positives.
|
||||
|
|
|
|||
|
|
@ -126,8 +126,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
mock_getutility.notification = mock.MagicMock(return_value=True)
|
||||
names = self.config.get_all_names()
|
||||
self.assertEqual(names, set(
|
||||
["certbot.demo", "ocspvhost.com", "encryption-example.demo",
|
||||
"ip-172-30-0-17", "*.blue.purple.com"]))
|
||||
["certbot.demo", "ocspvhost.com", "encryption-example.demo"]
|
||||
))
|
||||
|
||||
@certbot_util.patch_get_utility()
|
||||
@mock.patch("certbot_apache.configurator.socket.gethostbyaddr")
|
||||
|
|
@ -146,7 +146,8 @@ class MultipleVhostsTest(util.ApacheTest):
|
|||
self.config.vhosts.append(vhost)
|
||||
|
||||
names = self.config.get_all_names()
|
||||
self.assertEqual(len(names), 7)
|
||||
# Names get filtered, only 5 are returned
|
||||
self.assertEqual(len(names), 5)
|
||||
self.assertTrue("zombo.com" in names)
|
||||
self.assertTrue("google.com" in names)
|
||||
self.assertTrue("certbot.demo" in names)
|
||||
|
|
|
|||
|
|
@ -178,7 +178,7 @@ class ParserInitTest(util.ApacheTest):
|
|||
shutil.rmtree(self.work_dir)
|
||||
|
||||
@mock.patch("certbot_apache.parser.ApacheParser._get_runtime_cfg")
|
||||
def test_unparsable(self, mock_cfg):
|
||||
def test_unparseable(self, mock_cfg):
|
||||
from certbot_apache.parser import ApacheParser
|
||||
mock_cfg.return_value = ('Define: TEST')
|
||||
self.assertRaises(
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ class ApacheTlsSni01(common.TLSSNI01):
|
|||
ips = " ".join(str(i) for i in ip_addrs)
|
||||
document_root = os.path.join(
|
||||
self.configurator.config.work_dir, "tls_sni_01_page/")
|
||||
# TODO: Python docs is not clear how mutliline string literal
|
||||
# TODO: Python docs is not clear how multiline string literal
|
||||
# newlines are parsed on different platforms. At least on
|
||||
# Linux (Debian sid), when source file uses CRLF, Python still
|
||||
# parses it as "\n"... c.f.:
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.11.0.dev0'
|
||||
version = '0.12.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
|
|||
234
certbot-auto
234
certbot-auto
|
|
@ -23,7 +23,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
|
||||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
LE_AUTO_VERSION="0.10.1"
|
||||
LE_AUTO_VERSION="0.11.0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -38,8 +38,9 @@ Help for certbot itself cannot be provided until it is installed.
|
|||
-n, --non-interactive, --noninteractive run without asking for user input
|
||||
--no-self-upgrade do not download updates
|
||||
--os-packages-only install OS dependencies and exit
|
||||
-q, --quiet provide only update/error output
|
||||
-v, --verbose provide more output
|
||||
-q, --quiet provide only update/error output;
|
||||
implies --non-interactive
|
||||
|
||||
All arguments are accepted and forwarded to the Certbot client when run."
|
||||
|
||||
|
|
@ -84,6 +85,11 @@ if [ $BASENAME = "letsencrypt-auto" ]; then
|
|||
HELP=0
|
||||
fi
|
||||
|
||||
# Set ASSUME_YES to 1 if QUIET (i.e. --quiet implies --non-interactive)
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
ASSUME_YES=1
|
||||
fi
|
||||
|
||||
# Support for busybox and others where there is no "command",
|
||||
# but "which" instead
|
||||
if command -v command > /dev/null 2>&1 ; then
|
||||
|
|
@ -99,7 +105,7 @@ fi
|
|||
# certbot itself needs root access for almost all modes of operation
|
||||
# The "normal" case is that sudo is used for the steps that need root, but
|
||||
# this script *can* be run as root (not recommended), or fall back to using
|
||||
# `su`. Auto-detection can be overrided by explicitly setting the
|
||||
# `su`. Auto-detection can be overridden by explicitly setting the
|
||||
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
|
||||
|
||||
# Because the parameters in `su -c` has to be a string,
|
||||
|
|
@ -207,7 +213,11 @@ BootstrapDebCommon() {
|
|||
#
|
||||
# - Debian 6.0.10 "squeeze" (x64)
|
||||
|
||||
$SUDO apt-get update || echo apt-get update hit problems but continuing anyway...
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='-qq'
|
||||
fi
|
||||
|
||||
$SUDO apt-get $QUIET_FLAG update || echo apt-get update hit problems but continuing anyway...
|
||||
|
||||
# virtualenv binary can be found in different packages depending on
|
||||
# distro version (#346)
|
||||
|
|
@ -215,74 +225,74 @@ BootstrapDebCommon() {
|
|||
virtualenv=
|
||||
# virtual env is known to apt and is installable
|
||||
if apt-cache show virtualenv > /dev/null 2>&1 ; then
|
||||
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
fi
|
||||
|
||||
if apt-cache show python-virtualenv > /dev/null 2>&1; then
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
fi
|
||||
|
||||
augeas_pkg="libaugeas0 augeas-lenses"
|
||||
AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
|
||||
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
YES_FLAG="-y"
|
||||
YES_FLAG="-y"
|
||||
fi
|
||||
|
||||
AddBackportRepo() {
|
||||
# ARGS:
|
||||
BACKPORT_NAME="$1"
|
||||
BACKPORT_SOURCELINE="$2"
|
||||
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
|
||||
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
|
||||
sleep 1s
|
||||
add_backports=1
|
||||
else
|
||||
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
|
||||
case $response in
|
||||
[yY][eE][sS]|[yY]|"")
|
||||
add_backports=1;;
|
||||
*)
|
||||
add_backports=0;;
|
||||
esac
|
||||
fi
|
||||
if [ "$add_backports" = 1 ]; then
|
||||
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO apt-get update
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$add_backports" != 0 ]; then
|
||||
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
|
||||
augeas_pkg=
|
||||
# ARGS:
|
||||
BACKPORT_NAME="$1"
|
||||
BACKPORT_SOURCELINE="$2"
|
||||
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
|
||||
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
|
||||
sleep 1s
|
||||
add_backports=1
|
||||
else
|
||||
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
|
||||
case $response in
|
||||
[yY][eE][sS]|[yY]|"")
|
||||
add_backports=1;;
|
||||
*)
|
||||
add_backports=0;;
|
||||
esac
|
||||
fi
|
||||
if [ "$add_backports" = 1 ]; then
|
||||
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO apt-get $QUIET_FLAG update
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$add_backports" != 0 ]; then
|
||||
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
|
||||
augeas_pkg=
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
|
||||
if lsb_release -a | grep -q wheezy ; then
|
||||
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
|
||||
elif lsb_release -a | grep -q precise ; then
|
||||
# XXX add ARM case
|
||||
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
|
||||
else
|
||||
echo "No libaugeas0 version is available that's new enough to run the"
|
||||
echo "Certbot apache plugin..."
|
||||
fi
|
||||
# XXX add a case for ubuntu PPAs
|
||||
if lsb_release -a | grep -q wheezy ; then
|
||||
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
|
||||
elif lsb_release -a | grep -q precise ; then
|
||||
# XXX add ARM case
|
||||
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
|
||||
else
|
||||
echo "No libaugeas0 version is available that's new enough to run the"
|
||||
echo "Certbot apache plugin..."
|
||||
fi
|
||||
# XXX add a case for ubuntu PPAs
|
||||
fi
|
||||
|
||||
$SUDO apt-get install $YES_FLAG --no-install-recommends \
|
||||
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \
|
||||
python \
|
||||
python-dev \
|
||||
$virtualenv \
|
||||
|
|
@ -294,7 +304,6 @@ BootstrapDebCommon() {
|
|||
ca-certificates \
|
||||
|
||||
|
||||
|
||||
if ! $EXISTS virtualenv > /dev/null ; then
|
||||
echo Failed to install a working \"virtualenv\" command, exiting
|
||||
exit 1
|
||||
|
|
@ -323,6 +332,9 @@ BootstrapRpmCommon() {
|
|||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
yes_flag="-y"
|
||||
fi
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='--quiet'
|
||||
fi
|
||||
|
||||
if ! $SUDO $tool list *virtualenv >/dev/null 2>&1; then
|
||||
echo "To use Certbot, packages from the EPEL repository need to be installed."
|
||||
|
|
@ -331,14 +343,14 @@ BootstrapRpmCommon() {
|
|||
exit 1
|
||||
fi
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
|
||||
sleep 1s
|
||||
fi
|
||||
if ! $SUDO $tool install $yes_flag epel-release; then
|
||||
if ! $SUDO $tool install $yes_flag $QUIET_FLAG epel-release; then
|
||||
echo "Could not enable EPEL. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -380,9 +392,9 @@ BootstrapRpmCommon() {
|
|||
"
|
||||
fi
|
||||
|
||||
if ! $SUDO $tool install $yes_flag $pkgs; then
|
||||
echo "Could not install OS dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
if ! $SUDO $tool install $yes_flag $QUIET_FLAG $pkgs; then
|
||||
echo "Could not install OS dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -394,7 +406,11 @@ BootstrapSuseCommon() {
|
|||
install_flags="-l"
|
||||
fi
|
||||
|
||||
$SUDO zypper $zypper_flags in $install_flags \
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='-qq'
|
||||
fi
|
||||
|
||||
$SUDO zypper $QUIET_FLAG $zypper_flags in $install_flags \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv \
|
||||
|
|
@ -432,7 +448,11 @@ BootstrapArchCommon() {
|
|||
fi
|
||||
|
||||
if [ "$missing" ]; then
|
||||
$SUDO pacman -S --needed $missing $noconfirm
|
||||
if [ "$QUIET" = 1]; then
|
||||
$SUDO pacman -S --needed $missing $noconfirm > /dev/null
|
||||
else
|
||||
$SUDO pacman -S --needed $missing $noconfirm
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +485,11 @@ BootstrapGentooCommon() {
|
|||
}
|
||||
|
||||
BootstrapFreeBsd() {
|
||||
$SUDO pkg install -Ay \
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG="--quiet"
|
||||
fi
|
||||
|
||||
$SUDO pkg install -Ay $QUIET_FLAG \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
augeas \
|
||||
|
|
@ -505,15 +529,15 @@ BootstrapMac() {
|
|||
fi
|
||||
|
||||
if ! hash pip 2>/dev/null; then
|
||||
echo "pip not installed"
|
||||
echo "Installing pip..."
|
||||
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
|
||||
echo "pip not installed"
|
||||
echo "Installing pip..."
|
||||
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
|
||||
fi
|
||||
|
||||
if ! hash virtualenv 2>/dev/null; then
|
||||
echo "virtualenv not installed."
|
||||
echo "Installing with pip..."
|
||||
pip install virtualenv
|
||||
echo "virtualenv not installed."
|
||||
echo "Installing with pip..."
|
||||
pip install virtualenv
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -523,26 +547,29 @@ BootstrapSmartOS() {
|
|||
}
|
||||
|
||||
BootstrapMageiaCommon() {
|
||||
if ! $SUDO urpmi --force \
|
||||
python \
|
||||
libpython-devel \
|
||||
python-virtualenv
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='--quiet'
|
||||
fi
|
||||
|
||||
if ! $SUDO urpmi --force $QUIET_FLAG \
|
||||
python \
|
||||
libpython-devel \
|
||||
python-virtualenv
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! $SUDO urpmi --force \
|
||||
git \
|
||||
gcc \
|
||||
python-augeas \
|
||||
openssl \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
rootcerts
|
||||
if ! $SUDO urpmi --force $QUIET_FLAG \
|
||||
git \
|
||||
gcc \
|
||||
python-augeas \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
rootcerts
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -609,6 +636,11 @@ if [ "$1" = "--le-auto-phase2" ]; then
|
|||
# --version output ran through grep due to python-cryptography DeprecationWarnings
|
||||
# grep for both certbot and letsencrypt until certbot and shim packages have been released
|
||||
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
|
||||
if [ -z "$INSTALLED_VERSION" ]; then
|
||||
echo "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2
|
||||
"$VENV_BIN/letsencrypt" --version
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
INSTALLED_VERSION="none"
|
||||
fi
|
||||
|
|
@ -801,18 +833,18 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.10.1 \
|
||||
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
|
||||
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
|
||||
certbot==0.10.1 \
|
||||
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
|
||||
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
|
||||
certbot-apache==0.10.1 \
|
||||
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
|
||||
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
|
||||
certbot-nginx==0.10.1 \
|
||||
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
|
||||
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
|
||||
acme==0.11.0 \
|
||||
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
|
||||
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
|
||||
certbot==0.11.0 \
|
||||
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
|
||||
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
|
||||
certbot-apache==0.11.0 \
|
||||
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
|
||||
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
|
||||
certbot-nginx==0.11.0 \
|
||||
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
|
||||
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
@ -1024,7 +1056,7 @@ UNLIKELY_EOF
|
|||
fi
|
||||
|
||||
else
|
||||
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
|
||||
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
|
||||
#
|
||||
# Each phase checks the version of only the thing it is responsible for
|
||||
# upgrading. Phase 1 checks the version of the latest release of
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ import zope.interface
|
|||
|
||||
from certbot import configuration
|
||||
from certbot import errors as le_errors
|
||||
from certbot import util as certbot_util
|
||||
from certbot_apache import configurator
|
||||
from certbot_apache import constants
|
||||
from certbot_compatibility_test import errors
|
||||
|
|
@ -106,4 +107,7 @@ def _get_names(config):
|
|||
not util.IP_REGEX.match(words[1]) and
|
||||
words[1].find(".") != -1):
|
||||
all_names.add(words[1])
|
||||
return all_names, non_ip_names
|
||||
return (
|
||||
certbot_util.get_filtered_names(all_names),
|
||||
certbot_util.get_filtered_names(non_ip_names)
|
||||
)
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ def test_deploy_cert(plugin, temp_dir, domains):
|
|||
plugin.deploy_cert(domain, cert_path, util.KEY_PATH, cert_path, cert_path)
|
||||
plugin.save() # Needed by the Apache plugin
|
||||
except le_errors.Error as error:
|
||||
logger.error("Plugin failed to deploy ceritificate for %s:", domain)
|
||||
logger.error("Plugin failed to deploy certificate for %s:", domain)
|
||||
logger.exception(error)
|
||||
return False
|
||||
|
||||
|
|
@ -202,7 +202,7 @@ def test_enhancements(plugin, domains):
|
|||
success = False
|
||||
|
||||
if success:
|
||||
logger.info("Enhancments test succeeded")
|
||||
logger.info("Enhancements test succeeded")
|
||||
|
||||
return success
|
||||
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ http {
|
|||
## Define a zone for limiting the number of simultaneous
|
||||
## connections nginx accepts. 1m means 32000 simultaneous
|
||||
## sessions. We need to define for each server the limit_conn
|
||||
## value refering to this or other zones.
|
||||
## value referring to this or other zones.
|
||||
## ** This syntax requires nginx version >=
|
||||
## ** 1.1.8. Cf. http://nginx.org/en/CHANGES. If using an older
|
||||
## ** version then use the limit_zone directive below
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ charset_map windows-1251 utf-8 {
|
|||
AA D084 ; # capital Ukrainian YE
|
||||
AB C2AB ; # left-pointing double angle quotation mark
|
||||
AC C2AC ; # not sign
|
||||
AD C2AD ; # soft hypen
|
||||
AD C2AD ; # soft hyphen
|
||||
AE C2AE ; # (R)
|
||||
AF D087 ; # capital Ukrainian YI
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ http {
|
|||
keepalive_timeout 60;
|
||||
|
||||
# http-redirects to https; even if using of hsts;
|
||||
# usefull if users are typing your server-name w/out https://
|
||||
# useful if users are typing your server-name w/out https://
|
||||
|
||||
|
||||
# logjam and a good idea anyway
|
||||
|
|
@ -98,7 +98,7 @@ http {
|
|||
#ssl_ciphers ALL:!ADH:!EXP:!LOW:!RC2:!3DES:!SEED:!RC4:+HIGH:+MEDIUM;
|
||||
|
||||
#
|
||||
# suggestions by mozilla-server-team - good compatibility, pfs, preferrable ciphers
|
||||
# suggestions by mozilla-server-team - good compatibility, pfs, preferable ciphers
|
||||
#
|
||||
# modern ciphers
|
||||
ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!3DES:!MD5:!PSK';
|
||||
|
|
@ -106,7 +106,7 @@ http {
|
|||
# intermediate ciphers
|
||||
#ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:CAMELLIA:DES-CBC3-SHA:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
|
||||
|
||||
# old ciphers (would need SSLv3, but is not recommende as of oct 2014
|
||||
# old ciphers (would need SSLv3, but is not recommended as of oct 2014
|
||||
#ssl_ciphers 'ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-DSS-AES128-GCM-SHA256:kEDH+AESGCM:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA:ECDHE-ECDSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-DSS-AES128-SHA256:DHE-RSA-AES256-SHA256:DHE-DSS-AES256-SHA:DHE-RSA-AES256-SHA:ECDHE-RSA-DES-CBC3-SHA:ECDHE-ECDSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:AES:DES-CBC3-SHA:HIGH:!aNULL:!eNULL:!EXPORT:!DES:!RC4:!MD5:!PSK:!aECDH:!EDH-DSS-DES-CBC3-SHA:!EDH-RSA-DES-CBC3-SHA:!KRB5-DES-CBC3-SHA';
|
||||
|
||||
# logjam / cipher suggested from weakdh.org
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ server {
|
|||
location ~* (index\.php|upload\.php|connector\.php|dl\.php|ut\.php|lt\.php|download\.php)$ {
|
||||
fastcgi_split_path_info ^(.|\.php)(/.+)$;
|
||||
|
||||
include /etc/nginx/fastcgi_params.conf; #standar fastcgi config file
|
||||
include /etc/nginx/fastcgi_params.conf; #standard fastcgi config file
|
||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
||||
fastcgi_intercept_errors on;
|
||||
fastcgi_pass 127.0.0.1:9000;
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.11.0.dev0'
|
||||
version = '0.12.0.dev0'
|
||||
|
||||
install_requires = [
|
||||
'certbot',
|
||||
|
|
|
|||
|
|
@ -403,25 +403,7 @@ class NginxConfigurator(common.Plugin):
|
|||
except (socket.error, socket.herror, socket.timeout):
|
||||
continue
|
||||
|
||||
return self._get_filtered_names(all_names)
|
||||
|
||||
def _get_filtered_names(self, all_names):
|
||||
"""Removes names that aren't considered valid by Let's Encrypt.
|
||||
|
||||
:param set all_names: all names found in the Nginx configuration
|
||||
|
||||
:returns: all found names that are considered valid by LE
|
||||
:rtype: set
|
||||
|
||||
"""
|
||||
filtered_names = set()
|
||||
for name in all_names:
|
||||
try:
|
||||
filtered_names.add(util.enforce_le_validity(name))
|
||||
except errors.ConfigurationError as error:
|
||||
logger.debug('Not suggesting name "%s"', name)
|
||||
logger.debug(error)
|
||||
return filtered_names
|
||||
return util.get_filtered_names(all_names)
|
||||
|
||||
def _get_snakeoil_paths(self):
|
||||
# TODO: generate only once
|
||||
|
|
|
|||
|
|
@ -52,10 +52,10 @@ class RawNginxParser(object):
|
|||
|
||||
map_statement = space + Literal("map") + space + nonspace + space + dollar_var + space
|
||||
# This is NOT an accurate way to parse nginx map entries; it's almost
|
||||
# certianly too permissive and may be wrong in other ways, but it should
|
||||
# certainly too permissive and may be wrong in other ways, but it should
|
||||
# preserve things correctly in mmmmost or all cases.
|
||||
#
|
||||
# - I can neither prove nor disprove that it is corect wrt all escaped
|
||||
# - I can neither prove nor disprove that it is correct wrt all escaped
|
||||
# semicolon situations
|
||||
# Addresses https://github.com/fatiherikli/nginxparser/issues/19
|
||||
map_pattern = Regex(r'".*"') | Regex(r"'.*'") | nonspace
|
||||
|
|
@ -143,7 +143,7 @@ class RawNginxDumper(object):
|
|||
def loads(source):
|
||||
"""Parses from a string.
|
||||
|
||||
:param str souce: The string to parse
|
||||
:param str source: The string to parse
|
||||
:returns: The parsed tree
|
||||
:rtype: list
|
||||
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@ MainRule "rx:%[2|3]." "msg:double encoding !" "mz:ARGS|URL|BODY|$HEADERS_VAR:Co
|
|||
####################################
|
||||
MainRule "str:&#" "msg: utf7/8 encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1400;
|
||||
MainRule "str:%U" "msg: M$ encoding" "mz:ARGS|BODY|URL|$HEADERS_VAR:Cookie" "s:$EVADE:4" id:1401;
|
||||
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither mulipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
|
||||
MainRule negative "rx:multipart/form-data|application/x-www-form-urlencoded" "msg:Content is neither multipart/x-www-form.." "mz:$HEADERS_VAR:Content-type" "s:$EVADE:4" id:1402;
|
||||
|
||||
#############################
|
||||
## File uploads: 1500-1600 ##
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ charset_map windows-1251 utf-8 {
|
|||
AA D084; # capital Ukrainian YE
|
||||
AB C2AB; # left-pointing double angle quotation mark
|
||||
AC C2AC; # not sign
|
||||
AD C2AD; # soft hypen
|
||||
AD C2AD; # soft hyphen
|
||||
AE C2AE; # (R)
|
||||
AF D087; # capital Ukrainian YI
|
||||
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ from setuptools import setup
|
|||
from setuptools import find_packages
|
||||
|
||||
|
||||
version = '0.11.0.dev0'
|
||||
version = '0.12.0.dev0'
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
install_requires = [
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Certbot client."""
|
||||
|
||||
# version number like 1.2.3a0, must have at least 2 parts, like 1.2
|
||||
__version__ = '0.11.0.dev0'
|
||||
__version__ = '0.12.0.dev0'
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
"""Client annotated ACME challenges.
|
||||
|
||||
Please use names such as ``achall`` to distiguish from variables "of type"
|
||||
Please use names such as ``achall`` to distinguish from variables "of type"
|
||||
:class:`acme.challenges.Challenge` (denoted by ``chall``)
|
||||
and :class:`.ChallengeBody` (denoted by ``challb``)::
|
||||
|
||||
|
|
|
|||
|
|
@ -34,8 +34,7 @@ class AuthHandler(object):
|
|||
:ivar list achalls: DV challenges in the form of
|
||||
:class:`certbot.achallenges.AnnotatedChallenge`
|
||||
:ivar list pref_challs: sorted user specified preferred challenges
|
||||
in the form of subclasses of :class:`acme.challenges.Challenge`
|
||||
with the most preferred challenge listed first
|
||||
type strings with the most preferred challenge listed first
|
||||
|
||||
"""
|
||||
def __init__(self, auth, acme, account, pref_challs):
|
||||
|
|
@ -252,8 +251,10 @@ class AuthHandler(object):
|
|||
# Make sure to make a copy...
|
||||
plugin_pref = self.auth.get_chall_pref(domain)
|
||||
if self.pref_challs:
|
||||
chall_prefs.extend(pref for pref in self.pref_challs
|
||||
if pref in plugin_pref)
|
||||
plugin_pref_types = set(chall.typ for chall in plugin_pref)
|
||||
for typ in self.pref_challs:
|
||||
if typ in plugin_pref_types:
|
||||
chall_prefs.append(challenges.Challenge.TYPES[typ])
|
||||
if chall_prefs:
|
||||
return chall_prefs
|
||||
raise errors.AuthorizationError(
|
||||
|
|
|
|||
|
|
@ -95,27 +95,23 @@ def delete(config):
|
|||
# Public Helpers
|
||||
###################
|
||||
|
||||
def lineage_for_certname(config, certname):
|
||||
def lineage_for_certname(cli_config, certname):
|
||||
"""Find a lineage object with name certname."""
|
||||
def update_cert_for_name_match(candidate_lineage, rv):
|
||||
"""Return cert if it has name certname, else return rv
|
||||
"""
|
||||
matching_lineage_name_cert = rv
|
||||
if candidate_lineage.lineagename == certname:
|
||||
matching_lineage_name_cert = candidate_lineage
|
||||
return matching_lineage_name_cert
|
||||
return _search_lineages(config, update_cert_for_name_match, None)
|
||||
configs_dir = cli_config.renewal_configs_dir
|
||||
# Verify the directory is there
|
||||
util.make_or_verify_dir(configs_dir, mode=0o755, uid=os.geteuid())
|
||||
renewal_file = storage.renewal_file_for_certname(cli_config, certname)
|
||||
try:
|
||||
return storage.RenewableCert(renewal_file, cli_config)
|
||||
except (errors.CertStorageError, IOError):
|
||||
logger.debug("Renewal conf file %s is broken.", renewal_file)
|
||||
logger.debug("Traceback was:\n%s", traceback.format_exc())
|
||||
return None
|
||||
|
||||
def domains_for_certname(config, certname):
|
||||
"""Find the domains in the cert with name certname."""
|
||||
def update_domains_for_name_match(candidate_lineage, rv):
|
||||
"""Return domains if certname matches, else return rv
|
||||
"""
|
||||
matching_domains = rv
|
||||
if candidate_lineage.lineagename == certname:
|
||||
matching_domains = candidate_lineage.names()
|
||||
return matching_domains
|
||||
return _search_lineages(config, update_domains_for_name_match, None)
|
||||
lineage = lineage_for_certname(config, certname)
|
||||
return lineage.names() if lineage else None
|
||||
|
||||
def find_duplicative_certs(config, domains):
|
||||
"""Find existing certs that duplicate the request."""
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
"""Certbot command line argument & config processing."""
|
||||
# pylint: disable=too-many-lines
|
||||
from __future__ import print_function
|
||||
import argparse
|
||||
import copy
|
||||
|
|
@ -149,7 +150,7 @@ def possible_deprecation_warning(config):
|
|||
if cli_command != LEAUTO:
|
||||
return
|
||||
if config.no_self_upgrade:
|
||||
# users setting --no-self-upgrade might be hanging on a clent version like 0.3.0
|
||||
# users setting --no-self-upgrade might be hanging on a client version like 0.3.0
|
||||
# or 0.5.0 which is the new script, but doesn't set CERTBOT_AUTO; they don't
|
||||
# need warnings
|
||||
return
|
||||
|
|
@ -317,7 +318,7 @@ class CustomHelpFormatter(argparse.HelpFormatter):
|
|||
# The attributes here are:
|
||||
# short: a string that will be displayed by "certbot -h commands"
|
||||
# opts: a string that heads the section of flags with which this command is documented,
|
||||
# both for "cerbot -h SUBCOMMAND" and "certbot -h all"
|
||||
# both for "certbot -h SUBCOMMAND" and "certbot -h all"
|
||||
# usage: an optional string that overrides the header of "certbot -h SUBCOMMAND"
|
||||
VERB_HELP = [
|
||||
("run (default)", {
|
||||
|
|
@ -333,7 +334,7 @@ VERB_HELP = [
|
|||
"This command obtains a TLS/SSL certificate without installing it anywhere.")
|
||||
}),
|
||||
("renew", {
|
||||
"short": "Renew all certificates (or one specifed with --cert-name)",
|
||||
"short": "Renew all certificates (or one specified with --cert-name)",
|
||||
"opts": ("The 'renew' subcommand will attempt to renew all"
|
||||
" certificates (or more precisely, certificate lineages) you have"
|
||||
" previously obtained if they are close to expiry, and print a"
|
||||
|
|
@ -438,7 +439,7 @@ class HelpfulArgumentParser(object):
|
|||
self.detect_defaults = detect_defaults
|
||||
self.args = args
|
||||
|
||||
if self.args[0] == 'help':
|
||||
if self.args and self.args[0] == 'help':
|
||||
self.args[0] = '--help'
|
||||
|
||||
self.determine_verb()
|
||||
|
|
@ -495,7 +496,7 @@ class HelpfulArgumentParser(object):
|
|||
if "apache" in plugins:
|
||||
apache_doc = "--apache Use the Apache plugin for authentication & installation"
|
||||
else:
|
||||
apache_doc = "(the cerbot apache plugin is not installed)"
|
||||
apache_doc = "(the certbot apache plugin is not installed)"
|
||||
|
||||
usage = SHORT_USAGE
|
||||
if help_arg == True:
|
||||
|
|
@ -879,6 +880,12 @@ def prepare_and_parse_args(plugins, args, detect_defaults=False): # pylint: dis
|
|||
helpful.add(
|
||||
["register", "unregister", "automation"], "-m", "--email",
|
||||
help=config_help("email"))
|
||||
helpful.add(["register", "automation"], "--eff-email", action="store_true",
|
||||
default=None, dest="eff_email",
|
||||
help="Share your e-mail address with EFF")
|
||||
helpful.add(["register", "automation"], "--no-eff-email", action="store_false",
|
||||
default=None, dest="eff_email",
|
||||
help="Don't share your e-mail address with EFF")
|
||||
helpful.add(
|
||||
["automation", "certonly", "run"],
|
||||
"--keep-until-expiring", "--keep", "--reinstall",
|
||||
|
|
@ -1236,13 +1243,31 @@ class _PrefChallAction(argparse.Action):
|
|||
"""Action class for parsing preferred challenges."""
|
||||
|
||||
def __call__(self, parser, namespace, pref_challs, option_string=None):
|
||||
aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"}
|
||||
challs = [c.strip() for c in pref_challs.split(",")]
|
||||
challs = [aliases[c] if c in aliases else c for c in challs]
|
||||
unrecognized = ", ".join(name for name in challs
|
||||
if name not in challenges.Challenge.TYPES)
|
||||
if unrecognized:
|
||||
raise argparse.ArgumentTypeError(
|
||||
"Unrecognized challenges: {0}".format(unrecognized))
|
||||
namespace.pref_challs.extend(challenges.Challenge.TYPES[name]
|
||||
for name in challs)
|
||||
try:
|
||||
challs = parse_preferred_challenges(pref_challs.split(","))
|
||||
except errors.Error as error:
|
||||
raise argparse.ArgumentTypeError(str(error))
|
||||
namespace.pref_challs.extend(challs)
|
||||
|
||||
|
||||
def parse_preferred_challenges(pref_challs):
|
||||
"""Translate and validate preferred challenges.
|
||||
|
||||
:param pref_challs: list of preferred challenge types
|
||||
:type pref_challs: `list` of `str`
|
||||
|
||||
:returns: validated list of preferred challenge types
|
||||
:rtype: `list` of `str`
|
||||
|
||||
:raises errors.Error: if pref_challs is invalid
|
||||
|
||||
"""
|
||||
aliases = {"dns": "dns-01", "http": "http-01", "tls-sni": "tls-sni-01"}
|
||||
challs = [c.strip() for c in pref_challs]
|
||||
challs = [aliases.get(c, c) for c in challs]
|
||||
unrecognized = ", ".join(name for name in challs
|
||||
if name not in challenges.Challenge.TYPES)
|
||||
if unrecognized:
|
||||
raise errors.Error(
|
||||
"Unrecognized challenges: {0}".format(unrecognized))
|
||||
return challs
|
||||
|
|
|
|||
|
|
@ -15,15 +15,16 @@ import certbot
|
|||
|
||||
from certbot import account
|
||||
from certbot import auth_handler
|
||||
from certbot import cli
|
||||
from certbot import constants
|
||||
from certbot import crypto_util
|
||||
from certbot import errors
|
||||
from certbot import eff
|
||||
from certbot import error_handler
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
from certbot import reverter
|
||||
from certbot import storage
|
||||
from certbot import cli
|
||||
from certbot import util
|
||||
|
||||
from certbot.display import ops as display_ops
|
||||
from certbot.display import enhancements
|
||||
|
|
@ -93,7 +94,7 @@ def register(config, account_storage, tos_cb=None):
|
|||
Terms of Service present in the contained
|
||||
`.Registration.terms_of_service` is accepted by the client, and
|
||||
``False`` otherwise. ``tos_cb`` will be called only if the
|
||||
client acction is necessary, i.e. when ``terms_of_service is not
|
||||
client action is necessary, i.e. when ``terms_of_service is not
|
||||
None``. This argument is optional, if not supplied it will
|
||||
default to automatic acceptance!
|
||||
|
||||
|
|
@ -139,6 +140,8 @@ def register(config, account_storage, tos_cb=None):
|
|||
account.report_new_account(acc, config)
|
||||
account_storage.save(acc)
|
||||
|
||||
eff.handle_subscription(config)
|
||||
|
||||
return acc, acme
|
||||
|
||||
|
||||
|
|
@ -438,7 +441,7 @@ class Client(object):
|
|||
self.installer.restart()
|
||||
|
||||
def apply_enhancement(self, domains, enhancement, options=None):
|
||||
"""Applies an enhacement on all domains.
|
||||
"""Applies an enhancement on all domains.
|
||||
|
||||
:param domains: list of ssl_vhosts
|
||||
:type list of str
|
||||
|
|
@ -494,7 +497,7 @@ class Client(object):
|
|||
self.installer.rollback_checkpoints()
|
||||
self.installer.restart()
|
||||
except:
|
||||
# TODO: suggest letshelp-letsencypt here
|
||||
# TODO: suggest letshelp-letsencrypt here
|
||||
reporter.add_message(
|
||||
"An error occurred and we failed to restore your config and "
|
||||
"restart your server. Please submit a bug report to "
|
||||
|
|
|
|||
|
|
@ -106,3 +106,6 @@ RENEWAL_CONFIGS_DIR = "renewal"
|
|||
|
||||
FORCE_INTERACTIVE_FLAG = "--force-interactive"
|
||||
"""Flag to disable TTY checking in IDisplay."""
|
||||
|
||||
EFF_SUBSCRIBE_URI = "https://supporters.eff.org/subscribe/certbot"
|
||||
"""EFF URI used to submit the e-mail address of users who opt-in."""
|
||||
|
|
|
|||
95
certbot/eff.py
Normal file
95
certbot/eff.py
Normal file
|
|
@ -0,0 +1,95 @@
|
|||
"""Subscribes users to the EFF newsletter."""
|
||||
import logging
|
||||
|
||||
import requests
|
||||
import zope.component
|
||||
|
||||
from certbot import constants
|
||||
from certbot import interfaces
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def handle_subscription(config):
|
||||
"""High level function to take care of EFF newsletter subscriptions.
|
||||
|
||||
The user may be asked if they want to sign up for the newsletter if
|
||||
they have not already specified.
|
||||
|
||||
:param .IConfig config: Client configuration.
|
||||
|
||||
"""
|
||||
if config.email is None:
|
||||
if config.eff_email:
|
||||
_report_failure("you didn't provide an e-mail address")
|
||||
return
|
||||
if config.eff_email is None:
|
||||
config.eff_email = _want_subscription()
|
||||
if config.eff_email:
|
||||
subscribe(config.email)
|
||||
|
||||
|
||||
def _want_subscription():
|
||||
"""Does the user want to be subscribed to the EFF newsletter?
|
||||
|
||||
:returns: True if we should subscribe the user, otherwise, False
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
prompt = (
|
||||
'Would you be willing to share your email address with the '
|
||||
"Electronic Frontier Foundation, a founding partner of the Let's "
|
||||
'Encrypt project and the non-profit organization that develops '
|
||||
"Certbot? We'd like to send you email about EFF and our work to "
|
||||
'encrypt the web, protect its users and defend digital rights.')
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
return display.yesno(prompt, default=False)
|
||||
|
||||
|
||||
def subscribe(email):
|
||||
"""Subscribe the user to the EFF mailing list.
|
||||
|
||||
:param str email: the e-mail address to subscribe
|
||||
|
||||
"""
|
||||
url = constants.EFF_SUBSCRIBE_URI
|
||||
data = {'data_type': 'json',
|
||||
'email': email,
|
||||
'form_id': 'eff_supporters_library_subscribe_form'}
|
||||
logger.debug('Sending POST request to %s:\n%s', url, data)
|
||||
_check_response(requests.post(url, data=data))
|
||||
|
||||
|
||||
def _check_response(response):
|
||||
"""Check for errors in the server's response.
|
||||
|
||||
If an error occurred, it will be reported to the user.
|
||||
|
||||
:param requests.Response response: the server's response to the
|
||||
subscription request
|
||||
|
||||
"""
|
||||
logger.debug('Received response:\n%s', response.content)
|
||||
if response.ok:
|
||||
if not response.json()['status']:
|
||||
_report_failure('your e-mail address appears to be invalid')
|
||||
else:
|
||||
_report_failure()
|
||||
|
||||
|
||||
def _report_failure(reason=None):
|
||||
"""Notify the user of failing to sign them up for the newsletter.
|
||||
|
||||
:param reason: a phrase describing what the problem was
|
||||
beginning with a lowercase letter and no closing punctuation
|
||||
:type reason: `str` or `None`
|
||||
|
||||
"""
|
||||
msg = ['We were unable to subscribe you the EFF mailing list']
|
||||
if reason is not None:
|
||||
msg.append(' because ')
|
||||
msg.append(reason)
|
||||
msg.append('. You can try again later by visiting https://act.eff.org.')
|
||||
reporter = zope.component.getUtility(interfaces.IReporter)
|
||||
reporter.add_message(''.join(msg), reporter.LOW_PRIORITY)
|
||||
|
|
@ -24,6 +24,7 @@ from certbot import crypto_util
|
|||
from certbot import colored_logging
|
||||
from certbot import configuration
|
||||
from certbot import constants
|
||||
from certbot import eff
|
||||
from certbot import errors
|
||||
from certbot import hooks
|
||||
from certbot import interfaces
|
||||
|
|
@ -474,6 +475,7 @@ def register(config, unused_plugins):
|
|||
acc.regr = acme_client.acme.update_registration(acc.regr.update(
|
||||
body=acc.regr.body.update(contact=('mailto:' + config.email,))))
|
||||
account_storage.save_regr(acc)
|
||||
eff.handle_subscription(config)
|
||||
add_msg("Your e-mail address was updated to {0}.".format(config.email))
|
||||
|
||||
|
||||
|
|
@ -812,7 +814,7 @@ def make_or_verify_core_dir(directory, mode, uid, strict):
|
|||
raise errors.Error(_PERM_ERR_FMT.format(error))
|
||||
|
||||
def make_or_verify_needed_dirs(config):
|
||||
"""Create or verify existance of config, work, or logs directories"""
|
||||
"""Create or verify existence of config, work, or logs directories"""
|
||||
make_or_verify_core_dir(config.config_dir, constants.CONFIG_DIRS_MODE,
|
||||
os.geteuid(), config.strict_permissions)
|
||||
make_or_verify_core_dir(config.work_dir, constants.CONFIG_DIRS_MODE,
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ class PluginEntryPoint(object):
|
|||
return self._initialized is not None
|
||||
|
||||
def init(self, config=None):
|
||||
"""Memoized plugin inititialization."""
|
||||
"""Memoized plugin initialization."""
|
||||
if not self.initialized:
|
||||
self.entry_point.require() # fetch extras!
|
||||
self._initialized = self.plugin_cls(config, self.name)
|
||||
|
|
@ -230,7 +230,7 @@ class PluginsRegistry(collections.Mapping):
|
|||
def available(self):
|
||||
"""Filter plugins based on availability."""
|
||||
return self.filter(lambda p_ep: p_ep.available)
|
||||
# succefully prepared + misconfigured
|
||||
# successfully prepared + misconfigured
|
||||
|
||||
def find_init(self, plugin):
|
||||
"""Find an initialized plugin.
|
||||
|
|
|
|||
|
|
@ -255,7 +255,7 @@ class PluginsRegistryTest(unittest.TestCase):
|
|||
|
||||
def test_find_init(self):
|
||||
self.assertTrue(self.reg.find_init(mock.Mock()) is None)
|
||||
self.plugin_ep.initalized = True
|
||||
self.plugin_ep.initialized = True
|
||||
self.assertTrue(
|
||||
self.reg.find_init(self.plugin_ep.init()) is self.plugin_ep)
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
"""Tests for letsenecrypt.plugins.selection"""
|
||||
"""Tests for letsencrypt.plugins.selection"""
|
||||
import sys
|
||||
import unittest
|
||||
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ from certbot import errors
|
|||
from certbot import interfaces
|
||||
|
||||
from certbot.plugins import common
|
||||
from certbot.plugins import util
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
|
@ -208,74 +207,38 @@ class Authenticator(common.Plugin):
|
|||
# pylint: disable=unused-argument,missing-docstring
|
||||
return self.supported_challenges
|
||||
|
||||
def _verify_ports_are_available(self, achalls):
|
||||
"""Confirm the ports are available to solve all achalls.
|
||||
|
||||
:param list achalls: list of
|
||||
:class:`~certbot.achallenges.AnnotatedChallenge`
|
||||
|
||||
:raises .errors.MisconfigurationError: if required port is
|
||||
unavailable
|
||||
|
||||
"""
|
||||
ports = []
|
||||
if any(isinstance(ac.chall, challenges.HTTP01) for ac in achalls):
|
||||
ports.append(self.config.http01_port)
|
||||
if any(isinstance(ac.chall, challenges.TLSSNI01) for ac in achalls):
|
||||
ports.append(self.config.tls_sni_01_port)
|
||||
|
||||
renewer = (self.config.verb == "renew")
|
||||
|
||||
if any(util.already_listening(port, renewer) for port in ports):
|
||||
raise errors.MisconfigurationError(
|
||||
"At least one of the required ports is already taken.")
|
||||
|
||||
def perform(self, achalls): # pylint: disable=missing-docstring
|
||||
self._verify_ports_are_available(achalls)
|
||||
return [self._try_perform_single(achall) for achall in achalls]
|
||||
|
||||
try:
|
||||
return self.perform2(achalls)
|
||||
except errors.StandaloneBindError as error:
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
def _try_perform_single(self, achall):
|
||||
while True:
|
||||
try:
|
||||
return self._perform_single(achall)
|
||||
except errors.StandaloneBindError as error:
|
||||
_handle_perform_error(error)
|
||||
|
||||
if error.socket_error.errno == socket.errno.EACCES:
|
||||
display.notification(
|
||||
"Could not bind TCP port {0} because you don't have "
|
||||
"the appropriate permissions (for example, you "
|
||||
"aren't running this program as "
|
||||
"root).".format(error.port), force_interactive=True)
|
||||
elif error.socket_error.errno == socket.errno.EADDRINUSE:
|
||||
display.notification(
|
||||
"Could not bind TCP port {0} because it is already in "
|
||||
"use by another process on this system (such as a web "
|
||||
"server). Please stop the program in question and then "
|
||||
"try again.".format(error.port), force_interactive=True)
|
||||
else:
|
||||
raise # XXX: How to handle unknown errors in binding?
|
||||
def _perform_single(self, achall):
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
server, response = self._perform_http_01(achall)
|
||||
else: # tls-sni-01
|
||||
server, response = self._perform_tls_sni_01(achall)
|
||||
self.served[server].add(achall)
|
||||
return response
|
||||
|
||||
def perform2(self, achalls):
|
||||
"""Perform achallenges without IDisplay interaction."""
|
||||
responses = []
|
||||
def _perform_http_01(self, achall):
|
||||
server = self.servers.run(self.config.http01_port, challenges.HTTP01)
|
||||
response, validation = achall.response_and_validation()
|
||||
resource = acme_standalone.HTTP01RequestHandler.HTTP01Resource(
|
||||
chall=achall.chall, response=response, validation=validation)
|
||||
self.http_01_resources.add(resource)
|
||||
return server, response
|
||||
|
||||
for achall in achalls:
|
||||
if isinstance(achall.chall, challenges.HTTP01):
|
||||
server = self.servers.run(
|
||||
self.config.http01_port, challenges.HTTP01)
|
||||
response, validation = achall.response_and_validation()
|
||||
self.http_01_resources.add(
|
||||
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
|
||||
chall=achall.chall, response=response,
|
||||
validation=validation))
|
||||
else: # tls-sni-01
|
||||
server = self.servers.run(
|
||||
self.config.tls_sni_01_port, challenges.TLSSNI01)
|
||||
response, (cert, _) = achall.response_and_validation(
|
||||
cert_key=self.key)
|
||||
self.certs[response.z_domain] = (self.key, cert)
|
||||
self.served[server].add(achall)
|
||||
responses.append(response)
|
||||
|
||||
return responses
|
||||
def _perform_tls_sni_01(self, achall):
|
||||
port = self.config.tls_sni_01_port
|
||||
server = self.servers.run(port, challenges.TLSSNI01)
|
||||
response, (cert, _) = achall.response_and_validation(cert_key=self.key)
|
||||
self.certs[response.z_domain] = (self.key, cert)
|
||||
return server, response
|
||||
|
||||
def cleanup(self, achalls): # pylint: disable=missing-docstring
|
||||
# reduce self.served and close servers if none challenges are served
|
||||
|
|
@ -286,3 +249,25 @@ class Authenticator(common.Plugin):
|
|||
for port, server in six.iteritems(self.servers.running()):
|
||||
if not self.served[server]:
|
||||
self.servers.stop(port)
|
||||
|
||||
|
||||
def _handle_perform_error(error):
|
||||
if error.socket_error.errno == socket.errno.EACCES:
|
||||
raise errors.PluginError(
|
||||
"Could not bind TCP port {0} because you don't have "
|
||||
"the appropriate permissions (for example, you "
|
||||
"aren't running this program as "
|
||||
"root).".format(error.port))
|
||||
elif error.socket_error.errno == socket.errno.EADDRINUSE:
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
msg = (
|
||||
"Could not bind TCP port {0} because it is already in "
|
||||
"use by another process on this system (such as a web "
|
||||
"server). Please stop the program in question and "
|
||||
"then try again.".format(error.port))
|
||||
should_retry = display.yesno(msg, "Retry",
|
||||
"Cancel", default=False)
|
||||
if not should_retry:
|
||||
raise errors.PluginError(msg)
|
||||
else:
|
||||
raise
|
||||
|
|
|
|||
|
|
@ -8,11 +8,9 @@ import six
|
|||
|
||||
from acme import challenges
|
||||
from acme import jose
|
||||
from acme import standalone as acme_standalone
|
||||
|
||||
from certbot import achallenges
|
||||
from certbot import errors
|
||||
from certbot import interfaces
|
||||
|
||||
from certbot.tests import acme_util
|
||||
from certbot.tests import util as test_util
|
||||
|
|
@ -114,6 +112,7 @@ def get_open_port():
|
|||
open_socket.close()
|
||||
return port
|
||||
|
||||
|
||||
class AuthenticatorTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.standalone.Authenticator."""
|
||||
|
||||
|
|
@ -124,6 +123,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
tls_sni_01_port=get_open_port(), http01_port=get_open_port(),
|
||||
standalone_supported_challenges="tls-sni-01,http-01")
|
||||
self.auth = Authenticator(self.config, name="standalone")
|
||||
self.auth.servers = mock.MagicMock()
|
||||
|
||||
def test_supported_challenges(self):
|
||||
self.assertEqual(self.auth.supported_challenges,
|
||||
|
|
@ -146,6 +146,52 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
self.assertEqual(self.auth.get_chall_pref(domain=None),
|
||||
[challenges.TLSSNI01])
|
||||
|
||||
def test_perform(self):
|
||||
achalls = self._get_achalls()
|
||||
response = self.auth.perform(achalls)
|
||||
|
||||
expected = [achall.response(achall.account_key) for achall in achalls]
|
||||
self.assertEqual(response, expected)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_perform_eaddrinuse_retry(self, mock_get_utility):
|
||||
errno = socket.errno.EADDRINUSE
|
||||
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
|
||||
self.auth.servers.run.side_effect = [error] + 2 * [mock.MagicMock()]
|
||||
mock_yesno = mock_get_utility.return_value.yesno
|
||||
mock_yesno.return_value = True
|
||||
|
||||
self.test_perform()
|
||||
self._assert_correct_yesno_call(mock_yesno)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_perform_eaddrinuse_no_retry(self, mock_get_utility):
|
||||
mock_yesno = mock_get_utility.return_value.yesno
|
||||
mock_yesno.return_value = False
|
||||
|
||||
errno = socket.errno.EADDRINUSE
|
||||
self.assertRaises(errors.PluginError, self._fail_perform, errno)
|
||||
self._assert_correct_yesno_call(mock_yesno)
|
||||
|
||||
def _assert_correct_yesno_call(self, mock_yesno):
|
||||
yesno_args, yesno_kwargs = mock_yesno.call_args
|
||||
self.assertTrue("in use" in yesno_args[0])
|
||||
self.assertFalse(yesno_kwargs.get("default", True))
|
||||
|
||||
def test_perform_eacces(self):
|
||||
errno = socket.errno.EACCES
|
||||
self.assertRaises(errors.PluginError, self._fail_perform, errno)
|
||||
|
||||
def test_perform_unexpected_socket_error(self):
|
||||
errno = socket.errno.ENOTCONN
|
||||
self.assertRaises(
|
||||
errors.StandaloneBindError, self._fail_perform, errno)
|
||||
|
||||
def _fail_perform(self, errno):
|
||||
error = errors.StandaloneBindError(mock.MagicMock(errno=errno), -1)
|
||||
self.auth.servers.run.side_effect = error
|
||||
self.auth.perform(self._get_achalls())
|
||||
|
||||
@classmethod
|
||||
def _get_achalls(cls):
|
||||
domain = b'localhost'
|
||||
|
|
@ -157,84 +203,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||
|
||||
return [http_01, tls_sni_01]
|
||||
|
||||
@mock.patch("certbot.plugins.standalone.util")
|
||||
def test_perform_already_listening(self, mock_util):
|
||||
http_01, tls_sni_01 = self._get_achalls()
|
||||
|
||||
for achall, port in ((http_01, self.config.http01_port,),
|
||||
(tls_sni_01, self.config.tls_sni_01_port)):
|
||||
mock_util.already_listening.return_value = True
|
||||
self.assertRaises(
|
||||
errors.MisconfigurationError, self.auth.perform, [achall])
|
||||
mock_util.already_listening.assert_called_once_with(port, False)
|
||||
mock_util.already_listening.reset_mock()
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_perform(self, unused_mock_get_utility):
|
||||
achalls = self._get_achalls()
|
||||
|
||||
self.auth.perform2 = mock.Mock(return_value=mock.sentinel.responses)
|
||||
self.assertEqual(mock.sentinel.responses, self.auth.perform(achalls))
|
||||
self.auth.perform2.assert_called_once_with(achalls)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def _test_perform_bind_errors(self, errno, achalls, mock_get_utility):
|
||||
port = get_open_port()
|
||||
def _perform2(unused_achalls):
|
||||
raise errors.StandaloneBindError(mock.Mock(errno=errno), port)
|
||||
|
||||
self.auth.perform2 = mock.MagicMock(side_effect=_perform2)
|
||||
self.auth.perform(achalls)
|
||||
mock_get_utility.assert_called_once_with(interfaces.IDisplay)
|
||||
notification = mock_get_utility.return_value.notification
|
||||
self.assertEqual(1, notification.call_count)
|
||||
self.assertTrue(str(port) in notification.call_args[0][0])
|
||||
|
||||
def test_perform_eacces(self):
|
||||
# pylint: disable=no-value-for-parameter
|
||||
self._test_perform_bind_errors(socket.errno.EACCES, [])
|
||||
|
||||
def test_perform_eaddrinuse(self):
|
||||
# pylint: disable=no-value-for-parameter
|
||||
self._test_perform_bind_errors(socket.errno.EADDRINUSE, [])
|
||||
|
||||
def test_perfom_unknown_bind_error(self):
|
||||
self.assertRaises(
|
||||
errors.StandaloneBindError, self._test_perform_bind_errors,
|
||||
socket.errno.ENOTCONN, [])
|
||||
|
||||
def test_perform2(self):
|
||||
http_01, tls_sni_01 = self._get_achalls()
|
||||
|
||||
self.auth.servers = mock.MagicMock()
|
||||
|
||||
def _run(port, tls): # pylint: disable=unused-argument
|
||||
return "server{0}".format(port)
|
||||
|
||||
self.auth.servers.run.side_effect = _run
|
||||
responses = self.auth.perform2([http_01, tls_sni_01])
|
||||
|
||||
self.assertTrue(isinstance(responses, list))
|
||||
self.assertEqual(2, len(responses))
|
||||
self.assertTrue(isinstance(responses[0], challenges.HTTP01Response))
|
||||
self.assertTrue(isinstance(responses[1], challenges.TLSSNI01Response))
|
||||
|
||||
self.assertEqual(self.auth.servers.run.mock_calls, [
|
||||
mock.call(self.config.http01_port, challenges.HTTP01),
|
||||
mock.call(self.config.tls_sni_01_port, challenges.TLSSNI01),
|
||||
])
|
||||
self.assertEqual(self.auth.served, {
|
||||
"server" + str(self.config.tls_sni_01_port): set([tls_sni_01]),
|
||||
"server" + str(self.config.http01_port): set([http_01]),
|
||||
})
|
||||
self.assertEqual(1, len(self.auth.http_01_resources))
|
||||
self.assertEqual(1, len(self.auth.certs))
|
||||
self.assertEqual(list(self.auth.http_01_resources), [
|
||||
acme_standalone.HTTP01RequestHandler.HTTP01Resource(
|
||||
acme_util.HTTP01, responses[0], mock.ANY)])
|
||||
|
||||
def test_cleanup(self):
|
||||
self.auth.servers = mock.Mock()
|
||||
self.auth.servers.running.return_value = {
|
||||
1: "server1",
|
||||
2: "server2",
|
||||
|
|
|
|||
|
|
@ -1,34 +1,11 @@
|
|||
"""Plugin utilities."""
|
||||
import logging
|
||||
import os
|
||||
import socket
|
||||
|
||||
import zope.component
|
||||
|
||||
from acme import errors as acme_errors
|
||||
from acme import util as acme_util
|
||||
|
||||
from certbot import interfaces
|
||||
from certbot import util
|
||||
|
||||
PSUTIL_REQUIREMENT = "psutil>=2.2.1"
|
||||
|
||||
try:
|
||||
acme_util.activate(PSUTIL_REQUIREMENT)
|
||||
import psutil # pragma: no cover
|
||||
USE_PSUTIL = True
|
||||
except acme_errors.DependencyError: # pragma: no cover
|
||||
USE_PSUTIL = False
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
RENEWER_EXTRA_MSG = (
|
||||
" For automated renewal, you may want to use a script that stops"
|
||||
" and starts your webserver. You can find an example at"
|
||||
" https://certbot.eff.org/docs/using.html#renewal ."
|
||||
" Alternatively you can use the webroot plugin to renew without"
|
||||
" needing to stop and start your webserver.")
|
||||
|
||||
|
||||
def path_surgery(cmd):
|
||||
"""Attempt to perform PATH surgery to find cmd
|
||||
|
|
@ -59,105 +36,3 @@ def path_surgery(cmd):
|
|||
logger.warning("Failed to find %s in%s PATH: %s", cmd,
|
||||
expanded, path)
|
||||
return False
|
||||
|
||||
|
||||
def already_listening(port, renewer=False):
|
||||
"""Check if a process is already listening on the port.
|
||||
|
||||
If so, also tell the user via a display notification.
|
||||
|
||||
.. warning::
|
||||
On some operating systems, this function can only usefully be
|
||||
run as root.
|
||||
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False.
|
||||
|
||||
"""
|
||||
|
||||
if USE_PSUTIL:
|
||||
return already_listening_psutil(port, renewer=renewer)
|
||||
else:
|
||||
logger.debug("Psutil not found, using simple socket check.")
|
||||
return already_listening_socket(port, renewer=renewer)
|
||||
|
||||
|
||||
def already_listening_socket(port, renewer=False):
|
||||
"""Simple socket based check to find out if port is already in use
|
||||
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False
|
||||
"""
|
||||
|
||||
try:
|
||||
testsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM, 0)
|
||||
testsocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
|
||||
try:
|
||||
testsocket.bind(("", port))
|
||||
except socket.error:
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
extra = ""
|
||||
if renewer:
|
||||
extra = RENEWER_EXTRA_MSG
|
||||
display.notification(
|
||||
"Port {0} is already in use by another process. This will "
|
||||
"prevent us from binding to that port. Please stop the "
|
||||
"process that is populating the port in question and try "
|
||||
"again. {1}".format(port, extra), force_interactive=True)
|
||||
return True
|
||||
finally:
|
||||
testsocket.close()
|
||||
except socket.error:
|
||||
pass
|
||||
return False
|
||||
|
||||
|
||||
def already_listening_psutil(port, renewer=False):
|
||||
"""Psutil variant of the open port check
|
||||
|
||||
:param int port: The TCP port in question.
|
||||
:returns: True or False.
|
||||
|
||||
"""
|
||||
try:
|
||||
net_connections = psutil.net_connections()
|
||||
except psutil.AccessDenied as error:
|
||||
logger.info("Access denied when trying to list network "
|
||||
"connections: %s. Are you root?", error)
|
||||
# this function is just a pre-check that often causes false
|
||||
# positives and problems in testing (c.f. #680 on Mac, #255
|
||||
# generally); we will fail later in bind() anyway
|
||||
return False
|
||||
|
||||
listeners = [conn.pid for conn in net_connections
|
||||
if conn.status == 'LISTEN' and
|
||||
conn.type == socket.SOCK_STREAM and
|
||||
conn.laddr[1] == port]
|
||||
try:
|
||||
if listeners and listeners[0] is not None:
|
||||
# conn.pid may be None if the current process doesn't have
|
||||
# permission to identify the listening process! Additionally,
|
||||
# listeners may have more than one element if separate
|
||||
# sockets have bound the same port on separate interfaces.
|
||||
# We currently only have UI to notify the user about one
|
||||
# of them at a time.
|
||||
pid = listeners[0]
|
||||
name = psutil.Process(pid).name()
|
||||
display = zope.component.getUtility(interfaces.IDisplay)
|
||||
extra = ""
|
||||
if renewer:
|
||||
extra = RENEWER_EXTRA_MSG
|
||||
display.notification(
|
||||
"The program {0} (process ID {1}) is already listening "
|
||||
"on TCP port {2}. This will prevent us from binding to "
|
||||
"that port. Please stop the {0} program temporarily "
|
||||
"and then try again.{3}".format(name, pid, port, extra),
|
||||
force_interactive=True)
|
||||
return True
|
||||
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||
# Perhaps the result of a race where the process could have
|
||||
# exited or relinquished the port (NoSuchProcess), or the result
|
||||
# of an OS policy where we're not allowed to look up the process
|
||||
# name (AccessDenied).
|
||||
pass
|
||||
return False
|
||||
|
|
|
|||
|
|
@ -1,13 +1,9 @@
|
|||
"""Tests for certbot.plugins.util."""
|
||||
import os
|
||||
import socket
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from certbot.plugins.util import PSUTIL_REQUIREMENT
|
||||
from certbot.tests import util as test_util
|
||||
|
||||
|
||||
class PathSurgeryTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.path_surgery."""
|
||||
|
|
@ -34,140 +30,5 @@ class PathSurgeryTest(unittest.TestCase):
|
|||
self.assertTrue("/tmp" in os.environ["PATH"])
|
||||
|
||||
|
||||
class AlreadyListeningTest(unittest.TestCase):
|
||||
"""Tests for certbot.plugins.already_listening."""
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
from certbot.plugins.util import already_listening
|
||||
return already_listening(*args, **kwargs)
|
||||
|
||||
|
||||
class AlreadyListeningTestNoPsutil(AlreadyListeningTest):
|
||||
"""Tests for certbot.plugins.already_listening when
|
||||
psutil is not available"""
|
||||
@classmethod
|
||||
def _call(cls, *args, **kwargs):
|
||||
with mock.patch("certbot.plugins.util.USE_PSUTIL", False):
|
||||
return super(
|
||||
AlreadyListeningTestNoPsutil, cls)._call(*args, **kwargs)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_ports_available(self, mock_getutil):
|
||||
# Ensure we don't get error
|
||||
with mock.patch("socket.socket.bind"):
|
||||
self.assertFalse(self._call(80))
|
||||
self.assertFalse(self._call(80, True))
|
||||
self.assertEqual(mock_getutil.call_count, 0)
|
||||
|
||||
@test_util.patch_get_utility()
|
||||
def test_ports_blocked(self, mock_getutil):
|
||||
with mock.patch("certbot.plugins.util.socket.socket.bind") as mock_bind:
|
||||
mock_bind.side_effect = socket.error
|
||||
self.assertTrue(self._call(80))
|
||||
self.assertTrue(self._call(80, True))
|
||||
with mock.patch("certbot.plugins.util.socket.socket") as mock_socket:
|
||||
mock_socket.side_effect = socket.error
|
||||
self.assertFalse(self._call(80))
|
||||
self.assertEqual(mock_getutil.call_count, 2)
|
||||
|
||||
|
||||
@test_util.skip_unless(test_util.requirement_available(PSUTIL_REQUIREMENT),
|
||||
"optional dependency psutil is not available")
|
||||
class AlreadyListeningTestPsutil(AlreadyListeningTest):
|
||||
"""Tests for certbot.plugins.already_listening."""
|
||||
@mock.patch("certbot.plugins.util.psutil.net_connections")
|
||||
@mock.patch("certbot.plugins.util.psutil.Process")
|
||||
@test_util.patch_get_utility()
|
||||
def test_race_condition(self, mock_get_utility, mock_process, mock_net):
|
||||
# This tests a race condition, or permission problem, or OS
|
||||
# incompatibility in which, for some reason, no process name can be
|
||||
# found to match the identified listening PID.
|
||||
import psutil
|
||||
from psutil._common import sconn
|
||||
conns = [
|
||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||
raddr=(), status="LISTEN", pid=None),
|
||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
||||
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
||||
raddr=(), status="LISTEN", pid=4416)]
|
||||
mock_net.return_value = conns
|
||||
mock_process.side_effect = psutil.NoSuchProcess("No such PID")
|
||||
# We simulate being unable to find the process name of PID 4416,
|
||||
# which results in returning False.
|
||||
self.assertFalse(self._call(17))
|
||||
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
|
||||
mock_process.assert_called_once_with(4416)
|
||||
|
||||
@mock.patch("certbot.plugins.util.psutil.net_connections")
|
||||
@mock.patch("certbot.plugins.util.psutil.Process")
|
||||
@test_util.patch_get_utility()
|
||||
def test_not_listening(self, mock_get_utility, mock_process, mock_net):
|
||||
from psutil._common import sconn
|
||||
conns = [
|
||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||
raddr=(), status="LISTEN", pid=None),
|
||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None)]
|
||||
mock_net.return_value = conns
|
||||
mock_process.name.return_value = "inetd"
|
||||
self.assertFalse(self._call(17))
|
||||
self.assertEqual(mock_get_utility.generic_notification.call_count, 0)
|
||||
self.assertEqual(mock_process.call_count, 0)
|
||||
|
||||
@mock.patch("certbot.plugins.util.psutil.net_connections")
|
||||
@mock.patch("certbot.plugins.util.psutil.Process")
|
||||
@test_util.patch_get_utility()
|
||||
def test_listening_ipv4(self, mock_get_utility, mock_process, mock_net):
|
||||
from psutil._common import sconn
|
||||
conns = [
|
||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||
raddr=(), status="LISTEN", pid=None),
|
||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
||||
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
||||
raddr=(), status="LISTEN", pid=4416)]
|
||||
mock_net.return_value = conns
|
||||
mock_process.name.return_value = "inetd"
|
||||
result = self._call(17, True)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
mock_process.assert_called_once_with(4416)
|
||||
|
||||
@mock.patch("certbot.plugins.util.psutil.net_connections")
|
||||
@mock.patch("certbot.plugins.util.psutil.Process")
|
||||
@test_util.patch_get_utility()
|
||||
def test_listening_ipv6(self, mock_get_utility, mock_process, mock_net):
|
||||
from psutil._common import sconn
|
||||
conns = [
|
||||
sconn(fd=-1, family=2, type=1, laddr=("0.0.0.0", 30),
|
||||
raddr=(), status="LISTEN", pid=None),
|
||||
sconn(fd=3, family=2, type=1, laddr=("192.168.5.10", 32783),
|
||||
raddr=("20.40.60.80", 22), status="ESTABLISHED", pid=1234),
|
||||
sconn(fd=-1, family=10, type=1, laddr=("::1", 54321),
|
||||
raddr=("::1", 111), status="CLOSE_WAIT", pid=None),
|
||||
sconn(fd=3, family=10, type=1, laddr=("::", 12345), raddr=(),
|
||||
status="LISTEN", pid=4420),
|
||||
sconn(fd=3, family=2, type=1, laddr=("0.0.0.0", 17),
|
||||
raddr=(), status="LISTEN", pid=4416)]
|
||||
mock_net.return_value = conns
|
||||
mock_process.name.return_value = "inetd"
|
||||
result = self._call(12345)
|
||||
self.assertTrue(result)
|
||||
self.assertEqual(mock_get_utility.call_count, 1)
|
||||
mock_process.assert_called_once_with(4420)
|
||||
|
||||
@mock.patch("certbot.plugins.util.psutil.net_connections")
|
||||
def test_access_denied_exception(self, mock_net):
|
||||
import psutil
|
||||
mock_net.side_effect = psutil.AccessDenied("")
|
||||
self.assertFalse(self._call(12345))
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ INT_CONFIG_ITEMS = ["rsa_key_size", "tls_sni_01_port", "http01_port"]
|
|||
BOOL_CONFIG_ITEMS = ["must_staple", "allow_subset_of_names"]
|
||||
|
||||
CONFIG_ITEMS = set(itertools.chain(
|
||||
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS))
|
||||
BOOL_CONFIG_ITEMS, INT_CONFIG_ITEMS, STR_CONFIG_ITEMS, ('pref_challs',)))
|
||||
|
||||
|
||||
def _reconstitute(config, full_path):
|
||||
|
|
@ -165,6 +165,7 @@ def restore_required_config_elements(config, renewalparams):
|
|||
"""
|
||||
|
||||
required_items = itertools.chain(
|
||||
(("pref_challs", _restore_pref_challs),),
|
||||
six.moves.zip(BOOL_CONFIG_ITEMS, itertools.repeat(_restore_bool)),
|
||||
six.moves.zip(INT_CONFIG_ITEMS, itertools.repeat(_restore_int)),
|
||||
six.moves.zip(STR_CONFIG_ITEMS, itertools.repeat(_restore_str)))
|
||||
|
|
@ -174,6 +175,28 @@ def restore_required_config_elements(config, renewalparams):
|
|||
setattr(config.namespace, item_name, value)
|
||||
|
||||
|
||||
def _restore_pref_challs(unused_name, value):
|
||||
"""Restores preferred challenges from a renewal config file.
|
||||
|
||||
If value is a `str`, it should be a single challenge type.
|
||||
|
||||
:param str unused_name: option name
|
||||
:param value: option value
|
||||
:type value: `list` of `str` or `str`
|
||||
|
||||
:returns: converted option value to be stored in the runtime config
|
||||
:rtype: `list` of `str`
|
||||
|
||||
:raises errors.Error: if value can't be converted to an bool
|
||||
|
||||
"""
|
||||
# If pref_challs has only one element, configobj saves the value
|
||||
# with a trailing comma so it's parsed as a list. If this comma is
|
||||
# removed by the user, the value is parsed as a str.
|
||||
value = [value] if isinstance(value, str) else value
|
||||
return cli.parse_preferred_challenges(value)
|
||||
|
||||
|
||||
def _restore_bool(name, value):
|
||||
"""Restores an boolean key-value pair from a renewal config file.
|
||||
|
||||
|
|
|
|||
|
|
@ -716,7 +716,7 @@ class RenewableCert(object):
|
|||
|
||||
:returns: ``True`` if there is a complete version of this
|
||||
lineage with a larger version number than the current
|
||||
version, and ``False`` otherwis
|
||||
version, and ``False`` otherwise
|
||||
:rtype: bool
|
||||
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -176,7 +176,8 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
mock_poll.side_effect = self._validate_all
|
||||
self.mock_auth.get_chall_pref.return_value.append(challenges.HTTP01)
|
||||
|
||||
self.handler.pref_challs.extend((challenges.HTTP01, challenges.DNS01,))
|
||||
self.handler.pref_challs.extend((challenges.HTTP01.typ,
|
||||
challenges.DNS01.typ,))
|
||||
|
||||
self.handler.get_authorizations(["0"])
|
||||
|
||||
|
|
@ -187,7 +188,7 @@ class GetAuthorizationsTest(unittest.TestCase):
|
|||
def test_preferred_challenges_not_supported(self):
|
||||
self.mock_net.request_domain_challenges.side_effect = functools.partial(
|
||||
gen_dom_authzr, challs=acme_util.CHALLENGES)
|
||||
self.handler.pref_challs.append(challenges.HTTP01)
|
||||
self.handler.pref_challs.append(challenges.HTTP01.typ)
|
||||
self.assertRaises(
|
||||
errors.AuthorizationError, self.handler.get_authorizations, ["0"])
|
||||
|
||||
|
|
|
|||
|
|
@ -268,11 +268,11 @@ class LineageForCertnameTest(BaseCertManagerTest):
|
|||
"""Tests for certbot.cert_manager.lineage_for_certname"""
|
||||
|
||||
@mock.patch('certbot.util.make_or_verify_dir')
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.renewal_file_for_certname')
|
||||
@mock.patch('certbot.storage.RenewableCert')
|
||||
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_files,
|
||||
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,
|
||||
mock_make_or_verify_dir):
|
||||
mock_renewal_conf_files.return_value = ["somefile.conf"]
|
||||
mock_renewal_conf_file.return_value = "somefile.conf"
|
||||
mock_match = mock.Mock(lineagename="example.com")
|
||||
mock_renewable_cert.return_value = mock_match
|
||||
from certbot import cert_manager
|
||||
|
|
@ -281,13 +281,10 @@ class LineageForCertnameTest(BaseCertManagerTest):
|
|||
self.assertTrue(mock_make_or_verify_dir.called)
|
||||
|
||||
@mock.patch('certbot.util.make_or_verify_dir')
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.RenewableCert')
|
||||
def test_no_match(self, mock_renewable_cert, mock_renewal_conf_files,
|
||||
@mock.patch('certbot.storage.renewal_file_for_certname')
|
||||
def test_no_match(self, mock_renewal_conf_file,
|
||||
mock_make_or_verify_dir):
|
||||
mock_renewal_conf_files.return_value = ["somefile.conf"]
|
||||
mock_match = mock.Mock(lineagename="other.com")
|
||||
mock_renewable_cert.return_value = mock_match
|
||||
mock_renewal_conf_file.return_value = "other.com.conf"
|
||||
from certbot import cert_manager
|
||||
self.assertEqual(cert_manager.lineage_for_certname(self.cli_config, "example.com"),
|
||||
None)
|
||||
|
|
@ -298,11 +295,11 @@ class DomainsForCertnameTest(BaseCertManagerTest):
|
|||
"""Tests for certbot.cert_manager.domains_for_certname"""
|
||||
|
||||
@mock.patch('certbot.util.make_or_verify_dir')
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.renewal_file_for_certname')
|
||||
@mock.patch('certbot.storage.RenewableCert')
|
||||
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_files,
|
||||
def test_found_match(self, mock_renewable_cert, mock_renewal_conf_file,
|
||||
mock_make_or_verify_dir):
|
||||
mock_renewal_conf_files.return_value = ["somefile.conf"]
|
||||
mock_renewal_conf_file.return_value = "somefile.conf"
|
||||
mock_match = mock.Mock(lineagename="example.com")
|
||||
domains = ["example.com", "example.org"]
|
||||
mock_match.names.return_value = domains
|
||||
|
|
@ -313,15 +310,10 @@ class DomainsForCertnameTest(BaseCertManagerTest):
|
|||
self.assertTrue(mock_make_or_verify_dir.called)
|
||||
|
||||
@mock.patch('certbot.util.make_or_verify_dir')
|
||||
@mock.patch('certbot.storage.renewal_conf_files')
|
||||
@mock.patch('certbot.storage.RenewableCert')
|
||||
def test_no_match(self, mock_renewable_cert, mock_renewal_conf_files,
|
||||
@mock.patch('certbot.storage.renewal_file_for_certname')
|
||||
def test_no_match(self, mock_renewal_conf_file,
|
||||
mock_make_or_verify_dir):
|
||||
mock_renewal_conf_files.return_value = ["somefile.conf"]
|
||||
mock_match = mock.Mock(lineagename="example.com")
|
||||
domains = ["example.com", "example.org"]
|
||||
mock_match.names.return_value = domains
|
||||
mock_renewable_cert.return_value = mock_match
|
||||
mock_renewal_conf_file.return_value = "somefile.conf"
|
||||
from certbot import cert_manager
|
||||
self.assertEqual(cert_manager.domains_for_certname(self.cli_config, "other.com"),
|
||||
None)
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import mock
|
|||
import six
|
||||
from six.moves import reload_module # pylint: disable=import-error
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import cli
|
||||
from certbot import constants
|
||||
from certbot import errors
|
||||
|
|
@ -50,7 +52,7 @@ class ParseTest(unittest.TestCase):
|
|||
return cli.prepare_and_parse_args(PLUGINS, *args, **kwargs)
|
||||
|
||||
def _help_output(self, args):
|
||||
"Run a command, and return the ouput string for scrutiny"
|
||||
"Run a command, and return the output string for scrutiny"
|
||||
|
||||
output = six.StringIO()
|
||||
with mock.patch('certbot.main.sys.stdout', new=output):
|
||||
|
|
@ -58,6 +60,11 @@ class ParseTest(unittest.TestCase):
|
|||
self.assertRaises(SystemExit, self.parse, args, output)
|
||||
return output.getvalue()
|
||||
|
||||
def test_no_args(self):
|
||||
namespace = self.parse([])
|
||||
for d in ('config_dir', 'logs_dir', 'work_dir'):
|
||||
self.assertEqual(getattr(namespace, d), cli.flag_default(d))
|
||||
|
||||
def test_install_abspath(self):
|
||||
cert = 'cert'
|
||||
key = 'key'
|
||||
|
|
@ -178,12 +185,12 @@ class ParseTest(unittest.TestCase):
|
|||
self.assertEqual(namespace.domains, ['example.com', 'another.net'])
|
||||
|
||||
def test_preferred_challenges(self):
|
||||
from acme.challenges import HTTP01, TLSSNI01, DNS01
|
||||
|
||||
short_args = ['--preferred-challenges', 'http, tls-sni-01, dns']
|
||||
namespace = self.parse(short_args)
|
||||
|
||||
self.assertEqual(namespace.pref_challs, [HTTP01, TLSSNI01, DNS01])
|
||||
expected = [challenges.HTTP01.typ,
|
||||
challenges.TLSSNI01.typ, challenges.DNS01.typ]
|
||||
self.assertEqual(namespace.pref_challs, expected)
|
||||
|
||||
short_args = ['--preferred-challenges', 'jumping-over-the-moon']
|
||||
self.assertRaises(argparse.ArgumentTypeError, self.parse, short_args)
|
||||
|
|
|
|||
|
|
@ -44,20 +44,25 @@ class RegisterTest(unittest.TestCase):
|
|||
def test_no_tos(self):
|
||||
with mock.patch("certbot.client.acme_client.Client") as mock_client:
|
||||
mock_client.register().terms_of_service = "http://tos"
|
||||
with mock.patch("certbot.account.report_new_account"):
|
||||
self.tos_cb.return_value = False
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
|
||||
with mock.patch("certbot.account.report_new_account"):
|
||||
self.tos_cb.return_value = False
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
self.assertFalse(mock_handle.called)
|
||||
|
||||
self.tos_cb.return_value = True
|
||||
self._call()
|
||||
self.tos_cb.return_value = True
|
||||
self._call()
|
||||
self.assertTrue(mock_handle.called)
|
||||
|
||||
self.tos_cb = None
|
||||
self._call()
|
||||
self.tos_cb = None
|
||||
self._call()
|
||||
self.assertEqual(mock_handle.call_count, 2)
|
||||
|
||||
def test_it(self):
|
||||
with mock.patch("certbot.client.acme_client.Client"):
|
||||
with mock.patch("certbot.account.report_new_account"):
|
||||
self._call()
|
||||
with mock.patch("certbot.eff.handle_subscription"):
|
||||
self._call()
|
||||
|
||||
@mock.patch("certbot.account.report_new_account")
|
||||
@mock.patch("certbot.client.display_ops.get_email")
|
||||
|
|
@ -67,9 +72,11 @@ class RegisterTest(unittest.TestCase):
|
|||
msg = "DNS problem: NXDOMAIN looking up MX for example.com"
|
||||
mx_err = messages.Error.with_code('invalidContact', detail=msg)
|
||||
with mock.patch("certbot.client.acme_client.Client") as mock_client:
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self._call()
|
||||
self.assertEqual(mock_get_email.call_count, 1)
|
||||
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self._call()
|
||||
self.assertEqual(mock_get_email.call_count, 1)
|
||||
self.assertTrue(mock_handle.called)
|
||||
|
||||
@mock.patch("certbot.account.report_new_account")
|
||||
def test_email_invalid_noninteractive(self, _rep):
|
||||
|
|
@ -77,8 +84,9 @@ class RegisterTest(unittest.TestCase):
|
|||
msg = "DNS problem: NXDOMAIN looking up MX for example.com"
|
||||
mx_err = messages.Error.with_code('invalidContact', detail=msg)
|
||||
with mock.patch("certbot.client.acme_client.Client") as mock_client:
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
with mock.patch("certbot.eff.handle_subscription"):
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self.assertRaises(errors.Error, self._call)
|
||||
|
||||
def test_needs_email(self):
|
||||
self.config.email = None
|
||||
|
|
@ -86,21 +94,25 @@ class RegisterTest(unittest.TestCase):
|
|||
|
||||
@mock.patch("certbot.client.logger")
|
||||
def test_without_email(self, mock_logger):
|
||||
with mock.patch("certbot.client.acme_client.Client"):
|
||||
with mock.patch("certbot.account.report_new_account"):
|
||||
self.config.email = None
|
||||
self.config.register_unsafely_without_email = True
|
||||
self.config.dry_run = False
|
||||
self._call()
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
|
||||
with mock.patch("certbot.client.acme_client.Client"):
|
||||
with mock.patch("certbot.account.report_new_account"):
|
||||
self.config.email = None
|
||||
self.config.register_unsafely_without_email = True
|
||||
self.config.dry_run = False
|
||||
self._call()
|
||||
mock_logger.warning.assert_called_once_with(mock.ANY)
|
||||
self.assertTrue(mock_handle.called)
|
||||
|
||||
def test_unsupported_error(self):
|
||||
from acme import messages
|
||||
msg = "Test"
|
||||
mx_err = messages.Error(detail=msg, typ="malformed", title="title")
|
||||
with mock.patch("certbot.client.acme_client.Client") as mock_client:
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self.assertRaises(messages.Error, self._call)
|
||||
with mock.patch("certbot.eff.handle_subscription") as mock_handle:
|
||||
mock_client().register.side_effect = [mx_err, mock.MagicMock()]
|
||||
self.assertRaises(messages.Error, self._call)
|
||||
self.assertFalse(mock_handle.called)
|
||||
|
||||
|
||||
class ClientTestCommon(unittest.TestCase):
|
||||
|
|
|
|||
135
certbot/tests/eff_test.py
Normal file
135
certbot/tests/eff_test.py
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
"""Tests for certbot.eff."""
|
||||
import unittest
|
||||
|
||||
import mock
|
||||
|
||||
from certbot import constants
|
||||
from certbot.tests import util
|
||||
|
||||
|
||||
class HandleSubscriptionTest(unittest.TestCase):
|
||||
"""Tests for certbot.eff.handle_subscription."""
|
||||
def setUp(self):
|
||||
self.email = 'certbot@example.org'
|
||||
self.config = mock.Mock(email=self.email, eff_email=None)
|
||||
|
||||
def _call(self):
|
||||
from certbot.eff import handle_subscription
|
||||
return handle_subscription(self.config)
|
||||
|
||||
@util.patch_get_utility()
|
||||
@mock.patch('certbot.eff.subscribe')
|
||||
def test_failure(self, mock_subscribe, mock_get_utility):
|
||||
self.config.email = None
|
||||
self.config.eff_email = True
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
self.assertFalse(mock_get_utility().yesno.called)
|
||||
actual = mock_get_utility().add_message.call_args[0][0]
|
||||
expected_part = "because you didn't provide an e-mail address"
|
||||
self.assertTrue(expected_part in actual)
|
||||
|
||||
@mock.patch('certbot.eff.subscribe')
|
||||
def test_no_subscribe_with_no_prompt(self, mock_subscribe):
|
||||
self.config.eff_email = False
|
||||
with util.patch_get_utility() as mock_get_utility:
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
self._assert_no_get_utility_calls(mock_get_utility)
|
||||
|
||||
@util.patch_get_utility()
|
||||
@mock.patch('certbot.eff.subscribe')
|
||||
def test_subscribe_with_no_prompt(self, mock_subscribe, mock_get_utility):
|
||||
self.config.eff_email = True
|
||||
self._call()
|
||||
self._assert_subscribed(mock_subscribe)
|
||||
self._assert_no_get_utility_calls(mock_get_utility)
|
||||
|
||||
def _assert_no_get_utility_calls(self, mock_get_utility):
|
||||
self.assertFalse(mock_get_utility().yesno.called)
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
|
||||
@util.patch_get_utility()
|
||||
@mock.patch('certbot.eff.subscribe')
|
||||
def test_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
|
||||
mock_get_utility().yesno.return_value = True
|
||||
self._call()
|
||||
self._assert_subscribed(mock_subscribe)
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
self._assert_correct_yesno_call(mock_get_utility)
|
||||
|
||||
def _assert_subscribed(self, mock_subscribe):
|
||||
self.assertTrue(mock_subscribe.called)
|
||||
self.assertEqual(mock_subscribe.call_args[0][0], self.email)
|
||||
|
||||
@util.patch_get_utility()
|
||||
@mock.patch('certbot.eff.subscribe')
|
||||
def test_no_subscribe_with_prompt(self, mock_subscribe, mock_get_utility):
|
||||
mock_get_utility().yesno.return_value = False
|
||||
self._call()
|
||||
self.assertFalse(mock_subscribe.called)
|
||||
self.assertFalse(mock_get_utility().add_message.called)
|
||||
self._assert_correct_yesno_call(mock_get_utility)
|
||||
|
||||
def _assert_correct_yesno_call(self, mock_get_utility):
|
||||
self.assertTrue(mock_get_utility().yesno.called)
|
||||
call_args, call_kwargs = mock_get_utility().yesno.call_args
|
||||
actual = call_args[0]
|
||||
expected_part = 'Electronic Frontier Foundation'
|
||||
self.assertTrue(expected_part in actual)
|
||||
self.assertFalse(call_kwargs.get('default', True))
|
||||
|
||||
|
||||
class SubscribeTest(unittest.TestCase):
|
||||
"""Tests for certbot.eff.subscribe."""
|
||||
def setUp(self):
|
||||
self.email = 'certbot@example.org'
|
||||
self.json = {'status': True}
|
||||
self.response = mock.Mock(ok=True)
|
||||
self.response.json.return_value = self.json
|
||||
|
||||
@mock.patch('certbot.eff.requests.post')
|
||||
def _call(self, mock_post):
|
||||
mock_post.return_value = self.response
|
||||
|
||||
from certbot.eff import subscribe
|
||||
subscribe(self.email)
|
||||
self._check_post_call(mock_post)
|
||||
|
||||
def _check_post_call(self, mock_post):
|
||||
self.assertEqual(mock_post.call_count, 1)
|
||||
call_args, call_kwargs = mock_post.call_args
|
||||
self.assertEqual(call_args[0], constants.EFF_SUBSCRIBE_URI)
|
||||
|
||||
data = call_kwargs.get('data')
|
||||
self.assertFalse(data is None)
|
||||
self.assertEqual(data.get('email'), self.email)
|
||||
|
||||
@util.patch_get_utility()
|
||||
def test_bad_status(self, mock_get_utility):
|
||||
self.json['status'] = False
|
||||
self._call() # pylint: disable=no-value-for-parameter
|
||||
actual = self._get_reported_message(mock_get_utility)
|
||||
expected_part = 'because your e-mail address appears to be invalid.'
|
||||
self.assertTrue(expected_part in actual)
|
||||
|
||||
@util.patch_get_utility()
|
||||
def test_not_ok(self, mock_get_utility):
|
||||
self.response.ok = False
|
||||
self._call() # pylint: disable=no-value-for-parameter
|
||||
actual = self._get_reported_message(mock_get_utility)
|
||||
unexpected_part = 'because'
|
||||
self.assertFalse(unexpected_part in actual)
|
||||
|
||||
def _get_reported_message(self, mock_get_utility):
|
||||
self.assertTrue(mock_get_utility().add_message.called)
|
||||
return mock_get_utility().add_message.call_args[0][0]
|
||||
|
||||
@util.patch_get_utility()
|
||||
def test_subscribe(self, mock_get_utility):
|
||||
self._call() # pylint: disable=no-value-for-parameter
|
||||
self.assertFalse(mock_get_utility.called)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main() # pragma: no cover
|
||||
|
|
@ -70,7 +70,7 @@ class ErrorHandlerTest(unittest.TestCase):
|
|||
send_signal(self.signals[0])
|
||||
should_be_42 *= 10
|
||||
|
||||
# check exectuion stoped when the signal was sent
|
||||
# check execution stoped when the signal was sent
|
||||
self.assertEqual(42, should_be_42)
|
||||
# assert signals were caught
|
||||
self.assertEqual([self.signals[0]], signals_received)
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from certbot import achallenges
|
|||
from certbot.tests import acme_util
|
||||
|
||||
|
||||
class FaiiledChallengesTest(unittest.TestCase):
|
||||
class FailedChallengesTest(unittest.TestCase):
|
||||
"""Tests for certbot.errors.FailedChallenges."""
|
||||
|
||||
def setUp(self):
|
||||
|
|
|
|||
|
|
@ -1145,9 +1145,9 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
def test_update_registration_with_email(self, mock_utility, mock_email):
|
||||
email = "user@example.com"
|
||||
mock_email.return_value = email
|
||||
with mock.patch('certbot.main.client') as mocked_client:
|
||||
with mock.patch('certbot.main.account') as mocked_account:
|
||||
with mock.patch('certbot.main._determine_account') as mocked_det:
|
||||
with mock.patch('certbot.eff.handle_subscription') as mock_handle:
|
||||
with mock.patch('certbot.main._determine_account') as mocked_det:
|
||||
with mock.patch('certbot.main.account') as mocked_account:
|
||||
with mock.patch('certbot.main.client') as mocked_client:
|
||||
mocked_storage = mock.MagicMock()
|
||||
mocked_account.AccountFileStorage.return_value = mocked_storage
|
||||
|
|
@ -1168,6 +1168,7 @@ class MainTest(unittest.TestCase): # pylint: disable=too-many-public-methods
|
|||
self.assertTrue(mocked_storage.save_regr.called)
|
||||
self.assertTrue(
|
||||
email in mock_utility().add_message.call_args[0][0])
|
||||
self.assertTrue(mock_handle.called)
|
||||
|
||||
|
||||
class UnregisterTest(unittest.TestCase):
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import unittest
|
|||
import shutil
|
||||
import tempfile
|
||||
|
||||
from acme import challenges
|
||||
|
||||
from certbot import configuration
|
||||
from certbot import errors
|
||||
from certbot import storage
|
||||
|
|
@ -59,6 +61,29 @@ class RestoreRequiredConfigElementsTest(unittest.TestCase):
|
|||
self.assertRaises(
|
||||
errors.Error, self._call, self.config, renewalparams)
|
||||
|
||||
@mock.patch('certbot.renewal.cli.set_by_cli')
|
||||
def test_pref_challs_list(self, mock_set_by_cli):
|
||||
mock_set_by_cli.return_value = False
|
||||
renewalparams = {'pref_challs': 'tls-sni, http-01, dns'.split(',')}
|
||||
self._call(self.config, renewalparams)
|
||||
expected = [challenges.TLSSNI01.typ,
|
||||
challenges.HTTP01.typ, challenges.DNS01.typ]
|
||||
self.assertEqual(self.config.namespace.pref_challs, expected)
|
||||
|
||||
@mock.patch('certbot.renewal.cli.set_by_cli')
|
||||
def test_pref_challs_str(self, mock_set_by_cli):
|
||||
mock_set_by_cli.return_value = False
|
||||
renewalparams = {'pref_challs': 'dns'}
|
||||
self._call(self.config, renewalparams)
|
||||
expected = [challenges.DNS01.typ]
|
||||
self.assertEqual(self.config.namespace.pref_challs, expected)
|
||||
|
||||
@mock.patch('certbot.renewal.cli.set_by_cli')
|
||||
def test_pref_challs_failure(self, mock_set_by_cli):
|
||||
mock_set_by_cli.return_value = False
|
||||
renewalparams = {'pref_challs': 'finding-a-shrubbery'}
|
||||
self.assertRaises(errors.Error, self._call, self.config, renewalparams)
|
||||
|
||||
@mock.patch('certbot.renewal.cli.set_by_cli')
|
||||
def test_must_staple_success(self, mock_set_by_cli):
|
||||
mock_set_by_cli.return_value = False
|
||||
|
|
|
|||
|
|
@ -394,7 +394,7 @@ class TestFullCheckpointsReverter(unittest.TestCase):
|
|||
self.assertTrue(mock_logger.info.call_count > 0)
|
||||
|
||||
def test_view_config_changes_bad_backups_dir(self):
|
||||
# There shouldn't be any "in progess directories when this is called
|
||||
# There shouldn't be any "in progress directories when this is called
|
||||
# It must just be clean checkpoints
|
||||
os.makedirs(os.path.join(self.config.backup_dir, "in_progress"))
|
||||
|
||||
|
|
|
|||
|
|
@ -13,9 +13,7 @@ from cryptography.hazmat.primitives import serialization
|
|||
import mock
|
||||
import OpenSSL
|
||||
|
||||
from acme import errors
|
||||
from acme import jose
|
||||
from acme import util
|
||||
|
||||
from certbot import constants
|
||||
from certbot import interfaces
|
||||
|
|
@ -86,20 +84,6 @@ def load_pyopenssl_private_key(*names):
|
|||
return OpenSSL.crypto.load_privatekey(loader, load_vector(*names))
|
||||
|
||||
|
||||
def requirement_available(requirement):
|
||||
"""Checks if requirement can be imported.
|
||||
|
||||
:rtype: bool
|
||||
:returns: ``True`` iff requirement can be imported
|
||||
|
||||
"""
|
||||
try:
|
||||
util.activate(requirement)
|
||||
except errors.DependencyError: # pragma: no cover
|
||||
return False
|
||||
return True # pragma: no cover
|
||||
|
||||
|
||||
def skip_unless(condition, reason): # pragma: no cover
|
||||
"""Skip tests unless a condition holds.
|
||||
|
||||
|
|
|
|||
|
|
@ -219,6 +219,25 @@ def safely_remove(path):
|
|||
raise
|
||||
|
||||
|
||||
def get_filtered_names(all_names):
|
||||
"""Removes names that aren't considered valid by Let's Encrypt.
|
||||
|
||||
:param set all_names: all names found in the configuration
|
||||
|
||||
:returns: all found names that are considered valid by LE
|
||||
:rtype: set
|
||||
|
||||
"""
|
||||
filtered_names = set()
|
||||
for name in all_names:
|
||||
try:
|
||||
filtered_names.add(enforce_le_validity(name))
|
||||
except errors.ConfigurationError as error:
|
||||
logger.debug('Not suggesting name "%s"', name)
|
||||
logger.debug(error)
|
||||
return filtered_names
|
||||
|
||||
|
||||
def get_os_info(filepath="/etc/os-release"):
|
||||
"""
|
||||
Get OS name and version
|
||||
|
|
|
|||
|
|
@ -206,7 +206,7 @@ or a family of enhancements, one per selectable ciphersuite configuration.
|
|||
|
||||
Feedback
|
||||
========
|
||||
We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some coallated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
|
||||
We receive lots of feedback on the type of ciphersuites that Let's Encrypt supports and list some collated feedback below. This section aims to track suggestions and references that people have offered or identified to improve the ciphersuites that Let's Encrypt enables when configuring TLS on servers.
|
||||
|
||||
Because of the Chatham House Rule applicable to some of the discussions, people are *not* individually credited for their suggestions, but most suggestions here were made or found by other people, and I thank them for their contributions.
|
||||
|
||||
|
|
|
|||
|
|
@ -86,7 +86,7 @@ optional arguments:
|
|||
statistics about success rates by OS and plugin. If
|
||||
you wish to hide your server OS version from the Let's
|
||||
Encrypt server, set this to "". (default:
|
||||
CertbotACMEClient/0.10.1 (Ubuntu 16.04.1 LTS)
|
||||
CertbotACMEClient/0.11.0 (Ubuntu 16.04.1 LTS)
|
||||
Authenticator/XXX Installer/YYY)
|
||||
|
||||
automation:
|
||||
|
|
@ -120,7 +120,6 @@ automation:
|
|||
system. This option cannot be used with --csr.
|
||||
(default: False)
|
||||
--agree-tos Agree to the ACME Subscriber Agreement (default: Ask)
|
||||
--account ACCOUNT_ID Account ID to use (default: None)
|
||||
--duplicate Allow making a certificate lineage that duplicates an
|
||||
existing one (both can be renewed in parallel)
|
||||
(default: False)
|
||||
|
|
@ -210,7 +209,7 @@ manage:
|
|||
|
||||
certificates List certificates managed by Certbot
|
||||
delete Clean up all files related to a certificate
|
||||
renew Renew all certificates (or one specifed with --cert-
|
||||
renew Renew all certificates (or one specified with --cert-
|
||||
name)
|
||||
revoke Revoke a certificate specified with --cert-path
|
||||
update_symlinks Recreate symlinks in your /etc/letsencrypt/live/
|
||||
|
|
@ -280,6 +279,9 @@ delete:
|
|||
revoke:
|
||||
Options for revocation of certs
|
||||
|
||||
--reason {keycompromise,affiliationchanged,superseded,unspecified,cessationofoperation}
|
||||
Specify reason for revoking certificate. (default: 0)
|
||||
|
||||
register:
|
||||
Options for account registration & modification
|
||||
|
||||
|
|
@ -301,6 +303,14 @@ register:
|
|||
-m EMAIL, --email EMAIL
|
||||
Email used for registration and recovery contact.
|
||||
(default: Ask)
|
||||
--eff-email Share your e-mail address with EFF (default: None)
|
||||
--no-eff-email Don't share your e-mail address with EFF (default:
|
||||
None)
|
||||
|
||||
unregister:
|
||||
Options for account deactivation.
|
||||
|
||||
--account ACCOUNT_ID Account ID to use (default: None)
|
||||
|
||||
install:
|
||||
Options for modifying how a cert is deployed
|
||||
|
|
|
|||
|
|
@ -336,7 +336,7 @@ the ``letsencrypt-auto-source`` and
|
|||
Building letsencrypt-auto-source/letsencrypt-auto
|
||||
-------------------------------------------------
|
||||
Once changes to any of the aforementioned files have been made, the
|
||||
``letesncrypt-auto-source/letsencrypt-auto`` script should be updated. In lieu of
|
||||
``letsencrypt-auto-source/letsencrypt-auto`` script should be updated. In lieu of
|
||||
manually updating this script, run the build script, which lives at
|
||||
``letsencrypt-auto-source/build.py``:
|
||||
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ control the domain(s) you are requesting a cert for, obtains a cert for the spec
|
|||
domain(s), and places the cert in the ``/etc/letsencrypt`` directory on your
|
||||
machine. The authenticator does not install the cert (it does not edit any of your server's configuration files to serve the
|
||||
obtained certificate). If you specify multiple domains to authenticate, they will
|
||||
all be listed in a single certificate. To obtain multiple seperate certificates
|
||||
all be listed in a single certificate. To obtain multiple separate certificates
|
||||
you will need to run Certbot multiple times.
|
||||
|
||||
Installers are Plugins used with the ``install`` command to install a cert.
|
||||
|
|
@ -62,7 +62,7 @@ manual_ Y N | Helps you obtain a cert by giving you instructions to pe
|
|||
|
||||
Under the hood, plugins use one of several ACME protocol "Challenges_" to
|
||||
prove you control a domain. The options are http-01_ (which uses port 80),
|
||||
tls-sni-01_ (port 443) and dns-01_ (requring configuration of a DNS server on
|
||||
tls-sni-01_ (port 443) and dns-01_ (requiring configuration of a DNS server on
|
||||
port 53, though that's often not the same machine as your webserver). A few
|
||||
plugins support more than one challenge type, in which case you can choose one
|
||||
with ``--preferred-challenges``.
|
||||
|
|
@ -433,7 +433,7 @@ variables to these scripts:
|
|||
|
||||
- ``CERTBOT_DOMAIN``: The domain being authenticated
|
||||
- ``CERTBOT_VALIDATION``: The validation string
|
||||
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenege (HTTP-01 only)
|
||||
- ``CERTBOT_TOKEN``: Resource name part of the HTTP-01 challenge (HTTP-01 only)
|
||||
|
||||
Additionally for cleanup:
|
||||
|
||||
|
|
|
|||
234
letsencrypt-auto
234
letsencrypt-auto
|
|
@ -23,7 +23,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
|
||||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
LE_AUTO_VERSION="0.10.1"
|
||||
LE_AUTO_VERSION="0.11.0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -38,8 +38,9 @@ Help for certbot itself cannot be provided until it is installed.
|
|||
-n, --non-interactive, --noninteractive run without asking for user input
|
||||
--no-self-upgrade do not download updates
|
||||
--os-packages-only install OS dependencies and exit
|
||||
-q, --quiet provide only update/error output
|
||||
-v, --verbose provide more output
|
||||
-q, --quiet provide only update/error output;
|
||||
implies --non-interactive
|
||||
|
||||
All arguments are accepted and forwarded to the Certbot client when run."
|
||||
|
||||
|
|
@ -84,6 +85,11 @@ if [ $BASENAME = "letsencrypt-auto" ]; then
|
|||
HELP=0
|
||||
fi
|
||||
|
||||
# Set ASSUME_YES to 1 if QUIET (i.e. --quiet implies --non-interactive)
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
ASSUME_YES=1
|
||||
fi
|
||||
|
||||
# Support for busybox and others where there is no "command",
|
||||
# but "which" instead
|
||||
if command -v command > /dev/null 2>&1 ; then
|
||||
|
|
@ -99,7 +105,7 @@ fi
|
|||
# certbot itself needs root access for almost all modes of operation
|
||||
# The "normal" case is that sudo is used for the steps that need root, but
|
||||
# this script *can* be run as root (not recommended), or fall back to using
|
||||
# `su`. Auto-detection can be overrided by explicitly setting the
|
||||
# `su`. Auto-detection can be overridden by explicitly setting the
|
||||
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
|
||||
|
||||
# Because the parameters in `su -c` has to be a string,
|
||||
|
|
@ -207,7 +213,11 @@ BootstrapDebCommon() {
|
|||
#
|
||||
# - Debian 6.0.10 "squeeze" (x64)
|
||||
|
||||
$SUDO apt-get update || echo apt-get update hit problems but continuing anyway...
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='-qq'
|
||||
fi
|
||||
|
||||
$SUDO apt-get $QUIET_FLAG update || echo apt-get update hit problems but continuing anyway...
|
||||
|
||||
# virtualenv binary can be found in different packages depending on
|
||||
# distro version (#346)
|
||||
|
|
@ -215,74 +225,74 @@ BootstrapDebCommon() {
|
|||
virtualenv=
|
||||
# virtual env is known to apt and is installable
|
||||
if apt-cache show virtualenv > /dev/null 2>&1 ; then
|
||||
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
if ! LC_ALL=C apt-cache --quiet=0 show virtualenv 2>&1 | grep -q 'No packages found'; then
|
||||
virtualenv="virtualenv"
|
||||
fi
|
||||
fi
|
||||
|
||||
if apt-cache show python-virtualenv > /dev/null 2>&1; then
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
virtualenv="$virtualenv python-virtualenv"
|
||||
fi
|
||||
|
||||
augeas_pkg="libaugeas0 augeas-lenses"
|
||||
AUGVERSION=`LC_ALL=C apt-cache show --no-all-versions libaugeas0 | grep ^Version: | cut -d" " -f2`
|
||||
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
YES_FLAG="-y"
|
||||
YES_FLAG="-y"
|
||||
fi
|
||||
|
||||
AddBackportRepo() {
|
||||
# ARGS:
|
||||
BACKPORT_NAME="$1"
|
||||
BACKPORT_SOURCELINE="$2"
|
||||
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
|
||||
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
|
||||
sleep 1s
|
||||
add_backports=1
|
||||
else
|
||||
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
|
||||
case $response in
|
||||
[yY][eE][sS]|[yY]|"")
|
||||
add_backports=1;;
|
||||
*)
|
||||
add_backports=0;;
|
||||
esac
|
||||
fi
|
||||
if [ "$add_backports" = 1 ]; then
|
||||
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO apt-get update
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$add_backports" != 0 ]; then
|
||||
$SUDO apt-get install $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
|
||||
augeas_pkg=
|
||||
# ARGS:
|
||||
BACKPORT_NAME="$1"
|
||||
BACKPORT_SOURCELINE="$2"
|
||||
echo "To use the Apache Certbot plugin, augeas needs to be installed from $BACKPORT_NAME."
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list | grep -q "$BACKPORT_NAME" ; then
|
||||
# This can theoretically error if sources.list.d is empty, but in that case we don't care.
|
||||
if ! grep -v -e ' *#' /etc/apt/sources.list.d/* 2>/dev/null | grep -q "$BACKPORT_NAME"; then
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
/bin/echo -n "Installing augeas from $BACKPORT_NAME in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rInstalling augeas from $BACKPORT_NAME in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rInstalling augeas from $BACKPORT_NAME in 1 second ..."
|
||||
sleep 1s
|
||||
add_backports=1
|
||||
else
|
||||
read -p "Would you like to enable the $BACKPORT_NAME repository [Y/n]? " response
|
||||
case $response in
|
||||
[yY][eE][sS]|[yY]|"")
|
||||
add_backports=1;;
|
||||
*)
|
||||
add_backports=0;;
|
||||
esac
|
||||
fi
|
||||
if [ "$add_backports" = 1 ]; then
|
||||
$SUDO sh -c "echo $BACKPORT_SOURCELINE >> /etc/apt/sources.list.d/$BACKPORT_NAME.list"
|
||||
$SUDO apt-get $QUIET_FLAG update
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
if [ "$add_backports" != 0 ]; then
|
||||
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends -t "$BACKPORT_NAME" $augeas_pkg
|
||||
augeas_pkg=
|
||||
fi
|
||||
}
|
||||
|
||||
|
||||
if dpkg --compare-versions 1.0 gt "$AUGVERSION" ; then
|
||||
if lsb_release -a | grep -q wheezy ; then
|
||||
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
|
||||
elif lsb_release -a | grep -q precise ; then
|
||||
# XXX add ARM case
|
||||
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
|
||||
else
|
||||
echo "No libaugeas0 version is available that's new enough to run the"
|
||||
echo "Certbot apache plugin..."
|
||||
fi
|
||||
# XXX add a case for ubuntu PPAs
|
||||
if lsb_release -a | grep -q wheezy ; then
|
||||
AddBackportRepo wheezy-backports "deb http://http.debian.net/debian wheezy-backports main"
|
||||
elif lsb_release -a | grep -q precise ; then
|
||||
# XXX add ARM case
|
||||
AddBackportRepo precise-backports "deb http://archive.ubuntu.com/ubuntu precise-backports main restricted universe multiverse"
|
||||
else
|
||||
echo "No libaugeas0 version is available that's new enough to run the"
|
||||
echo "Certbot apache plugin..."
|
||||
fi
|
||||
# XXX add a case for ubuntu PPAs
|
||||
fi
|
||||
|
||||
$SUDO apt-get install $YES_FLAG --no-install-recommends \
|
||||
$SUDO apt-get install $QUIET_FLAG $YES_FLAG --no-install-recommends \
|
||||
python \
|
||||
python-dev \
|
||||
$virtualenv \
|
||||
|
|
@ -294,7 +304,6 @@ BootstrapDebCommon() {
|
|||
ca-certificates \
|
||||
|
||||
|
||||
|
||||
if ! $EXISTS virtualenv > /dev/null ; then
|
||||
echo Failed to install a working \"virtualenv\" command, exiting
|
||||
exit 1
|
||||
|
|
@ -323,6 +332,9 @@ BootstrapRpmCommon() {
|
|||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
yes_flag="-y"
|
||||
fi
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='--quiet'
|
||||
fi
|
||||
|
||||
if ! $SUDO $tool list *virtualenv >/dev/null 2>&1; then
|
||||
echo "To use Certbot, packages from the EPEL repository need to be installed."
|
||||
|
|
@ -331,14 +343,14 @@ BootstrapRpmCommon() {
|
|||
exit 1
|
||||
fi
|
||||
if [ "$ASSUME_YES" = 1 ]; then
|
||||
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -n "Enabling the EPEL repository in 3 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -ne "\e[0K\rEnabling the EPEL repository in 2 seconds..."
|
||||
sleep 1s
|
||||
/bin/echo -e "\e[0K\rEnabling the EPEL repository in 1 seconds..."
|
||||
sleep 1s
|
||||
fi
|
||||
if ! $SUDO $tool install $yes_flag epel-release; then
|
||||
if ! $SUDO $tool install $yes_flag $QUIET_FLAG epel-release; then
|
||||
echo "Could not enable EPEL. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
|
|
@ -380,9 +392,9 @@ BootstrapRpmCommon() {
|
|||
"
|
||||
fi
|
||||
|
||||
if ! $SUDO $tool install $yes_flag $pkgs; then
|
||||
echo "Could not install OS dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
if ! $SUDO $tool install $yes_flag $QUIET_FLAG $pkgs; then
|
||||
echo "Could not install OS dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -394,7 +406,11 @@ BootstrapSuseCommon() {
|
|||
install_flags="-l"
|
||||
fi
|
||||
|
||||
$SUDO zypper $zypper_flags in $install_flags \
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='-qq'
|
||||
fi
|
||||
|
||||
$SUDO zypper $QUIET_FLAG $zypper_flags in $install_flags \
|
||||
python \
|
||||
python-devel \
|
||||
python-virtualenv \
|
||||
|
|
@ -432,7 +448,11 @@ BootstrapArchCommon() {
|
|||
fi
|
||||
|
||||
if [ "$missing" ]; then
|
||||
$SUDO pacman -S --needed $missing $noconfirm
|
||||
if [ "$QUIET" = 1]; then
|
||||
$SUDO pacman -S --needed $missing $noconfirm > /dev/null
|
||||
else
|
||||
$SUDO pacman -S --needed $missing $noconfirm
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -465,7 +485,11 @@ BootstrapGentooCommon() {
|
|||
}
|
||||
|
||||
BootstrapFreeBsd() {
|
||||
$SUDO pkg install -Ay \
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG="--quiet"
|
||||
fi
|
||||
|
||||
$SUDO pkg install -Ay $QUIET_FLAG \
|
||||
python \
|
||||
py27-virtualenv \
|
||||
augeas \
|
||||
|
|
@ -505,15 +529,15 @@ BootstrapMac() {
|
|||
fi
|
||||
|
||||
if ! hash pip 2>/dev/null; then
|
||||
echo "pip not installed"
|
||||
echo "Installing pip..."
|
||||
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
|
||||
echo "pip not installed"
|
||||
echo "Installing pip..."
|
||||
curl --silent --show-error --retry 5 https://bootstrap.pypa.io/get-pip.py | python
|
||||
fi
|
||||
|
||||
if ! hash virtualenv 2>/dev/null; then
|
||||
echo "virtualenv not installed."
|
||||
echo "Installing with pip..."
|
||||
pip install virtualenv
|
||||
echo "virtualenv not installed."
|
||||
echo "Installing with pip..."
|
||||
pip install virtualenv
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -523,26 +547,29 @@ BootstrapSmartOS() {
|
|||
}
|
||||
|
||||
BootstrapMageiaCommon() {
|
||||
if ! $SUDO urpmi --force \
|
||||
python \
|
||||
libpython-devel \
|
||||
python-virtualenv
|
||||
if [ "$QUIET" = 1 ]; then
|
||||
QUIET_FLAG='--quiet'
|
||||
fi
|
||||
|
||||
if ! $SUDO urpmi --force $QUIET_FLAG \
|
||||
python \
|
||||
libpython-devel \
|
||||
python-virtualenv
|
||||
then
|
||||
echo "Could not install Python dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
if ! $SUDO urpmi --force \
|
||||
git \
|
||||
gcc \
|
||||
python-augeas \
|
||||
openssl \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
rootcerts
|
||||
if ! $SUDO urpmi --force $QUIET_FLAG \
|
||||
git \
|
||||
gcc \
|
||||
python-augeas \
|
||||
libopenssl-devel \
|
||||
libffi-devel \
|
||||
rootcerts
|
||||
then
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
echo "Could not install additional dependencies. Aborting bootstrap!"
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
|
|
@ -609,6 +636,11 @@ if [ "$1" = "--le-auto-phase2" ]; then
|
|||
# --version output ran through grep due to python-cryptography DeprecationWarnings
|
||||
# grep for both certbot and letsencrypt until certbot and shim packages have been released
|
||||
INSTALLED_VERSION=$("$VENV_BIN/letsencrypt" --version 2>&1 | grep "^certbot\|^letsencrypt" | cut -d " " -f 2)
|
||||
if [ -z "$INSTALLED_VERSION" ]; then
|
||||
echo "Error: couldn't get currently installed version for $VENV_BIN/letsencrypt: " 1>&2
|
||||
"$VENV_BIN/letsencrypt" --version
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
INSTALLED_VERSION="none"
|
||||
fi
|
||||
|
|
@ -801,18 +833,18 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.10.1 \
|
||||
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
|
||||
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
|
||||
certbot==0.10.1 \
|
||||
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
|
||||
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
|
||||
certbot-apache==0.10.1 \
|
||||
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
|
||||
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
|
||||
certbot-nginx==0.10.1 \
|
||||
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
|
||||
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
|
||||
acme==0.11.0 \
|
||||
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
|
||||
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
|
||||
certbot==0.11.0 \
|
||||
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
|
||||
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
|
||||
certbot-apache==0.11.0 \
|
||||
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
|
||||
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
|
||||
certbot-nginx==0.11.0 \
|
||||
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
|
||||
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
@ -1024,7 +1056,7 @@ UNLIKELY_EOF
|
|||
fi
|
||||
|
||||
else
|
||||
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
|
||||
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
|
||||
#
|
||||
# Each phase checks the version of only the thing it is responsible for
|
||||
# upgrading. Phase 1 checks the version of the latest release of
|
||||
|
|
|
|||
|
|
@ -8,26 +8,13 @@ other, special definitions.
|
|||
"""
|
||||
from os.path import abspath, dirname, join
|
||||
import re
|
||||
from sys import argv
|
||||
|
||||
from version import certbot_version, file_contents
|
||||
|
||||
|
||||
DIR = dirname(abspath(__file__))
|
||||
|
||||
|
||||
def certbot_version(build_script_dir):
|
||||
"""Return the version number stamped in certbot/__init__.py."""
|
||||
return re.search('''^__version__ = ['"](.+)['"].*''',
|
||||
file_contents(join(dirname(build_script_dir),
|
||||
'certbot',
|
||||
'__init__.py')),
|
||||
re.M).group(1)
|
||||
|
||||
|
||||
def file_contents(path):
|
||||
with open(path) as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
def build(version=None, requirements=None):
|
||||
"""Return the built contents of the letsencrypt-auto script.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
-----BEGIN PGP SIGNATURE-----
|
||||
Version: GnuPG v1
|
||||
|
||||
iQEcBAABAgAGBQJYeWiuAAoJEE0XyZXNl3Xy4DsIALpCrYVDK9g1nQtdNzBJuFq7
|
||||
WlMEk3ofMrh+3sTEit1jr9+zJ2hV3POa3RtCRfRZsP0hsiNx7XbuR8t6yM6d4j3S
|
||||
pAO0L5brrNoJKvKPx5v8AVGKm1EIDH/lWx/hH+HiE7OE3z/w0ppwoIy0/xIqMXt9
|
||||
O7Q70qV5Rvzh0K3KwSS9pb82ybV/mQ35uugravSOTMo2hZ82Ifh2ZEkJIsS4si2j
|
||||
Oc26ruLi6ru4vwHJU2DkHZzl0oncyQZFolMZJmB47vNOCDtC303cR/poeQS9LSmF
|
||||
vq3Lr0FAunCBoCjuyIGMk2SfmIhtJMS1v5dxOpZppVexedBYkoqU0FoUp/10/rM=
|
||||
=7Xh1
|
||||
iQEcBAABAgAGBQJYkh+9AAoJEE0XyZXNl3XyDFcH/RvYdrkHaLNfc7HX6cRgZvkM
|
||||
9XgP5j+Fsb44NP/U7FwVNHfgqe7OSSSHzNuXPt4/wZTRtXgQZjKU4fWlg8OTgNe8
|
||||
MvTx7FL78GXOhMPC0DX3ZkYYOllApiAFHQuwOXroGb099PTr7msnatLunLk1yCUN
|
||||
wx/i+z0PHkJp+VDDb71sNOIwDtSzRx8w8/dtnnlODkDQWbbijkMUfslmEZnd5bRH
|
||||
7vd2miuDkSR2dMdYLi+Zx0tkSib06kR8ahakPEIcDuZ5CI3fRESkmRUjOxFZOt/z
|
||||
KqMVLMeEq0P81anlK3QCslwibhC88BlbafEma/FNPBZdzHz4UcQALWDGtYDn2tg=
|
||||
=+aXk
|
||||
-----END PGP SIGNATURE-----
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ if [ -z "$VENV_PATH" ]; then
|
|||
VENV_PATH="$XDG_DATA_HOME/$VENV_NAME"
|
||||
fi
|
||||
VENV_BIN="$VENV_PATH/bin"
|
||||
LE_AUTO_VERSION="0.11.0.dev0"
|
||||
LE_AUTO_VERSION="0.12.0.dev0"
|
||||
BASENAME=$(basename $0)
|
||||
USAGE="Usage: $BASENAME [OPTIONS]
|
||||
A self-updating wrapper script for the Certbot ACME client. When run, updates
|
||||
|
|
@ -105,7 +105,7 @@ fi
|
|||
# certbot itself needs root access for almost all modes of operation
|
||||
# The "normal" case is that sudo is used for the steps that need root, but
|
||||
# this script *can* be run as root (not recommended), or fall back to using
|
||||
# `su`. Auto-detection can be overrided by explicitly setting the
|
||||
# `su`. Auto-detection can be overridden by explicitly setting the
|
||||
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
|
||||
|
||||
# Because the parameters in `su -c` has to be a string,
|
||||
|
|
@ -833,18 +833,18 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.10.1 \
|
||||
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
|
||||
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
|
||||
certbot==0.10.1 \
|
||||
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
|
||||
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
|
||||
certbot-apache==0.10.1 \
|
||||
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
|
||||
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
|
||||
certbot-nginx==0.10.1 \
|
||||
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
|
||||
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
|
||||
acme==0.11.0 \
|
||||
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
|
||||
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
|
||||
certbot==0.11.0 \
|
||||
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
|
||||
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
|
||||
certbot-apache==0.11.0 \
|
||||
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
|
||||
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
|
||||
certbot-nginx==0.11.0 \
|
||||
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
|
||||
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
|
||||
|
||||
UNLIKELY_EOF
|
||||
# -------------------------------------------------------------------------
|
||||
|
|
@ -1056,7 +1056,7 @@ UNLIKELY_EOF
|
|||
fi
|
||||
|
||||
else
|
||||
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
|
||||
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
|
||||
#
|
||||
# Each phase checks the version of only the thing it is responsible for
|
||||
# upgrading. Phase 1 checks the version of the latest release of
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -105,7 +105,7 @@ fi
|
|||
# certbot itself needs root access for almost all modes of operation
|
||||
# The "normal" case is that sudo is used for the steps that need root, but
|
||||
# this script *can* be run as root (not recommended), or fall back to using
|
||||
# `su`. Auto-detection can be overrided by explicitly setting the
|
||||
# `su`. Auto-detection can be overridden by explicitly setting the
|
||||
# environment variable LE_AUTO_SUDO to 'sudo', 'sudo_su' or '' as used below.
|
||||
|
||||
# Because the parameters in `su -c` has to be a string,
|
||||
|
|
@ -355,7 +355,7 @@ UNLIKELY_EOF
|
|||
fi
|
||||
|
||||
else
|
||||
# Phase 1: Upgrade certbot-auto if neceesary, then self-invoke.
|
||||
# Phase 1: Upgrade certbot-auto if necessary, then self-invoke.
|
||||
#
|
||||
# Each phase checks the version of only the thing it is responsible for
|
||||
# upgrading. Phase 1 checks the version of the latest release of
|
||||
|
|
|
|||
|
|
@ -171,15 +171,15 @@ letsencrypt==0.7.0 \
|
|||
|
||||
# THE LINES BELOW ARE EDITED BY THE RELEASE SCRIPT; ADD ALL DEPENDENCIES ABOVE.
|
||||
|
||||
acme==0.10.1 \
|
||||
--hash=sha256:1dd5124078bc44739065409f3be51765608a90994c83460578c2680c582c1026 \
|
||||
--hash=sha256:f51c2fb0a31646364abeb7fdd8cfc7c8a4e63b0641b14ab3ce1b2e3a8921a211
|
||||
certbot==0.10.1 \
|
||||
--hash=sha256:9a56fc76f726beeed2f5a08d690088377cd430907f8a38c50e2aa9a258ee1253 \
|
||||
--hash=sha256:e0d699adb3f8ca3e077a4db339de29ebb3f790fbc5f3f02e446e227ed40aa743
|
||||
certbot-apache==0.10.1 \
|
||||
--hash=sha256:1252fd7e435ba48484b0bd9b72535a9755b03d8f0440f164b9c1c560d96cadb8 \
|
||||
--hash=sha256:134f46690da55262125defa58aa74472eb4a1555c9ed83edb3c8667df5a561b5
|
||||
certbot-nginx==0.10.1 \
|
||||
--hash=sha256:afd15ed9e4f3076056b63916f272b7287084d871cb8136477d16b08f64d514f0 \
|
||||
--hash=sha256:da1b7ea4831ead3f9eb526ee11bf1bf197da0fea4defeeb7b1ce24c5d3f45b51
|
||||
acme==0.11.0 \
|
||||
--hash=sha256:9c084f9a62241a11231af63266f2f12ad696be590393a4ab4974276a47d63404 \
|
||||
--hash=sha256:1513ae74ee8424c739a953a552890830315669d95d105469d1cff5b7db9ae888
|
||||
certbot==0.11.0 \
|
||||
--hash=sha256:cc94d890a8697b3bdbdc555bcf4ba93955f64324bc256b2ea710fd053fc03b8a \
|
||||
--hash=sha256:0f91fee360f9ce5e0584d0954fa3123832435f77f465915389032a90ac0248b1
|
||||
certbot-apache==0.11.0 \
|
||||
--hash=sha256:9a01883ca7e1159cff2a6e36bf97b83793c899c62944335f6ac2f59f9b3e8b5d \
|
||||
--hash=sha256:2b67871e7ae8bbfa7a2779fcd6444f28847fbb7a347ef4bfdb19fb55a0d75673
|
||||
certbot-nginx==0.11.0 \
|
||||
--hash=sha256:cfae45a42560e39889eebd287437556084c421a9e07a2deb3cbc0aeef92d2dab \
|
||||
--hash=sha256:dbddffe47c8e8b2d1cf47fe393b434003270a45aec896f133492c855c77e6f08
|
||||
|
|
|
|||
28
letsencrypt-auto-source/version.py
Executable file
28
letsencrypt-auto-source/version.py
Executable file
|
|
@ -0,0 +1,28 @@
|
|||
#!/usr/bin/env python
|
||||
"""Get the current Certbot version number.
|
||||
|
||||
Provides simple utilities for determining the Certbot version number and
|
||||
building letsencrypt-auto.
|
||||
|
||||
"""
|
||||
from __future__ import print_function
|
||||
from os.path import abspath, dirname, join
|
||||
import re
|
||||
|
||||
|
||||
def certbot_version(build_script_dir):
|
||||
"""Return the version number stamped in certbot/__init__.py."""
|
||||
return re.search('''^__version__ = ['"](.+)['"].*''',
|
||||
file_contents(join(dirname(build_script_dir),
|
||||
'certbot',
|
||||
'__init__.py')),
|
||||
re.M).group(1)
|
||||
|
||||
|
||||
def file_contents(path):
|
||||
with open(path) as file:
|
||||
return file.read()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
print(certbot_version(dirname(abspath(__file__))))
|
||||
4
setup.py
4
setup.py
|
|
@ -31,6 +31,9 @@ changes = read_file(os.path.join(here, 'CHANGES.rst'))
|
|||
version = meta['version']
|
||||
|
||||
# Please update tox.ini when modifying dependency version requirements
|
||||
# This package relies on requests, however, it isn't specified here to avoid
|
||||
# masking the more specific request requirements in acme. See
|
||||
# https://github.com/pypa/pip/issues/988 for more info.
|
||||
install_requires = [
|
||||
'acme=={0}'.format(version),
|
||||
# We technically need ConfigArgParse 0.10.0 for Python 2.6 support, but
|
||||
|
|
@ -67,7 +70,6 @@ dev_extras = [
|
|||
'astroid==1.3.5',
|
||||
'coverage',
|
||||
'nose',
|
||||
'psutil>=2.2.1', # for tests, optional
|
||||
'pylint==1.4.2', # upstream #248
|
||||
'tox',
|
||||
'twine',
|
||||
|
|
|
|||
|
|
@ -47,23 +47,24 @@ common() {
|
|||
|
||||
export HOOK_TEST="/tmp/hook$$"
|
||||
CheckHooks() {
|
||||
COMMON="wtf2.auth\nwtf2.cleanup\nrenew\nrenew"
|
||||
EXPECTED="/tmp/expected$$"
|
||||
if [ $(head -n1 $HOOK_TEST) = "wtf.pre" ]; then
|
||||
echo "wtf.pre" > "$EXPECTED"
|
||||
echo "wtf2.pre" >> "$EXPECTED"
|
||||
echo $COMMON >> "$EXPECTED"
|
||||
echo "renew" >> "$EXPECTED"
|
||||
echo "renew" >> "$EXPECTED"
|
||||
echo "wtf.post" >> "$EXPECTED"
|
||||
echo "wtf2.post" >> "$EXPECTED"
|
||||
else
|
||||
echo "wtf2.pre" > "$EXPECTED"
|
||||
echo "wtf.pre" >> "$EXPECTED"
|
||||
echo $COMMON >> "$EXPECTED"
|
||||
echo "renew" >> "$EXPECTED"
|
||||
echo "renew" >> "$EXPECTED"
|
||||
echo "wtf2.post" >> "$EXPECTED"
|
||||
echo "wtf.post" >> "$EXPECTED"
|
||||
fi
|
||||
|
||||
if cmp --quiet "$EXPECTED" "$HOOK_TEST" ; then
|
||||
if ! cmp --quiet "$EXPECTED" "$HOOK_TEST" ; then
|
||||
echo Hooks did not run as expected\; got
|
||||
cat "$HOOK_TEST"
|
||||
echo Expected
|
||||
|
|
@ -91,12 +92,12 @@ common --domains le2.wtf --preferred-challenges http-01 run \
|
|||
kill $python_server_pid
|
||||
|
||||
common certonly -a manual -d le.wtf --rsa-key-size 4096 \
|
||||
--manual-auth-hook 'echo wtf2.auth >> "$HOOK_TEST" && ./tests/manual-http-auth.sh' \
|
||||
--manual-cleanup-hook 'echo wtf2.cleanup >> "$HOOK_TEST" && ./tests/manual-http-cleanup.sh' \
|
||||
--manual-auth-hook ./tests/manual-http-auth.sh \
|
||||
--manual-cleanup-hook ./tests/manual-http-cleanup.sh \
|
||||
--pre-hook 'echo wtf2.pre >> "$HOOK_TEST"' \
|
||||
--post-hook 'echo wtf2.post >> "$HOOK_TEST"'
|
||||
|
||||
common certonly -a manual -d dns.le.wtf --preferred-challenges dns-01 \
|
||||
common certonly -a manual -d dns.le.wtf --preferred-challenges dns,tls-sni \
|
||||
--manual-auth-hook ./tests/manual-dns-auth.sh
|
||||
|
||||
export CSR_PATH="${root}/csr.der" KEY_PATH="${root}/key.pem" \
|
||||
|
|
@ -113,31 +114,32 @@ common --domains le3.wtf install \
|
|||
--key-path "${root}/csr/key.pem"
|
||||
|
||||
CheckCertCount() {
|
||||
CERTCOUNT=`ls "${root}/conf/archive/le.wtf/cert"* | wc -l`
|
||||
if [ "$CERTCOUNT" -ne "$1" ] ; then
|
||||
echo Wrong cert count, not "$1" `ls "${root}/conf/archive/le.wtf/"*`
|
||||
CERTCOUNT=`ls "${root}/conf/archive/$1/cert"* | wc -l`
|
||||
if [ "$CERTCOUNT" -ne "$2" ] ; then
|
||||
echo Wrong cert count, not "$2" `ls "${root}/conf/archive/$1/"*`
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
CheckCertCount 1
|
||||
CheckCertCount "le.wtf" 1
|
||||
# This won't renew (because it's not time yet)
|
||||
common_no_force_renew renew
|
||||
CheckCertCount 1
|
||||
CheckCertCount "le.wtf" 1
|
||||
|
||||
# --renew-by-default is used, so renewal should occur
|
||||
[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST"
|
||||
common renew
|
||||
CheckCertCount 2
|
||||
CheckHooks
|
||||
# renew using HTTP manual auth hooks
|
||||
common renew --cert-name le.wtf --authenticator manual
|
||||
CheckCertCount "le.wtf" 2
|
||||
|
||||
# renew using DNS manual auth hooks
|
||||
common renew --cert-name dns.le.wtf --authenticator manual
|
||||
CheckCertCount "dns.le.wtf" 2
|
||||
|
||||
# This will renew because the expiry is less than 10 years from now
|
||||
sed -i "4arenew_before_expiry = 4 years" "$root/conf/renewal/le.wtf.conf"
|
||||
common_no_force_renew renew --rsa-key-size 2048
|
||||
CheckCertCount 3
|
||||
CheckCertCount "le.wtf" 3
|
||||
|
||||
# The 4096 bit setting should persist to the first renewal, but be overriden in the second
|
||||
# The 4096 bit setting should persist to the first renewal, but be overridden in the second
|
||||
|
||||
size1=`wc -c ${root}/conf/archive/le.wtf/privkey1.pem | cut -d" " -f1`
|
||||
size2=`wc -c ${root}/conf/archive/le.wtf/privkey2.pem | cut -d" " -f1`
|
||||
|
|
@ -149,6 +151,12 @@ if [ "$size1" -lt 3000 ] || [ "$size2" -lt 3000 ] || [ "$size3" -gt 1800 ] ; the
|
|||
exit 1
|
||||
fi
|
||||
|
||||
# --renew-by-default is used, so renewal should occur
|
||||
[ -f "$HOOK_TEST" ] && rm -f "$HOOK_TEST"
|
||||
common renew
|
||||
CheckCertCount "le.wtf" 4
|
||||
CheckHooks
|
||||
|
||||
# ECDSA
|
||||
openssl ecparam -genkey -name secp384r1 -out "${root}/privkey-p384.pem"
|
||||
SAN="DNS:ecdsa.le.wtf" openssl req -new -sha256 \
|
||||
|
|
|
|||
|
|
@ -29,7 +29,8 @@ unset PIP_INDEX_URL
|
|||
export PIP_EXTRA_INDEX_URL="$SAVE"
|
||||
|
||||
git checkout -f "$BRANCH"
|
||||
if ! ./letsencrypt-auto -v --debug --version | grep 0.9.0 ; then
|
||||
EXPECTED_VERSION=$(grep -m1 LE_AUTO_VERSION letsencrypt-auto | cut -d\" -f2)
|
||||
if ! ./letsencrypt-auto -v --debug --version --no-self-upgrade | grep $EXPECTED_VERSION ; then
|
||||
echo upgrade appeared to fail
|
||||
exit 1
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ letsencrypt-auto certonly --no-self-upgrade -v --standalone --debug \
|
|||
# 1. be in the right directory
|
||||
cd tests/letstest/testdata/
|
||||
|
||||
# 2. refer to the config with the same level of relativitity that it itself
|
||||
# 2. refer to the config with the same level of relativity that it itself
|
||||
# contains :/
|
||||
OUT=`letsencrypt-auto certificates --config-dir sample-config -v`
|
||||
OUT=`letsencrypt-auto certificates --config-dir sample-config -v --no-self-upgrade`
|
||||
TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l`
|
||||
REVOKED=`echo "$OUT" | grep REVOKED | wc -l`
|
||||
|
||||
|
|
|
|||
|
|
@ -1,39 +0,0 @@
|
|||
#!/bin/bash -x
|
||||
|
||||
# $PUBLIC_IP $PRIVATE_IP $PUBLIC_HOSTNAME $BOULDER_URL are dynamically set at execution
|
||||
|
||||
# with curl, instance metadata available from EC2 metadata service:
|
||||
#public_host=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-hostname)
|
||||
#public_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/public-ipv4)
|
||||
#private_ip=$(curl -s http://169.254.169.254/2014-11-05/meta-data/local-ipv4)
|
||||
|
||||
cd letsencrypt
|
||||
export PATH="$PWD/letsencrypt-auto-source:$PATH"
|
||||
letsencrypt-auto-source/letsencrypt-auto --os-packages-only --debug --version
|
||||
tools/venv.sh
|
||||
sudo venv/bin/certbot certonly --no-self-upgrade -v --standalone --debug \
|
||||
--text --agree-dev-preview --agree-tos \
|
||||
--renew-by-default --redirect \
|
||||
--register-unsafely-without-email \
|
||||
--domain $PUBLIC_HOSTNAME --server $BOULDER_URL
|
||||
|
||||
# we have to jump through some hoops to cope with relative paths in renewal
|
||||
# conf files ...
|
||||
# 1. be in the right directory
|
||||
cd tests/letstest/testdata/
|
||||
|
||||
# 2. refer to the config with the same level of relativitity that it itself
|
||||
# contains :/
|
||||
OUT=`sudo ../../../venv/bin/certbot certificates -v --config-dir sample-config`
|
||||
TEST_CERTS=`echo "$OUT" | grep TEST_CERT | wc -l`
|
||||
REVOKED=`echo "$OUT" | grep REVOKED | wc -l`
|
||||
|
||||
if [ "$TEST_CERTS" != 2 ] ; then
|
||||
echo "Did not find two test certs as expected ($TEST_CERTS)"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ "$REVOKED" != 1 ] ; then
|
||||
echo "Did not find one revoked cert as expected ($REVOKED)"
|
||||
exit 1
|
||||
fi
|
||||
36
tests/letstest/scripts/test_sdists.sh
Executable file
36
tests/letstest/scripts/test_sdists.sh
Executable file
|
|
@ -0,0 +1,36 @@
|
|||
#!/bin/sh -xe
|
||||
|
||||
cd letsencrypt
|
||||
./certbot-auto --os-packages-only -n --debug
|
||||
|
||||
PLUGINS="certbot-apache certbot-nginx"
|
||||
PYTHON=$(command -v python2.7 || command -v python27 || command -v python2 || command -v python)
|
||||
TEMP_DIR=$(mktemp -d)
|
||||
VERSION=$(letsencrypt-auto-source/version.py)
|
||||
|
||||
# setup venv
|
||||
virtualenv --no-site-packages -p $PYTHON --setuptools venv
|
||||
. ./venv/bin/activate
|
||||
pip install -U pip
|
||||
pip install -U setuptools
|
||||
|
||||
# build sdists
|
||||
for pkg_dir in acme . $PLUGINS; do
|
||||
cd $pkg_dir
|
||||
python setup.py clean
|
||||
rm -rf build dist
|
||||
python setup.py sdist
|
||||
mv dist/* $TEMP_DIR
|
||||
cd -
|
||||
done
|
||||
|
||||
# test sdists
|
||||
cd $TEMP_DIR
|
||||
for pkg in acme certbot $PLUGINS; do
|
||||
tar -xvf "$pkg-$VERSION.tar.gz"
|
||||
cd "$pkg-$VERSION"
|
||||
python setup.py build
|
||||
python setup.py test
|
||||
python setup.py install
|
||||
cd -
|
||||
done
|
||||
|
|
@ -46,6 +46,7 @@ cd ${SCRIPT_PATH}/../
|
|||
cp letsencrypt-auto-source/letsencrypt-auto ${temp_dir}/original-lea
|
||||
python letsencrypt-auto-source/build.py
|
||||
cp letsencrypt-auto-source/letsencrypt-auto ${temp_dir}/build-lea
|
||||
cp ${temp_dir}/original-lea letsencrypt-auto-source/letsencrypt-auto
|
||||
|
||||
cd $temp_dir
|
||||
|
||||
|
|
@ -60,8 +61,8 @@ else
|
|||
build.py."
|
||||
fi
|
||||
|
||||
rm -rf $temp_dir
|
||||
|
||||
if $FLAG ; then
|
||||
exit 1
|
||||
fi
|
||||
|
||||
rm -rf temp_dir
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
|
||||
VENV_NAME=${VENV_NAME:-venv}
|
||||
|
||||
# .egg-info directories tend to cause bizzaire problems (e.g. `pip -e
|
||||
# .egg-info directories tend to cause bizarre problems (e.g. `pip -e
|
||||
# .` might unexpectedly install letshelp-certbot only, in case
|
||||
# `python letshelp-certbot/setup.py build` has been called
|
||||
# earlier)
|
||||
|
|
@ -12,13 +12,13 @@ rm -rf *.egg-info
|
|||
# `/home/jakub/dev/letsencrypt/letsencrypt/venv/bin/python2` and
|
||||
# `venv/bin/python2` are the same file
|
||||
mv $VENV_NAME "$VENV_NAME.$(date +%s).bak" || true
|
||||
virtualenv --no-site-packages $VENV_NAME $VENV_ARGS
|
||||
virtualenv --no-site-packages --setuptools $VENV_NAME $VENV_ARGS
|
||||
. ./$VENV_NAME/bin/activate
|
||||
|
||||
# Separately install setuptools and pip to make sure following
|
||||
# invocations use latest
|
||||
pip install -U setuptools
|
||||
pip install -U pip
|
||||
pip install -U setuptools
|
||||
pip install "$@"
|
||||
|
||||
set +x
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@ pip install -U virtualenv
|
|||
root_without_le="$version.$$"
|
||||
root="./releases/le.$root_without_le"
|
||||
|
||||
echo "Cloning into fresh copy at $root" # clean repo = no artificats
|
||||
echo "Cloning into fresh copy at $root" # clean repo = no artifacts
|
||||
git clone . $root
|
||||
git rev-parse HEAD
|
||||
cd $root
|
||||
|
|
|
|||
18
tox.ini
18
tox.ini
|
|
@ -13,7 +13,7 @@ envlist = modification,py{26,33,34,35,36},cover,lint
|
|||
# packages installed separately to ensure that downstream deps problems
|
||||
# are detected, c.f. #1002
|
||||
commands =
|
||||
pip install -e acme[dns,dev]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme --processes=-1
|
||||
pip install -e .[dev]
|
||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||
|
|
@ -37,35 +37,33 @@ deps =
|
|||
py{26,27}-oldest: cffi<=1.7
|
||||
py{26,27}-oldest: cryptography==0.8
|
||||
py{26,27}-oldest: configargparse==0.10.0
|
||||
py{26,27}-oldest: dnspython>=1.12
|
||||
py{26,27}-oldest: psutil==2.1.0
|
||||
py{26,27}-oldest: PyOpenSSL==0.13
|
||||
py{26,27}-oldest: requests<=2.11.1
|
||||
|
||||
[testenv:py33]
|
||||
commands =
|
||||
pip install -e acme[dns,dev]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme --processes=-1
|
||||
pip install -e .[dev]
|
||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||
|
||||
[testenv:py34]
|
||||
commands =
|
||||
pip install -e acme[dns,dev]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme --processes=-1
|
||||
pip install -e .[dev]
|
||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||
|
||||
[testenv:py35]
|
||||
commands =
|
||||
pip install -e acme[dns,dev]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme --processes=-1
|
||||
pip install -e .[dev]
|
||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||
|
||||
[testenv:py36]
|
||||
commands =
|
||||
pip install -e acme[dns,dev]
|
||||
pip install -e acme[dev]
|
||||
nosetests -v acme --processes=-1
|
||||
pip install -e .[dev]
|
||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||
|
|
@ -73,12 +71,12 @@ commands =
|
|||
[testenv:py27_install]
|
||||
basepython = python2.7
|
||||
commands =
|
||||
pip install -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
|
||||
pip install -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
|
||||
|
||||
[testenv:cover]
|
||||
basepython = python2.7
|
||||
commands =
|
||||
pip install -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
|
||||
pip install -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e letshelp-certbot
|
||||
./tox.cover.sh
|
||||
|
||||
[testenv:lint]
|
||||
|
|
@ -88,7 +86,7 @@ basepython = python2.7
|
|||
# duplicate code checking; if one of the commands fails, others will
|
||||
# continue, but tox return code will reflect previous error
|
||||
commands =
|
||||
pip install -q -e acme[dns,dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
|
||||
pip install -q -e acme[dev] -e .[dev] -e certbot-apache -e certbot-nginx -e certbot-compatibility-test -e letshelp-certbot
|
||||
pylint --reports=n --rcfile=.pylintrc acme/acme certbot certbot-apache/certbot_apache certbot-nginx/certbot_nginx certbot-compatibility-test/certbot_compatibility_test letshelp-certbot/letshelp_certbot
|
||||
|
||||
[testenv:apacheconftest]
|
||||
|
|
|
|||
Loading…
Reference in a new issue