winrm: attempting to get kerb auth to work on MacOS (#33795)

* winrm: attempting to get kerb auth to work on MacOS

* moved to use pexpect if possible as it is simpler

* Made the pexpect event more lenient around different localisations
This commit is contained in:
Jordan Borean 2017-12-19 09:49:21 +10:00 committed by ansibot
parent b25e333076
commit 92e52ef515

View file

@ -75,6 +75,10 @@ DOCUMENTATION = """
- kerberos usage mode.
- The managed option means Ansible will obtain kerberos ticket.
- While the manual one means a ticket must already have been obtained by the user.
- If having issues with Ansible freezing when trying to obtain the
Kerberos ticket, you can either set this to C(manual) and obtain
it outside Ansible or install C(pexpect) through pip and try
again.
choices: [managed, manual]
vars:
- name: ansible_winrm_kinit_mode
@ -110,7 +114,6 @@ except ImportError:
from ansible.errors import AnsibleError, AnsibleConnectionFailure
from ansible.errors import AnsibleFileNotFound
from ansible.module_utils.six import string_types
from ansible.module_utils.six.moves.urllib.parse import urlunsplit
from ansible.module_utils._text import to_bytes, to_native, to_text
from ansible.module_utils.six import binary_type
@ -135,6 +138,12 @@ except ImportError as e:
HAS_XMLTODICT = False
XMLTODICT_IMPORT_ERR = e
try:
import pexpect
HAS_PEXPECT = True
except ImportError as e:
HAS_PEXPECT = False
try:
from __main__ import display
except ImportError:
@ -239,20 +248,45 @@ class Connection(ConnectionBase):
def _kerb_auth(self, principal, password):
if password is None:
password = ""
self._kerb_ccache = tempfile.NamedTemporaryFile()
display.vvvvv("creating Kerberos CC at %s" % self._kerb_ccache.name)
krb5ccname = "FILE:%s" % self._kerb_ccache.name
krbenv = dict(KRB5CCNAME=krb5ccname)
os.environ["KRB5CCNAME"] = krb5ccname
kinit_cmdline = [self._kinit_cmd, principal]
krb5env = dict(KRB5CCNAME=krb5ccname)
display.vvvvv("calling kinit for principal %s" % principal)
p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=krbenv)
# pexpect runs the process in its own pty so it can correctly send
# the password as input even on MacOS which blocks subprocess from
# doing so. Unfortunately it is not available on the built in Python
# so we can only use it if someone has installed it
if HAS_PEXPECT:
kinit_cmdline = "%s %s" % (self._kinit_cmd, principal)
password = to_text(password, encoding='utf-8',
errors='surrogate_or_strict')
# TODO: unicode/py3
stdout, stderr = p.communicate(password + b'\n')
display.vvvv("calling kinit with pexpect for principal %s"
% principal)
events = {
".*:": password + "\n"
}
# technically this is the stdout but to match subprocess we wil call
# it stderr
stderr, rc = pexpect.run(kinit_cmdline, withexitstatus=True, events=events, env=krb5env, timeout=60)
else:
kinit_cmdline = [self._kinit_cmd, principal]
password = to_bytes(password, encoding='utf-8',
errors='surrogate_or_strict')
if p.returncode != 0:
display.vvvv("calling kinit with subprocess for principal %s"
% principal)
p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
env=krb5env)
stdout, stderr = p.communicate(password + b'\n')
rc = p.returncode != 0
if rc != 0:
raise AnsibleConnectionFailure("Kerberos auth failure: %s" % stderr.strip())
display.vvvvv("kinit succeeded for principal %s" % principal)