Files
EvoBGP/internal/firewallscripts/evobgp-firewall.sh
T
Denozordec 7b3f002e5f
CI / changes (push) Successful in 10s
CI / commitlint (push) Has been skipped
CI / openapi (push) Has been skipped
CI / web (push) Successful in 57s
CI / go (push) Successful in 1m11s
CI / bird2 (push) Successful in 15s
CI / release (push) Successful in 4m16s
feat(firewall): enhance firewall management with community selection and script handling
Added a community selection feature to the firewall rules management UI, allowing users to specify BGP communities for block/accept policies. Updated the backend to support reading firewall scripts from a specified directory, improving script management. Enhanced documentation to clarify the new community functionality and its implications for firewall rules. Additionally, introduced tests for the firewall script endpoints to ensure proper functionality.
2026-07-08 17:50:38 +07:00

170 lines
4.8 KiB
Bash

#!/usr/bin/env bash
set -euo pipefail
CONF_FILE=/etc/evobgp/firewall.conf
LOG_FILE=/var/log/evobgp-firewall.log
STATE_DIR=/var/lib/evobgp-firewall
HASH_FILE="${STATE_DIR}/last_hash"
log() { echo "$(date -u +%Y-%m-%dT%H:%M:%SZ) $*" | tee -a "$LOG_FILE"; }
if [[ ! -f "$CONF_FILE" ]]; then
log "missing $CONF_FILE"
exit 1
fi
# shellcheck disable=SC1090
source "$CONF_FILE"
: "${EVOBGP_CP_URL:?}"
: "${CLIENT_TOKEN:?}"
mkdir -p "$STATE_DIR"
BACKEND="${KERNEL_BACKEND:-auto}"
curl_get_blocklist() {
local url="$1"
local host
host=$(echo "$url" | sed -E 's#https?://([^/]+)/?.*#\1#')
local tmp
tmp=$(mktemp)
local code
code=$(curl -sS -o "$tmp" -w "%{http_code}" \
-H "Authorization: Bearer ${CLIENT_TOKEN}" \
-H "Accept: application/json" \
"${url}/v1/firewall/blocklist") || return 1
if [[ "$code" == "403" ]]; then
log "pending approval"
rm -f "$tmp"
exit 0
fi
if [[ "$code" != "200" ]]; then
log "blocklist HTTP $code from $url"
rm -f "$tmp"
return 1
fi
cat "$tmp"
rm -f "$tmp"
}
try_urls() {
local urls=()
if [[ -n "${EVOBGP_FAILOVER_URLS:-}" ]]; then
IFS=',' read -r -a urls <<<"$EVOBGP_FAILOVER_URLS"
else
urls=("${EVOBGP_CP_URL%/}")
fi
local u
for u in "${urls[@]}"; do
u="${u// /}"
u="${u%/}"
if OUT=$(curl_get_blocklist "$u"); then
CP_HIT="$u"
return 0
fi
done
return 1
}
if ! OUT=$(try_urls); then
log "all endpoints failed"
exit 1
fi
if command -v jq >/dev/null 2>&1; then
HASH=$(echo "$OUT" | jq -r '.hash // empty')
TOTAL=$(echo "$OUT" | jq -r '.total // 0')
mapfile -t PREFIXES < <(echo "$OUT" | jq -r '.prefixes[]?')
else
HASH=$(echo "$OUT" | grep -o '"hash"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"\(sha256:[^"]*\)".*/\1/')
TOTAL=$(echo "$OUT" | grep -o '"total"[[:space:]]*:[[:space:]]*[0-9]*' | head -1 | grep -o '[0-9]*$')
mapfile -t PREFIXES < <(echo "$OUT" | grep -o '"[0-9a-fA-F:.]*/[0-9]*"' | tr -d '"')
fi
if [[ -f "$HASH_FILE" && "$(cat "$HASH_FILE")" == "$HASH" ]]; then
log "unchanged hash $HASH — skip kernel apply"
exit 0
fi
apply_nft() {
local table=inet
local name=evobgp_blocklist
nft list table "$table" "$name" >/dev/null 2>&1 || nft add table "$table" "$name"
nft list set "$table" "$name" v4 >/dev/null 2>&1 || nft add set "$table" "$name" v4 '{ type ipv4_addr; flags interval; }'
nft flush set "$table" "$name" v4
if ((${#PREFIXES[@]})); then
local v4=()
local p
for p in "${PREFIXES[@]}"; do
[[ "$p" == *:* ]] && continue
v4+=("$p")
done
if ((${#v4[@]})); then
nft add element "$table" "$name" v4 "{ $(IFS=,; echo "${v4[*]}") }"
fi
fi
nft list chain "$table" "$name" input >/dev/null 2>&1 || {
nft add chain "$table" "$name" input '{ type filter hook input priority 0; }'
nft add rule "$table" "$name" input ip saddr @v4 drop
}
}
apply_ipset() {
local set=evobgp_blocklist_v4
ipset list "$set" >/dev/null 2>&1 || ipset create "$set" hash:net family inet hashsize 4096 maxelem 1048576
ipset flush "$set"
local p
for p in "${PREFIXES[@]}"; do
[[ "$p" == *:* ]] && continue
ipset add "$set" "$p" -exist
done
iptables -C INPUT -m set --match-set "$set" src -j DROP 2>/dev/null || \
iptables -I INPUT -m set --match-set "$set" src -j DROP
}
apply_iptables_only() {
iptables -D INPUT -m comment --comment evobgp-block -j DROP 2>/dev/null || true
if ((${#PREFIXES[@]})); then
local p
for p in "${PREFIXES[@]}"; do
[[ "$p" == *:* ]] && continue
iptables -C INPUT -s "$p" -j DROP 2>/dev/null || iptables -A INPUT -s "$p" -j DROP
done
fi
}
clear_block() {
case "$BACKEND" in
nft) nft delete table inet evobgp_blocklist 2>/dev/null || true ;;
ipset)
ipset destroy evobgp_blocklist_v4 2>/dev/null || true
iptables -D INPUT -m set --match-set evobgp_blocklist_v4 src -j DROP 2>/dev/null || true
;;
iptables) iptables -S INPUT | grep -i evobgp | sed 's/^-A /-D /' | while read -r line; do iptables $line 2>/dev/null || true; done ;;
esac
}
if [[ "$TOTAL" == "0" || ${#PREFIXES[@]} -eq 0 ]]; then
clear_block
else
case "$BACKEND" in
nft|auto) if command -v nft >/dev/null 2>&1; then apply_nft; else apply_ipset; fi ;;
ipset) apply_ipset ;;
iptables) apply_iptables_only ;;
*) apply_ipset ;;
esac
fi
echo "$HASH" >"$HASH_FILE"
log "applied $TOTAL prefixes from ${CP_HIT:-$EVOBGP_CP_URL} backend=$BACKEND"
REPORT=$(printf '{"status":"ok","prefix_count":%s,"ip_count":0,"source":"cp"}' "${TOTAL:-0}")
curl -fsS -X POST "${EVOBGP_CP_URL%/}/v1/firewall/apply-report" \
-H "Authorization: Bearer ${CLIENT_TOKEN}" \
-H "Content-Type: application/json" \
-d "$REPORT" >/dev/null 2>&1 || true
curl -fsS -X POST "${EVOBGP_CP_URL%/}/v1/firewall/heartbeat" \
-H "Authorization: Bearer ${CLIENT_TOKEN}" \
-H "Content-Type: application/json" \
-d '{"source":"cp"}' >/dev/null 2>&1 || true