e523398fa8
- apps/device-agent-embedded/: ESP32 WireGuard agent (C, ESP-IDF, mbedTLS) - reference/namespace-vrf/: Linux namespace/VRF network isolation docs - apps/android-agent: VPN permission check, StartResult sealed class
759 lines
19 KiB
Markdown
759 lines
19 KiB
Markdown
# 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 <token>" \
|
|
-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 <token>" \
|
|
-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 <token>" \
|
|
-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
|