feat(api): enhance agent scripts and enrollment process
Build and Push EvoFirewall Docker Image / build-and-push (push) Successful in 1m43s
Build and Push EvoFirewall Docker Image / create-release (push) Skipped

- Updated `evofw-firewall.sh` to handle empty deny/allow rule sets, allowing agents without rules to function correctly.
- Improved `install.sh` to ensure the sync script is downloaded before enrollment, with added validation for the script's content.
- Modified agent route to record `lastSeenAt` and `lastSeenIp` during enrollment and policy fetching, ensuring accurate tracking of agent status.
- Added tests to verify that approved agents can fetch an empty policy without rule sets.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Denozordec
2026-07-21 19:25:34 +07:00
co-authored by Cursor
parent 08e7c4d755
commit d516a9a093
4 changed files with 99 additions and 12 deletions
+7 -4
View File
@@ -76,6 +76,9 @@ PY
HASH=""; MODE=blacklist; DENY=(); ALLOW=()
parse_policy "$POLICY_FILE"
# Empty deny/allow is valid — agent may have no rule sets yet.
DENY=("${DENY[@]+"${DENY[@]}"}")
ALLOW=("${ALLOW[@]+"${ALLOW[@]}"}")
log "mode=$MODE deny=${#DENY[@]} allow=${#ALLOW[@]} hash=$HASH"
PACKETS_DROPPED=0
@@ -116,8 +119,8 @@ collect_nft_stats() {
apply_nft() {
local table=inet name=evofw
local deny_v4=() allow_v4=() p
for p in "${DENY[@]}"; do [[ "$p" == *:* ]] && continue; deny_v4+=("$p"); done
for p in "${ALLOW[@]}"; do [[ "$p" == *:* ]] && continue; allow_v4+=("$p"); done
for p in "${DENY[@]+"${DENY[@]}"}"; do [[ "$p" == *:* ]] && continue; deny_v4+=("$p"); done
for p in "${ALLOW[@]+"${ALLOW[@]}"}"; do [[ "$p" == *:* ]] && continue; allow_v4+=("$p"); done
nft list table "$table" "$name" >/dev/null 2>&1 || nft add table "$table" "$name"
nft list set "$table" "$name" deny_v4 >/dev/null 2>&1 || \
@@ -162,8 +165,8 @@ apply_ipset() {
ipset list "$aset" >/dev/null 2>&1 || ipset create "$aset" hash:net family inet
ipset flush "$dset"; ipset flush "$aset"
local p n=0
for p in "${DENY[@]}"; do [[ "$p" == *:* ]] && continue; ipset add "$dset" "$p" -exist; n=$((n+1)); done
for p in "${ALLOW[@]}"; do [[ "$p" == *:* ]] && continue; ipset add "$aset" "$p" -exist; n=$((n+1)); done
for p in "${DENY[@]+"${DENY[@]}"}"; do [[ "$p" == *:* ]] && continue; ipset add "$dset" "$p" -exist; n=$((n+1)); done
for p in "${ALLOW[@]+"${ALLOW[@]}"}"; do [[ "$p" == *:* ]] && continue; ipset add "$aset" "$p" -exist; n=$((n+1)); done
iptables -D INPUT -m set --match-set "$dset" src -j DROP 2>/dev/null || true
iptables -D INPUT -m set --match-set "$aset" src -j ACCEPT 2>/dev/null || true
if [[ "$MODE" == "whitelist" ]]; then
+26 -5
View File
@@ -43,6 +43,19 @@ CLIENT_TOKEN="$(gen_token)"
HOSTNAME="$(hostname -f 2>/dev/null || hostname)"
CP_URL="${EVOFW_CP_URL%/}"
# Fail fast: pull sync script before enroll so we never leave a DB agent without a local agent.
SYNC_TMP=$(mktemp)
ENROLL_TMP=$(mktemp)
trap 'rm -f "$SYNC_TMP" "$ENROLL_TMP"' EXIT
if ! curl -fsSL "${CP_URL}/v1/agent/sync-script" -o "$SYNC_TMP"; then
echo "failed to download sync script from ${CP_URL}/v1/agent/sync-script" >&2
exit 1
fi
if ! head -n1 "$SYNC_TMP" | grep -q '^#!'; then
echo "sync script is not a shell script (CP returned unexpected body)" >&2
exit 1
fi
if [[ -n "${EVOFW_INSTALL_LINK_ID:-}" ]]; then
ENROLL_BODY=$(printf '{"name":"%s","hostname":"%s","platform":"%s","token":"%s","client_version":"install.sh/1","install_link_id":"%s"}' \
"$EVOFW_CLIENT_NAME" "$HOSTNAME" "$PLATFORM" "$CLIENT_TOKEN" "$EVOFW_INSTALL_LINK_ID")
@@ -51,8 +64,6 @@ else
"$EVOFW_CLIENT_NAME" "$HOSTNAME" "$PLATFORM" "$CLIENT_TOKEN")
fi
ENROLL_TMP=$(mktemp)
trap 'rm -f "$ENROLL_TMP"' EXIT
ENROLL_CODE=$(curl -sS -o "$ENROLL_TMP" -w "%{http_code}" -X POST "${CP_URL}/v1/agent/enroll" \
-H "Content-Type: application/json" \
-H "X-EvoFW-Seed: ${EVOFW_SEED}" \
@@ -69,6 +80,11 @@ if command -v jq >/dev/null 2>&1; then
else
CLIENT_ID=$(echo "$RESP" | sed -n 's/.*"client_id"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p')
fi
if [[ -z "$CLIENT_ID" || "$CLIENT_ID" == "null" ]]; then
echo "enroll response missing client_id" >&2
cat "$ENROLL_TMP" >&2
exit 1
fi
mkdir -p "$CONF_DIR"
chmod 700 "$CONF_DIR"
@@ -81,8 +97,7 @@ KERNEL_BACKEND=auto
EOF
chmod 600 "$CONF_FILE"
curl -fsSL "${CP_URL}/v1/agent/sync-script" -o "$SYNC_SCRIPT"
chmod 755 "$SYNC_SCRIPT"
install -m 755 "$SYNC_TMP" "$SYNC_SCRIPT"
if command -v nft >/dev/null 2>&1; then
BACKEND=nft
@@ -103,6 +118,7 @@ if command -v systemctl >/dev/null 2>&1; then
[Unit]
Description=EvoFirewall sync
After=network-online.target
Wants=network-online.target
[Service]
Type=oneshot
@@ -116,6 +132,7 @@ Description=EvoFirewall sync timer
OnBootSec=30s
OnUnitActiveSec=${INTERVAL}
AccuracySec=5s
Persistent=true
Unit=evofw-firewall.service
[Install]
@@ -123,8 +140,12 @@ WantedBy=timers.target
UNIT
systemctl daemon-reload
systemctl enable --now evofw-firewall.timer
# First run now (pending → log "pending approval"; after Approve → empty policy is OK).
systemctl start evofw-firewall.service || true
else
(crontab -l 2>/dev/null | grep -v evofw-firewall; echo "*/1 * * * * $SYNC_SCRIPT") | crontab -
"$SYNC_SCRIPT" || true
fi
echo "Installed. Client id=${CLIENT_ID}. Approve in EvoFirewall UI, then: $SYNC_SCRIPT"
echo "Installed. Client id=${CLIENT_ID}. Approve in EvoFirewall UI (rules optional — can assign later)."
echo "If Still offline after Approve, run: $SYNC_SCRIPT"