feat(client): add authorized WireGuard device enrollment and desktop connection
This commit is contained in:
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)
|
||||
}
|
||||
Reference in new issue
Block a user