Nodes

Tutorial: A More Secure Ansible Playbook, Part 2

Posted by Joel Hans on Sep 07, 2017

Last week, we started building out a more sophisticated Ansible provision playbook. We made it as far as hardening SSH with a few simple-but-logical edits to /etc/ssh/sshd_config, but there’s still plenty more that we can do to improve the playbook’s immediate functionality.

If you haven’t seen the first part of this tutorial series, hop over there first to get a handle on how the playbook functions, and its basic structure: Tutorial: A More Secure Ansible Playbook, Part 1.

With all that said, let’s hit the ground running.

Step 6. Creating iptables/tasks/main.yml

With Ansible 2.0, there’s a built-in iptables module that lets us create rules without having to rely on running plain bash commands. It’s really convenient, but not very well documented, sadly. But before we get to those rules, let’s hit the ground running on laying the groundwork.

- name: Install the `iptables` package
  package:
    name: iptables
    state: latest

Using the package module is familiar territory by now—we’re just double-checking that iptables is installed.

Next up, starting from a clean slate:

- name: Flush existing firewall rules
  iptables:
    flush: true

Our first example of using the iptables Ansible module, just to flush any existing rules that might exist.

And now, onto creating rules:

- name: Firewall rule - allow all loopback traffic
  iptables:
    action: append
    chain: INPUT
    in_interface: lo
    jump: ACCEPT

We first want to allow any loopback traffic that might exist between various applications and services that could be running on your VPS now or in the future. We can now see how we specify the chain, which can be set to any of the built-in chains that iptables offers: ‘INPUT’, ‘FORWARD’, ‘OUTPUT’, ‘PREROUTING’, ‘POSTROUTING’, ‘SECMARK’, ‘CONNSECMARK’. The jump parameter is where we specify what we want to do with traffic that matches the chain and interface—we want to ACCEPT this traffic, but we could also REJECT or DROP it.

For convenience, we also want to allow established connections.

- name: Firewall rule - allow established connections
  iptables:
    chain: INPUT
    ctstate: ESTABLISHED,RELATED
    jump: ACCEPT

Here’s our first (and only) taste of the ctstate parameter, which can be used to create some more complex rules.

Now, let’s get to the juicy bits—allowing certain types of traffic, on certain ports, that are usually wanted on a VPS.

- 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 port 80/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

The first example here, using the icmp protocol, allows you to ping your server as usual—if you don’t allow icmp traffic, your pings will be dropped or rejected.

For the other three items, we’re simply allowing certain destination ports based on popular services that people usually want enabled on their VPS: port 22, for SSH traffic; port 80, for HTTP traffic; and port 433, for HTTPS traffic. Without these exceptions, you’ll find that many VPS applications won’t work properly.

Now that you know how to open ports as needed, you can make customizations to your playbook that exactly fit your needs.

Finally, let’s give the firewall the power to do what it knows best: block traffic.

- name: Firewall rule - drop any traffic without rule
  iptables:
    chain: INPUT
    jump: DROP

The only difference with this rule is with the jump parameter—by using DROP, we simply block traffic arriving on any port and via any protocol we haven’t opened using the above rules. You can also set jump to REJECT, which would return a message about the port being unreachable.

Finally, we want to make these new rules persistent between server reboots. Here’s the way to do just that on Ubuntu and Debian-based VPSs.

- name: Install `netfilter-persistent` && `iptables-persistent` packages
    package:
      name: "{{item}}"
      state: present
    with_items:
     - iptables-persistent
     - netfilter-persistent
  when: ansible_os_family == "Debian"

We’re using the same old package module here, in combination with a with_items list, to install both iptables-persistent and netfilter-persistent. This seems to do the trick on Ubuntu, hence using when: ansible_os_family == "Ubuntu".

Step 7. Creating fail2ban/tasks/main.yml

Next up, we want to quickly set up fail2ban to help block attempted brute force attacks on our VPS.

- name: Install the `fail2ban` package
  package:
    name: fail2ban
    state: latest

- name: Override some basic fail2ban configurations
  copy:
    src: ../templates/jail.local.j2
    dest: /etc/fail2ban/jail.local
    owner: root
    group: root
    mode: 0644

Here, we’re using the package module to install fail2ban. After that operation is completed, we want to establish a few custom configurations for how fail2ban runs. To accomplish this, we want to copy a file that’s kept inside the playbook structure to /etc/fail2ban/jail.local. The copy module allows us to do just this, in addition to establishing proper ownership and permissions.

What is the jail.local file? Well, create a new file under fail2ban/templates/jail.local.j2 and paste in the following:

[DEFAULT]
# Ban hosts for one hour:
bantime = 3600

# Override /etc/fail2ban/jail.d/00-firewalld.conf:
banaction = iptables-multiport

[sshd]
enabled = true
maxretry = 3

All this configuration does is ensure that fail2ban uses iptables, and enables the tool for sshd. There’s plenty more you could do with these settings, which you can explore via the official documentation.

Step 8. BONUS! Enabling unattended upgrades

Let’s quickly hop back into our roles/packages/tasks/main.yml file to configure unattended upgrades, which will help make the server a little more secure.

Here’s what we’re adding to the packages role:

- name: Install the `unattended-upgrades` package
  package:
    name: unattended-upgrades
    state: installed
  when: ansible_os_family == "Debian"

- name: Copy the `20auto-upgrades` configuration file
  copy:
    src: ../templates/20auto-upgrades.j2
    dest: /etc/apt/apt.conf.d/20auto-upgrades
    owner: root
    group: root
    mode: 0644
  when: ansible_os_family == "Debian"

First, we’re installing unattended-upgrades, followed by copying a configuration file at tasks/packages/templates/20auto-upgrades.j2. What’s inside that file?

APT::Periodic::Update-Package-Lists "1";
APT::Periodic::Unattended-Upgrade "1";

Just the barebones options necessary to enable the process. As is, this configuration enables automatic security upgrades, but won’t automatically reboot your server when those updates require it. If you want to dive a little further into some of your other options, the /etc/apt/apt.conf.d/50unattended-upgrades on your VPS is well-documented and can be reconfigured according to your specific needs.

Onwards!

The playbook is now “complete”! Naturally, there is much more that could be done to improve how this playbook functions, including better support for more OS options. More on that in the coming weeks, and I’ll update these two articles as needed when that support is built in. Also on the horizon is things like automatically installing Docker, logging software, and more.

I’ve put the full playbook on GitHub so that you can easily see the entire structure, in case you were confused about anything along the way. Or, you can simply clone the repository and have at it.

$ git clone https://github.com/joelhans/ssdnodes-ansible-provision.git

If you’d like to see any features added, or have questions about how this playbook is put together, feel free to let me know in the comments, or via email. Happy to take issues and/or pull requests on GitHub as well!

Get 8GB RAM and 40GB SSD of the fastest cloud hosting for only $7.49/mo.

Get started

Our most popular posts:

VPS Comparison: Vultr vs. Digital Ocean vs. Linode vs. SSD Nodes Using Docker and Nginx to Host Multiple Websites Tutorial: Installing OpenVPN on Ubuntu 16.04