233 lines
6.8 KiB
YAML
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
|