# Ansible ❖ Authenticating against Windows

Making Ansible and WinRM play nice

# Backstory

Managing a Windows environment at a relatively small scale usually means a lot of manually crafted virtual machines. It’s just the nature of the job. The infrastructure doesn’t frequently change, time is stretched and budgets moreso.

When every change is manual and the VMs have been in production for a long time, it becomes extremely difficult to ascertain the ‘desired’ state of a server. So how do we know when configuration has drifted enough to cause an issue?

So I set out to find a tool that will help me manage the our servers - and more - with ease, at an increasing scale. Enter Ansible.

## Configuration management with Ansible

‘Configuration management’ (CM) is a pretty broad term, but it boils down to looking at the entire infrastructure (or, at least the parts we want to manage) and thinking of the pieces by their ‘configuration’. Whether that’s the services that run, the user accounts that have access to them, the disks attached, etc. - It’s all configured somehow.

A CM tool such as Ansible provides us with a level of abstraction to help manage this mess, so instead of cobbling together scripts and hand tweaking parts of the system, we provide the information that details the desired state to our CM tool and it does the dirty work to ‘make it so’.

But with Group Policy, SCCM and PowerShell (remoting & DSC) - Why throw another tool into the mix?

• Imperative design.
Instead of writing a tonne of logic, we just use one of the many resources available (or write our own) and tell Ansible what we want. And because it is designed to be idempotent, we get the same results after every run.

• Instant results.
I want to be able to run ad-hoc commands or entire configurations on multiple hosts and get feedback now. Anyone who’s dealt with Group Policy & SCCM knows that you have to play the waiting game, and that makes iterating and improving upon results tedious!

• Cross-platform & scalable.
Ansible in particular is agentless, and therefore can manage Windows, macOS, Linux, network devices (switches/routers), Azure, AWS, … You get the picture.

• The paradigm of ‘infrastructure as code’.
Having human-readable playbooks (YAML) mean that all configurations are easy to grok straight away, version control, iterate upon, share, etc.

• Frankly I was fed up of pressing the same buttons over and over again, and wrestling with the verbose nature of the Microsoft offerings (eg. SCCM). While they’re still greatly powerful, I’d rather utilize them for what they excel at.

# Setup

I'm going to assume you know the fundamentals of Ansible. If you don't, you can pick it up in about 10 minutes by reading the docs!

Let’s get Ansible to wrangle our Windows boxes then.

First things first, we must ensure our VMs are running PowerShell v3.0 or higher. This is already available on Server 2012 onwards, but for older Windows Server operating systems, it can be installed with the WMF3. I was fortunate to only have a few pre-Server 2012 R2 machines, and a small script to install the update with PowerShell remoting got all of my machines up to snuff.

Next is configuring WinRM as our connection protocol, since Windows boxes don’t run SSH (by default). The docs provide a handy script to get up and running, which I pushed out via. SCCM with a bit of detection logic (here if you are interested)

This script from Ansible simply creates a self-signed HTTPS cert for secure WinRM communication and configures the WinRM service & endpoint ready for connection.

You must specify ansible_winrm_server_cert_validation=ignore if you use a self-signed certificate (such as in the above method). If you have a PKI, you can always use those certs!

And finally, don’t forget that our Ansible control machine will need the pywinrm module, which is just a pip install pywinrm away!

# Authenticating from our Ansible machine

Now that we can communicate with our Windows hosts, we need a way to actually authenticate.

We could use a local user account to authenticate with - and this is fine for dev boxes that might be discarded - but if we’re going to manage our production environment, more often that not we want to authenticate against Active Directory.

The official documentation mentions binding the control machine to the AD domain, and while this may be something to consider if this is your permanent Ansible control machine, it is possible to get around this. This is especially handy as it means we’re not tied down to a single machine.

## Option 1: Using NTLM

A relatively newer addition to Ansible - such much so as it’s still not in the docs! - is the option to use NTLM as the authentication method. This is great as it means we can simply pass a username (local or domain) and password, unlike Kerberos which requires a Kerberos ticket.

[general]
windowshost1

[general:vars]
ansible_user=domainuser@MYDOMAIN.LOCAL
ansible_port=5986
ansible_connection=winrm
ansible_winrm_transport=ntlm
ansible_winrm_server_cert_validation=ignore


Now, test it with a simple ansible general -m win_ping.

## Option 2: Using Kerberos

Using Kerberos as the authentication protocol has some benefits but also some caveats. Firstly, we have the potential annoyance of having to request a Kerberos ticket manually if we’re authenticating against domain as an outsider. But on the other hand, another relatively new feature makes it all worth it - Kerberos delegation.

This defeats a hurdle that is apparent in ntlm authentication (and even in general PowerShell remoting if not configured properly), wherein the initial authentication is not delegated to the ‘next hop’.

So, if we were to connect to our target windows box, and then from here needed to connect to another server (eg. to retrieve files from a network fileshare), the intial authentication is not passed on and will fail.

               authenticates              cannot auth!
ansiblecontrol ------------> windows box  ------------> fileserver


## Prerequisites

Firstly, we need to install python-Kerberos and its dependencies. Since my Ansible hosts are usually Ubuntu, it’s just a sudo apt-get install python-dev libkrb5-dev krb5-user && pip pip install Kerberos requests_Kerberos away.

Next is configuring the /etc/krb5.conf file for the domain(s) we’re going to authenticate against. For example, with two domains:

[libdefaults]
default_realm = MYDOMAIN.LOCAL
forwardable = true
proxiable = true

[realms]
MYDOMAIN.LOCAL = {
kdc = dc01.mydomain.local
kdc = dc02.mydomain.local
}

ANOTHERDOMAIN.LOCAL = {
kdc = dc01.anotherdomain.local
kdc = dc02.anotherdomain.local
}

[domain_realm]
.domain.local = DOMAIN.LOCAL
domain.local = DOMAIN.LOCAL

.anotherdomain.local = ANOTHERDOMAIN.LOCAL
anotherdomain.local = ANOTHERDOMAIN.LOCAL


It is important you respect the capitalisation in the example! Kerberos is very picky about the case, so ensure that copy the case of the sections above when supplying your domain(s).

Next, edit your host vars to switch the winrm transport to Kerberos and enable Kerberos delegation:

ansible_winrm_transport=Kerberos
ansible_winrm_Kerberos_delegation=yes


## Manually requesting a Kerberos ticket

We must request a Kerberos ticket for one of the domains in our krb5.conf. We do this with the kinit command, for example kinit domainuser@MYDOMAIN.LOCAL. You can even omit the domain name if you specified a default_realm.

As always, ‘silence is golden’, so no output mean it worked! Confirm with klist.

ansible@ansiblecontrol:/etc/ansible# klist
Ticket cache: FILE:/tmp/krb5cc_0
Default principal: mydomainuser@MYDOMAIN.LOCAL

Valid starting     Expires            Service principal
02/09/17 22:23:55  02/10/17 08:23:55  krbtgt/MYDOMAIN.LOCAL@MYDOMAIN.LOCAL
renew until 02/10/17 22:23:49


Once we have a Kerberos ticket, it should last for 24hours, and we can only acquire a ticket for a single principal (user)in each domain at a time.

## Automation!

Manually requesting a ticket is tedious though, so we can avoid this either by adding it to a cronjob to run every 24 hours, or my personal favorite, create a playbook that does this, and just include it at the start of our other playbooks. And of course, this lets us add it to our source control :thumbsup:

---
- name: Get Kerberos ticket
hosts: localhost
ansible_connection: local
gather_facts: no
vars:
- name: Run kinit


But this does rely on us using something like ansible-vault to encrypt the credentials.

Additionally, using this method allows us to remove the ansible_password=mypassword123! from our host vars, but we still need to make sure the name matches the principal in our acquired ticket.

## Using a keytab for authentication

Instead of recording usernames & passwords, we can save our credentials to a keytab file instead. This allows us to simply run kinit against it whenever we need to authenticate, and is especially handy as we can distrbute this without giving out a password (it is hashed inside the keytab). We can even save multiple credentials into the same keytab!

First, generate the keytab.

ansible@ansiblecontrol:/etc/ansible# ktutil
ktutil:  wkt keytabs/<mykeytabname>.keytab
ktutil:  quit


Now, we can authentication against it with kinit domainuser@MYDOMAIN.LOCAL -k -t /etc/ansible/keytabs/<mykeytabname>.keytab. Simple enough!

And if we wanted this in a playbook:

---
- name: Get Kerberos ticket
hosts: localhost
ansible_connection: local
gather_facts: no
vars:
keytab_path: /etc/ansible/keystabs/<mykeytabname>.keytab