Installation on Centos 8
sudo dnf makecache
sudo dnf install -y epel-releasesudo dnf makecache
sudo dnf install -y ansible
Resource: https://linuxhint.com/install_ansible_centos8
Build Control Node
I used an Ubuntu 20.04 instance for this. While I do provide installation instructions for Red Hat, everything is focused around Ubuntu. If you want to use another OS, you’ll just need to change the commands for installing and the username (ubuntu).
Create SSH key for management
ssh-keygen -b 4096 -f ~/.ssh/control_node -N ''
Copy SSH key to managed node(s)
If you want to use SCP, you could use the following function
(don’t forget to change your management key from mgmt_key.pem
to whatever you call it, or omit the -i ~/.ssh/mgmt_key.pem
if
you’re using passwords for SSH):
# Used to add the pub key to a managed node
add_pub_key() {
managed_node=$1
scp -i ~/.ssh/mgmt_key.pem ~/.ssh/control_node.pub ubuntu@$managed_node:
ssh -i ~/.ssh/mgmt_key.pem ubuntu@$managed_node \
'cat ~/control_node.pub | tee -a ~/.ssh/authorized_keys && rm ~/control_node.pub'
}
add_pub_key "your_ip_address_goes_here"
Install Ansible on Control Node
sudo apt-get update && sudo apt-get install -y ansible
Install Ansible on Red Hat, Fedora, or CentOS
dnf -y install https://dl.fedoraproject.org/pub/epel/epel-release-latest-8.noarch.rpm
dnf -y install --enablerepo epel-playground ansible
Inventory File
Add any managed nodes to /etc/ansible/hosts
. If you’re just
managing one node, you could do something like this:
[dev_machines]
<managed node ip> ansible_user=ubuntu ansible_ssh_private_key_file=/home/ubuntu/.ssh/mgmt_key.pem
Config File
Your config file should at a minimum point to your inventory like so:
[defaults]
inventory = /etc/ansible/hosts
Test connectivity to managed nodes
Run this command to ensure that you’re able to authenticate to your managed nodes:
ansible all -m ping
Resources:
Install ansible lint
# Ubuntu
sudo apt-get upgrade -y
sudo apt-get install -y ansible-lint
# Mac OS
brew install ansible-lint
Resource: https://zoomadmin.com/HowToInstall/UbuntuPackage/ansible-lint
CWD
- name: Go to the folder and execute command
command: chdir=/opt/tools/temp ls
Add apt repo
This will create docker.list
in /etc/apt/sources.list.d/
and
updates the apt repo to reflect this change:
- name: Configure docker apt repo
apt_repository:
repo: "deb https://download.docker.com/linux/debian stretch stable"
state: present
filename: "docker"
update_cache: "yes"
become: true
Multi line string
This creates a variable called custom_vim_plugins
and assigns it a multi-line string after the |
custom_vim_plugins: |
[[custom_plugins]]
name = 'hashivim/vim-terraform'
merged = 0
Create a directory
- name: Creates directory
file:
path: /src/www
state: directory
Resource: https://stackoverflow.com/questions/22844905/how-to-create-a-directory-using-ansible
Find filename and assign to var
- name: "Find path to freddy jar"
find:
paths:"{{ ansible_env.HOME }}/burpExtensions/freddy_deserialization_bug_finder/"
patterns: "*.jar"
recurse: "yes"
file_type: file
register: freddy_path
Running playbooks
Run locally
ansible-playbook site.yml -l localhost -vvv --user=<user> --ask-pass
Specify run locally in playbook
---
- name: A playbook
hosts: localhost
connection: local
become: yes
roles:
- role: your_role
Resource: https://www.middlewareinventory.com/blog/run-ansible-playbook-locally/
Run against a particular group of servers
ansible-playbook site.yml -l web -vvv
ansible hosts file
:
[web]
host.domain ansible_user=ubuntu ansible_ssh_private_key_file=~/.ssh/id_rsa.pem
Set python interpreter for host
If your host has multiple versions of python and you want to specify a particular one to use:
[kali]
host.domain.com ansible_user=kali ansible_ssh_private_key_file=~/.ssh/id_rsa.pem
ansible_python_interpreter=/usr/bin/python3
Resource: https://stackoverflow.com/questions/59380824/how-to-choose-a-python-interpreter-for-ansible-playbook
Set python interpreter as an extra argument
ansible-playbook -vvv site.yml -e "ansible_python_interpreter=/usr/bin/python3"
Resource: https://github.com/cisagov/kali-packer/blob/develop/src/packer.json
Specify hosts file location
In this example, we will use a hosts file found in the same directory as site.yml
:
ansible-playbook site.yml -i hosts
Test playbook without making changes
ansible-playbook site.yml -C -vvv
Best practices coding guide
https://docs.ansible.com/ansible/2.5/user_guide/playbooks_best_practices.html
Ansible Galaxy
Install galaxy roles to specific directory
Add this to your ansible.cfg
:
roles_path = roles.galaxy:roles
This will install all ansible galaxy roles in a directory called roles.galaxy
You can keep local roles in the roles
folder.
Resource: https://stackoverflow.com/questions/22201306/ansible-galaxy-roles-install-in-to-a-specific-directory
Collections
Create new collection
Ansible Collections is a way of packaging and distributing playbooks, roles, modules, and plugins.
Here is a guide on how you can start creating an Ansible Collection:
Install Ansible: Make sure you have installed the latest version of Ansible. You can install Ansible using pip:
python3 -m pip install ansible
Create a Collection: Use the ansible-galaxy command-line tool which comes with Ansible, to create a new collection structure:
ansible-galaxy collection init namespace.collection_name
Replace namespace
with your own namespace, and collection_name
with your
desired collection name. This command will create a new directory
structure like this:
namespace/
└── collection_name/
├── docs/
├── galaxy.yml
├── plugins/
│ ├── modules/
│ ├── inventory/
│ └── ...
├── README.md
└── roles/
docs/
: Local documentation for the collection.galaxy.yml
: The file contains all the metadata about the collection (author, support level, tags, etc.).plugins/
: Directory for Ansible plugins.README.md
: A README file containing information about the collection.roles/
: Directory for Ansible roles.
Move or Copy Your Roles and Playbooks: Now you can start moving your existing roles and playbooks into the new collection structure. Make sure you maintain the recommended structure of roles within Ansible.
Update Collection Metadata: Edit the galaxy.yml file to reflect the metadata of your collection. It is important because it provides information about the collection when publishing or sharing.
Build the Collection: Use the following command to build your collection:
ansible-galaxy collection build
This command will create a .tar.gz file which can be uploaded to Ansible Galaxy.
Publish the Collection: If you wish to share your collection, you can upload it to Ansible Galaxy with the following command:
ansible-galaxy collection publish ./namespace-collection_name-1.0.0.tar.gz
Install collections roles to specific directory
Add this to your ansible.cfg
:
collections_paths = roles.collections:roles
This will install all ansible galaxy roles in a directory called roles.collections
.
Your requirements.yml
will probably look somewhat like this if you add in collections:
# Install Docker
roles:
- name: geerlingguy.docker
collections:
- name: community.docker
source: https://galaxy.ansible.com
You can trigger installing any collections listed with:
ansible-galaxy collection install -r requirements.yml
Resources:
Collections best practicesansible-best-practices-using-project-local-collections-and-roles)
Molecule
Create role
This will generate the folders and files associated with a new role. Newer versions of molecule:
ROLE_NAME=l50.mysweetrole
molecule init role "${ROLE_NAME}"
Older versions of molecule:
molecule init role --role-name "${ROLE_NAME}"
Create test environment
molecule create
List test environments
molecule list
Run test
molecule converge
# Verbose output:
molecule -vvv converge
Destroy test environment
molecule destroy
Test format
There are a lot of ways to skin this cat. I appreciate the format that geerlingguy uses in his projects. It’s simple and gets the job done:
molecule
└── default
├── converge.yml
└── molecule.yml
The verification piece happens in the post_tasks
section of converge.yml
:
post_tasks:
- name: Test that package is installed
ansible.builtin.stat:
# Read from `vars/main.yml`:
path: "{{ package_path }}"
become: true
Resource: https://github.com/geerlingguy/ansible-role-postgresql
Fix unallowed value docker
To fix this error:
ERROR: Failed to pre-validate.
{'driver': [{'name': ['unallowed value docker']}]}
run this command:
pip3 install molecule-docker
Resource: https://github.com/ansible-community/molecule/issues/2891
Fix idempotency failures
Add the following to a block of code that’s failing the molecule idempotency check:
changed_when: false
Ignore ssh auth checking
There is a security sacrifice to keep in mind with doing this.
That being said, add these lines to the ansible.cfg
:
[defaults]
host_key_checking = False
Resource: https://stackoverflow.com/questions/32297456/how-to-ignore-ansible-ssh-authenticity-checking
Vault
Set env var for CI
This will cause Ansible to automatically to search for the
password specified in the file specified for ANSIBLE_VAULT_PASSWORD_FILE
.
export ANSIBLE_VAULT_PASSWORD_FILE=/path/to/vaultpass.txt
vaultpass.txt
should simply contain the password:
r3allyCompl3xp@ssw0rd!thatislongandrandomandallthegoodthings
Encrypt a file
ansible-vault encrypt file
Decrypt a file
ansible-vault decrypt file
Resources: https://docs.ansible.com/ansible/latest/user_guide/vault.html#encrypt-string-for-use-in-yaml https://docs.ansible.com/ansible/latest/user_guide/playbooks_vault.html
Decrypt a template, deploy and then delete it
---
- name: Decrypt template
local_action: "shell {{ view_encrypted_file_cmd }}
{{ role_path }}/templates/template.enc > {{ role_path }}/templates/template"
changed_when: False
- name: Deploy template
template:
src=templates/template
dest=/home/user/file
- name: Remove decrypted template
local_action: "file path={{ role_path }}/templates/template state=absent"
changed_when: False
Resource: https://stackoverflow.com/questions/37682928/ansible-un-vault-and-template-a-file
Hosts file priority
"$(pwd)/hosts"
"${HOME}/.ansible/hosts"
/etc/ansible/hosts
Resource: https://www.quora.com/What-is-the-difference-between-host-and-inventory-file-in-ansible
Host file vs. inventory
They are the same thing. If needed, you
can define a different inventory file in the ansible.cfg
file like so:
[defaults]
inventory = /etc/ansible/inventory
Show facts about a host
ansible $HOSTNAME -m ansible.builtin.setup
Show specific fact
ansible $HOSTNAME -m ansible.builtin.setup -a "filter=ansible_distribution"
Resource: https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html
Specify user and private key for host
If you are having connectivity issues and need to specify a username and private key to connect, you will need to do so in the ansible hosts file like so:
<hostname> ansible_user=<username to connect to target> ansible_ssh_private_key_file=/path/to/private/key/file
Test connectivity to all hosts
ansible -m ping all -vvv
Wait for init to finish
This can be used to wait for updates to install before kicking off your ansible code.
---
# Updating the cache immediately can conflict with cloud-init scripts and cause failures.
- name: Wait for /var/lib/dpkg/lock-frontend to be released
shell: while lsof /var/lib/dpkg/lock-frontend; do sleep 10; done;
SSH retries
Add this to the ansible.cfg
file:
[ssh_connection]
retries=2
Fix SSH UNREACHABLE Connection Error
Add the timeout field to the ansible.cfg
file:
[defaults]
timeout = 25
List managed hosts
ansible all --list-hosts
Resource: https://www.tecmint.com/install-and-configure-an-ansible-control-node/
Break up long command
We can use the YAML folding operator >
to make this command:
- name: Use service account
shell: gcloud auth activate-service-account "{{ sa_email }}"
--key-file "{{ sa_creds_file }}"
Much more manageable to see what’s going on:
- name: Use service account
shell: >
gcloud auth activate-service-account "{{ sa_email }}"
--key-file "{{ sa_creds_file }}"
Create empty variable
This particular example will set the users
variable
to whatever the value is for users
or to an empty list:
users: "{{ users | default([]) }}"
Resource: https://serverfault.com/questions/805576/how-to-assign-an-empty-value-to-a-variable-in-ansible
Store output of command across multiple roles
- name: Some command to get users
shell: wget http://127.0.0.1:6000/users
# the output of running the script is stored in users
register: users
- name: Store users as a fact to be used later
set_fact:
users: "{{ users.stdout }}"
cacheable: yes
Resources:
Create fact with list of dictionaries
If you have a list of dictionaries from a variable that has some attributes that you want to use to create a fact, syou can do the following:
vars/main.yml
:
users:
- username: "admin"
usergroup: "admin"
# port 5901 is 1
vnc_num: 1
Next, we will add a random 8 character long password for each VNC user.
tasks/main.yml
:
- name: Set vnc_users fact to be used later
set_fact:
vnc_users: "{{ vnc_users | default([]) \
+ [ {'username': item.username, 'vnc_num': item.vnc_num, \
'pass': lookup('password', '/dev/null chars=ascii_letters,digits,punctuation
length=8')}] }}"
# Make fact available to other roles
cacheable: yes
with_items: "{{ users }}"
Resources:
- Provided example code
- Generate random password in ansible
- Inspired my original idea
- Break a long fact down over multiple lines
String with delimiter to list
- name: Store users as a fact to be used later
set_fact:
# Split on newlines
users: "{{ users.stdout.split('\n') }}"
cacheable: yes
Resource: https://gist.github.com/VerosK/9853931
Create users and groups from a list
This particular example will create users and groups with the same name:
- name: Create group for each user
group:
name: "{{ item }}"
state: present
loop: "{{ users }}"
- name: Create home directories for each user
user:
name: "{{ item }}"
shell: /bin/bash
groups: "{{ item }}"
state: present
loop: "{{ users }}
Resource: https://stackoverflow.com/questions/62358569/ansible-create-users-and-group
Remove trailing and leading quotes from var
{{ variable_name | regex_replace('\"|\"$', '') }}
Modify items in a list with multiple regexes
- name: For each user in the emails list, replace @ with _ and store in a fact called users
set_fact:
users: "{{ emails | map('regex_replace', '@', '_') | list }}"
cacheable: yes
- name: For each user in the users list, replace @ with _ and store in a fact called users
set_fact:
users: "{{ users | map('regex_replace', '\\.', '_') | list }}"
cacheable: yes
Resources:
Merge Two lists into list of dictionaries
- name: Store usernames and emails together
set_fact:
users_emails: "{{ users_emails | default([]) + [dict(username=item[0],
email=item[1])] }}"
loop: "{{ users | zip(emails) | list }}"
Get ansible facts and output to a file
ansible localhost -m ansible.builtin.setup | tee facts
Resource: https://docs.ansible.com/ansible/latest/user_guide/playbooks_vars_facts.html
Useful tidbits
Template path example:
src: "{{ ansible_env.HOME }}/roles/your_role/templates/run.sh.j2"
Resource: https://www.middlewareinventory.com/blog/ansible-template-module-example/
Debug
Add this in whenever you need to figure out why your code isn’t working:
- debug:
msg: "System {{ inventory_hostname }} has uuid {{ ansible_product_uuid }}"
Another example where you can specify a name:
- name: "Get $PATH"
debug: msg="{{ lookup('env','PATH') }} is an environment variable"
Another example where we end the playbook run after we print the debug message:
- debug:
msg: "{{ ansible_distribution_release }}"
- meta: end_play
Resources:
Check for availability of role
ansible-doc community.docker
Resource: https://www.ansible.com/blog/hands-on-with-ansible-collections
Run role as specific user
---
- name: Prepare VMs for cluster
hosts: vm_host
roles:
- role: dependencies
become: true
- role_to_run_with_non_elevated_privs
ansible_become: false # equivalent of become:
ansible_user: someuser
Resources:
- Explanation of ansible_become
- https://stackoverflow.com/questions/39183100/define-become-yes-per-role-with-ansible
Define variables for a specific role
For example, if you want to use the geerlingguy.docker
role
for a bunch of Kali systems and not have it affect
your debian systems that you’re also provisioning,
you need to trick it by changing some of the facts for the role.
You may not want those facts to be global for all roles, so you can set them for that particular one:
---
- hosts: all
name: Prepare VMs for cluster
roles:
- role: geerlingguy.docker
become: yes
docker_users: ["ansible"]
ansible_distribution: "debian"
ansible_distribution_release: "buster"
ansible_os_family: "Debian"
- role: provision_debian
become: yes
You can also accomplish this using this syntax as well:
- hosts: all
name: Prepare VMs for cluster
roles:
- { role: geerlingguy.docker, become: yes, docker_users: ['ansible'],
ansible_distribution: 'debian', ansible_distribution_release: 'buster',
ansible_os_family: 'Debian'}
although I don’t think it’s as nice to read.
Resources:
Change fact values for all hosts
If you need to change the facts for all hosts, you can do do something like this:
---
- name: Prepare VMs for cluster
hosts: vm_host
vars:
ansible_distribution: "debian"
ansible_distribution_release: "buster"
ansible_os_family: "Debian"
roles:
- { role: geerlingguy.docker, become: yes }
Change a line in a file
I also added the bit that’s used to restart the service to make the example complete.
- name: Disable Root Login
lineinfile:
dest=/etc/ssh/sshd_config
regexp='^PermitRootLogin'
line="PermitRootLogin no"
state=present
backup=yes
notify:
- restart ssh
handlers:
- name: restart ssh
service:
name=sshd
state=restarted
Create SSH key for a user on a managed node
roles/ssh/tasks/main.yml
:
---
- name: Generate SSH key
openssh_keypair:
path: "{{ ssh_key_path }}"
type: rsa
size: 4096
state: present
# Don't recreate if one is already
force: no
- name: Set permissions for private key
file:
path: "{{ ssh_key_path }}"
owner: "{{ user }}"
group: "{{ user }}"
mode: 0600
- name: Set permissions for public key
file:
path: "{{ ssh_key_path }}.pub"
owner: "{{ user }}"
group: "{{ user }}"
mode: 0600
roles/ssh/vars/main.yml
:
user: username
key_type: rsa
ssh_key_filename: "{{ user }}_ssh_{{ key_type }}"
ssh_key_path: "/home/{{ user }}/.ssh/{{ ssh_key_filename }}"
Resource:
Include variables file based on OS info
Folder structure:
role_name
├── tasks
│ └── main.yml
└── vars
├── kali-rolling.yml
├── Debian-10.yml
└── Ubuntu-20.yml
In main.yml
:
---
- name: Include Kali specific variables
include_vars: "{{ ansible_distribution_release }}.yml"
when: ansible_distribution == 'Kali GNU/Linux'
- name: Include Distribution version specific variables
include_vars: "{{ ansible_distribution }}-{{ ansible_distribution_major_version }}.yml"
Resources:
Use variable with many values
One great use for this is if you have multiple users with multiple attributes:
vars/main.yml
:
vnc_users:
- username: "{{ ansible_facts['ansible_user_id'] }}"
usergroup: "{{ ansible_facts['ansible_user_id'] }}"
sudo: true
# port 5901
vnc_num: 1
- username: "someuser"
usergroup: "someuser"
sudo: true
# port 5902
vnc_num: 2
tasks/main.yml
:
- name: Create .vnc dirs
file:
path: "/home/{{ item.username }}/.vnc"
state: directory
mode: 0755
owner: "{{ item.username }}"
group: "{{ item.usergroup | default(item.username) }}"
with_items: "{{ vnc_users }}"
Resource: https://github.com/sdarwin/Ansible-VNC/blob/master/vars/Debian-10.yml
Create custom ansible module for role
If you have a problem that is not solvable through what Ansible offers you, STOP the StackOverflow hell and write your own module instead. A little bit of python goes a long way.
Creation and testing
First, grab the data structures you’ll be messing with in ansible and just write some python around them to figure out your logic. In my case, I wanted to add a new key to a list of dictionaries from a list.
test.py
:
list_of_dicts = [{"username": "bob", "usergroup": "bob", "session": 1},
{"username": "jim",
"usergroup": "jim", "session": 2}]
# uids
list_to_merge = ["1000", "1001"]
for a, b in zip(list_of_dicts, list_to_merge):
a['uid'] = b
print(list_of_dicts)
Great, it works! Now to make it into an ansible module for a role,
create a folder under the role name called library
:
mkdir your_role/library
Next, add your code to a python file with a descriptive
name using your work in test.py
:
library/merge_list_of_dicts_w_list.py
:
#!/usr/bin/python3
from ansible.module_utils.basic import AnsibleModule
import json
def run_module():
module_args = dict(
list_of_dicts = dict(type='list', required=True),
list_to_merge = dict(type='list', required=True)
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
ld = module.params['list_of_dicts']
l = module.params['list_to_merge']
for a, b in zip(ld, l):
a['uid'] = b
module.exit_json(changed=False, result=ld)
def main():
run_module()
if __name__ == '__main__':
main()
To call the module from ansible, add the following to your_role/tasks/main.yml
:
- name: Merge uids into vnc_users
merge_list_of_dicts_w_list:
list_of_dicts: "{{ vnc_users }}"
list_to_merge: "{{ uids }}"
register: vnc_users_uid
# show output to show that it works
- debug: msg="{{ vnc_users_uid.result }}"
# Get the values from the username keys
- debug: msg="{{ item.username }}"
with_items: "{{ vnc_users_uid.result }}"
Resources:
Install packages
vars/main.yml
:
install_packages:
- curl
- jq
- tmux
tasks/main.yml
:
- name: Install dependencies
apt:
name: "{{ install_packages }}"
state: present
update_cache: yes
environment: "DEBIAN_FRONTEND: noninteractive"
# If you have an OS specific task:
when: ansible_distribution_release == "kali-rolling"
Resource: https://github.com/sdarwin/Ansible-VNC/blob/master/tasks/main.yml
List to string
This will take a list (packages
) and turn it into a space-delimited string:
"{{ packages | join (' ') }}"
One example of where this could be useful (cause I can’t get this environment
variable to work with the apt
module,
and no one else on the internet seems to have either):
- name: Install dependencies
# DO NOT put quotes around the {{ }}, or the command wont' work
shell: DEBIAN_FRONTEND=noninteractive apt-get -y install {{ packages |
join (' ') }}
Resource: https://stackoverflow.com/questions/47244834/how-to-join-a-list-of-strings-in-ansible
Use a file in a role
If you need to copy a file for a role, you’ll need the following folders:
├── files
│ └── yourfile
├── tasks
│ └── main.yml
Put the file(s) in the files directory.
In tasks/main.yml
, you’ll need to use the copy
module to get
the file into place on the managed node. For example:
- name: Create file
copy:
src: yourfile # you don't need to point to the files directory,
# just put the file name here
dest: "/root/yourfile"
mode: 0755
owner: root
group: root
Use a template in a role
If you need a template for a role, you’ll need the following folders:
├── templates
│ └── yourtemplate.j2
├── tasks
│ └── main.yml
The template file needs to have a .j2
extension.
Here’s an example that sets up a systemd job to run VNC for multiple users (vncserver.j2
):
[Unit]
Description=Remote desktop service (VNC)
After=syslog.target network.target
[Service]
Type=forking
PAMName=login
ExecStartPre=/bin/sh -c '/usr/bin/vncserver -kill :{{ item.item.vnc_num }} >
/dev/null 2>&1 || :'
ExecStart=/usr/bin/vncserver :{{ item.item.vnc_num }} {{ vnc_client_options }} {{
vnc_client_options_per_user | default() }}
ExecStop=/usr/bin/vncserver -kill :{{ item.item.vnc_num }}
[Install]
WantedBy=default.target
You can then call it in your main.yml
with the template
module, like so:
- name: Update per-user systemd service files
template:
src: vncserver.j2
dest: "/home/{{ item.item.username }}/.config/systemd/user/vncserver.service"
mode: 0644
owner: "{{ item.item.username }}"
group: "{{ item.item.usergroup | default(item.item.username) }}"
when:
- vnc_ansible_managed_startup_scripts or not item.stat.exists
with_items: "{{ checksystemd.results }}"
Resources:
Run a yml file at end of main
If you want to run a play or task after main.yml
has finished,
you can put that file into the tasks
directory and then use the
following to call it:
- name: Configure systemd auto-start service
include: systemd.yml
Resource: https://github.com/sdarwin/Ansible-VNC/blob/master/tasks/main.yml
Variable Precedence for roles
The first place to define variables with the least precedence
will be in defaults/main.yml
.
These can be overwritten with (for example) variables defined
in a task (taskname/vars/main.yml
).
A full precedence list can be found here: https://docs.ansible.com/ansible/latest/user_guide/playbooks_variables.html
Resources:
Check if file exists
- name: Check if test_file exists
stat:
path: $HOME/test_file
register: test_file
- name: Report if test_file exists
debug:
msg: "The file exists"
when: test_file.stat.exists
Resource: https://phoenixnap.com/kb/ansible-check-if-file-exists
When bool is true or false
- name: Configure systemd auto-start service
include: systemd.yaml
when: var.setup_systemd is true
#when: var.setup_systemd is false
Delete file if it exists
- name: Delete a file (or symlink) if it exists
file:
path: $HOME/test_file
state: absent
Resource: https://www.toptechskills.com/ansible-tutorials-courses/ansible-file-module-tutorial-examples/
Clone git repo
---
- name: Clone repo
git:
repo: "{{ repo_url }}"
dest: "{{ clone_location }}"
Resource: https://docs.ansible.com/ansible/2.3/git_module.html
Clone CloudCommit repo
- name: Generate temp creds to clone repo
shell: |
git config --global credential.helper '!aws codecommit credential-helper $@'
git config --global credential.UseHttpPath true
- name: Clone repo
git:
repo: "{{ repo_url }}"
dest: "{{ clone_location }}"
Coincidentally, the above also showcases how to run multiple bash commands and break them over multiple lines.
Manage containers with docker-compose
Start by installing the community.docker
collection:
ansible-galaxy collection install community.docker
Next, create a role for the deployment, and put the following in it:
├── your-docker-compose-deployment
│ ├── docker-compose.yml
├── tasks
│ └── main.yml
In your-docker-compose-deployment
, you’ll want to have
the folder with your docker compose project.
In tasks/main.yml
, you’ll want the following to install required
dependencies and then run the deployment:
---
- name: Install docker sdk
pip:
name: docker
- name: Install docker-compose
pip:
name: docker-compose
- name: Create and start services
docker_compose:
Note that pip
will default to whatever interpreter you’ve specified.
Resources:
Create password and output to template
This can be useful if you want to use .env
for a docker-compose deployment,
for example.
env.j2
:
POSTGRES_PASSWORD={{ password }}
Snippet to put into your role:
- name: Generate password
become: true
become_user: "{{ docker_user }}"
template:
src: env.j2
dest: "{{ docker_compose_deployment_location }}/.env"
vars:
password: "{{ lookup('password', '/dev/null chars=ascii_letters,digits,
punctuation length=50') }}"
Resources:
Templates
Loops and conditionals
This is an example taken from the nginx ansible role:
{% for path in nginx_logrotate_conf.paths %}
{{ path }}
{% endfor %}
{
{% for option in nginx_logrotate_conf.options %}
{{ option }}
{% endfor %}
postrotate
{% if ansible_facts['os_family'] == "Debian" %}
if [ -f /var/run/nginx.pid ]; then
kill -USR1 `cat /var/run/nginx.pid`
fi
{% else %}
nginx -s reopen
{% endif %}
endscript
}
Resources:
Use custom python function in template
If you need a custom python function for a template, you’ll want to use an ansible filter plugin.
You will have to expose this code to all roles
by putting it in a filter_plugins
folder like so:
/etc/ansible/
├── ansible.cfg
├── filter_plugins
│ └── get_user.py
├── hosts
├── roles
get_user.py
:
#!/usr/bin/python3
class FilterModule(object):
def filters(self):
return {
'get_user': self.get_user
}
def get_user(self, users, username, birthdate):
for user in users:
if username in user.values() and user['birthdate'] == birthdate:
return user
You can call it in a template like so:
{{ list_of_users_in_a_dictionary | get_user('mikey','01/01/1988') }}
The first variable for the function, users
,
is defined to the left of the pipe.
The second (username
) and third (birthdate
)
are defined as you would normally define them for a
function call: get_user('mikey','01/01/1988')
.
You can also assign the output to a variable and then use that:
{% set user = list_of_users_in_a_dictionary | get_user('mikey','01/01/1988') %}
{{ user.info }}
{{ user.birthdate }}
Development
Similar to modules, write the code first outside of ansible, and then integrate it.
Debugging
Check the template that’s generated and make sure it’s working as you expect it to.
Resources:
Package Management
Install pip packages
roles/pipexample/tasks/main.yml
:
- name: Install pip packages
pip:
name: "{{ item }}"
state: present
loop: "{{ pip_packages }}"
roles/pipexample/vars/main.yml
:
pip_packages:
- package_name
- another_package_name
Install gems
roles/gemexample/tasks/main.yml
:
- name: Install gems
gem:
name: "{{ item }}"
user_install: no
loop: "{{ gems }}"
roles/gemexample/vars/main.yml
:
gems:
- gem_name
- another_gem_name
Run bash script
- name: Run a bash script
become: true
# Run as the ubuntu user
become_user: ubuntu
shell: bash some_script.sh
args:
chdir: "/home/ubuntu"
Get current host’s IP address
This does not appear to work well on cloud - see Query metadata if you’re running on a cloud instance.
{ { ansible_ssh_host } }
Resource: https://stackoverflow.com/questions/39819378/ansible-get-current-target-hosts-ip-address
Get value for $HOME
- debug: msg="{{ lookup('env','HOME') }}"
Resource: https://stackoverflow.com/questions/43126400/ansible-host-how-to-get-the-value-for-home-variable
Query metadata
roles/ip/tasks/main.yml
:
---
- name: Get aws public IP
uri:
method: GET
url: "http://169.254.169.254/latest/meta-data/public-ipv4"
return_content: yes
register: uri_output
- name: Print public IP
debug:
msg: "Public IP: {{ uri_output.content | regex_replace('\n','') }}"
Output to a template
roles/ip/tasks/main.yml
:
---
- name: Get aws public IP
uri:
method: GET
url: "http://169.254.169.254/latest/meta-data/public-ipv4"
return_content: yes
register: uri_output
- name: Public IP to template
template:
src: ip.j2
dest: "/tmp/pub_ip.txt"
vars:
public_ip: "{{ uri_output.content | regex_replace('\n','') }}"
ip.j2
:
PUBLIC_IP={{ public_ip }}
Resources:
Add line to end of file
This example will add the tools made available by MSF
(such as pattern_create.rb
) for exploit dev to the global $PATH:
- name: Add MSF tools to PATH
lineinfile:
path: /etc/zsh/zshrc
line: export PATH=$PATH:/usr/share/metasploit-framework/tools/exploit
when: ansible_distribution_release == "kali-rolling"
Resource: https://www.mydailytutorials.com/ansible-add-line-to-file/
Get home dir
Get the env var for the currently connected user:
chdir: "{{ ansible_user_dir }}"
Another way (I’ve gotten errors around this before in certain situations like calling ansible from user-data):
chdir: "{{ ansible_env.HOME }}"
Note: both of these will return /root
if you use become:
.
Get user’s home directory via env (this
will get the user that you connected with
if you’re using become
):
chdir: "{{ lookup('env','HOME') }}"
Debug line:
- debug:
msg: "One way: {{ ansible_user_dir }} | Another way: {{ ansible_env.HOME }}
| A final way: {{ lookup('env', 'HOME') }}"
Resources:
- https://stackoverflow.com/questions/33343215/how-to-get-an-arbitrary-remote-users-home-directory-in-ansible
- Suggestion for ansible_user_dir
Download and extract zip
- name: Download and extract AWS CLI
unarchive:
src: "{{ aws_download_server }}"
dest: "{{ ansible_user_dir }}"
remote_src: yes
Resource: https://docs.ansible.com/ansible/2.5/modules/unarchive_module.html
Value to upper
{ { ansible_hostname|upper } }
Value to lower
{ { ansible_hostname|lower } }
Download and install deb package
This particular example will install the Amazon SSM agent on a debian system.
- name: Download and install SSM Agent
apt:
deb: https://s3.{{ region }}.amazonaws.com/amazon-ssm-{{ region }}/latest/{{
ansible_distribution|lower }}_amd64/amazon-ssm-agent.deb
Resource: https://stackoverflow.com/questions/22939775/ansible-and-wget
Append template to existing file
- blockinfile:
insertafter: EOF
path: ce_hostname.conf
block: "{{ lookup('template', 'nokia_t1_port.j2') }}"
marker: ""
Resource: https://stackoverflow.com/questions/63725020/ansible-template-append-to-file
Get lsb release
This is equivalent to the lsb_release -sc
bash command:
- name: Add {{ ansible_distribution_release }} PPA repository for Mozilla Firefox
ansible.builtin.apt_repository:
repo: ppa:mozillateam/ppa
codename: "{{ ansible_distribution_release }}"
Resource: https://www.shellhacks.com/ansible-lsb_release-variable/
Set the verbosity with env vars
export ANSIBLE_VERBOSITY=4
export MOLECULE_DEBUG=1
Profile slow tasks via molecule
Add the following to molecule/default/molecule.yml
:
env:
ANSIBLE_CALLBACK_PLUGINS: "${MOLECULE_SCENARIO_DIRECTORY}/callback_plugins"
For example:
provisioner:
name: ansible
playbooks:
converge: ${MOLECULE_PLAYBOOK:-converge.yml}
env:
ANSIBLE_CALLBACK_PLUGINS: "${MOLECULE_SCENARIO_DIRECTORY}/callback_plugins"
Use Ansible with AWS SSM for Windows Instances
When using Ansible to manage Windows instances on AWS via Systems Manager (SSM), you might encounter some challenges. Here’s how to set it up correctly:
Inventory File (windows_aws_ec2.yaml)
plugin: aws_ec2
regions:
- us-west-1
hostnames:
- tag:Name
- instance-id
filters:
instance-id: i-1234567890abcdef0
"tag:Name":
- "*windows*"
"tag:Environment":
- "prod"
keyed_groups:
- key: tags.Name
- key: tags.Environment
compose:
ansible_host: instance_id
Playbook File (windows_playbook.yaml)
---
- hosts: all
gather_facts: false
vars:
ansible_connection: aws_ssm
ansible_aws_ssm_bucket_name: your-bucket-name-here # Be sure to replace this with your bucket name
ansible_aws_ssm_region: us-west-1
ansible_shell_type: powershell
tasks:
- name: Wait for system to become reachable
wait_for_connection:
timeout: 60
- name: Echo instance ID and name
ansible.windows.win_shell: |
Write-Output "Running on instance $env:COMPUTERNAME"
Write-Output "Instance ID: {{ ansible_host }}"
register: echo_result
- name: Display echo result
debug:
var: echo_result.stdout_lines
Key Points
- In the inventory file, use
compose: ansible_host: instance_id
to ensure SSM uses the correct instance ID format. - In the playbook, set
ansible_connection: aws_ssm
to use SSM for connections. - Specify the S3 bucket name and region for SSM operations.
- Use
ansible_shell_type: powershell
for Windows instances. - Include a
wait_for_connection
task to ensure the system is reachable before running commands. - Use
ansible.windows.win_shell
for running PowerShell commands on Windows instances.
Running the Playbook
Run the playbook with increased verbosity for debugging:
ansible-playbook -i ./windows_aws_ec2.yaml ./windows_playbook.yaml -vvv
Troubleshooting
If you encounter issues:
Ensure the IAM role for your EC2 instance has necessary SSM permissions.
Verify the SSM agent is installed and running on your Windows instance.
Try bypassing host key checking:
ansible-playbook -i ./windows_aws_ec2.yaml ./windows_playbook.yaml -vvv --ssh-extra-args="-o StrictHostKeyChecking=no"
Test SSM connectivity manually:
aws ssm start-session --target i-00abab71f33038568 --region us-west-1
Resource: Ansible AWS Guide