sftp.go 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194
  1. package command
  2. import (
  3. "context"
  4. "fmt"
  5. "github.com/seaweedfs/seaweedfs/weed/util/version"
  6. "net"
  7. "os"
  8. "runtime"
  9. "time"
  10. "github.com/seaweedfs/seaweedfs/weed/glog"
  11. "github.com/seaweedfs/seaweedfs/weed/pb"
  12. filer_pb "github.com/seaweedfs/seaweedfs/weed/pb/filer_pb"
  13. "github.com/seaweedfs/seaweedfs/weed/security"
  14. "github.com/seaweedfs/seaweedfs/weed/sftpd"
  15. stats_collect "github.com/seaweedfs/seaweedfs/weed/stats"
  16. "github.com/seaweedfs/seaweedfs/weed/util"
  17. )
  18. var (
  19. sftpOptionsStandalone SftpOptions
  20. )
  21. // SftpOptions holds configuration options for the SFTP server.
  22. type SftpOptions struct {
  23. filer *string
  24. bindIp *string
  25. port *int
  26. sshPrivateKey *string
  27. hostKeysFolder *string
  28. authMethods *string
  29. maxAuthTries *int
  30. bannerMessage *string
  31. loginGraceTime *time.Duration
  32. clientAliveInterval *time.Duration
  33. clientAliveCountMax *int
  34. userStoreFile *string
  35. dataCenter *string
  36. metricsHttpPort *int
  37. metricsHttpIp *string
  38. localSocket *string
  39. }
  40. // cmdSftp defines the SFTP command similar to the S3 command.
  41. var cmdSftp = &Command{
  42. UsageLine: "sftp [-port=2022] [-filer=<ip:port>] [-sshPrivateKey=</path/to/private_key>]",
  43. Short: "start an SFTP server that is backed by a SeaweedFS filer",
  44. Long: `Start an SFTP server that leverages the SeaweedFS filer service to handle file operations.
  45. Instead of reading from or writing to a local filesystem, all file operations
  46. are routed through the filer (filer_pb) gRPC API. This allows you to centralize
  47. your file management in SeaweedFS.
  48. `,
  49. }
  50. func init() {
  51. // Register the command to avoid cyclic dependencies.
  52. cmdSftp.Run = runSftp
  53. sftpOptionsStandalone.filer = cmdSftp.Flag.String("filer", "localhost:8888", "filer server address (ip:port)")
  54. sftpOptionsStandalone.bindIp = cmdSftp.Flag.String("ip.bind", "0.0.0.0", "ip address to bind SFTP server")
  55. sftpOptionsStandalone.port = cmdSftp.Flag.Int("port", 2022, "SFTP server listen port")
  56. sftpOptionsStandalone.sshPrivateKey = cmdSftp.Flag.String("sshPrivateKey", "", "path to the SSH private key file for host authentication")
  57. sftpOptionsStandalone.hostKeysFolder = cmdSftp.Flag.String("hostKeysFolder", "", "path to folder containing SSH private key files for host authentication")
  58. sftpOptionsStandalone.authMethods = cmdSftp.Flag.String("authMethods", "password,publickey", "comma-separated list of allowed auth methods: password, publickey, keyboard-interactive")
  59. sftpOptionsStandalone.maxAuthTries = cmdSftp.Flag.Int("maxAuthTries", 6, "maximum number of authentication attempts per connection")
  60. sftpOptionsStandalone.bannerMessage = cmdSftp.Flag.String("bannerMessage", "SeaweedFS SFTP Server - Unauthorized access is prohibited", "message displayed before authentication")
  61. sftpOptionsStandalone.loginGraceTime = cmdSftp.Flag.Duration("loginGraceTime", 2*time.Minute, "timeout for authentication")
  62. sftpOptionsStandalone.clientAliveInterval = cmdSftp.Flag.Duration("clientAliveInterval", 5*time.Second, "interval for sending keep-alive messages")
  63. sftpOptionsStandalone.clientAliveCountMax = cmdSftp.Flag.Int("clientAliveCountMax", 3, "maximum number of missed keep-alive messages before disconnecting")
  64. sftpOptionsStandalone.userStoreFile = cmdSftp.Flag.String("userStoreFile", "", "path to JSON file containing user credentials and permissions")
  65. sftpOptionsStandalone.dataCenter = cmdSftp.Flag.String("dataCenter", "", "prefer to read and write to volumes in this data center")
  66. sftpOptionsStandalone.metricsHttpPort = cmdSftp.Flag.Int("metricsPort", 0, "Prometheus metrics listen port")
  67. sftpOptionsStandalone.metricsHttpIp = cmdSftp.Flag.String("metricsIp", "", "metrics listen ip. If empty, default to same as -ip.bind option.")
  68. sftpOptionsStandalone.localSocket = cmdSftp.Flag.String("localSocket", "", "default to /tmp/seaweedfs-sftp-<port>.sock")
  69. }
  70. // runSftp is the command entry point.
  71. func runSftp(cmd *Command, args []string) bool {
  72. // Load security configuration as done in other SeaweedFS services.
  73. util.LoadSecurityConfiguration()
  74. // Configure metrics
  75. switch {
  76. case *sftpOptionsStandalone.metricsHttpIp != "":
  77. // nothing to do, use sftpOptionsStandalone.metricsHttpIp
  78. case *sftpOptionsStandalone.bindIp != "":
  79. *sftpOptionsStandalone.metricsHttpIp = *sftpOptionsStandalone.bindIp
  80. }
  81. go stats_collect.StartMetricsServer(*sftpOptionsStandalone.metricsHttpIp, *sftpOptionsStandalone.metricsHttpPort)
  82. return sftpOptionsStandalone.startSftpServer()
  83. }
  84. func (sftpOpt *SftpOptions) startSftpServer() bool {
  85. filerAddress := pb.ServerAddress(*sftpOpt.filer)
  86. grpcDialOption := security.LoadClientTLS(util.GetViper(), "grpc.client")
  87. // metrics read from the filer
  88. var metricsAddress string
  89. var metricsIntervalSec int
  90. var filerGroup string
  91. // Connect to the filer service and try to retrieve basic configuration.
  92. for {
  93. err := pb.WithGrpcFilerClient(false, 0, filerAddress, grpcDialOption, func(client filer_pb.SeaweedFilerClient) error {
  94. resp, err := client.GetFilerConfiguration(context.Background(), &filer_pb.GetFilerConfigurationRequest{})
  95. if err != nil {
  96. return fmt.Errorf("get filer %s configuration: %v", filerAddress, err)
  97. }
  98. metricsAddress, metricsIntervalSec = resp.MetricsAddress, int(resp.MetricsIntervalSec)
  99. filerGroup = resp.FilerGroup
  100. glog.V(0).Infof("SFTP read filer configuration, using filer at: %s", filerAddress)
  101. return nil
  102. })
  103. if err != nil {
  104. glog.V(0).Infof("Waiting to connect to filer %s grpc address %s...", *sftpOpt.filer, filerAddress.ToGrpcAddress())
  105. time.Sleep(time.Second)
  106. } else {
  107. glog.V(0).Infof("Connected to filer %s grpc address %s", *sftpOpt.filer, filerAddress.ToGrpcAddress())
  108. break
  109. }
  110. }
  111. go stats_collect.LoopPushingMetric("sftp", stats_collect.SourceName(uint32(*sftpOpt.port)), metricsAddress, metricsIntervalSec)
  112. // Parse auth methods
  113. var authMethods []string
  114. if *sftpOpt.authMethods != "" {
  115. authMethods = util.StringSplit(*sftpOpt.authMethods, ",")
  116. }
  117. // Create a new SFTP service instance with all options
  118. service := sftpd.NewSFTPService(&sftpd.SFTPServiceOptions{
  119. GrpcDialOption: grpcDialOption,
  120. DataCenter: *sftpOpt.dataCenter,
  121. FilerGroup: filerGroup,
  122. Filer: filerAddress,
  123. SshPrivateKey: *sftpOpt.sshPrivateKey,
  124. HostKeysFolder: *sftpOpt.hostKeysFolder,
  125. AuthMethods: authMethods,
  126. MaxAuthTries: *sftpOpt.maxAuthTries,
  127. BannerMessage: *sftpOpt.bannerMessage,
  128. LoginGraceTime: *sftpOpt.loginGraceTime,
  129. ClientAliveInterval: *sftpOpt.clientAliveInterval,
  130. ClientAliveCountMax: *sftpOpt.clientAliveCountMax,
  131. UserStoreFile: *sftpOpt.userStoreFile,
  132. })
  133. // Set up Unix socket if on non-Windows platforms
  134. if runtime.GOOS != "windows" {
  135. localSocket := *sftpOpt.localSocket
  136. if localSocket == "" {
  137. localSocket = fmt.Sprintf("/tmp/seaweedfs-sftp-%d.sock", *sftpOpt.port)
  138. }
  139. if err := os.Remove(localSocket); err != nil && !os.IsNotExist(err) {
  140. glog.Fatalf("Failed to remove %s, error: %s", localSocket, err.Error())
  141. }
  142. go func() {
  143. // start on local unix socket
  144. sftpSocketListener, err := net.Listen("unix", localSocket)
  145. if err != nil {
  146. glog.Fatalf("Failed to listen on %s: %v", localSocket, err)
  147. }
  148. if err := service.Serve(sftpSocketListener); err != nil {
  149. glog.Fatalf("Failed to serve SFTP on socket %s: %v", localSocket, err)
  150. }
  151. }()
  152. }
  153. // Start the SFTP service on TCP
  154. listenAddress := fmt.Sprintf("%s:%d", *sftpOpt.bindIp, *sftpOpt.port)
  155. sftpListener, sftpLocalListener, err := util.NewIpAndLocalListeners(*sftpOpt.bindIp, *sftpOpt.port, time.Duration(10)*time.Second)
  156. if err != nil {
  157. glog.Fatalf("SFTP server listener on %s error: %v", listenAddress, err)
  158. }
  159. glog.V(0).Infof("Start Seaweed SFTP Server %s at %s", version.Version(), listenAddress)
  160. if sftpLocalListener != nil {
  161. go func() {
  162. if err := service.Serve(sftpLocalListener); err != nil {
  163. glog.Fatalf("SFTP Server failed to serve on local listener: %v", err)
  164. }
  165. }()
  166. }
  167. if err := service.Serve(sftpListener); err != nil {
  168. glog.Fatalf("SFTP Server failed to serve: %v", err)
  169. }
  170. return true
  171. }