From 2a0823ca4160ecdd66282645ae7c119c3676a38d Mon Sep 17 00:00:00 2001 From: Kristoffer Dalby Date: Fri, 2 Oct 2026 08:57:20 +0000 Subject: [PATCH] policy/v2: send an empty SSH policy when no rule applies Nil SSHPolicy means "unchanged" to clients; removed rules stayed live. Match SaaS: "rules":[]. Fixes #3508 --- hscontrol/policy/policy_test.go | 2 +- hscontrol/policy/v2/filter.go | 8 +++++--- hscontrol/policy/v2/filter_test.go | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/hscontrol/policy/policy_test.go b/hscontrol/policy/policy_test.go index 7132c0d24..53f5813d5 100644 --- a/hscontrol/policy/policy_test.go +++ b/hscontrol/policy/policy_test.go @@ -458,7 +458,7 @@ func TestSSHPolicyRules(t *testing.T) { } ] }`, - wantSSH: &tailcfg.SSHPolicy{Rules: nil}, + wantSSH: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}}, }, { name: "invalid-action", diff --git a/hscontrol/policy/v2/filter.go b/hscontrol/policy/v2/filter.go index 283d3106b..7029b107c 100644 --- a/hscontrol/policy/v2/filter.go +++ b/hscontrol/policy/v2/filter.go @@ -261,13 +261,15 @@ func (pol *Policy) compileSSHPolicy( node types.NodeView, nodes views.Slice[types.NodeView], ) (*tailcfg.SSHPolicy, error) { - if pol == nil || pol.SSHs == nil || len(pol.SSHs) == 0 { - return nil, nil //nolint:nilnil // intentional: no SSH policy when none configured + // Never nil: clients read a nil SSHPolicy as "keep the previous + // rules", so revoked access would stay. SaaS sends "rules":[]. + if pol == nil || len(pol.SSHs) == 0 { + return &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}}, nil } log.Trace().Caller().Msgf("compiling SSH policy for node %q", node.Hostname()) - var rules []*tailcfg.SSHRule + rules := []*tailcfg.SSHRule{} for index, rule := range pol.SSHs { var autogroupSelfDests, otherDests []Alias diff --git a/hscontrol/policy/v2/filter_test.go b/hscontrol/policy/v2/filter_test.go index 83f89b991..80cbe0ce5 100644 --- a/hscontrol/policy/v2/filter_test.go +++ b/hscontrol/policy/v2/filter_test.go @@ -618,7 +618,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) { }, }, }, - want: &tailcfg.SSHPolicy{}, + want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{}}, }, }