mirror of
https://github.com/juanfont/headscale.git
synced 2026-11-04 04:34:07 +09:00
policy/v2: map login 0 to no local user in SSH rules
Every rule that maps root now carries "0": "", as Tailscale SaaS writes it, also when the policy lists "0".
This commit is contained in:
1 parent
cb706b1999
commit
b56b6dcae1
8 files changed
+67
-206
No files matched your search
@@ -385,6 +385,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"*": "=",
|
||||
"root": "",
|
||||
},
|
||||
@@ -424,6 +425,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"*": "=",
|
||||
"root": "",
|
||||
},
|
||||
@@ -538,6 +540,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"autogroup:invalid": "autogroup:invalid",
|
||||
"root": "",
|
||||
},
|
||||
@@ -577,6 +580,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"*": "=",
|
||||
"root": "",
|
||||
},
|
||||
@@ -616,6 +620,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"*": "=",
|
||||
"root": "root",
|
||||
},
|
||||
@@ -655,6 +660,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"root": "root",
|
||||
"ubuntu": "ubuntu",
|
||||
},
|
||||
@@ -705,6 +711,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
{NodeIP: "100.64.0.2"},
|
||||
},
|
||||
SSHUsers: map[string]string{
|
||||
"0": "",
|
||||
"debian": "debian",
|
||||
"root": "",
|
||||
},
|
||||
@@ -738,7 +745,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
wantSSH: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.6"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: &tailcfg.SSHAction{
|
||||
Accept: true,
|
||||
AllowAgentForwarding: true,
|
||||
@@ -758,7 +765,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.7"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: &tailcfg.SSHAction{
|
||||
Accept: true,
|
||||
AllowAgentForwarding: true,
|
||||
@@ -799,7 +806,7 @@ func TestSSHPolicyRules(t *testing.T) {
|
||||
wantSSH: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.6"}},
|
||||
SSHUsers: map[string]string{"root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "root"},
|
||||
Action: &tailcfg.SSHAction{
|
||||
Accept: true,
|
||||
AllowAgentForwarding: true,
|
||||
|
||||
@@ -283,6 +283,10 @@ type compiledSSHRule struct {
|
||||
destRules []*tailcfg.SSHRule
|
||||
}
|
||||
|
||||
// uidZeroLogin is a login name that can resolve to uid 0 on the node, so
|
||||
// every rule that maps root maps it to no local user, as Tailscale SaaS does.
|
||||
const uidZeroLogin = "0"
|
||||
|
||||
// sshSources is what the sources of an [SSH] rule resolve to. It is
|
||||
// read-only once built: rules with the same source addresses share it.
|
||||
type sshSources struct {
|
||||
@@ -407,6 +411,9 @@ func (pol *Policy) compileSSHRules(
|
||||
cr.sshUsers[u.String()] = u.String()
|
||||
}
|
||||
|
||||
// After the literal users, so listing "0" cannot map it.
|
||||
cr.sshUsers[uidZeroLogin] = ""
|
||||
|
||||
if cr.hasLocalpart {
|
||||
cr.localpartByUser = resolveLocalparts(
|
||||
rule.Users.LocalpartEntries(), users,
|
||||
|
||||
@@ -460,7 +460,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: user2Principal,
|
||||
SSHUsers: map[string]string{"root": "", "ssh-it-user": "ssh-it-user"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "", "ssh-it-user": "ssh-it-user"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -487,7 +487,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: user2Principal,
|
||||
SSHUsers: map[string]string{"root": "", "ubuntu": "ubuntu", "admin": "admin", "deploy": "deploy"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "", "ubuntu": "ubuntu", "admin": "admin", "deploy": "deploy"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -514,7 +514,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: user2Principal,
|
||||
SSHUsers: map[string]string{"*": "=", "root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "*": "=", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -541,7 +541,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: user2Principal,
|
||||
SSHUsers: map[string]string{"root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "root"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -568,7 +568,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: user2Principal,
|
||||
SSHUsers: map[string]string{"*": "=", "root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "*": "=", "root": "root"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -595,7 +595,7 @@ func TestCompileSSHPolicy_UserMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: user2Principal,
|
||||
SSHUsers: map[string]string{"*": "=", "root": "root", "ubuntu": "ubuntu", "admin": "admin"},
|
||||
SSHUsers: map[string]string{"0": "", "*": "=", "root": "root", "ubuntu": "ubuntu", "admin": "admin"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -715,7 +715,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.2"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -725,7 +725,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.3"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -735,12 +735,12 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.4"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.5"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -765,7 +765,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.2"}},
|
||||
SSHUsers: map[string]string{"root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "root"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -775,7 +775,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.3"}},
|
||||
SSHUsers: map[string]string{"root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "root"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -785,12 +785,12 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.4"}},
|
||||
SSHUsers: map[string]string{"root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "root"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.5"}},
|
||||
SSHUsers: map[string]string{"root": "root"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "root"},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -815,22 +815,22 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.2"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.3"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.4"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.5"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -869,7 +869,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.10"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -913,7 +913,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.5"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -942,7 +942,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
want: &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.2"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -952,7 +952,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.3"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -962,7 +962,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.4"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
{
|
||||
@@ -972,7 +972,7 @@ func TestCompileSSHPolicy_LocalpartMapping(t *testing.T) {
|
||||
},
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.5"}},
|
||||
SSHUsers: map[string]string{"root": ""},
|
||||
SSHUsers: map[string]string{"0": "", "root": ""},
|
||||
Action: acceptAction,
|
||||
},
|
||||
}},
|
||||
@@ -1057,6 +1057,7 @@ func TestCompileSSHPolicy_CheckAction(t *testing.T) {
|
||||
|
||||
// Verify SSH users are correctly mapped
|
||||
expectedUsers := map[string]string{
|
||||
"0": "",
|
||||
"ssh-it-user": "ssh-it-user",
|
||||
"root": "",
|
||||
}
|
||||
@@ -1201,7 +1202,7 @@ func TestSSHIntegrationReproduction(t *testing.T) {
|
||||
want := &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.2"}},
|
||||
SSHUsers: map[string]string{"root": "", "ssh-it-user": "ssh-it-user"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "", "ssh-it-user": "ssh-it-user"},
|
||||
Action: &tailcfg.SSHAction{
|
||||
Accept: true,
|
||||
AllowAgentForwarding: true,
|
||||
@@ -1252,7 +1253,7 @@ func TestSSHJSONSerialization(t *testing.T) {
|
||||
want := &tailcfg.SSHPolicy{Rules: []*tailcfg.SSHRule{
|
||||
{
|
||||
Principals: []*tailcfg.SSHPrincipal{{NodeIP: "100.64.0.1"}},
|
||||
SSHUsers: map[string]string{"root": "", "ssh-it-user": "ssh-it-user", "ubuntu": "ubuntu", "admin": "admin"},
|
||||
SSHUsers: map[string]string{"0": "", "root": "", "ssh-it-user": "ssh-it-user", "ubuntu": "ubuntu", "admin": "admin"},
|
||||
Action: &tailcfg.SSHAction{
|
||||
Accept: true,
|
||||
AllowAgentForwarding: true,
|
||||
|
||||
@@ -639,18 +639,22 @@ func sshNodesShareUser(srcNode, dstNode types.NodeView) bool {
|
||||
// sshRuleAllowsUser reports whether rule lets src log in as localUser,
|
||||
// mirroring the SSHUsers maps compileSSHPolicy emits: root when listed,
|
||||
// other users via autogroup:nonroot or a literal name, and anyone, root
|
||||
// included, whose name is the source user's localpart.
|
||||
// included, whose name is the source user's localpart. Login "0" only the
|
||||
// last way: the rule that maps root maps it to no local user.
|
||||
func (pm *PolicyManager) sshRuleAllowsUser(rule SSH, srcNode types.NodeView, localUser string) bool {
|
||||
isRoot := localUser == "root"
|
||||
|
||||
switch {
|
||||
case localUser == "":
|
||||
return false
|
||||
case localUser == uidZeroLogin:
|
||||
case isRoot && rule.Users.ContainsRoot(),
|
||||
!isRoot && rule.Users.ContainsNonRoot(),
|
||||
!isRoot && slices.Contains(rule.Users.NormalUsers(), SSHUser(localUser)):
|
||||
return true
|
||||
case srcNode.IsTagged() || !srcNode.User().Valid() || !rule.Users.ContainsLocalpart():
|
||||
}
|
||||
|
||||
if srcNode.IsTagged() || !srcNode.User().Valid() || !rule.Users.ContainsLocalpart() {
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -41,38 +41,6 @@ var policySequenceSkipReasons = map[string]string{
|
||||
"policy-generation/G3/rt-b/filter": viaRuleMerged,
|
||||
"policy-generation/G4/rt-b/filter": viaRuleMerged,
|
||||
"policy-generation/G5/rt-b/filter": viaRuleMerged,
|
||||
|
||||
"policy-generation/G0/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G0/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G0/thor-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G1/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G1/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G1/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G1/thor-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G2/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G2/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G2/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G2/thor-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G2/thor-2/uid-zero": sshUIDZero,
|
||||
"policy-generation/G3/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G3/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G3/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G3/thor-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G3/thor-2/uid-zero": sshUIDZero,
|
||||
"policy-generation/G4/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G4/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G4/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G4/thor-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G4/thor-2/uid-zero": sshUIDZero,
|
||||
"policy-generation/G5/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G5/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G5/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G5/thor-2/uid-zero": sshUIDZero,
|
||||
"policy-generation/G7/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G7/odin-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G7/srv/uid-zero": sshUIDZero,
|
||||
"policy-generation/G8/freya-1/uid-zero": sshUIDZero,
|
||||
"policy-generation/G8/odin-1/uid-zero": sshUIDZero,
|
||||
}
|
||||
|
||||
const viaRuleMerged = "VIA_RULE_MERGED: on a via router headscale merges the via grant's filter rule with an ACL rule of the same sources; SaaS sends the two apart"
|
||||
@@ -82,6 +50,7 @@ const viaRuleMerged = "VIA_RULE_MERGED: on a via router headscale merges the via
|
||||
var policySequenceNotCompared = []string{
|
||||
"policy-generation/G7/rt-plain/filter",
|
||||
"policy-generation/G7/srv/ssh-rules",
|
||||
"policy-generation/G7/srv/uid-zero",
|
||||
"policy-generation/G7/srv/filter",
|
||||
"policy-generation/G7/rt-b/filter",
|
||||
}
|
||||
|
||||
@@ -127,143 +127,8 @@ func TestSSHForwardingDataCompat(t *testing.T) {
|
||||
var sshSkipReasons = map[string]string{
|
||||
"ssh-b5": "headscale has no passkey authentication; user:*@passkey wildcard unsupported",
|
||||
"ssh-d10": "headscale has no passkey authentication; user:*@passkey wildcard unsupported",
|
||||
|
||||
"ssh-a1/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-a1/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-a1/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-a2/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-a2/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-a2/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-a3/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-a3/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-a3/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-a4/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-a5/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-a6/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-a6/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-a6/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-a7/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-a7/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-a7/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-a8/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-a8/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-a8/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-acceptenv-null/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-acceptenv-omitted/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-b1/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-b2/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-b3/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-b6/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-c1/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-c1/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-c1/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-c2/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-c3/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-c4/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-c4/kakuna/uid-zero": sshUIDZero,
|
||||
"ssh-checkperiod-null/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d11/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d11/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d11/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d11/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d12/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d12/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d12/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d12/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d2/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d2/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d2/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d2/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d3/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d3/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d3/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d3/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d4/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d4/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d4/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d4/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d5/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d5/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d5/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d5/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d6/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d6/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d6/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d6/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d7/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-d7/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d7/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d7/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d8/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d8/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d8/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-d9/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-d9/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-d9/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-dst-trailing-whitespace/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-duplicate-rules/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-duplicate-rules/kakuna/uid-zero": sshUIDZero,
|
||||
"ssh-e5/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-e5/kakuna/uid-zero": sshUIDZero,
|
||||
"ssh-e6/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-e6/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-e6/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-e6/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-equal-src-aliases/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-f1/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-f1/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-f1/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-f1/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-f2/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-f3/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-f3/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-f3/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-f3/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-f4/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-f4/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-f4/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-f4/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-f5/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-f5/bulbasaur/uid-zero": sshUIDZero,
|
||||
"ssh-f5/ivysaur/uid-zero": sshUIDZero,
|
||||
"ssh-f5/venusaur/uid-zero": sshUIDZero,
|
||||
"ssh-g1/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-g2/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-acceptenv-bad-glob/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-acceptenv-glob-shapes/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-action-whitespace/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-checkperiod-exact-max/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-checkperiod-too-short/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-checkperiod-zero/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-autogroup-internet/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-autogroup-member/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-autogroup-self/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-autogroup-tagged/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-group-prefix/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-localpart-empty/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-localpart-no-at/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-localpart-no-domain/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-localpart-no-glob/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-tag-prefix/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-malformed-user-whitespace-leading/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-many-tags-as-dst/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-many-tags-as-dst/kakuna/uid-zero": sshUIDZero,
|
||||
"ssh-multi-rule-accept-then-check/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-multi-rule-check-then-accept/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-multi-rule-disjoint-srcs/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-multi-rule-overlap-conflicting-acceptenv/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-multi-rule-overlap-different-users/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-multi-src-multi-dst-multi-user/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-multi-src-multi-dst-multi-user/kakuna/uid-zero": sshUIDZero,
|
||||
"ssh-src-autogroup-tagged-no-tagged-nodes/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-src-leading-whitespace/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-tab-in-user/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-unicode-emoji-user/beedrill/uid-zero": sshUIDZero,
|
||||
"ssh-unicode-rtl-user/beedrill/uid-zero": sshUIDZero,
|
||||
}
|
||||
|
||||
const sshUIDZero = `SSH_UID_ZERO: SaaS maps login "0" to no local user in every rule; headscale leaves it out`
|
||||
|
||||
// sshRejectSkipReasons documents captures the upstream control plane
|
||||
// rejects for reasons headscale cannot apply, keyed <test_id> for the whole
|
||||
// capture or <test_id>/<check>. Each entry names the feature gap.
|
||||
@@ -514,7 +379,7 @@ func withoutUIDZero(capture testcapture.Node) testcapture.Node {
|
||||
rules := make([]*tailcfg.SSHRule, 0, len(capture.SSHRules))
|
||||
|
||||
for _, r := range capture.SSHRules {
|
||||
if local, ok := r.SSHUsers["0"]; ok && local == "" {
|
||||
if mapsUIDZero(r) {
|
||||
c := *r
|
||||
c.SSHUsers = maps.Clone(r.SSHUsers)
|
||||
delete(c.SSHUsers, "0")
|
||||
@@ -529,6 +394,13 @@ func withoutUIDZero(capture testcapture.Node) testcapture.Node {
|
||||
return capture
|
||||
}
|
||||
|
||||
// mapsUIDZero reports whether r maps login "0" to no local user.
|
||||
func mapsUIDZero(r *tailcfg.SSHRule) bool {
|
||||
local, ok := r.SSHUsers["0"]
|
||||
|
||||
return ok && local == ""
|
||||
}
|
||||
|
||||
// uidZeroMismatch compares, rule by rule, which rules map login "0" to no
|
||||
// local user; "" is a match.
|
||||
func uidZeroMismatch(pm *PolicyManager, node *types.Node, capture testcapture.Node) string {
|
||||
@@ -540,8 +412,7 @@ func uidZeroMismatch(pm *PolicyManager, node *types.Node, capture testcapture.No
|
||||
uidZero := func(rules []*tailcfg.SSHRule) []bool {
|
||||
out := make([]bool, 0, len(rules))
|
||||
for _, r := range rules {
|
||||
local, ok := r.SSHUsers["0"]
|
||||
out = append(out, ok && local == "")
|
||||
out = append(out, mapsUIDZero(r))
|
||||
}
|
||||
|
||||
return out
|
||||
@@ -679,22 +550,25 @@ func TestSSHCheckParamsMatchesCaptures(t *testing.T) {
|
||||
|
||||
// TestSSHCheckParamsMatchesCompiledRules pins SSHCheckParams to headscale's
|
||||
// own compiled rules for shapes the captures lack, such as a user whose
|
||||
// email localpart is root.
|
||||
// email localpart is root or "0".
|
||||
func TestSSHCheckParamsMatchesCompiledRules(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
users := types.Users{
|
||||
{Name: "root", Email: "root@example.com"},
|
||||
{Name: "alice", Email: "alice@example.com"},
|
||||
{Name: "zero", Email: "0@example.com"},
|
||||
}
|
||||
users[0].ID = 1
|
||||
users[1].ID = 2
|
||||
users[2].ID = 3
|
||||
|
||||
nodes := types.Nodes{
|
||||
node("root-1", "100.64.0.1", "fd7a:115c:a1e0::1", users[0]),
|
||||
node("root-2", "100.64.0.2", "fd7a:115c:a1e0::2", users[0]),
|
||||
node("alice-1", "100.64.0.3", "fd7a:115c:a1e0::3", users[1]),
|
||||
node("server", "100.64.0.4", "fd7a:115c:a1e0::4", users[1]),
|
||||
node("zero-1", "100.64.0.5", "fd7a:115c:a1e0::5", users[2]),
|
||||
}
|
||||
for i, n := range nodes {
|
||||
n.ID = types.NodeID(i + 1) //nolint:gosec
|
||||
@@ -715,6 +589,7 @@ func TestSSHCheckParamsMatchesCompiledRules(t *testing.T) {
|
||||
"localpart on self": {check("autogroup:self", "localpart:*@example.com")},
|
||||
"localpart then root": {check("tag:server", "localpart:*@example.com"), check("autogroup:self", "root")},
|
||||
"nonroot and literal on self": {check("autogroup:self", "autogroup:nonroot", "deploy")},
|
||||
"literal 0 on self": {check("autogroup:self", "0", "autogroup:nonroot")},
|
||||
} {
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
@@ -206,9 +206,7 @@ var userTokenSkipReasons = map[string]string{
|
||||
"users-bare-at-test-src/no-email-two/user-list": bareAtNoEmail,
|
||||
"users-bare-at-test-src/okta-name-only/verdict": bareAtNoEmail,
|
||||
|
||||
"users-short-name/server/filter": shortUsername,
|
||||
"users-unknown-member/prod/ssh": sshUIDZero,
|
||||
"users-unknown-member/server/ssh": sshUIDZero,
|
||||
"users-short-name/server/filter": shortUsername,
|
||||
}
|
||||
|
||||
const (
|
||||
|
||||
@@ -470,7 +470,7 @@ func checkScaleSSH(
|
||||
require.True(t, rule.Action.Accept)
|
||||
require.False(t, rule.Action.Reject)
|
||||
require.Empty(t, rule.Action.HoldAndDelegate)
|
||||
require.Equal(t, map[string]string{"root": "root"}, rule.SSHUsers)
|
||||
require.Equal(t, map[string]string{"0": "", "root": "root"}, rule.SSHUsers)
|
||||
|
||||
wantIPs := make(map[string]bool)
|
||||
|
||||
|
||||
Reference in new issue
Block a user