setns: support changing user.

To match existing third party plugin.
pull/232/head
David Wilson 7 years ago
parent 947d35649c
commit 7c5bbc5168

@ -112,6 +112,26 @@ def _connect_lxc(spec):
} }
def _connect_machinectl(spec):
return _connect_setns(dict(spec, mitogen_kind='machinectl'))
def _connect_setns(spec):
print 'ULTRAFLEEN', spec['remote_addr'], spec['remote_user']
return {
'method': 'setns',
'kwargs': {
'container': spec['remote_addr'],
'username': spec['remote_user'],
'python_path': spec['python_path'],
'kind': spec['mitogen_kind'],
'docker_path': spec['mitogen_docker_path'],
'lxc_info_path': spec['mitogen_lxc_info_path'],
'machinectl_path': spec['mitogen_machinectl_path'],
}
}
def _connect_sudo(spec): def _connect_sudo(spec):
return { return {
'method': 'sudo', 'method': 'sudo',
@ -132,6 +152,8 @@ CONNECTION_METHOD = {
'local': _connect_local, 'local': _connect_local,
'lxc': _connect_lxc, 'lxc': _connect_lxc,
'lxd': _connect_lxc, 'lxd': _connect_lxc,
'machinectl': _connect_machinectl,
'setns': _connect_setns,
'ssh': _connect_ssh, 'ssh': _connect_ssh,
'sudo': _connect_sudo, 'sudo': _connect_sudo,
} }
@ -178,6 +200,10 @@ def config_from_play_context(transport, inventory_name, connection):
for term in shlex.split(s or '') for term in shlex.split(s or '')
], ],
'mitogen_via': connection.mitogen_via, 'mitogen_via': connection.mitogen_via,
'mitogen_kind': connection.mitogen_kind,
'mitogen_docker_path': connection.mitogen_docker_path,
'mitogen_lxc_info_path': connection.mitogen_lxc_info_path,
'mitogen_machinectl_path': connection.mitogen_machinectl_path,
} }
@ -202,6 +228,10 @@ def config_from_hostvars(transport, inventory_name, connection,
'private_key_file': (hostvars.get('ansible_ssh_private_key_file') or 'private_key_file': (hostvars.get('ansible_ssh_private_key_file') or
hostvars.get('ansible_private_key_file')), hostvars.get('ansible_private_key_file')),
'mitogen_via': hostvars.get('mitogen_via'), 'mitogen_via': hostvars.get('mitogen_via'),
'mitogen_kind': hostvars.get('mitogen_kind'),
'mitogen_docker_path': hostvars.get('mitogen_docker_path'),
'mitogen_lxc_info_path': hostvars.get('mitogen_lxc_info_path'),
'mitogen_machinectl_path': hostvars.get('mitogen_machinctl_path'),
}) })
@ -232,6 +262,18 @@ class Connection(ansible.plugins.connection.ConnectionBase):
#: Set to 'mitogen_via' by on_action_run(). #: Set to 'mitogen_via' by on_action_run().
mitogen_via = None mitogen_via = None
#: Set to 'mitogen_kind' by on_action_run().
mitogen_kind = None
#: Set to 'mitogen_docker_path' by on_action_run().
mitogen_docker_path = None
#: Set to 'mitogen_lxc_info_path' by on_action_run().
mitogen_lxc_info_path = None
#: Set to 'mitogen_lxc_info_path' by on_action_run().
mitogen_machinectl_path = None
#: Set to 'inventory_hostname' by on_action_run(). #: Set to 'inventory_hostname' by on_action_run().
inventory_hostname = None inventory_hostname = None
@ -267,6 +309,10 @@ class Connection(ansible.plugins.connection.ConnectionBase):
self.python_path = task_vars.get('ansible_python_interpreter', self.python_path = task_vars.get('ansible_python_interpreter',
'/usr/bin/python') '/usr/bin/python')
self.mitogen_via = task_vars.get('mitogen_via') self.mitogen_via = task_vars.get('mitogen_via')
self.mitogen_kind = task_vars.get('mitogen_kind')
self.mitogen_docker_path = task_vars.get('mitogen_docker_path')
self.mitogen_lxc_info_path = task_vars.get('mitogen_lxc_info_path')
self.mitogen_machinectl_path = task_vars.get('mitogen_machinectl_path')
self.inventory_hostname = task_vars['inventory_hostname'] self.inventory_hostname = task_vars['inventory_hostname']
self.host_vars = task_vars['hostvars'] self.host_vars = task_vars['hostvars']
self.close(new_task=True) self.close(new_task=True)

@ -63,12 +63,11 @@ def wrap_action_loader__get(name, *args, **kwargs):
def wrap_connection_loader__get(name, play_context, new_stdin, **kwargs): def wrap_connection_loader__get(name, play_context, new_stdin, **kwargs):
""" """
While the mitogen strategy is active, rewrite connection_loader.get() calls While the strategy is active, rewrite connection_loader.get() calls for
for the 'ssh' and 'local' transports into corresponding requests for the some transports into requests for a compatible Mitogen transport.
'mitogen' connection type, passing the original transport name into it as
an argument, so that it can emulate the original type.
""" """
if name in ('ssh', 'local', 'docker', 'lxc', 'lxd', 'jail'): if name in ('docker', 'jail', 'local', 'lxc',
'lxd', 'machinectl', 'setns', 'ssh'):
name = 'mitogen_' + name name = 'mitogen_' + name
return connection_loader__get(name, play_context, new_stdin, **kwargs) return connection_loader__get(name, play_context, new_stdin, **kwargs)

@ -159,7 +159,7 @@ def transfer_file(context, in_path, out_path, sync=False, set_owner=False):
prefix='.ansible_mitogen_transfer-', prefix='.ansible_mitogen_transfer-',
dir=os.path.dirname(out_path)) dir=os.path.dirname(out_path))
fp = os.fdopen(fd, 'wb', mitogen.core.CHUNK_SIZE) fp = os.fdopen(fd, 'wb', mitogen.core.CHUNK_SIZE)
LOG.debug('transfer_file(%r) tempory file: %s', out_path, tmp_path) LOG.debug('transfer_file(%r) temporary file: %s', out_path, tmp_path)
try: try:
try: try:

@ -455,6 +455,20 @@ connection delegation is supported.
* ``ansible_user``: Name of user within the container to execute as. * ``ansible_user``: Name of user within the container to execute as.
Machinectl
~~~~~~~~~~
Behaves like `machinectl
<https://github.com/BaxterStockman/ansible-connection-machinectl>`_ except
connection delegation is supported. This is a lightweight wrapper around the
``setns`` method below.
* ``ansible_host``: Name of Docker container (default: inventory hostname).
* ``ansible_user``: Name of user within the container to execute as.
* ``mitogen_machinectl_path``: path to ``machinectl`` command if not available
as ``/bin/machinectl``.
Sudo Sudo
~~~~ ~~~~
@ -478,9 +492,10 @@ supported.
Utility programs must still be installed to discover the PID of the container's Utility programs must still be installed to discover the PID of the container's
root process. root process.
* ``mitogen_container_kind``: one of ``docker``, ``lxc`` or ``machinectl``. * ``mitogen_kind``: one of ``docker``, ``lxc`` or ``machinectl``.
* ``ansible_host``: Name of container as it is known to the corresponding tool * ``ansible_host``: Name of container as it is known to the corresponding tool
(default: inventory hostname). (default: inventory hostname).
* ``ansible_user``: Name of user within the container to execute as.
* ``mitogen_docker_path``: path to Docker if not available on the system path. * ``mitogen_docker_path``: path to Docker if not available on the system path.
* ``mitogen_lxc_info_path``: path to ``lxc-info`` command if not available as * ``mitogen_lxc_info_path``: path to ``lxc-info`` command if not available as
``/usr/bin/lxc-info``. ``/usr/bin/lxc-info``.
@ -537,6 +552,7 @@ rather than the LXC Python bindings, as is usual with the ``lxc`` method.
The ``lxc-attach`` command must be available on the host machine. The ``lxc-attach`` command must be available on the host machine.
* ``ansible_python_interpreter``
* ``ansible_host``: Name of LXC container (default: inventory hostname). * ``ansible_host``: Name of LXC container (default: inventory hostname).

@ -484,9 +484,9 @@ def _proxy_connect(name, method_name, kwargs, econtext):
return { return {
'id': None, 'id': None,
'name': None, 'name': None,
'msg': '%s (error occurred on host %s)' % ( 'msg': 'error occurred on host %s: %s' % (
sys.exc_info()[1],
socket.gethostname(), socket.gethostname(),
sys.exc_info()[1],
), ),
} }

@ -27,9 +27,12 @@
# POSSIBILITY OF SUCH DAMAGE. # POSSIBILITY OF SUCH DAMAGE.
import ctypes import ctypes
import grp
import logging import logging
import os import os
import pwd
import subprocess import subprocess
import sys
import mitogen.core import mitogen.core
import mitogen.parent import mitogen.parent
@ -53,11 +56,16 @@ def setns(kind, fd):
def _run_command(args): def _run_command(args):
proc = subprocess.Popen( argv = mitogen.parent.Argv(args)
args=args, try:
stdout=subprocess.PIPE, proc = subprocess.Popen(
stderr=subprocess.STDOUT args=args,
) stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
except OSError:
e = sys.exc_info()[1]
raise Error('could not execute %s: %s', argv, e)
output, _ = proc.communicate() output, _ = proc.communicate()
if not proc.returncode: if not proc.returncode:
@ -97,6 +105,7 @@ def get_machinectl_pid(path, name):
class Stream(mitogen.parent.Stream): class Stream(mitogen.parent.Stream):
container = None container = None
username = None
kind = None kind = None
docker_path = 'docker' docker_path = 'docker'
lxc_info_path = 'lxc-info' lxc_info_path = 'lxc-info'
@ -108,7 +117,7 @@ class Stream(mitogen.parent.Stream):
'machinectl': ('machinectl_path', get_machinectl_pid), 'machinectl': ('machinectl_path', get_machinectl_pid),
} }
def construct(self, container, kind, docker_path=None, def construct(self, container, kind, username=None, docker_path=None,
lxc_info_path=None, machinectl_path=None, **kwargs): lxc_info_path=None, machinectl_path=None, **kwargs):
super(Stream, self).construct(**kwargs) super(Stream, self).construct(**kwargs)
if kind not in self.GET_LEADER_BY_KIND: if kind not in self.GET_LEADER_BY_KIND:
@ -116,6 +125,8 @@ class Stream(mitogen.parent.Stream):
self.container = container self.container = container
self.kind = kind self.kind = kind
if username:
self.username = username
if docker_path: if docker_path:
self.docker_path = docker_path self.docker_path = docker_path
if lxc_info_path: if lxc_info_path:
@ -140,12 +151,43 @@ class Stream(mitogen.parent.Stream):
except Exception, e: except Exception, e:
raise Error(str(e)) raise Error(str(e))
os.chroot('/proc/%s/root' % (self.leader_pid,)) os.chdir('/proc/%s/root' % (self.leader_pid,))
os.chroot('.')
os.chdir('/') os.chdir('/')
for fp in ns_fps: for fp in ns_fps:
setns(fp.name, fp.fileno()) setns(fp.name, fp.fileno())
fp.close() fp.close()
for sym in 'endpwent', 'endgrent', 'endspent', 'endsgent':
try:
getattr(LIBC, sym)()
except AttributeError:
pass
if self.username:
try:
os.setgroups([grent.gr_gid
for grent in grp.getgrall()
if self.username in grent.gr_mem])
pwent = pwd.getpwnam(self.username)
os.setreuid(pwent.pw_uid, pwent.pw_uid)
# shadow-4.4/libmisc/setupenv.c. Not done: MAIL, PATH
os.environ.update({
'HOME': pwent.pw_dir,
'SHELL': pwent.pw_shell or '/bin/sh',
'LOGNAME': self.username,
'USER': self.username,
})
if ((os.path.exists(pwent.pw_dir) and
os.access(pwent.pw_dir, os.X_OK))):
os.chdir(pwent.pw_dir)
except Exception:
e = sys.exc_info()[1]
raise Error(self.username_msg, self.username, self.container,
type(e).__name__, e)
username_msg = 'while transitioning to user %r in container %r: %s: %s'
def get_boot_command(self): def get_boot_command(self):
# With setns(CLONE_NEWPID), new children of the caller receive a new # With setns(CLONE_NEWPID), new children of the caller receive a new
# PID namespace, however the caller's namespace won't change. That # PID namespace, however the caller's namespace won't change. That

@ -68,7 +68,7 @@ class StreamErrorTest(testlib.RouterMixin, testlib.TestCase):
connect_timeout=3, connect_timeout=3,
) )
) )
self.assertEquals(e.args[0], "EOF on stream; last 300 bytes received: ''") self.assertTrue("EOF on stream; last 300 bytes received: ''" in e.args[0])
def test_direct_enoent(self): def test_direct_enoent(self):
e = self.assertRaises(mitogen.core.StreamError, e = self.assertRaises(mitogen.core.StreamError,
@ -89,8 +89,8 @@ class StreamErrorTest(testlib.RouterMixin, testlib.TestCase):
connect_timeout=3, connect_timeout=3,
) )
) )
prefix = 'Child start failed: [Errno 2] No such file or directory.' s = 'Child start failed: [Errno 2] No such file or directory.'
self.assertTrue(e.args[0].startswith(prefix)) self.assertTrue(s in e.args[0])
class ContextTest(testlib.RouterMixin, unittest2.TestCase): class ContextTest(testlib.RouterMixin, unittest2.TestCase):

Loading…
Cancel
Save