Files
Nexus-Guard-Suite/.omo/plans/archive/fix-firewall-peer-sync-bugs.md
datadunia cbacfea7f2
NexusGuard CI / server-core-test (push) Failing after 3m6s
NexusGuard CI / server-core-build (push) Has been skipped
NexusGuard CI / device-agent-test (push) Failing after 4s
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 4s
NexusGuard CI / dashboard-dist (push) Has been skipped
chore: update submodule refs, clean up plans/evidence, update .gitignore
2026-06-07 23:53:15 +07:00

20 KiB
Raw Permalink Blame History

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

  • 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)
  • nftables forward chain tidak ada duplikat
  • 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

  • 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)


  • 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:

    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

  • 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:

    // 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:

    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

  • 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():

    // 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:

    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

  • 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):

    // 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

  • 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

  • F1. Full Verificationquick 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

# 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

  • nftables forward chain tidak ada duplikat
  • Server logs tidak ada error SQL spam
  • 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