initial commit
This commit is contained in:
@@ -0,0 +1,132 @@
|
||||
#!/bin/bash
|
||||
# wg-sync-policy.sh — Reads wg0.conf, validates, generates policy.json atomically
|
||||
# Fixed: IP validation, atomic write, proper locking, error handling
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
source "${SCRIPT_DIR}/wg-policy-lib.sh"
|
||||
|
||||
# ============================================================
|
||||
# MAIN
|
||||
# ============================================================
|
||||
|
||||
main() {
|
||||
log_info "Starting policy sync..."
|
||||
|
||||
# Validate prerequisites
|
||||
if [[ ! -f "$WG_CONF" ]]; then
|
||||
log_error "WireGuard config not found: $WG_CONF"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! command -v jq &>/dev/null; then
|
||||
log_error "jq is required but not installed"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Acquire lock
|
||||
acquire_lock
|
||||
trap 'release_lock' EXIT
|
||||
|
||||
# Backup current policy
|
||||
backup_policy
|
||||
|
||||
# Temporary file for atomic write
|
||||
local tmp_policy
|
||||
tmp_policy="$(mktemp /tmp/wg-policy.XXXXXX)"
|
||||
trap 'rm -f "$tmp_policy" 2>/dev/null; release_lock' EXIT
|
||||
|
||||
echo '{"clients":{}}' > "$tmp_policy"
|
||||
|
||||
# Parse peers from wg0.conf
|
||||
# AWK extracts IP and #Access comment per [Peer] block
|
||||
local parse_errors=0
|
||||
|
||||
awk '
|
||||
BEGIN { RS="\n\\[Peer\\]\n"; FS="\n" }
|
||||
NR>1 {
|
||||
ip=""; access=""
|
||||
for(i=1;i<=NF;i++){
|
||||
if($i ~ /^AllowedIPs/) {
|
||||
split($i,a," = ")
|
||||
gsub(/ /,"",a[2])
|
||||
split(a[2],b,",")
|
||||
split(b[1],c,"/")
|
||||
ip=c[1]
|
||||
}
|
||||
if($i ~ /^#Access/) {
|
||||
sub(/^#Access[ \t]+/, "", $i)
|
||||
access=$i
|
||||
}
|
||||
}
|
||||
if(ip!="" && ip!="0.0.0.0" && ip!="::") {
|
||||
printf "%s|%s\n", ip, access
|
||||
}
|
||||
}
|
||||
' "$WG_CONF" | while IFS="|" read -r ip access_string; do
|
||||
|
||||
# === VALIDATE CLIENT IP ===
|
||||
if ! validate_cidr "$ip"; then
|
||||
log_warn "Invalid client IP skipped: '$ip'"
|
||||
(( parse_errors++ )) || true
|
||||
continue
|
||||
fi
|
||||
|
||||
# === PARSE AND VALIDATE ACCESS TARGETS ===
|
||||
local ACCESS_JSON="[]"
|
||||
|
||||
if [[ -n "$access_string" ]]; then
|
||||
# Split by ; and , then validate each entry
|
||||
local valid_targets=()
|
||||
local IFS_OLD="$IFS"
|
||||
IFS=';,'
|
||||
read -ra targets <<< "$access_string"
|
||||
IFS="$IFS_OLD"
|
||||
|
||||
for target in "${targets[@]}"; do
|
||||
# Trim whitespace
|
||||
target="$(echo "$target" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//')"
|
||||
|
||||
[[ -z "$target" ]] && continue
|
||||
|
||||
if validate_cidr "$target"; then
|
||||
valid_targets+=("$target")
|
||||
else
|
||||
log_warn "Invalid access target skipped for $ip: '$target'"
|
||||
(( parse_errors++ )) || true
|
||||
fi
|
||||
done
|
||||
|
||||
if (( ${#valid_targets[@]} > 0 )); then
|
||||
ACCESS_JSON=$(printf '%s\n' "${valid_targets[@]}" | jq -R . | jq -s .)
|
||||
fi
|
||||
fi
|
||||
|
||||
# Write to temp policy
|
||||
jq --arg ip "$ip" --argjson access "$ACCESS_JSON" \
|
||||
'.clients[$ip] = {"name": $ip, "access": $access}' \
|
||||
"$tmp_policy" > "${tmp_policy}.tmp" && mv "${tmp_policy}.tmp" "$tmp_policy"
|
||||
|
||||
done
|
||||
|
||||
# Validate JSON before atomic move
|
||||
if ! jq empty "$tmp_policy" 2>/dev/null; then
|
||||
log_error "Generated JSON is invalid, aborting. Check $tmp_policy"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Atomic move (same filesystem = atomic rename)
|
||||
mv -f "$tmp_policy" "$POLICY_FILE"
|
||||
log_info "policy.json updated successfully"
|
||||
|
||||
if (( parse_errors > 0 )); then
|
||||
log_warn "$parse_errors validation errors encountered (see warnings above)"
|
||||
fi
|
||||
|
||||
local client_count
|
||||
client_count=$(jq '(.clients // {}) | length' "$POLICY_FILE")
|
||||
log_info "Total clients in policy: $client_count"
|
||||
}
|
||||
|
||||
main "$@"
|
||||
Reference in New Issue
Block a user