From 06353c055a5671847e642e2c1313b0a09baac717 Mon Sep 17 00:00:00 2001 From: Matt Davis Date: Tue, 24 Jan 2017 15:04:11 -0800 Subject: [PATCH] winrm managed kinit (#20416) --- docs/docsite/rst/roadmap/ROADMAP_2_3.rst | 2 +- lib/ansible/plugins/connection/winrm.py | 49 ++++++++++++++++++++++-- 2 files changed, 46 insertions(+), 5 deletions(-) diff --git a/docs/docsite/rst/roadmap/ROADMAP_2_3.rst b/docs/docsite/rst/roadmap/ROADMAP_2_3.rst index 27584bf6ec4..97f594f4a1c 100644 --- a/docs/docsite/rst/roadmap/ROADMAP_2_3.rst +++ b/docs/docsite/rst/roadmap/ROADMAP_2_3.rst @@ -40,7 +40,7 @@ Target: February/March 2017 - Pipelining support - Become support - - Integrated kerberos ticket management (via ansible_user/ansible_password) + - Integrated kerberos ticket management (via ansible_user/ansible_password) (done) - Switch PS input encoding to BOM-less UTF8 - Server 2016 support/testing (now RTM’d) - Modularize Windows module_utils (allow N files) diff --git a/lib/ansible/plugins/connection/winrm.py b/lib/ansible/plugins/connection/winrm.py index 5116f41be0c..860b58f4b05 100644 --- a/lib/ansible/plugins/connection/winrm.py +++ b/lib/ansible/plugins/connection/winrm.py @@ -25,6 +25,8 @@ import re import shlex import traceback import json +import tempfile +import subprocess HAVE_KERBEROS = False try: @@ -76,7 +78,6 @@ class Connection(ConnectionBase): self.shell_id = None self.delegate = None self._shell_type = 'powershell' - # FUTURE: Add runas support super(Connection, self).__init__(*args, **kwargs) @@ -92,6 +93,8 @@ class Connection(ConnectionBase): self._winrm_user = self._play_context.remote_user self._winrm_pass = self._play_context.password + self._kinit_cmd = hostvars.get('ansible_winrm_kinit_cmd', 'kinit') + if hasattr(winrm, 'FEATURE_SUPPORTED_AUTHTYPES'): self._winrm_supported_authtypes = set(winrm.FEATURE_SUPPORTED_AUTHTYPES) else: @@ -114,6 +117,18 @@ class Connection(ConnectionBase): if unsupported_transports: raise AnsibleError('The installed version of WinRM does not support transport(s) %s' % list(unsupported_transports)) + # if kerberos is among our transports and there's a password specified, we're managing the tickets + kinit_mode = str(hostvars.get('ansible_winrm_kinit_mode', '')).strip() + if kinit_mode == "": + # HACK: ideally, remove multi-transport stuff + self._kerb_managed = "kerberos" in self._winrm_transport and self._winrm_pass + elif kinit_mode == "managed": + self._kerb_managed = True + elif kinit_mode == "manual": + self._kerb_managed = False + else: + raise AnsibleError('Unknown ansible_winrm_kinit_mode value: %s' % kinit_mode) + # arg names we're going passing directly internal_kwarg_mask = set(['self', 'endpoint', 'transport', 'username', 'password', 'scheme', 'path']) @@ -132,6 +147,29 @@ class Connection(ConnectionBase): for arg in passed_winrm_args.difference(internal_kwarg_mask).intersection(supported_winrm_args): self._winrm_kwargs[arg] = hostvars['ansible_winrm_%s' % arg] + # Until pykerberos has enough goodies to implement a rudimentary kinit/klist, simplest way is to let each connection + # auth itself with a private CCACHE. + 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] + + display.vvvvv("calling kinit for principal %s" % principal) + p = subprocess.Popen(kinit_cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=krbenv) + + # TODO: unicode/py3 + stdout, stderr = p.communicate(password + b'\n') + + if p.returncode != 0: + raise AnsibleConnectionFailure("Kerberos auth failure: %s" % stderr.strip()) + + display.vvvvv("kinit succeeded for principal %s" % principal) + def _winrm_connect(self): ''' Establish a WinRM connection over HTTP/HTTPS. @@ -142,9 +180,12 @@ class Connection(ConnectionBase): endpoint = urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', '')) errors = [] for transport in self._winrm_transport: - if transport == 'kerberos' and not HAVE_KERBEROS: - errors.append('kerberos: the python kerberos library is not installed') - continue + if transport == 'kerberos': + if not HAVE_KERBEROS: + errors.append('kerberos: the python kerberos library is not installed') + continue + if self._kerb_managed: + self._kerb_auth(self._winrm_user, self._winrm_pass) display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host) try: protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs)