aboutsummaryrefslogtreecommitdiffstatshomepage
path: root/roles/firewall
diff options
context:
space:
mode:
Diffstat (limited to 'roles/firewall')
-rw-r--r--roles/firewall/defaults/main.yml10
-rw-r--r--roles/firewall/handlers/main.yml5
-rw-r--r--roles/firewall/tasks/main.yml22
-rw-r--r--roles/firewall/templates/rules.v448
-rw-r--r--roles/firewall/templates/rules.v658
5 files changed, 143 insertions, 0 deletions
diff --git a/roles/firewall/defaults/main.yml b/roles/firewall/defaults/main.yml
new file mode 100644
index 0000000..c2ac034
--- /dev/null
+++ b/roles/firewall/defaults/main.yml
@@ -0,0 +1,10 @@
+firewall_ports_tcp: []
+firewall_ports4_tcp: []
+firewall_ports6_tcp: []
+
+firewall_ports_udp: []
+firewall_ports4_udp: []
+firewall_ports6_udp: []
+
+firewall_rules4: []
+firewall_rules6: []
diff --git a/roles/firewall/handlers/main.yml b/roles/firewall/handlers/main.yml
new file mode 100644
index 0000000..70387f3
--- /dev/null
+++ b/roles/firewall/handlers/main.yml
@@ -0,0 +1,5 @@
+- name: Reload iptables
+ become: true
+ ansible.builtin.systemd_service:
+ name: netfilter-persistent
+ state: restarted
diff --git a/roles/firewall/tasks/main.yml b/roles/firewall/tasks/main.yml
new file mode 100644
index 0000000..9fcf543
--- /dev/null
+++ b/roles/firewall/tasks/main.yml
@@ -0,0 +1,22 @@
+- name: Install iptables-persistent
+ become: true
+ ansible.builtin.apt:
+ install_recommends: false
+ name: iptables-persistent
+ state: present
+
+- name: 'Configure rules'
+ become: true
+ ansible.builtin.template:
+ src: '{{ item.src }}'
+ dest: '{{ item.dest }}'
+ owner: root
+ group: root
+ mode: '640'
+ loop:
+ - {src: rules.v4, dest: /etc/iptables/rules.v4}
+ - {src: rules.v6, dest: /etc/iptables/rules.v6}
+ notify: Reload iptables
+
+- name: Flush handlers
+ ansible.builtin.meta: flush_handlers
diff --git a/roles/firewall/templates/rules.v4 b/roles/firewall/templates/rules.v4
new file mode 100644
index 0000000..7ea1162
--- /dev/null
+++ b/roles/firewall/templates/rules.v4
@@ -0,0 +1,48 @@
+*filter
+
+# By default, drop incoming packets:
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+# By default, accept outgoing packets:
+:OUTPUT ACCEPT [0:0]
+
+# Accept packets for localhost:
+-A INPUT -i lo -j ACCEPT
+
+# Accept any packet for an open connection:
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+
+# The SSH port is always open:
+{% set ssh_port = hostvars[inventory_hostname].ansible_port %}
+
+# Open TCP ports:
+{% set tcp_ports = firewall_ports_tcp + firewall_ports4_tcp + [ssh_port] %}
+{% set tcp_ports = tcp_ports | unique %}
+
+{% for port in tcp_ports %}
+{% set num = port.port if port.port is defined else port %}
+{% set src = '-s ' + port.source if port.source is defined else '' %}
+-A INPUT -p tcp {{ src }} --dport {{ num }} -m conntrack --ctstate NEW -j ACCEPT
+{% endfor %}
+
+# Open UDP ports:
+{% set udp_ports = firewall_ports_udp + firewall_ports4_udp %}
+{% set udp_ports = udp_ports | unique %}
+
+{% for port in udp_ports %}
+{% set num = port.port if port.port is defined else port %}
+{% set src = '-s ' + port.source if port.source is defined else '' %}
+-A INPUT -p udp {{ src }} --dport {{ num }} -j ACCEPT
+{% endfor %}
+
+# Any additional IPv4 rules:
+{{ firewall_rules4 | join('\n') }}
+
+# ICMP; allow only pings and rate-limit them:
+-A INPUT -p icmp --icmp-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT
+
+# Log denies (this must be at the bottom of the file):
+-A INPUT -m limit --limit 3/min -j LOG --log-prefix "iptables denied: " --log-level 4
+
+COMMIT
diff --git a/roles/firewall/templates/rules.v6 b/roles/firewall/templates/rules.v6
new file mode 100644
index 0000000..27bf58b
--- /dev/null
+++ b/roles/firewall/templates/rules.v6
@@ -0,0 +1,58 @@
+*filter
+
+# By default, drop incoming packets:
+:INPUT DROP [0:0]
+:FORWARD DROP [0:0]
+# By default, accept outgoing packets:
+:OUTPUT ACCEPT [0:0]
+
+# Accept packets for localhost:
+-A INPUT -i lo -j ACCEPT
+
+# Accept any packet for an open connection:
+-A INPUT -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+-A FORWARD -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
+
+# The SSH port is always open:
+{% set ssh_port = hostvars[inventory_hostname].ansible_port %}
+
+# Open TCP ports:
+{% set tcp_ports = firewall_ports_tcp + firewall_ports6_tcp + [ssh_port] %}
+{% set tcp_ports = tcp_ports | unique %}
+
+{% for port in tcp_ports %}
+{% set num = port.port if port.port is defined else port %}
+{% set src = '-s ' + port.source if port.source is defined else '' %}
+-A INPUT -p tcp {{ src }} --dport {{ num }} -m conntrack --ctstate NEW -j ACCEPT
+{% endfor %}
+
+# Open UDP ports:
+{% set udp_ports = firewall_ports_udp + firewall_ports6_udp %}
+{% set udp_ports = udp_ports | unique %}
+
+{% for port in udp_ports %}
+{% set num = port.port if port.port is defined else port %}
+{% set src = '-s ' + port.source if port.source is defined else '' %}
+-A INPUT -p udp {{ src }} --dport {{ num }} -j ACCEPT
+{% endfor %}
+
+# Any additional IPv6 rules:
+{{ firewall_rules6 | join('\n') }}
+
+# ICMP; allow only pings and rate-limit them:
+-A INPUT -p icmpv6 --icmpv6-type echo-request -m hashlimit --hashlimit-upto 5/s --hashlimit-mode srcip --hashlimit-srcmask 32 --hashlimit-name icmp-echo-drop -j ACCEPT
+
+# ICMP; IPv6 stuff. To be honest, I don't really understand it; this was copied
+# from trailofbits/algo's rules.v6 template at
+#
+# https://github.com/trailofbits/algo/blob/master/roles/common/templates/rules.v6.j2
+#
+-A INPUT -p icmpv6 --icmpv6-type router-advertisement -m hl --hl-eq 255 -j ACCEPT
+-A INPUT -p icmpv6 --icmpv6-type neighbor-solicitation -m hl --hl-eq 255 -j ACCEPT
+-A INPUT -p icmpv6 --icmpv6-type neighbor-advertisement -m hl --hl-eq 255 -j ACCEPT
+-A INPUT -p icmpv6 --icmpv6-type redirect -m hl --hl-eq 255 -j ACCEPT
+
+# Log denies (this must be at the bottom of the file):
+-A INPUT -m limit --limit 3/min -j LOG --log-prefix "iptables denied: " --log-level 4
+
+COMMIT