Use a pty for local connections (#73023)
* Use a pty for local connections Fixes #38696 Co-authored-by: James Cammarata <jimi@sngx.net>
This commit is contained in:
parent
d500e6ec45
commit
30d93995dd
4 changed files with 36 additions and 2 deletions
2
changelogs/fragments/enable_su_on_local.yaml
Normal file
2
changelogs/fragments/enable_su_on_local.yaml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
bugfixes:
|
||||||
|
- allow become method 'su' to work on 'local' connection by allocating a fake tty.
|
|
@ -17,6 +17,7 @@ DOCUMENTATION = '''
|
||||||
'''
|
'''
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import pty
|
||||||
import shutil
|
import shutil
|
||||||
import subprocess
|
import subprocess
|
||||||
import fcntl
|
import fcntl
|
||||||
|
@ -79,15 +80,32 @@ class Connection(ConnectionBase):
|
||||||
else:
|
else:
|
||||||
cmd = map(to_bytes, cmd)
|
cmd = map(to_bytes, cmd)
|
||||||
|
|
||||||
|
master = None
|
||||||
|
stdin = subprocess.PIPE
|
||||||
|
if sudoable and self.become and self.become.expect_prompt():
|
||||||
|
# Create a pty if sudoable for privlege escalation that needs it.
|
||||||
|
# Falls back to using a standard pipe if this fails, which may
|
||||||
|
# cause the command to fail in certain situations where we are escalating
|
||||||
|
# privileges or the command otherwise needs a pty.
|
||||||
|
try:
|
||||||
|
master, stdin = pty.openpty()
|
||||||
|
except (IOError, OSError) as e:
|
||||||
|
display.debug("Unable to open pty: %s" % to_native(e))
|
||||||
|
|
||||||
p = subprocess.Popen(
|
p = subprocess.Popen(
|
||||||
cmd,
|
cmd,
|
||||||
shell=isinstance(cmd, (text_type, binary_type)),
|
shell=isinstance(cmd, (text_type, binary_type)),
|
||||||
executable=executable,
|
executable=executable,
|
||||||
cwd=self.cwd,
|
cwd=self.cwd,
|
||||||
stdin=subprocess.PIPE,
|
stdin=stdin,
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
stderr=subprocess.PIPE,
|
stderr=subprocess.PIPE,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# if we created a master, we can close the other half of the pty now
|
||||||
|
if master is not None:
|
||||||
|
os.close(stdin)
|
||||||
|
|
||||||
display.debug("done running command with Popen()")
|
display.debug("done running command with Popen()")
|
||||||
|
|
||||||
if self.become and self.become.expect_prompt() and sudoable:
|
if self.become and self.become.expect_prompt() and sudoable:
|
||||||
|
@ -120,7 +138,8 @@ class Connection(ConnectionBase):
|
||||||
|
|
||||||
if not self.become.check_success(become_output):
|
if not self.become.check_success(become_output):
|
||||||
become_pass = self.become.get_option('become_pass', playcontext=self._play_context)
|
become_pass = self.become.get_option('become_pass', playcontext=self._play_context)
|
||||||
p.stdin.write(to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
|
os.write(master, to_bytes(become_pass, errors='surrogate_or_strict') + b'\n')
|
||||||
|
|
||||||
fcntl.fcntl(p.stdout, fcntl.F_SETFL, fcntl.fcntl(p.stdout, fcntl.F_GETFL) & ~os.O_NONBLOCK)
|
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)
|
fcntl.fcntl(p.stderr, fcntl.F_SETFL, fcntl.fcntl(p.stderr, fcntl.F_GETFL) & ~os.O_NONBLOCK)
|
||||||
|
|
||||||
|
@ -128,6 +147,10 @@ class Connection(ConnectionBase):
|
||||||
stdout, stderr = p.communicate(in_data)
|
stdout, stderr = p.communicate(in_data)
|
||||||
display.debug("done communicating")
|
display.debug("done communicating")
|
||||||
|
|
||||||
|
# finally, close the other half of the pty, if it was created
|
||||||
|
if master:
|
||||||
|
os.close(master)
|
||||||
|
|
||||||
display.debug("done with local.exec_command()")
|
display.debug("done with local.exec_command()")
|
||||||
return (p.returncode, stdout, stderr)
|
return (p.returncode, stdout, stderr)
|
||||||
|
|
||||||
|
|
3
test/integration/targets/become_su/aliases
Normal file
3
test/integration/targets/become_su/aliases
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
destructive
|
||||||
|
shippable/posix/group1
|
||||||
|
skip/aix
|
6
test/integration/targets/become_su/runme.sh
Executable file
6
test/integration/targets/become_su/runme.sh
Executable file
|
@ -0,0 +1,6 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
set -eux
|
||||||
|
|
||||||
|
# ensure we execute su with a pseudo terminal
|
||||||
|
[ "$(ansible -a whoami --become-method=su localhost --become)" != "su: requires a terminal to execute" ]
|
Loading…
Reference in a new issue