Skip to content

Commit

Permalink
ssh/tailssh: add support for unix sockets
Browse files Browse the repository at this point in the history
Updates tailscale#6232
Signed-off-by: Samuel Corsi-House <chouse.samuel@gmail.com>
  • Loading branch information
Xenfo committed May 10, 2024
1 parent 5708fc0 commit 3db0e28
Show file tree
Hide file tree
Showing 13 changed files with 663 additions and 35 deletions.
44 changes: 40 additions & 4 deletions ssh/tailssh/tailssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -440,7 +440,8 @@ func (srv *server) newConn() (*conn, error) {
c := &conn{srv: srv}
now := srv.now()
c.connID = fmt.Sprintf("ssh-conn-%s-%02x", now.UTC().Format("20060102T150405"), randBytes(5))
fwdHandler := &ssh.ForwardedTCPHandler{}
fwdHandlerTCP := &ssh.ForwardedTCPHandler{}
fwdHandlerUnix := &ssh.ForwardedUnixHandler{}
c.Server = &ssh.Server{
Version: "Tailscale",
ServerConfigCallback: c.ServerConfig,
Expand All @@ -452,18 +453,23 @@ func (srv *server) newConn() (*conn, error) {
Handler: c.handleSessionPostSSHAuth,
LocalPortForwardingCallback: c.mayForwardLocalPortTo,
ReversePortForwardingCallback: c.mayReversePortForwardTo,
LocalUnixForwardingCallback: c.mayForwardLocalUnixTo,
ReverseUnixForwardingCallback: c.mayReverseUnixForwardTo,
SubsystemHandlers: map[string]ssh.SubsystemHandler{
"sftp": c.handleSessionPostSSHAuth,
},
// Note: the direct-tcpip channel handler and LocalPortForwardingCallback
// only adds support for forwarding ports from the local machine.
// TODO(maisem/bradfitz): add remote port forwarding support.
ChannelHandlers: map[string]ssh.ChannelHandler{
"direct-tcpip": ssh.DirectTCPIPHandler,
"direct-tcpip": ssh.DirectTCPIPHandler,
"direct-streamlocal@openssh.com": ssh.DirectStreamLocalHandler,
},
RequestHandlers: map[string]ssh.RequestHandler{
"tcpip-forward": fwdHandler.HandleSSHRequest,
"cancel-tcpip-forward": fwdHandler.HandleSSHRequest,
"tcpip-forward": fwdHandlerTCP.HandleSSHRequest,
"cancel-tcpip-forward": fwdHandlerTCP.HandleSSHRequest,
"streamlocal-forward@openssh.com": fwdHandlerUnix.HandleSSHRequest,
"cancel-streamlocal-forward@openssh.com": fwdHandlerUnix.HandleSSHRequest,
},
}
ss := c.Server
Expand Down Expand Up @@ -514,6 +520,34 @@ func (c *conn) mayForwardLocalPortTo(ctx ssh.Context, destinationHost string, de
return false
}

// mayReverseUnixForwardTo reports whether the ctx should be allowed to unix forward
// to the specified host.
func (c *conn) mayReverseUnixForwardTo(ctx ssh.Context, socketPath string) bool {
if sshDisableForwarding() {
return false
}
if c.finalAction != nil && c.finalAction.AllowRemoteUnixForwarding {
metricRemoteUnixForward.Add(1)
return true
}
// TODO(Xenfo): undo
return true
}

// mayForwardLocalUnixTo reports whether the ctx should be allowed to unix forward
// to the specified host.
func (c *conn) mayForwardLocalUnixTo(ctx ssh.Context, socketPath string) bool {
if sshDisableForwarding() {
return false
}
if c.finalAction != nil && c.finalAction.AllowLocalUnixForwarding {
metricLocalUnixForward.Add(1)
return true
}
// TODO(Xenfo): undo
return true
}

// havePubKeyPolicy reports whether any policy rule may provide access by means
// of a ssh.PublicKey.
func (c *conn) havePubKeyPolicy() bool {
Expand Down Expand Up @@ -1928,6 +1962,8 @@ var (
metricSFTP = clientmetric.NewCounter("ssh_sftp_sessions")
metricLocalPortForward = clientmetric.NewCounter("ssh_local_port_forward_requests")
metricRemotePortForward = clientmetric.NewCounter("ssh_remote_port_forward_requests")
metricLocalUnixForward = clientmetric.NewCounter("ssh_local_unix_forward_requests")
metricRemoteUnixForward = clientmetric.NewCounter("ssh_remote_unix_forward_requests")
)

// userVisibleError is a wrapper around an error that implements
Expand Down
11 changes: 10 additions & 1 deletion tailcfg/tailcfg.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,8 @@ type CapabilityVersion int
// - 93: 2024-05-06: added support for stateful firewalling.
// - 94: 2024-05-06: Client understands Node.IsJailed.
// - 95: 2024-05-06: Client uses NodeAttrUserDialUseRoutes to change DNS dialing behavior.
const CurrentCapabilityVersion CapabilityVersion = 95
// - 96: 2023-06-08: Client understands SSHAction.AllowLocalUnixForwarding and SSHAction.AllowRemoteUnixForwarding.
const CurrentCapabilityVersion CapabilityVersion = 96

type StableID string

Expand Down Expand Up @@ -2456,6 +2457,14 @@ type SSHAction struct {
// to use remote port forwarding if requested.
AllowRemotePortForwarding bool `json:"allowRemotePortForwarding,omitempty"`

// AllowLocalUnixForwarding, if true, allows accepted connections
// to use local unix forwarding if requested.
AllowLocalUnixForwarding bool `json:"allowLocalUnixForwarding,omitempty"`

// AllowRemoteUnixForwarding, if true, allows accepted connections
// to use remote unix forwarding if requested.
AllowRemoteUnixForwarding bool `json:"allowRemoteUnixForwarding,omitempty"`

// Recorders defines the destinations of the SSH session recorders.
// The recording will be uploaded to http://addr:port/record.
Recorders []netip.AddrPort `json:"recorders,omitempty"`
Expand Down
2 changes: 2 additions & 0 deletions tailcfg/tailcfg_clone.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions tailcfg/tailcfg_view.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion tempfork/gliderlabs/ssh/options_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ func TestPasswordAuth(t *testing.T) {

func TestPasswordAuthBadPass(t *testing.T) {
t.Parallel()
l := newLocalListener()
l := newLocalTCPListener()
srv := &Server{Handler: func(s Session) {}}
srv.SetOption(PasswordAuth(func(ctx Context, password string) bool {
return false
Expand Down
2 changes: 2 additions & 0 deletions tempfork/gliderlabs/ssh/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,9 @@ type Server struct {
PtyCallback PtyCallback // callback for allowing PTY sessions, allows all if nil
ConnCallback ConnCallback // optional callback for wrapping net.Conn before handling
LocalPortForwardingCallback LocalPortForwardingCallback // callback for allowing local port forwarding, denies all if nil
LocalUnixForwardingCallback LocalUnixForwardingCallback // callback for allowing local unix forwarding (direct-streamlocal@openssh.com), denies all if nil
ReversePortForwardingCallback ReversePortForwardingCallback // callback for allowing reverse port forwarding, denies all if nil
ReverseUnixForwardingCallback ReverseUnixForwardingCallback // callback for allowing reverse unix forwarding (streamlocal-forward@openssh.com), denies all if nil
ServerConfigCallback ServerConfigCallback // callback for configuring detailed SSH options
SessionRequestCallback SessionRequestCallback // callback for allowing or denying SSH sessions

Expand Down
4 changes: 2 additions & 2 deletions tempfork/gliderlabs/ssh/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func TestAddHostKey(t *testing.T) {
}

func TestServerShutdown(t *testing.T) {
l := newLocalListener()
l := newLocalTCPListener()
testBytes := []byte("Hello world\n")
s := &Server{
Handler: func(s Session) {
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestServerShutdown(t *testing.T) {
}

func TestServerClose(t *testing.T) {
l := newLocalListener()
l := newLocalTCPListener()
s := &Server{
Handler: func(s Session) {
time.Sleep(5 * time.Second)
Expand Down
19 changes: 15 additions & 4 deletions tempfork/gliderlabs/ssh/session_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,14 +22,25 @@ func (srv *Server) serveOnce(l net.Listener) error {
return e
}
srv.ChannelHandlers = map[string]ChannelHandler{
"session": DefaultSessionHandler,
"direct-tcpip": DirectTCPIPHandler,
"session": DefaultSessionHandler,
"direct-tcpip": DirectTCPIPHandler,
"direct-streamlocal@openssh.com": DirectStreamLocalHandler,
}

forwardedTCPHandler := &ForwardedTCPHandler{}
forwardedUnixHandler := &ForwardedUnixHandler{}
srv.RequestHandlers = map[string]RequestHandler{
"tcpip-forward": forwardedTCPHandler.HandleSSHRequest,
"cancel-tcpip-forward": forwardedTCPHandler.HandleSSHRequest,
"streamlocal-forward@openssh.com": forwardedUnixHandler.HandleSSHRequest,
"cancel-streamlocal-forward@openssh.com": forwardedUnixHandler.HandleSSHRequest,
}

srv.HandleConn(conn)
return nil
}

func newLocalListener() net.Listener {
func newLocalTCPListener() net.Listener {
l, err := net.Listen("tcp", "127.0.0.1:0")
if err != nil {
if l, err = net.Listen("tcp6", "[::1]:0"); err != nil {
Expand Down Expand Up @@ -66,7 +77,7 @@ func newClientSession(t *testing.T, addr string, config *gossh.ClientConfig) (*g
}

func newTestSession(t *testing.T, srv *Server, cfg *gossh.ClientConfig) (*gossh.Session, *gossh.Client, func()) {
l := newLocalListener()
l := newLocalTCPListener()
go srv.serveOnce(l)
return newClientSession(t, l.Addr().String(), cfg)
}
Expand Down
8 changes: 8 additions & 0 deletions tempfork/gliderlabs/ssh/ssh.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,9 +62,17 @@ type ConnCallback func(ctx Context, conn net.Conn) net.Conn
// LocalPortForwardingCallback is a hook for allowing port forwarding
type LocalPortForwardingCallback func(ctx Context, destinationHost string, destinationPort uint32) bool

// LocalUnixForwardingCallback is a hook for allowing unix forwarding
// (direct-streamlocal@openssh.com)
type LocalUnixForwardingCallback func(ctx Context, socketPath string) bool

// ReversePortForwardingCallback is a hook for allowing reverse port forwarding
type ReversePortForwardingCallback func(ctx Context, bindHost string, bindPort uint32) bool

// ReverseUnixForwardingCallback is a hook for allowing reverse unix forwarding
// (streamlocal-forward@openssh.com).
type ReverseUnixForwardingCallback func(ctx Context, socketPath string) bool

// ServerConfigCallback is a hook for creating custom default server configs
type ServerConfigCallback func(ctx Context) *gossh.ServerConfig

Expand Down

0 comments on commit 3db0e28

Please sign in to comment.