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.")
|
"chosen port is displayed in the service's log file.")
|
||||||
eventlet_backdoor_opts = [
|
eventlet_backdoor_opts = [
|
||||||
cfg.StrOpt('backdoor_port',
|
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 = [
|
periodic_opts = [
|
||||||
|
|||||||
@@ -133,6 +133,27 @@ def _listen(host, start_port, end_port, listen_func):
|
|||||||
try_port += 1
|
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):
|
def _initialize_if_enabled(conf):
|
||||||
conf.register_opts(_options.eventlet_backdoor_opts)
|
conf.register_opts(_options.eventlet_backdoor_opts)
|
||||||
backdoor_locals = {
|
backdoor_locals = {
|
||||||
@@ -143,10 +164,18 @@ def _initialize_if_enabled(conf):
|
|||||||
'pnt': _print_nativethreads,
|
'pnt': _print_nativethreads,
|
||||||
}
|
}
|
||||||
|
|
||||||
if conf.backdoor_port is None:
|
if conf.backdoor_port is None and conf.backdoor_socket is None:
|
||||||
return 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
|
# NOTE(johannes): The standard sys.displayhook will print the value of
|
||||||
# the last expression and set it to __builtin__._, which overwrites
|
# the last expression and set it to __builtin__._, which overwrites
|
||||||
@@ -158,27 +187,23 @@ def _initialize_if_enabled(conf):
|
|||||||
pprint.pprint(val)
|
pprint.pprint(val)
|
||||||
sys.displayhook = displayhook
|
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(
|
LOG.info(
|
||||||
_LI('Eventlet backdoor listening on %(port)s for process %(pid)d'),
|
_LI('Eventlet backdoor listening on %(where_running)s for'
|
||||||
{'port': port, 'pid': os.getpid()}
|
' process %(pid)d'),
|
||||||
|
{'where_running': where_running, 'pid': os.getpid()}
|
||||||
)
|
)
|
||||||
thread = eventlet.spawn(eventlet.backdoor.backdoor_server, sock,
|
thread = eventlet.spawn(eventlet.backdoor.backdoor_server, sock,
|
||||||
locals=backdoor_locals)
|
locals=backdoor_locals)
|
||||||
return (port, thread)
|
return (where_running, thread)
|
||||||
|
|
||||||
|
|
||||||
def initialize_if_enabled(conf):
|
def initialize_if_enabled(conf):
|
||||||
port_thread = _initialize_if_enabled(conf)
|
where_running_thread = _initialize_if_enabled(conf)
|
||||||
if not port_thread:
|
if not where_running_thread:
|
||||||
return None
|
return None
|
||||||
else:
|
else:
|
||||||
port, _thread = port_thread
|
where_running, _thread = where_running_thread
|
||||||
return port
|
return where_running
|
||||||
|
|
||||||
|
|
||||||
def _main():
|
def _main():
|
||||||
@@ -193,11 +218,11 @@ def _main():
|
|||||||
conf.register_cli_opts(_options.eventlet_backdoor_opts)
|
conf.register_cli_opts(_options.eventlet_backdoor_opts)
|
||||||
conf(sys.argv[1:])
|
conf(sys.argv[1:])
|
||||||
|
|
||||||
port_thread = _initialize_if_enabled(conf)
|
where_running_thread = _initialize_if_enabled(conf)
|
||||||
if not port_thread:
|
if not where_running_thread:
|
||||||
raise RuntimeError("Did not create backdoor at requested port")
|
raise RuntimeError("Did not create backdoor at requested location")
|
||||||
else:
|
else:
|
||||||
_port, thread = port_thread
|
_where_running, thread = where_running_thread
|
||||||
thread.wait()
|
thread.wait()
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
Unit Tests for eventlet backdoor
|
Unit Tests for eventlet backdoor
|
||||||
"""
|
"""
|
||||||
import errno
|
import errno
|
||||||
|
import os
|
||||||
import socket
|
import socket
|
||||||
|
|
||||||
import eventlet
|
import eventlet
|
||||||
@@ -27,6 +28,63 @@ from oslo_service import eventlet_backdoor
|
|||||||
from oslo_service.tests import base
|
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):
|
class BackdoorPortTest(base.ServiceBaseTestCase):
|
||||||
|
|
||||||
@mock.patch.object(eventlet, 'spawn')
|
@mock.patch.object(eventlet, 'spawn')
|
||||||
|
|||||||
Reference in New Issue
Block a user