winrm managed kinit (#20416)

This commit is contained in:
Matt Davis 2017-01-24 15:04:11 -08:00 committed by GitHub
parent ff1a732ffd
commit 06353c055a
2 changed files with 46 additions and 5 deletions

View file

@ -40,7 +40,7 @@ Target: February/March 2017
- Pipelining support - Pipelining support
- Become 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 - Switch PS input encoding to BOM-less UTF8
- Server 2016 support/testing (now RTMd) - Server 2016 support/testing (now RTMd)
- Modularize Windows module_utils (allow N files) - Modularize Windows module_utils (allow N files)

View file

@ -25,6 +25,8 @@ import re
import shlex import shlex
import traceback import traceback
import json import json
import tempfile
import subprocess
HAVE_KERBEROS = False HAVE_KERBEROS = False
try: try:
@ -76,7 +78,6 @@ class Connection(ConnectionBase):
self.shell_id = None self.shell_id = None
self.delegate = None self.delegate = None
self._shell_type = 'powershell' self._shell_type = 'powershell'
# FUTURE: Add runas support # FUTURE: Add runas support
super(Connection, self).__init__(*args, **kwargs) super(Connection, self).__init__(*args, **kwargs)
@ -92,6 +93,8 @@ class Connection(ConnectionBase):
self._winrm_user = self._play_context.remote_user self._winrm_user = self._play_context.remote_user
self._winrm_pass = self._play_context.password self._winrm_pass = self._play_context.password
self._kinit_cmd = hostvars.get('ansible_winrm_kinit_cmd', 'kinit')
if hasattr(winrm, 'FEATURE_SUPPORTED_AUTHTYPES'): if hasattr(winrm, 'FEATURE_SUPPORTED_AUTHTYPES'):
self._winrm_supported_authtypes = set(winrm.FEATURE_SUPPORTED_AUTHTYPES) self._winrm_supported_authtypes = set(winrm.FEATURE_SUPPORTED_AUTHTYPES)
else: else:
@ -114,6 +117,18 @@ class Connection(ConnectionBase):
if unsupported_transports: if unsupported_transports:
raise AnsibleError('The installed version of WinRM does not support transport(s) %s' % list(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 # arg names we're going passing directly
internal_kwarg_mask = set(['self', 'endpoint', 'transport', 'username', 'password', 'scheme', 'path']) 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): for arg in passed_winrm_args.difference(internal_kwarg_mask).intersection(supported_winrm_args):
self._winrm_kwargs[arg] = hostvars['ansible_winrm_%s' % arg] 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): def _winrm_connect(self):
''' '''
Establish a WinRM connection over HTTP/HTTPS. Establish a WinRM connection over HTTP/HTTPS.
@ -142,9 +180,12 @@ class Connection(ConnectionBase):
endpoint = urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', '')) endpoint = urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', ''))
errors = [] errors = []
for transport in self._winrm_transport: for transport in self._winrm_transport:
if transport == 'kerberos' and not HAVE_KERBEROS: if transport == 'kerberos':
if not HAVE_KERBEROS:
errors.append('kerberos: the python kerberos library is not installed') errors.append('kerberos: the python kerberos library is not installed')
continue 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) display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host)
try: try:
protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs) protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs)