ssh.go 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. package nebula
  2. import (
  3. "fmt"
  4. "log/slog"
  5. "net"
  6. "os"
  7. "slices"
  8. "strings"
  9. "github.com/slackhq/nebula/config"
  10. "github.com/slackhq/nebula/sshd"
  11. )
  12. func wireSSHReload(l *slog.Logger, ssh *sshd.SSHServer, c *config.C) {
  13. c.RegisterReloadCallback(func(c *config.C, r *config.Reload) {
  14. if c.GetBool("sshd.enabled", false) {
  15. // Restarting the server drops every open session, the one that asked for this reload included. Only restart
  16. // when it isn't running or its config changed. The host key file can change under the same config, new
  17. // handshakes use the new key without a restart
  18. if ssh.Running() && !slices.ContainsFunc(sshdServerKeys, c.HasChanged) {
  19. key, err := loadSSHHostKey(c)
  20. if err == nil {
  21. err = ssh.SetHostKey(key)
  22. }
  23. if err != nil {
  24. l.Error("Failed to reconfigure the sshd", "error", err)
  25. ssh.Stop()
  26. r.Failed()
  27. }
  28. return
  29. }
  30. if _, err := configSSH(l, ssh, c, r); err != nil {
  31. l.Error("Failed to reconfigure the sshd", "error", err)
  32. ssh.Stop()
  33. r.Failed()
  34. return
  35. }
  36. // Bind here rather than in the background, so a reload that can't listen says so
  37. listener, err := ssh.Listen(c.GetString("sshd.listen", ""))
  38. if err != nil {
  39. l.Error("Failed to run the SSH server", "error", err)
  40. r.Failed()
  41. return
  42. }
  43. go ssh.Serve(listener)
  44. } else {
  45. ssh.Stop()
  46. }
  47. })
  48. }
  49. // sshdServerKeys are what configSSH reads
  50. var sshdServerKeys = []string{"sshd.enabled", "sshd.listen", "sshd.host_key", "sshd.trusted_cas", "sshd.authorized_users"}
  51. // loadSSHHostKey returns sshd.host_key, read from its file when it names one.
  52. func loadSSHHostKey(c *config.C) ([]byte, error) {
  53. hostKeyPathOrKey := c.GetString("sshd.host_key", "")
  54. if hostKeyPathOrKey == "" {
  55. return nil, fmt.Errorf("sshd.host_key must be provided")
  56. }
  57. if strings.Contains(hostKeyPathOrKey, "-----BEGIN") {
  58. return []byte(hostKeyPathOrKey), nil
  59. }
  60. b, err := os.ReadFile(hostKeyPathOrKey)
  61. if err != nil {
  62. return nil, fmt.Errorf("error while loading sshd.host_key file: %s", err)
  63. }
  64. return b, nil
  65. }
  66. // configSSH reads the ssh info out of the passed-in Config and
  67. // updates the passed-in SSHServer. On success, it returns a function
  68. // that callers may invoke to run the configured ssh server. On
  69. // failure, it returns nil, error.
  70. // r is nil at startup, entries that are skipped with a warning fail it on a reload
  71. func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C, r *config.Reload) (func(), error) {
  72. listen := c.GetString("sshd.listen", "")
  73. if listen == "" {
  74. return nil, fmt.Errorf("sshd.listen must be provided")
  75. }
  76. _, port, err := net.SplitHostPort(listen)
  77. if err != nil {
  78. return nil, fmt.Errorf("invalid sshd.listen address: %s", err)
  79. }
  80. if port == "22" {
  81. return nil, fmt.Errorf("sshd.listen can not use port 22")
  82. }
  83. hostKeyBytes, err := loadSSHHostKey(c)
  84. if err != nil {
  85. return nil, err
  86. }
  87. err = ssh.SetHostKey(hostKeyBytes)
  88. if err != nil {
  89. return nil, fmt.Errorf("error while adding sshd.host_key: %s", err)
  90. }
  91. // Clear existing trusted CAs and authorized keys
  92. ssh.ClearTrustedCAs()
  93. ssh.ClearAuthorizedKeys()
  94. rawCAs := c.GetStringSlice("sshd.trusted_cas", []string{})
  95. for _, caAuthorizedKey := range rawCAs {
  96. err := ssh.AddTrustedCA(caAuthorizedKey)
  97. if err != nil {
  98. l.Warn("SSH CA had an error, ignoring", "error", err, "sshCA", caAuthorizedKey)
  99. r.Failed()
  100. continue
  101. }
  102. }
  103. rawKeys := c.Get("sshd.authorized_users")
  104. keys, ok := rawKeys.([]any)
  105. if ok {
  106. for _, rk := range keys {
  107. kDef, ok := rk.(map[string]any)
  108. if !ok {
  109. l.Warn("Authorized user had an error, ignoring", "sshKeyConfig", rk)
  110. r.Failed()
  111. continue
  112. }
  113. user, ok := kDef["user"].(string)
  114. if !ok {
  115. l.Warn("Authorized user is missing the user field", "sshKeyConfig", rk)
  116. r.Failed()
  117. continue
  118. }
  119. k := kDef["keys"]
  120. switch v := k.(type) {
  121. case string:
  122. err := ssh.AddAuthorizedKey(user, v)
  123. if err != nil {
  124. l.Warn("Failed to authorize key",
  125. "error", err,
  126. "sshKeyConfig", rk,
  127. "sshKey", v,
  128. )
  129. r.Failed()
  130. continue
  131. }
  132. case []any:
  133. for _, subK := range v {
  134. sk, ok := subK.(string)
  135. if !ok {
  136. l.Warn("Did not understand ssh key",
  137. "sshKeyConfig", rk,
  138. "sshKey", subK,
  139. )
  140. r.Failed()
  141. continue
  142. }
  143. err := ssh.AddAuthorizedKey(user, sk)
  144. if err != nil {
  145. l.Warn("Failed to authorize key",
  146. "error", err,
  147. "sshKeyConfig", sk,
  148. )
  149. r.Failed()
  150. continue
  151. }
  152. }
  153. default:
  154. l.Warn("Authorized user is missing the keys field or was not understood", "sshKeyConfig", rk)
  155. r.Failed()
  156. }
  157. }
  158. } else {
  159. l.Info("no ssh users to authorize")
  160. }
  161. var runner func()
  162. if c.GetBool("sshd.enabled", false) {
  163. ssh.Stop()
  164. runner = func() {
  165. if err := ssh.Run(listen); err != nil {
  166. l.Warn("Failed to run the SSH server", "error", err)
  167. }
  168. }
  169. } else {
  170. ssh.Stop()
  171. }
  172. return runner, nil
  173. }