Merge branch 'feature/permissions' into develop

This commit is contained in:
Ryo Ota 2023-08-09 20:54:10 +09:00
commit 9e54b43be9
3 changed files with 130 additions and 11 deletions

View file

@ -40,6 +40,25 @@ handy-sshd -p 2222 --user "john:" --user "alice:"
handy-sshd --unix-socket /tmp/my-unix-socket --user "john:" handy-sshd --unix-socket /tmp/my-unix-socket --user "john:"
``` ```
## Permissions
There are some permissions.
* --allow-direct-tcpip
* --allow-execute
* --allow-sftp
* --allow-tcpip-forward
All permissions are allowed when nothing is specified.
Specifying `--allow-direct-tcpip` and `--allow-execute` allows only "direct-tcpip" and command executions.
The log shows "allowed: " and "NOT allowed: " permissions as follows.
```console
$ handy-sshd --user "john:" --allow-direct-tcpip --allow-execute
2023/08/09 20:49:35 INFO listening on :2222...
2023/08/09 20:49:35 INFO allowed: "direct-tcpip", "execute"
2023/08/09 20:49:35 INFO NOT allowed: "tcpip-forward", "sftp"
```
## --help ## --help
``` ```
@ -49,6 +68,10 @@ Usage:
handy-sshd [flags] handy-sshd [flags]
Flags: Flags:
--allow-direct-tcpip client can use local forwarding and SOCKS proxy
--allow-execute client can use shell/interactive shell
--allow-sftp client can use SFTP and SSHFS
--allow-tcpip-forward client can use remote forwarding
-h, --help help for handy-sshd -h, --help help for handy-sshd
--host string SSH server host (e.g. 127.0.0.1) --host string SSH server host (e.g. 127.0.0.1)
-p, --port uint16 SSH server port (default 2222) -p, --port uint16 SSH server port (default 2222)

View file

@ -21,6 +21,21 @@ var flag struct {
sshUnixSocket string sshUnixSocket string
sshShell string sshShell string
sshUsers []string sshUsers []string
allowTcpipForward bool
allowDirectTcpip bool
allowExecute bool
allowSftp bool
}
var allPermissionFlags = []struct {
name string
flagPtr *bool
}{
{name: "tcpip-forward", flagPtr: &flag.allowTcpipForward},
{name: "direct-tcpip", flagPtr: &flag.allowDirectTcpip},
{name: "execute", flagPtr: &flag.allowExecute},
{name: "sftp", flagPtr: &flag.allowSftp},
} }
type sshUser struct { type sshUser struct {
@ -38,6 +53,12 @@ func init() {
RootCmd.PersistentFlags().StringVarP(&flag.sshShell, "shell", "", "", "Shell") RootCmd.PersistentFlags().StringVarP(&flag.sshShell, "shell", "", "", "Shell")
//RootCmd.PersistentFlags().StringVar(&flag.dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)") //RootCmd.PersistentFlags().StringVar(&flag.dnsServer, "dns-server", "", "DNS server (e.g. 1.1.1.1:53)")
RootCmd.PersistentFlags().StringArrayVarP(&flag.sshUsers, "user", "", nil, `SSH user name (e.g. "john:mypassword")`) RootCmd.PersistentFlags().StringArrayVarP(&flag.sshUsers, "user", "", nil, `SSH user name (e.g. "john:mypassword")`)
// Permission flags
RootCmd.PersistentFlags().BoolVarP(&flag.allowTcpipForward, "allow-tcpip-forward", "", false, "client can use remote forwarding")
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.allowSftp, "allow-sftp", "", false, "client can use SFTP and SSHFS")
} }
var RootCmd = &cobra.Command{ var RootCmd = &cobra.Command{
@ -51,8 +72,26 @@ var RootCmd = &cobra.Command{
return nil return nil
} }
logger := slog.Default() logger := slog.Default()
// Allow all permissions if all permission is not set
{
allPermissionFalse := true
for _, permissionFlag := range allPermissionFlags {
allPermissionFalse = allPermissionFalse && !*permissionFlag.flagPtr
}
if allPermissionFalse {
for _, permissionFlag := range allPermissionFlags {
*permissionFlag.flagPtr = true
}
}
}
sshServer := &handy_sshd.Server{ sshServer := &handy_sshd.Server{
Logger: logger, Logger: logger,
AllowTcpipForward: flag.allowTcpipForward,
AllowDirectTcpip: flag.allowDirectTcpip,
AllowExecute: flag.allowExecute,
AllowSftp: flag.allowSftp,
} }
var sshUsers []sshUser var sshUsers []sshUser
for _, u := range flag.sshUsers { for _, u := range flag.sshUsers {
@ -109,6 +148,9 @@ var RootCmd = &cobra.Command{
logger.Info(fmt.Sprintf("listening on %s...", flag.sshUnixSocket)) logger.Info(fmt.Sprintf("listening on %s...", flag.sshUnixSocket))
} }
defer ln.Close() defer ln.Close()
showPermissions(logger)
for { for {
conn, err := ln.Accept() conn, err := ln.Accept()
if err != nil { if err != nil {
@ -121,9 +163,29 @@ var RootCmd = &cobra.Command{
conn.Close() conn.Close()
continue continue
} }
logger.Info("new SSH connection", "client_version", string(sshConn.ClientVersion())) logger.Info("new SSH connection", "remote_address", sshConn.RemoteAddr(), "client_version", string(sshConn.ClientVersion()))
go sshServer.HandleGlobalRequests(sshConn, reqs) go sshServer.HandleGlobalRequests(sshConn, reqs)
go sshServer.HandleChannels(flag.sshShell, chans) go sshServer.HandleChannels(flag.sshShell, chans)
} }
}, },
} }
func showPermissions(logger *slog.Logger) {
var allowedList []string
var notAllowedList []string
for _, permissionFlag := range allPermissionFlags {
if *permissionFlag.flagPtr {
allowedList = append(allowedList, `"`+permissionFlag.name+`"`)
} else {
notAllowedList = append(notAllowedList, `"`+permissionFlag.name+`"`)
}
}
showList := func(l []string) string {
if len(l) == 0 {
return "none"
}
return strings.Join(l, ", ")
}
logger.Info(fmt.Sprintf("allowed: %s", showList(allowedList)))
logger.Info(fmt.Sprintf("NOT allowed: %s", showList(notAllowedList)))
}

View file

@ -28,6 +28,13 @@ import (
type Server struct { type Server struct {
Logger *slog.Logger Logger *slog.Logger
// Permissions
AllowTcpipForward 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.
AllowSftp bool
// TODO: DNS server ? // TODO: DNS server ?
} }
@ -47,6 +54,10 @@ func (s *Server) handleChannel(shell string, newChannel ssh.NewChannel) {
case "session": case "session":
s.handleSession(shell, newChannel) s.handleSession(shell, newChannel)
case "direct-tcpip": case "direct-tcpip":
if !s.AllowDirectTcpip {
newChannel.Reject(ssh.Prohibited, "direct-tcpip not allowed")
break
}
s.handleDirectTcpip(newChannel) s.handleDirectTcpip(newChannel)
default: default:
newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType())) newChannel.Reject(ssh.UnknownChannelType, fmt.Sprintf("unknown channel type: %s", newChannel.ChannelType()))
@ -67,6 +78,11 @@ func (s *Server) handleSession(shell string, newChannel ssh.NewChannel) {
for req := range requests { for req := range requests {
switch req.Type { switch req.Type {
case "exec": case "exec":
if !s.AllowExecute {
s.Logger.Info("execution not allowed (exec)")
req.Reply(false, nil)
break
}
s.handleExecRequest(req, connection) s.handleExecRequest(req, connection)
case "shell": case "shell":
// We only accept the default shell // We only accept the default shell
@ -75,6 +91,11 @@ func (s *Server) handleSession(shell string, newChannel ssh.NewChannel) {
req.Reply(true, nil) req.Reply(true, nil)
} }
case "pty-req": case "pty-req":
if !s.AllowExecute {
s.Logger.Info("execution not allowed (pty-req)")
req.Reply(false, nil)
break
}
termLen := req.Payload[3] termLen := req.Payload[3]
w, h := parseDims(req.Payload[termLen+4:]) w, h := parseDims(req.Payload[termLen+4:])
shf, err = s.createPty(shell, connection) shf, err = s.createPty(shell, connection)
@ -140,9 +161,17 @@ func (s *Server) handleExecRequest(req *ssh.Request, connection ssh.Channel) {
func (s *Server) handleSessionSubSystem(req *ssh.Request, connection ssh.Channel) { func (s *Server) handleSessionSubSystem(req *ssh.Request, connection ssh.Channel) {
// https://github.com/pkg/sftp/blob/42e9800606febe03f9cdf1d1283719af4a5e6456/examples/go-sftp-server/main.go#L111 // https://github.com/pkg/sftp/blob/42e9800606febe03f9cdf1d1283719af4a5e6456/examples/go-sftp-server/main.go#L111
ok := string(req.Payload[4:]) == "sftp" if string(req.Payload[4:]) != "sftp" {
req.Reply(ok, nil) req.Reply(false, nil)
return
}
if !s.AllowSftp {
s.Logger.Info("sftp not allowed")
req.Reply(false, nil)
return
}
req.Reply(true, nil)
serverOptions := []sftp.ServerOption{ serverOptions := []sftp.ServerOption{
sftp.WithDebug(os.Stderr), sftp.WithDebug(os.Stderr),
} }
@ -232,6 +261,11 @@ func (s *Server) HandleGlobalRequests(sshConn *ssh.ServerConn, reqs <-chan *ssh.
for req := range reqs { for req := range reqs {
switch req.Type { switch req.Type {
case "tcpip-forward": case "tcpip-forward":
if !s.AllowTcpipForward {
s.Logger.Info("tcpip-forward not allowed")
req.Reply(false, nil)
break
}
s.handleTcpipForward(sshConn, req) s.handleTcpipForward(sshConn, req)
default: default:
// discard // discard