session.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. package sshd
  2. import (
  3. "log/slog"
  4. "strings"
  5. "github.com/slackhq/nebula/commands"
  6. "golang.org/x/crypto/ssh"
  7. "golang.org/x/term"
  8. )
  9. type session struct {
  10. l *slog.Logger
  11. c *ssh.ServerConn
  12. term *term.Terminal
  13. commands *commands.Registry
  14. cancel func()
  15. }
  16. func NewSession(reg *commands.Registry, conn *ssh.ServerConn, chans <-chan ssh.NewChannel, cancel func(), l *slog.Logger) *session {
  17. s := &session{
  18. commands: reg.Clone(),
  19. l: l,
  20. c: conn,
  21. cancel: cancel,
  22. }
  23. s.commands.Register(&commands.Command{
  24. Name: commands.Logout,
  25. ShortDescription: "Ends the current session",
  26. Callback: func(a any, args []string, w commands.StringWriter) error {
  27. s.Close()
  28. return nil
  29. },
  30. })
  31. go s.handleChannels(chans)
  32. return s
  33. }
  34. func (s *session) handleChannels(chans <-chan ssh.NewChannel) {
  35. defer s.Close()
  36. for newChannel := range chans {
  37. if newChannel.ChannelType() != "session" {
  38. s.l.Error("unknown channel type", "sshChannelType", newChannel.ChannelType())
  39. newChannel.Reject(ssh.UnknownChannelType, "unknown channel type")
  40. continue
  41. }
  42. channel, requests, err := newChannel.Accept()
  43. if err != nil {
  44. s.l.Warn("could not accept channel", "error", err)
  45. continue
  46. }
  47. go s.handleRequests(requests, channel)
  48. }
  49. }
  50. func (s *session) handleRequests(in <-chan *ssh.Request, channel ssh.Channel) {
  51. for req := range in {
  52. var err error
  53. switch req.Type {
  54. case "shell":
  55. if s.term == nil {
  56. s.term = s.createTerm(channel)
  57. err = req.Reply(true, nil)
  58. } else {
  59. err = req.Reply(false, nil)
  60. }
  61. case "pty-req":
  62. err = req.Reply(true, nil)
  63. case "window-change":
  64. err = req.Reply(true, nil)
  65. case "exec":
  66. var payload = struct{ Value string }{}
  67. cErr := ssh.Unmarshal(req.Payload, &payload)
  68. if cErr != nil {
  69. req.Reply(false, nil)
  70. return
  71. }
  72. req.Reply(true, nil)
  73. s.dispatchCommand(payload.Value, commands.NewStringWriter(channel))
  74. status := struct{ Status uint32 }{uint32(0)}
  75. channel.SendRequest("exit-status", false, ssh.Marshal(status))
  76. channel.Close()
  77. return
  78. default:
  79. s.l.Debug("Rejected unknown request", "sshRequest", req.Type)
  80. err = req.Reply(false, nil)
  81. }
  82. if err != nil {
  83. s.l.Info("Error handling ssh session requests", "error", err)
  84. return
  85. }
  86. }
  87. }
  88. func (s *session) createTerm(channel ssh.Channel) *term.Terminal {
  89. term := term.NewTerminal(channel, s.c.User()+"@nebula > ")
  90. term.AutoCompleteCallback = func(line string, pos int, key rune) (newLine string, newPos int, ok bool) {
  91. // key 9 is tab
  92. if key == 9 {
  93. cmds := s.commands.Match(line)
  94. if len(cmds) == 1 {
  95. return cmds[0] + " ", len(cmds[0]) + 1, true
  96. }
  97. term.Write([]byte(strings.Join(cmds, "\n") + "\n\n"))
  98. }
  99. return "", 0, false
  100. }
  101. go s.handleInput()
  102. return term
  103. }
  104. func (s *session) handleInput() {
  105. w := commands.NewStringWriter(s.term)
  106. for {
  107. line, err := s.term.ReadLine()
  108. if err != nil {
  109. break
  110. }
  111. s.dispatchCommand(line, w)
  112. }
  113. }
  114. func (s *session) dispatchCommand(line string, w commands.StringWriter) {
  115. s.commands.Dispatch(line, w)
  116. }
  117. func (s *session) Close() {
  118. s.c.Close()
  119. s.cancel()
  120. }