diff options
author | Lars Kellogg-Stedman <lars@redhat.com> | 2019-04-26 09:19:53 -0400 |
---|---|---|
committer | Toshio Kuratomi <a.badger@gmail.com> | 2019-05-20 18:14:48 -0700 |
commit | a8e14cfe1f8c0845af5a63fbd7cf8598ca305c95 (patch) | |
tree | 5c84f93973c795c2dde1dc86d6493340cb024f26 | |
parent | 6bc671a46a35f611b29b68c034014fc7d9f8bed5 (diff) | |
download | ansible-a8e14cfe1f8c0845af5a63fbd7cf8598ca305c95.tar.gz |
connection/docker: add privilege escalation support
As described in #53385 (and #31759), the docker connection driver did
not support privilege escalation. This commit is a shameless
cut-and-paste of the privilege escalation support from the `local`
connection plugin into the `docker` plugin.
This is a backport to stable-2.7 of #55816.
-rw-r--r-- | changelogs/fragments/53385-docker-privilege-escalation.yml | 4 | ||||
-rw-r--r-- | lib/ansible/plugins/connection/docker.py | 48 |
2 files changed, 50 insertions, 2 deletions
diff --git a/changelogs/fragments/53385-docker-privilege-escalation.yml b/changelogs/fragments/53385-docker-privilege-escalation.yml new file mode 100644 index 0000000000..f727d794d1 --- /dev/null +++ b/changelogs/fragments/53385-docker-privilege-escalation.yml @@ -0,0 +1,4 @@ +--- +bugfixes: + - Fix privilege escalation support for the docker connection plugin when + credentials need to be supplied (e.g. sudo with password). diff --git a/lib/ansible/plugins/connection/docker.py b/lib/ansible/plugins/connection/docker.py index 76859a7183..9316d47c1e 100644 --- a/lib/ansible/plugins/connection/docker.py +++ b/lib/ansible/plugins/connection/docker.py @@ -40,6 +40,7 @@ DOCUMENTATION = """ """ import distutils.spawn +import fcntl import os import os.path import subprocess @@ -48,6 +49,7 @@ import re from distutils.version import LooseVersion import ansible.constants as C +from ansible.compat import selectors from ansible.errors import AnsibleError, AnsibleFileNotFound from ansible.module_utils.six.moves import shlex_quote from ansible.module_utils._text import to_bytes, to_native, to_text @@ -211,10 +213,52 @@ class Connection(ConnectionBase): display.vvv("EXEC %s" % (local_cmd,), host=self._play_context.remote_addr) local_cmd = [to_bytes(i, errors='surrogate_or_strict') for i in local_cmd] - p = subprocess.Popen(local_cmd, shell=False, stdin=subprocess.PIPE, - stdout=subprocess.PIPE, stderr=subprocess.PIPE) + p = subprocess.Popen( + local_cmd, + stdin=subprocess.PIPE, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + + if self._play_context.prompt and sudoable: + 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) + selector = selectors.DefaultSelector() + selector.register(p.stdout, selectors.EVENT_READ) + selector.register(p.stderr, selectors.EVENT_READ) + + become_output = b'' + try: + while not self.check_become_success(become_output) and not self.check_password_prompt(become_output): + events = selector.select(self._play_context.timeout) + if not events: + stdout, stderr = p.communicate() + raise AnsibleError('timeout waiting for privilege escalation password prompt:\n' + to_native(become_output)) + + for key, event in events: + if key.fileobj == p.stdout: + chunk = p.stdout.read() + elif key.fileobj == p.stderr: + chunk = p.stderr.read() + + if not chunk: + stdout, stderr = p.communicate() + raise AnsibleError('privilege output closed while waiting for password prompt:\n' + to_native(become_output)) + become_output += chunk + finally: + selector.close() + + if not self.check_become_success(become_output): + p.stdin.write(to_bytes(self._play_context.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) + + display.debug("getting output with communicate()") stdout, stderr = p.communicate(in_data) + display.debug("done communicating") + + display.debug("done with docker.exec_command()") return (p.returncode, stdout, stderr) def _prefix_login_path(self, remote_path): |