Files
opsx-vpn/backend/internal/service/wireguard/wireguard_test.go
T

145 lines
4.9 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 TestNATOnlyAppliesToWireGuardSubnet(t *testing.T) {
conf := renderServerConfig(map[string]interface{}{
"address": "10.88.0.1/24", "listen_port": 51820,
"enable_nat": true, "egress_interface": "eth0",
}, "test-key", nil, nil)
if !strings.Contains(conf, "POSTROUTING -s 10.88.0.0/24 -o eth0 -j MASQUERADE") {
t.Fatal("NAT must be limited to the tunnel subnet")
}
if strings.Count(conf, "POSTROUTING -s 10.88.0.0/24") != 2 {
t.Fatal("PostUp and PostDown must use the same subnet")
}
}
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"
}