Ansible: Managing a Windows host using Ansible

Ansible is an agentless configuration management tool that helps operations teams manage installation, patching, and command execution across a set of servers.

Ansible was started as a Linux only solution, leveraging ssh to provide a management channel to a target server.  However, starting at Ansible 1.7, support for Windows hosts was added by using Powershell remoting over WinRM.

Installation

I ran into several issues while trying to use the Kerberos/CredSSP Python libraries,  and the instructions provided in my article here address crypto libraries and other dependencies that were necessary to get CredSSP/Kerberos support for Ansible under Ubuntu 14.04.

Follow the instructions in the “Installation” and “Configuration” sections.

Test remoting from a Windows host

Exploring all the nuances of enabling remote WinRM and Powershell for a guest OS is far beyond the scope of this article.  In this article, we’ll assume the Windows hosts are all domain joined and that we are connecting as either a Domain Administrator or regular domain user that is a member of the local Administrator group of the target host.

The official Ansible Windows documentation provides a ConfigureRemotingForAnsible.ps1 script that can be used to setup a target Windows host for WinRM and here are some other helpful links for enabling remote WinRM access [1,2,3,4,5,6,7,8,9,10,11,12,13].  Here are links for remote Powershell access [1,2,3,4,5,6,7,8].

After you have proven WinRM/Powershell connectivity to yourself from one Windows guest to the target guest OS you want to control via Ansible, use the following commands as a smoke test of remote WinRM connectivity.

# execute these from a Windows client to the target host
$ winrm identify -u:myuser -p:Mypass123! -r:http://targetHost:5985

$ winrs -u:myuser -p:Mypass123! -r:http://targetHost:5985 "dir c:\"

Then use the Powershell commands below to validate Powershell remoting using CredSSP and Kerberos credentials.  The “$user” below should either be a Domain Administrator or a member of the local Administrator group on the target host.

> powershell -executionpolicy bypass

# setup parameters
PS> $computerName = "targetHost"
PS> $user = "myuser"
PS> $pass = "Mypass123!"

# encrypt password into Credentials object
PS> $securePassword = ConvertTo-SecureString $pass -AsPlainText -force
PS> $cred = New-Object System.Management.Automation.PSCredential($user,$securePassword)

#########
# test using CredSSP authentication
PS> Enter-PSSession -ComputerName $computerName -Authentication CredSSP -Credential $cred

# now commands are executed against remote host
[targetHost]: PS> dir c:\
# exit remote session
[targetHost]: PS> exit


#########
# test using Kerberos authentication
PS> Enter-PSSession -ComputerName $computerName -Authentication Kerberos -Credential $cred

# now commands are executed against remote host
[targetHost]: PS> dir c:\
# exit remote session
[targetHost]: PS> exit

NOTE: That without this basic connectivity proven, it is pointless to move on to testing the same principles from a Linux host.

Test remoting from the Ansible host

Now that we’ve seen WinRM/Powershell remoting work from a Windows host, let’s move on to doing the same thing from our Linux based Ansible server.

These are the additional packages and modules I found necessary for Ubuntu 14.04.

# user prompted for REALM name and KDC for Kerberos
$ sudo apt-get install python-dev libkrb5-dev krb5-user

# python WinRM module
$ sudo pip install pyOpenSSL --upgrade
$ sudo pip install "pywinrm>=0.2.2"

# ignore warnings about maj_stat
$ sudo pip install kerberos
# Kerberos and CredSSP
$ sudo pip install "pywinrm[kerberos]"
$ sudo pip install "pywinrm[credssp]"
$ sudo pip install "requests-credssp" "requests-kerberos"

You’ll need to modify “/etc/krb5.conf” in three different sections: libdefaults, realms, and domain_realm to point to the correct domain and kdc.

[libdefaults]
default_realm = MYDOMAIN.COM

[realms]
 MYDOMAIN.COM = {
 kdc = mydc.mydomain.com
 admin_server = mydc.mydomain.com
 }

[domain_realm]
 .mydomain.com = MYDOMAIN.COM
 mydomain.com = MYDOMAIN.COM

Now test Kerberos authentication using the OS level Kerberos utilities.  Below I am getting a Kerberos TGT using ‘tester1’, who can either be a Domain Administrator or a regular domain user who is a member of the local Administrators group of the target host.

# login
$ kinit tester1@MYDOMAIN.COM
Password for tester1@MYDOMAIN.COM:

# list credentials
$ klist

# destroy credentials
$ kdestroy

It is important that DNS name resolution as well as reverse IP resolution works properly from Ansible host to the target Windows host.

We can also check the availability of WinRM on the target host using curl:

# get xmllint for pretty print of SOAP response
$ sudo apt-get install libxml2-utils -y

# replace 'targetHost' with the target Windows host
$ curl --header "Content-Type: application/soap+xml;charset=UTF-8" --header "WSMANIDENTIFY: unauthenticated" http://targetHost:5985/wsman --data '<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd"><s:Header/><s:Body><wsmid:Identify/></s:Body></s:Envelope>' | xmllint --format -


<?xml version="1.0"?>
<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope">
 <s:Header/>
 <s:Body>
 <wsmid:IdentifyResponse xmlns:wsmid="http://schemas.dmtf.org/wbem/wsman/identity/1/wsmanidentity.xsd">
 <wsmid:ProtocolVersion>http://schemas.dmtf.org/wbem/wsman/1/wsman.xsd</wsmid:ProtocolVersion>
 <wsmid:ProductVendor>Microsoft Corporation</wsmid:ProductVendor>
 <wsmid:ProductVersion>OS: 0.0.0 SP: 0.0 Stack: 3.0</wsmid:ProductVersion>
 </wsmid:IdentifyResponse>
 </s:Body>
</s:Envelope>

Configure Ansible

With basic Kerberos and WinRM connectivity proven out, now let’s allow Ansible to use the pyWinRM module to make the remote connection.

First, add the Windows host to the inventory file in the ‘windows’ host group, being sure to use the FQDN:

# ~/ansible/hosts

[windows]
targetHost.mydomain.com

Then create variables for ‘windows’ group under “~/ansible/group_vars/windows.yaml”:

ansible_user: 'tester1@MYDOMAIN.COM'
ansible_password: 'Mypass123!'
ansible_winrm_transport: kerberos

ansible_port: '5985'
ansible_connection: 'winrm'

ansible_winrm_server_cert_validation: ignore
validate_certs: false

And then invoke a call using the remote WinRM channel, authenticating with Kerberos.

$ ansible targethost.mydomain.com -m win_ping

targethost.mydomain.com | SUCCESS => {
 "changed": false, 
 "ping": "pong"
}

Then, by changing a single line in “group_vars/windows.yaml”, we can instead use CredSSP for authentication:

ansible_winrm_transport: credssp

The result of invoking the win_ping module again will now provide the exact same output, but since we enabled verbose logging, it is clear from the log messages that CredSSP was used.

$ ansible targethost.mydomain.com -m win_ping -vvvvv

Ansible Windows Modules

Ansible has numerous modules for working with Windows OS.  You are already familiar with win_ping from the section above.

Here is an example of getting a directory listing of the c:\ drive using win_command:

$ ansible targethost.mydomain.com -m win_command -a "cmd.exe /c dir c:\\"

Here is how you pull the facts about the target host:

$ ansible targethost.mydomain.com -m setup

Execute a powershell file on the remote Windows host by first creating a powershell file named “hello.ps1” containing

param($who="world")
write-host "hello ${who} from powershell"

And then a playbook named “testps.yaml”

---
- hosts: windows
  tasks:
    - 
      name: exeute hello powershell
      script: hello.ps1 universe
      register: out

    - debug: var=out.stdout_lines

When executed using ‘ansible-playbook’, the results will look like:

$ ansible-playbook testps.yaml

PLAY [windows] *******************************************************************

TASK [Gathering Facts] ***********************************************************
ok: [targethost.mydomain.com]

TASK [exeute hello powershell] ***************************************************
changed: [targethost.mydomain.com]

TASK [debug] *********************************************************************
ok: [targethost.mydomain.com] => {
    "out.stdout_lines": [
        "hello universe from powershell"
    ]
}

PLAY RECAP ***********************************************************************
targethost.mydomain.com      : ok=3    changed=1    unreachable=0    failed=0 

 

 

 

REFERENCES

https://technet.microsoft.com/en-us/library/hh921475(v=ws.11).aspx

https://technet.microsoft.com/en-us/library/ff700227.aspx

https://support.microsoft.com/en-us/help/555966

https://www.infrasightlabs.com/how-to-enable-winrm-on-windows-servers-clients

https://docs.saltstack.com/en/latest/topics/cloud/windows.html

http://www.hurryupandwait.io/blog/understanding-and-troubleshooting-winrm-connection-and-authentication-a-thrill-seekers-guide-to-adventure

http://linux.last-bastion.net/infocentre/how-to/winrm

https://www.howtogeek.com/117192/how-to-run-powershell-commands-on-remote-computers/

https://www.howtogeek.com/138624/geek-school-learn-to-use-remoting-in-powershell/

https://msdn.microsoft.com/en-us/powershell/reference/5.1/microsoft.powershell.core/enable-psremoting

http://techgenix.com/remote-management-powershell-part1/

http://www.computerperformance.co.uk/powershell/powershell_remote.htm

https://blogs.msdn.microsoft.com/powershell/2015/10/27/compromising-yourself-with-winrms-allowunencrypted-true/

https://msdn.microsoft.com/en-us/library/aa384372(v=vs.85).aspx

https://github.com/ansible/ansible/issues/16478

https://github.com/rundeck-plugins/rundeck-winrm-plugin/wiki/Windows-Server-Setup

http://www.diegoluisi.eti.br/17/06/2015/rundeck-how-to-add-windows-node/

http://www.diegoluisi.eti.br/17/06/2015/rundeck-how-to-add-windows-node/

https://www.sevecek.com/Lists/Posts/Post.aspx?ID=280 (WinRM for non-administrative users using WMI namespace security)

https://pubs.vmware.com/orchestrator-plugins/index.jsp?topic=%2Fcom.vmware.using.powershell.plugin.doc_10%2FGUID-D4ACA4EF-D018-448A-866A-DECDDA5CC3C1.html (winrm)

https://github.com/diyan/pywinrm/issues/84 (winrm https)

https://github.com/diyan/pywinrm/issues/68

https://stackoverflow.com/questions/11153692/wsman-and-basic-authorization (BASIC winrm only works for local accounts, not domain)

https://github.com/ansible/ansible/issues/12324

https://github.com/ansible/ansible/blob/devel/examples/scripts/ConfigureRemotingForAnsible.ps1

https://github.com/mitmproxy/mitmproxy/issues/705 (no attribute TLSv1_2_METHOD)

http://stevenmurawski.com/powershell/2015/06/need-to-test-if-winrm-is-listening/ (test winrm using curl)

https://github.com/ansible/ansible/issues/3887 (realtime output from shell)

https://hanbaobao2005.wordpress.com/2016/11/30/try-ansible-on-windows/

https://www.tecmint.com/join-ubuntu-to-active-directory-domain-member-samba-winbind/

https://help.ubuntu.com/lts/serverguide/kerberos.html

https://github.com/ansible/ansible/issues/17311

https://serverfault.com/questions/537060/how-to-see-stdout-of-ansible-commands (stdout callback minimal)

http://hakunin.com/six-ansible-practices

https://github.com/ansible/ansible/issues/15973

atarget1 | UNREACHABLE! => {
“changed”: false,
“msg”: “plaintext: ‘Session’ object has no attribute ‘merge_environment_settings'”,
“unreachable”: true
}

https://stackoverflow.com/questions/5315714/whats-the-difference-between-usr-local-lib-python2-6-and-usr-lib-python2-6

 

find / -name sessions.py

/usr/lib/python2.7/dist-packages/requests/sessions.py (apt-get install, old requests)
/usr/local/lib/python2.7/dist-packages/requests/sessions.py (pip install)

apt-get purge python-requests

 

 

WinRM client check

winrm quickconfig

winrm e winrm/config/listener

winrm get winrm/config/service/auth

winrm get winrm/config/service