# Namespace + VRF Implementation Guide ## Overview This document provides a step-by-step implementation guide for the Namespace + VRF architecture in NexusGuard. ## Prerequisites ### System Requirements ```bash # Check kernel version (need 4.3+ for VRF) uname -r # Check VRF support modprobe vrf # Check namespace support ip netns list # Install required packages apt install -y iproute2 nftables wireguard-tools ``` ### Development Environment ```bash # Go 1.25+ go version # PostgreSQL 15+ psql --version # Node.js 24+ (for dashboard) node --version ``` ## Phase 1: Database Schema ### Step 1.1: Create Migration File ```bash cd apps/server-core touch internal/models/migration_vrf.go ``` ### Step 1.2: Define Models ```go // internal/models/namespace.go package models import ( "time" "github.com/google/uuid" "gorm.io/gorm" ) type Namespace struct { ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` Name string `json:"name" gorm:"uniqueIndex;not null;size:64"` OwnerID uuid.UUID `json:"owner_id" gorm:"type:uuid;not null;index"` Owner User `json:"owner" gorm:"foreignKey:OwnerID"` Users []NamespaceUser `json:"users" gorm:"foreignKey:NamespaceID"` VRFs []VRF `json:"vrfs" gorm:"foreignKey:NamespaceID"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (n *Namespace) BeforeCreate(tx *gorm.DB) (err error) { if n.ID == uuid.Nil { n.ID = uuid.New() } return } type NamespaceUser struct { ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` NamespaceID uuid.UUID `json:"namespace_id" gorm:"type:uuid;not null;index"` Namespace Namespace `json:"namespace" gorm:"foreignKey:NamespaceID"` UserID uuid.UUID `json:"user_id" gorm:"type:uuid;not null;index"` User User `json:"user" gorm:"foreignKey:UserID"` Role string `json:"role" gorm:"not null;size:16;default:'viewer'"` // admin, operator, viewer CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (n *NamespaceUser) BeforeCreate(tx *gorm.DB) (err error) { if n.ID == uuid.Nil { n.ID = uuid.New() } return } type VRF struct { ID uuid.UUID `json:"id" gorm:"type:uuid;primaryKey"` NamespaceID uuid.UUID `json:"namespace_id" gorm:"type:uuid;not null;index"` Namespace Namespace `json:"namespace" gorm:"foreignKey:NamespaceID"` Name string `json:"name" gorm:"not null;size:64"` TableID int `json:"table_id" gorm:"not null"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } func (v *VRF) BeforeCreate(tx *gorm.DB) (err error) { if v.ID == uuid.Nil { v.ID = uuid.New() } return } ``` ### Step 1.3: Update WgServer Model ```go // internal/models/models.go type WgServer struct { // ... existing fields // VRF fields NamespaceID *uuid.UUID `json:"namespace_id" gorm:"type:uuid;index"` Namespace *Namespace `json:"namespace" gorm:"foreignKey:NamespaceID"` VRFID *uuid.UUID `json:"vrf_id" gorm:"type:uuid;index"` VRF *VRF `json:"vrf" gorm:"foreignKey:VRFID"` } ``` ### Step 1.4: Create Migration ```go // internal/models/migration_vrf.go package models import "gorm.io/gorm" func MigrateVRF(db *gorm.DB) error { // Create namespaces table if err := db.Exec(` CREATE TABLE IF NOT EXISTS namespaces ( id UUID PRIMARY KEY, name VARCHAR(64) UNIQUE NOT NULL, owner_id UUID NOT NULL REFERENCES users(id), created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW() ) `).Error; err != nil { return err } // Create namespace_users table if err := db.Exec(` CREATE TABLE IF NOT EXISTS namespace_users ( id UUID PRIMARY KEY, namespace_id UUID NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE, user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, role VARCHAR(16) NOT NULL DEFAULT 'viewer', created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(namespace_id, user_id) ) `).Error; err != nil { return err } // Create vrfs table if err := db.Exec(` CREATE TABLE IF NOT EXISTS vrfs ( id UUID PRIMARY KEY, namespace_id UUID NOT NULL REFERENCES namespaces(id) ON DELETE CASCADE, name VARCHAR(64) NOT NULL, table_id INTEGER NOT NULL, created_at TIMESTAMP DEFAULT NOW(), updated_at TIMESTAMP DEFAULT NOW(), UNIQUE(namespace_id, name) ) `).Error; err != nil { return err } // Add VRF fields to wg_servers if err := db.Exec(` ALTER TABLE wg_servers ADD COLUMN IF NOT EXISTS namespace_id UUID REFERENCES namespaces(id), ADD COLUMN IF NOT EXISTS vrf_id UUID REFERENCES vrfs(id) `).Error; err != nil { return err } return nil } ``` ### Step 1.5: Run Migration ```bash cd apps/server-core go run -tags dev . -migrate ``` ## Phase 2: Namespace Manager ### Step 2.1: Create Namespace Manager ```go // internal/namespace/manager.go package namespace import ( "fmt" "os/exec" "github.com/google/uuid" "git.datadunia.com/nexusguard/nexus-server-core/internal/models" "gorm.io/gorm" ) type Manager struct { db *gorm.DB } func NewManager(db *gorm.DB) *Manager { return &Manager{db: db} } func (m *Manager) Create(name string, ownerID uuid.UUID) (*models.Namespace, error) { // Create namespace in database ns := &models.Namespace{ Name: name, OwnerID: ownerID, } if err := m.db.Create(ns).Error; err != nil { return nil, err } // Create Linux network namespace cmd := exec.Command("ip", "netns", "add", "ns_"+name) if err := cmd.Run(); err != nil { return nil, fmt.Errorf("failed to create network namespace: %w", err) } // Add owner as admin user nsUser := &models.NamespaceUser{ NamespaceID: ns.ID, UserID: ownerID, Role: "admin", } if err := m.db.Create(nsUser).Error; err != nil { return nil, err } return ns, nil } func (m *Manager) Delete(name string) error { // Delete from database if err := m.db.Where("name = ?", name).Delete(&models.Namespace{}).Error; err != nil { return err } // Delete Linux network namespace cmd := exec.Command("ip", "netns", "del", "ns_"+name) if err := cmd.Run(); err != nil { return fmt.Errorf("failed to delete network namespace: %w", err) } return nil } func (m *Manager) Get(name string) (*models.Namespace, error) { var ns models.Namespace if err := m.db.Where("name = ?", name).First(&ns).Error; err != nil { return nil, err } return &ns, nil } func (m *Manager) List() ([]models.Namespace, error) { var namespaces []models.Namespace if err := m.db.Find(&namespaces).Error; err != nil { return nil, err } return namespaces, nil } func (m *Manager) AddUser(namespaceID, userID uuid.UUID, role string) error { nsUser := &models.NamespaceUser{ NamespaceID: namespaceID, UserID: userID, Role: role, } return m.db.Create(nsUser).Error } func (m *Manager) RemoveUser(namespaceID, userID uuid.UUID) error { return m.db.Where("namespace_id = ? AND user_id = ?", namespaceID, userID). Delete(&models.NamespaceUser{}).Error } func (m *Manager) UpdateUserRole(namespaceID, userID uuid.UUID, role string) error { return m.db.Model(&models.NamespaceUser{}). Where("namespace_id = ? AND user_id = ?", namespaceID, userID). Update("role", role).Error } ``` ### Step 2.2: Create Namespace API Handler ```go // api/namespaces.go package api import ( "net/http" "github.com/gin-gonic/gin" "github.com/google/uuid" "git.datadunia.com/nexusguard/nexus-server-core/internal/namespace" ) type NamespacesHandler struct { mgr *namespace.Manager } func NewNamespacesHandler(mgr *namespace.Manager) *NamespacesHandler { return &NamespacesHandler{mgr: mgr} } type CreateNamespaceRequest struct { Name string `json:"name" binding:"required"` } func (h *NamespacesHandler) Create(c *gin.Context) { var req CreateNamespaceRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ownerID, _ := c.Get("userID") ns, err := h.mgr.Create(req.Name, ownerID.(uuid.UUID)) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, ns) } func (h *NamespacesHandler) List(c *gin.Context) { namespaces, err := h.mgr.List() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, namespaces) } func (h *NamespacesHandler) Get(c *gin.Context) { name := c.Param("name") ns, err := h.mgr.Get(name) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"}) return } c.JSON(http.StatusOK, ns) } func (h *NamespacesHandler) Delete(c *gin.Context) { name := c.Param("name") if err := h.mgr.Delete(name); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "deleted"}) } type AddUserRequest struct { UserID string `json:"user_id" binding:"required"` Role string `json:"role" binding:"required"` } func (h *NamespacesHandler) AddUser(c *gin.Context) { name := c.Param("name") var req AddUserRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } ns, err := h.mgr.Get(name) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"}) return } userID, err := uuid.Parse(req.UserID) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user_id"}) return } if err := h.mgr.AddUser(ns.ID, userID, req.Role); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "added"}) } func (h *NamespacesHandler) RemoveUser(c *gin.Context) { name := c.Param("name") userIDStr := c.Param("user_id") ns, err := h.mgr.Get(name) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "namespace not found"}) return } userID, err := uuid.Parse(userIDStr) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "invalid user_id"}) return } if err := h.mgr.RemoveUser(ns.ID, userID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "removed"}) } ``` ### Step 2.3: Register API Routes ```go // main.go func main() { // ... existing code ... // Create namespace manager nsMgr := namespace.NewManager(db) nsHandler := api.NewNamespacesHandler(nsMgr) // Register namespace API routes v1 := router.Group("/api/v1") { // Namespace management (admin only) v1.POST("/namespaces", nsHandler.Create) v1.GET("/namespaces", nsHandler.List) v1.GET("/namespaces/:name", nsHandler.Get) v1.DELETE("/namespaces/:name", nsHandler.Delete) // Namespace user management v1.POST("/namespaces/:name/users", nsHandler.AddUser) v1.DELETE("/namespaces/:name/users/:user_id", nsHandler.RemoveUser) } } ``` ## Phase 3: VRF Manager ### Step 3.1: Create VRF Manager ```go // internal/vrf/manager.go package vrf import ( "fmt" "os/exec" "github.com/google/uuid" "git.datadunia.com/nexusguard/nexus-server-core/internal/models" "gorm.io/gorm" ) type Manager struct { db *gorm.DB } func NewManager(db *gorm.DB) *Manager { return &Manager{db: db} } func (m *Manager) Create(namespaceID uuid.UUID, name string) (*models.VRF, error) { // Get next available table ID var maxTableID int m.db.Model(&models.VRF{}). Where("namespace_id = ?", namespaceID). Select("COALESCE(MAX(table_id), 99)"). Scan(&maxTableID) tableID := maxTableID + 1 // Create VRF in database vrf := &models.VRF{ NamespaceID: namespaceID, Name: name, TableID: tableID, } if err := m.db.Create(vrf).Error; err != nil { return nil, err } // Get namespace name for Linux command var ns models.Namespace if err := m.db.First(&ns, namespaceID).Error; err != nil { return nil, err } // Create VRF in Linux namespace vrfName := fmt.Sprintf("vrf:%s", name) cmd := exec.Command("ip", "netns", "exec", "ns_"+ns.Name, "ip", "link", "add", vrfName, "type", "vrf", "table", fmt.Sprint(tableID)) if err := cmd.Run(); err != nil { return nil, fmt.Errorf("failed to create VRF: %w", err) } // Up VRF cmd = exec.Command("ip", "netns", "exec", "ns_"+ns.Name, "ip", "link", "set", vrfName, "up") if err := cmd.Run(); err != nil { return nil, fmt.Errorf("failed to up VRF: %w", err) } return vrf, nil } func (m *Manager) Delete(namespaceID uuid.UUID, name string) error { // Get namespace name var ns models.Namespace if err := m.db.First(&ns, namespaceID).Error; err != nil { return err } // Delete VRF from Linux namespace vrfName := fmt.Sprintf("vrf:%s", name) cmd := exec.Command("ip", "netns", "exec", "ns_"+ns.Name, "ip", "link", "del", vrfName) if err := cmd.Run(); err != nil { return fmt.Errorf("failed to delete VRF: %w", err) } // Delete from database return m.db.Where("namespace_id = ? AND name = ?", namespaceID, name). Delete(&models.VRF{}).Error } func (m *Manager) Get(namespaceID uuid.UUID, name string) (*models.VRF, error) { var vrf models.VRF if err := m.db.Where("namespace_id = ? AND name = ?", namespaceID, name). First(&vrf).Error; err != nil { return nil, err } return &vrf, nil } func (m *Manager) List(namespaceID uuid.UUID) ([]models.VRF, error) { var vrfs []models.VRF if err := m.db.Where("namespace_id = ?", namespaceID).Find(&vrfs).Error; err != nil { return nil, err } return vrfs, nil } ``` ## Phase 4: WireGuard Integration ### Step 4.1: Update WireGuard Manager ```go // internal/wgmanager/manager.go type UpConfig struct { // ... existing fields NamespaceName string // e.g., "zerocode" VRFName string // e.g., "main" } ``` ### Step 4.2: Update WireGuard Linux Implementation ```go // internal/wgmanager/wgmanager_linux.go func (m *LinuxWgManager) Up(cfg UpConfig) error { // 1. Create WG interface in namespace if cfg.NamespaceName != "" { // Create in namespace cmd := exec.Command("ip", "netns", "exec", "ns_"+cfg.NamespaceName, "ip", "link", "add", cfg.InterfaceName, "type", "wireguard") if err := cmd.Run(); err != nil { return fmt.Errorf("failed to create WG interface: %w", err) } } else { // Create in default namespace (existing code) // ... } // 2. Bind to VRF (if specified) if cfg.VRFName != "" && cfg.NamespaceName != "" { vrfName := fmt.Sprintf("vrf:%s", cfg.VRFName) cmd := exec.Command("ip", "netns", "exec", "ns_"+cfg.NamespaceName, "ip", "link", "set", cfg.InterfaceName, "vrf", vrfName) if err := cmd.Run(); err != nil { return fmt.Errorf("failed to bind to VRF: %w", err) } } // 3. Configure WG (existing code, but in namespace) // ... return nil } ``` ## Phase 5: Testing ### Step 5.1: Unit Tests ```go // internal/namespace/manager_test.go package namespace import ( "testing" "github.com/google/uuid" "git.datadunia.com/nexusguard/nexus-server-core/internal/models" "gorm.io/driver/sqlite" "gorm.io/gorm" ) func TestCreateNamespace(t *testing.T) { // Setup test database db, err := gorm.Open(sqlite.Open(":memory:"), &gorm.Config{}) if err != nil { t.Fatal(err) } // Auto migrate db.AutoMigrate(&models.User{}, &models.Namespace{}, &models.NamespaceUser{}) // Create manager mgr := NewManager(db) // Create test user user := &models.User{Username: "testuser"} db.Create(user) // Create namespace ns, err := mgr.Create("testns", user.ID) if err != nil { t.Fatalf("failed to create namespace: %v", err) } // Verify if ns.Name != "testns" { t.Errorf("expected name 'testns', got '%s'", ns.Name) } // Cleanup mgr.Delete("testns") } ``` ### Step 5.2: Integration Tests ```bash # Test namespace creation curl -X POST http://localhost:8080/api/v1/namespaces \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"name": "testns"}' # Test VRF creation curl -X POST http://localhost:8080/api/v1/ns/testns/vrfs \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"name": "main"}' # Test server creation in namespace curl -X POST http://localhost:8080/api/v1/ns/testns/servers \ -H "Authorization: Bearer " \ -H "Content-Type: application/json" \ -d '{"name": "node1", "public_endpoint": "1.2.3.4:51820"}' ``` ## Deployment ### Step 1: Build ```bash cd apps/server-core CGO_ENABLED=0 go build -o ../../bin/server-core . ``` ### Step 2: Deploy ```bash # Copy binary to server scp ../../bin/server-core root@172.20.8.191:/usr/local/bin/nexusguard-server # Restart service ssh root@172.20.8.191 "systemctl restart nexusguard-server" ``` ### Step 3: Verify ```bash # Check namespaces ip netns list # Check VRFs in namespace ip netns exec ns_testns ip link show type vrf # Check WireGuard in namespace ip netns exec ns_testns wg show ``` ## Rollback ### Step 1: Disable VRF ```bash # Update database psql -U nexusguard -d nexusguard -c " UPDATE wg_servers SET namespace_id = NULL, vrf_id = NULL; " # Delete namespaces ip netns list | grep ns_ | xargs -I {} ip netns del {} ``` ### Step 2: Restart Service ```bash ssh root@172.20.8.191 "systemctl restart nexusguard-server" ``` --- **Status:** Implementation guide — ready for development **Last Updated:** 2026-07-10 **Author:** NexusGuard Team