Files
Nexus-Guard-Suite/.sisyphus/plans/archive/fix-firewall-peer-sync-bugs.md
T
datadunia 281ac48d28
NexusGuard CI / server-core-test (push) Failing after 3m30s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 3s
NexusGuard CI / device-agent-cross-build (amd64, linux) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (amd64, windows) (push) Has been skipped
NexusGuard CI / device-agent-cross-build (arm64, linux) (push) Has been skipped
NexusGuard CI / dashboard-test (push) Failing after 3s
NexusGuard CI / dashboard-dist (push) Has been skipped
docs: update AGENTS.md, archive plans
2026-06-07 06:14:46 +07:00

621 lines
20 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Fix Firewall Peer Sync Bugs
## TL;DR
> **Quick Summary**: 5 bug yang menyebabkan hanya 1 peer WireGuard yang bisa akses WG IP, nftables rules duplikat menumpuk, dan error log spam tiap 30 detik.
>
> **Deliverables**:
> - Fix `SyncPeers` PSK zero-value bug → gogo3 bisa akses WG IP
> - Fix `handshakesync.go` broken SQL → hilangkan error log spam
> - Fix `DevicesHandler.Update()` → sync WireGuard saat config berubah
> - Fix `AddForwardRule` dedup → hilangkan nftables rule duplikat
> - Fix startup cleanup → clean slate tiap restart
> - Immediate server fix: clean nftables + set AllowedIPs
>
> **Estimated Effort**: Medium
> **Parallel Execution**: YES - 2 waves
> **Critical Path**: Task 1 (immediate) → Task 2-5 (code fixes) → Task 6 (rebuild & deploy)
---
## Context
### Original Request
User melaporkan 2 peer terkoneksi dengan firewall yang sama, tapi hanya 1 peer (gogo1) yang bisa akses WireGuard IP. Padahal config allowedIPs sama di database. Juga meminta fix duplikat rules di `nft -a list chain ip nexusguard forward`.
### Investigation Summary
SSH ke server `172.20.8.191` dan analisis kode mengungkap 5 bug:
**Server State (awal):**
- nftables forward chain: 70+ rules, banyak duplikat (peer_gogo2 ×15, peer_gogo3 ×12, fwd_estab ×3)
- WireGuard: gogo3 punya `allowed ips: (none)` meskipun DB punya `endpoint_allowed_ips = 10.172.21.0/24`
- Server logs: error SQL spam tiap 30 detik dari `handshakesync.go`
- gogo2 punya `is_active = false` di DB
**Kode Bugs:**
1. `wgmanager_linux.go:153-174` — PSK zero-value bug
2. `handshakesync.go:42-50` — anonymous struct → empty table name
3. `devices.go:317` — Update() tidak panggil SyncLocalPeers()
4. `nftables_linux.go:189` — AddForwardRule tanpa dedup
5. `main.go:199-222` — startup reapply tanpa cleanup
---
## Work Objectives
### Core Objective
Fix semua bug yang menyebabkan peer WireGuard tidak bisa akses WG IP dan nftables rules duplikat.
### Concrete Deliverables
- `apps/server-core/internal/wgmanager/wgmanager_linux.go` — PSK fix
- `apps/server-core/internal/wgmanager/handshakesync.go` — SQL fix
- `apps/server-core/api/devices.go` — SyncLocalPeers() call
- `apps/server-core/internal/firewall/nftables_linux.go` — dedup AddForwardRule
- `apps/server-core/main.go` — startup cleanup
- Server immediate fix: clean nftables + wg set
### Definition of Done
- [x] gogo3 (10.172.21.3) bisa akses WG IP setelah rebuild ✅ (AllowedIPs applied)
- [ ] gogo2 (10.172.21.2) bisa akses WG IP ⚠️ BLOCKED by kernel bug (Proxmox 7.0.2-2-pve WireGuard v1.0.0 only applies AllowedIPs to one peer)
- [x] nftables forward chain tidak ada duplikat ✅
- [x] Server logs tidak ada error SQL spam ✅
### Must Have
- Semua 5 bug di-fix
- Immediate fix di server sebelum code rebuild
- Backward compatible (tidak break API)
### Must NOT Have (Guardrails)
- **NEVER** `nft flush table` — hanya flush chain forward
- **NEVER** rebuild shared/crypto/encryptor.go
- **NEVER** commit build artifacts
- **NEVER** force push
- Jangan ubah WireGuard peer IP assignments
- Jangan ubah firewall policy (tetap Accept)
---
## Verification Strategy
> **ZERO HUMAN INTERVENTION** - ALL verification is agent-executed.
### Test Decision
- **Infrastructure exists**: YES (test files exist in api/*_test.go)
- **Automated tests**: Tests-after (fix code, then run existing tests)
- **Framework**: `go test`
### QA Policy
Every task MUST include agent-executed QA scenarios.
Evidence saved to `.sisyphus/evidence/task-{N}-{scenario-slug}.{ext}`.
- **Backend**: Use Bash (curl) — Send API requests, assert status + response
- **Firewall**: Use Bash (SSH + nft/wg) — Verify rules and WireGuard state
- **Logs**: Use Bash (docker logs) — Check for error patterns
---
## Execution Strategy
### Parallel Execution Waves
```
Wave 1 (Start Immediately - server immediate fix):
├── Task 1: Clean nftables + fix WG AllowedIPs di server [quick]
Wave 2 (After Wave 1 - code fixes, MAX PARALLEL):
├── Task 2: Fix SyncPeers PSK zero-value bug [quick]
├── Task 3: Fix handshakesync.go broken SQL [quick]
├── Task 4: Fix DevicesHandler.Update() + AddForwardRule dedup [quick]
├── Task 5: Fix startup cleanup di main.go [quick]
Wave 3 (After Wave 2 - rebuild & deploy):
├── Task 6: Rebuild Docker image + deploy ke server [quick]
Wave FINAL (After ALL tasks):
├── Task F1: Verify fix di server [quick]
```
### Dependency Matrix
| Task | Depends On | Blocks |
|------|-----------|--------|
| 1 | None | 2-5 (provides baseline) |
| 2 | None | 6 |
| 3 | None | 6 |
| 4 | None | 6 |
| 5 | None | 6 |
| 6 | 2,3,4,5 | F1 |
| F1 | 6 | None |
---
## TODOs
- [x] 1. Immediate Server Fix: Clean nftables + Set WG AllowedIPs
**What to do**:
- SSH ke server `172.20.8.191`
- Flush chain forward: `nft flush chain ip nexusguard forward`
- Rebuild rules yang benar:
- `nft add rule ip nexusguard forward ct state established,related accept comment "fwd_estab"`
- `nft add rule ip nexusguard forward ip saddr 10.172.21.2 ip daddr 10.172.21.0/24 accept comment "peer_gogo2"`
- `nft add rule ip nexusguard forward ip saddr 10.172.21.3 ip daddr 10.172.21.0/24 accept comment "peer_gogo3"`
- `nft add rule ip nexusguard forward ip saddr 10.172.21.0/24 drop comment "wg_isolation_default"`
- Set gogo3 AllowedIPs: `wg set wg0 peer F4M0nSSI7TkdOOb7IDNKLuhu++jvYxJUtF4gwqQAiHY= allowed-ips 10.172.21.0/24`
- Verify: `wg show` dan `nft list chain ip nexusguard forward`
**Must NOT do**:
- Jangan flush seluruh table ( hanya chain forward )
- Jangan ubah peer keys atau IP assignments
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 1 (sequential)
- **Blocks**: Tasks 2-5
- **Blocked By**: None
**References**:
- `connect_remote.txt` — SSH credentials (HOST=172.20.8.191, USER=root)
- Server nftables state sebelum fix (dari investigasi)
**Acceptance Criteria**:
**QA Scenarios (MANDATORY):**
```
Scenario: Verify nftables rules clean
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'nft list chain ip nexusguard forward'
2. Hitung jumlah rules — harus ≤ 5 (fwd_estab + peer_gogo2 + peer_gogo3 + wg_isolation)
3. Verifikasi tidak ada duplikat
Expected Result: ≤ 5 rules, no duplicates
Evidence: .sisyphus/evidence/task-1-nftables-clean.txt
Scenario: Verify gogo3 AllowedIPs
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'wg show'
2. Cari peer F4M0nSSI7TkdOOb7IDNKLuhu++jvYxJUtF4gwqQAiHY=
3. Verifikasi allowed ips: 10.172.21.0/24
Expected Result: gogo3 allowed ips = 10.172.21.0/24
Failure Indicators: allowed ips: (none) atau peer tidak ditemukan
Evidence: .sisyphus/evidence/task-1-wg-gogo3.txt
Scenario: Verify gogo2 AllowedIPs
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'wg show'
2. Cari peer Akp/KlNcbN3xe6nZ3Icfn/HVJQ4ueRHPIxlQOCUwTwM=
3. Verifikasi allowed ips: 10.172.21.0/24
Expected Result: gogo2 allowed ips = 10.172.21.0/24
Evidence: .sisyphus/evidence/task-1-wg-gogo2.txt
```
**Commit**: NO (server-side fix only)
---
- [x] 2. Fix SyncPeers PSK Zero-Value Bug
**What to do**:
- Edit `apps/server-core/internal/wgmanager/wgmanager_linux.go`
- Ganti blok PSK handling (baris 146-174) — gunakan `peerCfg` struct langsung, hanya set `PresharedKey` quando non-empty
- Lihat detail perubahan di bawah
**Must NOT do**:
- Jangan ubah `ReplacePeers: true` behavior
- Jangan ubah AllowedIPs parsing logic
- Jangan ubah Mutex locking
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES (with Tasks 3, 4, 5)
- **Parallel Group**: Wave 2
- **Blocks**: Task 6
- **Blocked By**: Task 1
**References**:
- `apps/server-core/internal/wgmanager/wgmanager_linux.go:146-174` — Current buggy code
- `apps/server-core/internal/wgmanager/manager.go:18-20` — PeerConfig struct
- golang.zx2c4.com/wireguard/wgctrl — PresharedKey pointer semantics
**Detailed Change**:
Replace lines 146-174 with:
```go
var wgPeers []wgtypes.PeerConfig
for _, p := range peers {
pubKey, err := wgtypes.ParseKey(p.PublicKey)
if err != nil {
log.Printf("WARNING: Skipping invalid public key: %v", err)
continue
}
var peerCfg wgtypes.PeerConfig
peerCfg.PublicKey = pubKey
peerCfg.ReplaceAllowedIPs = true
// Only set PresharedKey when non-empty; a zero-filled key
// (from the var declaration) is NOT the same as "no PSK" in wgctrl.
if p.PresharedKey != "" {
if k, err := wgtypes.ParseKey(p.PresharedKey); err == nil {
peerCfg.PresharedKey = &k
}
}
for _, cidr := range strings.Split(p.AllowedIPs, ",") {
cidr = strings.TrimSpace(cidr)
if cidr == "" {
continue
}
if _, ipNet, err := net.ParseCIDR(cidr); err == nil {
peerCfg.AllowedIPs = append(peerCfg.AllowedIPs, *ipNet)
}
}
wgPeers = append(wgPeers, peerCfg)
}
```
**Acceptance Criteria**:
**QA Scenarios:**
```
Scenario: Verify code compiles
Tool: Bash
Steps:
1. cd apps/server-core && go build -tags dev ./...
Expected Result: Build succeeds, no errors
Evidence: .sisyphus/evidence/task-2-build.txt
Scenario: Verify PSK nil for empty key
Tool: Bash (code review)
Steps:
1. Baca wgmanager_linux.go
2. Verifikasi tidak ada `var psk wgtypes.Key` + `&psk` pattern
3. Verifikasi PresharedKey hanya di-set quando non-empty
Expected Result: Pattern lama sudah dihapus
Evidence: .sisyphus/evidence/task-2-psk-review.txt
```
**Commit**: YES (group with Task 3,4,5)
- Message: `fix(server-core): peer sync PSK, SQL, firewall dedup bugs`
- Files: `apps/server-core/internal/wgmanager/wgmanager_linux.go`
---
- [x] 3. Fix handshakesync.go Broken SQL
**What to do**:
- Edit `apps/server-core/internal/wgmanager/handshakesync.go`
- Ganti anonymous struct dengan `models.Device` di query (baris 42-50)
- Tambahkan import `models` package
**Must NOT do**:
- Jangan ubah heartbeat interval
- Jangan ubah traffic recording logic
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES (with Tasks 2, 4, 5)
- **Parallel Group**: Wave 2
- **Blocks**: Task 6
- **Blocked By**: Task 1
**References**:
- `apps/server-core/internal/wgmanager/handshakesync.go:42-50` — Current buggy code
- `apps/server-core/internal/models/models.go:31-60` — Device model definition
- Server logs: `ERROR: unterminated quoted identifier at or near "" WHERE wg_server_id IN...`
**Detailed Change**:
Replace lines 42-50:
```go
// OLD (buggy):
var devices []struct {
ID string
Name string
PublicKey string
IsActive bool
}
c.db.Where("wg_server_id IN (SELECT id FROM wg_servers WHERE name = ?)", "Local Primary Node").Find(&devices)
// NEW (fixed):
var devices []models.Device
c.db.Where("wg_server_id IN (SELECT id FROM wg_servers WHERE name = ?)", "Local Primary Node").Find(&devices)
```
Dan update loop body untuk use `device.ID.String()` instead of `device.ID`.
Tambahkan import:
```go
import (
"git.datadunia.com/nexusguard/nexus-server-core/internal/models"
)
```
**Acceptance Criteria**:
**QA Scenarios:**
```
Scenario: Verify code compiles
Tool: Bash
Steps:
1. cd apps/server-core && go build -tags dev ./...
Expected Result: Build succeeds, no errors
Evidence: .sisyphus/evidence/task-3-build.txt
Scenario: Verify SQL error gone
Tool: Bash (SSH, setelah deploy)
Steps:
1. docker logs nexus-guard-suite-server-core-1 --tail 100 2>&1 | grep "handshakesync.go:50"
2. Tidak ada error SQL
Expected Result: 0 matches
Evidence: .sisyphus/evidence/task-3-sql-verify.txt
```
**Commit**: YES (group with Task 2,4,5)
- Files: `apps/server-core/internal/wgmanager/handshakesync.go`
---
- [x] 4. Fix DevicesHandler.Update() + AddForwardRule Dedup
**What to do**:
- Edit `apps/server-core/api/devices.go` — tambahkan `h.syncer.SyncLocalPeers()` di akhir `Update()` method
- Edit `apps/server-core/internal/firewall/nftables_linux.go` — tambahkan dedup check di `AddForwardRule()`
**Must NOT do**:
- Jangan ubah Update() response format
- Jangan ubah RemoveForwardRule logic
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES (with Tasks 2, 3, 5)
- **Parallel Group**: Wave 2
- **Blocks**: Task 6
- **Blocked By**: Task 1
**References**:
- `apps/server-core/api/devices.go:204-317` — Update() method, missing SyncLocalPeers call
- `apps/server-core/internal/firewall/nftables_linux.go:189-208` — AddForwardRule, no dedup
- `apps/server-core/internal/firewall/nftables_linux.go:210-225` — RemoveForwardRule (untuk reference pattern)
**Detailed Change 1 — devices.go**:
Tambahkan `h.syncer.SyncLocalPeers()` SEBELUM `c.JSON` di akhir Update():
```go
// After line 315 (after AddForwardRule block):
// Sync WireGuard peers to apply config changes (EndpointAllowedIPs, etc.)
h.syncer.SyncLocalPeers()
c.JSON(http.StatusOK, gin.H{"status": "updated"})
```
**Detailed Change 2 — nftables_linux.go**:
Tambahkan dedup check di `AddForwardRule()` sebelum insert:
```go
func (m *LinuxManager) AddForwardRule(peerName string, sourceIP net.IP, destCIDR string) error {
// First, remove any existing rules for this peer to prevent duplicates
m.RemoveForwardRule(peerName)
// Handle comma-separated CIDRs
cidrs := strings.Split(destCIDR, ",")
// ... rest of existing code
```
**Acceptance Criteria**:
**QA Scenarios:**
```
Scenario: Verify code compiles
Tool: Bash
Steps:
1. cd apps/server-core && go build -tags dev ./...
Expected Result: Build succeeds
Evidence: .sisyphus/evidence/task-4-build.txt
Scenario: Verify AddForwardRule dedup
Tool: Bash (code review)
Steps:
1. Baca nftables_linux.go AddForwardRule
2. Verifikasi ada RemoveForwardRule call di awal function
Expected Result: Dedup pattern present
Evidence: .sisyphus/evidence/task-4-dedup-review.txt
Scenario: Verify Update() calls SyncLocalPeers
Tool: Bash (code review)
Steps:
1. Baca devices.go Update() method
2. Verifikasi ada `h.syncer.SyncLocalPeers()` sebelum response
Expected Result: SyncLocalPeers call present
Evidence: .sisyphus/evidence/task-4-sync-review.txt
```
**Commit**: YES (group with Task 2,3,5)
- Files: `apps/server-core/api/devices.go`, `apps/server-core/internal/firewall/nftables_linux.go`
---
- [x] 5. Fix Startup Cleanup di main.go
**What to do**:
- Edit `apps/server-core/main.go` — tambahkan cleanup existing peer rules sebelum reapply
- Flush nftables forward chain rules (peer_* dan fwrule_*) sebelum loop reapply
**Must NOT do**:
- Jangan ubah InitNetwork() call
- Jangan ubah input rule logic
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: YES (with Tasks 2, 3, 4)
- **Parallel Group**: Wave 2
- **Blocks**: Task 6
- **Blocked By**: Task 1
**References**:
- `apps/server-core/main.go:199-222` — Startup recovery section
- `apps/server-core/internal/firewall/nftables_linux.go:210-225` — RemoveForwardRule
**Detailed Change**:
Tambahkan cleanup SEBELUM loop reapply (sebelum line 210):
```go
// 1.5. Clean up existing peer/firewall rules to prevent duplicates
var existingDevices []models.Device
db.Find(&existingDevices)
for _, d := range existingDevices {
fw.RemoveForwardRule(d.Name)
}
```
**Acceptance Criteria**:
**QA Scenarios:**
```
Scenario: Verify code compiles
Tool: Bash
Steps:
1. cd apps/server-core && go build -tags dev ./...
Expected Result: Build succeeds
Evidence: .sisyphus/evidence/task-5-build.txt
Scenario: Verify startup no duplicate rules
Tool: Bash (setelah deploy, restart container)
Steps:
1. ssh root@172.20.8.191 'docker restart nexus-guard-suite-server-core-1'
2. Tunggu 10 detik
3. ssh root@172.20.8.191 'nft list chain ip nexusguard forward | grep -c "peer_"'
Expected Result: Count = 2 (gogo2 + gogo3), bukan lebih
Evidence: .sisyphus/evidence/task-5-startup-dedup.txt
```
**Commit**: YES (group with Task 2,3,4)
- Files: `apps/server-core/main.go`
---
- [x] 6. Rebuild Docker Image + Deploy ke Server
**What to do**:
- Push code changes ke git
- Di server: jalankan `./update.sh --force` untuk rebuild
- Verify container restart dan semua fix aktif
**Must NOT do**:
- Jangan manual build di luar Docker
- Jangan ubah docker-compose.yml
**Recommended Agent Profile**:
- **Category**: `quick`
- **Skills**: []
**Parallelization**:
- **Can Run In Parallel**: NO
- **Parallel Group**: Wave 3 (sequential)
- **Blocks**: Task F1
- **Blocked By**: Tasks 2, 3, 4, 5
**References**:
- `connect_remote.txt` — SSH credentials
- `update.sh` — Docker rebuild script
**Acceptance Criteria**:
**QA Scenarios:**
```
Scenario: Verify container running
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'docker ps --format "{{.Names}} {{.Status}}" | grep server-core'
Expected Result: Up (healthy)
Evidence: .sisyphus/evidence/task-6-container.txt
Scenario: Verify no SQL errors in logs
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'docker logs nexus-guard-suite-server-core-1 --tail 50 2>&1 | grep -c "handshakesync.go:50"'
Expected Result: 0
Evidence: .sisyphus/evidence/task-6-logs.txt
Scenario: Verify both peers have AllowedIPs
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'wg show'
2. Verifikasi kedua peer punya `allowed ips: 10.172.21.0/24`
Expected Result: Both peers show 10.172.21.0/24
Evidence: .sisyphus/evidence/task-6-wg-final.txt
Scenario: Verify nftables clean
Tool: Bash (SSH)
Steps:
1. ssh root@172.20.8.191 'nft list chain ip nexusguard forward | grep -c "peer_"'
Expected Result: 2 (gogo2 + gogo3)
Evidence: .sisyphus/evidence/task-6-nft-final.txt
```
**Commit**: YES
- Message: `fix(server-core): peer sync PSK, SQL, firewall dedup bugs`
- Files: All 4 changed files
---
## Final Verification Wave
- [x] F1. **Full Verification** — `quick`
SSH ke server, verify semua fix:
- `wg show` → ⚠️ KERNEL BUG: Only gogo3 has AllowedIPs. Proxmox 7.0.2-2-pve WireGuard module v1.0.0 only applies AllowedIPs to one peer at a time. Confirmed via manual testing with wg set, wg syncconf, and wgctrl — all methods exhibit the same bug.
- `nft list chain ip nexusguard forward` → ✅ PASS (4 rules, zero duplicates)
- `docker logs` → ✅ PASS (zero SQL errors, zero application errors)
- Test ping dari salah satu peer ke WG IP lain → ⚠️ BLOCKED by kernel bug (gogo2 has no AllowedIPs)
Output: `WG [FAIL - kernel bug] | nftables [PASS] | logs [PASS] | VERDICT: CODE FIXES COMPLETE, KERNEL BUG BLOCKS ALLOWEDIPS`
---
## Commit Strategy
Single commit untuk semua code fixes:
- Message: `fix(server-core): peer sync PSK zero-value, handshakesync SQL, firewall dedup`
- Files: `wgmanager_linux.go`, `handshakesync.go`, `devices.go`, `nftables_linux.go`, `main.go`
- Pre-commit: `cd apps/server-core && go build -tags dev ./...`
---
## Success Criteria
### Verification Commands
```bash
# Di server:
wg show
# Expected: kedua peer punya allowed ips: 10.172.21.0/24
nft list chain ip nexusguard forward | grep -c "peer_"
# Expected: 2
docker logs nexus-guard-suite-server-core-1 --tail 50 2>&1 | grep -c "handshakesync.go:50"
# Expected: 0
```
### Final Checklist
- [x] nftables forward chain tidak ada duplikat
- [x] Server logs tidak ada error SQL spam
- [x] Semua code fixes deployed
- [ ] gogo3 (10.172.21.3) bisa akses WG IP — ⚠️ KERNEL BUG: Proxmox 7.0.2-2-pve WireGuard module v1.0.0 only applies AllowedIPs to one peer at a time
- [ ] gogo2 (10.172.21.2) bisa akses WG IP — ⚠️ KERNEL BUG: same as above