infra/roles/firewall/tasks/main.yml

233 lines
6.8 KiB
YAML

---
- name: Ensure UFW is installed
apt:
name: ufw
state: present
- name: Configure UFW defaults
command: "ufw {{ item }}"
loop:
- default deny incoming
- default allow outgoing
changed_when: false
- name: Allow SSH through UFW
command: "ufw allow {{ ansible_port | default(22) }}/tcp"
changed_when: false
- name: Rate limit SSH attempts
command: "ufw limit {{ ansible_port | default(22) }}/tcp"
changed_when: false
- name: Allow HTTP through UFW
command: ufw allow 80/tcp
changed_when: false
- name: Allow HTTPS through UFW
command: ufw allow 443/tcp
changed_when: false
- name: Allow Cloudflare IPs through UFW
command: "ufw allow from {{ item }}"
loop: "{{ cloudflare_ips }}"
changed_when: false
- name: Enable UFW
command: ufw --force enable
changed_when: false
- name: Set UFW logging to low
command: ufw logging low
register: ufw_logging
changed_when: "'Logging enabled' in ufw_logging.stdout"
- name: Ensure iptables persistence packages are installed
apt:
name:
- iptables
- iptables-persistent
state: present
- name: Define Cloudflare IP allowlist
set_fact:
cloudflare_ips:
- 173.245.48.0/20
- 103.21.244.0/22
- 103.22.200.0/22
- 103.31.4.0/22
- 141.101.64.0/18
- 108.162.192.0/18
- 190.93.240.0/20
- 188.114.96.0/20
- 197.234.240.0/22
- 198.41.128.0/17
- 162.158.0.0/15
- 104.16.0.0/13
- 104.24.0.0/14
- 172.64.0.0/13
- 131.0.72.0/22
- 2400:cb00::/32
- 2606:4700::/32
- 2803:f800::/32
- 2405:b500::/32
- 2405:8100::/32
- 2a06:98c0::/29
- 2c0f:f248::/32
- name: Remove legacy broad DOCKER-USER allow rules for port 80/443 (IPv4)
shell: |
set -euo pipefail
while iptables -C DOCKER-USER -p tcp -s {{ item }} --dport 80 -j ACCEPT 2>/dev/null; do
iptables -D DOCKER-USER -p tcp -s {{ item }} --dport 80 -j ACCEPT
done
while iptables -C DOCKER-USER -p tcp -s {{ item }} --dport 443 -j ACCEPT 2>/dev/null; do
iptables -D DOCKER-USER -p tcp -s {{ item }} --dport 443 -j ACCEPT
done
args:
executable: /bin/bash
loop: "{{ cloudflare_ips }}"
changed_when: false
failed_when: false
when: "':' not in item"
- name: Remove legacy broad DOCKER-USER drop rule for ports 80/443 (IPv4)
shell: |
set -euo pipefail
while iptables -C DOCKER-USER -p tcp -m multiport --dports 80,443 -j DROP 2>/dev/null; do
iptables -D DOCKER-USER -p tcp -m multiport --dports 80,443 -j DROP
done
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Ensure DOCKER-USER chain has an early accept for established connections (IPv4)
shell: |
set -euo pipefail
iptables -C DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
iptables -I DOCKER-USER 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Allowlist Cloudflare IPs to Docker-published port 80 (IPv4)
shell: |
set -euo pipefail
for iface in docker0 br+; do
iptables -C DOCKER-USER -o "$iface" -p tcp -s {{ item }} --dport 80 -j ACCEPT 2>/dev/null || \
iptables -I DOCKER-USER 2 -o "$iface" -p tcp -s {{ item }} --dport 80 -j ACCEPT
done
args:
executable: /bin/bash
loop: "{{ cloudflare_ips }}"
changed_when: false
failed_when: false
when: "':' not in item"
- name: Allowlist Cloudflare IPs to Docker-published port 443 (IPv4)
shell: |
set -euo pipefail
for iface in docker0 br+; do
iptables -C DOCKER-USER -o "$iface" -p tcp -s {{ item }} --dport 443 -j ACCEPT 2>/dev/null || \
iptables -I DOCKER-USER 2 -o "$iface" -p tcp -s {{ item }} --dport 443 -j ACCEPT
done
args:
executable: /bin/bash
loop: "{{ cloudflare_ips }}"
changed_when: false
failed_when: false
when: "':' not in item"
- name: Drop non-Cloudflare traffic to Docker-published ports 80/443 (IPv4)
shell: |
set -euo pipefail
for iface in docker0 br+; do
iptables -C DOCKER-USER -o "$iface" -p tcp -m multiport --dports 80,443 -j DROP 2>/dev/null || \
iptables -A DOCKER-USER -o "$iface" -p tcp -m multiport --dports 80,443 -j DROP
done
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Ensure DOCKER-USER chain ends with RETURN (IPv4)
shell: |
set -euo pipefail
iptables -C DOCKER-USER -j RETURN 2>/dev/null || iptables -A DOCKER-USER -j RETURN
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Ensure DOCKER-USER chain has an early accept for established connections (IPv6)
shell: |
set -euo pipefail
ip6tables -C DOCKER-USER -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT 2>/dev/null || \
ip6tables -I DOCKER-USER 1 -m conntrack --ctstate RELATED,ESTABLISHED -j ACCEPT
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Allowlist Cloudflare IPs to Docker-published port 80 (IPv6)
shell: |
set -euo pipefail
for iface in docker0 br+; do
ip6tables -C DOCKER-USER -o "$iface" -p tcp -s {{ item }} --dport 80 -j ACCEPT 2>/dev/null || \
ip6tables -I DOCKER-USER 2 -o "$iface" -p tcp -s {{ item }} --dport 80 -j ACCEPT
done
args:
executable: /bin/bash
loop: "{{ cloudflare_ips }}"
changed_when: false
failed_when: false
when: "':' in item"
- name: Allowlist Cloudflare IPs to Docker-published port 443 (IPv6)
shell: |
set -euo pipefail
for iface in docker0 br+; do
ip6tables -C DOCKER-USER -o "$iface" -p tcp -s {{ item }} --dport 443 -j ACCEPT 2>/dev/null || \
ip6tables -I DOCKER-USER 2 -o "$iface" -p tcp -s {{ item }} --dport 443 -j ACCEPT
done
args:
executable: /bin/bash
loop: "{{ cloudflare_ips }}"
changed_when: false
failed_when: false
when: "':' in item"
- name: Drop non-Cloudflare traffic to Docker-published ports 80/443 (IPv6)
shell: |
set -euo pipefail
for iface in docker0 br+; do
ip6tables -C DOCKER-USER -o "$iface" -p tcp -m multiport --dports 80,443 -j DROP 2>/dev/null || \
ip6tables -A DOCKER-USER -o "$iface" -p tcp -m multiport --dports 80,443 -j DROP
done
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Ensure DOCKER-USER chain ends with RETURN (IPv6)
shell: |
set -euo pipefail
ip6tables -C DOCKER-USER -j RETURN 2>/dev/null || ip6tables -A DOCKER-USER -j RETURN
args:
executable: /bin/bash
changed_when: false
failed_when: false
- name: Persist iptables rules
command: netfilter-persistent save
changed_when: false
failed_when: false
- name: Fix sudo hostname resolution (ensure hostname is in /etc/hosts)
lineinfile:
path: /etc/hosts
regexp: '^127\\.0\\.1\\.1\\s+'
line: "127.0.1.1 {{ ansible_facts['hostname'] }}"
state: present
create: true