ssh.go 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187
  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. sshRun, err := configSSH(l, ssh, c, r)
  31. if err != nil {
  32. l.Error("Failed to reconfigure the sshd", "error", err)
  33. ssh.Stop()
  34. r.Failed()
  35. }
  36. if sshRun != nil {
  37. go sshRun()
  38. }
  39. } else {
  40. ssh.Stop()
  41. }
  42. })
  43. }
  44. // sshdServerKeys are what configSSH reads
  45. var sshdServerKeys = []string{"sshd.enabled", "sshd.listen", "sshd.host_key", "sshd.trusted_cas", "sshd.authorized_users"}
  46. // loadSSHHostKey returns sshd.host_key, read from its file when it names one.
  47. func loadSSHHostKey(c *config.C) ([]byte, error) {
  48. hostKeyPathOrKey := c.GetString("sshd.host_key", "")
  49. if hostKeyPathOrKey == "" {
  50. return nil, fmt.Errorf("sshd.host_key must be provided")
  51. }
  52. if strings.Contains(hostKeyPathOrKey, "-----BEGIN") {
  53. return []byte(hostKeyPathOrKey), nil
  54. }
  55. b, err := os.ReadFile(hostKeyPathOrKey)
  56. if err != nil {
  57. return nil, fmt.Errorf("error while loading sshd.host_key file: %s", err)
  58. }
  59. return b, nil
  60. }
  61. // configSSH reads the ssh info out of the passed-in Config and
  62. // updates the passed-in SSHServer. On success, it returns a function
  63. // that callers may invoke to run the configured ssh server. On
  64. // failure, it returns nil, error.
  65. // r is nil at startup, entries that are skipped with a warning fail it on a reload
  66. func configSSH(l *slog.Logger, ssh *sshd.SSHServer, c *config.C, r *config.Reload) (func(), error) {
  67. listen := c.GetString("sshd.listen", "")
  68. if listen == "" {
  69. return nil, fmt.Errorf("sshd.listen must be provided")
  70. }
  71. _, port, err := net.SplitHostPort(listen)
  72. if err != nil {
  73. return nil, fmt.Errorf("invalid sshd.listen address: %s", err)
  74. }
  75. if port == "22" {
  76. return nil, fmt.Errorf("sshd.listen can not use port 22")
  77. }
  78. hostKeyBytes, err := loadSSHHostKey(c)
  79. if err != nil {
  80. return nil, err
  81. }
  82. err = ssh.SetHostKey(hostKeyBytes)
  83. if err != nil {
  84. return nil, fmt.Errorf("error while adding sshd.host_key: %s", err)
  85. }
  86. // Clear existing trusted CAs and authorized keys
  87. ssh.ClearTrustedCAs()
  88. ssh.ClearAuthorizedKeys()
  89. rawCAs := c.GetStringSlice("sshd.trusted_cas", []string{})
  90. for _, caAuthorizedKey := range rawCAs {
  91. err := ssh.AddTrustedCA(caAuthorizedKey)
  92. if err != nil {
  93. l.Warn("SSH CA had an error, ignoring", "error", err, "sshCA", caAuthorizedKey)
  94. r.Failed()
  95. continue
  96. }
  97. }
  98. rawKeys := c.Get("sshd.authorized_users")
  99. keys, ok := rawKeys.([]any)
  100. if ok {
  101. for _, rk := range keys {
  102. kDef, ok := rk.(map[string]any)
  103. if !ok {
  104. l.Warn("Authorized user had an error, ignoring", "sshKeyConfig", rk)
  105. r.Failed()
  106. continue
  107. }
  108. user, ok := kDef["user"].(string)
  109. if !ok {
  110. l.Warn("Authorized user is missing the user field", "sshKeyConfig", rk)
  111. r.Failed()
  112. continue
  113. }
  114. k := kDef["keys"]
  115. switch v := k.(type) {
  116. case string:
  117. err := ssh.AddAuthorizedKey(user, v)
  118. if err != nil {
  119. l.Warn("Failed to authorize key",
  120. "error", err,
  121. "sshKeyConfig", rk,
  122. "sshKey", v,
  123. )
  124. r.Failed()
  125. continue
  126. }
  127. case []any:
  128. for _, subK := range v {
  129. sk, ok := subK.(string)
  130. if !ok {
  131. l.Warn("Did not understand ssh key",
  132. "sshKeyConfig", rk,
  133. "sshKey", subK,
  134. )
  135. r.Failed()
  136. continue
  137. }
  138. err := ssh.AddAuthorizedKey(user, sk)
  139. if err != nil {
  140. l.Warn("Failed to authorize key",
  141. "error", err,
  142. "sshKeyConfig", sk,
  143. )
  144. r.Failed()
  145. continue
  146. }
  147. }
  148. default:
  149. l.Warn("Authorized user is missing the keys field or was not understood", "sshKeyConfig", rk)
  150. r.Failed()
  151. }
  152. }
  153. } else {
  154. l.Info("no ssh users to authorize")
  155. }
  156. var runner func()
  157. if c.GetBool("sshd.enabled", false) {
  158. ssh.Stop()
  159. runner = func() {
  160. if err := ssh.Run(listen); err != nil {
  161. l.Warn("Failed to run the SSH server", "error", err)
  162. }
  163. }
  164. } else {
  165. ssh.Stop()
  166. }
  167. return runner, nil
  168. }