diff options
author | Brian Coca <bcoca@users.noreply.github.com> | 2021-02-07 18:53:10 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2021-02-07 17:53:10 -0600 |
commit | 3ef061bdc4610bbf213f70bc70976fdc3005e2cc (patch) | |
tree | 1093fff0d51041f6bfdcd4693ec3cbe351b2a6d3 | |
parent | 63ead90ab9d81f9b5ac3cdc08a6a3a932c1a81d7 (diff) | |
download | ansible-3ef061bdc4610bbf213f70bc70976fdc3005e2cc.tar.gz |
Use a pty for local connections (#73023) (#73281)
Fixes #38696
Co-authored-by: James Cammarata <jimi@sngx.net>
(cherry picked from commit 30d93995ddcf92636919c2238435064a8089e240)
-rw-r--r-- | changelogs/fragments/enable_su_on_local.yaml | 2 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/local.py | 27 | ||||
-rw-r--r-- | test/integration/targets/become_su/aliases | 3 | ||||
-rwxr-xr-x | test/integration/targets/become_su/runme.sh | 6 |
4 files changed, 36 insertions, 2 deletions
diff --git a/changelogs/fragments/enable_su_on_local.yaml b/changelogs/fragments/enable_su_on_local.yaml new file mode 100644 index 0000000000..1f5cdb4bf9 --- /dev/null +++ b/changelogs/fragments/enable_su_on_local.yaml @@ -0,0 +1,2 @@ +bugfixes: + - allow become method 'su' to work on 'local' connection by allocating a fake tty. diff --git a/lib/ansible/plugins/connection/local.py b/lib/ansible/plugins/connection/local.py index 29505cc27e..85cb72b01c 100644 --- a/lib/ansible/plugins/connection/local.py +++ b/lib/ansible/plugins/connection/local.py @@ -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) diff --git a/test/integration/targets/become_su/aliases b/test/integration/targets/become_su/aliases new file mode 100644 index 0000000000..3a07aab32d --- /dev/null +++ b/test/integration/targets/become_su/aliases @@ -0,0 +1,3 @@ +destructive +shippable/posix/group1 +skip/aix diff --git a/test/integration/targets/become_su/runme.sh b/test/integration/targets/become_su/runme.sh new file mode 100755 index 0000000000..87a3511f65 --- /dev/null +++ b/test/integration/targets/become_su/runme.sh @@ -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" ] |