b5c9b180dc
- Rewrite wg-policy-lib.sh: replace ipset functions with nft helpers, add backup_nftables() - Rewrite wg-policy-engine.sh: generate nft ruleset file, atomic load via nft -f, rollback support - Simplify wg-policy-cleanup.sh: single nft delete table inet wg_policy - Update wg-policy-ctl: nft commands for rules/ipset/stats/backup - Rebuild install.sh: nftables dependency, WireGuard pre-check, PostUp/PostDown auto-integration - Update build.sh/build.bat: match install.sh changes - Update README.md: nftables prerequisites and references
174 lines
4.6 KiB
Bash
174 lines
4.6 KiB
Bash
#!/bin/bash
|
|
# wg-policy-ctl — CLI management tool for WireGuard Policy Firewall
|
|
|
|
set -euo pipefail
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
source "${SCRIPT_DIR}/wg-policy-lib.sh"
|
|
|
|
usage() {
|
|
cat <<EOF
|
|
Usage: $(basename "$0") <command>
|
|
|
|
Commands:
|
|
status Show full health check report
|
|
policy Display current policy.json formatted
|
|
rules Show current nftables rules
|
|
ipset Show nftables set contents
|
|
log Tail WG_DROP logs (last 50 lines)
|
|
reload Force re-sync and re-apply policy
|
|
backup Manual backup of policy + nftables
|
|
stats Show connection and rule statistics
|
|
validate Validate policy.json without applying
|
|
help Show this help
|
|
EOF
|
|
}
|
|
|
|
cmd_status() {
|
|
echo "========================================="
|
|
echo " WireGuard Policy Firewall Status"
|
|
echo " $(date '+%Y-%m-%d %H:%M:%S')"
|
|
echo "========================================="
|
|
echo ""
|
|
health_check
|
|
}
|
|
|
|
cmd_policy() {
|
|
if [[ -f "$POLICY_FILE" ]]; then
|
|
jq '.' "$POLICY_FILE"
|
|
else
|
|
log_error "policy.json not found"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
cmd_rules() {
|
|
echo "=== Table: $NFT_TABLE_FULL ==="
|
|
if nft list table "$NFT_TABLE_FULL" 2>/dev/null; then
|
|
echo ""
|
|
else
|
|
echo "(table not found)"
|
|
fi
|
|
}
|
|
|
|
cmd_ipset() {
|
|
for set_name in "$NFT_SET_V4" "$NFT_SET_V6"; do
|
|
echo "=== nft set: $set_name ==="
|
|
if nft list set "$NFT_TABLE_FULL" "$set_name" 2>/dev/null; then
|
|
echo ""
|
|
else
|
|
echo "(not found)"
|
|
echo ""
|
|
fi
|
|
done
|
|
}
|
|
|
|
cmd_log() {
|
|
echo "=== Recent WG_DROP log entries ==="
|
|
(journalctl -k --no-pager -n 50 2>/dev/null || dmesg | tail -50) | grep "$LOG_PREFIX" || echo "(no entries)"
|
|
}
|
|
|
|
cmd_reload() {
|
|
log_info "Force reloading policy..."
|
|
if retry /usr/local/bin/wg-sync-policy.sh; then
|
|
if retry /usr/local/bin/wg-policy-engine.sh; then
|
|
log_info "Reload complete"
|
|
else
|
|
log_error "Engine failed"
|
|
exit 1
|
|
fi
|
|
else
|
|
log_error "Sync failed"
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
cmd_backup() {
|
|
backup_policy
|
|
backup_nftables
|
|
log_info "Manual backup complete. Files in: $BACKUP_DIR"
|
|
}
|
|
|
|
cmd_stats() {
|
|
echo "=== Client Count ==="
|
|
jq '(.clients // {}) | length' "$POLICY_FILE" 2>/dev/null || echo "N/A"
|
|
|
|
echo ""
|
|
echo "=== Clients with Access ==="
|
|
jq -r '.clients // {} | to_entries[] | select(.value.access | length > 0) | "\(.key): \(.value.access | join(", "))"' "$POLICY_FILE" 2>/dev/null || echo "N/A"
|
|
|
|
echo ""
|
|
echo "=== Clients without Access (Internet Only) ==="
|
|
jq -r '.clients // {} | to_entries[] | select(.value.access | length == 0) | .key' "$POLICY_FILE" 2>/dev/null || echo "N/A"
|
|
|
|
echo ""
|
|
echo "=== Active nftables rules ==="
|
|
nft list chain "$NFT_TABLE_FULL" forward 2>/dev/null | grep -c '#' || echo "N/A"
|
|
|
|
echo ""
|
|
echo "=== nft set entries ==="
|
|
for set_name in "$NFT_SET_V4" "$NFT_SET_V6"; do
|
|
local count
|
|
count=$(nft list set "$NFT_TABLE_FULL" "$set_name" 2>/dev/null | grep -c -E '^\s+[0-9a-f]' || echo 0)
|
|
echo " $set_name: $count entries"
|
|
done
|
|
}
|
|
|
|
cmd_validate() {
|
|
log_info "Validating policy.json..."
|
|
|
|
if [[ ! -f "$POLICY_FILE" ]]; then
|
|
log_error "File not found: $POLICY_FILE"
|
|
exit 1
|
|
fi
|
|
|
|
if ! jq empty "$POLICY_FILE" 2>/dev/null; then
|
|
log_error "Invalid JSON"
|
|
exit 1
|
|
fi
|
|
|
|
local errors=0
|
|
local total=0
|
|
|
|
jq -r '.clients // {} | to_entries[] | "\(.key)|\(.value.access // [] | join(","))"' "$POLICY_FILE" | \
|
|
while IFS="|" read -r ip access_str; do
|
|
(( total++ ))
|
|
|
|
if ! validate_cidr "$ip"; then
|
|
log_error "Invalid client IP: $ip"
|
|
(( errors++ )) || true
|
|
fi
|
|
|
|
if [[ -n "$access_str" ]]; then
|
|
IFS=',' read -ra targets <<< "$access_str"
|
|
for target in "${targets[@]}"; do
|
|
if ! validate_cidr "$target"; then
|
|
log_error "Invalid access target for $ip: $target"
|
|
(( errors++ )) || true
|
|
fi
|
|
done
|
|
fi
|
|
done
|
|
|
|
if (( errors > 0 )); then
|
|
log_error "Validation failed: $errors errors"
|
|
exit 1
|
|
else
|
|
log_info "Validation passed: $total clients, 0 errors"
|
|
fi
|
|
}
|
|
|
|
# === DISPATCH ===
|
|
case "${1:-help}" in
|
|
status) cmd_status ;;
|
|
policy) cmd_policy ;;
|
|
rules) cmd_rules ;;
|
|
ipset) cmd_ipset ;;
|
|
log) cmd_log ;;
|
|
reload) cmd_reload ;;
|
|
backup) cmd_backup ;;
|
|
stats) cmd_stats ;;
|
|
validate) cmd_validate ;;
|
|
help|*) usage ;;
|
|
esac
|