#!/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