Ansible Windows
Running Ansible on Windows requires WinRM to be configured, and it requires one of several different authentication options. In real environments this is typically done with HTTPS certs, but since there is no Matt-Cloud Infosec, I can just use a username and password. Furthermore, since I don't have a secure password vault, I have done some creative stuff with the registry to get this done. Group policy used to allow for creating a local account with a specific password, but that feature has been depreciated. I have used Group Policy to have a user account be created, and then a startup script will run cosmosrm.ps1 to set the password based on a registry key.
cosmosrm.ps1
# script for setting ansible service account to registry key
$username = "cosmos-ansible"
$ansible_registry = "HKLM:\SOFTWARE\Cosmos\Ansible"
$password_key = "Password"
$password = (Get-ItemProperty $ansible_registry).$password_key
# This is what the thing needs to set the password
$securePassword = ConvertTo-SecureString $password -AsPlainText -Force
# Set password
$UserAccount = Get-LocalUser -Name $username
$UserAccount | Set-LocalUser -Password $securePassword
# Make it a local admin
Add-LocalGroupMember -Group "Administrators" -Member $username
# Various Ansible Settings
Set-Item -Path WSMan:\localhost\Service\Auth\Basic -Value $true
Enable-WSManCredSSP -Role Server -Force
The same password is in Jenkins, and the dynamic inventory file generator script adds the password to the inventory file. Like I said, not super secure, but it works.
inventory.sh
#!/bin/bash
# Dynamic inventory generation script ansible windows
# Function to display usage
usage() {
echo "Windows Ansible Dynamic Inventory File Generation Script"
echo "Usage: $0 -i IP_LIST -u JENKINS_USER -g JENKINS_GROUP -w WINDOWS_USER -p ANSIBLE_PASSWORD [-a SERVER_SUBNET_GROUP] [-s] [-v] [-e]"
echo "Options:"
echo " -i IP_LIST Comma-separated list of IPs. Will not fail if blank, but why 0_o"
echo " -u JENKINS_USER Jenkins user"
echo " -g JENKINS_GROUP Jenkins primary group"
echo " -a SERVER_SUBNET_GROUP Jenkins group for SSH access, need to pass something when called"
echo " -w WINDOWS_USER Windows user"
echo " -p ANSIBLE_PASSWORD Password for the service account (Windows user)"
echo " -q Be quieter"
echo " -s Set variable to true if more than one IP is passed"
echo " -v Display Ansible Version"
exit 1
}
# Initialize variables with default values
skip=false
more_than_one=false
display_version=false
allsubnet_group=missing
be_quiet=false
# Parse command line options
while getopts ":i:u:w:p:g:a:svq" opt; do
case ${opt} in
i ) # process option i
IP_LIST=$OPTARG
;;
u ) # process option u
JENKINS_USER=$OPTARG
;;
w ) # process option w
WINDOWS_USER=$OPTARG
;;
p ) # process option p
ANSIBLE_PASSWORD=$OPTARG
;;
g ) # process option g
JENKINS_GROUP=$OPTARG
;;
s ) # process option s
skip=true
;;
v ) # process option v
display_version=true
;;
q ) # process option q
be_quiet=true
;;
a ) # process option a
allsubnet_group=$OPTARG
;;
\? ) usage
;;
esac
done
shift $((OPTIND -1))
# Check if all required options are provided
if [ -z "$JENKINS_USER" ] || [ -z "$JENKINS_GROUP" ] || [ -z "$WINDOWS_USER" ] || [ -z "$ANSIBLE_PASSWORD" ]; then
usage
fi
if $display_version; then
if ! $be_quiet; then
echo "Showing ansible version"
ansible --version
fi
fi
# Generate an 8-character hash from the IP list
hash=$(echo -n "$IP_LIST" | md5sum | cut -c 1-8)
if ! $be_quiet; then
echo "IP List:"
echo $IP_LIST
echo $hash
fi
# Define the inventory file path with the hash
inventory_file="/var/jenkins_home/ansible-windows/.inv/inventory-$hash.yml"
if $skip; then
IFS=',' read -ra IPS <<< "$IP_LIST"
if [ ${#IPS[@]} -gt 1 ]; then
more_than_one=true
fi
fi
if $skip; then
if ! $be_quiet; then
echo "Single host option set"
fi
if $more_than_one; then
if ! $be_quiet; then
echo "IP list provided, inventory will be emptied"
fi
IP_LIST=""
fi
fi
# Initialize the YAML inventory content
inventory_content="---
all:
hosts:
"
# Loop through each IP in the comma-separated list
# skip if restricted user and subnet
IFS=',' read -ra IPS <<< "$IP_LIST"
for IP in "${IPS[@]}"; do
ip_check=$(curl -s http://172.25.100.15:15010/ip_check?ip=${IP} | jq .in_subnets)
# if this is a restricted subnet, then check the group
if $ip_check; then
if ! $be_quiet; then
echo "Subnet restricted, checking group membership"
fi
if [ "$allsubnet_group" == "$SERVER_SUBNET_GROUP" ]; then
if ! $be_quiet; then
echo "IP Check Passed, adding endpoint ${IP} to inventory"
fi
inventory_content+=" ${IP}:
ansible_host: ${IP}
"
else
if ! $be_quiet; then
echo "Warning: User ${JENKINS_USER} not member of ${SERVER_SUBNET_GROUP}!"
echo "Auth Check Failed for endpoint ${IP}, not adding to inventory"
fi
fi
# if the subnet is not restricted, just add the endpoint to the inventory
else
if ! $be_quiet; then
echo "Unrestricted subnet, adding endpoint ${IP} to inventory"
fi
inventory_content+=" ${IP}:
ansible_host: ${IP}
"
fi
done
inventory_content+=" vars:
# windows user info
ansible_user: ${WINDOWS_USER}
ansible_password: '${ANSIBLE_PASSWORD}'
ansible_become_user: ${WINDOWS_USER}
ansible_become_pass: '${ANSIBLE_PASSWORD}'
# ansible connection info
ansible_connection: winrm
ansible_winrm_transport: basic
ansible_winrm_server_cert_validation: ignore
ansible_winrm_scheme: http
ansible_winrm_port: 5985
# jenkins user info
jenkins_user: '${JENKINS_USER}'
jenkins_group: '${JENKINS_GROUP}'
subnet_group_check: '${allsubnet_group}'
SERVER_SUBNET_GROUP: '${SERVER_SUBNET_GROUP}'
# other variables
ansible_python_interpreter: /usr/bin/python3
"
# Write the inventory content to the file
echo "$inventory_content" > $inventory_file
# secure inventory file
if ! $be_quiet; then
echo "Securing inventory file"
fi
chmod 700 $inventory_file
# echo inventory
if ! $be_quiet; then
echo "Inventory file created at $inventory_file with the following content:"
cat $inventory_file
fi
I want to set up Ansible with Windows, but that requires a lot of new hoops to jump through. To this end, I am setting up a secure way to set up a local account with a password using group policy. I'll write up an actual guide later, but I think I'm on to something.
I have this working, and as much as I want to get a guide going right now, it's too late so I will instead go to sleep.