support Unix domain socket remote forwarding

This commit is contained in:
Ryo Ota 2023-08-11 10:48:51 +09:00
parent 4b7793e445
commit 5cc6ad44c6
2 changed files with 85 additions and 17 deletions

View file

@ -22,11 +22,12 @@ type flagType struct {
sshShell string sshShell string
sshUsers []string sshUsers []string
allowTcpipForward bool allowTcpipForward bool
allowDirectTcpip bool allowDirectTcpip bool
allowExecute bool allowExecute bool
allowSftp bool allowSftp bool
allowDirectStreamlocal bool allowStreamlocalForward bool
allowDirectStreamlocal bool
} }
type permissionFlagType = struct { type permissionFlagType = struct {
@ -50,6 +51,7 @@ func RootCmd() *cobra.Command {
{name: "direct-tcpip", flagPtr: &flag.allowDirectTcpip}, {name: "direct-tcpip", flagPtr: &flag.allowDirectTcpip},
{name: "execute", flagPtr: &flag.allowExecute}, {name: "execute", flagPtr: &flag.allowExecute},
{name: "sftp", flagPtr: &flag.allowSftp}, {name: "sftp", flagPtr: &flag.allowSftp},
{name: "streamlocal-forward", flagPtr: &flag.allowStreamlocalForward},
{name: "direct-streamlocal", flagPtr: &flag.allowDirectStreamlocal}, {name: "direct-streamlocal", flagPtr: &flag.allowDirectStreamlocal},
} }
rootCmd := cobra.Command{ rootCmd := cobra.Command{
@ -76,6 +78,7 @@ func RootCmd() *cobra.Command {
rootCmd.PersistentFlags().BoolVarP(&flag.allowDirectTcpip, "allow-direct-tcpip", "", false, "client can use local forwarding and SOCKS proxy") rootCmd.PersistentFlags().BoolVarP(&flag.allowDirectTcpip, "allow-direct-tcpip", "", false, "client can use local forwarding and SOCKS proxy")
rootCmd.PersistentFlags().BoolVarP(&flag.allowExecute, "allow-execute", "", false, "client can use shell/interactive shell") rootCmd.PersistentFlags().BoolVarP(&flag.allowExecute, "allow-execute", "", false, "client can use shell/interactive shell")
rootCmd.PersistentFlags().BoolVarP(&flag.allowSftp, "allow-sftp", "", false, "client can use SFTP and SSHFS") rootCmd.PersistentFlags().BoolVarP(&flag.allowSftp, "allow-sftp", "", false, "client can use SFTP and SSHFS")
rootCmd.PersistentFlags().BoolVarP(&flag.allowStreamlocalForward, "allow-streamlocal-forward", "", false, "client can use Unix domain socket remote forwarding")
rootCmd.PersistentFlags().BoolVarP(&flag.allowDirectStreamlocal, "allow-direct-streamlocal", "", false, "client can use Unix domain socket local forwarding") rootCmd.PersistentFlags().BoolVarP(&flag.allowDirectStreamlocal, "allow-direct-streamlocal", "", false, "client can use Unix domain socket local forwarding")
return &rootCmd return &rootCmd
@ -102,12 +105,13 @@ func rootRunEWithExtra(cmd *cobra.Command, args []string, flag *flagType, allPer
} }
sshServer := &handy_sshd.Server{ sshServer := &handy_sshd.Server{
Logger: logger, Logger: logger,
AllowTcpipForward: flag.allowTcpipForward, AllowTcpipForward: flag.allowTcpipForward,
AllowDirectTcpip: flag.allowDirectTcpip, AllowDirectTcpip: flag.allowDirectTcpip,
AllowExecute: flag.allowExecute, AllowExecute: flag.allowExecute,
AllowSftp: flag.allowSftp, AllowSftp: flag.allowSftp,
AllowDirectStreamlocal: flag.allowDirectStreamlocal, AllowStreamlocalForward: flag.allowStreamlocalForward,
AllowDirectStreamlocal: flag.allowDirectStreamlocal,
} }
var sshUsers []sshUser var sshUsers []sshUser
for _, u := range flag.sshUsers { for _, u := range flag.sshUsers {

View file

@ -30,11 +30,12 @@ type Server struct {
Logger *slog.Logger Logger *slog.Logger
// Permissions // Permissions
AllowTcpipForward bool AllowTcpipForward bool
AllowDirectTcpip bool AllowDirectTcpip bool
AllowExecute bool // this should not be split into "allow-exec" and "allow-pty-req" for now because "pty-req" can be used not for shell execution. AllowExecute bool // this should not be split into "allow-exec" and "allow-pty-req" for now because "pty-req" can be used not for shell execution.
AllowSftp bool AllowSftp bool
AllowDirectStreamlocal bool AllowStreamlocalForward bool
AllowDirectStreamlocal bool
// TODO: DNS server ? // TODO: DNS server ?
} }
@ -312,7 +313,13 @@ func (s *Server) HandleGlobalRequests(sshConn *ssh.ServerConn, reqs <-chan *ssh.
break break
} }
s.handleTcpipForward(sshConn, req) s.handleTcpipForward(sshConn, req)
// TODO: support: streamlocal-forward@openssh.com https://github.com/golang/crypto/blob/master/ssh/streamlocal.go case "streamlocal-forward@openssh.com":
if !s.AllowStreamlocalForward {
s.Logger.Info("streamlocal-forward not allowed")
req.Reply(false, nil)
break
}
s.handleStreamlocalForward(sshConn, req)
default: default:
// discard // discard
if req.WantReply { if req.WantReply {
@ -347,6 +354,7 @@ func (s *Server) handleTcpipForward(sshConn *ssh.ServerConn, req *ssh.Request) {
for { for {
conn, err := ln.Accept() conn, err := ln.Accept()
if err != nil { if err != nil {
s.Logger.Info("failed to accept", "err", err)
return return
} }
var replyMsg struct { var replyMsg struct {
@ -387,3 +395,59 @@ func (s *Server) handleTcpipForward(sshConn *ssh.ServerConn, req *ssh.Request) {
}() }()
} }
} }
// client side: https://github.com/golang/crypto/blob/b4ddeeda5bc71549846db71ba23e83ecb26f36ed/ssh/streamlocal.go#L34
func (s *Server) handleStreamlocalForward(sshConn *ssh.ServerConn, req *ssh.Request) {
// https://github.com/openssh/openssh-portable/blob/f9f18006678d2eac8b0c5a5dddf17ab7c50d1e9f/PROTOCOL#L272
var msg struct {
SocketPath string
}
if err := ssh.Unmarshal(req.Payload, &msg); err != nil {
req.Reply(false, nil)
return
}
ln, err := net.Listen("unix", msg.SocketPath)
if err != nil {
req.Reply(false, nil)
return
}
req.Reply(true, nil)
go func() {
sshConn.Wait()
ln.Close()
s.Logger.Info("connection closed", "address", ln.Addr().String())
}()
for {
conn, err := ln.Accept()
if err != nil {
s.Logger.Info("failed to accept", "err", err)
return
}
// https://github.com/openssh/openssh-portable/blob/f9f18006678d2eac8b0c5a5dddf17ab7c50d1e9f/PROTOCOL#L255
var replyMsg struct {
SocketPath string
Reserved string
}
replyMsg.SocketPath = msg.SocketPath
go func() {
channel, reqs, err := sshConn.OpenChannel("forwarded-streamlocal@openssh.com", ssh.Marshal(&replyMsg))
if err != nil {
req.Reply(false, nil)
conn.Close()
return
}
go ssh.DiscardRequests(reqs)
go func() {
io.Copy(channel, conn)
conn.Close()
channel.Close()
}()
go func() {
io.Copy(conn, channel)
conn.Close()
channel.Close()
}()
}()
}
}