Support additional options for WinRM connections via inventory variables.

pull/12054/head
Chris Church 9 years ago
parent 8b56bc1c01
commit 056c6b77d0

@ -164,7 +164,7 @@ In group_vars/windows.yml, define the following inventory variables::
ansible_port: 5986 ansible_port: 5986
ansible_connection: winrm ansible_connection: winrm
Notice that the ssh_port is not actually for SSH, but this is a holdover variable name from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH. Notice that the ssh_port is not actually for SSH, but this is a holdover variable name from how Ansible is mostly an SSH-oriented system. Again, Windows management will not happen over SSH (`yet <http://blogs.msdn.com/b/powershell/archive/2015/06/03/looking-forward-microsoft-support-for-secure-shell-ssh.aspx>`).
If you have installed the ``kerberos`` module and ``ansible_user`` contains ``@`` (e.g. ``username@realm``), Ansible will first attempt Kerberos authentication. *This method uses the principal you are authenticated to Kerberos with on the control machine and not ``ansible_user``*. If that fails, either because you are not signed into Kerberos on the control machine or because the corresponding domain account on the remote host is not available, then Ansible will fall back to "plain" username/password authentication. If you have installed the ``kerberos`` module and ``ansible_user`` contains ``@`` (e.g. ``username@realm``), Ansible will first attempt Kerberos authentication. *This method uses the principal you are authenticated to Kerberos with on the control machine and not ``ansible_user``*. If that fails, either because you are not signed into Kerberos on the control machine or because the corresponding domain account on the remote host is not available, then Ansible will fall back to "plain" username/password authentication.
@ -181,6 +181,18 @@ a version that is 3 or higher.
You'll run this command again later though, to make sure everything is working. You'll run this command again later though, to make sure everything is working.
Since 2.0, the following custom inventory variables are also supported for additional configuration of WinRM connections::
* ``ansible_winrm_scheme``: Specify the connection scheme (``http`` or ``https``) to use for the WinRM connection. Ansible uses ``https`` by default unless the port is 5985.
* ``ansible_winrm_host``: May be used as an alias for ``ansible_ssh_host`` and will take precedence if both are defined.
* ``ansible_winrm_port``: May be used an alias for ``ansible_ssh_port`` and will take precedence if both are defined.
* ``ansible_winrm_path``: Specify an alternate path to the WinRM endpoint. Ansible uses ``/wsman`` by default.
* ``ansible_winrm_user``: May be used an alias for ``ansible_ssh_user`` and will take precedence if both are defined.
* ``ansible_winrm_pass``: May be used an alias for ``ansible_ssh_pass`` and will take precedence if both are defined.
* ``ansible_winrm_realm``: Specify the realm to use for Kerberos authentication. If the username contains ``@``, Ansible will use the part of the username after ``@`` by default.
* ``ansible_winrm_transport``: Specify one or more transports as a comma-separated list. By default, Ansible will use ``kerberos,plaintext`` if the ``kerberos`` module is installed and a realm is defined, otherwise ``plaintext``.
* ``ansible_winrm_*``: Any additional keyword arguments supported by ``winrm.Protocol`` may be provided.
.. _windows_system_prep: .. _windows_system_prep:
Windows System Prep Windows System Prep

@ -19,12 +19,12 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type __metaclass__ = type
import base64 import base64
import inspect
import os import os
import re import re
import shlex import shlex
import traceback import traceback
import urlparse
from six.moves.urllib import parse
try: try:
from winrm import Response from winrm import Response
@ -50,11 +50,6 @@ from ansible.utils.unicode import to_bytes, to_unicode
class Connection(ConnectionBase): class Connection(ConnectionBase):
'''WinRM connections over HTTP/HTTPS.''' '''WinRM connections over HTTP/HTTPS.'''
transport_schemes = {
'http': [('kerberos', 'http'), ('plaintext', 'http'), ('plaintext', 'https')],
'https': [('kerberos', 'https'), ('plaintext', 'https')],
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.has_pipelining = False self.has_pipelining = False
@ -74,35 +69,53 @@ class Connection(ConnectionBase):
''' used to identify this connection object from other classes ''' ''' used to identify this connection object from other classes '''
return 'winrm' return 'winrm'
def _winrm_connect(self): def set_host_overrides(self, host):
''' '''
Establish a WinRM connection over HTTP/HTTPS. Override WinRM-specific options from host variables.
''' '''
port = self._play_context.port or 5986 host_vars = host.get_vars()
self._display.vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \
(self._play_context.remote_user, port, self._play_context.remote_addr), host=self._play_context.remote_addr)
netloc = '%s:%d' % (self._play_context.remote_addr, port)
exc = None
for transport, scheme in self.transport_schemes['http' if port == 5985 else 'https']:
if transport == 'kerberos' and (not HAVE_KERBEROS or not '@' in self._play_context.remote_user):
continue
if transport == 'kerberos': self._winrm_host = host_vars.get('ansible_winrm_host', self._play_context.remote_addr)
realm = self._play_context.remote_user.split('@', 1)[1].strip() or None self._winrm_port = int(host_vars.get('ansible_winrm_port', self._play_context.port or 5986))
else: self._winrm_scheme = host_vars.get('ansible_winrm_scheme', 'http' if self._winrm_port == 5985 else 'https')
realm = None self._winrm_path = host_vars.get('ansible_winrm_path', '/wsman')
self._winrm_user = host_vars.get('ansible_winrm_user', self._play_context.remote_user)
self._winrm_pass = host_vars.get('ansible_winrm_pass', self._play_context.password)
if '@' in self._winrm_user:
self._winrm_realm = self._winrm_user.split('@', 1)[1].strip() or None
else:
self._winrm_realm = None
self._winrm_realm = host_vars.get('ansible_winrm_realm', self._winrm_realm) or None
endpoint = parse.urlunsplit((scheme, netloc, '/wsman', '', '')) if HAVE_KERBEROS and ('@' in self._winrm_user or self._winrm_realm):
self._winrm_transport = 'kerberos,plaintext'
else:
self._winrm_transport = 'plaintext'
self._winrm_transport = host_vars.get('ansible_winrm_transport', self._winrm_transport)
if isinstance(self._winrm_transport, basestring):
self._winrm_transport = [x.strip() for x in self._winrm_transport.split(',') if x.strip()]
self._display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._play_context.remote_addr) self._winrm_kwargs = dict(username=self._winrm_user, password=self._winrm_pass, realm=self._winrm_realm)
protocol = Protocol( argspec = inspect.getargspec(Protocol.__init__)
endpoint, for arg in argspec.args:
transport=transport, if arg in ('self', 'endpoint', 'transport', 'username', 'password', 'realm'):
username=self._play_context.remote_user, continue
password=self._play_context.password, if 'ansible_winrm_%s' % arg in host_vars:
realm=realm self._winrm_kwargs[arg] = host_vars['ansible_winrm_%s' % arg]
)
def _winrm_connect(self):
'''
Establish a WinRM connection over HTTP/HTTPS.
'''
self._display.vvv("ESTABLISH WINRM CONNECTION FOR USER: %s on PORT %s TO %s" % \
(self._winrm_user, self._winrm_port, self._winrm_host), host=self._winrm_host)
netloc = '%s:%d' % (self._winrm_host, self._winrm_port)
endpoint = urlparse.urlunsplit((self._winrm_scheme, netloc, self._winrm_path, '', ''))
exc = 'No transport found for WinRM connection'
for transport in self._winrm_transport:
self._display.vvvvv('WINRM CONNECT: transport=%s endpoint=%s' % (transport, endpoint), host=self._winrm_host)
protocol = Protocol(endpoint, transport=transport, **self._winrm_kwargs)
try: try:
protocol.send_message('') protocol.send_message('')
return protocol return protocol
@ -117,16 +130,16 @@ class Connection(ConnectionBase):
raise AnsibleError("the username/password specified for this server was incorrect") raise AnsibleError("the username/password specified for this server was incorrect")
elif code == 411: elif code == 411:
return protocol return protocol
self._display.vvvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self._play_context.remote_addr) self._display.vvvvv('WINRM CONNECTION ERROR: %s' % err_msg, host=self._winrm_host)
continue continue
if exc: if exc:
raise AnsibleError(str(exc)) raise AnsibleError(str(exc))
def _winrm_exec(self, command, args=(), from_exec=False): def _winrm_exec(self, command, args=(), from_exec=False):
if from_exec: if from_exec:
self._display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._play_context.remote_addr) self._display.vvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host)
else: else:
self._display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._play_context.remote_addr) self._display.vvvvvv("WINRM EXEC %r %r" % (command, args), host=self._winrm_host)
if not self.protocol: if not self.protocol:
self.protocol = self._winrm_connect() self.protocol = self._winrm_connect()
if not self.shell_id: if not self.shell_id:
@ -136,11 +149,11 @@ class Connection(ConnectionBase):
command_id = self.protocol.run_command(self.shell_id, to_bytes(command), map(to_bytes, args)) command_id = self.protocol.run_command(self.shell_id, to_bytes(command), map(to_bytes, args))
response = Response(self.protocol.get_command_output(self.shell_id, command_id)) response = Response(self.protocol.get_command_output(self.shell_id, command_id))
if from_exec: if from_exec:
self._display.vvvvv('WINRM RESULT %r' % to_unicode(response), host=self._play_context.remote_addr) self._display.vvvvv('WINRM RESULT %r' % to_unicode(response), host=self._winrm_host)
else: else:
self._display.vvvvv('WINRM RESULT %r' % to_unicode(response), host=self._play_context.remote_addr) self._display.vvvvvv('WINRM RESULT %r' % to_unicode(response), host=self._winrm_host)
self._display.vvvvvv('WINRM STDOUT %s' % to_unicode(response.std_out), host=self._play_context.remote_addr) self._display.vvvvvv('WINRM STDOUT %s' % to_unicode(response.std_out), host=self._winrm_host)
self._display.vvvvvv('WINRM STDERR %s' % to_unicode(response.std_err), host=self._play_context.remote_addr) self._display.vvvvvv('WINRM STDERR %s' % to_unicode(response.std_err), host=self._winrm_host)
return response return response
finally: finally:
if command_id: if command_id:
@ -171,9 +184,9 @@ class Connection(ConnectionBase):
if '-EncodedCommand' in cmd_parts: if '-EncodedCommand' in cmd_parts:
encoded_cmd = cmd_parts[cmd_parts.index('-EncodedCommand') + 1] encoded_cmd = cmd_parts[cmd_parts.index('-EncodedCommand') + 1]
decoded_cmd = to_unicode(base64.b64decode(encoded_cmd).decode('utf-16-le')) decoded_cmd = to_unicode(base64.b64decode(encoded_cmd).decode('utf-16-le'))
self._display.vvv("EXEC %s" % decoded_cmd, host=self._play_context.remote_addr) self._display.vvv("EXEC %s" % decoded_cmd, host=self._winrm_host)
else: else:
self._display.vvv("EXEC %s" % cmd, host=self._play_context.remote_addr) self._display.vvv("EXEC %s" % cmd, host=self._winrm_host)
try: try:
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:], from_exec=True)
except Exception as e: except Exception as e:
@ -186,7 +199,7 @@ class Connection(ConnectionBase):
def put_file(self, in_path, out_path): def put_file(self, in_path, out_path):
super(Connection, self).put_file(in_path, out_path) super(Connection, self).put_file(in_path, out_path)
out_path = self._shell._unquote(out_path) out_path = self._shell._unquote(out_path)
self._display.vvv('PUT "%s" TO "%s"' % (in_path, out_path), host=self._play_context.remote_addr) self._display.vvv('PUT "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
if not os.path.exists(in_path): if not os.path.exists(in_path):
raise AnsibleFileNotFound('file or module does not exist: "%s"' % in_path) raise AnsibleFileNotFound('file or module does not exist: "%s"' % in_path)
with open(in_path) as in_file: with open(in_path) as in_file:
@ -214,7 +227,7 @@ class Connection(ConnectionBase):
out_path = out_path + '.ps1' out_path = out_path + '.ps1'
b64_data = base64.b64encode(out_data) b64_data = base64.b64encode(out_data)
script = script_template % (self._shell._escape(out_path), offset, b64_data, in_size) script = script_template % (self._shell._escape(out_path), offset, b64_data, in_size)
self._display.vvvvv('WINRM PUT "%s" to "%s" (offset=%d size=%d)' % (in_path, out_path, offset, len(out_data)), host=self._play_context.remote_addr) self._display.vvvvv('WINRM PUT "%s" to "%s" (offset=%d size=%d)' % (in_path, out_path, offset, len(out_data)), host=self._winrm_host)
cmd_parts = self._shell._encode_script(script, as_list=True) cmd_parts = self._shell._encode_script(script, as_list=True)
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
if result.status_code != 0: if result.status_code != 0:
@ -227,7 +240,7 @@ class Connection(ConnectionBase):
super(Connection, self).fetch_file(in_path, out_path) super(Connection, self).fetch_file(in_path, out_path)
in_path = self._shell._unquote(in_path) in_path = self._shell._unquote(in_path)
out_path = out_path.replace('\\', '/') out_path = out_path.replace('\\', '/')
self._display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._play_context.remote_addr) self._display.vvv('FETCH "%s" TO "%s"' % (in_path, out_path), host=self._winrm_host)
buffer_size = 2**19 # 0.5MB chunks buffer_size = 2**19 # 0.5MB chunks
makedirs_safe(os.path.dirname(out_path)) makedirs_safe(os.path.dirname(out_path))
out_file = None out_file = None
@ -256,7 +269,7 @@ class Connection(ConnectionBase):
Exit 1; Exit 1;
} }
''' % dict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset) ''' % dict(buffer_size=buffer_size, path=self._shell._escape(in_path), offset=offset)
self._display.vvvvv('WINRM FETCH "%s" to "%s" (offset=%d)' % (in_path, out_path, offset), host=self._play_context.remote_addr) self._display.vvvvv('WINRM FETCH "%s" to "%s" (offset=%d)' % (in_path, out_path, offset), host=self._winrm_host)
cmd_parts = self._shell._encode_script(script, as_list=True) cmd_parts = self._shell._encode_script(script, as_list=True)
result = self._winrm_exec(cmd_parts[0], cmd_parts[1:]) result = self._winrm_exec(cmd_parts[0], cmd_parts[1:])
if result.status_code != 0: if result.status_code != 0:

Loading…
Cancel
Save