WinRM+Ansible

Jussi Saine // January 17 2018

A blog text from one of my dear colleague’s trip to AnsibleFest caught my eye the other day. She stated that one of the curiosities was Ansible’s Matt Davis’ presentation about managing windows hosts with Ansible. I’ve been playing with Windows and Ansible myself a bit so I decided it might be worthwhile to share some basics about that.

I’m going to go through how to set up WinRM connection and how the server can be managed from a linux server with Ansible after that. I’m also going to describe how to replace the password based authentication with client certificate authentication. I will do most of the configurations with Ansible to show some examples of the possible usage when Ansible is enabled. As a disclaimer, the examples I’m going to show will be pretty simplified and while all commands are tested to work when running against a fresh windows installation, they may fail if you try to run them several times. In any case you should get an idea of how to use Ansible against Windows server and what benefits it might give.

A couple of limitations that you should be aware of:

• Client certificate authentication can only be bound to a local user. If domain users are needed, a Kerberos authentication is the way to go.
• Windows 2008 R2 servers a powershell update is probably required as Ansible only supports Powershell 3.0 and above is worth mentioning.

I will be using a Centos 7 based server as management server, but I’ll add corresponding commands for Ubuntu 16.04 as well if they differ from Centos.

Starting with basic Windows server setup

At start we will need to log in to the server with Remote desktop to be able to do the basic configuration for WinRM, but once that is done the fun with Ansible can begin. Note that there is a ready made script for enabling Winrm for Ansible, but I will go through the required steps as I do think it is good to know what it actually requires.

We will start by enabling WinRM:

Enable-PSRemoting

This will actually run command Set-WSManQuickConfig and prompt you for a few questions. You may use -Force -parameter for this command to just enable without the questions. You can verify the setup with command:

winrm enumerate winrm/config/listener

As you can see by default WinRM is enabled without TLS on port 5985 and while the traffic is actually encrypted in this port as well, client certificate authentication is not supported on this port. Using port 5985 from Linux requires also enabling basic authentication and allowing unencrypted traffic (we don’t want that), so it’s best to enable TLS.

Building trust with certificates

For the purpose of this document I will be using self signed certificates. CA creation is a bit out of scope for this document so for information about creating your own CA, you may refer to for example this article or with Windows this article.

First we need to import the CA certificate to the windows server (unless that has been done already), copy the CA cert to the server and run:

certutil -addstore “CA” c:\path\to\ca.pem

certutil -addstore “Root” c:\path\to\ca.pem

This will allow the Windows server to trust certificates signed by your CA

The certificate for the WinRM server needs be similar to any other server side certificate, so it needs to have Server Authentication -extension on. If you already have a server certificate available for your server, you may use that and skip this part.

You can use your own templates for creating the certificate request, but in case you don’t have any on hand, here is an example for the request:

Change <server FQDN> parts of the above template to match the Fully Qualified Domain Name of your server and save the request. For more information about certificate requests, refer to this technet article. You may also use GUI for the certificate request if you wish, but for consistency I will only be describing command line approach. Please note the FriendlyName in the template above, I will be using it to find the certificate later on, so if you use some other method for creating the cert, please add similar friendly name for that.

Certificate handling needs to happen with administrative permissions, so open a powershell terminal with ‘Run as administrator’ option and run:

certreq -new <path to request template> <path to the request file>

e.g.

certreq -new c:\template.txt c:\users\administrator\documents\certificate.pem

This will create file certificate_request.csr under Administrator-user’s documents-directory. After this, get the certificate request (for example copy and paste the base64 encoded text) to your CA and sign the cert. When you have the signed certificate, copy it to the server and accept the certificate:

certreq -accept <path to signed certificate file>

e.g.

certreq -accept c:\users\administrator\documents\certificate.pem

Now we have the certificate installed, so TLS can be enabled for WinRM. First we need to check the certificate thumbprint for the certificate we requested (note the friendly name here):

Get-ChildItem -Path cert:\LocalMachine\My|ForEach-Object -Process {if($_.FriendlyName -eq ‘Certificate for WinRM’){$_.Thumbprint}}

Copy the Thumbprint and issue command:

New-Item WSMan:\localhost\listener -Address * -Transport HTTPS -CertificateThumbprint <AddThumbHere>

There will be once again a “Do you want to continue” -prompt, which you can disable with -Force parameter.

We can now see that we have WinRM listening in two ports now:

winrm enumerate winrm/config/listener

Configuring some firewall magic

The server is listening to both ports 5985 and 5986 (HTTP and HTTPS), but by default Enable-PSRemoting opens only the port 5985 from Windows firewall, so we’ll need to open 5986 as well:

netsh advfirewall firewall add rule name="Windows Remote Management (HTTPS-In)" dir=in action=allow protocol=TCP localport=5986 remoteip=<management server ip>

Replace the <management server ip> with your management server IP. It is possible to open access from anywhere instead of one IP only, but I strongly recommend limiting access if possible. Also as we won’t be needing the non TLS port 5985, let’s close that from firewall:

netsh advfirewall firewall delete rule name="Windows Remote Management (HTTP-In)"

It’s time to try the connection out, let’s to move to the Linux server from which management is going to be done!

Continuing with Python and Ansible in management server

If you are already using Ansible for other tasks, you probably have basic Ansible setup in place on your server, but I’ll walk you through for the necessary parts starting with installing Ansible and after that continuing to winrm support

Installing and configuring Ansible

First make sure that ansible is installed, for Centos Ansible is available from EPEL repository.

Centos:

yum install -y epel-release

yum install -y ansible libselinux-python

Ubuntu:

apt-get install ansible

Now create a group-vars file for the windows-host, edit /etc/ansible/group_vars/windows.yml (create directory if it does not exist) and add:

With basic ansible setup in place we still need to install pywinrm to enable WinRM support.

Installing pywinrm

Pywinrm is also available from EPEL, package named python2-winrm, but the package can be installed with Python pip as well as described on the pywinrm site. At the time of writing, EPEL only provides package for Python 2 only, so if and when Python 3 is needed, pip is the only option. For this document I will be using Python 2 and the library from EPEL:

yum install -y python2-winrm

On Ubuntu 16.04 both python 2 and python 3 versions of the library can be found from repository and can be installed respectively:

# for python 2

apt-get install python-winrm

# for python 3

apt-get install python3-winrm

Before running a quick test, let’s import CA certificate to the server’s certificate store to prevent any certificate errors when connecting. On CentOS/RHEL 7 the procedure would be:

Install ca-certificates and enable dynamic configuration of CA certs:

sudo yum install ca-certificates

sudo update-ca-trust force-enable

copy ca.pem to /etc/pki/ca-trust/source/anchors/ and add it to trusted CA’s

sudo update-ca-trust extract

On Ubuntu copy ca.pem to /usr/local/share/ca-certificates and run command:

sudo update-ca-certificates

Now we can try to run a quick test according to example from the pywinrm site, start a python prompt with:

python

And write following:

import winrm

s = winrm.Session('https://windows-host:5986′,auth=(‘administrator’,’Passw0rd!’),transport=’ntlm')

r = s.run_cmd('ipconfig')

print r.std_out

To exit the prompt use:

quit() or ctrl+d

But ok, we’re not here to play with python code, so let’s try it with Ansible as well:

ansible -m win_command -a "ipconfig" -u administrator –ask-pass windows

Note that the ‘windows’ -part should match the header line entered to Ansible configuration file.

You should once again see your ipconfig but output is a bit different from that of the python script:

Client certificate authentication

Now Ansible works, but we still need to input passwords, so let’s continue to client certificate part.

Create certificate request

openssl req -newkey rsa:4096 -keyform PEM -keyout winrm-admin.key.enc -out winrm-admin.csr -outform PEM

This command will ask a few questions of which common name will be most important. Make sure you add winrm-admin to that. Other parts you may fill in as you wish. You may also enter a dot (.) as value for the entry to leave it completely empty.

Sign the certificate with the same CA as the certificate for winrm HTTPS, but note that you should use template that has the clientAuth allowed as extended/intended usage.

When you have the certificate signed, copy that to the windows server (note double backslashes in dest):

ansible -m win_copy -a ‘src=”./winrm-admin.crt” dest=”c:\\users\\administrator\\documents\\winrm-admin.crt”‘ –ask-pass -u administrator windows

Next import the certificate to local keystore:

ansible -m win_command -a ‘certutil -addstore “My” c:\users\administrator\documents\winrm-admin.crt’ -u ‘administrator’ –ask-pass windows

While certificate can be added for normal administrator user as well, I prefer to create a management specific user for this. As the user creation and binding client certificate requires using a couple of variables let’s try a different feature of Ansible, let’s create a script by using a Jinja template! Create a file called create-user.j2 with following content (you don’t need to substitute anything in here by hand):

Now the playbook for the user creation, user-create-playbook.yml (substitute <path to your CA cert> and <Subject from your client or *> with proper values):

This playbook gets thumbprint from CA certificate, then creates an actual powershell script from the jinja template. The password -lookup used for password -variable will automatically create a random password and it saves the generated string to password-file.txt -file in /tmp/password-file.txt. You may replace that with custom password string if you wish. When it has created an actual script file, it will run that script on the remote server and remove the created script file.

Run the script:

ansible-playbook –ask-pass user-create-playbook.yml

Final testing part

Now we’re ready to test the connection with certificate authentication from the management server. Once again, let’s start with Python script to get a bit better error messages if some error occurs. But let’s remove password from our key first:

openssl rsa -in winrm-admin.key.enc -out winrm-admin.key

We’ll need to use the low level API as basic pywinrm session does not accept certificate as authentication method currently (note that this is assumed to be run at the same directory you have your certificate and key, fix paths accordingly if it is not):

python

from winrm.protocol import Protocol

p = Protocol(

endpoint='https://windows-host:5986/wsman',

transport='certificate',

username='winrm-admin',

cert_pem='winrm-admin.crt',

cert_key_pem='winrm-admin.key')

shell_id = p.open_shell()

command_id = p.run_command(shell_id, 'ipconfig')

std_out, std_err, status_code = p.get_command_output(shell_id, command_id)

print std_out

p.cleanup_command(shell_id, command_id)

p.close_shell(shell_id)

Ipconfig should once again emerge on your screen:

Finally it’s time to test access with certificates using Ansible, let’s check what version of windows we’re running for a change. Create a playbook windows-host.yml with following content:

now let’s run the playbook:

ansible-playbook windows-host.yml

This should output something similar to this:

Congratulations, you now have a working setup to manage your windows servers!

Conclusions

In this document I’ve shown you a few ways how to benefit of the use of ansible by running single commands, copying files from your local machine to your Windows server and running your own scripts against windows servers. For me these are pretty much the core reasons why I like to manage my servers with Ansible. I can easily run my custom scripts against a bunch of servers, I can check available updates from the servers, enable windows services, pretty much anything I have ever needed and all this from the soothing environment of my Linux box! But the possibilities of how to use Ansible with Windows do not end here, so make sure you check the documentation and see if you can find the modules you love the most?

By Jussi Saine, IT & DevOps Specialist, Digitalist