Allow the backdoor to serve from a local unix domain socket
Local files can be made accessible to certain users vs random ports which can be accessible to anyone on a machine so allow using unix files as a way to start the eventlet backdoor (so that user and group permissions common on unix are not lost). To use this new type of files `socat` is needed (or other way to interact with telnet over a unix domain socket). For example (with the path at /tmp/my_special_socket): socat - UNIX-CONNECT:/tmp/my_special_socket Depends-On: Ia2385879e09991102f8f305ec41dbb651a4374de Change-Id: I7f25913168ebe5854f360db3d6310b72a56b2b4d
This commit is contained in:
committed by
Joshua Harlow
parent
de959dc78f
commit
db1fc249e6
@@ -24,7 +24,14 @@ help_for_backdoor_port = (
|
||||
"chosen port is displayed in the service's log file.")
|
||||
eventlet_backdoor_opts = [
|
||||
cfg.StrOpt('backdoor_port',
|
||||
help="Enable eventlet backdoor. %s" % help_for_backdoor_port)
|
||||
help="Enable eventlet backdoor. %s" % help_for_backdoor_port),
|
||||
cfg.StrOpt('backdoor_socket',
|
||||
help="Enable eventlet backdoor, using the provided path"
|
||||
" as a unix socket that can receive connections. This"
|
||||
" option is mutually exclusive with 'backdoor_port' in"
|
||||
" that only one should be be provided. If both are"
|
||||
" provided then the existence of this option overrides"
|
||||
" the usage of that option.")
|
||||
]
|
||||
|
||||
periodic_opts = [
|
||||
|
||||
@@ -133,6 +133,27 @@ def _listen(host, start_port, end_port, listen_func):
|
||||
try_port += 1
|
||||
|
||||
|
||||
def _try_open_unix_domain_socket(socket_path):
|
||||
try:
|
||||
return eventlet.listen(socket_path, socket.AF_UNIX)
|
||||
except socket.error as e:
|
||||
if e.errno != errno.EADDRINUSE:
|
||||
# NOTE(harlowja): Some other non-address in use error
|
||||
# occurred, since we aren't handling those, re-raise
|
||||
# and give up...
|
||||
raise
|
||||
else:
|
||||
# Attempt to remove the file before opening it again.
|
||||
try:
|
||||
os.unlink(socket_path)
|
||||
except OSError as e:
|
||||
if e.errno != errno.ENOENT:
|
||||
# NOTE(harlowja): File existed, but we couldn't
|
||||
# delete it, give up...
|
||||
raise
|
||||
return eventlet.listen(socket_path, socket.AF_UNIX)
|
||||
|
||||
|
||||
def _initialize_if_enabled(conf):
|
||||
conf.register_opts(_options.eventlet_backdoor_opts)
|
||||
backdoor_locals = {
|
||||
@@ -143,10 +164,18 @@ def _initialize_if_enabled(conf):
|
||||
'pnt': _print_nativethreads,
|
||||
}
|
||||
|
||||
if conf.backdoor_port is None:
|
||||
if conf.backdoor_port is None and conf.backdoor_socket is None:
|
||||
return None
|
||||
|
||||
start_port, end_port = _parse_port_range(str(conf.backdoor_port))
|
||||
if conf.backdoor_socket is None:
|
||||
start_port, end_port = _parse_port_range(str(conf.backdoor_port))
|
||||
sock = _listen('localhost', start_port, end_port, eventlet.listen)
|
||||
# In the case of backdoor port being zero, a port number is assigned by
|
||||
# listen(). In any case, pull the port number out here.
|
||||
where_running = sock.getsockname()[1]
|
||||
else:
|
||||
sock = _try_open_unix_domain_socket(conf.backdoor_socket)
|
||||
where_running = conf.backdoor_socket
|
||||
|
||||
# NOTE(johannes): The standard sys.displayhook will print the value of
|
||||
# the last expression and set it to __builtin__._, which overwrites
|
||||
@@ -158,27 +187,23 @@ def _initialize_if_enabled(conf):
|
||||
pprint.pprint(val)
|
||||
sys.displayhook = displayhook
|
||||
|
||||
sock = _listen('localhost', start_port, end_port, eventlet.listen)
|
||||
|
||||
# In the case of backdoor port being zero, a port number is assigned by
|
||||
# listen(). In any case, pull the port number out here.
|
||||
port = sock.getsockname()[1]
|
||||
LOG.info(
|
||||
_LI('Eventlet backdoor listening on %(port)s for process %(pid)d'),
|
||||
{'port': port, 'pid': os.getpid()}
|
||||
_LI('Eventlet backdoor listening on %(where_running)s for'
|
||||
' process %(pid)d'),
|
||||
{'where_running': where_running, 'pid': os.getpid()}
|
||||
)
|
||||
thread = eventlet.spawn(eventlet.backdoor.backdoor_server, sock,
|
||||
locals=backdoor_locals)
|
||||
return (port, thread)
|
||||
return (where_running, thread)
|
||||
|
||||
|
||||
def initialize_if_enabled(conf):
|
||||
port_thread = _initialize_if_enabled(conf)
|
||||
if not port_thread:
|
||||
where_running_thread = _initialize_if_enabled(conf)
|
||||
if not where_running_thread:
|
||||
return None
|
||||
else:
|
||||
port, _thread = port_thread
|
||||
return port
|
||||
where_running, _thread = where_running_thread
|
||||
return where_running
|
||||
|
||||
|
||||
def _main():
|
||||
@@ -193,11 +218,11 @@ def _main():
|
||||
conf.register_cli_opts(_options.eventlet_backdoor_opts)
|
||||
conf(sys.argv[1:])
|
||||
|
||||
port_thread = _initialize_if_enabled(conf)
|
||||
if not port_thread:
|
||||
raise RuntimeError("Did not create backdoor at requested port")
|
||||
where_running_thread = _initialize_if_enabled(conf)
|
||||
if not where_running_thread:
|
||||
raise RuntimeError("Did not create backdoor at requested location")
|
||||
else:
|
||||
_port, thread = port_thread
|
||||
_where_running, thread = where_running_thread
|
||||
thread.wait()
|
||||
|
||||
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
Unit Tests for eventlet backdoor
|
||||
"""
|
||||
import errno
|
||||
import os
|
||||
import socket
|
||||
|
||||
import eventlet
|
||||
@@ -27,6 +28,63 @@ from oslo_service import eventlet_backdoor
|
||||
from oslo_service.tests import base
|
||||
|
||||
|
||||
class BackdoorSocketPathTest(base.ServiceBaseTestCase):
|
||||
|
||||
@mock.patch.object(eventlet, 'spawn')
|
||||
@mock.patch.object(eventlet, 'listen')
|
||||
def test_backdoor_path(self, listen_mock, spawn_mock):
|
||||
self.config(backdoor_socket="/tmp/my_special_socket")
|
||||
listen_mock.side_effect = mock.MagicMock()
|
||||
path = eventlet_backdoor.initialize_if_enabled(self.conf)
|
||||
self.assertEqual(path, "/tmp/my_special_socket")
|
||||
|
||||
@mock.patch.object(os, 'unlink')
|
||||
@mock.patch.object(eventlet, 'spawn')
|
||||
@mock.patch.object(eventlet, 'listen')
|
||||
def test_backdoor_path_already_exists(self, listen_mock,
|
||||
spawn_mock, unlink_mock):
|
||||
self.config(backdoor_socket="/tmp/my_special_socket")
|
||||
sock = mock.MagicMock()
|
||||
listen_mock.side_effect = [socket.error(errno.EADDRINUSE, ''), sock]
|
||||
path = eventlet_backdoor.initialize_if_enabled(self.conf)
|
||||
self.assertEqual(path, "/tmp/my_special_socket")
|
||||
unlink_mock.assert_called_with("/tmp/my_special_socket")
|
||||
|
||||
@mock.patch.object(os, 'unlink')
|
||||
@mock.patch.object(eventlet, 'spawn')
|
||||
@mock.patch.object(eventlet, 'listen')
|
||||
def test_backdoor_path_already_exists_and_gone(self, listen_mock,
|
||||
spawn_mock, unlink_mock):
|
||||
self.config(backdoor_socket="/tmp/my_special_socket")
|
||||
sock = mock.MagicMock()
|
||||
listen_mock.side_effect = [socket.error(errno.EADDRINUSE, ''), sock]
|
||||
unlink_mock.side_effect = OSError(errno.ENOENT, '')
|
||||
path = eventlet_backdoor.initialize_if_enabled(self.conf)
|
||||
self.assertEqual(path, "/tmp/my_special_socket")
|
||||
unlink_mock.assert_called_with("/tmp/my_special_socket")
|
||||
|
||||
@mock.patch.object(os, 'unlink')
|
||||
@mock.patch.object(eventlet, 'spawn')
|
||||
@mock.patch.object(eventlet, 'listen')
|
||||
def test_backdoor_path_already_exists_and_not_gone(self, listen_mock,
|
||||
spawn_mock,
|
||||
unlink_mock):
|
||||
self.config(backdoor_socket="/tmp/my_special_socket")
|
||||
listen_mock.side_effect = socket.error(errno.EADDRINUSE, '')
|
||||
unlink_mock.side_effect = OSError(errno.EPERM, '')
|
||||
self.assertRaises(OSError, eventlet_backdoor.initialize_if_enabled,
|
||||
self.conf)
|
||||
|
||||
@mock.patch.object(eventlet, 'spawn')
|
||||
@mock.patch.object(eventlet, 'listen')
|
||||
def test_backdoor_path_no_perms(self, listen_mock, spawn_mock):
|
||||
self.config(backdoor_socket="/tmp/my_special_socket")
|
||||
listen_mock.side_effect = socket.error(errno.EPERM, '')
|
||||
self.assertRaises(socket.error,
|
||||
eventlet_backdoor.initialize_if_enabled,
|
||||
self.conf)
|
||||
|
||||
|
||||
class BackdoorPortTest(base.ServiceBaseTestCase):
|
||||
|
||||
@mock.patch.object(eventlet, 'spawn')
|
||||
|
||||
Reference in New Issue
Block a user