Skip to content

Commit

Permalink
connection/docker: add privilege escalation support (#55816)
Browse files Browse the repository at this point in the history
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.

Closes: #53385
  • Loading branch information
larsks authored and ansibot committed May 9, 2019
1 parent 87d42ca commit 61e476b
Show file tree
Hide file tree
Showing 2 changed files with 53 additions and 2 deletions.
4 changes: 4 additions & 0 deletions changelogs/fragments/53385-docker-privilege-escalation.yml
@@ -0,0 +1,4 @@
---
bugfixes:
- Fix privilege escalation support for the docker connection plugin when
credentials needs to be supplied (e.g. sudo with password).
51 changes: 49 additions & 2 deletions lib/ansible/plugins/connection/docker.py
Expand Up @@ -39,6 +39,7 @@
"""

import distutils.spawn
import fcntl
import os
import os.path
import subprocess
Expand All @@ -47,6 +48,7 @@
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
Expand Down Expand Up @@ -205,10 +207,55 @@ def exec_command(self, cmd, in_data=None, sudoable=False):

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.become and self.become.expect_prompt() and sudoable:
display.debug("handling privilege escalation")
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.become.check_success(become_output) and not self.become.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()
break
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.become.check_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):
Expand Down

0 comments on commit 61e476b

Please sign in to comment.