Learning Ansible Roles with CentOS 7 Linux

For those interested in learning Ansible with Ansible roles, this is a short document on how to install, setup, and use Ansible roles for automation and Configuration Management with CentOS 7 Linux.

For those who have a Chef background, Ansible roles are analogous to Chef Cookbooks with the directory structure predefined for deployment. An Ansible Playbook is the nuts and bolts of getting tasks done and resembles Chef Recipes within a Chef Cookbook.

Ansible Playbooks do not work within a specified universally accepted directory structure. In fact, you can use what ever directory structure you want, with its associated files, scripts, and templates.

We can take the Ansible tasks, files, and templates of an Ansible Playbook and move them into the predefined standardized directory structure of the Ansible role.

An Ansible role is a self-contained directory structure with the templates, tasks, files, and vars all packaged up. Ansible roles enable Ansible content and projects to be easily maintained and shared. Ansible’s pathing within a role directory structure means the templates, files, vars are all easily accessed from within the associated tasks of the Ansible role.

To learn Ansible automation and Ansible roles with this document have a CentOS 7 server created as the Ansible automation server and CentOS 7 servers created as the Ansible clients. You can do this with your favorite virtualization tool such as KVM, Virtual Box, VMWare, or whatever.

You could have the Ansible server named as ansibleserver and the Ansible clients named as ansibleclient1, ansibleclient2,….ansibleclientN.

On the Ansible server install EPEL by executing as root:

yum install https://dl.fedoraproject.org/pub/epel/epel-release-latest-7.noarch.rpm

With the new Ansible 2.8 version EPEL no longer has the latest Ansible version. To install Ansible we need to install Python’s pip; execute as root:

yum install python2-pip

Then execute:

pip install ansible --upgrade

This will install the new Ansible version 2.8 or greater and will install Ansible from https://ansible.com/.

On the Ansible CentOS 7 server create a login named lnxcfg. The lnxcfg login will be used for Ansible role and Ansible task execution which will allow us to manipulate, modify, and configure Ansible client servers. On the Ansible server you could create a lnxcfg userid like this:

groupadd -g 2002 lnxcfg
useradd -u 2002 -g 2002 -c "Ansible Automation Account" -s /bin/bash -m -d /home/lnxcfg lnxcfg

Ansible’s configuration file is located in /etc/ansible/ansible.cfg. To override and customize Ansible we can create a local ansible.cfg located in the root directory of the lnxcfg user. Create a flat file in the root directory of lnxcfg named ansible.cfg and add the following to the file:

/home/lnxcfg/ansible.cfg:

[defaults]
inventory = /home/lnxcfg/inventory
host_key_checking = False

The inventory line of inventory = /home/lnxcfg/inventory will allow us to define the Ansible client system inventory in the local lnxcfg home directory.

The host_key_checking = False will stop Ansible from prompting to add a client server to the ssh known_hosts when an Ansible playbook is executed.

On the Ansible server as the lnxcfg login create a flat file named inventory which will house the Ansible client inventory names. For example a /home/lnxcfg/inventory file could look like this:

[my_hosts]
ansibleclient1
ansibleclient2
ansibleclient3
[test_hosts]
ansibletest1

Ansible uses ssh to login to all of its inventory clients. Using ssh-keys, sudo access, and the lnxcfg login you will allow Ansible to execute on the Ansible client systems. The Ansible roles will be able to configure, maintain, manipulate, and manage the CentOS 7 client Linux servers.

As the lnxcfg user create an ssh-key that will be used by Ansible to ssh into its clients. To do this Execute:

ssh-keygen

press <enter> at all the default prompts.

The next step is to create the lnxcfg login on all Ansible client systems. You will need the Ansible server’s lnxcfg user’s .ssh/id_rsa.pub string for this purpose.

You could do this through a Linux Kickstart configuration file for the Ansible client systems or the below Bash script named setup-lnxcfg-user could help with this process. The script creates the lnxcfg login id, sets up ssh-keys, and sudo access on the Ansible client systems.

Provide your own lnxcfg login password for the clients and the lnxcfg .ssh/id_rsa.pub string you created earlier in the script below. Don’t forget to chmod 755 the setup-lnxcfg-user bash script prior to executing.

#!/bin/bash
# setup-lnxcfg-user
# create lnxcfg user for Ansible automation
# and configuration management
# create lnxcfg user
getentUser=$(/usr/bin/getent passwd lnxcfg)
if [ -z "$getentUser" ]
then
echo "User lnxcfg does not exist. Will Add..."
/usr/sbin/groupadd -g 2002 lnxcfg
/usr/sbin/useradd -u 2002 -g 2002 -c "Ansible Automation Account" -s /bin/bash -m -d /home/lnxcfg lnxcfg
echo "lnxcfg:<PUT IN YOUR OWN lnxcfg PASSWORD>" | /usr/sbin/chpasswdmkdir -p /home/lnxcfg/.sshfi# setup ssh authorization keys for Ansible access
echo "setting up ssh authorization keys..."
cat << 'EOF' >> /home/lnxcfg/.ssh/authorized_keys
<PUT IN YOUR OWN .ssh/id_rsa.pub SSH RSA KEY>
EOF
chown -R lnxcfg:lnxcfg /home/lnxcfg/.ssh
chmod 700 /home/lnxcfg/.ssh
# setup sudo access for Ansible
if [ ! -s /etc/sudoers.d/lnxcfg ]
then
echo "User lnxcfg sudoers does not exist. Will Add..."
cat << 'EOF' > /etc/sudoers.d/lnxcfg
User_Alias ANSIBLE_AUTOMATION = lnxcfg
ANSIBLE_AUTOMATION ALL=(ALL) NOPASSWD: ALL
EOF
chmod 400 /etc/sudoers.d/lnxcfg
fi
# disable login for lnxcfg except through
# ssh keys
cat << 'EOF' >> /etc/ssh/sshd_config
Match User lnxcfg
PasswordAuthentication no
AuthenticationMethods publickey
EOF# restart sshd
systemctl restart sshd
# end of script

Notice the script disables ssh logins for the lnxcfg user on an Ansible client except through ssh-keys. This is a good idea, for security reasons, as the lnxcfg user has sudo no password privileges, which is useful to successfully execute Ansible automation without human interaction against a client. However, if you want or need to use sudo you can use the ansible-playbook argument ansible_sudo_pass when executing Ansible; make sure you remove the NOPASSWD in the sudo snippet above. The Ansible Playbooks described in this document will assume you are using the NOPASSWD in the sudo snippet.

To execute the script on an Ansible client from the Ansible server you could execute:

ssh root@ansibleclient1 'bash -s' < setup-lnxcfg-user

If using a Linux Kickstart configuration file which is probably the preferred way of creating the lnxcfg user while building the Ansible client systems you could always carve out the script from above and put the majority of the Bash code in the Kickstart’s %post section.

Now on the actual Ansible roles. Ansible roles and the role’s associated tasks will allow us to configure, manipulate, and maintain Ansible clients. Ansible roles will provide a distinct directory structure that is widely used and beneficial for an Ansible’s project.

In order to work with roles, create a roles directory in the lnxcfg userid home directory by executing:

[lnxcfg@ansibleserver ~]$ mkdir roles

Ansible has a nice tool named ansible-galaxy which is used to perform various role related operations. One of these operations named ansible-galaxy init is to setup the predefined Ansible role directory structure for your Ansible projects.

Inside the roles directory you will execute the ansible-galaxy command with the init argument and the Ansible role name you wish to develop.

[lnxcfg@ansibleserver roles]$ ansible-galaxy init <name of the ansible role>

After you execute the ansible-galaxy init command you will see the distinct, universal, predefined directory structure of the Ansible role you just created. That directory structure will be:

.
├── defaults
│ └── main.yml
├── files
├── handlers
│ └── main.yml
├── meta
│ └── main.yml
├── README.md
├── tasks
│ └── main.yml
├── templates
├── tests
│ ├── inventory
│ └── test.yml
└── vars
└── main.yml

Inside the defaults, handlers, tasks, and vars directories you will see a main.yml. The main.yml file in these directories contains the relevant content for that specific Ansible directory and the main.yml file is guaranteed to execute by Ansible. If you do not have anything to put in the main.yml file you can leave it as is.

An explanation of the directory structure:

The default directory is where you would put the default variables for the Ansible role. These variables will go inside the main.yml file in this directory.

The vars directory at the bottom of the directory structure is where you would put other variables for the role. However, being that this directory is named vars you can use this directory as the primary variable directory. The main.yml file in this directory would be the location to define variables for the Ansible role.

Any variable defined in the default or vars directory will be available to all tasks and templates within the role.

The files directory contains files which can be deployed via this role. You can put static content that will not need any variable template modifications performed against these files in the files directory. It is also a great location to put any Bash, Python, or other scripts you would like Ansible to execute on a particular Ansible client.

The handlers directory contains handlers, which may be used by this role or even anywhere outside this role. Handlers in an Ansible Playbook can be moved into the main.yml file within this directory. If you are dissecting an Ansible Playbook into an Ansible role you can move the Playbook’s handlers to the main.yml file in this directory.

The meta directory defines some meta data for this role.

The README.md contains a description and purpose of the role and is used for documentation.

The tasks directory contains the tasks to be executed by the role. This directory is the nuts and bolts of actions you want to execute against the Ansible Linux clients. The main.yml is a file that will execute against the Ansible clients. If the tasks to be executed against an Ansible client is simple you can put the Ansible code in the main.yml file. However, it is good idea not to pollute this file with an overwhelming amount of Ansible code. Instead use the main.yml file as a control file that executes other yml files within the tasks directory. To do this use the includes_tasks module in the main.yml file to call other appropriately named Ansible yml files. As an example a main.yml file might have a task inside the main.yml file which executes an openFirewall.yml Ansible task:

- include_tasks: openFirewall.yml

The templates directory contains templates which can be deployed via this role. The templates directory is where Ansible Jinja2 templating are used to render variables within a file otherwise known as a template.

The tests directory is a way you can test the Ansible role locally against the Ansible server if you wanted to.

To execute an Ansible role you can create a file named site.yml in the root directory of the lnxcfg config login.

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- AnyRoleNameInTheRolesDirectory

You can test using:

ansible-playbook site.yml

Let us make an Ansible role.

Change directories to the roles directory:

lnxcfg@ansibleserver ~]$ cd roles/

The first Ansible role to learn from is a simple message of the day. To do this execute the ansible-galaxy command from within the roles directory:

ansible-galaxy init motd 

Inside the motd directory you will see the Ansible role directory structure as shown above.

The templates directory will house the motd.j2 template which will use Jinja2 templating.

templates/motd.j2:

motd from ansible

Ansible Node: {{ ansible_nodename }}
FQDN: {{ ansible_fqdn }}
IP Address: {{ ansible_default_ipv4.address }}
Distribution: {{ ansible_distribution }} {{ ansible_distribution_release }} {{ ansible_distribution_version }}
Kernel: {{ ansible_kernel }}
Python Version: {{ ansible_python_version }}
CPUs: {{ ansible_processor_vcpus }}
Memory: {{ ansible_memtotal_mb }} MB
Virtualization: {{ ansible_virtualization_type }}
The current date is: {{ ansible_date_time.date }} and it is a {{ ansible_date_time.weekday }}
Ansible User Information:
user: {{ ansible_env.SUDO_USER }}
uid: {{ ansible_env.SUDO_UID }}
gid: {{ ansible_env.SUDO_GID }}
home: {{ ansible_env.HOME }}
pwd: {{ ansible_env.PWD }}

The main.yml file within the tasks directory would look like this:

tasks/mail.yml:

---
# tasks file for motd
- name: set motd
template:
src: motd.j2
dest: /etc/motd
owner: root
group: root
mode: 0644

Because the Ansible task is so simple using the main.yml file is a preferred way to build a task. However, for complex tasks breaking the tasks main.yml file into more manageable parts is a good idea.

Notice Ansible’s pathing. The src source motd.j2 finds the template in the templates directory without hard coding a path.

To execute an Ansible role you can create a file named site.yml in the root directory of the lnxcfg config login.

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- motd

You can test using:

ansible-playbook site.yml

A second Ansible role to learn from is to install and configure httpd. To do this execute the ansible-galaxy command from within the roles directory:

ansible-galaxy init installHttpd

Inside the installHttpd directory you will see the Ansible role directory structure as shown above.

The templates directory will house the index.html.j2 template which will use Jinja2 templating.

templates/index.html.j2:

<html>
<body>
<h1>Hello from Ansible: {{ ansible_nodename }} </h1>
<br>
<pre>
Ansible Node: {{ ansible_nodename }}
FQDN: {{ ansible_fqdn }}
IP Address: {{ ansible_default_ipv4.address }}
Distribution: {{ ansible_distribution }} {{ ansible_distribution_release }} {{ ansible_distribution_version }}
Kernel: {{ ansible_kernel }}
Python Version: {{ ansible_python_version }}
CPUs: {{ ansible_processor_vcpus }}
Memory: {{ ansible_memtotal_mb }} MB
Virtualization: {{ ansible_virtualization_type }}
The current date is: {{ ansible_date_time.date }} and it is a {{ ansible_date_time.weekday }}
Ansible User Information:
user: {{ ansible_env.SUDO_USER }}
uid: {{ ansible_env.SUDO_UID }}
gid: {{ ansible_env.SUDO_GID }}
home: {{ ansible_env.HOME }}
pwd: {{ ansible_env.PWD }}
</pre>
</body>
</html>

The handlers directory contains the main.yml file where we can define our handler for this particular role. Below we define a handler to restart httpd.

handlers/main.yml:

---
# handlers file for installHttp
- name: restart httpd
service:
name: httpd
enabled: yes
state: restarted

The tasks directory contains the tasks to install and configure the httpd packages and server. The main.yml file is used as a control file and executes other tasks within the tasks directory.

tasks/main.yml:

---
# tasks file for installHttpd
- include_tasks: openFirewall.yml
- include_tasks: installHttpd.yml
- include_tasks: createIndexHtml.yml

tasks/openFirewall.yml:

---
- name: open http firewall port
firewalld:
service: http
zone: public
immediate: yes
permanent: true
state: enabled

tasks/installHttpd.yml:

---
- name: install httpd
yum:
name: httpd
state: present

tasks/createIndexHtml.yml:

---
- name: httpd template for index.html
template:
src: index.html.j2
dest: /var/www/html/index.html
owner: root
group: root
mode: 0644
notify: restart httpd

To execute the new installHttpd Ansible role you could use:

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- installHttpd

You can test using:

ansible-playbook site.yml

A third Ansible role to learn from is to install and configure chronyd. To do this execute the ansible-galaxy command from within the roles directory:

ansible-galaxy init setupChronyd

As with the previous Ansible roles inside the setupChronyd directory you will see the Ansible role directory structure.

The vars directory will have the variables used for this Ansible role.

vars/main.yml:

---
# vars file for setupChronyd
#
chronySourceNIST: ['time-a-g.nist.gov',
'time-b-g.nist.gov',
'time-c-g.nist.gov']

The handlers directory contains the main.yml file where we can define our handler for this particular role. Below we define a handler to restart chronyd.

handlers/main.yml:

---
# handlers file for setupChronyd
#
- name: restart chronyd
service:
name: chronyd
enabled: yes
state: restarted

The templates directory will have the template for the chrony.conf file and will use a Jinja2 templating with a for loop to populate the values from the chronySourcesNIST variable.

templates/chrony.conf.j2:

# chrony.conf from ansible{% for var in chronySourceNIST %}
server {{ var }} iburst
{% endfor %}
# Record the rate at which the system clock gains/losses time.
driftfile /var/lib/chrony/drift
# Allow the system clock to be stepped in the first three updates
# if its offset is larger than 1 second.
makestep 1.0 3
# Enable kernel synchronization of the real-time clock (RTC).
rtcsync
# Enable hardware timestamping on all interfaces that support it.
#hwtimestamp *
# Increase the minimum number of selectable sources required to adjust
# the system clock.
#minsources 2
# Allow NTP client access from local network.
#allow 192.168.0.0/16
# Serve time even if not synchronized to a time source.
#local stratum 10
# Specify file containing keys for NTP authentication.
#keyfile /etc/chrony.keys
# Specify directory for log files.
logdir /var/log/chrony
# Select which information is logged.
#log measurements statistics tracking

The tasks directory will have the main.yml file and the other yml files included from the main.yml file.

tasks/main.yml:

---
# tasks file for setupChronyd
#
- include_tasks: installChronyd.yml
- include_tasks: configureChronyd.yml

tasks/installChronyd.yml:

---
- name: install chronyd
yum:
name: chrony
state: present

tasks/configureChronyd.yml:

---
- name: configure chronyd from template
template:
src: chrony.conf.j2
dest: /etc/chrony.conf
owner: root
group: root
mode: 0644
notify: restart chronyd

To execute the new setupChrony role you could use the site.yml which would be executed from the root directory of the lnxcfg user:

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- setupChrony

To test you would execute:

ansible-playbook site.yml

The fourth Ansible role to learn from is a role to add and remove users. To do this execute the ansible-galaxy command from within the roles directory:

ansible-galaxy init configUsers

As with the previous Ansible roles inside the configUsers directory you will see the Ansible role directory structure.

The vars directory will have the variables used for this Ansible role. Inside the main.yml of the vars directory is a dictionary of two users.

vars/main.yml:

---
# User to modify
users:
user1:
login: 'linuxuser1'
uid: '12001'
group: 'users'
supplementalGroups: 'accounting,finance'
gecos: 'Linux User One Account'
shell: '/bin/bash'
state: 'present'
user2:
login: 'linuxuser2'
uid: '12002'
group: 'users'
supplementalGroups: 'accounting,finance'
gecos: 'Linux User Two Account'
shell: '/bin/bash'
state: 'present'

The tasks directory contains the tasks to add the accounting and finance groups plus configure the users. The main.yml file is used as a control file and executes the two tasks within the tasks directory.

tasks/main.yml:

---
# tasks file for configUsers
#
- include_tasks: createGroups.yml
- include_tasks: configUsers.yml

tasks/createGroups.yml:

---
- name: add accounting group
group:
name: accounting
gid: 3000
state: present
- name: add finance group
group:
name: finance
gid: 3001
state: present

tasks/configUsers:

---
- name: config a user
user:
name: "{{ item.value.login }}" #login id
comment: "{{ item.value.gecos }}"
shell: "{{ item.value.shell }}"
uid: "{{ item.value.uid }}"
group: "{{ item.value.group }}"
groups: "{{ item.value.supplementalGroups }}"
home: "/home/{{ item.value.login }}"
password: "{{ 'changeMe' | password_hash('sha512') }}"
state: "{{ item.value.state }}"
append: yes
remove: yes
update_password: always
loop: "{{ lookup('dict', users) }}"
- name: execute chage on new user - make user change password
command: 'chage -d 0 "{{ item.value.login }}"'
with_dict: "{{ users }}"
when: item.value.state == 'present'

the remove: yes only works if you change the dictionary of users state value to absent from present. To make sure a user changes their password the Linux chage command is used to set the last day to zero which forces a user to change their password once they login.

To execute the new configUsers role you could use the site.yml which would be executed from the root directory of the lnxcfg user:

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- configUsers

To test you would execute:

ansible-playbook site.yml

The fifth Ansible role to learn from is a role to manage systemd services. Below is an Ansible role which creates a simple systemd service which can be used as a systemd replacement for the old system V rc.local script:

To do this execute the ansible-galaxy command from within the roles directory:

ansible-galaxy init rebootSystemdService

As with the previous Ansible roles inside the configUsers directory you will see the Ansible role directory structure.

The files directory will have the a Bash script and a corresponding systemd unit file which executes the Bash script.

The Bash script created from a file is as follows. You could do more complex tasks in this Bash script such as starting Oracle databases, emailing your team or yourself that the server has been rebooted, and much more:

files/systemd-script:

#!/bin/bash
echo "Systemd script: $(/bin/date)" > /home/lnxcfg/systemd-script-results

The systemd unit file to replace an rc.local script:

files/systemd-script.service:

[Unit]
Description = systemd-script service
After = network.target
[Service]
Type=forking
ExecStart = /usr/local/bin/systemd-script

[Install]
WantedBy = multi-user.target

The tasks directory will have the main.yml which is a control file and executes other yml files as follows. Notice the Ansible block sequence enables the Ansible role to only execute on version 7 of a CentOS Ansible client.

tasks/main.yml:

---
# tasks file for rebootSystemdService
- name: configure rebootSystemdService block
block:
- include_tasks: configureRebootScript.yml
- include_tasks: configureSystemdService.yml
- include_tasks: installSystemdService.yml
when: ansible_distribution == "CentOS" and ansible_distribution_major_version == "7"

tasks/configureRebootScript.yml:

---
- name: configure reboot script
copy:
src: systemd-script
dest: /usr/local/bin/systemd-script
owner: root
group: root
mode: 0755

tasks/configureSystemdService.yml:

---
- name: configure systemd service
copy:
src: systemd-script.service
dest: /etc/systemd/system/systemd-script.service
owner: root
group: root
mode: 0644

tasks/installSystemdService.yml:

---
- name: install systemd service
systemd:
name: systemd-script
enabled: yes
masked: no
daemon_reload: yes

To execute the new rebootSystemdService role you could use the site.yml which would be executed from the root directory of the lnxcfg user:

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- rebootSystemdService

To test you would execute:

ansible-playbook site.yml

The sixth Ansible role to learn from is working with Ansible Facts. Using Ansible Facts is a good way of sharing variables between role tasks. To assign a variable to an Ansible Fact you use the Ansible set_fact module.

To do this execute the ansible-galaxy command from within the roles directory:

ansible-galaxy init createDeveloperFact

As with the previous Ansible roles inside the createDeveloperFact directory you will see the Ansible role directory structure.

The tasks directory will have two files: the setFactOfDeveloperLanguages.yml which will create an Ansible Fact from a variable and the createFileofDeveloperLanguages.yml which will write the Ansible Fact variable into a file using the Ansible blockinfile module.

tasks/setFactOfDeveloperLanguages.yml:

---
- name: build list of developer languages and set an ansible fact
vars:
developerLanguages: []
set_fact:
developerLanguages: "{{ developerLanguages }} + [ '{{ item.name }}' ]"
with_items:
- { name: 'bash' }
- { name: 'ruby' }
- { name: 'python' }
- { name: 'java' }
- { name: 'c' }
- { name: 'php' }
- { name: 'rexx' }
- { name: 'visual basic' }

tasks/createFileofDeveloperLanguages.yml:

---
- name: take variable from fact and create a file of developer languages.
blockinfile:
path: /home/lnxcfg/developerLanguages
create: true
owner: lnxcfg
group: lnxcfg
mode: 0644
block: |
{% for var in developerLanguages %}
{{ var }}
{% endfor %}

Notice the use of Jinja2 templating syntax to load the variable data into the blockinfile in the above task.

The main.yml file will tie all this together:

tasks/main.yml:

---
# tasks file for createDeveloperFact
- include_tasks: setFactOfDeveloperLanguages.yml
- include_tasks: createFileofDeveloperLanguages.yml

To execute the new createDeveloperFact role you could use the site.yml which would be executed from the root directory of the lnxcfg user:

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- createDeveloperFact

The final Ansible role to learn from is the AIDE file based intrusion detection software which executes a Bash script to initialize and configure AIDE. The Ansible role is idempotent and will only execute based on the status of the AIDE database.

Inside the roles directory execute:

ansible-galaxy init aideInstallAndSetupRole

Inside the aideInstallAndSetupRole directory you will see the Ansible role directory structure as shown above.

In the tasks directory we will use an array variable named: aide_nfs_mounts. Any variable defined in the default or vars directory will be available to all tasks and templates within the role.

To define the aide_nfs_mounts variable in the vars directory edit the main.yml file as below. The aide_nfs_mounts will be an array of NFS mounted file systems on the client server.

vars/main.yml:

---
# vars file for aideInstallAndSetupRole
#
aide_nfs_mounts: []

Inside the files directory create a configAIDE file which will be a Bash script for initializing the AIDE database:

files/configAIDE:

#!/bin/bash
# configAide
# configure file based intrusion detection - AIDE

/sbin/aide --init
echo "aide has been initialized"
/bin/mv /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
echo "aide has been configured

files/aide-check:

#!/bin/bash
# execute daily script to execute AIDE
echo "executing $0 ......"/bin/nice -n 19 /sbin/aide --update/bin/rm -f /var/lib/aide/aide.db.gz
/bin/mv -f /var/lib/aide/aide.db.new.gz /var/lib/aide/aide.db.gz
logger -s 'AIDE DAILY FILE CHANGE AND INTRUSION DETECTION CHECK HAS EXECUTED'

The tasks directory will have the tasks for configuring AIDE. Create the following tasks files for this configuration:

tasks/installAIDE.yml:

---
# install the AIDE package
- name: install aide -- file based intrusion detection system
yum:
name: aide
state: present

The following configAIDE.yml file will configure the /etc/aide.conf file. Notice the use of the blockinfile which inserts a block of text into the /etc/aide.conf. As you can see blockinfile allows the interpretation of variables inside the block with Jinja2 template code. This is done instead of using a templates file.

tasks/configAIDE.yml:

---
# find nfs mounts on the client server so they may be excluded
# from AIDE scans. Build array of NFS mounted file systems
- name: build list of nfs mounts which should not be scanned
set_fact:
aide_nfs_mounts: "{{ aide_nfs_mounts }} + [ '{{ item.mount }}' ]"
when: item.fstype == "nfs"
with_items: "{{ ansible_mounts }}"
# the following block will insert a block of text AND interpret the
# variable of nfs mounted file systems into the /etc/aide.conf file.
- name: insert disabled cataloging of files in /etc/aide.conf
blockinfile:
path: /etc/aide.conf
block: |
# disable AIDE cataloging all /var/log
!/var/log
# disable AIDE cataloging all aide files
!/usr/local/bin/aide-check
!/etc/aide.conf
# disable AIDE cataloging nfs mounts
{% for var in aide_nfs_mounts %}
!{{ var }}
{% endfor %}

tasks/checkAIDEIdempotent.yml:

---
# setup idempotent result based on aide file
- name: Check if /var/lib/aide/aide.db.gz exists
stat:
path: /var/lib/aide/aide.db.gz
register: stat_result

tasks/executeAIDEConfigureScript.yml:

---
# execute external script to configure aide
- name: configure aide
block:
- name: copy configAIDE to /tmp
copy:
src: configAIDE
dest: /tmp/configAIDE
owner: root
group: root
mode: 0755
- name: execute bash script /tmp/configAIDE
command: '/tmp/configAIDE'
- name: remove /tmp/configAIDE
file:
state: absent
path: '/tmp/configAIDE'
when: stat_result.stat.exists == False

tasks/createDailyAideScript.yml:

---
# create daily script to execute AIDE
- name: create daily script to execute AIDE
copy:
src: aide-check
dest: /usr/local/bin/aide-check
owner: root
group: root
mode: 0755

tasks/createSymbolicLink.yml:

---
# create daily cron job for AIDE
- name: create sybolic link to /etc/cron.daily
file:
src: /usr/local/bin/aide-check
dest: /etc/cron.daily/aide-check
owner: root
group: root
state: link
force: yes

The main.yml file will tie all this together:

tasks/main.yml:

---
# tasks file for aideInstallAndSetupRole
- include_tasks: installAIDE.yml
- include_tasks: configAIDE.yml
- include_tasks: checkAIDEIdempotent.yml
- include_tasks: executeAIDEConfigureScript.yml
- include_tasks: createDailyAideScript.yml
- include_tasks: createSymbolicLink.yml

To execute the new aideInstallAndSetupRole role you could use this site.yml file which would be executed from the root directory of the lnxcfg user:

site.yml:

---
- hosts: my_hosts
remote_user: lnxcfg
become: true
become_method: sudo
become_user: root
roles:
- aideInstallAndSetupRole

To test you would execute:

ansible-playbook site.yml

Learning and using Ansible can be great fun. There is a lot more to it but hopefully this will get you started.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store