diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py index f01925f9591..5c5e43aef77 100644 --- a/lib/ansible/plugins/connection/winrm.py +++ b/lib/ansible/plugins/connection/winrm.py @@ -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)