171 lines
6.0 KiB
Go
171 lines
6.0 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 TestValidateWireGuardAuthMode(t *testing.T) {
|
|
for _, mode := range []string{"", "password"} {
|
|
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "auth_mode": mode}); err != nil {
|
|
t.Fatalf("legacy/password mode %q rejected: %v", mode, err)
|
|
}
|
|
}
|
|
for _, mode := range []string{"oidc", "unknown"} {
|
|
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "auth_mode": mode}); err == nil {
|
|
t.Fatalf("unsupported authentication mode %q accepted", mode)
|
|
}
|
|
}
|
|
}
|
|
|
|
func TestValidateLatencyProbeTarget(t *testing.T) {
|
|
for _, target := range []string{"10.88.0.1:22", "[2001:db8::1]:443", ""} {
|
|
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "latency_probe": target}); err != nil {
|
|
t.Fatalf("valid target %q rejected: %v", target, err)
|
|
}
|
|
}
|
|
for _, target := range []string{"example.com:443", "10.88.0.1:0", "10.88.0.1:65536", "invalid"} {
|
|
if _, err := New().Validate(map[string]interface{}{"address": "10.8.0.1/24", "latency_probe": target}); err == nil {
|
|
t.Fatalf("invalid target %q accepted", target)
|
|
}
|
|
}
|
|
}
|
|
|
|
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"
|
|
}
|