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.

Readiness of Linux server side

I ran into several issues while trying to use the Kerberos/CredSSP Python libraries if installing Ansible on Linux according to the vanilla instructions.

The instructions provided in my article here in the “Installation” and “Configuration” sections will create the proper dependencies that were necessary to get CredSSP/Kerberos support for Ansible under Ubuntu 14.04.

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].

Use the following commands as a smoke test of remote WinRM connectivity from one Windows host to your target Windows host.

# 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:\"

As a final validation between these Windows peers, 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 Windows-to-Windows 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 working between two Windows hosts, 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 -

Which produces output which looks like:

<?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