summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Kellogg-Stedman <lars@redhat.com>2019-04-26 09:19:53 -0400
committerToshio Kuratomi <a.badger@gmail.com>2019-05-20 18:14:48 -0700
commita8e14cfe1f8c0845af5a63fbd7cf8598ca305c95 (patch)
tree5c84f93973c795c2dde1dc86d6493340cb024f26
parent6bc671a46a35f611b29b68c034014fc7d9f8bed5 (diff)
downloadansible-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.yml4
-rw-r--r--lib/ansible/plugins/connection/docker.py48
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):