11#! /bin/bash
22set -euo pipefail
33
4+ trap ' log "ERROR: Command failed at line $LINENO: $BASH_COMMAND"; exit 1' ERR
5+
46# =============================================================================
57# Unified Firewall Script for hetzner-k3s
68# Handles initial setup, ongoing updates, and restoration on boot
@@ -45,12 +47,13 @@ validate_ip_network() {
4547}
4648
4749normalise_networks () {
48- grep -v ' ^[[:space:]]*$' | \
49- grep -v ' ^[[:space:]]*#' | \
50+ # Use || true to handle empty input gracefully with pipefail
51+ { grep -v ' ^[[:space:]]*$' || true ; } | \
52+ { grep -v ' ^[[:space:]]*#' || true ; } | \
5053 tr -d ' \r' | \
5154 sed ' s/^[[:space:]]*//;s/[[:space:]]*$//' | \
5255 sed -E ' s/^([0-9]+\.[0-9]+\.[0-9]+\.[0-9]+)$/\1\/32/' | \
53- sort -u
56+ sort -u || true
5457}
5558
5659# =============================================================================
@@ -112,15 +115,13 @@ update_ipset() {
112115 local networks=$2
113116 local temp_name=" ${name} _temp"
114117
115- # Get current networks
116118 local current=" "
117119 if ipset list -n 2> /dev/null | grep -q " ^${name} $" ; then
118- current=$( ipset list " $name " 2> /dev/null | sed -n ' /^Members:/,$p' | tail -n +2 | normalise_networks)
120+ current=$( ipset list " $name " 2> /dev/null | sed -n ' /^Members:/,$p' | tail -n +2 | normalise_networks || echo " " )
119121 fi
120122
121- # Normalise new networks
122123 local new
123- new=$( echo " $networks " | normalise_networks)
124+ new=$( echo " $networks " | normalise_networks || echo " " )
124125
125126 # Check for changes
126127 if [ " $current " = " $new " ]; then
@@ -135,7 +136,7 @@ update_ipset() {
135136 while IFS= read -r network; do
136137 if [ -n " $network " ] && validate_ip_network " $network " ; then
137138 if ipset add " $temp_name " " $network " 2> /dev/null; then
138- (( count++ ))
139+ count= $ (( count + 1 ))
139140 fi
140141 fi
141142 done <<< " $new"
@@ -202,14 +203,28 @@ setup_iptables() {
202203
203204save_iptables () {
204205 mkdir -p " $( dirname " $IPTABLES_RULES_FILE " ) "
205- iptables-save > " $IPTABLES_RULES_FILE "
206+ iptables -P INPUT DROP 2> /dev/null || true
207+ iptables-save | grep -v -E ' KUBE-|FLANNEL-|CNI-' > " $IPTABLES_RULES_FILE "
206208}
207209
208210restore_iptables () {
211+ # First ensure ipsets exist (rules reference them)
212+ create_ipset_if_missing " $IPSET_NODES "
213+ create_ipset_if_missing " $IPSET_SSH "
214+ create_ipset_if_missing " $IPSET_API "
215+
209216 if [ -f " $IPTABLES_RULES_FILE " ]; then
210217 iptables-restore -w < " $IPTABLES_RULES_FILE " 2> /dev/null || true
211218 log " Restored iptables from $IPTABLES_RULES_FILE "
212219 fi
220+
221+ # Ensure INPUT policy is DROP (defense in depth)
222+ local current_policy
223+ current_policy=$( iptables -S INPUT 2> /dev/null | head -1)
224+ if [[ ! " $current_policy " =~ " -P INPUT DROP" ]]; then
225+ iptables -P INPUT DROP
226+ log " Set INPUT policy is DROP (was: $current_policy )"
227+ fi
213228}
214229
215230# =============================================================================
@@ -260,6 +275,15 @@ read_networks_file() {
260275# Update Loop
261276# =============================================================================
262277
278+ populate_ssh_ipset () {
279+ local ssh_networks
280+ ssh_networks=$( read_networks_file " $SSH_NETWORKS_FILE " )
281+ if [ -n " $ssh_networks " ]; then
282+ update_ipset " $IPSET_SSH " " $ssh_networks "
283+ log " Populated SSH ipset from $SSH_NETWORKS_FILE "
284+ fi
285+ }
286+
263287update_ipsets () {
264288 # Fetch node IPs from Hetzner API
265289 local node_ips
@@ -325,25 +349,41 @@ main() {
325349 initial_setup
326350 ;;
327351 restore)
352+ create_ipset_if_missing " $IPSET_NODES "
353+ create_ipset_if_missing " $IPSET_SSH "
354+ create_ipset_if_missing " $IPSET_API "
355+ populate_ssh_ipset
328356 restore_ipsets
329357 restore_iptables
358+ update_ipsets
330359 ;;
331360 update)
332361 update_ipsets
333362 ;;
334363 daemon)
335364 log " Starting firewall daemon..."
365+ # Create ipsets first (rules reference them)
366+ create_ipset_if_missing " $IPSET_NODES "
367+ create_ipset_if_missing " $IPSET_SSH "
368+ create_ipset_if_missing " $IPSET_API "
369+ # Populate SSH ipset BEFORE iptables restore (so SSH works immediately)
370+ populate_ssh_ipset
336371 restore_ipsets
337372 restore_iptables
373+ update_ipsets
338374 run_update_loop
339375 ;;
340376 * )
341- # Default: setup then daemon mode
342377 if [ ! -f " $IPTABLES_RULES_FILE " ]; then
343378 initial_setup
344379 else
380+ create_ipset_if_missing " $IPSET_NODES "
381+ create_ipset_if_missing " $IPSET_SSH "
382+ create_ipset_if_missing " $IPSET_API "
383+ populate_ssh_ipset
345384 restore_ipsets
346385 restore_iptables
386+ update_ipsets
347387 fi
348388 run_update_loop
349389 ;;
0 commit comments