I speak here {^_^}

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.]