diff --git a/roles/common/files/abusech-ipv4.nft b/roles/common/files/abusech-ipv4.nft new file mode 100644 index 0000000..04c9f49 --- /dev/null +++ b/roles/common/files/abusech-ipv4.nft @@ -0,0 +1,5 @@ +#!/usr/sbin/nft -f + +define ABUSECH_IPV4 = { +192.168.254.254 +} diff --git a/roles/common/files/update-abusech-nftables.service b/roles/common/files/update-abusech-nftables.service new file mode 100644 index 0000000..14824a3 --- /dev/null +++ b/roles/common/files/update-abusech-nftables.service @@ -0,0 +1,27 @@ +[Unit] +Description=Update Abuse.ch SSL Blacklist IPs +# This service will fail if nftables is not running so we use Requires to make +# sure that nftables is started. +Requires=nftables.service +# Make sure the network is up and nftables is started +After=network-online.target nftables.service +Wants=network-online.target update-abusech-nftables.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/nftables +PrivateTmp=true +WorkingDirectory=/var/tmp + +SyslogIdentifier=update-abusech-nftables +ExecStart=/usr/bin/flock -x update-abusech-nftables.lck \ + /usr/local/bin/update-abusech-nftables.sh + +[Install] +WantedBy=multi-user.target diff --git a/roles/common/files/update-abusech-nftables.sh b/roles/common/files/update-abusech-nftables.sh new file mode 100755 index 0000000..6827ace --- /dev/null +++ b/roles/common/files/update-abusech-nftables.sh @@ -0,0 +1,63 @@ +#!/usr/bin/env bash +# +# update-abuseipdb-nftables.sh v0.0.1 +# +# Download IP addresses seen using a blacklisted SSL certificate and load them +# into nftables sets. As of 2021-07-28 these appear to only be IPv4. +# +# See: https://sslbl.abuse.ch/blacklist +# +# Copyright (C) 2021 Alan Orth +# +# SPDX-License-Identifier: GPL-3.0-only + +# Exit on first error +set -o errexit + +abusech_ipv4_set_path=/etc/nftables/abusech-ipv4.nft +abusech_list_temp=$(mktemp) + +echo "Downloading Abuse.sh SSL Blacklist IPs" + +abusech_response=$(curl -s -G -w "%{http_code}\n" https://sslbl.abuse.ch/blacklist/sslipblacklist.txt --output "$abusech_list_temp") + +if [[ $abusech_response -ne 200 ]]; then + echo "Abuse.ch responded: HTTP $abusech_response" + + exit 1 +fi + +if [[ -f "$abusech_list_temp" ]]; then + echo "Processing IPv4 list" + + abusech_ipv4_list_temp=$(mktemp) + abusech_ipv4_set_temp=$(mktemp) + + # Remove comments, DOS carriage returns, and IPv6 addresses (even though + # Abuse.ch seems to only have IPv4 addresses, let's not break our shit on + # that assumption some time down the line). + sed -e '/#/d' -e 's/ //' -e '/:/d' "$abusech_list_temp" > "$abusech_ipv4_list_temp" + + echo "Building abusech-ipv4 set" + cat << NFT_HEAD > "$abusech_ipv4_set_temp" +#!/usr/sbin/nft -f + +define ABUSECH_IPV4 = { +NFT_HEAD + + while read -r network; do + # nftables doesn't mind if the last element in the set has a trailing + # comma so we don't need to do anything special here. + echo "$network," >> "$abusech_ipv4_set_temp" + done < $abusech_ipv4_list_temp + + echo "}" >> "$abusech_ipv4_set_temp" + + install -m 0600 "$abusech_ipv4_set_temp" "$abusech_ipv4_set_path" + + rm -f "$abusech_list_temp" "$abusech_ipv4_list_temp" "$abusech_ipv4_set_temp" +fi + +echo "Reloading nftables" +# The abusech nftables sets are included by nftables.conf +/usr/sbin/nft -f /etc/nftables.conf diff --git a/roles/common/files/update-abusech-nftables.timer b/roles/common/files/update-abusech-nftables.timer new file mode 100644 index 0000000..ef91f7b --- /dev/null +++ b/roles/common/files/update-abusech-nftables.timer @@ -0,0 +1,12 @@ +[Unit] +Description=Update Abuse.ch SSL Blacklist IPs + +[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_Debian.yml b/roles/common/tasks/firewall_Debian.yml index 824dead..8bee761 100644 --- a/roles/common/tasks/firewall_Debian.yml +++ b/roles/common/tasks/firewall_Debian.yml @@ -39,6 +39,7 @@ loop: - spamhaus-ipv4.nft - spamhaus-ipv6.nft + - abusech-ipv4.nft notify: - reload nftables @@ -102,20 +103,23 @@ loop: - update-spamhaus-nftables.sh - aggregate-cidr-addresses.pl + - update-abusech-nftables.sh - - name: Copy Spamhaus nftables systemd units + - name: Copy nftables systemd units when: ansible_distribution_version is version('11', '>=') copy: src={{ item }} dest=/etc/systemd/system/{{ item }} mode=0644 owner=root group=root loop: - update-spamhaus-nftables.service - update-spamhaus-nftables.timer - register: spamhaus_nftables_systemd_units + - update-abusech-nftables.service + - update-abusech-nftables.timer + register: nftables_systemd_units # need to reload to pick up service/timer/environment changes - name: Reload systemd daemon systemd: daemon_reload=yes when: spamhaus_firewalld_systemd_units is changed or - spamhaus_nftables_systemd_units is changed + nftables_systemd_units is changed - name: Start and enable Spamhaus firewalld update timer when: ansible_distribution_version is version('10', '<=') @@ -123,9 +127,12 @@ notify: - restart firewalld - - name: Start and enable Spamhaus nftables update timer + - name: Start and enable nftables update timers when: ansible_distribution_version is version('11', '>=') - systemd: name=update-spamhaus-nftables.timer state=started enabled=yes + systemd: name={{ item }} state=started enabled=yes + loop: + - update-spamhaus-nftables.timer + - update-abusech-nftables.timer - name: Start and enable nftables when: ansible_distribution_major_version is version('11', '>=') diff --git a/roles/common/tasks/firewall_Ubuntu.yml b/roles/common/tasks/firewall_Ubuntu.yml index 11a9625..7710fce 100644 --- a/roles/common/tasks/firewall_Ubuntu.yml +++ b/roles/common/tasks/firewall_Ubuntu.yml @@ -45,6 +45,7 @@ loop: - spamhaus-ipv4.nft - spamhaus-ipv6.nft + - abusech-ipv4.nft notify: - reload nftables @@ -81,26 +82,29 @@ - update-spamhaus-lists.timer register: spamhaus_firewalld_systemd_units - - name: Copy Spamhaus nftables update scripts + - name: Copy nftables update scripts when: ansible_distribution_version is version('20.04', '>=') copy: src={{ item }} dest=/usr/local/bin/{{ item }} mode=0755 owner=root group=root loop: - update-spamhaus-nftables.sh - aggregate-cidr-addresses.pl + - update-abusech-nftables.sh - - name: Copy Spamhaus nftables systemd units + - name: Copy nftables systemd units when: ansible_distribution_version is version('20.04', '>=') copy: src={{ item }} dest=/etc/systemd/system/{{ item }} mode=0644 owner=root group=root loop: - update-spamhaus-nftables.service - update-spamhaus-nftables.timer - register: spamhaus_nftables_systemd_units + - update-abusech-nftables.service + - update-abusech-nftables.timer + register: nftables_systemd_units # need to reload to pick up service/timer/environment changes - name: Reload systemd daemon systemd: daemon_reload=yes when: spamhaus_firewalld_systemd_units is changed or - spamhaus_nftables_systemd_units is changed + nftables_systemd_units is changed - name: Start and enable Spamhaus firewalld update timer when: ansible_distribution_version is version('18.04', '<=') @@ -108,9 +112,12 @@ notify: - restart firewalld - - name: Start and enable Spamhaus nftables update timer + - name: Start and enable nftables update timers when: ansible_distribution_version is version('20.04', '>=') - systemd: name=update-spamhaus-nftables.timer state=started enabled=yes + systemd: name={{ item }} state=started enabled=yes + loop: + - update-spamhaus-nftables.timer + - update-abusech-nftables.timer - name: Start and enable nftables when: ansible_distribution_version is version('20.04', '>=') diff --git a/roles/common/templates/nftables.conf.j2 b/roles/common/templates/nftables.conf.j2 index 16754f5..90e263d 100755 --- a/roles/common/templates/nftables.conf.j2 +++ b/roles/common/templates/nftables.conf.j2 @@ -9,6 +9,9 @@ flush ruleset include "/etc/nftables/spamhaus-ipv4.nft" include "/etc/nftables/spamhaus-ipv6.nft" +# Lists updated daily by update-abusech-nftables.sh +include "/etc/nftables/abusech-ipv4.nft" + # Notes: # - tables hold chains, chains hold rules # - inet is for both ipv4 and ipv6 @@ -26,6 +29,11 @@ table inet filter { elements = $SPAMHAUS_IPV6 } + set abusech-ipv4 { + type ipv4_addr + elements = $ABUSECH_IPV4 + } + chain input { type filter hook input priority 0; @@ -39,6 +47,9 @@ table inet filter { ip saddr @spamhaus-ipv4 counter drop ip6 saddr @spamhaus-ipv6 counter drop + # Drop packets matching the abusech set early. + ip saddr @abusech-ipv4 counter drop + # Allow loopback traffic. iifname lo accept @@ -83,5 +94,8 @@ table inet filter { # Drop outgoing packets matching the spamhaus sets too ip daddr @spamhaus-ipv4 counter drop ip6 daddr @spamhaus-ipv6 counter drop + + # Drop outgoing packets matching the abusech sets too + ip daddr @abusech-ipv4 counter drop } }