From 961535351f309d6fff6ffde89ba5cc9b9be624b9 Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Wed, 30 Sep 2026 15:02:53 +0000 Subject: [PATCH] policy/v2: check SSHPolicy nil vs empty against captures Nil keeps client's old rules; empty clears them. Old normalization hid it. Updates #3508 --- .../v2/tailscale_ssh_data_compat_test.go | 26 ++++++++++++------- 1 file changed, 17 insertions(+), 9 deletions(-) diff --git a/hscontrol/policy/v2/tailscale_ssh_data_compat_test.go b/hscontrol/policy/v2/tailscale_ssh_data_compat_test.go index b04e799fa..60a12d8df 100644 --- a/hscontrol/policy/v2/tailscale_ssh_data_compat_test.go +++ b/hscontrol/policy/v2/tailscale_ssh_data_compat_test.go @@ -189,15 +189,12 @@ func TestSSHDataCompat(t *testing.T) { nodeName, ) - // Build expected SSHPolicy from the typed rules. - var wantSSH *tailcfg.SSHPolicy - if len(capture.SSHRules) > 0 { - wantSSH = &tailcfg.SSHPolicy{Rules: capture.SSHRules} - } - - // Normalize: treat empty-rules SSHPolicy as nil - if gotSSH != nil && len(gotSSH.Rules) == 0 { - gotSSH = nil + // Nil and empty SSHPolicy differ on the wire: nil + // keeps the client's previous rules, empty clears + // them. Take presence from the captured netmap. + wantSSH := &tailcfg.SSHPolicy{Rules: capture.SSHRules} + if capture.Netmap != nil && capture.Netmap.SSHPolicy == nil { + wantSSH = nil } // Compare headscale output against Tailscale expected. @@ -220,6 +217,17 @@ func TestSSHDataCompat(t *testing.T) { ) } + // EquateEmpty hides "rules":null vs "rules":[]; + // pin the captured shape separately. + if gotSSH != nil && capture.Netmap != nil && + capture.Netmap.SSHPolicy != nil { + assert.Equalf(t, + capture.Netmap.SSHPolicy.Rules == nil, + gotSSH.Rules == nil, + "%s/%s: rules null-vs-[] mismatch", tf.TestID, nodeName, + ) + } + // Separate presence check: the fields ignored by // the diff above must still be populated on matching // rules. This catches regressions where headscale