feat: harden firewall policies telemetry and operations
quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m3s
quality / api (push) Successful in 1m15s
quality / linux-agent-smoke (push) Failing after 27s
quality / docker-check (push) Failing after 2m28s
CD / quality (push) Failing after 5m52s
CD / publish (push) Skipped
quality / changes (push) Successful in 7s
quality / commitlint (push) Skipped
quality / openapi (push) Successful in 30s
quality / web (push) Successful in 1m3s
quality / api (push) Successful in 1m15s
quality / linux-agent-smoke (push) Failing after 27s
quality / docker-check (push) Failing after 2m28s
CD / quality (push) Failing after 5m52s
CD / publish (push) Skipped
Implement reliable agent apply, telemetry v2, analytics retention, guarded rollouts, enrollment, labels and webhooks. Improve ReUI frontend data safety and bounded list loading, and add Docker smoke, recovery and resource checks. Document local verification and pending native acceptance.
This commit is contained in:
1 parent
aa475848fd
commit
9fa6269174
242 files changed
+19872
-3057
No files matched your search
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env bash
|
||||
# EvoFirewall Linux install one-liner
|
||||
# Re-run on an already-installed host updates scripts/timer **and forces nft
|
||||
# rule re-apply** (clears last_hash), keeping credentials
|
||||
# rule re-apply** (clears kernel verification), keeping credentials
|
||||
# (unless EVOFW_INSTALL_FORCE=1 → full re-enroll).
|
||||
set -euo pipefail
|
||||
|
||||
@@ -49,13 +49,12 @@ ensure_pkg() {
|
||||
ensure_pkg curl curl || exit 1
|
||||
need_cmd bash || { echo "missing bash" >&2; exit 1; }
|
||||
|
||||
# JSON parse for enroll; python3 is an acceptable fallback in sync script.
|
||||
if ! need_cmd jq && ! need_cmd python3; then
|
||||
ensure_pkg jq jq || ensure_pkg python3 python3 || {
|
||||
echo "evofw install: need jq or python3" >&2
|
||||
exit 1
|
||||
}
|
||||
fi
|
||||
# Policy validation and nft batch generation require Python; jq alone is not
|
||||
# sufficient. util-linux supplies flock for serialized timer/manual sync runs.
|
||||
ensure_pkg python3 python3 || exit 1
|
||||
ensure_pkg flock util-linux || exit 1
|
||||
ensure_pkg timeout coreutils || exit 1
|
||||
|
||||
|
||||
# Firewall tools: never reinstall/replace if already present.
|
||||
if need_cmd nft; then
|
||||
@@ -63,8 +62,8 @@ if need_cmd nft; then
|
||||
elif need_cmd iptables; then
|
||||
echo "evofw install: iptables already present — skip nftables/iptables install"
|
||||
if ! need_cmd ipset; then
|
||||
echo "evofw install: installing ipset (optional companion for iptables)..."
|
||||
pkg_install ipset 2>/dev/null || true
|
||||
echo "evofw install: installing ipset (required companion for iptables)..."
|
||||
ensure_pkg ipset ipset || exit 1
|
||||
fi
|
||||
else
|
||||
echo "evofw install: no nft/iptables — installing firewall backend..."
|
||||
@@ -111,7 +110,7 @@ if [[ "$HAS_SYSTEMD" -ne 1 ]]; then
|
||||
fi
|
||||
|
||||
: "${EVOFW_CP_URL:?EVOFW_CP_URL required}"
|
||||
: "${EVOFW_SEED:?EVOFW_SEED required}"
|
||||
: "${EVOFW_ENROLLMENT_CREDENTIAL:?Scoped EVOFW_ENROLLMENT_CREDENTIAL required}"
|
||||
: "${EVOFW_CLIENT_NAME:?EVOFW_CLIENT_NAME required}"
|
||||
|
||||
CONF_DIR=/etc/evofw
|
||||
@@ -161,14 +160,24 @@ write_conf() {
|
||||
printf 'CLIENT_TOKEN=%s\n' "$(shell_quote "$client_token")"
|
||||
printf 'CLIENT_NAME=%s\n' "$(shell_quote "$client_name")"
|
||||
printf 'KERNEL_BACKEND=%s\n' "$(shell_quote "$backend")"
|
||||
} >"$CONF_FILE"
|
||||
chmod 600 "$CONF_FILE"
|
||||
} >"${CONF_FILE}.new"
|
||||
chmod 600 "${CONF_FILE}.new"
|
||||
python3 - "${CONF_FILE}.new" "$CONF_FILE" <<'PY_CONF_COMMIT'
|
||||
import os, sys
|
||||
with open(sys.argv[1], 'rb') as value: os.fsync(value.fileno())
|
||||
os.replace(sys.argv[1], sys.argv[2])
|
||||
fd = os.open(os.path.dirname(sys.argv[2]), os.O_RDONLY)
|
||||
try: os.fsync(fd)
|
||||
finally: os.close(fd)
|
||||
PY_CONF_COMMIT
|
||||
}
|
||||
|
||||
install_sync_and_uninstall() {
|
||||
local sync_tmp=$1
|
||||
install -m 755 "$sync_tmp" "$SYNC_SCRIPT"
|
||||
if curl -fsSL "${CP_URL}/v1/agent/uninstall.sh" -o "$UNINSTALL_SCRIPT" 2>/dev/null; then
|
||||
bash -n "$sync_tmp" || return 1
|
||||
install -m 755 "$sync_tmp" "${SYNC_SCRIPT}.new"
|
||||
mv -f "${SYNC_SCRIPT}.new" "$SYNC_SCRIPT"
|
||||
if curl --connect-timeout 10 --max-time 60 -fsSL "${CP_URL}/v1/agent/uninstall.sh" -o "$UNINSTALL_SCRIPT" 2>/dev/null; then
|
||||
chmod 755 "$UNINSTALL_SCRIPT"
|
||||
else
|
||||
echo "evofw install: warning — could not download uninstall.sh (optional)" >&2
|
||||
@@ -214,7 +223,7 @@ UNIT
|
||||
|
||||
download_sync_script() {
|
||||
local out=$1
|
||||
if ! curl -fsSL "${CP_URL}/v1/agent/sync-script" -o "$out"; then
|
||||
if ! curl --connect-timeout 10 --max-time 60 -fsSL "${CP_URL}/v1/agent/sync-script" -o "$out"; then
|
||||
echo "failed to download sync script from ${CP_URL}/v1/agent/sync-script" >&2
|
||||
return 1
|
||||
fi
|
||||
@@ -222,6 +231,7 @@ download_sync_script() {
|
||||
echo "sync script is not a shell script (CP returned unexpected body)" >&2
|
||||
return 1
|
||||
fi
|
||||
bash -n "$out"
|
||||
}
|
||||
|
||||
SYNC_TMP=$(mktemp)
|
||||
@@ -251,33 +261,56 @@ if [[ -f "$CONF_FILE" && "${EVOFW_INSTALL_FORCE:-}" != "1" ]]; then
|
||||
download_sync_script "$SYNC_TMP" || exit 1
|
||||
write_conf "$CLIENT_ID" "$CLIENT_TOKEN" "$CLIENT_NAME" "$BACKEND"
|
||||
install_sync_and_uninstall "$SYNC_TMP"
|
||||
# Force one apply after script refresh (nft set upgrade, counters, port hits, etc.).
|
||||
rm -f /var/lib/evofw/last_hash
|
||||
# Invalidate structural verification to force reconciliation after refresh.
|
||||
rm -f /var/lib/evofw/kernel_state
|
||||
enable_scheduler_and_run
|
||||
echo "Updated. Client id=${CLIENT_ID}. Sync script + timer refreshed; nft rules re-applied."
|
||||
echo "Force sync: $SYNC_SCRIPT"
|
||||
echo "Uninstall: $UNINSTALL_SCRIPT (or: curl -fsSL ${CP_URL}/v1/agent/uninstall.sh | bash)"
|
||||
echo "Uninstall: $UNINSTALL_SCRIPT (or: curl --connect-timeout 10 --max-time 60 -fsSL ${CP_URL}/v1/agent/uninstall.sh | bash)"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
# --- Fresh install (or EVOFW_INSTALL_FORCE=1) ---
|
||||
CLIENT_TOKEN="$(gen_token)"
|
||||
mkdir -p "$CONF_DIR" /var/lib/evofw
|
||||
chmod 700 "$CONF_DIR"
|
||||
exec 8>/var/lib/evofw/sync.lock
|
||||
flock -x 8
|
||||
ENROLL_PENDING="$CONF_DIR/enrollment-pending.json"
|
||||
CLIENT_TOKEN=$(python3 - "$ENROLL_PENDING" "$CP_URL" "${EVOFW_INSTALL_LINK_ID:-}" <<'PY_ENROLL_PENDING'
|
||||
import json, os, secrets, sys
|
||||
from pathlib import Path
|
||||
path = Path(sys.argv[1]); origin, link = sys.argv[2:]
|
||||
if not link: raise SystemExit('install link required')
|
||||
if path.exists():
|
||||
state = json.loads(path.read_text())
|
||||
if state.get('origin') != origin or state.get('link') != link: raise SystemExit('pending enrollment belongs to another link')
|
||||
else:
|
||||
state = {'origin': origin, 'link': link, 'token': 'evofw_' + secrets.token_urlsafe(32)}
|
||||
fd = os.open(str(path) + '.new', os.O_WRONLY | os.O_CREAT | os.O_TRUNC, 0o600)
|
||||
with os.fdopen(fd, 'w') as out:
|
||||
json.dump(state, out); out.flush(); os.fsync(out.fileno())
|
||||
os.replace(str(path) + '.new', path)
|
||||
fd = os.open(str(path.parent), os.O_RDONLY)
|
||||
try: os.fsync(fd)
|
||||
finally: os.close(fd)
|
||||
print(state['token'])
|
||||
PY_ENROLL_PENDING
|
||||
)
|
||||
HOSTNAME="$(hostname -f 2>/dev/null || hostname)"
|
||||
|
||||
# Fail fast: pull sync script before enroll so we never leave a DB agent without a local agent.
|
||||
download_sync_script "$SYNC_TMP" || exit 1
|
||||
|
||||
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")
|
||||
else
|
||||
ENROLL_BODY=$(printf '{"name":"%s","hostname":"%s","platform":"%s","token":"%s","client_version":"install.sh/1"}' \
|
||||
"$EVOFW_CLIENT_NAME" "$HOSTNAME" "$PLATFORM" "$CLIENT_TOKEN")
|
||||
fi
|
||||
ENROLL_BODY=$(python3 - "$EVOFW_CLIENT_NAME" "$HOSTNAME" "$PLATFORM" "$CLIENT_TOKEN" "$EVOFW_INSTALL_LINK_ID" <<'PY_ENROLL_BODY'
|
||||
import json, sys
|
||||
name, hostname, platform, token, link = sys.argv[1:]
|
||||
print(json.dumps(dict(name=name, hostname=hostname, platform=platform, token=token, client_version='install.sh/2', install_link_id=link)))
|
||||
PY_ENROLL_BODY
|
||||
)
|
||||
|
||||
ENROLL_CODE=$(curl -sS -o "$ENROLL_TMP" -w "%{http_code}" -X POST "${CP_URL}/v1/agent/enroll" \
|
||||
ENROLL_CODE=$(curl --connect-timeout 10 --max-time 60 -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}" \
|
||||
-H "X-EvoFW-Enrollment-Credential: ${EVOFW_ENROLLMENT_CREDENTIAL}" \
|
||||
-d "$ENROLL_BODY")
|
||||
if [[ "$ENROLL_CODE" != "201" && "$ENROLL_CODE" != "200" ]]; then
|
||||
echo "enroll failed: HTTP ${ENROLL_CODE}" >&2
|
||||
@@ -304,10 +337,14 @@ if [[ -z "$BACKEND" ]]; then
|
||||
fi
|
||||
|
||||
write_conf "$CLIENT_ID" "$CLIENT_TOKEN" "$EVOFW_CLIENT_NAME" "$BACKEND"
|
||||
# Config is committed before pending enrollment can be removed.
|
||||
rm -f "$ENROLL_PENDING"
|
||||
flock -u 8
|
||||
exec 8>&-
|
||||
install_sync_and_uninstall "$SYNC_TMP"
|
||||
enable_scheduler_and_run
|
||||
|
||||
echo "Installed. Client id=${CLIENT_ID}. Approve in EvoFirewall UI (rules optional — can assign later)."
|
||||
echo "If still offline after Approve, run: $SYNC_SCRIPT"
|
||||
echo "Re-run the same install URL to update scripts without re-enroll."
|
||||
echo "Uninstall: $UNINSTALL_SCRIPT (or: curl -fsSL ${CP_URL}/v1/agent/uninstall.sh | bash)"
|
||||
echo "Uninstall: $UNINSTALL_SCRIPT (or: curl --connect-timeout 10 --max-time 60 -fsSL ${CP_URL}/v1/agent/uninstall.sh | bash)"
|
||||
Reference in new issue
Block a user