From d7c34a30a3087fcf432a1c5ac68ca85290cb45d5 Mon Sep 17 00:00:00 2001 From: Alan Orth Date: Wed, 21 Jul 2021 09:34:51 +0300 Subject: [PATCH] roles/common: Add Spamhaus DROP lists to firewalld ipsets This configures the recommended DROP, EDROP, and DROPv6 lists from Spamhaus as ipsets in firewalld. First we copy an empty placeholder ipset to seed firewalld, then we use a shell script to download the real lists and activate them. The same shell script is run daily as a service (update-spamhaus-lists.service) by a systemd timer. I am strictly avoiding any direct ipset commands here because I want to make sure that this works on older hosts where ipsets is used as well as newer hosts that have moved to nftables such as Ubuntu 20.04. So far I have tested this on Ubuntu 16.04, 18.04, and 20.04, but ev- entually I need to abstract the tasks and run them on CentOS 7+ as well. See: https://www.spamhaus.org/drop/ --- roles/common/files/spamhaus-ipv4.xml | 6 + roles/common/files/spamhaus-ipv6.xml | 6 + .../files/update-spamhaus-lists.service | 27 +++++ roles/common/files/update-spamhaus-lists.sh | 107 ++++++++++++++++++ .../common/files/update-spamhaus-lists.timer | 12 ++ roles/common/tasks/firewall_Ubuntu.yml | 26 +++++ roles/common/templates/public.xml.j2 | 9 ++ 7 files changed, 193 insertions(+) create mode 100644 roles/common/files/spamhaus-ipv4.xml create mode 100644 roles/common/files/spamhaus-ipv6.xml create mode 100644 roles/common/files/update-spamhaus-lists.service create mode 100755 roles/common/files/update-spamhaus-lists.sh create mode 100644 roles/common/files/update-spamhaus-lists.timer 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 @@ + + + + + + + + +