Improve `helper_command' config default
This option needs to capture the current oslo_config in some way, so it can be reconstructed in the new privileged process (when using the 'sudo/rootwrap' method). The previous version had a "$project" placeholder default that didn't work in real usage (as expected). This new version generates a default value at run-time based on the values of cfg.CONF.{config_file,config_dir}. If the deployer has provided an explicit value for `helper_command' then the new cfg.CONF logic is also ignored. Note secure deployments will capture this command line in sudoers or rootwrap filters, and it is up to sudo/rootwrap to verify that whatever we generate here is secure and reasonable. Note also that this and the surrounding code is ignored when using the 'fork' method. Change-Id: I0d31bf24cac6c26f10b5d1eebaa8f475402f73d2
This commit is contained in:
parent
4962e83a31
commit
525a028012
|
@ -257,22 +257,6 @@ class RootwrapClientChannel(_ClientChannel):
|
|||
Uses sudo/rootwrap to gain privileges.
|
||||
"""
|
||||
|
||||
# We need to be able to reconstruct the context object in the new
|
||||
# python process we'll get after rootwrap/sudo. This means we
|
||||
# need to construct the context object and store it somewhere
|
||||
# globally accessible, and then use that python name to find it
|
||||
# again in the new python interpreter. Yes, it's all a bit
|
||||
# clumsy, and none of it is required when using the fork-based
|
||||
# alternative above.
|
||||
# These asserts here are just attempts to catch errors earlier.
|
||||
# TODO(gus): Consider replacing with setuptools entry_points.
|
||||
assert context.pypath is not None, (
|
||||
'RootwrapClientChannel requires priv_context '
|
||||
'pypath to be specified')
|
||||
assert importutils.import_class(context.pypath) is context, (
|
||||
'RootwrapClientChannel requires priv_context pypath '
|
||||
'for context object')
|
||||
|
||||
listen_sock = socket.socket(socket.AF_UNIX)
|
||||
|
||||
# Note we listen() on the unprivileged side, and connect to it
|
||||
|
@ -290,9 +274,7 @@ class RootwrapClientChannel(_ClientChannel):
|
|||
listen_sock.bind(sockpath)
|
||||
listen_sock.listen(1)
|
||||
|
||||
cmd = shlex.split(context.conf.helper_command) + [
|
||||
'--privsep_context', context.pypath,
|
||||
'--privsep_sock_path', sockpath]
|
||||
cmd = self._helper_command(context, sockpath)
|
||||
LOG.info(_LI('Running privsep helper: %s'), cmd)
|
||||
proc = subprocess.Popen(cmd, shell=False, stderr=_fd_logger())
|
||||
if proc.wait() != 0:
|
||||
|
@ -317,6 +299,50 @@ class RootwrapClientChannel(_ClientChannel):
|
|||
|
||||
super(RootwrapClientChannel, self).__init__(sock)
|
||||
|
||||
@staticmethod
|
||||
def _helper_command(context, sockpath):
|
||||
# We need to be able to reconstruct the context object in the new
|
||||
# python process we'll get after rootwrap/sudo. This means we
|
||||
# need to construct the context object and store it somewhere
|
||||
# globally accessible, and then use that python name to find it
|
||||
# again in the new python interpreter. Yes, it's all a bit
|
||||
# clumsy, and none of it is required when using the fork-based
|
||||
# alternative above.
|
||||
# These asserts here are just attempts to catch errors earlier.
|
||||
# TODO(gus): Consider replacing with setuptools entry_points.
|
||||
assert context.pypath is not None, (
|
||||
'RootwrapClientChannel requires priv_context '
|
||||
'pypath to be specified')
|
||||
assert importutils.import_class(context.pypath) is context, (
|
||||
'RootwrapClientChannel requires priv_context pypath '
|
||||
'for context object')
|
||||
|
||||
# Note order is important here. Deployments will (hopefully)
|
||||
# have the exact arguments in sudoers/rootwrap configs and
|
||||
# reordering args will break configs!
|
||||
|
||||
if context.conf.helper_command:
|
||||
cmd = shlex.split(context.conf.helper_command)
|
||||
else:
|
||||
cmd = ['sudo', 'privsep-helper']
|
||||
|
||||
try:
|
||||
for cfg_file in cfg.CONF.config_file:
|
||||
cmd.extend(['--config-file', cfg_file])
|
||||
except cfg.NoSuchOptError:
|
||||
pass
|
||||
|
||||
try:
|
||||
cmd.extend(['--config-dir', cfg.CONF.config_dir])
|
||||
except cfg.NoSuchOptError:
|
||||
pass
|
||||
|
||||
cmd.extend(
|
||||
['--privsep_context', context.pypath,
|
||||
'--privsep_sock_path', sockpath])
|
||||
|
||||
return cmd
|
||||
|
||||
|
||||
class Daemon(object):
|
||||
"""NB: This doesn't fork() - do that yourself before calling run()"""
|
||||
|
|
|
@ -46,11 +46,13 @@ OPTS = [
|
|||
help=_('List of Linux capabilities retained by the privsep '
|
||||
'daemon.')),
|
||||
cfg.StrOpt('helper_command',
|
||||
default=('sudo privsep-helper'
|
||||
# TODO(gus): how do I find a good config path?
|
||||
' --config-file=/etc/$project/$project.conf'),
|
||||
help=_('Command to invoke via sudo/rootwrap to start '
|
||||
'the privsep daemon.')),
|
||||
help=_('Command to invoke to start the privsep daemon if '
|
||||
'not using the "fork" method. '
|
||||
'If not specified, a default is generated using '
|
||||
'"sudo privsep-helper" and arguments designed to '
|
||||
'recreate the current configuration. '
|
||||
'This command must accept suitable --privsep_context '
|
||||
'and --privsep_sock_path arguments.')),
|
||||
]
|
||||
|
||||
_ENTRYPOINT_ATTR = 'privsep_entrypoint'
|
||||
|
|
|
@ -104,3 +104,29 @@ class TestWithContext(testctx.TestContextTestCase):
|
|||
self.assertRaisesRegexp(
|
||||
NameError, 'undecorated not exported',
|
||||
testctx.context._wrap, undecorated)
|
||||
|
||||
def test_helper_command(self):
|
||||
self.privsep_conf.privsep.helper_command = 'foo --bar'
|
||||
cmd = daemon.RootwrapClientChannel._helper_command(
|
||||
testctx.context, '/tmp/sockpath')
|
||||
expected = [
|
||||
'foo', '--bar',
|
||||
'--privsep_context', testctx.context.pypath,
|
||||
'--privsep_sock_path', '/tmp/sockpath',
|
||||
]
|
||||
self.assertEqual(expected, cmd)
|
||||
|
||||
def test_helper_command_default(self):
|
||||
self.privsep_conf.config_file = ['/bar.conf', '/baz.conf']
|
||||
self.privsep_conf.config_dir = '/foo.d'
|
||||
cmd = daemon.RootwrapClientChannel._helper_command(
|
||||
testctx.context, '/tmp/sockpath')
|
||||
expected = [
|
||||
'sudo', 'privsep-helper',
|
||||
'--config-file', '/bar.conf',
|
||||
'--config-file', '/baz.conf',
|
||||
'--config-dir', '/foo.d',
|
||||
'--privsep_context', testctx.context.pypath,
|
||||
'--privsep_sock_path', '/tmp/sockpath',
|
||||
]
|
||||
self.assertEqual(expected, cmd)
|
||||
|
|
Loading…
Reference in New Issue