|
| 1 | +#!/usr/bin/env bash |
| 2 | +# ============================================================================= |
| 3 | +# NFTBan v1.100 Amendment 4 - nft Table Classifier (PR26.6 / 6A) |
| 4 | +# ============================================================================= |
| 5 | +# SPDX-License-Identifier: MPL-2.0 |
| 6 | +# meta:name="nftban_table_classify" |
| 7 | +# meta:type="lib" |
| 8 | +# meta:version="1.100.0" |
| 9 | +# meta:owner="Antonios Voulvoulis <contact@nftban.com>" |
| 10 | +# meta:created_date="2026-04-30" |
| 11 | +# meta:description="Classify nft tables before destructive cleanup — TAKEOVER-PRESERVES-NON-NFTBAN-AUTHORITY-001" |
| 12 | +# meta:inventory.files="cli/lib/nftban/core/nftban_table_classify.sh" |
| 13 | +# meta:inventory.binaries="" |
| 14 | +# meta:inventory.env_vars="" |
| 15 | +# meta:inventory.config_files="" |
| 16 | +# meta:inventory.systemd_units="" |
| 17 | +# meta:inventory.network="" |
| 18 | +# meta:inventory.privileges="none" |
| 19 | +# |
| 20 | +# Invariant TAKEOVER-PRESERVES-NON-NFTBAN-AUTHORITY-001: |
| 21 | +# During takeover and rebuild, nftban may disable external firewall |
| 22 | +# authority through reversible lifecycle operations, but must not |
| 23 | +# destructively delete non-nftban-owned authority or operator-safety |
| 24 | +# assets. |
| 25 | +# |
| 26 | +# Replaces the prior allowlist-sweep pattern that silently called |
| 27 | +# nft delete table <every-non-allowlisted-table> |
| 28 | +# which on dns2 (2026-04-30) wiped operator-retained inet ssh_safety. |
| 29 | +# |
| 30 | +# Classes: |
| 31 | +# NFTBAN_OWNED — flush/delete authorized |
| 32 | +# EXTERNAL_AUTHORITY_GHOST — delete authorized (CSF/firewalld iptables-nft compat) |
| 33 | +# KERNEL_DEFAULT — preserve silently (ip raw, ip6 raw) |
| 34 | +# OPERATOR_SAFETY — preserve, emit warning (default policy in PR26.6) |
| 35 | +# ============================================================================= |
| 36 | + |
| 37 | +set -Eeuo pipefail |
| 38 | + |
| 39 | +[[ -n "${_NFTBAN_TABLE_CLASSIFY_LOADED:-}" ]] && return 0 |
| 40 | +_NFTBAN_TABLE_CLASSIFY_LOADED=1 |
| 41 | + |
| 42 | +# shellcheck disable=SC2034 |
| 43 | +readonly TC_NFTBAN_OWNED="NFTBAN_OWNED" |
| 44 | +# shellcheck disable=SC2034 |
| 45 | +readonly TC_EXTERNAL_AUTHORITY_GHOST="EXTERNAL_AUTHORITY_GHOST" |
| 46 | +# shellcheck disable=SC2034 |
| 47 | +readonly TC_KERNEL_DEFAULT="KERNEL_DEFAULT" |
| 48 | +# shellcheck disable=SC2034 |
| 49 | +readonly TC_OPERATOR_SAFETY="OPERATOR_SAFETY" |
| 50 | + |
| 51 | +# nftban_classify_table — classify a single nft table by family + name. |
| 52 | +# |
| 53 | +# Input (positional): |
| 54 | +# $1 family — one of: ip, ip6, inet, arp, bridge, netdev |
| 55 | +# $2 name — table name |
| 56 | +# Output: |
| 57 | +# prints classification token to stdout (one of TC_* values) |
| 58 | +# Exit code: always 0 |
| 59 | +# |
| 60 | +# Pinned table sets are kept in this single function so |
| 61 | +# install-side, rebuild-side, and autoheal-side all agree. |
| 62 | +nftban_classify_table() { |
| 63 | + local family="$1" |
| 64 | + local name="$2" |
| 65 | + local spec="${family} ${name}" |
| 66 | + |
| 67 | + case "$spec" in |
| 68 | + # NFTBan-owned tables |
| 69 | + "ip nftban"|"ip6 nftban"|"inet nftban"|"inet nftban_install_emergency") |
| 70 | + echo "$TC_NFTBAN_OWNED" |
| 71 | + return 0 |
| 72 | + ;; |
| 73 | + # Kernel default empty tables (always present on EL9+, harmless) |
| 74 | + "ip raw"|"ip6 raw") |
| 75 | + echo "$TC_KERNEL_DEFAULT" |
| 76 | + return 0 |
| 77 | + ;; |
| 78 | + # External firewall ghost tables — created by iptables-nft compat |
| 79 | + # layer (CSF/lfd, firewalld, fail2ban, docker). Delete authorized |
| 80 | + # during takeover-driven rebuild. |
| 81 | + "ip filter"|"ip6 filter"|\ |
| 82 | + "ip nat"|"ip6 nat"|\ |
| 83 | + "ip mangle"|"ip6 mangle"|\ |
| 84 | + "ip security"|"ip6 security"|\ |
| 85 | + "inet firewalld"|"inet filter") |
| 86 | + echo "$TC_EXTERNAL_AUTHORITY_GHOST" |
| 87 | + return 0 |
| 88 | + ;; |
| 89 | + *) |
| 90 | + # Anything else — including operator-retained tables such as |
| 91 | + # `inet ssh_safety` — is OPERATOR_SAFETY. PR26.6 default policy |
| 92 | + # is WARN-and-preserve. |
| 93 | + echo "$TC_OPERATOR_SAFETY" |
| 94 | + return 0 |
| 95 | + ;; |
| 96 | + esac |
| 97 | +} |
| 98 | + |
| 99 | +# nftban_classify_table_line — accept "table <family> <name>" line as |
| 100 | +# emitted by `nft list tables` and route through nftban_classify_table. |
| 101 | +nftban_classify_table_line() { |
| 102 | + local line="$1" |
| 103 | + # Strip leading "table " |
| 104 | + local rest="${line#table }" |
| 105 | + # Split on first whitespace |
| 106 | + local family="${rest%% *}" |
| 107 | + local name="${rest#* }" |
| 108 | + if [[ -z "$family" || -z "$name" || "$family" == "$name" ]]; then |
| 109 | + echo "$TC_OPERATOR_SAFETY" |
| 110 | + return 0 |
| 111 | + fi |
| 112 | + nftban_classify_table "$family" "$name" |
| 113 | +} |
| 114 | + |
| 115 | +# nftban_table_should_delete_for_takeover — true (rc 0) if the table |
| 116 | +# may be destructively deleted during takeover-driven rebuild cleanup. |
| 117 | +# |
| 118 | +# NFTBAN_OWNED → false (the rebuild flushes nftban tables itself, does |
| 119 | +# not nft-delete them — preserving set state references). |
| 120 | +# EXTERNAL_AUTHORITY_GHOST → true. |
| 121 | +# KERNEL_DEFAULT, OPERATOR_SAFETY → false. |
| 122 | +nftban_table_should_delete_for_takeover() { |
| 123 | + local family="$1" |
| 124 | + local name="$2" |
| 125 | + local class |
| 126 | + class="$(nftban_classify_table "$family" "$name")" |
| 127 | + [[ "$class" == "$TC_EXTERNAL_AUTHORITY_GHOST" ]] |
| 128 | +} |
0 commit comments