4.7 KiB
WireGuard Dynamic Policy Firewall
A lightweight, robust, and highly dynamic iptables/ipset policy firewall engine designed to restrict and control WireGuard peer traffic (egress traffic mapping) straight from wg0.conf.
Rather than allowing all VPN clients to reach any part of your internal network, this tool isolates clients from each other by default and reads a custom #Access comment inside wg0.conf to automatically generate strict iptables rules and ipset whitelists per-client on the fly.
🎯 Architecture & Data Flow
wg0.conf: The standard WireGuard configuration. Contains standard[Peer]configs alongside a custom#Accesstag.wg-sync-policy.sh: Safely parseswg0.confand generates a structured/etc/wireguard/policy.jsonatomically.wg-policy-engine.sh: Readspolicy.jsonto generate robust rules, applyingiptablesandipsetdirectly to the system.- Watcher Daemon: Monitors
wg0.conffor changes viainotifywaitand triggers the pipeline seamlessly when updates are made.
📁 File Locations & Installation
All scripts must be placed in /usr/local/bin/ and made executable.
| File | Location | Description |
|---|---|---|
wg-policy-lib.sh |
/usr/local/bin/wg-policy-lib.sh |
Shared library and core validation tools. |
wg-sync-policy.sh |
/usr/local/bin/wg-sync-policy.sh |
Extracts config into atomic JSON format. |
wg-policy-engine.sh |
/usr/local/bin/wg-policy-engine.sh |
Translates JSON into iptables & ipset logic. |
wg-policy-cleanup.sh |
/usr/local/bin/wg-policy-cleanup.sh |
Reverts and cleans up all firewall traces safely. |
wg-sync-watch.sh |
/usr/local/bin/wg-sync-watch.sh |
Debounced file watcher (daemon). |
wg-policy-ctl |
/usr/local/bin/wg-policy-ctl |
Handy command-line interface tool. |
Make sure they are executable:
chmod +x /usr/local/bin/wg-*.sh /usr/local/bin/wg-policy-ctl
⚙️ Integrasi ke wg0.conf
To integrate the engine, you need to append hooks into your wg0.conf interface block, and declare the #Access tags under each peer.
1. Interface Block (Hooks)
Add the PostUp and PostDown scripts so the engine initializes correctly during VPN startup and removes traces upon shutdown.
[Interface]
Address = 10.0.0.1/24
ListenPort = 51820
PrivateKey = <SERVER_PRIVATE_KEY>
# PostUp: sync policy + apply engine
PostUp = /usr/local/bin/wg-sync-policy.sh && /usr/local/bin/wg-policy-engine.sh
# PostDown: safe cleanup
PostDown = /usr/local/bin/wg-policy-cleanup.sh
2. Peer Block (#Access Tags)
For each client, use the #Access comment line. Define the destinations (targets) the peer is allowed to access. You can separate multiple IPs or CIDRs with commas or semicolons.
[Peer]
PublicKey = <CLIENT_1_PUBKEY>
AllowedIPs = 10.0.0.2/32
#Access 192.168.1.10/32, 192.168.12.0/24
[Peer]
PublicKey = <CLIENT_2_PUBKEY>
AllowedIPs = 10.0.0.3/32
#Access = 10.0.0.1/32
[Peer]
PublicKey = <CLIENT_3_PUBKEY>
AllowedIPs = 10.0.0.4/32
#Access
# ^ (Empty Access implies internet-only, client isolation applies)
⚠️ Important constraint: Why #Access instead of just using AllowedIPs directly?
WireGuard uses AllowedIPs for Cryptokey Routing (deciding which tunnel interface to route outbound packets). If you put an internal server IP inside the server's wg0.conf AllowedIPs block, WireGuard will aggressively capture and redirect packets bound for that internal server into the VPN client's tunnel. The #Access comment separates routing parameters from firewall parameters cleanly.
🛠 wg-policy-ctl CLI Usage
You don't need to manually interact with iptables or .json files. Use the wg-policy-ctl wrapper.
# View the health of the firewall engine and active locks
wg-policy-ctl status
# View the raw, parsed JSON policy
wg-policy-ctl policy
# Inspect active iptables rules
wg-policy-ctl rules
# Check memory sets mapping IP targets (ipset)
wg-policy-ctl ipset
# Manually re-sync rules immediately
wg-policy-ctl reload
# Inspect dropped packets (rate-limited log output)
wg-policy-ctl log
# See connection statistics, how many targets loaded
wg-policy-ctl stats
# Force validation of the policy schema
wg-policy-ctl validate
🔧 Systemd Integration (Watcher Daemon)
If you are using the daemon mode to auto-sync changes instantly upon editing wg0.conf (without needing to run wg-policy-ctl reload or restarting the interface).
Enable the systemd services:
systemctl daemon-reload
systemctl enable wg-policy.service
systemctl enable wg-policy-health.timer
systemctl start wg-policy.service
systemctl start wg-policy-health.timer
Check the watcher logs:
journalctl -u wg-policy.service -f