able to get to 'sudo: source not found' after preventing escape of && so python connects

pull/658/head
Steven Robertson 5 years ago
parent 2ff48316f6
commit 24b170311a

@ -325,6 +325,7 @@ class PlayContextSpec(Spec):
PlayContext. It is used for normal connections and delegate_to connections,
and should always be accurate.
"""
def __init__(self, connection, play_context, transport, inventory_name):
self._connection = connection
self._play_context = play_context
@ -366,6 +367,7 @@ class PlayContextSpec(Spec):
# #511, #536: executor/module_common.py::_get_shebang() hard-wires
# "/usr/bin/python" as the default interpreter path if no other
# interpreter is specified.
# raise ValueError(parse_python_path(s))
return parse_python_path(s or '/usr/bin/python')
def private_key_file(self):
@ -379,9 +381,9 @@ class PlayContextSpec(Spec):
def ansible_ssh_timeout(self):
return (
self._connection.get_task_var('ansible_timeout') or
self._connection.get_task_var('ansible_ssh_timeout') or
self.timeout()
self._connection.get_task_var('ansible_timeout')
or self._connection.get_task_var('ansible_ssh_timeout')
or self.timeout()
)
def ssh_args(self):
@ -468,8 +470,8 @@ class PlayContextSpec(Spec):
def ansible_doas_exe(self):
return (
self._connection.get_task_var('ansible_doas_exe') or
os.environ.get('ANSIBLE_DOAS_EXE')
self._connection.get_task_var('ansible_doas_exe')
or os.environ.get('ANSIBLE_DOAS_EXE')
)
@ -490,6 +492,7 @@ class MitogenViaSpec(Spec):
having a configruation problem with connection delegation, the answer to
your problem lies in the method implementations below!
"""
def __init__(self, inventory_name, host_vars, become_method, become_user,
play_context):
"""
@ -520,8 +523,8 @@ class MitogenViaSpec(Spec):
def transport(self):
return (
self._host_vars.get('ansible_connection') or
C.DEFAULT_TRANSPORT
self._host_vars.get('ansible_connection')
or C.DEFAULT_TRANSPORT
)
def inventory_name(self):
@ -530,16 +533,16 @@ class MitogenViaSpec(Spec):
def remote_addr(self):
# play_context.py::MAGIC_VARIABLE_MAPPING
return (
self._host_vars.get('ansible_ssh_host') or
self._host_vars.get('ansible_host') or
self._inventory_name
self._host_vars.get('ansible_ssh_host')
or self._host_vars.get('ansible_host')
or self._inventory_name
)
def remote_user(self):
return (
self._host_vars.get('ansible_ssh_user') or
self._host_vars.get('ansible_user') or
C.DEFAULT_REMOTE_USER
self._host_vars.get('ansible_ssh_user')
or self._host_vars.get('ansible_user')
or C.DEFAULT_REMOTE_USER
)
def become(self):
@ -547,9 +550,9 @@ class MitogenViaSpec(Spec):
def become_method(self):
return (
self._become_method or
self._host_vars.get('ansible_become_method') or
C.DEFAULT_BECOME_METHOD
self._become_method
or self._host_vars.get('ansible_become_method')
or C.DEFAULT_BECOME_METHOD
)
def become_user(self):
@ -557,21 +560,21 @@ class MitogenViaSpec(Spec):
def become_pass(self):
return optional_secret(
self._host_vars.get('ansible_become_password') or
self._host_vars.get('ansible_become_pass')
self._host_vars.get('ansible_become_password')
or self._host_vars.get('ansible_become_pass')
)
def password(self):
return optional_secret(
self._host_vars.get('ansible_ssh_pass') or
self._host_vars.get('ansible_password')
self._host_vars.get('ansible_ssh_pass')
or self._host_vars.get('ansible_password')
)
def port(self):
return (
self._host_vars.get('ansible_ssh_port') or
self._host_vars.get('ansible_port') or
C.DEFAULT_REMOTE_PORT
self._host_vars.get('ansible_ssh_port')
or self._host_vars.get('ansible_port')
or C.DEFAULT_REMOTE_PORT
)
def python_path(self):
@ -579,20 +582,21 @@ class MitogenViaSpec(Spec):
# #511, #536: executor/module_common.py::_get_shebang() hard-wires
# "/usr/bin/python" as the default interpreter path if no other
# interpreter is specified.
# raise ValueError(parse_python_path(s))
return parse_python_path(s or '/usr/bin/python')
def private_key_file(self):
# TODO: must come from PlayContext too.
return (
self._host_vars.get('ansible_ssh_private_key_file') or
self._host_vars.get('ansible_private_key_file') or
C.DEFAULT_PRIVATE_KEY_FILE
self._host_vars.get('ansible_ssh_private_key_file')
or self._host_vars.get('ansible_private_key_file')
or C.DEFAULT_PRIVATE_KEY_FILE
)
def ssh_executable(self):
return (
self._host_vars.get('ansible_ssh_executable') or
C.ANSIBLE_SSH_EXECUTABLE
self._host_vars.get('ansible_ssh_executable')
or C.ANSIBLE_SSH_EXECUTABLE
)
def timeout(self):
@ -601,9 +605,9 @@ class MitogenViaSpec(Spec):
def ansible_ssh_timeout(self):
return (
self._host_vars.get('ansible_timeout') or
self._host_vars.get('ansible_ssh_timeout') or
self.timeout()
self._host_vars.get('ansible_timeout')
or self._host_vars.get('ansible_ssh_timeout')
or self.timeout()
)
def ssh_args(self):
@ -611,19 +615,19 @@ class MitogenViaSpec(Spec):
mitogen.core.to_text(term)
for s in (
(
self._host_vars.get('ansible_ssh_args') or
getattr(C, 'ANSIBLE_SSH_ARGS', None) or
os.environ.get('ANSIBLE_SSH_ARGS')
self._host_vars.get('ansible_ssh_args')
or getattr(C, 'ANSIBLE_SSH_ARGS', None)
or os.environ.get('ANSIBLE_SSH_ARGS')
# TODO: ini entry. older versions.
),
(
self._host_vars.get('ansible_ssh_common_args') or
os.environ.get('ANSIBLE_SSH_COMMON_ARGS')
self._host_vars.get('ansible_ssh_common_args')
or os.environ.get('ANSIBLE_SSH_COMMON_ARGS')
# TODO: ini entry.
),
(
self._host_vars.get('ansible_ssh_extra_args') or
os.environ.get('ANSIBLE_SSH_EXTRA_ARGS')
self._host_vars.get('ansible_ssh_extra_args')
or os.environ.get('ANSIBLE_SSH_EXTRA_ARGS')
# TODO: ini entry.
),
)
@ -633,8 +637,8 @@ class MitogenViaSpec(Spec):
def become_exe(self):
return (
self._host_vars.get('ansible_become_exe') or
C.DEFAULT_BECOME_EXE
self._host_vars.get('ansible_become_exe')
or C.DEFAULT_BECOME_EXE
)
def sudo_args(self):
@ -694,6 +698,6 @@ class MitogenViaSpec(Spec):
def ansible_doas_exe(self):
return (
self._host_vars.get('ansible_doas_exe') or
os.environ.get('ANSIBLE_DOAS_EXE')
self._host_vars.get('ansible_doas_exe')
or os.environ.get('ANSIBLE_DOAS_EXE')
)

@ -1455,7 +1455,7 @@ class Connection(object):
os.write(1, 'MITO001\n'.encode())
os.close(2)
def get_python_cmd(self, encoded):
def get_python_argv(self):
"""
Return the command necessary to invoke Python,
by returning a 1-element list containing :attr:`python_path` + codecs
@ -1464,17 +1464,21 @@ class Connection(object):
be set to e.g. `['/usr/bin/env', 'python']` or
`['source', '/opt/rh/rh-python36/enable, '&&', 'python']`
"""
if isinstance(self.options.python_path, list):
python_path = " ".join(self.options.python_path)
else:
pthon_path = self.options.python_path
# if isinstance(self.options.python_path, list):
# python_path = " ".join(self.options.python_path)
# else:
# python_path = self.options.python_path
# quoting the entire command necessary to invoke python supports
# complex python_paths
return ["'" + python_path, '-c',
'import codecs,os,sys;_=codecs.decode;'
'exec(_(_("%s".encode(),"base64"),"zip"))\'' % (encoded.decode(),)
]
# return ["'" + python_path, '-c',
# 'import codecs,os,sys;_=codecs.decode;'
# 'exec(_(_("%s".encode(),"base64"),"zip"))\'' % (encoded.decode(),)
# ]
if isinstance(self.options.python_path, list):
return self.options.python_path
return [self.options.python_path]
"""
return self.get_python_argv() + [
@ -1504,7 +1508,11 @@ class Connection(object):
# with 2.4 (no bytes literal), an extra .encode() either returns the
# same str (2.x) or an equivalent bytes (3.x).
# return self.get_python_cmd(encoded)
return self.get_python_cmd(encoded)
return self.get_python_argv() + [
'-c',
'import codecs,os,sys;_=codecs.decode;'
'exec(_(_("%s".encode(),"base64"),"zip"))' % (encoded.decode(),)
]
def get_econtext_config(self):
assert self.options.max_message_size is not None
@ -1541,6 +1549,8 @@ class Connection(object):
def start_child(self):
args = self.get_boot_command()
LOG.debug('%s', args)
LOG.debug('walrus')
LOG.debug('command line for %r: %s', self, Argv(args))
try:
return self.create_child(args=args, **self.create_child_args)

@ -221,6 +221,14 @@ class Connection(mitogen.parent.Connection):
child_is_immediate_subprocess = False
# strings that, if escaped, cause problems creating connections
# example: `source /opt/rh/rh-python36/enable && python`
# is an acceptable ansible_python_version but shlex would quote the &&
# and prevent python from executing
SHLEX_IGNORE = [
"&&"
]
def _get_name(self):
s = u'ssh.' + mitogen.core.to_text(self.options.hostname)
if self.options.port and self.options.port != 22:
@ -233,8 +241,8 @@ class Connection(mitogen.parent.Connection):
because it must interactively accept host keys or type a password.
"""
return (
self.options.check_host_keys == 'accept' or
self.options.password is not None
self.options.check_host_keys == 'accept'
or self.options.password is not None
)
def create_child(self, **kwargs):
@ -259,8 +267,8 @@ class Connection(mitogen.parent.Connection):
bits += ['-l', self.options.username]
if self.options.port is not None:
bits += ['-p', str(self.options.port)]
if self.options.identities_only and (self.options.identity_file or
self.options.password):
if self.options.identities_only and (self.options.identity_file
or self.options.password):
bits += ['-o', 'IdentitiesOnly yes']
if self.options.identity_file:
bits += ['-i', self.options.identity_file]
@ -290,5 +298,12 @@ class Connection(mitogen.parent.Connection):
if self.options.ssh_args:
bits += self.options.ssh_args
bits.append(self.options.hostname)
# import pdb
# pdb.set_trace()
base = super(Connection, self).get_boot_command()
return bits + [shlex_quote(s).strip() for s in base]
base_parts = []
for s in base:
val = s if s in self.SHLEX_IGNORE else shlex_quote(s).strip()
base_parts.append(val)
return bits + base_parts
# return bits + [shlex_quote(s).strip() for s in base]

Loading…
Cancel
Save