diff --git a/ssh/tailssh/tailssh.go b/ssh/tailssh/tailssh.go index 53c322895..a4859196e 100644 --- a/ssh/tailssh/tailssh.go +++ b/ssh/tailssh/tailssh.go @@ -624,10 +624,14 @@ func matchRule(r *tailcfg.SSHRule, ci *sshConnInfo) (a *tailcfg.SSHAction, local } func mapLocalUser(ruleSSHUsers map[string]string, reqSSHUser string) (localUser string) { - if v, ok := ruleSSHUsers[reqSSHUser]; ok { - return v + v, ok := ruleSSHUsers[reqSSHUser] + if !ok { + v = ruleSSHUsers["*"] + } + if v == "=" { + return reqSSHUser } - return ruleSSHUsers["*"] + return v } func matchesPrincipal(ps []*tailcfg.SSHPrincipal, ci *sshConnInfo) bool { diff --git a/ssh/tailssh/tailssh_test.go b/ssh/tailssh/tailssh_test.go index 84bf5b772..9a442f7fd 100644 --- a/ssh/tailssh/tailssh_test.go +++ b/ssh/tailssh/tailssh_test.go @@ -153,6 +153,18 @@ func TestMatchRule(t *testing.T) { ci: &sshConnInfo{uprof: &tailcfg.UserProfile{LoginName: "foo@bar.com"}}, wantUser: "ubuntu", }, + { + name: "ssh-user-equal", + rule: &tailcfg.SSHRule{ + Action: someAction, + Principals: []*tailcfg.SSHPrincipal{{Any: true}}, + SSHUsers: map[string]string{ + "*": "=", + }, + }, + ci: &sshConnInfo{sshUser: "alice"}, + wantUser: "alice", + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { diff --git a/tailcfg/tailcfg.go b/tailcfg/tailcfg.go index 4273f79c6..adb17a103 100644 --- a/tailcfg/tailcfg.go +++ b/tailcfg/tailcfg.go @@ -1573,6 +1573,8 @@ type SSHRule struct { // actual user that's logged in. // If the map value is the empty string (for either the // requested SSH user or "*"), the rule doesn't match. + // If the map value is "=", it means the ssh-user should map + // directly to the local-user. // It may be nil if the Action is reject. SSHUsers map[string]string `json:"sshUsers"`