Files
opsx-vpn/backend/internal/service/wireguard/wireguard_test.go
T
Logan.Li 1e2f8ef24f feat: 全面优化 — UI/UX重构 + 流量审计与会话管理 + 协议部署
UI/UX 优化:
- 登录页分屏布局 + 可访问性修复 + 内联错误提示
- 侧栏菜单分组(专线运维/资源管理/系统) + 图标修正 + 子路由高亮
- Dashboard 统计卡渐变图标 + 空状态引导 + 统计卡可点击
- 创建向导: 依赖检测门控 + 高级折叠 + 必填校验 + WireGuard推荐标
- 专线列表: 空状态CTA + 操作列「更多」下拉 + 状态筛选 + SSE日志流
- 协议品牌SVG图标(ProtocolIcon组件)替换emoji
- 设计令牌系统(tokens.css)统一颜色/间距/圆角/阴影

功能增强:
- 在线会话管理(sessionmgr): 跨协议会话同步 + 踢下线 + 连接审计日志
- 流量配额引擎(trafficmgr): 配额检查 + 超额动作(block/warn/throttle) + 周期重置
- 异常流量告警: 突发流量检测(>90MB/15s) + 审计记录
- 节点SSH凭证管理: 密码/私钥认证 + 即时测试连接 + AES-GCM加密存储
- 协议二进制管理(binmgr): FRP/OpenVPN GitHub下载 + 系统包安装引导(apt/yum/dnf)
- 右上角修改密码弹窗 + MFA独立验证页 + 密码强度提示
- 用户管理: 批量导入 + LDAP浏览导入 + 平台用户导入VPN拨号用户
- 404路由 + IPSec对接页可达性修复
- MFA备用码持久化弹窗(替代Toast)
- 轮询可见性守卫 + ECharts resize修复

后端:
- 3张新表: VPNSession/ConnectionLog/TrafficPolicy
- monitor增强: 会话同步+配额检查+异常告警+到期审计
- changePassword统一8位+新旧不同校验
- openvpn-otp文案专业化(移除抄袭感)
- SSE日志流(token降级query参数)
2026-07-23 01:42:36 +08:00

132 lines
4.4 KiB
Go

package wireguard
import (
"encoding/json"
"os"
"strconv"
"strings"
"testing"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/service"
"vpn-platform/backend/internal/store"
)
func TestBuildPeersMigratesLegacySingleClient(t *testing.T) {
cfg := map[string]interface{}{
"address": "10.8.0.1/24",
"client_address": "10.8.0.2/32",
"allowed_ips": "0.0.0.0/0, ::/0",
}
peers, secrets, err := buildPeers(cfg, map[string]interface{}{})
if err != nil {
t.Fatalf("buildPeers: %v", err)
}
if len(peers) != 1 || peers[0].ID != "client-1" || !peers[0].Managed {
t.Fatalf("legacy peer migration = %#v", peers)
}
if !isWireGuardKey(peers[0].PublicKey) || !isWireGuardKey(secrets["client-1"].PrivateKey) || !isWireGuardKey(secrets["client-1"].PresharedKey) {
t.Fatal("generated peer keys are not WireGuard keys")
}
cfg["server_public_key"] = "server-public-key"
cfg["listen_port"] = 51820
client, err := generateClientConfig(cfg, peers, secrets, "client-1")
if err != nil {
t.Fatalf("generateClientConfig: %v", err)
}
if !strings.Contains(client, "Address = 10.8.0.2/32") || !strings.Contains(client, "Endpoint = your-server-ip:51820") {
t.Fatalf("unexpected client config:\n%s", client)
}
}
func TestBuildPeersKeepsExternalPeerPrivateKeyOutOfPlatform(t *testing.T) {
_, externalPublic, err := genKey()
if err != nil {
t.Fatal(err)
}
cfg := map[string]interface{}{
"allowed_ips": "10.0.0.0/8",
"peers": []interface{}{map[string]interface{}{
"id": "branch-office",
"name": "Branch office",
"public_key": externalPublic,
"client_address": "10.8.0.10/32",
"enabled": true,
}},
}
peers, secrets, err := buildPeers(cfg, map[string]interface{}{})
if err != nil {
t.Fatalf("buildPeers: %v", err)
}
if peers[0].Managed || secrets["branch-office"].PrivateKey != "" {
t.Fatalf("external peer must not gain a platform-held private key: %#v %#v", peers[0], secrets["branch-office"])
}
if _, err := generateClientConfig(cfg, peers, secrets, "branch-office"); err == nil {
t.Fatal("external peer configuration should not be downloadable")
}
}
func TestValidateRejectsUnsafeNATAndInvalidPeerAddress(t *testing.T) {
s := New()
// enable_nat without egress:若本机无默认路由则失败;有默认路由则自动填 egress_interface。
if cfg, err := s.Validate(map[string]interface{}{"address": "10.8.0.1/24", "enable_nat": true}); err == nil {
if service.Str(cfg, "egress_interface") == "" {
t.Fatal("NAT auto-detect succeeded but egress_interface is empty")
}
}
_, err := s.Validate(map[string]interface{}{
"address": "10.8.0.1/24",
"peers": []interface{}{map[string]interface{}{"id": "bad", "client_address": "not-a-cidr"}},
})
if err == nil {
t.Fatal("invalid peer address was accepted")
}
}
func TestInterfaceNameFitsLinuxLimit(t *testing.T) {
name := interfaceName(&model.Tunnel{ID: ^uint(0)})
if len(name) > 15 {
t.Fatalf("interface name %q exceeds IFNAMSIZ-1", name)
}
}
func TestCreatePersistsOnlyPublicPeerData(t *testing.T) {
dataDir := t.TempDir()
t.Setenv("VPN_DATA_DIR", dataDir)
if err := store.Init(dataDir); err != nil {
t.Fatalf("store.Init: %v", err)
}
tunnel := &model.Tunnel{
Name: "wg-test",
Protocol: "wireguard",
Config: `{"address":"10.60.0.1/24","client_address":"10.60.0.2/32"}`,
}
if err := store.DB.Create(tunnel).Error; err != nil {
t.Fatal(err)
}
if err := New().Create(tunnel); err != nil {
t.Fatalf("Create: %v", err)
}
if strings.Contains(tunnel.Config, "private_key") || strings.Contains(tunnel.Config, "preshared_key") {
t.Fatalf("private material leaked into Config: %s", tunnel.Config)
}
var config map[string]interface{}
if err := json.Unmarshal([]byte(tunnel.Config), &config); err != nil {
t.Fatal(err)
}
if config["interface_name"] != interfaceName(tunnel) || int(config["listen_port"].(float64)) < defaultPort {
t.Fatalf("missing interface/allocated port: %#v", config)
}
conf, err := New().GenerateClientConfig(tunnel)
if err != nil || !strings.Contains(conf, "PrivateKey = ") {
t.Fatalf("client configuration unavailable: %v\n%s", err, conf)
}
if _, err := os.Stat(serviceConfigPath(tunnel)); err != nil {
t.Fatalf("server config not written: %v", err)
}
}
func serviceConfigPath(tunnel *model.Tunnel) string {
return os.Getenv("VPN_DATA_DIR") + "/instances/" + strconv.Itoa(int(tunnel.ID)) + "-" + tunnel.Protocol + "/" + interfaceName(tunnel) + ".conf"
}