firewall: replace iptables with nftables (#2505)

* first adjustments to use Fail2Ban with nftables

* replace `iptables` -> `nftables` and adjust tests

nftables lists IPs a bit differently , so the order was adjusted for the
tests to be more flexible.

* line correction in mailserver.env

* change from `.conf` -> `.local` and remove redundant config

* revert HEREDOC to `echo`

Co-authored-by: Casper <casperklein@users.noreply.github.com>
This commit is contained in:
Georg Lauterbach 2022-04-05 15:13:59 +02:00 committed by GitHub
parent 7c150402a0
commit a9305a073f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 42 additions and 72 deletions

View file

@ -55,10 +55,10 @@ RUN \
dovecot-ldap dovecot-lmtpd dovecot-managesieved dovecot-pop3d \ dovecot-ldap dovecot-lmtpd dovecot-managesieved dovecot-pop3d \
dovecot-sieve dovecot-solr dumb-init \ dovecot-sieve dovecot-solr dumb-init \
# E - O # E - O
ed fetchmail file gamin gnupg gzip iproute2 iptables \ ed fetchmail file gamin gnupg gzip iproute2 \
locales logwatch lhasa libdate-manip-perl libldap-common liblz4-tool \ locales logwatch lhasa libdate-manip-perl libldap-common liblz4-tool \
libmail-spf-perl libnet-dns-perl libsasl2-modules lrzip lzop \ libmail-spf-perl libnet-dns-perl libsasl2-modules lrzip lzop \
netcat-openbsd nomarch opendkim opendkim-tools opendmarc \ netcat-openbsd nftables nomarch opendkim opendkim-tools opendmarc \
# P - Z # P - Z
pax pflogsumm postgrey p7zip-full postfix-ldap postfix-pcre \ pax pflogsumm postgrey p7zip-full postfix-ldap postfix-pcre \
postfix-policyd-spf-python postsrsd pyzor \ postfix-policyd-spf-python postsrsd pyzor \
@ -194,11 +194,6 @@ COPY target/opendmarc/opendmarc.conf /etc/opendmarc.conf
COPY target/opendmarc/default-opendmarc /etc/default/opendmarc COPY target/opendmarc/default-opendmarc /etc/default/opendmarc
COPY target/opendmarc/ignore.hosts /etc/opendmarc/ignore.hosts COPY target/opendmarc/ignore.hosts /etc/opendmarc/ignore.hosts
RUN \
# switch iptables and ip6tables to legacy for Fail2Ban
update-alternatives --set iptables /usr/sbin/iptables-legacy && \
update-alternatives --set ip6tables /usr/sbin/ip6tables-legacy
# ----------------------------------------------- # -----------------------------------------------
# --- Fetchmail, Postfix & Let'sEncrypt --------- # --- Fetchmail, Postfix & Let'sEncrypt ---------
# ----------------------------------------------- # -----------------------------------------------

View file

@ -16,6 +16,6 @@
#ignoreip = 127.0.0.1/8 #ignoreip = 127.0.0.1/8
# Default ban action # Default ban action
# iptables-multiport: block IP only on affected port # nftables-multiport: block IP only on affected port
# iptables-allports: block IP on all ports # nftables-allports: block IP on all ports
#banaction = iptables-allports #banaction = nftables-allports

View file

@ -100,7 +100,7 @@ cap_add:
- NET_ADMIN - NET_ADMIN
``` ```
Otherwise, `iptables` won't be able to ban IPs. Otherwise, `nftables` won't be able to ban IPs.
##### FAIL2BAN_BLOCKTYPE ##### FAIL2BAN_BLOCKTYPE

View file

@ -29,7 +29,7 @@ Example configuration volume bind:
``` ```
!!! attention !!! attention
`docker-mailserver` must be launched with the `NET_ADMIN` capability in order to be able to install the iptable rules that actually ban IP addresses. `docker-mailserver` must be launched with the `NET_ADMIN` capability in order to be able to install the nftables rules that actually ban IP addresses.
Thus either include `--cap-add=NET_ADMIN` in the `docker run` command, or the equivalent in `docker-compose.yml`: Thus either include `--cap-add=NET_ADMIN` in the `docker run` command, or the equivalent in `docker-compose.yml`:
@ -38,16 +38,6 @@ Example configuration volume bind:
- NET_ADMIN - NET_ADMIN
``` ```
If you don't you will see errors the form of:
```log
iptables -w -X f2b-postfix -- stderr: "getsockopt failed strangely: Operation not permitted\niptables v1.4.21: can't initialize iptabl
es table `filter': Permission denied (you must be root)\nPerhaps iptables or your kernel needs to be upgraded.\niptables v1.4.21: can'
t initialize iptables table `filter': Permission denied (you must be root)\nPerhaps iptables or your kernel needs to be upgraded.\n"
2016-06-01 00:53:51,284 fail2ban.action [678]: ERROR iptables -w -D INPUT -p tcp -m multiport --dports smtp,465,submission -
j f2b-postfix
```
## Running fail2ban in a rootless container ## Running fail2ban in a rootless container
[`RootlessKit`][rootless::rootless-kit] is the _fakeroot_ implementation for supporting _rootless mode_ in Docker and Podman. By default RootlessKit uses the [`builtin` port forwarding driver][rootless::port-drivers], which does not propagate source IP addresses. [`RootlessKit`][rootless::rootless-kit] is the _fakeroot_ implementation for supporting _rootless mode_ in Docker and Podman. By default RootlessKit uses the [`builtin` port forwarding driver][rootless::port-drivers], which does not propagate source IP addresses.

View file

@ -112,7 +112,7 @@ ENABLE_DNSBL=0
# If you enable Fail2Ban, don't forget to add the following lines to your `docker-compose.yml`: # If you enable Fail2Ban, don't forget to add the following lines to your `docker-compose.yml`:
# cap_add: # cap_add:
# - NET_ADMIN # - NET_ADMIN
# Otherwise, `iptables` won't be able to ban IPs. # Otherwise, `nftables` won't be able to ban IPs.
ENABLE_FAIL2BAN=0 ENABLE_FAIL2BAN=0
# Fail2Ban blocktype # Fail2Ban blocktype

View file

@ -3,26 +3,12 @@
# shellcheck source=../scripts/helpers/index.sh # shellcheck source=../scripts/helpers/index.sh
source /usr/local/bin/helpers/index.sh source /usr/local/bin/helpers/index.sh
if ! IPTABLES_OUTPUT=$(iptables -L -n 2>&1)
then
_exit_with_error "IPTables is not functioning correctly
The output of \`iptables -L\` was:
${IPTABLES_OUTPUT}
Possible causes for this error are
1. Missing capabilities (you need CAP_NET_RAW & CAP_NET_ADMIN, see \`capsh --print\`)
2. Modifications caused by user-patches.sh
3. Host is configured incorrectly
"
fi
function __usage { echo "Usage: ${0} [<unban> <ip-address>]" ; } function __usage { echo "Usage: ${0} [<unban> <ip-address>]" ; }
unset JAILS unset JAILS
declare -a JAILS declare -a JAILS
IP_REGEXP='((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)'
for LIST in $(fail2ban-client status | grep "Jail list" | cut -f2- | sed 's/,/ /g') for LIST in $(fail2ban-client status | grep "Jail list" | cut -f2- | sed 's/,/ /g')
do do
JAILS+=("${LIST}") JAILS+=("${LIST}")
@ -30,25 +16,22 @@ done
if [[ -z ${1} ]] if [[ -z ${1} ]]
then then
IPS_BANNED=0
IP_COUNT=0
for JAIL in "${JAILS[@]}" for JAIL in "${JAILS[@]}"
do do
BANNED_IP="$(iptables -L "f2b-${JAIL}" -n 2>/dev/null | grep -Eo '[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}' | grep -v '0.0.0.0')" BANNED_IPS=$(fail2ban-client status "${JAIL}" \
| grep 'Banned IP list' \
| grep -oE "${IP_REGEXP}")
if [[ -n ${BANNED_IP} ]] if [[ -n ${BANNED_IPS} ]]
then then
echo "Banned in ${JAIL}: ${BANNED_IP//$'\n'/, }" echo "Banned in ${JAIL}: ${BANNED_IPS//$'\n'/, }"
IP_COUNT=$(( IP_COUNT + 1 )) IPS_BANNED=1
fi fi
done done
if [[ ${IP_COUNT} -eq 0 ]] [[ ${IPS_BANNED} -eq 0 ]] && _log 'info' "No IPs have been banned"
then
_log 'info' 'No IPs have been banned'
fi
else else
case "${1}" in case "${1}" in

View file

@ -15,10 +15,10 @@ maxretry = 3
# can be defined using space (and/or comma) separator. # can be defined using space (and/or comma) separator.
ignoreip = 127.0.0.1/8 ignoreip = 127.0.0.1/8
# Default ban action # default ban action
# iptables-multiport: block IP only on affected port # nftables-multiport: block IP only on affected port
# iptables-allports: block IP on all ports # nftables-allports: block IP on all ports
banaction = iptables-allports banaction = nftables-allports
[dovecot] [dovecot]
enabled = true enabled = true

View file

@ -1144,7 +1144,7 @@ function _setup_fail2ban
_log 'debug' 'Setting up Fail2Ban' _log 'debug' 'Setting up Fail2Ban'
if [[ ${FAIL2BAN_BLOCKTYPE} != 'reject' ]] if [[ ${FAIL2BAN_BLOCKTYPE} != 'reject' ]]
then then
echo -e '[Init]\nblocktype = DROP' >/etc/fail2ban/action.d/iptables-common.local echo -e '[Init]\nblocktype = drop' >/etc/fail2ban/action.d/nftables-common.local
fi fi
} }

View file

@ -11,6 +11,6 @@ findtime = 321
maxretry = 2 maxretry = 2
# Default ban action # Default ban action
# iptables-multiport: block IP only on affected port # nftables-multiport: block IP only on affected port
# iptables-allports: block IP on all ports # nftables-allports: block IP on all ports
banaction = iptables-multiport banaction = nftables-multiport

View file

@ -64,14 +64,14 @@ function teardown_file() {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} maxretry" run docker exec mail_fail2ban /bin/sh -c "fail2ban-client get ${FILTER} maxretry"
assert_output 2 assert_output 2
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'dovecot', 'addaction', 'iptables-multiport']\"" run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'dovecot', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'dovecot', 'addaction', 'iptables-multiport']" assert_output "['set', 'dovecot', 'addaction', 'nftables-multiport']"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix', 'addaction', 'iptables-multiport']\"" run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'postfix', 'addaction', 'iptables-multiport']" assert_output "['set', 'postfix', 'addaction', 'nftables-multiport']"
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix-sasl', 'addaction', 'iptables-multiport']\"" run docker exec mail_fail2ban /bin/sh -c "fail2ban-client -d | grep -F \"['set', 'postfix-sasl', 'addaction', 'nftables-multiport']\""
assert_output "['set', 'postfix-sasl', 'addaction', 'iptables-multiport']" assert_output "['set', 'postfix-sasl', 'addaction', 'nftables-multiport']"
done done
} }
@ -96,9 +96,9 @@ function teardown_file() {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep '${FAIL_AUTH_MAILER_IP}'" run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep '${FAIL_AUTH_MAILER_IP}'"
assert_success assert_success
# Checking that FAIL_AUTH_MAILER_IP is banned by iptables and blocktype set to DROP # Checking that FAIL_AUTH_MAILER_IP is banned by nftables and blocktype set to DROP
run docker exec mail_fail2ban /bin/sh -c "iptables -n -L f2b-postfix-sasl" run docker exec mail_fail2ban /bin/sh -c "nft list set inet f2b-table addr-set-postfix-sasl 2>/dev/null"
assert_output --regexp "DROP.+all.+${FAIL_AUTH_MAILER_IP}" assert_output --regexp "${FAIL_AUTH_MAILER_IP}"
} }
@test "checking fail2ban: unban ip works" { @test "checking fail2ban: unban ip works" {
@ -111,9 +111,9 @@ function teardown_file() {
run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep 'IP list:.*${FAIL_AUTH_MAILER_IP}'" run docker exec mail_fail2ban /bin/sh -c "fail2ban-client status postfix-sasl | grep 'IP list:.*${FAIL_AUTH_MAILER_IP}'"
assert_failure assert_failure
# Checking that FAIL_AUTH_MAILER_IP is unbanned by iptables # Checking that FAIL_AUTH_MAILER_IP is unbanned by nftables
run docker exec mail_fail2ban /bin/sh -c "iptables -L f2b-postfix-sasl -n | grep REJECT | grep '${FAIL_AUTH_MAILER_IP}'" run docker exec mail_fail2ban /bin/sh -c "nft list set inet f2b-table addr-set-postfix-sasl 2>/dev/null"
assert_failure refute_output "${FAIL_AUTH_MAILER_IP}"
} }
# #
@ -128,13 +128,15 @@ function teardown_file() {
sleep 10 sleep 10
run ./setup.sh -c mail_fail2ban debug fail2ban run ./setup.sh -c mail_fail2ban debug fail2ban
assert_output --regexp "^Banned in dovecot: 192.0.66.5, 192.0.66.4.*" assert_output --partial 'Banned in dovecot:'
assert_output --partial '192.0.66.5'
assert_output --partial '192.0.66.4'
run ./setup.sh -c mail_fail2ban debug fail2ban unban 192.0.66.4 run ./setup.sh -c mail_fail2ban debug fail2ban unban 192.0.66.4
assert_output --partial "Unbanned IP from dovecot: 1" assert_output --partial "Unbanned IP from dovecot: 1"
run ./setup.sh -c mail_fail2ban debug fail2ban run ./setup.sh -c mail_fail2ban debug fail2ban
assert_output --regexp "^Banned in dovecot: 192.0.66.5.*" assert_output --regexp "^Banned in dovecot:.*192.0.66.5.*"
run ./setup.sh -c mail_fail2ban debug fail2ban unban 192.0.66.5 run ./setup.sh -c mail_fail2ban debug fail2ban unban 192.0.66.5
assert_output --partial "Unbanned IP from dovecot: 1" assert_output --partial "Unbanned IP from dovecot: 1"