Proposing fix for Issue #4324; adding support for su in connection plugins ssh, ssh_alt

Fixes for ssh_alt support, adding in references to in_data where appropriate
This commit is contained in:
Paul Durivage
2013-12-17 16:26:58 -06:00
parent 297a28aa79
commit 4088243deb
10 changed files with 246 additions and 78 deletions

View File

@@ -141,6 +141,9 @@ class Runner(object):
accelerate=False, # use accelerated connection
accelerate_ipv6=False, # accelerated connection w/ IPv6
accelerate_port=None, # port to use with accelerated connection
su=False, # Are we running our command via su?
su_user=None, # User to su to when running command, ex: 'root'
su_pass=C.DEFAULT_SU_PASS
):
# used to lock multiprocess inputs and outputs at various levels
@@ -188,6 +191,9 @@ class Runner(object):
self.accelerate_ipv6 = accelerate_ipv6
self.callbacks.runner = self
self.original_transport = self.transport
self.su = su
self.su_user = su_user
self.su_pass = su_pass
if self.transport == 'smart':
# if the transport is 'smart' see if SSH can support ControlPersist if not use paramiko
@@ -311,12 +317,13 @@ class Runner(object):
or async_jid is not None
or not conn.has_pipelining
or not C.ANSIBLE_SSH_PIPELINING
or C.DEFAULT_KEEP_REMOTE_FILES):
or C.DEFAULT_KEEP_REMOTE_FILES
or self.su):
self._transfer_str(conn, tmp, module_name, module_data)
environment_string = self._compute_environment_string(inject)
if tmp.find("tmp") != -1 and self.sudo and self.sudo_user != 'root':
if (self.sudo or self.su) and (self.sudo_user != 'root' or self.su_user != 'root'):
# deal with possible umask issues once sudo'ed to other user
cmd_chmod = "chmod a+r %s" % remote_module_path
self._low_level_exec_command(conn, cmd_chmod, tmp, sudoable=False)
@@ -343,7 +350,7 @@ class Runner(object):
else:
argsfile = self._transfer_str(conn, tmp, 'arguments', args)
if self.sudo and self.sudo_user != 'root':
if (self.sudo or self.su) and (self.sudo_user != 'root' or self.su_user != 'root'):
# deal with possible umask issues once sudo'ed to other user
cmd_args_chmod = "chmod a+r %s" % argsfile
self._low_level_exec_command(conn, cmd_args_chmod, tmp, sudoable=False)
@@ -354,7 +361,7 @@ class Runner(object):
cmd = " ".join([str(x) for x in [remote_module_path, async_jid, async_limit, async_module, argsfile]])
else:
if async_jid is None:
if conn.has_pipelining and C.ANSIBLE_SSH_PIPELINING and not C.DEFAULT_KEEP_REMOTE_FILES:
if conn.has_pipelining and C.ANSIBLE_SSH_PIPELINING and not C.DEFAULT_KEEP_REMOTE_FILES and not self.su:
in_data = module_data
else:
cmd = "%s" % (remote_module_path)
@@ -369,7 +376,7 @@ class Runner(object):
cmd = cmd.strip()
if tmp.find("tmp") != -1 and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files:
if not self.sudo or self.sudo_user == 'root':
if not self.sudo or self.su or self.sudo_user == 'root' or self.su_user == 'root':
# not sudoing or sudoing to root, so can cleanup files in the same step
cmd = cmd + "; rm -rf %s >/dev/null 2>&1" % tmp
@@ -379,10 +386,13 @@ class Runner(object):
# specified in the play, not the sudo_user
sudoable = False
res = self._low_level_exec_command(conn, cmd, tmp, sudoable=sudoable, in_data=in_data)
if self.su:
res = self._low_level_exec_command(conn, cmd, tmp, su=sudoable, in_data=in_data)
else:
res = self._low_level_exec_command(conn, cmd, tmp, sudoable=sudoable, in_data=in_data)
if tmp.find("tmp") != -1 and not C.DEFAULT_KEEP_REMOTE_FILES and not persist_files:
if self.sudo and self.sudo_user != 'root':
if (self.sudo or self.su) and (self.sudo_user != 'root' or self.su_user != 'root'):
# not sudoing to root, so maybe can't delete files as that other user
# have to clean up temp files as original user in a second step
cmd2 = "rm -rf %s >/dev/null 2>&1" % tmp
@@ -798,7 +808,10 @@ class Runner(object):
if tmp.find("tmp") != -1:
# tmp has already been created
return False
if not conn.has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES:
if not conn.has_pipelining or not C.ANSIBLE_SSH_PIPELINING or C.DEFAULT_KEEP_REMOTE_FILES or self.su:
# tmp is necessary to store module source code
return True
if not conn.has_pipelining:
# tmp is necessary to store the module source code
# or we want to keep the files on the target system
return True
@@ -810,20 +823,36 @@ class Runner(object):
# *****************************************************
def _low_level_exec_command(self, conn, cmd, tmp, sudoable=False, executable=None, in_data=None):
def _low_level_exec_command(self, conn, cmd, tmp, sudoable=False,
executable=None, su=False, in_data=None):
''' execute a command string over SSH, return the output '''
if executable is None:
executable = C.DEFAULT_EXECUTABLE
sudo_user = self.sudo_user
su_user = self.su_user
# compare connection user to sudo_user and disable if the same
if hasattr(conn, 'user'):
if conn.user == sudo_user:
if conn.user == sudo_user or conn.user == su_user:
sudoable = False
su = False
rc, stdin, stdout, stderr = conn.exec_command(cmd, tmp, sudo_user, sudoable=sudoable, executable=executable, in_data=in_data)
if su:
rc, stdin, stdout, stderr = conn.exec_command(cmd,
tmp,
su_user=su_user,
su=su,
executable=executable,
in_data=in_data)
else:
rc, stdin, stdout, stderr = conn.exec_command(cmd,
tmp,
sudo_user,
sudoable=sudoable,
executable=executable,
in_data=in_data)
if type(stdout) not in [ str, unicode ]:
out = ''.join(stdout.readlines())
@@ -881,11 +910,11 @@ class Runner(object):
basefile = 'ansible-tmp-%s-%s' % (time.time(), random.randint(0, 2**48))
basetmp = os.path.join(C.DEFAULT_REMOTE_TMP, basefile)
if self.sudo and self.sudo_user != 'root' and basetmp.startswith('$HOME'):
if (self.sudo or self.su) and (self.sudo_user != 'root' or self.su != 'root') and basetmp.startswith('$HOME'):
basetmp = os.path.join('/tmp', basefile)
cmd = 'mkdir -p %s' % basetmp
if self.remote_user != 'root' or (self.sudo and self.sudo_user != 'root'):
if self.remote_user != 'root' or ((self.sudo or self.su) and (self.sudo_user != 'root' or self.su != 'root')):
cmd += ' && chmod a+rx %s' % basetmp
cmd += ' && echo %s' % basetmp

View File

@@ -145,7 +145,7 @@ class Connection(object):
return False
return True
def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh', in_data=None):
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su_user=None, su=False):
''' run a command on the remote host '''
ssh_cmd = self._password_cmd()
@@ -165,7 +165,10 @@ class Connection(object):
ssh_cmd += ['-6']
ssh_cmd += [self.host]
if not self.runner.sudo or not sudoable:
if su and su_user:
sudocmd, prompt, success_key = utils.make_su_cmd(su_user, executable, cmd)
ssh_cmd.append(sudocmd)
elif not self.runner.sudo or not sudoable:
if executable:
ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd))
else:
@@ -183,7 +186,7 @@ class Connection(object):
# the host to known hosts is not intermingled with multiprocess output.
fcntl.lockf(self.runner.process_lockfile, fcntl.LOCK_EX)
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
# create process
if in_data:
# do not use pseudo-pty
@@ -206,7 +209,8 @@ class Connection(object):
self._send_password()
if self.runner.sudo and sudoable and self.runner.sudo_pass:
if (self.runner.sudo and sudoable and self.runner.sudo_pass) or \
(self.runner.su and su and self.runner.su_pass):
# several cases are handled for sudo privileges with password
# * NOPASSWD (tty & no-tty): detect success_key on stdout
# * without NOPASSWD:
@@ -237,16 +241,19 @@ class Connection(object):
if p.stdout in rfd:
chunk = p.stdout.read()
if not chunk:
raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt')
raise errors.AnsibleError('ssh connection closed waiting for sudo or su password prompt')
sudo_output += chunk
if not rfd:
# timeout. wrap up process communication
stdout = p.communicate()
raise errors.AnsibleError('ssh connection error waiting for sudo password prompt')
raise errors.AnsibleError('ssh connection error waiting for sudo or su password prompt')
if success_key not in sudo_output:
stdin.write(self.runner.sudo_pass + '\n')
if sudoable:
stdin.write(self.runner.sudo_pass + '\n')
elif su:
stdin.write(self.runner.su_pass + '\n')
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
# We can't use p.communicate here because the ControlMaster may have stdout open as well
@@ -262,12 +269,18 @@ class Connection(object):
while True:
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
# fail early if the sudo password is wrong
# fail early if the sudo/su password is wrong
if self.runner.sudo and sudoable and self.runner.sudo_pass:
incorrect_password = gettext.dgettext(
"sudo", "Sorry, try again.")
if stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
raise errors.AnsibleError('Incorrect sudo password')
raise errors.AnsibleError('Incorrect sudo password')
if self.runner.su and su and self.runner.sudo_pass:
incorrect_password = gettext.dgettext(
"su", "Sorry")
if stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
raise errors.AnsibleError('Incorrect su password')
if p.stdout in rfd:
dat = os.read(p.stdout.fileno(), 9000)

View File

@@ -145,7 +145,7 @@ class Connection(object):
return False
return True
def exec_command(self, cmd, tmp_path, sudo_user,sudoable=False, executable='/bin/sh', in_data=None):
def exec_command(self, cmd, tmp_path, sudo_user=None, sudoable=False, executable='/bin/sh', in_data=None, su=False, su_user=None):
''' run a command on the remote host '''
if in_data:
@@ -163,7 +163,10 @@ class Connection(object):
ssh_cmd += ['-6']
ssh_cmd += [self.host]
if not self.runner.sudo or not sudoable:
if su and su_user:
sudocmd, prompt, success_key = utils.make_su_cmd(su_user, executable, cmd)
ssh_cmd.append(sudocmd)
elif not self.runner.sudo or not sudoable:
if executable:
ssh_cmd.append(executable + ' -c ' + pipes.quote(cmd))
else:
@@ -183,7 +186,6 @@ class Connection(object):
fcntl.lockf(self.runner.output_lockfile, fcntl.LOCK_EX)
try:
# Make sure stdin is a proper (pseudo) pty to avoid: tcgetattr errors
master, slave = pty.openpty()
@@ -198,7 +200,8 @@ class Connection(object):
self._send_password()
if self.runner.sudo and sudoable and self.runner.sudo_pass:
if (self.runner.sudo and sudoable and self.runner.sudo_pass) or \
(self.runner.su and su and self.runner.su_pass):
fcntl.fcntl(p.stdout, fcntl.F_SETFL,
fcntl.fcntl(p.stdout, fcntl.F_GETFL) | os.O_NONBLOCK)
sudo_output = ''
@@ -208,13 +211,17 @@ class Connection(object):
if p.stdout in rfd:
chunk = p.stdout.read()
if not chunk:
raise errors.AnsibleError('ssh connection closed waiting for sudo password prompt')
raise errors.AnsibleError('ssh connection closed waiting for sudo or su password prompt')
sudo_output += chunk
else:
stdout = p.communicate()
raise errors.AnsibleError('ssh connection error waiting for sudo password prompt')
raise errors.AnsibleError('ssh connection error waiting for sudo or su password prompt')
if success_key not in sudo_output:
stdin.write(self.runner.sudo_pass + '\n')
if sudoable:
stdin.write(self.runner.sudo_pass + '\n')
elif su:
stdin.write(self.runner.su_pass + '\n')
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
# We can't use p.communicate here because the ControlMaster may have stdout open as well
@@ -224,12 +231,18 @@ class Connection(object):
while True:
rfd, wfd, efd = select.select(rpipes, [], rpipes, 1)
# fail early if the sudo password is wrong
# fail early if the sudo/su password is wrong
if self.runner.sudo and sudoable and self.runner.sudo_pass:
incorrect_password = gettext.dgettext(
"sudo", "Sorry, try again.")
if stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
raise errors.AnsibleError('Incorrect sudo password')
raise errors.AnsibleError('Incorrect sudo password')
if self.runner.su and su and self.runner.su_pass:
incorrect_password = gettext.dgettext(
"su", "su: Authentication failure")
if stdout.endswith("%s\r\n%s" % (incorrect_password, prompt)):
raise errors.AnsibleError('Incorrect su password')
if p.stdout in rfd:
dat = os.read(p.stdout.fileno(), 9000)