I have some Ansible playbooks I want to run against some Windows hosts. I've followed the various Ansible guides for setting up WinRM and they have worked fine, but the default setups are very insecure and I want something more production ready. However, the instructions for how to do this are incredibly sparse. So far I've done the following:
On my Windows box:
- Enabled WinRM using the supplied ConfigureRemotingForAnsible.ps1 script
- Configured target machine to to use a valid cert on HTTPS/5986 rather than the self-signed one generated by the above script
- Enabled both Kerberos and CredSSP auth methods on the target machine in WinRM. Some of my role steps require CredSSP to work reliably.
So far so good, the Windows side seems to work fine. However, getting Ansible to connect is proving a nightmare. I can't figure out how to get Ansible to trust the HTTPS cert on the target despite adding it. On my Centos 7 'push box' I've done the following:
- Install Ansible and pip with the pywinrm, requests_kerberos and requests_credssp modules
- Added my CA certificate to both /etc/pki/tls/certs and /etc/pki/ca-cert
- Set my inventory to the following:
ansible_user=ADMINISTRATOR@DOMAIN.COM
ansible_password=Password1
ansible_port=5986
ansible_connection=winrm
ansible_winrm_scheme=https
/#ansible_winrm_server_cert_validation=ignore
ansible_winrm_transport=credssp
With certification validation turned on the connection fails with the following error:
fatal: [host.domain.com]:
UNREACHABLE! => {
"changed": false,
"msg": "credssp: HTTPSConnectionPool(host='host.domain.com', port=5986): Max retries exceeded with url: /wsman (Caused by SSLError(SSLError(\"bad handshake: Error([('SSL routines', 'tls_process_server_certificate', 'certificate verify failed')],)\",),))",
"unreachable": true}
With cert validation turned off it works fine.
So my question: How do I get Ansible to trust my CA cert?
Best Answer
This isn't simple. The problem is there are 3 different groups that don't really want to play:
First, you will need 3 sets of OpenSSL certs/keys: the self-signed Certificate Authority (CA), the server winRM HTTPS, and the target user.
There is nothing magic about the CA cert/key; it can be generated with the following:
There are a myriad of other options to these OpenSSL commands which may (or not) work in your specific environment. This is one of those instances of "don't want to be responsible for OpenSSL".
Second, you will need the WinRM HTTPS cert/key. First, you have to generate the server cert. The key detail of generating the cert is that the 'common name' attribute on the cert MUST match the target hostname, as with any HTTPS cert.
Now, here comes the tricky part. You can't just sign the key as-is; the signing process must add a couple of additional attributes to the cert or it won't work. Create a text file I'll call 'attributes.txt' with the following two lines:
The first line, the 'subjectAltName', is required or the Python/OpenSSL client will reject the key. You need to substitute the proper values for the target hostname. The second line is required by Windows, or Windows will not use this as for an HTTPS server.
Now sign the key with the CA generated earlier:
Another little wrinkle: Windows will not import a server key unless you combine the key and cert into a PKCS#12 file. This can be done with the following command, which creates a PKCS#12 file with no password:
This will generate a file 'windows.pfx' which we'll need later.
Bored yet?
Next we need another key/cert for the user login. This is assuming that we're going to be using a local user on the system, not a domain user. (You're on your own for that.) Generating the key is almost the same as for the server key, except that the 'commom name' attribute (CN) MUST match the target username. (In later usage, I'll call our user 'ansible').
This private key is going to be the login key, analogous to an SSH private key. Next, we must sign the req. As with the server key, we need to add some extended attributes, or it won't work. Our 'attributes.txt' file must contain the following lines:
The oddball identifier in the 'altNames' is some proprietary Microsoft thingy, which must contain the 'username@localhost'. As I mentioned before, I'm using a local user 'ansible'. The extendedKeyUsage attribute is required or Windows will not allow the key to be used for user authentication. Finally, we sign the cert:
Just so you know, I'm starting to get on my own nerves here. We're almost there. Finally, copy the CA cert (caroot.pem), the server PKCS#12 cert/key (windows.pfx), and the user cert (winlogin.pem) to the target windows system somewhere. Run the following PowerShell script from the same directory. It will create the local ansible user and import all of the SSL artifacts into their proper target locations. Sorry if this isn't commercial quality code. This also has a hardcoded password for the ansible user, but that is irrelevant and can be freely changed. If this actually runs successfully, and you survive the shock, the file artifacts (the PFX and PEM files) can be removed from the Windows server.
Also note that this script adds a firewall rule at the end. This, of course, will have to be appropriately modified for your environment.
Okay, now that Windows is done, we should be ready for Ansible. Within your inventory, you will need the following variables to apply to the Windows systems:
At this point, Ansible should connect to the Windows server using HTTPS and certificate authentication. I hope I hit all of the steps, it was a while ago I set this up.