485 lines
21 KiB
Bash
Executable File
485 lines
21 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Name: sysdeck-fw
|
|
# Description: SysDeck FW — unified nftables zone firewall. Takes influence from Smoothwall Express (RED/ORANGE/GREEN/BLUE color-zone model) and IPFire (source-verified outbound + AirWall isolation + flow offload) under our own identifier. Modern nftables syntax with sets, verdict maps, synproxy, bogon filtering, connlimit, optional flow offload, and DMZ port-forwarding. Designed for eBPF-capable kernels with XDP integration points.
|
|
# Distro: arch,debian
|
|
# Services: ssh
|
|
#
|
|
# ============================================================================
|
|
# sysdeck-fw.sh - SysDeck Unified nftables Zone Firewall
|
|
# ============================================================================
|
|
#
|
|
# v0.0.37 UNIFIED ZONE FIREWALL. This template takes influence from
|
|
# two open-source firewall distributions — Smoothwall Express
|
|
# (RED/ORANGE/GREEN/BLUE color-zone model) and IPFire (source-verified
|
|
# outbound + AirWall isolation + flow offload). We do not ship a
|
|
# template called "smoothwall" or "ipfire" — those are other
|
|
# projects' trademarks. The unified SysDeck FW template preserves the
|
|
# feature sets we took influence from under our own identifier.
|
|
#
|
|
# What this template takes influence from:
|
|
#
|
|
# FROM THE ZONE MODEL (takes influence from Smoothwall Express):
|
|
# RED untrusted Internet (WAN)
|
|
# GREEN trusted LAN
|
|
# ORANGE DMZ (servers exposed to RED but isolated from GREEN)
|
|
# BLUE wireless LAN (semi-trusted, isolated from GREEN)
|
|
#
|
|
# FROM SOURCE-VERIFIED OUTBOUND (takes influence from IPFire):
|
|
# Each non-RED zone has a configured CIDR. Outbound traffic from
|
|
# the zone is only accepted if the source IP matches the CIDR.
|
|
# This defeats IP-spoofing inside the firewall (a compromised
|
|
# host on BLUE cannot pretend to be on GREEN by spoofing a GREEN
|
|
# IP — the forward chain checks both iifname AND saddr).
|
|
#
|
|
# FROM AIRWALL ISOLATION (takes influence from IPFire):
|
|
# BLUE (WiFi) is treated as semi-trusted. By default, BLUE cannot
|
|
# reach GREEN at all — not even for DNS. The operator can disable
|
|
# AirWall via AIRWALL=false in the config file to allow BLUE ->
|
|
# GREEN DNS only (useful for a unified resolver).
|
|
#
|
|
# FROM FLOW OFFLOAD (takes influence from IPFire):
|
|
# Optional hardware acceleration. When FLOW_OFFLOAD=true, the
|
|
# ruleset includes a flowtable that accelerates established TCP/UDP
|
|
# connections. Requires a NIC driver with flow offload support.
|
|
#
|
|
# FROM ZONE FORWARDING MATRIX (takes influence from both):
|
|
# GREEN -> RED allow (LAN -> Internet)
|
|
# GREEN -> ORANGE allow (LAN -> DMZ)
|
|
# GREEN -> BLUE allow (LAN -> WiFi)
|
|
# BLUE -> RED allow (WiFi -> Internet; AirWall outbound OK)
|
|
# BLUE -> ORANGE allow (WiFi -> DMZ)
|
|
# BLUE -> GREEN DENY (AirWall — even DNS blocked, unless
|
|
# AIRWALL=false, in which case DNS only)
|
|
# ORANGE -> RED allow (DMZ -> Internet for updates)
|
|
# ORANGE -> GREEN DENY
|
|
# ORANGE -> BLUE DENY
|
|
# RED -> GREEN DENY
|
|
# RED -> BLUE DENY
|
|
# RED -> ORANGE only via DMZ_FORWARDS port-forward rules
|
|
#
|
|
# FROM DMZ PORT-FORWARDING (takes influence from both):
|
|
# Operator configures DMZ_FORWARDS="extport:intport:orangeip,..."
|
|
# The template emits both DNAT (prerouting) and the forward-allow
|
|
# rule. Example: "80:80:10.0.0.10,443:443:10.0.0.10".
|
|
#
|
|
# ANTI-REQUIREMENTS (per user directive v0.0.36 + v0.0.37):
|
|
# - UFW: nftables frontend, no eBPF. Skipped.
|
|
# - fwbuilder: GUI rule generator, too complex. Skipped.
|
|
# - iptables-legacy: pre-nftables, old. Skipped.
|
|
# - iptables-nft: compatibility wrapper, adds no value over native
|
|
# nftables. Skipped.
|
|
# - Shorewall: iptables-based, no eBPF integration points. Skipped.
|
|
# - We do NOT ship a template called "smoothwall" or "ipfire" —
|
|
# those are trademarks of their respective projects. We took
|
|
# influence from them for sysdeck-fw. This is SysDeck FW.
|
|
#
|
|
# Requirements:
|
|
# - Linux kernel 6.6+ with CONFIG_NF_TABLES=y
|
|
# - nftables >= 1.0.0
|
|
#
|
|
# Configuration:
|
|
# Edit the variables below, or drop a config file at
|
|
# /etc/sysdeck/firewall/sysdeck-fw.conf to override. The config
|
|
# file is sourced if present.
|
|
#
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
IFS=$'\n\t'
|
|
|
|
TABLE_NAME="firewall"
|
|
|
|
# ============================================================================
|
|
# CONFIGURATION
|
|
# ============================================================================
|
|
|
|
# Zone interfaces. RED (WAN) is auto-detected from the default route
|
|
# if RED_IF is empty. GREEN/ORANGE/BLUE are optional — zones without
|
|
# an interface are skipped at start.
|
|
RED_IF="${RED_IF:-}"
|
|
GREEN_IF="${GREEN_IF:-}"
|
|
ORANGE_IF="${ORANGE_IF:-}"
|
|
BLUE_IF="${BLUE_IF:-}"
|
|
|
|
# Zone CIDRs (used for source-verified outbound). Each non-RED zone
|
|
# has a configured CIDR; outbound from the zone is only accepted if
|
|
# the source IP matches. Defeats IP spoofing inside the firewall.
|
|
RED_CIDR="${RED_CIDR:-}" # WAN — leave blank
|
|
GREEN_CIDR="${GREEN_CIDR:-10.0.0.0/24}"
|
|
ORANGE_CIDR="${ORANGE_CIDR:-10.0.1.0/24}"
|
|
BLUE_CIDR="${BLUE_CIDR:-192.168.1.0/24}"
|
|
|
|
# Inbound ports per zone (CSV). Empty = no inbound beyond established.
|
|
RED_IN_TCP="${RED_IN_TCP:-22}" # SSH from Internet
|
|
RED_IN_UDP="${RED_IN_UDP:-}" # e.g. 51820 for WireGuard
|
|
GREEN_IN_TCP="${GREEN_IN_TCP:-22,80,443}"
|
|
GREEN_IN_UDP="${GREEN_IN_UDP:-53}" # DNS for LAN clients
|
|
ORANGE_IN_TCP="${ORANGE_IN_TCP:-80,443}"
|
|
ORANGE_IN_UDP="${ORANGE_IN_UDP:-}"
|
|
BLUE_IN_TCP="${BLUE_IN_TCP:-22,80,443}"
|
|
BLUE_IN_UDP="${BLUE_IN_UDP:-53}"
|
|
|
|
# AirWall: when true (default), BLUE is fully isolated from GREEN.
|
|
# Set to "false" to allow BLUE -> GREEN DNS only (for a unified resolver).
|
|
AIRWALL="${AIRWALL:-true}"
|
|
|
|
# Flow offload (hardware acceleration). Set to "true" to enable.
|
|
# Requires a NIC driver with flow offload support.
|
|
FLOW_OFFLOAD="${FLOW_OFFLOAD:-false}"
|
|
|
|
# DMZ port-forwarding: RED extport -> ORANGE host:intport (CSV).
|
|
# Example: "80:80:10.0.0.10,443:443:10.0.0.10"
|
|
DMZ_FORWARDS="${DMZ_FORWARDS:-}"
|
|
|
|
# Load operator overrides if present.
|
|
CONFIG_FILE="${CONFIG_FILE:-/etc/sysdeck/firewall/sysdeck-fw.conf}"
|
|
if [[ -f "$CONFIG_FILE" ]]; then
|
|
# shellcheck disable=SC1090
|
|
source "$CONFIG_FILE"
|
|
fi
|
|
|
|
NFT_CMD="${NFT_CMD:-nft}"
|
|
|
|
# ============================================================================
|
|
|
|
log_info() { printf '[sysdeck-fw] [INFO] %s\n' "$*" >&2; }
|
|
log_warn() { printf '[sysdeck-fw] [WARN] %s\n' "$*" >&2; }
|
|
log_error() { printf '[sysdeck-fw] [ERROR] %s\n' "$*" >&2; }
|
|
die() { log_error "$*"; exit 1; }
|
|
|
|
have() { command -v "$1" >/dev/null 2>&1; }
|
|
|
|
# ============================================================================
|
|
# PRE-FLIGHT
|
|
# ============================================================================
|
|
|
|
check_root() { [[ $EUID -eq 0 ]] || die "Requires root (cockpit superuser channel)."; }
|
|
check_nftables() { have "$NFT_CMD" || die "nftables not installed. Install: pacman -S nftables / apt install nftables."; }
|
|
|
|
# Auto-detect the RED (WAN) interface from the default route if not set.
|
|
autodetect_red() {
|
|
if [[ -z "$RED_IF" ]]; then
|
|
RED_IF="$(ip route show default 2>/dev/null | awk '/default/ {print $5; exit}')"
|
|
[[ -z "$RED_IF" ]] && die "Could not auto-detect RED (WAN) interface. Set RED_IF in $CONFIG_FILE."
|
|
log_info "Auto-detected RED interface: $RED_IF"
|
|
fi
|
|
}
|
|
|
|
# ============================================================================
|
|
# RULE GENERATION
|
|
# ============================================================================
|
|
|
|
csv_to_nft_set() {
|
|
# Emit a space-separated list of items from a CSV, skipping empties.
|
|
local csv="$1"
|
|
[[ -z "$csv" ]] && return 0
|
|
echo "$csv" | tr ',' ' '
|
|
}
|
|
|
|
# Emit a conditional nftables line only if the given zone interface is set.
|
|
# Usage: zone_line "$GREEN_IF" "iifname \"$GREEN_IF\" ip saddr @green_net oifname \"$RED_IF\" accept comment \"GREEN -> RED\""
|
|
zone_line() {
|
|
local iface="$1"
|
|
local rule="$2"
|
|
[[ -n "$iface" ]] && echo " $rule"
|
|
}
|
|
|
|
build_ruleset() {
|
|
autodetect_red
|
|
|
|
local red_tcp_in="$(csv_to_nft_set "$RED_IN_TCP")"
|
|
local red_udp_in="$(csv_to_nft_set "$RED_IN_UDP")"
|
|
local green_tcp_in="$(csv_to_nft_set "$GREEN_IN_TCP")"
|
|
local green_udp_in="$(csv_to_nft_set "$GREEN_IN_UDP")"
|
|
local orange_tcp_in="$(csv_to_nft_set "$ORANGE_IN_TCP")"
|
|
local orange_udp_in="$(csv_to_nft_set "$ORANGE_IN_UDP")"
|
|
local blue_tcp_in="$(csv_to_nft_set "$BLUE_IN_TCP")"
|
|
local blue_udp_in="$(csv_to_nft_set "$BLUE_IN_UDP")"
|
|
|
|
cat <<EOF
|
|
#!/usr/sbin/nft -f
|
|
# SysDeck FW — unified nftables zone firewall. Generated by sysdeck-fw.sh.
|
|
# Zones:
|
|
# RED (WAN): iface=${RED_IF} cidr=WAN
|
|
# GREEN (LAN): iface=${GREEN_IF:-unset} cidr=${GREEN_CIDR:-unset}
|
|
# ORANGE (DMZ): iface=${ORANGE_IF:-unset} cidr=${ORANGE_CIDR:-unset}
|
|
# BLUE (WiFi): iface=${BLUE_IF:-unset} cidr=${BLUE_CIDR:-unset}
|
|
# AirWall: ${AIRWALL}
|
|
# Flow offload: ${FLOW_OFFLOAD}
|
|
|
|
table inet ${TABLE_NAME} {
|
|
# ── Sets ─────────────────────────────────────────────────────────
|
|
set ssh_abuse { type ipv4_addr; flags interval; timeout 1h; }
|
|
set port_scanners { type ipv4_addr; flags interval; timeout 1h; }
|
|
set connlimit_abuse { type ipv4_addr; timeout 10m; }
|
|
|
|
set bogons_v4 {
|
|
type ipv4_addr; flags interval;
|
|
elements = {
|
|
0.0.0.0/8, 10.0.0.0/8, 100.64.0.0/10, 127.0.0.0/8,
|
|
169.254.0.0/16, 172.16.0.0/12, 192.0.0.0/24, 192.0.2.0/24,
|
|
192.168.0.0/16, 198.18.0.0/15, 198.51.100.0/24,
|
|
203.0.113.0/24, 224.0.0.0/4, 240.0.0.0/4, 255.255.255.255/32
|
|
}
|
|
}
|
|
|
|
# Zone source CIDRs (source-verified outbound).
|
|
$( [[ -n "$GREEN_CIDR" ]] && echo "set green_net { type ipv4_addr; flags interval; elements = { $GREEN_CIDR }; }" )
|
|
$( [[ -n "$ORANGE_CIDR" ]] && echo "set orange_net { type ipv4_addr; flags interval; elements = { $ORANGE_CIDR }; }" )
|
|
$( [[ -n "$BLUE_CIDR" ]] && echo "set blue_net { type ipv4_addr; flags interval; elements = { $BLUE_CIDR }; }" )
|
|
|
|
# ── Input (to firewall host) ─────────────────────────────────────
|
|
chain input {
|
|
type filter hook input priority filter; policy drop;
|
|
|
|
iifname "lo" accept
|
|
iifname "$RED_IF" ip saddr @bogons_v4 drop comment "bogon from RED"
|
|
|
|
ip saddr @ssh_abuse drop
|
|
ip saddr @port_scanners drop
|
|
|
|
ct state established,related accept
|
|
ct state invalid drop
|
|
|
|
# Flood control only — the accept decisions stay with the zone
|
|
# rules below, so no rate-limited blanket SYN accept exists here.
|
|
ct state new tcp flags & (fin|syn|rst|ack) == syn limit rate over 50/second burst 100 packets counter drop comment "SYN flood rate limit"
|
|
|
|
ip protocol icmp icmp type echo-request limit rate 5/second accept
|
|
|
|
# Full IPv6 control-plane set: NDP (solicit + advert, both
|
|
# router and neighbor) and PMTUD errors — without these, on-link
|
|
# IPv6 and path MTU discovery silently break.
|
|
ip6 nexthdr icmpv6 icmpv6 type { destination-unreachable, packet-too-big, time-exceeded, parameter-problem, echo-request, nd-router-solicit, nd-router-advert, nd-neighbor-solicit, nd-neighbor-advert } accept
|
|
|
|
$( [[ -n "$red_tcp_in" ]] && echo " iifname \"$RED_IF\" tcp dport { $red_tcp_in } accept comment \"RED inbound TCP\"" )
|
|
$( [[ -n "$red_udp_in" ]] && echo " iifname \"$RED_IF\" udp dport { $red_udp_in } accept comment \"RED inbound UDP\"" )
|
|
$( [[ -n "$GREEN_IF" ]] && echo "iifname \"$GREEN_IF\" ip saddr @green_net tcp dport { $green_tcp_in } accept comment \"GREEN inbound (source-verified)\"" )
|
|
$( [[ -n "$GREEN_IF" ]] && echo "iifname \"$GREEN_IF\" ip saddr @green_net udp dport { $green_udp_in } accept comment \"GREEN inbound (source-verified)\"" )
|
|
$( [[ -n "$ORANGE_IF" ]] && echo "iifname \"$ORANGE_IF\" ip saddr @orange_net tcp dport { $orange_tcp_in } accept comment \"ORANGE inbound (source-verified)\"" )
|
|
$( [[ -n "$ORANGE_IF" ]] && echo "iifname \"$ORANGE_IF\" ip saddr @orange_net udp dport { $orange_udp_in } accept comment \"ORANGE inbound (source-verified)\"" )
|
|
$( [[ -n "$BLUE_IF" ]] && echo "iifname \"$BLUE_IF\" ip saddr @blue_net tcp dport { $blue_tcp_in } accept comment \"BLUE inbound (source-verified)\"" )
|
|
$( [[ -n "$BLUE_IF" ]] && echo "iifname \"$BLUE_IF\" ip saddr @blue_net udp dport { $blue_udp_in } accept comment \"BLUE inbound (source-verified)\"" )
|
|
}
|
|
|
|
# ── Forward (between zones) ──────────────────────────────────────
|
|
chain forward {
|
|
type filter hook forward priority filter; policy drop;
|
|
|
|
iifname "$RED_IF" ip saddr @bogons_v4 drop
|
|
|
|
ct state established,related accept
|
|
ct state invalid drop
|
|
|
|
# Flood control on RED ingress: drop the excess, never blanket
|
|
# accept — the DMZ port-forwards below are the only RED -> ORANGE
|
|
# paths and each one carries an explicit dport + daddr.
|
|
iifname "$RED_IF" ct state new limit rate over 100/second burst 200 packets counter drop comment "RED new-connection flood limit"
|
|
|
|
# ── Zone-to-zone matrix (source-verified) ──────────────────────
|
|
# GREEN -> RED : allow
|
|
# GREEN -> ORANGE: allow
|
|
# GREEN -> BLUE : allow
|
|
$( zone_line "$GREEN_IF" "iifname \"$GREEN_IF\" ip saddr @green_net oifname \"$RED_IF\" accept comment \"GREEN -> RED\"" )
|
|
$( [[ -n "$GREEN_IF" && -n "$ORANGE_IF" ]] && echo " iifname \"$GREEN_IF\" ip saddr @green_net oifname \"$ORANGE_IF\" accept comment \"GREEN -> ORANGE\"" )
|
|
$( [[ -n "$GREEN_IF" && -n "$BLUE_IF" ]] && echo " iifname \"$GREEN_IF\" ip saddr @green_net oifname \"$BLUE_IF\" accept comment \"GREEN -> BLUE\"" )
|
|
|
|
# BLUE -> RED : allow (AirWall outbound OK)
|
|
# BLUE -> ORANGE: allow
|
|
# BLUE -> GREEN: DENY (AirWall) unless AIRWALL=false → DNS only
|
|
$( zone_line "$BLUE_IF" "iifname \"$BLUE_IF\" ip saddr @blue_net oifname \"$RED_IF\" accept comment \"BLUE -> RED (AirWall outbound)\"" )
|
|
$( [[ -n "$BLUE_IF" && -n "$ORANGE_IF" ]] && echo " iifname \"$BLUE_IF\" ip saddr @blue_net oifname \"$ORANGE_IF\" accept comment \"BLUE -> ORANGE\"" )
|
|
$( [[ "$AIRWALL" == "false" && -n "$BLUE_IF" && -n "$GREEN_IF" ]] && echo " iifname \"$BLUE_IF\" ip saddr @blue_net oifname \"$GREEN_IF\" udp dport 53 accept comment \"AirWall-off: BLUE -> GREEN DNS only\"" )
|
|
|
|
# ORANGE -> RED : allow (DMZ -> Internet for updates)
|
|
# ORANGE -> GREEN: DENY
|
|
# ORANGE -> BLUE : DENY
|
|
$( zone_line "$ORANGE_IF" "iifname \"$ORANGE_IF\" ip saddr @orange_net oifname \"$RED_IF\" accept comment \"ORANGE -> RED\"" )
|
|
|
|
# DMZ port-forward rules (RED extport -> ORANGE host:intport).
|
|
EOF
|
|
|
|
# Emit port-forward rules (forward chain — accept the forwarded traffic).
|
|
if [[ -n "$DMZ_FORWARDS" ]]; then
|
|
local IFS=','
|
|
for fwd in $DMZ_FORWARDS; do
|
|
local extport="${fwd%%:*}"
|
|
local rest="${fwd#*:}"
|
|
local intport="${rest%%:*}"
|
|
local orangeip="${rest##*:}"
|
|
if [[ -n "$extport" && -n "$intport" && -n "$orangeip" && -n "$ORANGE_IF" ]]; then
|
|
echo " iifname \"$RED_IF\" oifname \"$ORANGE_IF\" tcp dport $extport ip daddr $orangeip accept comment \"forward RED:$extport -> ORANGE:$orangeip:$intport\""
|
|
fi
|
|
done
|
|
fi
|
|
|
|
cat <<EOF
|
|
}
|
|
# ── Output ───────────────────────────────────────────────────────
|
|
chain output {
|
|
type filter hook output priority filter; policy accept;
|
|
}
|
|
EOF
|
|
|
|
# Flow offload (optional, hardware acceleration).
|
|
if [[ "$FLOW_OFFLOAD" == "true" ]]; then
|
|
local flow_devices="$RED_IF"
|
|
[[ -n "$GREEN_IF" ]] && flow_devices+=", $GREEN_IF"
|
|
cat <<EOF
|
|
|
|
# ── Flow offload (hardware acceleration) ─────────────────────────
|
|
flowtable f1 {
|
|
hook ingress priority 0; devices = { $flow_devices };
|
|
}
|
|
chain flowtable {
|
|
type filter hook forward priority 0; policy accept;
|
|
meta l4proto { tcp, udp } flow add 2>/dev/null accept comment "flow offload"
|
|
}
|
|
EOF
|
|
fi
|
|
|
|
cat <<EOF
|
|
|
|
# ── NAT ──────────────────────────────────────────────────────────
|
|
chain nat_postrouting {
|
|
type nat hook postrouting priority srcnat; policy accept;
|
|
# Masquerade traffic from GREEN/BLUE/ORANGE going out RED.
|
|
$( [[ -n "$GREEN_IF" ]] && echo "oifname \"$RED_IF\" ip saddr $GREEN_CIDR masquerade comment \"GREEN NAT\"" )
|
|
$( [[ -n "$BLUE_IF" ]] && echo "oifname \"$RED_IF\" ip saddr $BLUE_CIDR masquerade comment \"BLUE NAT\"" )
|
|
$( [[ -n "$ORANGE_IF" ]] && echo "oifname \"$RED_IF\" ip saddr $ORANGE_CIDR masquerade comment \"ORANGE NAT\"" )
|
|
}
|
|
|
|
chain nat_prerouting {
|
|
type nat hook prerouting priority dstnat; policy accept;
|
|
# Port-forwarding DNAT rules (RED extport -> ORANGE host:intport).
|
|
EOF
|
|
|
|
# Emit DNAT rules.
|
|
if [[ -n "$DMZ_FORWARDS" ]]; then
|
|
local IFS=','
|
|
for fwd in $DMZ_FORWARDS; do
|
|
local extport="${fwd%%:*}"
|
|
local rest="${fwd#*:}"
|
|
local intport="${rest%%:*}"
|
|
local orangeip="${rest##*:}"
|
|
if [[ -n "$extport" && -n "$intport" && -n "$orangeip" ]]; then
|
|
echo " iifname \"$RED_IF\" tcp dport $extport dnat to $orangeip:$intport"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
cat <<EOF
|
|
}
|
|
}
|
|
EOF
|
|
}
|
|
|
|
# ============================================================================
|
|
# ACTIONS
|
|
# ============================================================================
|
|
|
|
RULES_FILE="$(mktemp /tmp/sysdeck-fw-XXXXXX.nft)"
|
|
trap 'rm -f "$RULES_FILE"' EXIT
|
|
|
|
fw_start() {
|
|
log_info "Starting SysDeck FW (unified nftables zone firewall)..."
|
|
check_root
|
|
check_nftables
|
|
|
|
log_info "Generating ruleset..."
|
|
build_ruleset > "$RULES_FILE"
|
|
|
|
log_info "Validating..."
|
|
if ! "$NFT_CMD" -c -f "$RULES_FILE"; then
|
|
# mktemp, never a fixed /tmp path a local user could pre-place
|
|
local failed_nft
|
|
failed_nft="$(mktemp /tmp/sysdeck-fw-failed.XXXXXX.nft)" || die "mktemp failed"
|
|
cp "$RULES_FILE" "$failed_nft"
|
|
die "Validation failed. Ruleset saved to ${failed_nft}"
|
|
fi
|
|
# Fresh table on every apply: the shell owns the redirect, and a
|
|
# missing table is not an error here (first apply).
|
|
"$NFT_CMD" delete table inet "${TABLE_NAME}" 2>/dev/null || true
|
|
|
|
log_info "Loading..."
|
|
if "$NFT_CMD" -f "$RULES_FILE"; then
|
|
log_info "Firewall started."
|
|
else
|
|
die "Failed to load ruleset."
|
|
fi
|
|
}
|
|
|
|
fw_stop() {
|
|
log_info "Stopping SysDeck FW..."
|
|
check_root
|
|
check_nftables
|
|
"$NFT_CMD" delete table inet "$TABLE_NAME" 2>/dev/null || log_warn "table $TABLE_NAME not present."
|
|
log_info "Firewall stopped."
|
|
}
|
|
|
|
fw_restart() {
|
|
fw_stop
|
|
sleep 1
|
|
fw_start
|
|
}
|
|
|
|
fw_detect() {
|
|
local os_name="unknown"
|
|
if [[ -f /etc/os-release ]]; then . /etc/os-release; os_name="${PRETTY_NAME:-${NAME:-unknown}}"; fi
|
|
autodetect_red 2>/dev/null || RED_IF="unknown"
|
|
cat <<EOF
|
|
+-----------------------------------------------------+
|
|
| Service Detection Summary |
|
|
+-----------------------------------------------------+
|
|
| OS: ${os_name}
|
|
| Template: sysdeck-fw (unified nftables zone firewall)
|
|
| AirWall: ${AIRWALL}
|
|
| Flow offload: ${FLOW_OFFLOAD}
|
|
| Zones:
|
|
| RED (WAN): iface=${RED_IF:-unset} cidr=WAN
|
|
| GREEN (LAN): iface=${GREEN_IF:-unset} cidr=${GREEN_CIDR:-unset}
|
|
| ORANGE (DMZ): iface=${ORANGE_IF:-unset} cidr=${ORANGE_CIDR:-unset}
|
|
| BLUE (WiFi): iface=${BLUE_IF:-unset} cidr=${BLUE_CIDR:-unset}
|
|
| Inbound RED TCP: ${RED_IN_TCP:-none}
|
|
| DMZ forwards: ${DMZ_FORWARDS:-none}
|
|
| Config file: ${CONFIG_FILE}
|
|
+-----------------------------------------------------+
|
|
EOF
|
|
}
|
|
|
|
fw_status() {
|
|
check_nftables
|
|
"$NFT_CMD" list table inet "$TABLE_NAME" 2>&1 || echo "table not loaded."
|
|
}
|
|
|
|
fw_check() {
|
|
check_nftables
|
|
build_ruleset > "$RULES_FILE"
|
|
"$NFT_CMD" -c -f "$RULES_FILE"
|
|
}
|
|
|
|
# ============================================================================
|
|
# DISPATCH
|
|
# ============================================================================
|
|
|
|
main() {
|
|
local command="${1:-help}"
|
|
case "$command" in
|
|
start) fw_start ;;
|
|
stop) fw_stop ;;
|
|
restart|reload) fw_restart ;;
|
|
detect) fw_detect ;;
|
|
status) fw_status ;;
|
|
check|validate) fw_check ;;
|
|
help|--help|-h)
|
|
sed -n '1,90p' "$0"
|
|
;;
|
|
*)
|
|
die "Unknown command: $command\nRun '$0 help' for usage."
|
|
;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|