mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 02:54:34 +02:00
Remove optional dependencies (#4088)
* Stop using already_listening in standalone * remove already_listening * remove psutil entirely * fix #595 * Add basic perform test * make pep8 happy * Add test_perform_eacces * add _setup_perform_error * Add test_perform_unexpected_socket_error * add test_perform_eaddrinuse_no_retry * add test_perform_eaddrinuse_retry * cleanup tests * stop using dnspython * don't install dns extras in tox * remove dns extras from setup.py * Add simple_verify back to DNS response * remove dnspython from oldest tests
This commit is contained in:
+9
-22
@@ -9,7 +9,6 @@ from cryptography.hazmat.primitives import hashes
|
|||||||
import OpenSSL
|
import OpenSSL
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
from acme import dns_resolver
|
|
||||||
from acme import errors
|
from acme import errors
|
||||||
from acme import crypto_util
|
from acme import crypto_util
|
||||||
from acme import fields
|
from acme import fields
|
||||||
@@ -214,36 +213,24 @@ class DNS01Response(KeyAuthorizationChallengeResponse):
|
|||||||
def simple_verify(self, chall, domain, account_public_key):
|
def simple_verify(self, chall, domain, account_public_key):
|
||||||
"""Simple verify.
|
"""Simple verify.
|
||||||
|
|
||||||
|
This method no longer checks DNS records and is a simple wrapper
|
||||||
|
around `KeyAuthorizationChallengeResponse.verify`.
|
||||||
|
|
||||||
:param challenges.DNS01 chall: Corresponding challenge.
|
:param challenges.DNS01 chall: Corresponding challenge.
|
||||||
:param unicode domain: Domain name being verified.
|
:param unicode domain: Domain name being verified.
|
||||||
:param JWK account_public_key: Public key for the key pair
|
:param JWK account_public_key: Public key for the key pair
|
||||||
being authorized.
|
being authorized.
|
||||||
|
|
||||||
:returns: ``True`` iff validation with the TXT records resolved from a
|
:return: ``True`` iff verification of the key authorization was
|
||||||
DNS server is successful.
|
successful.
|
||||||
:rtype: bool
|
: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")
|
logger.debug("Verification of key authorization in response failed")
|
||||||
return False
|
return verified
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
@Challenge.register # pylint: disable=too-many-ancestors
|
@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 errors
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import test_util
|
from acme import test_util
|
||||||
from acme.dns_resolver import DNS_REQUIREMENT
|
|
||||||
|
|
||||||
CERT = test_util.load_comparable_cert('cert.pem')
|
CERT = test_util.load_comparable_cert('cert.pem')
|
||||||
KEY = jose.JWKRSA(key=test_util.load_rsa_private_key('rsa512_key.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
|
from acme.challenges import DNS01
|
||||||
self.chall = DNS01(token=(b'x' * 16))
|
self.chall = DNS01(token=(b'x' * 16))
|
||||||
self.response = self.chall.response(KEY)
|
self.response = self.chall.response(KEY)
|
||||||
self.records_for_name_path = "acme.dns_resolver.txt_records_for_name"
|
|
||||||
|
|
||||||
def test_to_partial_json(self):
|
def test_to_partial_json(self):
|
||||||
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
self.assertEqual(self.jmsg, self.msg.to_partial_json())
|
||||||
@@ -105,45 +103,16 @@ class DNS01ResponseTest(unittest.TestCase):
|
|||||||
from acme.challenges import DNS01Response
|
from acme.challenges import DNS01Response
|
||||||
hash(DNS01Response.from_json(self.jmsg))
|
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'))
|
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_success(self):
|
||||||
def test_simple_verify_without_dns(self):
|
public_key = KEY.public_key()
|
||||||
self.assertRaises(
|
verified = self.response.simple_verify(self.chall, "local", public_key)
|
||||||
errors.DependencyError, self.response.simple_verify,
|
self.assertTrue(verified)
|
||||||
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()))
|
|
||||||
|
|
||||||
|
|
||||||
class DNS01Test(unittest.TestCase):
|
class DNS01Test(unittest.TestCase):
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -11,9 +11,7 @@ from cryptography.hazmat.backends import default_backend
|
|||||||
from cryptography.hazmat.primitives import serialization
|
from cryptography.hazmat.primitives import serialization
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
|
|
||||||
from acme import errors
|
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import util
|
|
||||||
|
|
||||||
|
|
||||||
def vector_path(*names):
|
def vector_path(*names):
|
||||||
@@ -78,20 +76,6 @@ def load_pyopenssl_private_key(*names):
|
|||||||
return OpenSSL.crypto.load_privatekey(loader, load_vector(*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
|
def skip_unless(condition, reason): # pragma: no cover
|
||||||
"""Skip tests unless a condition holds.
|
"""Skip tests unless a condition holds.
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,7 @@
|
|||||||
"""ACME utilities."""
|
"""ACME utilities."""
|
||||||
import pkg_resources
|
|
||||||
import six
|
import six
|
||||||
|
|
||||||
from acme import errors
|
|
||||||
|
|
||||||
|
|
||||||
def map_keys(dikt, func):
|
def map_keys(dikt, func):
|
||||||
"""Map dictionary keys."""
|
"""Map dictionary keys."""
|
||||||
return dict((func(key), value) for key, value in six.iteritems(dikt))
|
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."""
|
"""Tests for acme.util."""
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from acme import errors
|
|
||||||
|
|
||||||
|
|
||||||
class MapKeysTest(unittest.TestCase):
|
class MapKeysTest(unittest.TestCase):
|
||||||
"""Tests for acme.util.map_keys."""
|
"""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))
|
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__':
|
if __name__ == '__main__':
|
||||||
unittest.main() # pragma: no cover
|
unittest.main() # pragma: no cover
|
||||||
|
|||||||
@@ -33,11 +33,6 @@ if sys.version_info < (2, 7):
|
|||||||
else:
|
else:
|
||||||
install_requires.append('mock')
|
install_requires.append('mock')
|
||||||
|
|
||||||
# dnspython 1.12 is required to support both Python 2 and Python 3.
|
|
||||||
dns_extras = [
|
|
||||||
'dnspython>=1.12',
|
|
||||||
]
|
|
||||||
|
|
||||||
dev_extras = [
|
dev_extras = [
|
||||||
'nose',
|
'nose',
|
||||||
'tox',
|
'tox',
|
||||||
@@ -77,7 +72,6 @@ setup(
|
|||||||
include_package_data=True,
|
include_package_data=True,
|
||||||
install_requires=install_requires,
|
install_requires=install_requires,
|
||||||
extras_require={
|
extras_require={
|
||||||
'dns': dns_extras,
|
|
||||||
'dev': dev_extras,
|
'dev': dev_extras,
|
||||||
'docs': docs_extras,
|
'docs': docs_extras,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -18,7 +18,6 @@ from certbot import errors
|
|||||||
from certbot import interfaces
|
from certbot import interfaces
|
||||||
|
|
||||||
from certbot.plugins import common
|
from certbot.plugins import common
|
||||||
from certbot.plugins import util
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -208,74 +207,38 @@ class Authenticator(common.Plugin):
|
|||||||
# pylint: disable=unused-argument,missing-docstring
|
# pylint: disable=unused-argument,missing-docstring
|
||||||
return self.supported_challenges
|
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
|
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:
|
def _try_perform_single(self, achall):
|
||||||
return self.perform2(achalls)
|
while True:
|
||||||
except errors.StandaloneBindError as error:
|
try:
|
||||||
display = zope.component.getUtility(interfaces.IDisplay)
|
return self._perform_single(achall)
|
||||||
|
except errors.StandaloneBindError as error:
|
||||||
|
_handle_perform_error(error)
|
||||||
|
|
||||||
if error.socket_error.errno == socket.errno.EACCES:
|
def _perform_single(self, achall):
|
||||||
display.notification(
|
if isinstance(achall.chall, challenges.HTTP01):
|
||||||
"Could not bind TCP port {0} because you don't have "
|
server, response = self._perform_http_01(achall)
|
||||||
"the appropriate permissions (for example, you "
|
else: # tls-sni-01
|
||||||
"aren't running this program as "
|
server, response = self._perform_tls_sni_01(achall)
|
||||||
"root).".format(error.port), force_interactive=True)
|
self.served[server].add(achall)
|
||||||
elif error.socket_error.errno == socket.errno.EADDRINUSE:
|
return response
|
||||||
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 perform2(self, achalls):
|
def _perform_http_01(self, achall):
|
||||||
"""Perform achallenges without IDisplay interaction."""
|
server = self.servers.run(self.config.http01_port, challenges.HTTP01)
|
||||||
responses = []
|
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:
|
def _perform_tls_sni_01(self, achall):
|
||||||
if isinstance(achall.chall, challenges.HTTP01):
|
port = self.config.tls_sni_01_port
|
||||||
server = self.servers.run(
|
server = self.servers.run(port, challenges.TLSSNI01)
|
||||||
self.config.http01_port, challenges.HTTP01)
|
response, (cert, _) = achall.response_and_validation(cert_key=self.key)
|
||||||
response, validation = achall.response_and_validation()
|
self.certs[response.z_domain] = (self.key, cert)
|
||||||
self.http_01_resources.add(
|
return server, response
|
||||||
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 cleanup(self, achalls): # pylint: disable=missing-docstring
|
def cleanup(self, achalls): # pylint: disable=missing-docstring
|
||||||
# reduce self.served and close servers if none challenges are served
|
# 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()):
|
for port, server in six.iteritems(self.servers.running()):
|
||||||
if not self.served[server]:
|
if not self.served[server]:
|
||||||
self.servers.stop(port)
|
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 challenges
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import standalone as acme_standalone
|
|
||||||
|
|
||||||
from certbot import achallenges
|
from certbot import achallenges
|
||||||
from certbot import errors
|
from certbot import errors
|
||||||
from certbot import interfaces
|
|
||||||
|
|
||||||
from certbot.tests import acme_util
|
from certbot.tests import acme_util
|
||||||
from certbot.tests import util as test_util
|
from certbot.tests import util as test_util
|
||||||
@@ -114,6 +112,7 @@ def get_open_port():
|
|||||||
open_socket.close()
|
open_socket.close()
|
||||||
return port
|
return port
|
||||||
|
|
||||||
|
|
||||||
class AuthenticatorTest(unittest.TestCase):
|
class AuthenticatorTest(unittest.TestCase):
|
||||||
"""Tests for certbot.plugins.standalone.Authenticator."""
|
"""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(),
|
tls_sni_01_port=get_open_port(), http01_port=get_open_port(),
|
||||||
standalone_supported_challenges="tls-sni-01,http-01")
|
standalone_supported_challenges="tls-sni-01,http-01")
|
||||||
self.auth = Authenticator(self.config, name="standalone")
|
self.auth = Authenticator(self.config, name="standalone")
|
||||||
|
self.auth.servers = mock.MagicMock()
|
||||||
|
|
||||||
def test_supported_challenges(self):
|
def test_supported_challenges(self):
|
||||||
self.assertEqual(self.auth.supported_challenges,
|
self.assertEqual(self.auth.supported_challenges,
|
||||||
@@ -146,6 +146,52 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
self.assertEqual(self.auth.get_chall_pref(domain=None),
|
self.assertEqual(self.auth.get_chall_pref(domain=None),
|
||||||
[challenges.TLSSNI01])
|
[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
|
@classmethod
|
||||||
def _get_achalls(cls):
|
def _get_achalls(cls):
|
||||||
domain = b'localhost'
|
domain = b'localhost'
|
||||||
@@ -157,84 +203,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
|
|
||||||
return [http_01, tls_sni_01]
|
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):
|
def test_cleanup(self):
|
||||||
self.auth.servers = mock.Mock()
|
|
||||||
self.auth.servers.running.return_value = {
|
self.auth.servers.running.return_value = {
|
||||||
1: "server1",
|
1: "server1",
|
||||||
2: "server2",
|
2: "server2",
|
||||||
|
|||||||
@@ -1,34 +1,11 @@
|
|||||||
"""Plugin utilities."""
|
"""Plugin utilities."""
|
||||||
import logging
|
import logging
|
||||||
import os
|
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
|
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__)
|
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):
|
def path_surgery(cmd):
|
||||||
"""Attempt to perform PATH surgery to find 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,
|
logger.warning("Failed to find %s in%s PATH: %s", cmd,
|
||||||
expanded, path)
|
expanded, path)
|
||||||
return False
|
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."""
|
"""Tests for certbot.plugins.util."""
|
||||||
import os
|
import os
|
||||||
import socket
|
|
||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
|
||||||
from certbot.plugins.util import PSUTIL_REQUIREMENT
|
|
||||||
from certbot.tests import util as test_util
|
|
||||||
|
|
||||||
|
|
||||||
class PathSurgeryTest(unittest.TestCase):
|
class PathSurgeryTest(unittest.TestCase):
|
||||||
"""Tests for certbot.plugins.path_surgery."""
|
"""Tests for certbot.plugins.path_surgery."""
|
||||||
@@ -34,140 +30,5 @@ class PathSurgeryTest(unittest.TestCase):
|
|||||||
self.assertTrue("/tmp" in os.environ["PATH"])
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main() # pragma: no cover
|
unittest.main() # pragma: no cover
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ from cryptography.hazmat.primitives import serialization
|
|||||||
import mock
|
import mock
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
|
|
||||||
from acme import errors
|
|
||||||
from acme import jose
|
from acme import jose
|
||||||
from acme import util
|
|
||||||
|
|
||||||
from certbot import constants
|
from certbot import constants
|
||||||
from certbot import interfaces
|
from certbot import interfaces
|
||||||
@@ -86,20 +84,6 @@ def load_pyopenssl_private_key(*names):
|
|||||||
return OpenSSL.crypto.load_privatekey(loader, load_vector(*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
|
def skip_unless(condition, reason): # pragma: no cover
|
||||||
"""Skip tests unless a condition holds.
|
"""Skip tests unless a condition holds.
|
||||||
|
|
||||||
|
|||||||
@@ -67,7 +67,6 @@ dev_extras = [
|
|||||||
'astroid==1.3.5',
|
'astroid==1.3.5',
|
||||||
'coverage',
|
'coverage',
|
||||||
'nose',
|
'nose',
|
||||||
'psutil>=2.2.1', # for tests, optional
|
|
||||||
'pylint==1.4.2', # upstream #248
|
'pylint==1.4.2', # upstream #248
|
||||||
'tox',
|
'tox',
|
||||||
'twine',
|
'twine',
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ envlist = modification,py{26,33,34,35,36},cover,lint
|
|||||||
# packages installed separately to ensure that downstream deps problems
|
# packages installed separately to ensure that downstream deps problems
|
||||||
# are detected, c.f. #1002
|
# are detected, c.f. #1002
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dns,dev]
|
pip install -e acme[dev]
|
||||||
nosetests -v acme --processes=-1
|
nosetests -v acme --processes=-1
|
||||||
pip install -e .[dev]
|
pip install -e .[dev]
|
||||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||||
@@ -37,35 +37,33 @@ deps =
|
|||||||
py{26,27}-oldest: cffi<=1.7
|
py{26,27}-oldest: cffi<=1.7
|
||||||
py{26,27}-oldest: cryptography==0.8
|
py{26,27}-oldest: cryptography==0.8
|
||||||
py{26,27}-oldest: configargparse==0.10.0
|
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: PyOpenSSL==0.13
|
||||||
py{26,27}-oldest: requests<=2.11.1
|
py{26,27}-oldest: requests<=2.11.1
|
||||||
|
|
||||||
[testenv:py33]
|
[testenv:py33]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dns,dev]
|
pip install -e acme[dev]
|
||||||
nosetests -v acme --processes=-1
|
nosetests -v acme --processes=-1
|
||||||
pip install -e .[dev]
|
pip install -e .[dev]
|
||||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||||
|
|
||||||
[testenv:py34]
|
[testenv:py34]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dns,dev]
|
pip install -e acme[dev]
|
||||||
nosetests -v acme --processes=-1
|
nosetests -v acme --processes=-1
|
||||||
pip install -e .[dev]
|
pip install -e .[dev]
|
||||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||||
|
|
||||||
[testenv:py35]
|
[testenv:py35]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dns,dev]
|
pip install -e acme[dev]
|
||||||
nosetests -v acme --processes=-1
|
nosetests -v acme --processes=-1
|
||||||
pip install -e .[dev]
|
pip install -e .[dev]
|
||||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||||
|
|
||||||
[testenv:py36]
|
[testenv:py36]
|
||||||
commands =
|
commands =
|
||||||
pip install -e acme[dns,dev]
|
pip install -e acme[dev]
|
||||||
nosetests -v acme --processes=-1
|
nosetests -v acme --processes=-1
|
||||||
pip install -e .[dev]
|
pip install -e .[dev]
|
||||||
nosetests -v certbot --processes=-1 --process-timeout=100
|
nosetests -v certbot --processes=-1 --process-timeout=100
|
||||||
@@ -73,12 +71,12 @@ commands =
|
|||||||
[testenv:py27_install]
|
[testenv:py27_install]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
commands =
|
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]
|
[testenv:cover]
|
||||||
basepython = python2.7
|
basepython = python2.7
|
||||||
commands =
|
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
|
./tox.cover.sh
|
||||||
|
|
||||||
[testenv:lint]
|
[testenv:lint]
|
||||||
@@ -88,7 +86,7 @@ basepython = python2.7
|
|||||||
# duplicate code checking; if one of the commands fails, others will
|
# duplicate code checking; if one of the commands fails, others will
|
||||||
# continue, but tox return code will reflect previous error
|
# continue, but tox return code will reflect previous error
|
||||||
commands =
|
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
|
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]
|
[testenv:apacheconftest]
|
||||||
|
|||||||
Reference in New Issue
Block a user