mirror of
https://github.com/certbot/certbot.git
synced 2026-08-01 16:19:13 +02:00
standalone 2.0: lint, docs, cleanup.
This commit is contained in:
@@ -27,8 +27,14 @@ _DEFAULT_DVSNI_SSL_METHOD = OpenSSL.SSL.SSLv23_METHOD
|
|||||||
|
|
||||||
|
|
||||||
class SSLSocket(object): # pylint: disable=too-few-public-methods
|
class SSLSocket(object): # pylint: disable=too-few-public-methods
|
||||||
"""SSL wrapper for sockets."""
|
"""SSL wrapper for sockets.
|
||||||
|
|
||||||
|
:ivar socket sock: Original wrapped socket.
|
||||||
|
:ivar dict certs: Mapping from domain names (`bytes`) to
|
||||||
|
`OpenSSL.crypto.X509`.
|
||||||
|
:ivar method: See `OpenSSL.SSL.Context` for allowed values.
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, sock, certs, method=_DEFAULT_DVSNI_SSL_METHOD):
|
def __init__(self, sock, certs, method=_DEFAULT_DVSNI_SSL_METHOD):
|
||||||
self.sock = sock
|
self.sock = sock
|
||||||
self.certs = certs
|
self.certs = certs
|
||||||
|
|||||||
@@ -6,8 +6,6 @@ import random
|
|||||||
import socket
|
import socket
|
||||||
import threading
|
import threading
|
||||||
|
|
||||||
from six.moves import BaseHTTPServer # pylint: disable=import-error
|
|
||||||
|
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
import zope.interface
|
import zope.interface
|
||||||
|
|
||||||
@@ -26,17 +24,39 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class ServerManager(object):
|
class ServerManager(object):
|
||||||
"""Standalone servers manager."""
|
"""Standalone servers manager.
|
||||||
|
|
||||||
|
Manager for `ACMEServer` and `ACMETLSServer` instances.
|
||||||
|
|
||||||
|
`certs` and `simple_http_resources` correspond to
|
||||||
|
`acme.crypto_util.SSLSocket.certs` and
|
||||||
|
`acme.crypto_util.SSLSocket.simple_http_resources` respectively. All
|
||||||
|
created servers share the same certificates and resources, so if
|
||||||
|
you're running both TLS and non-TLS instances, SimpleHTTP handlers
|
||||||
|
will serve the same URLs!
|
||||||
|
|
||||||
|
"""
|
||||||
def __init__(self, certs, simple_http_resources):
|
def __init__(self, certs, simple_http_resources):
|
||||||
self.servers = {}
|
self._servers = {}
|
||||||
self.certs = certs
|
self.certs = certs
|
||||||
self.simple_http_resources = simple_http_resources
|
self.simple_http_resources = simple_http_resources
|
||||||
|
|
||||||
def run(self, port, tls):
|
def run(self, port, tls):
|
||||||
"""Run ACME server on specified ``port``."""
|
"""Run ACME server on specified ``port``.
|
||||||
if port in self.servers:
|
|
||||||
return self.servers[port]
|
This method is idempotent, i.e. all calls with the same pair of
|
||||||
|
``(port, tls)`` will reuse the same server.
|
||||||
|
|
||||||
|
:param int port: Port to run the server on.
|
||||||
|
:param bool tls: TLS or non-TLS?
|
||||||
|
|
||||||
|
:returns: Server instance (`ACMEServerMixin`) and the
|
||||||
|
corresponding (already started) thread (`threading.Thread`).
|
||||||
|
:rtype: tuple
|
||||||
|
|
||||||
|
"""
|
||||||
|
if port in self._servers:
|
||||||
|
return self._servers[port]
|
||||||
|
|
||||||
logger.debug("Starting new server at %s (tls=%s)", port, tls)
|
logger.debug("Starting new server at %s (tls=%s)", port, tls)
|
||||||
handler = acme_standalone.ACMERequestHandler.partial_init(
|
handler = acme_standalone.ACMERequestHandler.partial_init(
|
||||||
@@ -54,23 +74,38 @@ class ServerManager(object):
|
|||||||
raise errors.StandaloneBindError(error, port)
|
raise errors.StandaloneBindError(error, port)
|
||||||
|
|
||||||
# if port == 0, then random free port on OS is taken
|
# if port == 0, then random free port on OS is taken
|
||||||
real_port = server.socket.getsockname()
|
# pylint: disable=no-member
|
||||||
|
host, real_port = server.socket.getsockname()
|
||||||
|
|
||||||
thread = threading.Thread(target=server.serve_forever2)
|
thread = threading.Thread(target=server.serve_forever2)
|
||||||
|
logger.debug("Starting server at %s:%d", host, real_port)
|
||||||
thread.start()
|
thread.start()
|
||||||
self.servers[real_port] = (server, thread)
|
|
||||||
return self.servers[real_port]
|
self._servers[real_port] = (server, thread)
|
||||||
|
return self._servers[real_port]
|
||||||
|
|
||||||
def stop(self, port):
|
def stop(self, port):
|
||||||
"""Stop ACME server running on the specified ``port``."""
|
"""Stop ACME server running on the specified ``port``.
|
||||||
server, thread = self.servers[port]
|
|
||||||
|
:param int port:
|
||||||
|
|
||||||
|
"""
|
||||||
|
server, thread = self._servers[port]
|
||||||
server.shutdown2()
|
server.shutdown2()
|
||||||
thread.join()
|
thread.join()
|
||||||
del self.servers[port]
|
del self._servers[port]
|
||||||
|
|
||||||
def items(self):
|
def running(self):
|
||||||
"""Return a list of all port, server tuples."""
|
"""Return all running instances.
|
||||||
return self.servers.items()
|
|
||||||
|
Once the server is stopped using `stop`, it will not be
|
||||||
|
returned.
|
||||||
|
|
||||||
|
:returns: ``(port, (server, thread))``
|
||||||
|
:rtype: tuple
|
||||||
|
|
||||||
|
"""
|
||||||
|
return self._servers.items()
|
||||||
|
|
||||||
|
|
||||||
class Authenticator(common.Plugin):
|
class Authenticator(common.Plugin):
|
||||||
@@ -98,7 +133,6 @@ class Authenticator(common.Plugin):
|
|||||||
self.simple_http_cert = acme_crypto_util.gen_ss_cert(
|
self.simple_http_cert = acme_crypto_util.gen_ss_cert(
|
||||||
self.key, domains=["temp server"])
|
self.key, domains=["temp server"])
|
||||||
|
|
||||||
self.responses = {}
|
|
||||||
self.served = collections.defaultdict(set)
|
self.served = collections.defaultdict(set)
|
||||||
|
|
||||||
# Stuff below is shared across threads (i.e. servers read
|
# Stuff below is shared across threads (i.e. servers read
|
||||||
@@ -117,7 +151,8 @@ class Authenticator(common.Plugin):
|
|||||||
if any(util.already_listening(port) for port in
|
if any(util.already_listening(port) for port in
|
||||||
(self.config.dvsni_port, self.config.simple_http_port)):
|
(self.config.dvsni_port, self.config.simple_http_port)):
|
||||||
raise errors.MisconfigurationError(
|
raise errors.MisconfigurationError(
|
||||||
"One of the (possibly) required ports is already taken.")
|
"At least one of the (possibly) required ports is "
|
||||||
|
"already taken.")
|
||||||
|
|
||||||
# TODO: add --chall-pref flag
|
# TODO: add --chall-pref flag
|
||||||
def get_chall_pref(self, domain):
|
def get_chall_pref(self, domain):
|
||||||
@@ -167,7 +202,6 @@ class Authenticator(common.Plugin):
|
|||||||
response, cert, _ = achall.gen_cert_and_response(self.key)
|
response, cert, _ = achall.gen_cert_and_response(self.key)
|
||||||
domain = response.z_domain
|
domain = response.z_domain
|
||||||
self.certs[domain] = (self.key, cert)
|
self.certs[domain] = (self.key, cert)
|
||||||
self.responses[achall] = response
|
|
||||||
self.served[server].add(achall)
|
self.served[server].add(achall)
|
||||||
responses.append(response)
|
responses.append(response)
|
||||||
|
|
||||||
@@ -179,6 +213,6 @@ class Authenticator(common.Plugin):
|
|||||||
for achall in achalls:
|
for achall in achalls:
|
||||||
if achall in server_achalls:
|
if achall in server_achalls:
|
||||||
server_achalls.remove(achall)
|
server_achalls.remove(achall)
|
||||||
for port, (server, _) in self.servers.items():
|
for port, (server, _) in self.servers.running():
|
||||||
if not self.served[server]:
|
if not self.served[server]:
|
||||||
self.servers.stop(port)
|
self.servers.stop(port)
|
||||||
|
|||||||
@@ -26,19 +26,19 @@ class ServerManagerTest(unittest.TestCase):
|
|||||||
self.mgr.simple_http_resources is self.simple_http_resources)
|
self.mgr.simple_http_resources is self.simple_http_resources)
|
||||||
|
|
||||||
def test_run_stop_non_tls(self):
|
def test_run_stop_non_tls(self):
|
||||||
server, thread = self.mgr.run(port=0, tls=False)
|
server, _ = self.mgr.run(port=0, tls=False)
|
||||||
self.mgr.stop(port=server.socket.getsockname())
|
self.mgr.stop(port=server.socket.getsockname()[1])
|
||||||
|
|
||||||
def test_run_stop_tls(self):
|
def test_run_stop_tls(self):
|
||||||
server, thread = self.mgr.run(port=0, tls=True)
|
server, _ = self.mgr.run(port=0, tls=True)
|
||||||
self.mgr.stop(port=server.socket.getsockname())
|
self.mgr.stop(port=server.socket.getsockname()[1])
|
||||||
|
|
||||||
def test_run_idempotent(self):
|
def test_run_idempotent(self):
|
||||||
server, thread = self.mgr.run(port=0, tls=False)
|
server, thread = self.mgr.run(port=0, tls=False)
|
||||||
port = server.socket.getsockname()
|
port = server.socket.getsockname()[1]
|
||||||
server2, thread2 = self.mgr.run(port=port, tls=False)
|
server2, thread2 = self.mgr.run(port=port, tls=False)
|
||||||
self.assertTrue(server is server2)
|
self.assertTrue(server is server2)
|
||||||
self.assertTrue(thread2 is thread2)
|
self.assertTrue(thread is thread2)
|
||||||
self.mgr.stop(port)
|
self.mgr.stop(port)
|
||||||
|
|
||||||
def test_run_bind_error(self):
|
def test_run_bind_error(self):
|
||||||
@@ -48,12 +48,12 @@ class ServerManagerTest(unittest.TestCase):
|
|||||||
self.assertRaises(
|
self.assertRaises(
|
||||||
errors.StandaloneBindError, self.mgr.run, port, tls=False)
|
errors.StandaloneBindError, self.mgr.run, port, tls=False)
|
||||||
|
|
||||||
def test_items(self):
|
def test_running(self):
|
||||||
server, thread = self.mgr.run(port=0, tls=True)
|
server, thread = self.mgr.run(port=0, tls=True)
|
||||||
port = server.socket.getsockname()
|
port = server.socket.getsockname()[1]
|
||||||
self.assertEqual(port, self.mgr.items()[0][0])
|
self.assertEqual(port, self.mgr.running()[0][0])
|
||||||
self.assertTrue(self.mgr.items()[0][1][0] is server)
|
self.assertTrue(self.mgr.running()[0][1][0] is server)
|
||||||
self.assertTrue(self.mgr.items()[0][1][1] is thread)
|
self.assertTrue(self.mgr.running()[0][1][1] is thread)
|
||||||
self.mgr.stop(port=port)
|
self.mgr.stop(port=port)
|
||||||
|
|
||||||
|
|
||||||
@@ -79,7 +79,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
set([challenges.SimpleHTTP, challenges.DVSNI]))
|
set([challenges.SimpleHTTP, challenges.DVSNI]))
|
||||||
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility")
|
@mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility")
|
||||||
def test_perform(self, mock_get_utility):
|
def test_perform(self, unused_mock_get_utility):
|
||||||
achalls = [1, 2, 3]
|
achalls = [1, 2, 3]
|
||||||
self.auth.perform2 = mock.Mock(return_value=mock.sentinel.responses)
|
self.auth.perform2 = mock.Mock(return_value=mock.sentinel.responses)
|
||||||
self.assertEqual(mock.sentinel.responses, self.auth.perform(achalls))
|
self.assertEqual(mock.sentinel.responses, self.auth.perform(achalls))
|
||||||
@@ -87,7 +87,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
|
|
||||||
@mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility")
|
@mock.patch("letsencrypt.plugins.standalone.zope.component.getUtility")
|
||||||
def _test_perform_bind_errors(self, errno, achalls, mock_get_utility):
|
def _test_perform_bind_errors(self, errno, achalls, mock_get_utility):
|
||||||
def _perform2(achalls):
|
def _perform2(unused_achalls):
|
||||||
raise errors.StandaloneBindError(mock.Mock(errno=errno), 1234)
|
raise errors.StandaloneBindError(mock.Mock(errno=errno), 1234)
|
||||||
|
|
||||||
self.auth.perform2 = mock.MagicMock(side_effect=_perform2)
|
self.auth.perform2 = mock.MagicMock(side_effect=_perform2)
|
||||||
@@ -98,9 +98,11 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
self.assertTrue("1234" in notification.call_args[0][0])
|
self.assertTrue("1234" in notification.call_args[0][0])
|
||||||
|
|
||||||
def test_perform_eacces(self):
|
def test_perform_eacces(self):
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
self._test_perform_bind_errors(socket.errno.EACCES, [])
|
self._test_perform_bind_errors(socket.errno.EACCES, [])
|
||||||
|
|
||||||
def test_perform_eaddrinuse(self):
|
def test_perform_eaddrinuse(self):
|
||||||
|
# pylint: disable=no-value-for-parameter
|
||||||
self._test_perform_bind_errors(socket.errno.EADDRINUSE, [])
|
self._test_perform_bind_errors(socket.errno.EADDRINUSE, [])
|
||||||
|
|
||||||
def test_perfom_unknown_bind_error(self):
|
def test_perfom_unknown_bind_error(self):
|
||||||
@@ -109,9 +111,8 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
socket.errno.ENOTCONN, [])
|
socket.errno.ENOTCONN, [])
|
||||||
|
|
||||||
def test_cleanup(self):
|
def test_cleanup(self):
|
||||||
servers = {1: "server1", 2: "server2"}
|
|
||||||
self.auth.servers = mock.Mock()
|
self.auth.servers = mock.Mock()
|
||||||
self.auth.servers.items.return_value = [
|
self.auth.servers.running.return_value = [
|
||||||
(1, ("server1", "thread1")),
|
(1, ("server1", "thread1")),
|
||||||
(2, ("server2", "thread2")),
|
(2, ("server2", "thread2")),
|
||||||
]
|
]
|
||||||
@@ -123,7 +124,7 @@ class AuthenticatorTest(unittest.TestCase):
|
|||||||
"server1": set(), "server2": set(["chall2", "chall3"])})
|
"server1": set(), "server2": set(["chall2", "chall3"])})
|
||||||
self.auth.servers.stop.assert_called_once_with(1)
|
self.auth.servers.stop.assert_called_once_with(1)
|
||||||
|
|
||||||
self.auth.servers.items.return_value = [
|
self.auth.servers.running.return_value = [
|
||||||
(2, ("server2", "thread2")),
|
(2, ("server2", "thread2")),
|
||||||
]
|
]
|
||||||
self.auth.cleanup(["chall2"])
|
self.auth.cleanup(["chall2"])
|
||||||
|
|||||||
Reference in New Issue
Block a user