policy/v2: check SSHPolicy nil vs empty against captures

Nil keeps client's old rules; empty clears them. Old normalization hid it.

Updates #3508
This commit is contained in:
Kristoffer Dalby
2026-09-30 15:02:53 +00:00
committed by Kristoffer Dalby
parent 48046dc9c5
commit 961535351f
@@ -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