I recently setup Ansible to communicate with a Windows host. Unlike NIX-based hosts (Linux/Unix), which use SSH by default, Windows hosts are not a good fit for SSH configuration with Ansible. As per the Ansible documentation, “use this (SSH with Windows) feature at your own risk! Using SSH with Windows is experimental, the implementation may make backwards incompatible changes in feature releases. The server side components can be unreliable depending on the version that is installed”. So instead, I choose the WinRM setup. Here’s how I did it.
What Is WinRM
WinRM (Windows Remote Management) is a management protocol used by Windows to remotely communicate with another server. It is a SOAP-based protocol that communicates over HTTP/HTTPS, and is included in all recent Windows operating systems. Since Windows Server 2012, WinRM has been enabled by default, but in most cases extra configuration is required to use WinRM with Ansible.
Certificate Authentication
When connecting to a Windows host, there are several different options that can be used when authenticating with an account. The preferred authentication method used for this demo is certificate based authentication.
Certificate authentication uses certificates as keys similar to SSH key pairs, but the file format and key generation process is different.
Setup WinRM
Install Python 3 on Ansible Host
As you already know, there are two Python versions that are being actively developed. While Python 2 is well-supported and active, Python 3 is considered to be the present and future of the language.
By default RHEL/CentOS 8 doesn’t have an unversioned system-wide python command to avoid locking the users to a specific version of Python. Instead, it gives the user a choice to install, configure, and run a specific Python version. The system tools such as yum use an internal Python binary and libraries.
To install Python 3 on CentOS 8 run the following command:
# dnf install python3
Install PIP on Ansible Host
Pip is Python’s package manager, which is also comes pre-installed, but again, in case Pip is missing on your system, install it using the command. Note, to install the pip version that matches python version.
# dnf install python3-pip
Install Ansible Automation Tool
With all the prerequisites met, install ansible by running the command on CentOS 8.
# pip3 install ansible –user
# ansible --version
To test ansible, first ensure that ssh is up and running
# systemctl status sshd
Next, we need to create the hosts file in the /etc/ansible directory to define host machines.
# mkdir /etc/ansible
# cd /etc/ansible
# sudo touch hosts
Install pywinrm
Ansible uses the pywinrm package to communicate with Windows servers over WinRM. It is not installed by default with the Ansible package. Use the command below to install pywinrm. Note, on distributions with multiple python versions, use pip2 or pip2.x, where x matches the python minor version Ansible is running under.
# pip3 install pywinrm
Enable Certificate Authentication On Windows
Certificate authentication is not enabled by default on a Windows host but can be enabled by running the following in PowerShell.
PS> Set-Item -Path WSMan:\localhost\Service\Auth\Certificate -Value $true
Generate Certificate on Windows
A certificate must be generated before it can be mapped to a local user. This can be done using various methods. The preferred method used for this demo is to use PowerShell (using the “New-SelfSignedCertificate” cmdlet) to generate a certificate. This approach only works when being generated from a Windows 10 or Windows Server 2012 R2 host or later.
Copy the code below into a PowerShell script called generate-cert.ps1, then execute the script:
# Set the name of the local user that will have the key mapped
$username = "username"
$output_path = "C:\temp"
# Instead of generating a file, the cert will be added to the personal
# LocalComputer folder in the certificate store
$cert = New-SelfSignedCertificate -Type Custom `
-Subject "CN=$username" `
-TextExtension @("2.5.29.37={text}1.3.6.1.5.5.7.3.2","2.5.29.17={text}upn=$username@localhost") `
-KeyUsage DigitalSignature,KeyEncipherment `
-KeyAlgorithm RSA `
-KeyLength 2048
# Export the public key
$pem_output = @()
$pem_output += "-----BEGIN CERTIFICATE-----"
$pem_output += [System.Convert]::ToBase64String($cert.RawData) -replace ".{64}", "$&`n"
$pem_output += "-----END CERTIFICATE-----"
[System.IO.File]::WriteAllLines("$output_path\cert.pem", $pem_output)
# Export the private key in a PFX file
[System.IO.File]::WriteAllBytes("$output_path\cert.pfx", $cert.Export("Pfx"))
Convert PFX File on Windows
- The public key should’ve been exported as C:\temp\cert.pem
- The private key is a PFX file exported as C:\temp\cert.pfx
- To convert the PFX file to a private key that pywinrm can use, run the following command with OpenSSL
OpenSSL> pkcs12 -in cert.pfx -nocerts -nodes -out cert_key.pem -passin pass: -passout pass:
Import to Certificate Store on Windows
Once a certificate has been generated to C:\temp, the issuing certificate needs to be imported into the Trusted Root Certificate Authorities of the LocalMachine store, and the client certificate public key must be present in the Trusted People folder of the LocalMachine store. For this example, both the issuing certificate and public key are the same. Simply copy and past the code below into a PowerShell script called import-cert.ps1, then execute the script.
$cert = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Certificate2
$cert.Import("cert.pem")
$store_name = [System.Security.Cryptography.X509Certificates.StoreName]::Root
$store_location = [System.Security.Cryptography.X509Certificates.StoreLocation]::LocalMachine
$store = New-Object -TypeName System.Security.Cryptography.X509Certificates.X509Store -
ArgumentList $store_name, $store_location
$store.Open("MaxAllowed")
$store.Add($cert)
$store.Close()
Map Certificate to Account on Windows
Once the certificate has been imported, map it to the local user account by simply copying the code below into a PowerShell script called map-cert.ps1, then execute the script. Note, change the “username” and “password” in code below:
$username = "Put Your Username"
$password = ConvertTo-SecureString -String "Put Your Password" -AsPlainText -Force
$credential = New-Object -TypeName System.Management.Automation.PSCredential -ArgumentList
$username, $password
# This is the issuer thumbprint which in the case of a self generated cert
# is the public key thumbprint, additional logic may be required for other
# scenarios
$thumbprint = (Get-ChildItem -Path cert:\LocalMachine\root | Where-Object { $_.Subject -eq
"CN=$username" }).Thumbprint
New-Item -Path WSMan:\localhost\ClientCertificate `
-Subject "$username@localhost" `
-URI * `
-Issuer $thumbprint `
-Credential $credential `
-Force
Copy Public and Private Key
Copy the following keys from Windows target to Ansible Controller
- Publick Key = C:\temp\cert.pem
- Private Key = C:\temp\cert_key.pem
Update Ansible Inventory
For this demo, the public and private keys were copied to /root. The following example shows host vars configured in /etc/ansible/hosts for certificate authentication:
[win]
34.239.161.196
[win:vars]
ansible_connection=winrm
ansible_winrm_cert_pem=/root/cert.pem
ansible_winrm_cert_key_pem=/root/cert_key.pem
ansible_winrm_transport=certificate
ansible_winrm_server_cert_validation=ignore
Note, I’ve added “ansible_winrm_server_cert_validation=ignore” because there seems to be some issues with the current version of python that I’m using. I will update this blog once I get to the bottom of this.