Contact Us
Automating PCI Infrastructure Hardening with Ansible – Part 1 – Prerequisites

Estimated time: 30 Minutes

Ok, if you didn't read my amazing, non-AI generated intro, then that makes me sad. But if you're here for technical guidance on how to configure this puppy, then you've come to the right starting point.

I am sad if you came directly here, but I also understand.

Before we get started, there's a few assumptions I am making about the readers of this. You're employed by a company, or doing this in your own private environment, and in both cases you have permission from the owner of your infrastructure to do this. You'll Also need room for at least 1 Linux VM on your virtualization platform, or physical server. If you want me to write an article on how to setup both, let me know on linkedin! But to Summarize:

  • Room on your cloud/virtualization/physical servers for a Linux VM.
  • Internet connection.
  • Administrative access to your windows and linux environments.
  • An account that has admin permissions on your windows environments.
  • Access to your Domain Controllers
  • SSH Client (Windows)
  • WinSCP (Optional, but recommended)
  • VSCodium (Optional, but recommended)
  • Your choice of chillout music (AVB for me!).
If you're going to build, build in style.

Step 1 - Configure Ansible on Linux

Installing and Configuring Ansible

Ok, for the sake of this guide, I am going to presume you can get a VM setup to the point you have a Blank Linux box that has just been deployed. I am using Fedora Server for this walk through, but if you want to use another distro, it's up to you. Make sure you adapt the commands accordingly, as path's and Syntax can very between distro's.

First, lets make sure your VM is up-to-date:

#input your sudo credentials
sudo dnf -y update && sudo dnf -y upgrade
#reboot your linux vm to ensure the updates didn't break anything
sudo reboot

Ok, Now that you have a Linux VM Patched and ready, lets get all of the pre-requisites you'll need for the ansible install

sudo dnf -y install nano ansible python3-pip krb5-workstation krb5-libs python3-kerberos 
pip3 install pywinrm
ansible-galaxy collection install ansible.windows

Then, create the ansible user

sudo useradd --system --create-home --shell /bin/bash ansible

Now, we'll take care of some housecleaning to make sure logging and permissions are set.

sudo touch /var/log/ansible.log
# Creates the log file if it doesn’t exist.
sudo chown ansible:ansible /var/log/ansible.log
# Assigns ownership to ansible.
sudo chmod 640 /var/log/ansible.log
# Restricts access 
sudo chown -R ansible:ansible /etc/ansible
#Ensures Ansible owns its config directory.
sudo chmod -R 750 /etc/ansible
#Restricts access (750 → ansible can read/write, group can read, others denied).


sudo chown -R ansible:ansible /etc/ansible
sudo chmod -R 750 /etc/ansible
sudo chown ansible:ansible /var/log/ansible.log
sudo chmod 640 /var/log/ansible.log

#To avoid sudo every time, add yourself to the ansible group:

sudo usermod -aG ansible username

#Ensure your account can write to the ansible log file
sudo touch /var/log/ansible.log
sudo chown username:username /var/log/ansible.log
sudo chmod 644 /var/log/ansible.log

#Restart to ensure everything is fresh and ready
sudo reboot

Ok, That's most of the system pre-work out of the way, now lets get the configuration done.

Open your Ansible.cof file at /etc/ansible/ansible.cfg and paste in the following

[defaults]
inventory = /etc/ansible/hosts
log_path = /var/log/ansible.log
forks = 5
remote_user = ansible
retry_files_enabled = False
host_key_checking = True
timeout = 30

[privilege_escalation]
become = True
become_method = sudo

[ssh_connection]
pipelining = True

[winrm]
transport = kerberos
kerberos_delegation = true

Now, this one is a bit tricky, you need to modify your Kerberos config file located at /etc/krb5.conf with the information related to your domain controllers. Here's an example file:

sudo nano /etc/krb5.conf


Replace its contents with:

# To opt out of the system crypto-policies configuration of krb5, remove the
# symlink at /etc/krb5.conf.d/crypto-policies which will not be recreated.
includedir /etc/krb5.conf.d/

[logging]
    default = FILE:/var/log/krb5libs.log
    kdc = FILE:/var/log/krb5kdc.log
    admin_server = FILE:/var/log/kadmind.log

[libdefaults]
    default_realm = DOMAIN.COM
    dns_lookup_realm = false
    dns_lookup_kdc = true
    ticket_lifetime = 24h
    renew_lifetime = 7d
    forwardable = true
    rdns = false
    pkinit_anchors = FILE:/etc/pki/tls/certs/ca-bundle.crt

[realms]
    ONS.COM = {
        kdc = DC01.DOMAIN.COM
        kdc = DC02.DOMAIN.COM
        admin_server = DC01.DOMAIN.COM.COM
    }

[domain_realm]
    .domain.com = DOMAIN.COM
    domain.com = DOMAIN.COM

[logging]
    default = FILE:/var/log/krb5libs.log
    kdc = FILE:/var/log/krb5kdc.log
    admin_server = FILE:/var/log/kadmind.log

There's a few items here, but here are the key points:

  • default_realm = Your Domain Name.
  • kdc = Your Domain Controller's FQDN.
    • If possible, add more than one kdc so ansible can talk to multiple DC's.
  • The entries in the config file are capitalized, this will be important soon.
  • Adjust ticket lifetimes so something more suitable to your environment.

Ok, that's it for ansible config! If everything is working, you should be able to grab a kerberos ticket using the kinit command:

kinit username@DOMAIN.LOCAL
#The Domain Case must match your krb5.conf file, so in this case, you need to use an Uppercase DOMAIN NAME

Test your response using klist, and you should see the kerberos ticket, meaning you're good to move onto Step 2!

Step 2 - Configuring Windows to work with Ansible

1. Prepare the Windows Server (Domain Member)

Before Ansible can manage the Windows server, you must enable and configure WinRM (Windows Remote Management).

1.1.1: Enable WinRM Service Without HTTP Listener

Set-Service WinRM -StartupType Manual

Start-Service WinRM

Use the Following Script to setup all the requirements needed on the windows domain computer:

# ================================
# Secure WinRM Setup for Ansible
# ================================

# Set the IP address of the Ansible control node (Modify this)
$ansibleIP = "1.2.3.4"

# Define a domain user to be added to the local Administrators group (Modify this)
$domainUser = "DOMAIN\UserName"  # Change DOMAIN\UserName to the actual domain and username

# Start and configure the WinRM service
Write-Host "Starting WinRM service and setting it to start automatically..."
Set-Service -Name WinRM -StartupType Automatic
Start-Service -Name WinRM

# Get the Hostname and Domain Name
$hostname = $env:COMPUTERNAME
$domain = (Get-WmiObject Win32_ComputerSystem).Domain

# Construct the Fully Qualified Domain Name (FQDN)
$fqdn = "$hostname.$domain"

# Generate a Self-Signed Certificate for WinRM HTTPS
$cert = New-SelfSignedCertificate -DnsName $fqdn -CertStoreLocation Cert:\LocalMachine\My

# Move the certificate to the Trusted Root Certification Authorities store
$certPath = "Cert:\LocalMachine\My\$($cert.Thumbprint)"
$destStore = New-Object System.Security.Cryptography.X509Certificates.X509Store "Root", "LocalMachine"
$destStore.Open("ReadWrite")
$destStore.Add($cert)
$destStore.Close()

# Retrieve the Certificate Thumbprint
$thumbprint = (Get-ChildItem -Path Cert:\LocalMachine\My | Where-Object { $_.DnsNameList -contains $fqdn }).Thumbprint

# Remove any existing HTTPS and HTTP listeners (if any)
$existingListeners = winrm enumerate winrm/config/listener | Out-String
if ($existingListeners -match "Transport = HTTPS") {
    winrm delete winrm/config/listener?Address=*+Transport=HTTPS
}
if ($existingListeners -match "Transport = HTTP") {
    winrm delete winrm/config/listener?Address=*+Transport=HTTP
}

# Create a Secure WinRM Listener with HTTPS
$winrmCommand = 'winrm create winrm/config/Listener?Address=*+Transport=HTTPS "@{Hostname=""' + $fqdn + '""; CertificateThumbprint=""' + $thumbprint + '""}"'
Invoke-Expression $winrmCommand

# Disable Unencrypted Communication and Basic Authentication
Set-Item -Path WSMan:\localhost\Service\AllowUnencrypted -Value $false
Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $false

# Remove any existing firewall rule for WinRM HTTPS
if (Get-NetFirewallRule -DisplayName "Allow WinRM HTTPS from Ansible" -ErrorAction SilentlyContinue) {
    Remove-NetFirewallRule -DisplayName "Allow WinRM HTTPS from Ansible"
}

# Allow Secure WinRM Traffic Only from the Ansible Server
New-NetFirewallRule -DisplayName "Allow WinRM HTTPS from Ansible" -Direction Inbound -Action Allow -Protocol TCP -LocalPort 5986 -RemoteAddress $ansibleIP

# Add the specified domain user to the local Administrators group
if ($domainUser -ne "") {
    Write-Host "Adding $domainUser to the local Administrators group..."
    try {
        Add-LocalGroupMember -Group "Administrators" -Member $domainUser -ErrorAction Stop
        Write-Host "Successfully added $domainUser to the Administrators group."
    } catch {
        Write-Host "Failed to add $domainUser to Administrators group: $_"
    }
}

Verify Remote Connection from Ansible

Go back to your Fedora Ansible node, and Create a new Hosts file:

#Path /etc/ansible/kerberos_hosts
[windows]
FQDN.DOMAIN.COM

[windows:vars]
ansible_become=False
ansible_user=username
ansible_password=ivegotalittleplaceinaspen
ansible_connection=winrm
ansible_winrm_transport=kerberos
ansible_winrm_server_cert_validation=ignore

Test Connectivity

Run from your Ansible control node:

ansible -m win_ping -i "your_windows_server," all -e ansible_connection=winrm -e ansible_winrm_transport=https

Expected Output:your_windows_server | SUCCESS => { "changed": false, "ping": "pong"}

Congratulations, You've got Ansible talking to your Domain Computer via Kerberos! Stay tuned for Part 2, where I show you how to start hardening your domain computers!