Skip to content
Permalink
Browse files

new blog post: vultr and firewalling

  • Loading branch information...
resmo committed Mar 20, 2018
1 parent aac55e1 commit 1360402f378c94d707c6b39c5694c283293fa206
@@ -0,0 +1,267 @@
---
title: "Vultr Firewalling with Ansible"
date: 2018-03-19T22:23:30+01:00
categories:
- sysadmin
tags :
- ansible
- vultr
- ansible-2.5
---

This is the second part of a post series _What's new in Ansible 2.5_. In the previous post we learned the basic steps to deploy a server on Vultr.

In this follow up post, you will see how we can secure our server.

![Vultr Firewall Rules](/img/blog/vultr-firewall.png)

## Firewall Groups

Vultr has the concept of "Firewall Groups" also known as "Security Groups". One group can have one or more Firewall rules and one server can be assigned to exactly one group.

{{% note %}}
Unlike other cloud APIs, Vultr only allows one server to be in one security group.
{{% /note %}}

That said, I think we need to create a firewall group. Let's create one.

But first we look back what we got in our existing playbook:

{{< highlight yaml >}}
---
- hosts: cloud
gather_facts: no
tasks:
- name: Ensure a cloud server exists
local_action:
module: vr_server
name: "{{ inventory_hostname_short }}"
os: "CentOS 7 x64"
plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
region: New Jersey
{{< / highlight >}}

The module to create a firewall group is called `vr_firewall_group`, unsurprisingly.

We try to add the task to the existing play:

{{< highlight yaml "hl_lines=5-8">}}
---
- hosts: cloud
gather_facts: no
tasks:
- name: Create a firewall group
local_action:
module: vr_firewall_group
name: web

- name: Ensure a cloud server exists
local_action:
module: vr_server
name: "{{ inventory_hostname_short }}"
os: "CentOS 7 x64"
plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
region: New Jersey
{{< / highlight >}}

That doesn't look to wrong, but the challenge here is, we don't want to create a group for each cloud hosts, just one group in total.

Yes, you are right! This could be achieved by adding a `run_once: yes` to this task.

{{< highlight yaml "hl_lines=9">}}
---
- hosts: cloud
gather_facts: no
tasks:
- name: Create a firewall group
local_action:
module: vr_firewall_group
name: web
run_once: yes

- name: Ensure a cloud server exists
local_action:
module: vr_server
name: "{{ inventory_hostname_short }}"
os: "CentOS 7 x64"
plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
region: New Jersey
{{< / highlight >}}

Yes, that would work. However, there is a drawback when a play uses `serial`. The `run_once: yes` would be executed for every batch of host.

That is why I prefer to create a separate play with target `localhost` for the things that only needs to be applied once in total. And it makes things much more clear, let's change that.

{{< highlight yaml "hl_lines=2-7">}}
---
- hosts: localhost
gather_facts: no
tasks:
- name: Create a firewall group
vr_firewall_group:
name: web

- hosts: cloud
gather_facts: no
tasks:
- name: Ensure a cloud server exists
local_action:
module: vr_server
name: "{{ inventory_hostname_short }}"
os: "CentOS 7 x64"
plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
region: New Jersey
{{< / highlight >}}

For the new play with target `localhost`, we skip `local_action` because the scope is already local. Great, let's add some HTTP rules.

## Firewall Rules

For adding firewall rules, we use the `vr_firewall_rule` module. It only creates one rule at a time, but with existing Ansible functionality, we are able to apply a list of rules.

{{% note %}}
There is no need to add an custom SSH rule. An SSH rule is always added by Vultr, but feel free to restrict the acccess to a specific CIDR range!
{{% /note %}}

We place the task right to the first play right after the firewall group task, because it also only needs to be run once.

{{< highlight yaml "hl_lines=9-17">}}
---
- hosts: localhost
gather_facts: no
tasks:
- name: Create a firewall group
vr_firewall_group:
name: web

- name: Create firewall rules
vr_firewall_rule:
group: web
protocol: tcp
port: "{{ item.port }}"
cidr: "{{ item.cidr }}"
with_items:
- { port: 80, cidr: "0.0.0.0/0" }
- { port: 443, cidr: "0.0.0.0/0" }

- hosts: cloud
gather_facts: no
tasks:
- name: Ensure a cloud server exists
local_action:
module: vr_server
name: "{{ inventory_hostname_short }}"
os: "CentOS 7 x64"
plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
region: New Jersey
{{< / highlight >}}

We added 2 rules with a list of dicts and let ansible loop over it to create each rule.

Once this has been set up, our cloud servers only needs to have the firewall group `web` assigned.

{{< highlight yaml "hl_lines=7 28">}}
---
- hosts: localhost
gather_facts: no
tasks:
- name: Create a firewall group
vr_firewall_group:
name: web

- name: Create firewall rules
vr_firewall_rule:
group: web
protocol: tcp
port: "{{ item.port }}"
cidr: "{{ item.cidr }}"
with_items:
- { port: 80, cidr: "0.0.0.0/0" }
- { port: 443, cidr: "0.0.0.0/0" }

- hosts: cloud
gather_facts: no
tasks:
- name: Ensure a cloud server exists
local_action:
module: vr_server
name: "{{ inventory_hostname_short }}"
os: "CentOS 7 x64"
plan: "2048 MB RAM,40 GB SSD,2.00 TB BW"
firewall_group: web
region: New Jersey
{{< / highlight >}}

## Run the Playbook

I think we are ready to run our evolved playbook! I take the chance to show you another super useful feature of the vultr ansible modules, the `--diff` support!

We see exactly what changed:

~~~
$ ansible-playbook playbooks/cloud.yml -i hosts/production --diff
PLAY [localhost] ***************************************************************
TASK [Create a firewall group] *************************************************
--- before
+++ after
@@ -1 +1,3 @@
-{}
+{
+ "description": "web"
+}
changed: [localhost]
TASK [Create firewall rules] ***************************************************
--- before
+++ after
@@ -1 +1,9 @@
-{}
+{
+ "FIREWALLGROUPID": "fda48556",
+ "direction": "in",
+ "ip_type": "v4",
+ "port": 80,
+ "protocol": "tcp",
+ "subnet": "0.0.0.0",
+ "subnet_size": "0"
+}
changed: [localhost] => (item={u'cidr': u'0.0.0.0/0', u'start_port': 80})
--- before
+++ after
@@ -1 +1,9 @@
-{}
+{
+ "FIREWALLGROUPID": "fda48556",
+ "direction": "in",
+ "ip_type": "v4",
+ "port": 443,
+ "protocol": "tcp",
+ "subnet": "0.0.0.0",
+ "subnet_size": "0"
+}
changed: [localhost] => (item={u'cidr': u'0.0.0.0/0', u'start_port': 443})
PLAY [cloud] *******************************************************************
TASK [Ensure a cloud server exists] ********************************************
--- before
+++ after
@@ -1,3 +1,3 @@
{
- "firewall_group": null
+ "firewall_group": "web"
}
changed: [web-01 -> localhost]
PLAY RECAP *********************************************************************
localhost : ok=2 changed=2 unreachable=0 failed=0
web-01 : ok=1 changed=1 unreachable=0 failed=0
~~~

That's it for today. Hope you enjoyed the reading and looking forward to the follow up posts!
Binary file not shown.

0 comments on commit 1360402

Please sign in to comment.
You can’t perform that action at this time.
You signed in with another tab or window. Reload to refresh your session. You signed out in another tab or window. Reload to refresh your session.