CI / changes (push) Successful in 12s
CI / commitlint (push) Has been skipped
CI / openapi (push) Successful in 25s
CI / web (push) Successful in 46s
CI / go (push) Successful in 1m15s
CI / bird2 (push) Successful in 18s
CI / release (push) Successful in 3m59s
Introduced a comprehensive firewall blocklist feature, allowing for the management of firewall clients and their associated rules. This includes endpoints for enrolling clients, listing clients and rules, and reporting apply statuses. Enhanced the API to support firewall operations, including the ability to handle block/accept policies. Updated the documentation to reflect these changes and added necessary components in the web UI for better user interaction. Additionally, modified the agent server to support firewall failover and integrated firewall functionality into the existing architecture.
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
|