The Simplified playbook!
August 18, 2019
Ah, I literally procastinated a lot for writing this blog post. But I, "actually", am not regretting at all for this once, because the time was justly spent well with my parents & family.
Anyways, before moving forward with other tasks in the series, I am supposed to finish the backlogs (finish writing my 2 blogposts, including this one).
And therefore, let me quickly describe the reason for writing this post.
This post doesn't actually have a distinct topic rather it's just an update/improvement to one of my last blogpost "A guide to a “safer” SSH!" . Back there, I was fairly doing every errand by writing separate individual tasks. For instance, when I was supposed to make changes in sshd_config file, I used an approach to find the intended lines using "regex" and replace each one of them individually with the new required configurations. Similar was the case while writing iptables rule through ansible playbook on a remote machine.
But these individual execution of co-related tasks was making the whole ansible implementation/deployment process extremely time-consuming and the ansible playbook itself look unneccesarily lengthy and complex. Thus, the real idea of writing these playbooks to automate stuffs in a faster and easier manner, proved to be pretty much worthless in my case.
So, here I am taking over kushal's advice of improving these ansible playbooks to achieve simplicity and better optimized execution time. The whole idea is to compile up these co-related tasks (for example, making changes in sshd_config file for the purpose of SSH-hardening) in a single file and copy this file to the intended location/path/directory on the remote node/server.
Let me quickly walk you through some simple hands-on examples to make the idea more precise and understand in action. (We will be improving our existing ansible playbook only)
- So, earlier in the post, while writing the "ssh role", our tasks looked something like this:
---
# tasks file for ssh
- name: Add local public key for key-based SSH authentication
authorized_key:
user: ""
state: present
key: ""
with_fileglob: public_keys/*.pub
- name: Harden sshd configuration
lineinfile:
dest: /etc/ssh/sshd_config
regexp: ""
line: ""
state: present
with_items:
- regexp: "^#?PermitRootLogin"
line: "PermitRootLogin no"
- regexp: "^^#?PasswordAuthentication"
line: "PasswordAuthentication no"
- regexp: "^#?AllowAgentForwarding"
line: "AllowAgentForwarding no"
- regexp: "^#?AllowTcpForwarding"
line: "AllowTcpForwarding no"
- regexp: "^#?MaxAuthTries"
line: "MaxAuthTries 2"
- regexp: "^#?MaxSessions"
line: "MaxSessions 2"
- regexp: "^#?TCPKeepAlive"
line: "TCPKeepAlive no"
- regexp: "^#?UseDNS"
line: "UseDNS no"
- regexp: "^#?AllowAgentForwarding"
line: "AllowAgentForwarding no"
- name: Restart sshd
systemd:
state: restarted
daemon_reload: yes
name: sshd
...
And if we observe closely at the last second task, we are altering each intended line of the sshd_config file in an individual fashion which is definitely not required. Rather the changes could be made at once, in a new copied file of the existing "sshd_config" file and thus sent to the remote node at the required location/path/directory.
This copied sshd_file will reside in the "files/" directory of our "ssh role".
├── ssh
│ ├── defaults
│ │ └── main.yml
│ ├── files 👈(HERE)
│ ├── handlers
│ │ └── main.yml
│ ├── meta
│ │ └── main.yml
│ ├── README.md
│ ├── tasks
│ │ └── main.yml
│ ├── templates
│ ├── tests
│ │ ├── inventory
│ │ └── test.yml
│ └── vars
│ └── main.yml
- Copy the local sshd_config file to this files/ directory.
# like in my case, the ansible playbook is residing at "/etc/ansible/playbooks/"
$ sudo cp /etc/ssh/sshd_config /etc/ansible/playbooks/ssh/files/
- And then make the required changes in this file as specified in the last second task of our old "ssh role".
- Finally modify the "ssh role" by replacing the last second task with the task of copying this file in the remote node at "/etc/ssh/" directory path thus removing the un-neccessary recursive steps.
- Now, the new "ssh role" would look like the following.
---
# tasks file for ssh
- name: Add local public key for key-based SSH authentication
authorized_key:
user: ""
state: present
key: ""
with_fileglob: public_keys/*.pub
- name: Copy the modified sshd_config file to remote node's /etc/ssh/ directory.
copy:
src: /etc/ansible/playbooks/ssh/files/sshd_config
dest: /etc/ssh/sshd_config
owner: root
group: root
mode: 0644
- name: Restart sshd
systemd:
state: restarted
daemon_reload: yes
name: sshd
...
And we are done. This will execute considerably much faster than the old ansible role and looks comparatively much simpler as well.
Similar improvements can be made in case of "iptables role" as well.
Our old "iptables role" looked something like this:
---
# tasks file for iptables
- name: Install the `iptables` package
package:
name: iptables
state: latest
- name: Flush existing firewall rules
iptables:
flush: true
- name: Firewall rule - allow all loopback traffic
iptables:
action: append
chain: INPUT
in_interface: lo
jump: ACCEPT
- name: Firewall rule - allow established connections
iptables:
chain: INPUT
ctstate: ESTABLISHED,RELATED
jump: ACCEPT
- name: Firewall rule - allow port ping traffic
iptables:
chain: INPUT
jump: ACCEPT
protocol: icmp
- name: Firewall rule - allow port 22/SSH traffic
iptables:
chain: INPUT
destination_port: 22
jump: ACCEPT
protocol: tcp
- name: Firewall rule - allow port80/HTTP traffic
iptables:
chain: INPUT
destination_port: 80
jump: ACCEPT
protocol: tcp
- name: Firewall rule - allow port 443/HTTPS traffic
iptables:
chain: INPUT
destination_port: 443
jump: ACCEPT
protocol: tcp
- name: Firewall rule - drop any traffic without rule
iptables:
chain: INPUT
jump: DROP
- name: Firewall rule - drop any traffic without rule
iptables:
chain: INPUT
jump: DROP
- name: Install `netfilter-persistent` && `iptables-persistent` packages
package:
name: ""
state: present
with_items:
- iptables-persistent
- netfilter-persistent
...
- In order to simplify it, Create a new file, named "rules.v4" in the "files/" directory of "iptables role" and paste the following iptables rule in there.
*filter
:INPUT ACCEPT [0:0]
:FORWARD ACCEPT [0:0]
:OUTPUT ACCEPT [151:12868]
:sshguard - [0:0]
-A INPUT -i lo -j ACCEPT
-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
-A INPUT -p icmp -j ACCEPT
-A INPUT -p tcp -m tcp --dport 22 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 80 -j ACCEPT
-A INPUT -p tcp -m tcp --dport 443 -j ACCEPT
-A INPUT -j DROP
COMMIT
- And the final step would be same as above role, ie. copying this "rules.v4" file in the "/etc/iptables" directory of the remote node.
- So, the new improved "iptables role" will now look like the following.
---
# tasks file for iptables
- name: Install the `iptables` package
package:
name: iptables
state: latest
- name: Flush existing firewall rules
iptables:
flush: true
- name: Inserting iptables rules in the "/etc/iptables/rules.v4" file.
copy:
src: /etc/ansible/playbooks/iptables/files/rules.v4
dest: /etc/iptables/rules.v4
owner: root
group: root
mode: 0644
- name: Install `netfilter-persistent` && `iptables-persistent` packages
package:
name: ""
state: present
with_items:
- iptables-persistent
- netfilter-persistent
...
That's all about this quick blogpost on how to efficiently write recursive co-related tasks in an ansible playbook.
Hope it helped.
Till next time. o/
[Note:- I will link this article in the old post as an update.]