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{}}, }, }