Skip to content

Commit 7ef4474

Browse files
committed
tailscale: Add tailssh server
1 parent 0b71e5a commit 7ef4474

31 files changed

Lines changed: 2863 additions & 3 deletions

adapter/platform.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,22 @@ type PlatformInterface interface {
4444
UsePlatformNeighborResolver() bool
4545
StartNeighborMonitor(listener NeighborUpdateListener) error
4646
CloseNeighborMonitor(listener NeighborUpdateListener) error
47+
48+
UsePlatformShell() bool
49+
CheckPlatformShell() error
50+
OpenShellSession(user *PlatformUser, command string, env []string, term string, rows int32, cols int32) (ShellSession, error)
51+
LookupUser(username string) (*PlatformUser, error)
52+
LookupSFTPServer() (string, error)
53+
ReadSystemSSHHostKey() ([]byte, error)
54+
}
55+
56+
type PlatformUser struct {
57+
Username string
58+
Uid int
59+
Gid int
60+
HomeDir string
61+
Shell string
62+
Groups []int
4763
}
4864

4965
type FindConnectionOwnerRequest struct {

adapter/tailscale.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,3 +54,11 @@ type TailscalePeer struct {
5454
KeyExpiry int64
5555
LastSeen int64
5656
}
57+
58+
type ShellSession interface {
59+
MasterFD() int32
60+
Resize(rows int32, cols int32) error
61+
Signal(signal int32) error
62+
WaitExit() (int32, error)
63+
Close() error
64+
}

docs/configuration/endpoint/tailscale.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
icon: material/new-box
33
---
44

5+
!!! quote "Changes in sing-box 1.14.0"
6+
7+
:material-plus: [ssh_server](#ssh_server)
8+
59
!!! quote "Changes in sing-box 1.13.0"
610

711
:material-plus: [relay_server_port](#relay_server_port)
@@ -36,6 +40,7 @@ icon: material/new-box
3640
"system_interface_name": "",
3741
"system_interface_mtu": 0,
3842
"udp_timeout": "5m",
43+
"ssh_server": false,
3944

4045
... // Dial Fields
4146
}
@@ -148,6 +153,49 @@ UDP NAT expiration time.
148153

149154
`5m` will be used by default.
150155

156+
#### ssh_server
157+
158+
!!! question "Since sing-box 1.14.0"
159+
160+
Run a Tailscale SSH server on tailnet port 22.
161+
162+
Access is controlled by the SSH ACL in the Tailscale admin console, which maps each connection to a local user. How that user is resolved, and which users are allowed, depends on the platform:
163+
164+
- **Linux** and **macOS**: the user is resolved from the system user database. Switching to a user other than the one sing-box runs as requires running as root; without root, sessions are limited to the current user.
165+
- **Windows**: sessions run as the sing-box process identity; the mapped user is not impersonated, so a session mapped to a different local account is refused.
166+
- **Android**: the user is resolved by the app rather than the system user database. `root` is the superuser (UID 0) and `shell` is the ADB shell user (UID 2000); every other name is resolved as the package name of an installed application, running as that application's UID with its data directory as the home directory, so the target application must be installed. `termux` is a shortcut for `com.termux`, and `sing-box` for the app's own package name; when Termux is installed, the `root` and `termux` users load the Termux environment. Running as the sing-box application itself requires no root, while any other user requires granted root access; without root, sessions are limited to the sing-box user.
167+
- **macOS**: the SSH server is only available in the standalone version and requires the Root Helper; the App Store version is not supported.
168+
- **iOS** and **tvOS**: not yet supported.
169+
170+
Object format:
171+
172+
```json
173+
{
174+
"enabled": true,
175+
"disable_pty": false,
176+
"disable_sftp": false,
177+
"disable_forwarding": false
178+
}
179+
```
180+
181+
Setting `ssh_server` value to `true` is equivalent to `{ "enabled": true }`.
182+
183+
#### ssh_server.enabled
184+
185+
Enable the SSH server.
186+
187+
#### ssh_server.disable_pty
188+
189+
Refuse PTY allocation requests.
190+
191+
#### ssh_server.disable_sftp
192+
193+
Refuse the SFTP subsystem.
194+
195+
#### ssh_server.disable_forwarding
196+
197+
Refuse local and remote TCP and Unix-socket forwarding, including SSH agent forwarding.
198+
151199
### Dial Fields
152200

153201
!!! note

docs/configuration/endpoint/tailscale.zh.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@
22
icon: material/new-box
33
---
44

5+
!!! quote "sing-box 1.14.0 中的更改"
6+
7+
:material-plus: [ssh_server](#ssh_server)
8+
59
!!! quote "sing-box 1.13.0 中的更改"
610

711
:material-plus: [relay_server_port](#relay_server_port)
@@ -36,6 +40,7 @@ icon: material/new-box
3640
"system_interface_name": "",
3741
"system_interface_mtu": 0,
3842
"udp_timeout": "5m",
43+
"ssh_server": false,
3944

4045
... // 拨号字段
4146
}
@@ -147,6 +152,49 @@ UDP NAT 过期时间。
147152

148153
默认使用 `5m`
149154

155+
#### ssh_server
156+
157+
!!! question "自 sing-box 1.14.0 起"
158+
159+
在 tailnet 的 TCP 22 端口上运行 Tailscale SSH 服务器。
160+
161+
访问控制由 Tailscale 管理控制台中的 SSH ACL 决定,它将每个连接映射到一个本地用户。该用户如何解析、以及允许哪些用户,取决于平台:
162+
163+
- **Linux****macOS**:从系统用户数据库解析用户。要切换到 sing-box 运行身份以外的用户需要以 root 运行;非 root 时,会话仅限于当前用户。
164+
- **Windows**:会话以 sing-box 进程的身份运行;映射的用户不会被模拟,因此映射到其他本地账户的会话将被拒绝。
165+
- **Android**:用户由应用解析,而非系统用户数据库。`root` 即超级用户(UID 0),`shell` 为 ADB shell 用户(UID 2000);其他名称均作为已安装应用的包名解析,以该应用的 UID 运行,并使用其数据目录作为主目录,因此目标应用必须已安装。`termux``com.termux` 的快捷方式,`sing-box` 是应用自身包名的快捷方式;当 Termux 已安装时,`root``termux` 用户将加载 Termux 环境。以 sing-box 应用自身身份运行无需 root,其他用户则需要已授予的 root 权限;非 root 时,会话仅限于 sing-box 用户。
166+
- **macOS**:SSH 服务器仅在独立版本中可用,且需要 Root Helper;App Store 版本不支持。
167+
- **iOS****tvOS**:暂不支持。
168+
169+
对象格式:
170+
171+
```json
172+
{
173+
"enabled": true,
174+
"disable_pty": false,
175+
"disable_sftp": false,
176+
"disable_forwarding": false
177+
}
178+
```
179+
180+
`ssh_server` 值设置为 `true` 等同于 `{ "enabled": true }`
181+
182+
#### ssh_server.enabled
183+
184+
启用 SSH 服务器。
185+
186+
#### ssh_server.disable_pty
187+
188+
拒绝 PTY 分配请求。
189+
190+
#### ssh_server.disable_sftp
191+
192+
拒绝 SFTP 子系统。
193+
194+
#### ssh_server.disable_forwarding
195+
196+
拒绝本地和远程的 TCP 与 Unix 套接字转发,包括 SSH agent 转发。
197+
150198
### 拨号字段
151199

152200
!!! note

experimental/libbox/config.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -167,6 +167,30 @@ func (s *platformInterfaceStub) CloseNeighborMonitor(listener adapter.NeighborUp
167167
return nil
168168
}
169169

170+
func (s *platformInterfaceStub) UsePlatformShell() bool {
171+
return false
172+
}
173+
174+
func (s *platformInterfaceStub) CheckPlatformShell() error {
175+
return nil
176+
}
177+
178+
func (s *platformInterfaceStub) OpenShellSession(user *adapter.PlatformUser, command string, env []string, term string, rows int32, cols int32) (adapter.ShellSession, error) {
179+
return nil, os.ErrInvalid
180+
}
181+
182+
func (s *platformInterfaceStub) LookupSFTPServer() (string, error) {
183+
return "", os.ErrInvalid
184+
}
185+
186+
func (s *platformInterfaceStub) ReadSystemSSHHostKey() ([]byte, error) {
187+
return nil, os.ErrInvalid
188+
}
189+
190+
func (s *platformInterfaceStub) LookupUser(username string) (*adapter.PlatformUser, error) {
191+
return nil, os.ErrInvalid
192+
}
193+
170194
func (s *platformInterfaceStub) UsePlatformLocalDNSTransport() bool {
171195
return false
172196
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
//go:build linux || android || (darwin && !ios)
2+
3+
package libbox
4+
5+
import (
6+
"github.com/sagernet/sing-box/protocol/tailscale/tailssh"
7+
"github.com/sagernet/sing/common"
8+
)
9+
10+
type nativeShellSession struct {
11+
shell *tailssh.Shell
12+
}
13+
14+
func OpenNativeShellSession(
15+
shell, cwd string,
16+
args, environ StringIterator,
17+
term string,
18+
rows, cols, uid, gid int32,
19+
groups Int32Iterator,
20+
) (ShellSession, error) {
21+
sh, err := tailssh.OpenPtyShell(
22+
shell,
23+
iteratorToArray[string](args),
24+
iteratorToArray[string](environ),
25+
cwd,
26+
int(uid), int(gid),
27+
common.Map(iteratorToArray[int32](groups), func(g int32) int { return int(g) }),
28+
uint16(rows), uint16(cols),
29+
)
30+
if err != nil {
31+
return nil, err
32+
}
33+
return &nativeShellSession{shell: sh}, nil
34+
}
35+
36+
func OpenNativePipeSession(
37+
shell, cwd string,
38+
args, environ StringIterator,
39+
uid, gid int32,
40+
groups Int32Iterator,
41+
) (ShellSession, error) {
42+
sh, err := tailssh.OpenSocketpairShell(
43+
shell,
44+
iteratorToArray[string](args),
45+
iteratorToArray[string](environ),
46+
cwd,
47+
int(uid), int(gid),
48+
common.Map(iteratorToArray[int32](groups), func(g int32) int { return int(g) }),
49+
)
50+
if err != nil {
51+
return nil, err
52+
}
53+
return &nativeShellSession{shell: sh}, nil
54+
}
55+
56+
func (s *nativeShellSession) MasterFD() int32 {
57+
return int32(s.shell.MasterFD())
58+
}
59+
60+
func (s *nativeShellSession) Resize(rows, cols int32) error {
61+
return s.shell.Resize(uint16(rows), uint16(cols))
62+
}
63+
64+
func (s *nativeShellSession) Signal(sig int32) error {
65+
return s.shell.Signal(int(sig))
66+
}
67+
68+
func (s *nativeShellSession) WaitExit() (int32, error) {
69+
status, err := s.shell.Wait()
70+
if err != nil {
71+
return 0, err
72+
}
73+
return int32(status), nil
74+
}
75+
76+
func (s *nativeShellSession) Close() error {
77+
return s.shell.Close()
78+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
//go:build !linux && !android && (!darwin || ios)
2+
3+
package libbox
4+
5+
import (
6+
E "github.com/sagernet/sing/common/exceptions"
7+
)
8+
9+
func OpenNativeShellSession(
10+
shell, cwd string,
11+
args, environ StringIterator,
12+
term string,
13+
rows, cols, uid, gid int32,
14+
groups Int32Iterator,
15+
) (ShellSession, error) {
16+
return nil, E.New("native shell session not supported on this platform")
17+
}
18+
19+
func OpenNativePipeSession(
20+
shell, cwd string,
21+
args, environ StringIterator,
22+
uid, gid int32,
23+
groups Int32Iterator,
24+
) (ShellSession, error) {
25+
return nil, E.New("native pipe session not supported on this platform")
26+
}

experimental/libbox/platform.go

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,30 @@ type PlatformInterface interface {
2121
StartNeighborMonitor(listener NeighborUpdateListener) error
2222
CloseNeighborMonitor(listener NeighborUpdateListener) error
2323
RegisterMyInterface(name string)
24+
UsePlatformShell() bool
25+
CheckPlatformShell() error
26+
OpenShellSession(user *PlatformUser, command string, environ StringIterator, term string, rows int32, cols int32) (ShellSession, error)
27+
LookupUser(username string) (*PlatformUser, error)
28+
LookupSFTPServer() (*StringBox, error)
29+
ReadSystemSSHHostKey() (*StringBox, error)
30+
}
31+
32+
type PlatformUser struct {
33+
Username string
34+
Uid int32
35+
Gid int32
36+
HomeDir string
37+
Shell string
38+
39+
groups []int32
40+
}
41+
42+
func (u *PlatformUser) SetGroups(groups Int32Iterator) {
43+
u.groups = iteratorToArray[int32](groups)
44+
}
45+
46+
func (u *PlatformUser) Groups() Int32Iterator {
47+
return newIterator(u.groups)
2448
}
2549

2650
type NeighborUpdateListener interface {

0 commit comments

Comments
 (0)