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, PlayContext. It is used for normal connections and delegate_to connections,
and should always be accurate. and should always be accurate.
""" """
def __init__(self, connection, play_context, transport, inventory_name): def __init__(self, connection, play_context, transport, inventory_name):
self._connection = connection self._connection = connection
self._play_context = play_context self._play_context = play_context
@ -366,6 +367,7 @@ class PlayContextSpec(Spec):
# #511, #536: executor/module_common.py::_get_shebang() hard-wires # #511, #536: executor/module_common.py::_get_shebang() hard-wires
# "/usr/bin/python" as the default interpreter path if no other # "/usr/bin/python" as the default interpreter path if no other
# interpreter is specified. # interpreter is specified.
# raise ValueError(parse_python_path(s))
return parse_python_path(s or '/usr/bin/python') return parse_python_path(s or '/usr/bin/python')
def private_key_file(self): def private_key_file(self):
@ -379,9 +381,9 @@ class PlayContextSpec(Spec):
def ansible_ssh_timeout(self): def ansible_ssh_timeout(self):
return ( return (
self._connection.get_task_var('ansible_timeout') or self._connection.get_task_var('ansible_timeout')
self._connection.get_task_var('ansible_ssh_timeout') or or self._connection.get_task_var('ansible_ssh_timeout')
self.timeout() or self.timeout()
) )
def ssh_args(self): def ssh_args(self):
@ -468,8 +470,8 @@ class PlayContextSpec(Spec):
def ansible_doas_exe(self): def ansible_doas_exe(self):
return ( return (
self._connection.get_task_var('ansible_doas_exe') or self._connection.get_task_var('ansible_doas_exe')
os.environ.get('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 having a configruation problem with connection delegation, the answer to
your problem lies in the method implementations below! your problem lies in the method implementations below!
""" """
def __init__(self, inventory_name, host_vars, become_method, become_user, def __init__(self, inventory_name, host_vars, become_method, become_user,
play_context): play_context):
""" """
@ -520,8 +523,8 @@ class MitogenViaSpec(Spec):
def transport(self): def transport(self):
return ( return (
self._host_vars.get('ansible_connection') or self._host_vars.get('ansible_connection')
C.DEFAULT_TRANSPORT or C.DEFAULT_TRANSPORT
) )
def inventory_name(self): def inventory_name(self):
@ -530,16 +533,16 @@ class MitogenViaSpec(Spec):
def remote_addr(self): def remote_addr(self):
# play_context.py::MAGIC_VARIABLE_MAPPING # play_context.py::MAGIC_VARIABLE_MAPPING
return ( return (
self._host_vars.get('ansible_ssh_host') or self._host_vars.get('ansible_ssh_host')
self._host_vars.get('ansible_host') or or self._host_vars.get('ansible_host')
self._inventory_name or self._inventory_name
) )
def remote_user(self): def remote_user(self):
return ( return (
self._host_vars.get('ansible_ssh_user') or self._host_vars.get('ansible_ssh_user')
self._host_vars.get('ansible_user') or or self._host_vars.get('ansible_user')
C.DEFAULT_REMOTE_USER or C.DEFAULT_REMOTE_USER
) )
def become(self): def become(self):
@ -547,9 +550,9 @@ class MitogenViaSpec(Spec):
def become_method(self): def become_method(self):
return ( return (
self._become_method or self._become_method
self._host_vars.get('ansible_become_method') or or self._host_vars.get('ansible_become_method')
C.DEFAULT_BECOME_METHOD or C.DEFAULT_BECOME_METHOD
) )
def become_user(self): def become_user(self):
@ -557,21 +560,21 @@ class MitogenViaSpec(Spec):
def become_pass(self): def become_pass(self):
return optional_secret( return optional_secret(
self._host_vars.get('ansible_become_password') or self._host_vars.get('ansible_become_password')
self._host_vars.get('ansible_become_pass') or self._host_vars.get('ansible_become_pass')
) )
def password(self): def password(self):
return optional_secret( return optional_secret(
self._host_vars.get('ansible_ssh_pass') or self._host_vars.get('ansible_ssh_pass')
self._host_vars.get('ansible_password') or self._host_vars.get('ansible_password')
) )
def port(self): def port(self):
return ( return (
self._host_vars.get('ansible_ssh_port') or self._host_vars.get('ansible_ssh_port')
self._host_vars.get('ansible_port') or or self._host_vars.get('ansible_port')
C.DEFAULT_REMOTE_PORT or C.DEFAULT_REMOTE_PORT
) )
def python_path(self): def python_path(self):
@ -579,20 +582,21 @@ class MitogenViaSpec(Spec):
# #511, #536: executor/module_common.py::_get_shebang() hard-wires # #511, #536: executor/module_common.py::_get_shebang() hard-wires
# "/usr/bin/python" as the default interpreter path if no other # "/usr/bin/python" as the default interpreter path if no other
# interpreter is specified. # interpreter is specified.
# raise ValueError(parse_python_path(s))
return parse_python_path(s or '/usr/bin/python') return parse_python_path(s or '/usr/bin/python')
def private_key_file(self): def private_key_file(self):
# TODO: must come from PlayContext too. # TODO: must come from PlayContext too.
return ( return (
self._host_vars.get('ansible_ssh_private_key_file') or self._host_vars.get('ansible_ssh_private_key_file')
self._host_vars.get('ansible_private_key_file') or or self._host_vars.get('ansible_private_key_file')
C.DEFAULT_PRIVATE_KEY_FILE or C.DEFAULT_PRIVATE_KEY_FILE
) )
def ssh_executable(self): def ssh_executable(self):
return ( return (
self._host_vars.get('ansible_ssh_executable') or self._host_vars.get('ansible_ssh_executable')
C.ANSIBLE_SSH_EXECUTABLE or C.ANSIBLE_SSH_EXECUTABLE
) )
def timeout(self): def timeout(self):
@ -601,9 +605,9 @@ class MitogenViaSpec(Spec):
def ansible_ssh_timeout(self): def ansible_ssh_timeout(self):
return ( return (
self._host_vars.get('ansible_timeout') or self._host_vars.get('ansible_timeout')
self._host_vars.get('ansible_ssh_timeout') or or self._host_vars.get('ansible_ssh_timeout')
self.timeout() or self.timeout()
) )
def ssh_args(self): def ssh_args(self):
@ -611,19 +615,19 @@ class MitogenViaSpec(Spec):
mitogen.core.to_text(term) mitogen.core.to_text(term)
for s in ( for s in (
( (
self._host_vars.get('ansible_ssh_args') or self._host_vars.get('ansible_ssh_args')
getattr(C, 'ANSIBLE_SSH_ARGS', None) or or getattr(C, 'ANSIBLE_SSH_ARGS', None)
os.environ.get('ANSIBLE_SSH_ARGS') or os.environ.get('ANSIBLE_SSH_ARGS')
# TODO: ini entry. older versions. # TODO: ini entry. older versions.
), ),
( (
self._host_vars.get('ansible_ssh_common_args') or self._host_vars.get('ansible_ssh_common_args')
os.environ.get('ANSIBLE_SSH_COMMON_ARGS') or os.environ.get('ANSIBLE_SSH_COMMON_ARGS')
# TODO: ini entry. # TODO: ini entry.
), ),
( (
self._host_vars.get('ansible_ssh_extra_args') or self._host_vars.get('ansible_ssh_extra_args')
os.environ.get('ANSIBLE_SSH_EXTRA_ARGS') or os.environ.get('ANSIBLE_SSH_EXTRA_ARGS')
# TODO: ini entry. # TODO: ini entry.
), ),
) )
@ -633,8 +637,8 @@ class MitogenViaSpec(Spec):
def become_exe(self): def become_exe(self):
return ( return (
self._host_vars.get('ansible_become_exe') or self._host_vars.get('ansible_become_exe')
C.DEFAULT_BECOME_EXE or C.DEFAULT_BECOME_EXE
) )
def sudo_args(self): def sudo_args(self):
@ -694,6 +698,6 @@ class MitogenViaSpec(Spec):
def ansible_doas_exe(self): def ansible_doas_exe(self):
return ( return (
self._host_vars.get('ansible_doas_exe') or self._host_vars.get('ansible_doas_exe')
os.environ.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.write(1, 'MITO001\n'.encode())
os.close(2) os.close(2)
def get_python_cmd(self, encoded): def get_python_argv(self):
""" """
Return the command necessary to invoke Python, Return the command necessary to invoke Python,
by returning a 1-element list containing :attr:`python_path` + codecs 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 be set to e.g. `['/usr/bin/env', 'python']` or
`['source', '/opt/rh/rh-python36/enable, '&&', 'python']` `['source', '/opt/rh/rh-python36/enable, '&&', 'python']`
""" """
if isinstance(self.options.python_path, list): # if isinstance(self.options.python_path, list):
python_path = " ".join(self.options.python_path) # python_path = " ".join(self.options.python_path)
else: # else:
pthon_path = self.options.python_path # python_path = self.options.python_path
# quoting the entire command necessary to invoke python supports # quoting the entire command necessary to invoke python supports
# complex python_paths # complex python_paths
return ["'" + python_path, '-c', # return ["'" + python_path, '-c',
'import codecs,os,sys;_=codecs.decode;' # 'import codecs,os,sys;_=codecs.decode;'
'exec(_(_("%s".encode(),"base64"),"zip"))\'' % (encoded.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() + [ return self.get_python_argv() + [
@ -1504,7 +1508,11 @@ class Connection(object):
# with 2.4 (no bytes literal), an extra .encode() either returns the # with 2.4 (no bytes literal), an extra .encode() either returns the
# same str (2.x) or an equivalent bytes (3.x). # 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_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): def get_econtext_config(self):
assert self.options.max_message_size is not None assert self.options.max_message_size is not None
@ -1541,6 +1549,8 @@ class Connection(object):
def start_child(self): def start_child(self):
args = self.get_boot_command() args = self.get_boot_command()
LOG.debug('%s', args)
LOG.debug('walrus')
LOG.debug('command line for %r: %s', self, Argv(args)) LOG.debug('command line for %r: %s', self, Argv(args))
try: try:
return self.create_child(args=args, **self.create_child_args) return self.create_child(args=args, **self.create_child_args)

@ -221,6 +221,14 @@ class Connection(mitogen.parent.Connection):
child_is_immediate_subprocess = False 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): def _get_name(self):
s = u'ssh.' + mitogen.core.to_text(self.options.hostname) s = u'ssh.' + mitogen.core.to_text(self.options.hostname)
if self.options.port and self.options.port != 22: 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. because it must interactively accept host keys or type a password.
""" """
return ( return (
self.options.check_host_keys == 'accept' or self.options.check_host_keys == 'accept'
self.options.password is not None or self.options.password is not None
) )
def create_child(self, **kwargs): def create_child(self, **kwargs):
@ -259,8 +267,8 @@ class Connection(mitogen.parent.Connection):
bits += ['-l', self.options.username] bits += ['-l', self.options.username]
if self.options.port is not None: if self.options.port is not None:
bits += ['-p', str(self.options.port)] bits += ['-p', str(self.options.port)]
if self.options.identities_only and (self.options.identity_file or if self.options.identities_only and (self.options.identity_file
self.options.password): or self.options.password):
bits += ['-o', 'IdentitiesOnly yes'] bits += ['-o', 'IdentitiesOnly yes']
if self.options.identity_file: if self.options.identity_file:
bits += ['-i', self.options.identity_file] bits += ['-i', self.options.identity_file]
@ -290,5 +298,12 @@ class Connection(mitogen.parent.Connection):
if self.options.ssh_args: if self.options.ssh_args:
bits += self.options.ssh_args bits += self.options.ssh_args
bits.append(self.options.hostname) bits.append(self.options.hostname)
# import pdb
# pdb.set_trace()
base = super(Connection, self).get_boot_command() 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