Ansible security playbook for your VPS (part 2)

In part 1 of our Ansible security playbook tutorial, we started building out an Ansible playbook with a focus on creating better security for your VPS.
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: Ansible security playbook for your VPS.
We’ll jump right back in where we left off– using Ansible iptables
and installing fail2ban
with Ansible to prevent brute force attacks.
What’s the BEST DEAL in cloud hosting?
Develop at hyperspeed with a Performance VPS from SSD Nodes. We DOUBLED the amount of blazing-fast NVMe storage on our most popular plan and beefed up the CPU offering on these plans. There’s nothing else like it on the market, at least not at these prices.
Score a 16GB Performance VPS with 160GB of NVMe storage for just $99/year for a limited time!
Step 6. Creating Ansible iptables/tasks/main.yml
With Ansible 2.2, 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 by installing the Ansible iptables module.
- 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
using Ansible 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 Ansiblepackage
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";
These are 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.
Congrats– your Ansible security playbook is complete!
You’ve successfully automated the installation of Ansible iptables
and used fail2ban
to protect your VPS from brute force attacks.
And the beauty of Ansible is that you can use this playbook again and again to repeat all these steps automatically and secure multiple servers in just moments.
Naturally, there is much you can do improve how this playbook functions, including better support for more OS options. And you can also Ansible to automate other installations– like 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 create an issue and/or pull requests on GitHub.
Like what you saw? Subscribe to our weekly newsletter.