From f21721224757062066597a84e3137fadfde70c73 Mon Sep 17 00:00:00 2001 From: Matt Van Horn Date: Wed, 7 Oct 2026 01:55:41 -0700 Subject: [PATCH] web: overlay WireGuard peer public key from the inbound (#6751) The WireGuard branch now copies PublicKey from the matching inbound settings client. When that entry's KeepAlive is nil the peer keepalive is cleared, and when it is set the integer is copied into a new pointer. The dual-tunnel test seeds different keys and keepalives, checks that the shared row still has the AmneziaWG identity, and expects the emitted WireGuard peer to use the WireGuard public key and keepalive, including a case where the WireGuard settings entry has no keepalive and the peer omits it. Fixes #6731 --- internal/web/service/xray.go | 8 ++++ .../web/service/xray_wireguard_config_test.go | 44 ++++++++++++++++--- 2 files changed, 46 insertions(+), 6 deletions(-) diff --git a/internal/web/service/xray.go b/internal/web/service/xray.go index 5dbbaa50c..cd297c7c8 100644 --- a/internal/web/service/xray.go +++ b/internal/web/service/xray.go @@ -286,6 +286,14 @@ func (s *XrayService) GetXrayConfig() (*xray.Config, error) { if inboundClient, ok := wireguardClientsByEmail[strings.ToLower(strings.TrimSpace(c.Email))]; ok { c.AllowedIPs = inboundClient.AllowedIPs c.PreSharedKey = inboundClient.PreSharedKey + c.PublicKey = inboundClient.PublicKey + // #6731: a nil settings keepalive must not keep the other tunnel's value. + if inboundClient.KeepAlive == nil { + c.KeepAlive = nil + } else { + keepalive := *inboundClient.KeepAlive + c.KeepAlive = &keepalive + } } wgPeers = append(wgPeers, model.WireguardPeerFromClient(c)) continue diff --git a/internal/web/service/xray_wireguard_config_test.go b/internal/web/service/xray_wireguard_config_test.go index 443cb72a5..874f3cbc0 100644 --- a/internal/web/service/xray_wireguard_config_test.go +++ b/internal/web/service/xray_wireguard_config_test.go @@ -55,7 +55,7 @@ func seedWGInbound(t *testing.T, tag string, port int, clients []model.Client) { } } -func seedDualTunnelClient(t *testing.T, enabled bool) string { +func seedDualTunnelClient(t *testing.T, enabled bool, wgKeepAlive *int) string { t.Helper() setupSettingTestDB(t) db := database.GetDB() @@ -64,13 +64,16 @@ func seedDualTunnelClient(t *testing.T, enabled bool) string { wgClient := model.Client{ Email: email, Enable: true, - PublicKey: "pub-dual", + PublicKey: "pub-wg", AllowedIPs: []string{"10.0.0.5/32"}, PreSharedKey: "wg-psk", + KeepAlive: wgKeepAlive, } awgClient := wgClient + awgClient.PublicKey = "pub-awg" awgClient.AllowedIPs = []string{"10.8.1.5/32"} awgClient.PreSharedKey = "awg-psk" + awgClient.KeepAlive = model.KeepAlivePtr(25) wgSettings, err := json.Marshal(map[string]any{ "secretKey": wgTestSecretKey(), @@ -191,14 +194,14 @@ func TestGetXrayConfigWireGuardDisabledClientExcluded(t *testing.T) { } func TestGetXrayConfigWireGuardUsesInboundLocalTunnelFields(t *testing.T) { - email := seedDualTunnelClient(t, true) + email := seedDualTunnelClient(t, true, model.KeepAlivePtr(15)) var shared model.ClientRecord if err := database.GetDB().Where("email = ?", email).First(&shared).Error; err != nil { t.Fatalf("read shared client: %v", err) } - if shared.AllowedIPs != "10.8.1.5/32" || shared.PreSharedKey != "awg-psk" { - t.Fatalf("test setup did not persist AmneziaWG last: allowedIPs=%q preSharedKey=%q", shared.AllowedIPs, shared.PreSharedKey) + if shared.AllowedIPs != "10.8.1.5/32" || shared.PreSharedKey != "awg-psk" || shared.PublicKey != "pub-awg" || shared.KeepAlive != 25 { + t.Fatalf("test setup did not persist AmneziaWG last: allowedIPs=%q preSharedKey=%q publicKey=%q keepAlive=%d", shared.AllowedIPs, shared.PreSharedKey, shared.PublicKey, shared.KeepAlive) } peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual")) @@ -212,10 +215,39 @@ func TestGetXrayConfigWireGuardUsesInboundLocalTunnelFields(t *testing.T) { if peers[0]["preSharedKey"] != "wg-psk" { t.Fatalf("WireGuard peer preSharedKey = %v, want wg-psk", peers[0]["preSharedKey"]) } + if peers[0]["publicKey"] != "pub-wg" { + t.Fatalf("WireGuard peer publicKey = %v, want pub-wg", peers[0]["publicKey"]) + } + if peers[0]["keepAlive"] != float64(15) { + t.Fatalf("WireGuard peer keepAlive = %v, want 15", peers[0]["keepAlive"]) + } +} + +func TestGetXrayConfigWireGuardOmitsKeepAliveAbsentFromSettings(t *testing.T) { + email := seedDualTunnelClient(t, true, nil) + + var shared model.ClientRecord + if err := database.GetDB().Where("email = ?", email).First(&shared).Error; err != nil { + t.Fatalf("read shared client: %v", err) + } + if shared.PublicKey != "pub-awg" || shared.KeepAlive != 25 { + t.Fatalf("test setup did not persist AmneziaWG identity: publicKey=%q keepAlive=%d", shared.PublicKey, shared.KeepAlive) + } + + peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual")) + if len(peers) != 1 { + t.Fatalf("expected 1 peer, got %d: %v", len(peers), peers) + } + if peers[0]["publicKey"] != "pub-wg" { + t.Fatalf("WireGuard peer publicKey = %v, want pub-wg", peers[0]["publicKey"]) + } + if _, ok := peers[0]["keepAlive"]; ok { + t.Fatalf("WireGuard peer keepAlive = %v, want absent", peers[0]["keepAlive"]) + } } func TestGetXrayConfigWireGuardDisabledDualProtocolClientExcluded(t *testing.T) { - seedDualTunnelClient(t, false) + seedDualTunnelClient(t, false, model.KeepAlivePtr(15)) peers := wgPeerList(t, wgInboundEmittedSettings(t, "wg-dual")) if len(peers) != 0 {