diff --git a/roles/common/files/spamhaus-ipv4.xml b/roles/common/files/spamhaus-ipv4.xml new file mode 100644 index 0000000..6854d00 --- /dev/null +++ b/roles/common/files/spamhaus-ipv4.xml @@ -0,0 +1,6 @@ + + + diff --git a/roles/common/files/spamhaus-ipv6.xml b/roles/common/files/spamhaus-ipv6.xml new file mode 100644 index 0000000..445a2a7 --- /dev/null +++ b/roles/common/files/spamhaus-ipv6.xml @@ -0,0 +1,6 @@ + + + diff --git a/roles/common/files/update-spamhaus-lists.service b/roles/common/files/update-spamhaus-lists.service new file mode 100644 index 0000000..ca0bf30 --- /dev/null +++ b/roles/common/files/update-spamhaus-lists.service @@ -0,0 +1,27 @@ +[Unit] +Description=Update Spamhaus lists +# This service will fail if firewalld is not running so we use Requires to make +# sure that firewalld is started. +Requires=firewalld.service +# Make sure the network is up and firewalld is started +After=network-online.target firewalld.service +Wants=network-online.target update-spamhaus-lists.timer + +[Service] +# https://www.ctrl.blog/entry/systemd-service-hardening.html +# Doesn't need access to /home or /root +ProtectHome=true +# Possibly only works on Ubuntu 18.04+ +ProtectKernelTunables=true +ProtectSystem=full +# Newer systemd can use ReadWritePaths to list files, but this works everywhere +ReadWriteDirectories=/etc/firewalld/ipsets +PrivateTmp=true +WorkingDirectory=/var/tmp + +SyslogIdentifier=update-spamhaus-lists +ExecStart=/usr/bin/flock -x update-spamhaus-lists.lck \ + /usr/local/bin/update-spamhaus-lists.sh + +[Install] +WantedBy=multi-user.target diff --git a/roles/common/files/update-spamhaus-lists.sh b/roles/common/files/update-spamhaus-lists.sh new file mode 100755 index 0000000..a05e679 --- /dev/null +++ b/roles/common/files/update-spamhaus-lists.sh @@ -0,0 +1,107 @@ +#!/usr/bin/env bash +# +# update-spamhaus-lists.sh v0.0.5 +# +# Download Spamhaus DROP lists and load them into firewalld ipsets. Should work +# with both the iptables and nftables backends. +# +# See: https://www.spamhaus.org/drop/ +# +# Copyright (C) 2021 Alan Orth +# +# SPDX-License-Identifier: GPL-3.0-only + +# Exit on first error +set -o errexit + +firewalld_ipsets=$(firewall-cmd --get-ipsets) +xml_temp=$(mktemp) +spamhaus_ipv4_ipset_path=/etc/firewalld/ipsets/spamhaus-ipv4.xml +spamhaus_ipv6_ipset_path=/etc/firewalld/ipsets/spamhaus-ipv6.xml + +function download() { + echo "Downloading $1" + wget -q -O - "https://www.spamhaus.org/drop/$1" > "$1" +} + +download drop.txt +download edrop.txt +download dropv6.txt + +if [[ -f "drop.txt" && -f "edrop.txt" ]]; then + echo "Processing IPv4 DROP lists" + + # Extract all networks from drop.txt and edrop.txt, skipping blank lines and + # comments. + networks=$(cat drop.txt edrop.txt | sed -e '/^$/d' -e '/^;.*/d' -e 's/[[:space:]];[[:space:]].*//') + + # If firewalld already has this ipset we should delete it first to emulate + # `ipset flush` (but I don't want to use that because newer hosts might be + # using nftables and firewalld will handle that for us). + if [[ "$firewalld_ipsets" =~ spamhaus-ipv4 ]]; then + echo "Deleting existing spamhaus-ipv4 ipset" + # This deletes the firewalld ipset XML file as well as the ipset itself + firewall-cmd --permanent --delete-ipset=spamhaus-ipv4 + else + echo "Creating placeholder spamhaus-ipv4 ipset" + # Create a placeholder ipset so firewalld doesn't complain when we try + # to reload the ipset later after having added a new XML definition. I + # don't know why, but depending on the system state there may not be a + # ipset defined and firewalld errors on INVALID_IPSET. + firewall-cmd --permanent --new-ipset=spamhaus-ipv4 --type=hash:net --option=family=inet + fi + + # I'm not proud of this, but writing the XML directly is WAY faster than + # using firewall-cmd to add each entry one by one (and we can't add from + # a file because many of our hosts are using old firewalld). + cat << XML_HEAD > "$xml_temp" + + + " >> "$xml_temp" + + install -m 0600 "$xml_temp" "$spamhaus_ipv4_ipset_path" +fi + +if [[ -f "dropv6.txt" ]]; then + echo "Processing IPv6 DROP list" + + networks=$(sed -e '/^$/d' -e '/^;.*/d' -e 's/[[:space:]];[[:space:]].*//' dropv6.txt) + + if [[ "$firewalld_ipsets" =~ spamhaus-ipv6 ]]; then + echo "Deleting existing spamhaus-ipv6 ipset" + firewall-cmd --permanent --delete-ipset=spamhaus-ipv6 + else + echo "Creating placeholder spamhaus-ipv6 ipset" + firewall-cmd --permanent --new-ipset=spamhaus-ipv6 --type=hash:net --option=family=inet6 + fi + + cat << XML_HEAD > "$xml_temp" + + + " >> "$xml_temp" + + install -m 0600 "$xml_temp" "$spamhaus_ipv6_ipset_path" +fi + +echo "Reloading firewalld" +firewall-cmd --reload + +rm -v drop.txt edrop.txt dropv6.txt "$xml_temp" diff --git a/roles/common/files/update-spamhaus-lists.timer b/roles/common/files/update-spamhaus-lists.timer new file mode 100644 index 0000000..626b3ae --- /dev/null +++ b/roles/common/files/update-spamhaus-lists.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Update Spamhaus lists + +[Timer] +# Once a day at midnight +OnCalendar=*-*-* 00:00:00 +# Add a random delay of 0–3600 seconds +RandomizedDelaySec=3600 +Persistent=true + +[Install] +WantedBy=timers.target diff --git a/roles/common/tasks/firewall_Ubuntu.yml b/roles/common/tasks/firewall_Ubuntu.yml index d27661e..eb3619f 100644 --- a/roles/common/tasks/firewall_Ubuntu.yml +++ b/roles/common/tasks/firewall_Ubuntu.yml @@ -54,6 +54,32 @@ loop: - abusers-ipv4.xml - abusers-ipv6.xml + - spamhaus-ipv4.xml + - spamhaus-ipv6.xml + notify: + - restart firewalld + + - name: Copy Spamhaus update script + when: ansible_distribution_version is version('16.04', '>=') + copy: src=update-spamhaus-lists.sh dest=/usr/local/bin/update-spamhaus-lists.sh mode=0755 owner=root group=root + + - name: Copy Spamhaus systemd units + when: ansible_distribution_version is version('16.04', '>=') + copy: src={{ item }} dest=/etc/systemd/system/{{ item }} mode=0644 owner=root group=root + loop: + - update-spamhaus-lists.service + - update-spamhaus-lists.timer + register: spamhaus_systemd_units + + # need to reload to pick up service/timer/environment changes + - name: Reload systemd daemon + systemd: daemon_reload=yes + when: spamhaus_systemd_units is changed + + - name: Start and enable Spamhaus update timer + when: ansible_distribution_version is version('16.04', '>=') + systemd: name=update-spamhaus-lists.timer state=started enabled=yes + notify: - restart firewalld diff --git a/roles/common/templates/public.xml.j2 b/roles/common/templates/public.xml.j2 index 949dbb0..6fe0969 100644 --- a/roles/common/templates/public.xml.j2 +++ b/roles/common/templates/public.xml.j2 @@ -69,4 +69,13 @@ + + + + + + + + +