Skip to content

Commit

Permalink
Merge branch 'maloddon-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
mikegleasonjr committed Oct 25, 2016
2 parents 4db8c81 + bfaa43b commit 175aad8
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 22 deletions.
47 changes: 36 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,11 @@ After I found out `UFW` was too limited in terms of functionalities, I tried sev
- Simplicity (not having to learn how role variables would generate the rules)
- Persistence (reload the rules at boot)

This role is an attempt to solve these requirements. It currently supports only ipv4 on Debian and RedHat distributions.
This role is an attempt to solve these requirements.

It supports **ipv4** and **ipv6*** on Debian and RedHat distributions.

*ipv6 support was brought up thanks to [@maloddon](https://github.com/maloddon). It is currently in early stages and knowledgable people should review the [default rules](https://github.com/mikegleasonjr/ansible-role-firewall/blob/ipv6/defaults/main.yml). ipv6 rules are not configured by default. If you which to use them, don't forget to set `firewall_v6_configure` to `true`.

Requirements
------------
Expand All @@ -28,9 +32,12 @@ Installation
Role Variables
--------------

There are only 3 dictionaries to override in `defaults/main.yml`:
`defaults/main.yml`:

```
firewall_v4_configure: true
firewall_v6_configure: false
firewall_v4_default_rules:
001 default policies:
- -P INPUT ACCEPT
Expand All @@ -47,16 +54,34 @@ firewall_v4_default_rules:
- -A INPUT -p tcp --dport ssh -j ACCEPT
999 drop everything:
- -P INPUT DROP
firewall_v4_group_rules: {}
firewall_v4_host_rules: {}
firewall_v6_default_rules:
001 default policies:
- -P INPUT ACCEPT
- -P OUTPUT ACCEPT
- -P FORWARD DROP
002 allow loopback:
- -A INPUT -i lo -s ::1/128 -d ::1/128 -j ACCEPT
- -A INPUT -i lo -s fe80::/64 -d fe80::/64 -j ACCEPT
003 allow ping replies:
- -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
- -A OUTPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
100 allow established related:
- -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
200 allow ssh:
- -A INPUT -p tcp --dport ssh -j ACCEPT
999 drop everything:
- -P INPUT DROP
firewall_v6_group_rules: {}
firewall_v6_host_rules: {}
```

The keys to the dictionaries (`001 default policies`, `002 allow loopback`, ...) can be anything. They are only used for rules **ordering** and **overriding** (explained later). On rules generation, the keys are sorted alphabetically. Hence the 001s and 999s.
The keys to the `*_rules` dictionaries (`001 default policies`, `002 allow loopback`, ...) can be anything. They are only used for rules **ordering** and **overriding**. On rules generation, the keys are sorted alphabetically. That's why I chose here the 001s and 999s.

Those defaults will generate the following script to be executed on the host:
Those defaults will generate the following script to be executed on the host (for ipv4):

```
#!/bin/sh
Expand Down Expand Up @@ -94,7 +119,7 @@ iptables -A INPUT -p tcp --dport ssh -j ACCEPT
iptables -P INPUT DROP
```

As you can see, the rules are ordered by the dictionary key. You can also observe that you can do pretty much what you want with the rules. In fact, the rules defined in the variables are simply the same rules you would pass to the `iptables` command. You have complete control over the rules syntax.
As you can see, you have complete control over the rules syntax.

`$ iptables -L -n` on the host then shows...

Expand All @@ -116,11 +141,11 @@ ACCEPT icmp -- 0.0.0.0/0 0.0.0.0/0 icmptype 0

Now that takes care of the default rules. What about overriding?

The role provides 2 more variables where you can define more rules. Rules defined in those variables will be merged with the default rules. In fact, rules in `firewall_v4_host_rules` will be merged with `firewall_v4_group_rules`, and then the result will be merged back with the defaults.
You can change the rules for specific hosts and groups instead of re-defining everything. Rules in `firewall_v4_host_rules` will be merged with `firewall_v4_group_rules`, and then the result will be merged back with the defaults. Same thing for ipv6.

This allows 3 levels of rules definition and overriding. I simply chose the names to match how the variable precedence works in Ansible (`all` -> `group` -> `host`). See the example playbook below to see rules overriding in action.

Example Playbook
Example Playbook (ipv4)
----------------

```
Expand Down Expand Up @@ -169,11 +194,11 @@ firewall_v4_host_rules:
200 allow ssh limiting brute force: []
```

That's right, to "delete" rules, you just assign an empty list to an existing dictionary key.
To "delete" rules, you just assign an empty list to an existing dictionary key.

To summarize, rules in `firewall_v4_host_rules` will overwrite rules in `firewall_v4_group_rules`, and then rules in `firewall_v4_group_rules` will overwrite rules in `firewall_v4_default_rules`.

You can play with the rules and see the generated script on the host at the following location: `/etc/iptables.v4.generated`.
You can play with the rules and see the generated script on the host at the following location: `/etc/iptables.v4.generated` and `/etc/iptables.v6.generated`.

Dependencies
------------
Expand Down
24 changes: 22 additions & 2 deletions defaults/main.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
---
firewall_v4_configure: true
firewall_v6_configure: false

firewall_v4_default_rules:
001 default policies:
- -P INPUT ACCEPT
Expand All @@ -15,7 +18,24 @@ firewall_v4_default_rules:
- -A INPUT -p tcp --dport ssh -j ACCEPT
999 drop everything:
- -P INPUT DROP

firewall_v4_group_rules: {}

firewall_v4_host_rules: {}

firewall_v6_default_rules:
001 default policies:
- -P INPUT ACCEPT
- -P OUTPUT ACCEPT
- -P FORWARD DROP
002 allow loopback:
- -A INPUT -i lo -s ::1/128 -d ::1/128 -j ACCEPT
003 allow ping replies:
- -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
- -A OUTPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
100 allow established related:
- -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
200 allow ssh:
- -A INPUT -p tcp --dport ssh -j ACCEPT
999 drop everything:
- -P INPUT DROP
firewall_v6_group_rules: {}
firewall_v6_host_rules: {}
26 changes: 17 additions & 9 deletions tasks/persist-debian.yml
Original file line number Diff line number Diff line change
@@ -1,20 +1,18 @@
---
- name: Remove any obsolete v4 save script
file: path=/etc/network/if-post-down.d/iptables-v4 state=absent

- name: Remove any obsolete v4 restore script
file: path=/etc/network/if-pre-up.d/iptables-v4 state=absent

- name: Remove any obsolete v4 saved rules
file: path=/etc/iptables.v4.saved state=absent
- name: Remove any obsolete scripts used by an old version of the role
file: path={{ item }} state=absent
with_items:
- /etc/network/if-post-down.d/iptables-v4
- /etc/network/if-pre-up.d/iptables-v4
- /etc/iptables.v4.saved

- name: Install iptables-persistent
apt: name=iptables-persistent state=present

- name: Check if netfilter-persistent is present
shell: which netfilter-persistent
register: is_netfilter
when: v4_script|changed
when: v4_script|changed or v6_script|changed
changed_when: false
ignore_errors: yes

Expand All @@ -25,3 +23,13 @@
- name: Save v4 rules (iptables-persistent)
command: /etc/init.d/iptables-persistent save
when: v4_script|changed and is_netfilter.rc == 1

- name: Save v6 rules (netfilter-persistent)
command: netfilter-persistent save
when: v6_script|changed and is_netfilter.rc == 0

- name: Save v6 rules (iptables-persistent)
command: /etc/init.d/iptables-persistent save
when: v6_script|changed and is_netfilter.rc == 1


4 changes: 4 additions & 0 deletions tasks/persist-redhat.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,10 @@
shell: iptables-save -c > /etc/sysconfig/iptables
when: v4_script|changed

- name: Save v6 rules (/etc/sysconfig/ip6tables)
shell: iptables-save -c > /etc/sysconfig/ip6tables
when: v6_script|changed

- name: Ensure iptables service is installed
yum: name=iptables-services state=present
when: ansible_distribution_major_version >= '7'
Expand Down
12 changes: 12 additions & 0 deletions tasks/rules.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,21 @@
- name: Generate v4 rules
template: src=generated.v4.j2 dest=/etc/iptables.v4.generated owner=root group=root mode=755
register: v4_script
when: firewall_v4_configure

- name: Load v4 rules
command: /etc/iptables.v4.generated
register: v4_script_load_result
failed_when: v4_script_load_result.rc != 0 or 'unknown option' in v4_script_load_result.stderr
when: v4_script|changed

- name: Generate v6 rules
template: src=generated.v6.j2 dest=/etc/iptables.v6.generated owner=root group=root mode=755
register: v6_script
when: firewall_v6_configure

- name: Load v6 rules
command: /etc/iptables.v6.generated
register: v6_script_load_result
failed_when: v6_script_load_result.rc != 0 or 'unknown option' in v6_script_load_result.stderr
when: v6_script|changed
26 changes: 26 additions & 0 deletions templates/generated.v6.j2
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#!/bin/sh
# {{ ansible_managed }}
{% set merged = firewall_v6_default_rules.copy() %}
{% set _ = merged.update(firewall_v6_group_rules) %}
{% set _ = merged.update(firewall_v6_host_rules) %}

# flush rules & delete user-defined chains
ip6tables -F
ip6tables -X
ip6tables -t raw -F
ip6tables -t raw -X
ip6tables -t nat -F
ip6tables -t nat -X
ip6tables -t mangle -F
ip6tables -t mangle -X

{% for group, rules in merged|dictsort %}
# {{ group }}
{% if not rules %}
# (none)
{% endif %}
{% for rule in rules %}
ip6tables {{ rule }}
{% endfor %}

{% endfor %}
30 changes: 30 additions & 0 deletions tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

roles:
- role: .
firewall_v6_configure: true

firewall_v4_group_rules:
400 allow http:
- -A INPUT -p tcp --dport http -j ACCEPT
Expand All @@ -12,11 +14,22 @@
firewall_v4_host_rules:
400 allow 7890: []

firewall_v6_group_rules:
400 allow http:
- -A INPUT -p tcp --dport http -j ACCEPT
400 allow 7890:
- -A INPUT -p tcp --dport 7890 -j ACCEPT
firewall_v6_host_rules:
400 allow 7890: []

tasks:
- name: Retrieve v4 rules
command: iptables -L -n
changed_when: false
register: v4_rules
- name: Check that INPUT policy has been applied
assert:
that: "'Chain INPUT (policy DROP' in v4_rules.stdout"
- name: Check that a default rule has been applied
assert:
that: "'tcp dpt:22' in v4_rules.stdout"
Expand All @@ -26,3 +39,20 @@
- name: Check that deleted rules are deleted
assert:
that: "'tcp dpt:7890' not in v4_rules.stdout"

- name: Retrieve v6 rules
command: ip6tables -L -n
changed_when: false
register: v6_rules
- name: Check that INPUT policy has been applied
assert:
that: "'Chain INPUT (policy DROP' in v6_rules.stdout"
- name: Check that a default rule has been applied
assert:
that: "'tcp dpt:22' in v6_rules.stdout"
- name: Check that a group rule has been applied
assert:
that: "'tcp dpt:80' in v6_rules.stdout"
- name: Check that deleted rules are deleted
assert:
that: "'tcp dpt:7890' not in v6_rules.stdout"

0 comments on commit 175aad8

Please sign in to comment.