feat(client): add authorized WireGuard device enrollment and desktop connection

This commit is contained in:
opsx committed 2026-10-21 14:53:55 +08:00
1 parent d2f1f5040e
commit 6dd3c7dd45
25 files changed
+3274 -131

No files matched your search

@@ -0,0 +1,129 @@
package wireguard
import (
"os"
"strings"
"testing"
"vpn-platform/backend/internal/model"
"vpn-platform/backend/internal/store"
)
func setupDeviceTestTunnel(t *testing.T) *model.Tunnel {
t.Helper()
base := t.TempDir()
t.Setenv("VPN_DATA_DIR", base)
if err := store.Init(base); err != nil {
t.Fatalf("store.Init: %v", err)
}
tun := &model.Tunnel{
Name: "客户端测试专线",
Protocol: "wireguard",
State: "stopped",
Config: `{"address":"10.9.0.1/24","listen_port":53999}`,
}
if err := store.DB.Create(tun).Error; err != nil {
t.Fatalf("create tunnel: %v", err)
}
if err := New().Create(tun); err != nil {
t.Fatalf("Create: %v", err)
}
return tun
}
func TestRegisterAndRemoveDevicePeer(t *testing.T) {
tun := setupDeviceTestTunnel(t)
svc := New()
// 初始 Create 会把无 peers 的旧配置迁移出 client-1 托管 Peer(默认 10.8.0.2/32)
used, err := svc.UsedClientAddresses(tun)
if err != nil {
t.Fatalf("UsedClientAddresses: %v", err)
}
if !used["10.9.0.1"] || !used["10.8.0.2"] {
t.Fatalf("应包含服务端与初始 Peer 地址: %v", used)
}
priv, pub, err := genKey()
if err != nil || priv == "" {
t.Fatalf("genKey: %v", err)
}
psk, err := svc.RegisterDevicePeer(tun, "dev-1", "测试手机", pub, "10.9.0.7/32")
if err != nil {
t.Fatalf("RegisterDevicePeer: %v", err)
}
if !isWireGuardKey(psk) {
t.Fatal("应为设备 Peer 生成合法 PresharedKey")
}
params, err := svc.DeviceConnectionParams(tun, "dev-1")
if err != nil {
t.Fatalf("DeviceConnectionParams: %v", err)
}
if params["client_address"] != "10.9.0.7/32" || params["preshared_key"] != psk {
t.Fatalf("连接参数不含分配地址或 PSK 不匹配: %v", params)
}
if params["server_public_key"] == "" || params["endpoint"] == "" {
t.Fatalf("连接参数缺少服务端公钥或端点: %v", params)
}
// 重复公钥或重复地址必须被拒绝
if _, err := svc.RegisterDevicePeer(tun, "dev-2", "重复密钥", pub, "10.9.0.8/32"); err == nil {
t.Fatal("重复公钥必须被拒绝")
}
_, pub2, _ := genKey()
if _, err := svc.RegisterDevicePeer(tun, "dev-3", "重复地址", pub2, "10.9.0.7/32"); err == nil {
t.Fatal("重复地址必须被拒绝")
}
// 服务端 conf 必须包含新 Peer
conf := readFile(t, InstanceConfPath(tun))
if !strings.Contains(conf, pub) {
t.Fatal("服务端配置未包含设备公钥")
}
if err := svc.RemoveDevicePeer(tun, "dev-1"); err != nil {
t.Fatalf("RemoveDevicePeer: %v", err)
}
if _, err := svc.DeviceConnectionParams(tun, "dev-1"); err == nil {
t.Fatal("撤销后不应再能取到连接参数")
}
conf = readFile(t, InstanceConfPath(tun))
if strings.Contains(conf, pub) {
t.Fatal("撤销后服务端配置仍包含设备公钥")
}
}
func TestRemoveDevicePeerKeepsLastPeer(t *testing.T) {
base := t.TempDir()
t.Setenv("VPN_DATA_DIR", base)
if err := store.Init(base); err != nil {
t.Fatalf("store.Init: %v", err)
}
// 没有任何既有 Peer 的隧道:登记一个设备后它就是唯一 Peer
tun := &model.Tunnel{
Name: "空专线", Protocol: "wireguard", State: "stopped",
Config: `{"address":"10.10.0.1/24","listen_port":53998,"peers":[]}`,
}
if err := store.DB.Create(tun).Error; err != nil {
t.Fatalf("create tunnel: %v", err)
}
// peers 为空数组时 Create 会拒绝;直接手工初始化密钥再登记设备
tun.Config = `{"address":"10.10.0.1/24","listen_port":53998,"peers":[{"id":"seed","name":"种子","public_key":"","client_address":"10.10.0.2/32"}]}`
if err := New().Create(tun); err != nil {
t.Fatalf("Create: %v", err)
}
svc := New()
if err := svc.RemoveDevicePeer(tun, "seed"); err == nil {
t.Fatal("不允许移除最后一个 Peer")
}
}
func readFile(t *testing.T, path string) string {
t.Helper()
raw, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read %s: %v", path, err)
}
return string(raw)
}