Merge pull request #462 from Roshan931/prilog/fix-error-newt-wg-connect-payload-decoded-into-10-06-2026-1e998b

fix: Health check config collides between two targets sharing the same backen...
This commit is contained in:
Owen Schwartz
2026-10-12 14:08:01 -04:00
committed by GitHub
2 changed files with 77 additions and 1 deletions
+10 -1
View File
@@ -23,6 +23,15 @@ import (
"golang.zx2c4.com/wireguard/tun/netstack"
)
func (n *Newt) applyWgData(jsonData []byte) error {
var wgData WgData
if err := json.Unmarshal(jsonData, &wgData); err != nil {
return err
}
n.wgData = wgData
return nil
}
func (n *Newt) handleConnect(ctx context.Context, msg websocket.WSMessage) {
logger.Debug("Received registration message")
regResult := "success"
@@ -63,7 +72,7 @@ func (n *Newt) handleConnect(ctx context.Context, msg websocket.WSMessage) {
return
}
if err := json.Unmarshal(jsonData, &n.wgData); err != nil {
if err := n.applyWgData(jsonData); err != nil {
logger.Info("Error unmarshaling target data: %v", err)
regResult = "failure"
return
+67
View File
@@ -8,6 +8,8 @@ import (
"sync/atomic"
"testing"
"time"
"github.com/fosrl/newt/healthcheck"
)
func TestWatchBlueprintFile_WriteTriggersSend(t *testing.T) {
@@ -353,6 +355,71 @@ func TestPingNilNetstackReturnsError(t *testing.T) {
}
}
func TestApplyWgDataReplacesPreviousPayload(t *testing.T) {
n := &Newt{wgData: WgData{
HealthCheckTargets: []healthcheck.Config{
{ID: 2, Status: 401, Path: "/api/mcp"},
{ID: 1, Status: 200, Path: "/manifest.json"},
},
}}
payload := []byte(`{
"healthCheckTargets": [
{"id": 1, "hcPath": "/manifest.json", "hcStatus": null},
{"id": 2, "hcPath": "/api/mcp", "hcStatus": 401}
]
}`)
if err := n.applyWgData(payload); err != nil {
t.Fatalf("applyWgData returned error: %v", err)
}
got := n.wgData.HealthCheckTargets
if len(got) != 2 {
t.Fatalf("expected 2 health check targets, got %d", len(got))
}
if got[0].ID != 1 || got[0].Status != 0 {
t.Errorf("target 1 = {ID: %d, Status: %d}, want {ID: 1, Status: 0}", got[0].ID, got[0].Status)
}
if got[1].ID != 2 || got[1].Status != 401 {
t.Errorf("target 2 = {ID: %d, Status: %d}, want {ID: 2, Status: 401}", got[1].ID, got[1].Status)
}
}
func TestApplyWgDataClearsStaleFields(t *testing.T) {
n := &Newt{wgData: WgData{
TunnelIP: "10.0.0.2",
RemoteExitNodeSubnets: []string{"10.1.0.0/24"},
HealthCheckTargets: []healthcheck.Config{{ID: 1, Status: 200}},
}}
if err := n.applyWgData([]byte(`{"tunnelIP": "10.0.0.3"}`)); err != nil {
t.Fatalf("applyWgData returned error: %v", err)
}
if n.wgData.TunnelIP != "10.0.0.3" {
t.Errorf("TunnelIP = %q, want %q", n.wgData.TunnelIP, "10.0.0.3")
}
if len(n.wgData.RemoteExitNodeSubnets) != 0 {
t.Errorf("RemoteExitNodeSubnets = %v, want empty", n.wgData.RemoteExitNodeSubnets)
}
if len(n.wgData.HealthCheckTargets) != 0 {
t.Errorf("HealthCheckTargets = %v, want empty", n.wgData.HealthCheckTargets)
}
}
func TestApplyWgDataKeepsExistingDataOnDecodeError(t *testing.T) {
n := &Newt{wgData: WgData{TunnelIP: "10.0.0.2"}}
if err := n.applyWgData([]byte(`{"tunnelIP":`)); err == nil {
t.Fatal("expected error for malformed payload, got nil")
}
if n.wgData.TunnelIP != "10.0.0.2" {
t.Errorf("TunnelIP = %q, want %q", n.wgData.TunnelIP, "10.0.0.2")
}
}
// TestShouldFireRecovery is the regression guard for the broken trigger gate
// that prevented data-plane recovery from ever firing under default settings
// (fosrl/newt#284, #310, pangolin#1004).