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
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.
170 lines
4.8 KiB
Bash
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
|