diff --git a/AGENTS.md b/AGENTS.md index 6598b46..7b4aad8 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,104 +1,116 @@ -# WireGuard Policy Firewall + WGRplane (`03.wireguard-policy`) +# AGENTS.md -- WireGuard Policy Firewall + WGRplane -This repository contains two components: -1. **WireGuard Policy Firewall** - Shell script-based dynamic iptables/ipset policy engine -2. **WGRplane** - Go-native control plane with Vue 3 frontend +**Generated:** 2026-05-08 +**Project:** 03.wireguard-policy ---- +## OVERVIEW +Hybrid project: (1) WireGuard Policy Firewall -- shell script iptables/ipset engine driven by `wg0.conf` `#Access` comments. (2) WGRplane -- Go binary (port 10087) serving REST API + Vue 3 SPA for WireGuard control plane management. -## Component 1: WireGuard Policy Firewall +## STRUCTURE +``` +03.wireguard-policy/ +├── app/ # WGRplane: Go backend + Vue 3 frontend (see app/AGENTS.md) +│ ├── *.go # 13 Go files, flat package (no subdirs) +│ ├── frontend/ # Vue 3 SPA (see app/frontend/src/AGENTS.md) +│ └── docs/ # Swagger auto-generated (DO NOT EDIT) +├── wg-sync-policy.sh # Parses wg0.conf → policy.json (atomic write + flock) +├── wg-policy-engine.sh # Reads policy.json → iptables/ipset WG_POLICY chain +├── wg-sync-watch.sh # inotifywait daemon, debounces wg0.conf changes +├── wg-policy-lib.sh # Shared shell library (source only, never execute directly) +├── wg-policy-ctl # CLI wrapper for operator use +├── wg-policy-cleanup.sh # PostDown cleanup (run by WireGuard) +├── *.service / *.timer # systemd units for daemon + health check +├── install.sh # Unified installer (embeds all scripts, built by build.sh) +├── build.sh / build.bat # Rebuilds install.sh from source scripts +├── Dockerfile # Multi-stage: Go build + frontend build +└── README.md # Full user documentation +``` -## Architecture & Configuration Flow -- **Goal:** Dynamic iptables/ipset rules based on WireGuard configuration (`wg0.conf`). -- **Data Flow:** `wg0.conf` -> `wg-sync-policy.sh` -> `policy.json` -> `wg-policy-engine.sh` -> `iptables`/`ipset` -- **File Watcher:** `wg-sync-watch.sh` monitors `wg0.conf` via `inotifywait` and debounces changes to re-run the sync and engine. +## WHERE TO LOOK +| Task | Location | +|------|----------| +| Policy firewall logic | `wg-sync-policy.sh`, `wg-policy-engine.sh`, `wg-policy-lib.sh` | +| Firewall rule chain | `wg-policy-engine.sh` -- WG_POLICY iptables chain | +| Policy JSON schema | `wg-sync-policy.sh` output / `wg-policy-engine.sh` input | +| WGRplane API handlers | `app/handlers.go` | +| WGRplane DB models | `app/models.go` | +| Auth middleware | `app/auth.go` (CAUTION: see known issues) | +| nftables rules (forward mode) | `app/nftables.go` | +| Webhook delivery | `app/webhook.go` | +| Vue frontend | `app/frontend/src/` | +| i18n translations | `app/frontend/src/i18n/locales/` (en, id, zh) | +| Backend i18n | `app/active.{en,id,zh}.json` | +| Systemd service config | `wg-policy.service`, `wgrplane.service` | -## Critical Parsing Rules & Design Constraints -- **Target IPs Parsing (`#Access`):** The firewall script uses the custom `#Access` comment in `wg0.conf` to define egress/firewall whitelists for clients. -- **Why `#Access` is mandatory:** WireGuard's native `AllowedIPs` on a Server dictates *routing* towards the client. If we put target destinations in the Server's `AllowedIPs`, the Server would wrongly route traffic destined for those IPs *into* the client tunnel. Therefore, a custom `#Access` comment is the only correct way to define firewall whitelist destinations without breaking WireGuard's Cryptokey Routing. -- **Do not remove `#Access`:** Future agents MUST NOT attempt to refactor the script to parse targets from `AllowedIPs`. It is architecturally incorrect for this use case. +## CRITICAL DESIGN RULES -## Testing & Verifying -- `wg-policy-ctl status`: Check the overall health, including interface status, JSON validity, lock files, and iptables rules counts. -- `wg-policy-ctl validate`: Validates `policy.json` without applying. -- `wg-policy-ctl rules`: View the applied iptables rules in the active chain (`WG_POLICY`). -- `wg-policy-ctl reload`: Forces a re-sync from `wg0.conf` and re-applies iptables. +### Policy Firewall -- `#Access` MUST NOT be changed +- WireGuard `AllowedIPs` on the server side = Cryptokey Routing, not firewall whitelist +- Putting destination IPs in server's `AllowedIPs` breaks routing (WG tunnels those packets INTO client) +- `#Access` comment is the ONLY correct way to declare firewall destinations per-peer +- **NEVER refactor to parse targets from `AllowedIPs`** -- architecturally incorrect -## Script Constraints & Gotchas -- **Atomic Operations:** Always use atomic writes (`mv -f tmp target`) for `policy.json` to prevent the policy engine from reading partial files. -- **Locking:** `wg-sync-policy.sh` uses file-based locking (`flock`) to prevent race conditions during updates. -- **Rollback:** `wg-policy-engine.sh` creates a backup chain (`WG_POLICY_BAK`) and uses a trap on `ERR` to rollback if applying rules fails halfway. -- **Dependencies:** Requires `jq` and `inotify-tools`. +### Atomic Writes +- Always `mv -f tmp target` for `policy.json` -- never write directly +- `wg-sync-policy.sh` uses `flock` -- never bypass locking -## Development Commands -- Restart the watcher service: `systemctl restart wg-policy.service` -- Check service logs: `journalctl -u wg-policy.service -f` +### Rollback +- `wg-policy-engine.sh` creates `WG_POLICY_BAK` chain; traps `ERR` for rollback ---- +### No SaveConfig +- WireGuard `SaveConfig = true` strips ALL comments including `#Access` -- NEVER enable -## Component 2: WGRplane (Go App in `/app`) +## KNOWN ISSUES / GOTCHAS +- **AuthMiddleware NOT applied**: `auth.go` defines `AuthMiddleware` but it is NOT wired to any routes in `main.go`. All API endpoints currently unprotected (auth header still checked inside handlers via manual if-check, but middleware chain is absent). +- **TOTP secrets in-memory**: `totpSecrets` map in `auth.go` is not persisted; lost on restart. +- **WebSocket stats are MOCK**: `stats.go` broadcasts randomly generated numbers, not real WireGuard traffic. +- **Plugin system is stub**: `plugins.go` TelegramNotifier/SlackNotifier just print to stdout. +- **Binary + DB in app/**: `wgrplane` binary and `wgrplane.db` live in `app/` (non-standard, intentional). +- **Duplicate i18n**: `app/active.*.json` (backend i18n) and `app/frontend/src/i18n/locales/` (frontend i18n) are separate systems. -## Architecture Overview -WGRplane is a Go-native WireGuard control plane application with a Vue 3 frontend, providing a modern web dashboard for WireGuard management. +## ANTI-PATTERNS +- Never parse firewall targets from `AllowedIPs` -- use `#Access` only +- Never write `policy.json` without atomic mv + flock +- Never run `wg-policy-lib.sh` directly (source-only library) +- Never enable `SaveConfig = true` in wg0.conf +- Do NOT edit `app/docs/docs.go` -- auto-generated by swaggo +- Do NOT put business logic in `app/main.go` -- it's bootstrap only -- **Single Binary:** The Go binary (`app/main.go`) serves both the REST API (port 10087) and the built Vue frontend from `app/frontend/dist/`. -- **Hybrid Mode:** - - `forward` - Directly applies nftables rules on the local machine - - `standalone` - Acts as a control plane that triggers webhooks to remote WireGuard servers -- **2-Column Policy:** Each peer has `AllowAccess` (CIDR whitelist) and `AllowInternet` (boolean toggle) +## COMMANDS -## Key File Locations -- `app/main.go` - Bootstrap server, routing, init DB -- `app/handlers.go` - REST API route handlers -- `app/models.go` - GORM models (Server, Peer, Webhook, SMTP) -- `app/auth.go` - JWT, TOTP, API key auth middleware -- `app/nftables.go` - nftables rule management (mode forward) -- `app/webhook.go` - Webhook engine with retry/backoff -- `app/scheduler.go` - Cron jobs (expiry, data limit, reset) -- `app/stats.go` - WebSocket Hub for real-time stats -- `app/frontend/` - Vue 3 SPA (TypeScript, TailwindCSS 4) +### Policy Firewall +```bash +wg-policy-ctl status # Health: interface, JSON validity, rule counts +wg-policy-ctl validate # Validate policy.json without applying +wg-policy-ctl rules # Show active WG_POLICY iptables rules +wg-policy-ctl reload # Force re-sync from wg0.conf + re-apply +wg-policy-ctl policy # Show raw policy.json +wg-policy-ctl log # Show dropped packet logs (rate-limited) +systemctl restart wg-policy.service +journalctl -u wg-policy.service -f +``` -## Tech Stack -| Component | Technology | -|-----------|------------| -| **Backend** | Go, Gorilla Mux, GORM (SQLite via glebarez/sqlite) | -| **Frontend** | Vue 3, TypeScript, Vite, TailwindCSS 4, vue-i18n 9 | -| **Auth** | JWT (golang-jwt/v5), TOTP (pquerna/otp), API Key | -| **WebSockets** | gorilla/websocket | -| **Webhooks** | Go net/http with retry + exponential backoff | -| **Scheduling** | robfig/cron v3 | -| **QR Code** | skip2/go-qrcode | -| **Email** | jordan-wright/email (SMTP) | +### WGRplane (Go App) +```bash +cd app && go build -o ../wgrplane . # Build Go binary +cd app/frontend && npm install && npm run build # Build Vue frontend +./wgrplane # Run (serves :10087) +systemctl restart wgrplane.service +journalctl -u wgrplane.service -f +curl -H "wg-rplane-datadunia: test-api-key" http://localhost:10087/api/servers +``` -## Authentication Methods -WGRplane supports three authentication methods: +### Installer +```bash +./build.sh # Rebuild install.sh from source scripts (Linux) +./build.bat # Rebuild install.sh from source scripts (Windows) +sudo ./install.sh install # Full install (deps + scripts + systemd + service) +sudo ./install.sh uninstall # Remove all +``` -| Method | Header | Notes | -|--------|--------|-------| -| API Key | `wg-rplane-datadunia: ` | Set via env var `WG_API_KEY`. Default: `test-api-key` | -| JWT | `Authorization: Bearer ` | Expires in 15 minutes. Secret via env var `JWT_SECRET` | -| TOTP | `X-TOTP: ` | Required if user enables TOTP | - -## Development Commands -- Build Go binary: `cd app && go build -o ../wgrplane .` -- Build frontend: `cd app/frontend && npm install && npm run build` -- Run locally: `./wgrplane` (serves on `http://localhost:10087`) -- Restart service: `systemctl restart wgrplane.service` -- Check service logs: `journalctl -u wgrplane.service -f` - -## Testing & Verifying -- Test auth (no header): `curl http://localhost:10087/api/servers` (expect 401) -- Test auth (correct): `curl -H "wg-rplane-datadunia: test-api-key" http://localhost:10087/api/servers` -- View Swagger docs: Visit `http://localhost:10087/swagger/` -- WebSocket test: Connect to `ws://localhost:10087/ws/stats` - -## References -- For detailed WGRplane documentation: See `app/README.md` -- For WGRplane-specific AI agent guidance: See `app/AGENTS.md` - ---- - -## Notes for AI Agents -- The `.sisyphus/` directory contains planning artifacts and may be cleaned up after plan completion. -- When working on Policy Firewall scripts, always preserve `#Access` comment parsing logic. -- When working on WGRplane, remember it's a single binary serving both API and frontend on port 10087. +## DEPENDENCIES +- Shell: `jq`, `inotify-tools` (required); `ipset` (optional, O(1) lookup) +- Go: 1.25.1, SQLite (glebarez/sqlite), Gorilla Mux, GORM, JWT, TOTP, WebSocket +- Frontend: Vue 3, TypeScript, Vite, TailwindCSS 4, vue-i18n 9 +- Auth headers: `wg-rplane-datadunia: ` (API key) | `Authorization: Bearer ` | `X-TOTP: ` +- Env vars: `WG_API_KEY` (default: test-api-key), `JWT_SECRET`, `WG_RPLANE_MODE` (forward|standalone), `APP_FRONTEND_DIR`