tailscale: Add tailssh server
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
//go:build with_gvisor && windows
|
||||
|
||||
package tailssh
|
||||
|
||||
import (
|
||||
"os"
|
||||
"os/user"
|
||||
"strings"
|
||||
|
||||
gliderssh "github.com/sagernet/gliderssh"
|
||||
"github.com/sagernet/sing-box/adapter"
|
||||
E "github.com/sagernet/sing/common/exceptions"
|
||||
"github.com/sagernet/tailscale/util/winutil"
|
||||
|
||||
"golang.org/x/sys/windows"
|
||||
)
|
||||
|
||||
func isPrivilegedUser() bool {
|
||||
return winutil.IsCurrentProcessElevated()
|
||||
}
|
||||
|
||||
// requestedUserMatchesProcess reports whether the ACL-mapped user is the same Windows
|
||||
// account the sing-box process runs as. Windows has no impersonation wired up, so a
|
||||
// session always runs with the process identity; this is the only case where the
|
||||
// identity it runs as equals the requested one.
|
||||
func requestedUserMatchesProcess(localUser *adapter.PlatformUser) (bool, error) {
|
||||
tokenUser, err := windows.GetCurrentProcessToken().GetTokenUser()
|
||||
if err != nil {
|
||||
return false, E.Cause(err, "query process token user")
|
||||
}
|
||||
requested, err := user.Lookup(localUser.Username)
|
||||
if err != nil {
|
||||
return false, E.Cause(err, "lookup requested user")
|
||||
}
|
||||
// On Windows os/user reports SIDs in the Uid field.
|
||||
return strings.EqualFold(tokenUser.User.Sid.String(), requested.Uid), nil
|
||||
}
|
||||
|
||||
// verifyShellIdentity refuses a spawned shell/SFTP session whose ACL-mapped user differs
|
||||
// from the process identity it would actually run as, since Windows has no impersonation.
|
||||
func verifyShellIdentity(localUser *adapter.PlatformUser) error {
|
||||
match, err := requestedUserMatchesProcess(localUser)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !match {
|
||||
return E.New("Windows SSH sessions run as the sing-box process identity; mapping to a different local user (", localUser.Username, ") requires impersonation, which is not implemented")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func systemHostKeyPath() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
func defaultPathEnv() string {
|
||||
systemRoot := os.Getenv("SystemRoot")
|
||||
return systemRoot + `\system32;` + systemRoot + `;` + systemRoot + `\System32\Wbem`
|
||||
}
|
||||
|
||||
func userSocketDirectories(localUser *adapter.PlatformUser) []string {
|
||||
return []string{localUser.HomeDir, os.TempDir()}
|
||||
}
|
||||
|
||||
// prepareAgentSocket is a no-op on Windows: shells run as the server identity, so
|
||||
// the agent socket needs no ownership change.
|
||||
func prepareAgentSocket(_ string, _, _ int) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func platformEnvironment(localUser *adapter.PlatformUser) []string {
|
||||
var env []string
|
||||
env = append(env, "USERPROFILE="+localUser.HomeDir)
|
||||
drive, path, found := strings.Cut(localUser.HomeDir, `\`)
|
||||
if found && len(drive) == 2 && drive[1] == ':' {
|
||||
env = append(env, "HOMEDRIVE="+drive)
|
||||
env = append(env, `HOMEPATH=\`+path)
|
||||
}
|
||||
env = append(env, "SYSTEMROOT="+os.Getenv("SystemRoot"))
|
||||
return env
|
||||
}
|
||||
|
||||
func sftpCommand(sftpPath string) string {
|
||||
return sftpPath
|
||||
}
|
||||
|
||||
func sshSignalToSyscall(sig gliderssh.Signal) int {
|
||||
switch sig {
|
||||
case gliderssh.SIGINT:
|
||||
return 2
|
||||
case gliderssh.SIGTERM:
|
||||
return 15
|
||||
case gliderssh.SIGKILL:
|
||||
return 9
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user