Skip to content

Commit 175aad8

Browse files
committed
Merge branch 'maloddon-master'
2 parents 4db8c81 + bfaa43b commit 175aad8

File tree

7 files changed

+147
-22
lines changed

7 files changed

+147
-22
lines changed

README.md

+36-11
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,11 @@ After I found out `UFW` was too limited in terms of functionalities, I tried sev
1313
- Simplicity (not having to learn how role variables would generate the rules)
1414
- Persistence (reload the rules at boot)
1515

16-
This role is an attempt to solve these requirements. It currently supports only ipv4 on Debian and RedHat distributions.
16+
This role is an attempt to solve these requirements.
17+
18+
It supports **ipv4** and **ipv6*** on Debian and RedHat distributions.
19+
20+
*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`.
1721

1822
Requirements
1923
------------
@@ -28,9 +32,12 @@ Installation
2832
Role Variables
2933
--------------
3034

31-
There are only 3 dictionaries to override in `defaults/main.yml`:
35+
`defaults/main.yml`:
3236

3337
```
38+
firewall_v4_configure: true
39+
firewall_v6_configure: false
40+
3441
firewall_v4_default_rules:
3542
001 default policies:
3643
- -P INPUT ACCEPT
@@ -47,16 +54,34 @@ firewall_v4_default_rules:
4754
- -A INPUT -p tcp --dport ssh -j ACCEPT
4855
999 drop everything:
4956
- -P INPUT DROP
50-
5157
firewall_v4_group_rules: {}
52-
5358
firewall_v4_host_rules: {}
5459
60+
firewall_v6_default_rules:
61+
001 default policies:
62+
- -P INPUT ACCEPT
63+
- -P OUTPUT ACCEPT
64+
- -P FORWARD DROP
65+
002 allow loopback:
66+
- -A INPUT -i lo -s ::1/128 -d ::1/128 -j ACCEPT
67+
- -A INPUT -i lo -s fe80::/64 -d fe80::/64 -j ACCEPT
68+
003 allow ping replies:
69+
- -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
70+
- -A OUTPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
71+
100 allow established related:
72+
- -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
73+
200 allow ssh:
74+
- -A INPUT -p tcp --dport ssh -j ACCEPT
75+
999 drop everything:
76+
- -P INPUT DROP
77+
firewall_v6_group_rules: {}
78+
firewall_v6_host_rules: {}
79+
5580
```
5681

57-
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.
82+
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.
5883

59-
Those defaults will generate the following script to be executed on the host:
84+
Those defaults will generate the following script to be executed on the host (for ipv4):
6085

6186
```
6287
#!/bin/sh
@@ -94,7 +119,7 @@ iptables -A INPUT -p tcp --dport ssh -j ACCEPT
94119
iptables -P INPUT DROP
95120
```
96121

97-
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.
122+
As you can see, you have complete control over the rules syntax.
98123

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

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

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

119-
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.
144+
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.
120145

121146
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.
122147

123-
Example Playbook
148+
Example Playbook (ipv4)
124149
----------------
125150

126151
```
@@ -169,11 +194,11 @@ firewall_v4_host_rules:
169194
200 allow ssh limiting brute force: []
170195
```
171196

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

174199
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`.
175200

176-
You can play with the rules and see the generated script on the host at the following location: `/etc/iptables.v4.generated`.
201+
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`.
177202

178203
Dependencies
179204
------------

defaults/main.yml

+22-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
---
2+
firewall_v4_configure: true
3+
firewall_v6_configure: false
4+
25
firewall_v4_default_rules:
36
001 default policies:
47
- -P INPUT ACCEPT
@@ -15,7 +18,24 @@ firewall_v4_default_rules:
1518
- -A INPUT -p tcp --dport ssh -j ACCEPT
1619
999 drop everything:
1720
- -P INPUT DROP
18-
1921
firewall_v4_group_rules: {}
20-
2122
firewall_v4_host_rules: {}
23+
24+
firewall_v6_default_rules:
25+
001 default policies:
26+
- -P INPUT ACCEPT
27+
- -P OUTPUT ACCEPT
28+
- -P FORWARD DROP
29+
002 allow loopback:
30+
- -A INPUT -i lo -s ::1/128 -d ::1/128 -j ACCEPT
31+
003 allow ping replies:
32+
- -A INPUT -p icmpv6 --icmpv6-type echo-request -j ACCEPT
33+
- -A OUTPUT -p icmpv6 --icmpv6-type echo-reply -j ACCEPT
34+
100 allow established related:
35+
- -A INPUT -m conntrack --ctstate ESTABLISHED,RELATED -j ACCEPT
36+
200 allow ssh:
37+
- -A INPUT -p tcp --dport ssh -j ACCEPT
38+
999 drop everything:
39+
- -P INPUT DROP
40+
firewall_v6_group_rules: {}
41+
firewall_v6_host_rules: {}

tasks/persist-debian.yml

+17-9
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,18 @@
11
---
2-
- name: Remove any obsolete v4 save script
3-
file: path=/etc/network/if-post-down.d/iptables-v4 state=absent
4-
5-
- name: Remove any obsolete v4 restore script
6-
file: path=/etc/network/if-pre-up.d/iptables-v4 state=absent
7-
8-
- name: Remove any obsolete v4 saved rules
9-
file: path=/etc/iptables.v4.saved state=absent
2+
- name: Remove any obsolete scripts used by an old version of the role
3+
file: path={{ item }} state=absent
4+
with_items:
5+
- /etc/network/if-post-down.d/iptables-v4
6+
- /etc/network/if-pre-up.d/iptables-v4
7+
- /etc/iptables.v4.saved
108

119
- name: Install iptables-persistent
1210
apt: name=iptables-persistent state=present
1311

1412
- name: Check if netfilter-persistent is present
1513
shell: which netfilter-persistent
1614
register: is_netfilter
17-
when: v4_script|changed
15+
when: v4_script|changed or v6_script|changed
1816
changed_when: false
1917
ignore_errors: yes
2018

@@ -25,3 +23,13 @@
2523
- name: Save v4 rules (iptables-persistent)
2624
command: /etc/init.d/iptables-persistent save
2725
when: v4_script|changed and is_netfilter.rc == 1
26+
27+
- name: Save v6 rules (netfilter-persistent)
28+
command: netfilter-persistent save
29+
when: v6_script|changed and is_netfilter.rc == 0
30+
31+
- name: Save v6 rules (iptables-persistent)
32+
command: /etc/init.d/iptables-persistent save
33+
when: v6_script|changed and is_netfilter.rc == 1
34+
35+

tasks/persist-redhat.yml

+4
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
shell: iptables-save -c > /etc/sysconfig/iptables
44
when: v4_script|changed
55

6+
- name: Save v6 rules (/etc/sysconfig/ip6tables)
7+
shell: iptables-save -c > /etc/sysconfig/ip6tables
8+
when: v6_script|changed
9+
610
- name: Ensure iptables service is installed
711
yum: name=iptables-services state=present
812
when: ansible_distribution_major_version >= '7'

tasks/rules.yml

+12
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,21 @@
22
- name: Generate v4 rules
33
template: src=generated.v4.j2 dest=/etc/iptables.v4.generated owner=root group=root mode=755
44
register: v4_script
5+
when: firewall_v4_configure
56

67
- name: Load v4 rules
78
command: /etc/iptables.v4.generated
89
register: v4_script_load_result
910
failed_when: v4_script_load_result.rc != 0 or 'unknown option' in v4_script_load_result.stderr
1011
when: v4_script|changed
12+
13+
- name: Generate v6 rules
14+
template: src=generated.v6.j2 dest=/etc/iptables.v6.generated owner=root group=root mode=755
15+
register: v6_script
16+
when: firewall_v6_configure
17+
18+
- name: Load v6 rules
19+
command: /etc/iptables.v6.generated
20+
register: v6_script_load_result
21+
failed_when: v6_script_load_result.rc != 0 or 'unknown option' in v6_script_load_result.stderr
22+
when: v6_script|changed

templates/generated.v6.j2

+26
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
#!/bin/sh
2+
# {{ ansible_managed }}
3+
{% set merged = firewall_v6_default_rules.copy() %}
4+
{% set _ = merged.update(firewall_v6_group_rules) %}
5+
{% set _ = merged.update(firewall_v6_host_rules) %}
6+
7+
# flush rules & delete user-defined chains
8+
ip6tables -F
9+
ip6tables -X
10+
ip6tables -t raw -F
11+
ip6tables -t raw -X
12+
ip6tables -t nat -F
13+
ip6tables -t nat -X
14+
ip6tables -t mangle -F
15+
ip6tables -t mangle -X
16+
17+
{% for group, rules in merged|dictsort %}
18+
# {{ group }}
19+
{% if not rules %}
20+
# (none)
21+
{% endif %}
22+
{% for rule in rules %}
23+
ip6tables {{ rule }}
24+
{% endfor %}
25+
26+
{% endfor %}

tests.yml

+30
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44

55
roles:
66
- role: .
7+
firewall_v6_configure: true
8+
79
firewall_v4_group_rules:
810
400 allow http:
911
- -A INPUT -p tcp --dport http -j ACCEPT
@@ -12,11 +14,22 @@
1214
firewall_v4_host_rules:
1315
400 allow 7890: []
1416

17+
firewall_v6_group_rules:
18+
400 allow http:
19+
- -A INPUT -p tcp --dport http -j ACCEPT
20+
400 allow 7890:
21+
- -A INPUT -p tcp --dport 7890 -j ACCEPT
22+
firewall_v6_host_rules:
23+
400 allow 7890: []
24+
1525
tasks:
1626
- name: Retrieve v4 rules
1727
command: iptables -L -n
1828
changed_when: false
1929
register: v4_rules
30+
- name: Check that INPUT policy has been applied
31+
assert:
32+
that: "'Chain INPUT (policy DROP' in v4_rules.stdout"
2033
- name: Check that a default rule has been applied
2134
assert:
2235
that: "'tcp dpt:22' in v4_rules.stdout"
@@ -26,3 +39,20 @@
2639
- name: Check that deleted rules are deleted
2740
assert:
2841
that: "'tcp dpt:7890' not in v4_rules.stdout"
42+
43+
- name: Retrieve v6 rules
44+
command: ip6tables -L -n
45+
changed_when: false
46+
register: v6_rules
47+
- name: Check that INPUT policy has been applied
48+
assert:
49+
that: "'Chain INPUT (policy DROP' in v6_rules.stdout"
50+
- name: Check that a default rule has been applied
51+
assert:
52+
that: "'tcp dpt:22' in v6_rules.stdout"
53+
- name: Check that a group rule has been applied
54+
assert:
55+
that: "'tcp dpt:80' in v6_rules.stdout"
56+
- name: Check that deleted rules are deleted
57+
assert:
58+
that: "'tcp dpt:7890' not in v6_rules.stdout"

0 commit comments

Comments
 (0)