From fea1d9fdc00ee43e74904e32fb3bd04ee324e0f3 Mon Sep 17 00:00:00 2001 From: Christoph Date: Sat, 27 Dec 2025 19:23:42 +0100 Subject: [PATCH] initial commit --- .gitignore | 7 ++ README.fail2ban | 15 ++++ bin/fw-apply | 155 +++++++++++++++++++++++++++++++++++++ bin/fw-stop | 5 ++ etc-default/nft-fw | 16 ++++ install.sh | 71 +++++++++++++++++ systemd/nft-fw.service | 16 ++++ templates/nftables.conf.in | 43 ++++++++++ 8 files changed, 328 insertions(+) create mode 100644 .gitignore create mode 100644 README.fail2ban create mode 100755 bin/fw-apply create mode 100755 bin/fw-stop create mode 100644 etc-default/nft-fw create mode 100755 install.sh create mode 100644 systemd/nft-fw.service create mode 100644 templates/nftables.conf.in diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..516f034 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +# OS / editor +.DS_Store +*.swp + +# Built artifacts +*.tar.gz +*.zip diff --git a/README.fail2ban b/README.fail2ban new file mode 100644 index 0000000..8f03251 --- /dev/null +++ b/README.fail2ban @@ -0,0 +1,15 @@ + + + +# !! Wichtig!! +# +# In the file '/etc/fail2ban/jail.local', replace 'bannaction' with 'nftables-multiport': +# +# [DEFAULT] +# banaction = nftables-multiport +# +# and restart fail2ban service +# +# systemctl restart fail2ban + + diff --git a/bin/fw-apply b/bin/fw-apply new file mode 100755 index 0000000..964fefc --- /dev/null +++ b/bin/fw-apply @@ -0,0 +1,155 @@ +#!/usr//bin/env bash +set -euo pipefail + +######################## +# Args +######################## +DO_APPLY=true +if [[ "${1:-}" == "--check" ]]; then + DO_APPLY=false +elif [[ "${1:-}" != "" ]]; then + echo "Usage: $0 [--check]" >&2 + exit 2 +fi + +######################## +# Defaults +######################## +EXT_IF="eth0" +PRIV_IF="enp7s0" +PRIV_NET="172.20.0.0/21" + +ALLOW_SSH_PUBLIC_IN=false +ALLOW_APT_PUBLIC_OUT=false + +ALLOW_ICMP4_PUBLIC=true +ALLOW_ICMP6_PUBLIC=true + +######################## +# Optional config file +######################## +CFG="/etc/default/nft-fw" +if [[ -r "$CFG" ]]; then + # shellcheck disable=SC1090 + source "$CFG" +fi + +######################## +# Normalize booleans +######################## +normalize_bool() { + # trim whitespace + remove CR (Windows line endings) + local v + v="$(printf '%s' "${1:-}" | tr -d '\r' | xargs)" + case "${v,,}" in + true|yes|1) echo true ;; + false|no|0|"") echo false ;; + *) echo false ;; + esac +} + +ALLOW_ICMP4_PUBLIC="$(normalize_bool "${ALLOW_ICMP4_PUBLIC:-true}")" +ALLOW_ICMP6_PUBLIC="$(normalize_bool "${ALLOW_ICMP6_PUBLIC:-true}")" +ALLOW_SSH_PUBLIC_IN="$(normalize_bool "${ALLOW_SSH_PUBLIC_IN:-false}")" +ALLOW_APT_PUBLIC_OUT="$(normalize_bool "${ALLOW_APT_PUBLIC_OUT:-false}")" + + +NEED_EXT=false +if [[ "$ALLOW_SSH_PUBLIC_IN" == "true" || "$ALLOW_APT_PUBLIC_OUT" == "true" || "$ALLOW_ICMP4_PUBLIC" == "true" || "$ALLOW_ICMP6_PUBLIC" == "true" ]]; then + NEED_EXT=true +fi + +EFFECTIVE_ALLOW_ICMP6_PUBLIC="$ALLOW_ICMP6_PUBLIC" +if [[ "$NEED_EXT" == "true" ]]; then + EFFECTIVE_ALLOW_ICMP6_PUBLIC=true +fi + +######################## +# Build optional rule blocks +######################## +ICMP_PUBLIC_IN_RULES="" +ICMP_PUBLIC_OUT_RULES="" +SSH_PUBLIC_IN_RULE="" +APT_PUBLIC_OUT_RULES="" + + +# ICMPv4 optional +if [[ "$ALLOW_ICMP4_PUBLIC" == "true" ]]; then + ICMP_PUBLIC_IN_RULES="$(cat < "$TMP" + +######################## +# Remove only our table (do NOT touch fail2ban) +######################## +nft delete table inet fw_static 2>/dev/null || true + +######################## +# Validate + apply +######################## +nft -c -f "$TMP" + +if [[ "$DO_APPLY" == "true" ]]; then + nft -f "$TMP" + install -m 600 "$TMP" /etc/nftables.conf +else + echo "" + echo " fw-apply: check mode" + [[ -r "$CFG" ]] && echo " - config loaded: $CFG" || echo " - config loaded: defaults only" + echo " - rendered nftables ruleset: OK" + echo " - syntax check (nft -c): OK" + echo " - no changes applied" + echo "" +fi diff --git a/bin/fw-stop b/bin/fw-stop new file mode 100755 index 0000000..0794fe9 --- /dev/null +++ b/bin/fw-stop @@ -0,0 +1,5 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Enstferne +nft delete table inet fw_static 2>/dev/null || true diff --git a/etc-default/nft-fw b/etc-default/nft-fw new file mode 100644 index 0000000..d3dca4a --- /dev/null +++ b/etc-default/nft-fw @@ -0,0 +1,16 @@ +# Öffentliches Interface +EXT_IF=eth0 + +# Privates Interface +PRIV_IF=enp7s0 + +# Privates Netz +PRIV_NET=172.20.0.0/21 + +# Public access toggles +ALLOW_SSH_PUBLIC_IN=true +ALLOW_APT_PUBLIC_OUT=true + +# ICMP toggles (separat) +ALLOW_ICMP4_PUBLIC=true +ALLOW_ICMP6_PUBLIC=true diff --git a/install.sh b/install.sh new file mode 100755 index 0000000..4f280e6 --- /dev/null +++ b/install.sh @@ -0,0 +1,71 @@ +#!/usr/bin/env bash +set -euo pipefail + +REPO_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" + +say(){ echo "[nft-fw-nd-priv] $*"; } + +say "Creating directories..." +install -d -m 0755 /usr/local/sbin + +say "Installing template..." +install -m 0644 "$REPO_DIR/templates/nftables.conf.in" /etc/nftables.conf.in + +say "Installing scripts..." +install -m 0755 "$REPO_DIR/bin/fw-apply" /usr/local/sbin/fw-apply +install -m 0755 "$REPO_DIR/bin/fw-stop" /usr/local/sbin/fw-stop + + +say "Installing default config (won't overwrite existing)..." +if [[ ! -f /etc/default/nft-fw ]]; then + install -m 0644 "$REPO_DIR/etc-default/nft-fw-nd-priv" /etc/default/nft-fw +else + say "Config already exists at /etc/default/nft-fw (leaving as-is)." +fi + +say "Installing systemd unit..." +install -m 0644 "$REPO_DIR/systemd/nft-fw.service" /etc/systemd/system/nft-fw.service + +systemctl daemon-reload +systemctl enable nft-fw.service + +say "Switching iptables binaries to nft backend (if available)..." +set_alt() { + local name="$1" target="$2" + if command -v update-alternatives >/dev/null 2>&1; then + if update-alternatives --list "$name" >/dev/null 2>&1; then + if update-alternatives --list "$name" | grep -qx "$target"; then + update-alternatives --set "$name" "$target" || true + say "Set alternative: $name -> $target" + fi + fi + fi +} + +# Common paths on Debian/Ubuntu +set_alt iptables /usr/sbin/iptables-nft +set_alt ip6tables /usr/sbin/ip6tables-nft +set_alt arptables /usr/sbin/arptables-nft +set_alt ebtables /usr/sbin/ebtables-nft + +say "Configuring fail2ban banaction for nftables (if installed)..." +if [[ -d /etc/fail2ban && -x /usr/bin/fail2ban-client ]]; then + install -d -m 0755 /etc/fail2ban/jail.d + cat > /etc/fail2ban/jail.d/nft-fw-nd-priv.local <<'JEOF' +[DEFAULT] +# Prefer nftables actions when the system uses nft backend +banaction = nftables-multiport +banaction_allports = nftables-allports +JEOF + say "Wrote /etc/fail2ban/jail.d/nft-fw-nd-priv.local" + systemctl restart fail2ban || true +else + say "fail2ban not found; skipping." +fi + +say "Applying firewall now..." +/usr/local/sbin/fw-apply + +say "Done. Edit /etc/default/nft-fw-nd-priv and re-run: fw-apply" + + diff --git a/systemd/nft-fw.service b/systemd/nft-fw.service new file mode 100644 index 0000000..56331a5 --- /dev/null +++ b/systemd/nft-fw.service @@ -0,0 +1,16 @@ +[Unit] +Description=Apply nftables firewall (parameterized) +Documentation=man:nft(8) +After=local-fs.target +Wants=network-pre.target +Before=network-pre.target + +[Service] +Type=oneshot +ExecStart=/usr/local/sbin/fw-apply +ExecStop=/usr/local/sbin/fw-stop +RemainAfterExit=yes +PrivateTmp=yes + +[Install] +WantedBy=multi-user.target diff --git a/templates/nftables.conf.in b/templates/nftables.conf.in new file mode 100644 index 0000000..0dfda16 --- /dev/null +++ b/templates/nftables.conf.in @@ -0,0 +1,43 @@ +#!/usr/sbin/nft -f + +table inet fw_static { + + chain input { + type filter hook input priority 0; + policy drop; + + iif "lo" accept + ct state established,related accept + + # Public: ICMP (optional) +$ICMP_PUBLIC_IN_RULES + + # Public: SSH IN (optional) +$SSH_PUBLIC_IN_RULE + + # Private network (in) + iif "$PRIV_IF" ip saddr $PRIV_NET accept + } + + chain output { + type filter hook output priority 0; + policy drop; + + oif "lo" accept + ct state established,related accept + + # Public: ICMP (optional) +$ICMP_PUBLIC_OUT_RULES + + # Public: APT OUT (optional) - includes DNS + HTTP/HTTPS +$APT_PUBLIC_OUT_RULES + + # Private network (out) + oif "$PRIV_IF" ip daddr $PRIV_NET accept + } + + chain forward { + type filter hook forward priority 0; + policy drop; + } +}