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 pty
|
||||
import shutil
|
||||
import subprocess
|
||||
import fcntl
|
||||
|
@ -79,15 +80,32 @@ class Connection(ConnectionBase):
|
|||
else:
|
||||
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(
|
||||
cmd,
|
||||
shell=isinstance(cmd, (text_type, binary_type)),
|
||||
executable=executable,
|
||||
cwd=self.cwd,
|
||||
stdin=subprocess.PIPE,
|
||||
stdin=stdin,
|
||||
stdout=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()")
|
||||
|
||||
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):
|
||||
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.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)
|
||||
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()")
|
||||
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