service_portable.go 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. // +build !noportable
  2. package service
  3. import (
  4. "fmt"
  5. "math/rand"
  6. "os"
  7. "os/signal"
  8. "strings"
  9. "syscall"
  10. "time"
  11. "github.com/grandcat/zeroconf"
  12. "github.com/drakkan/sftpgo/config"
  13. "github.com/drakkan/sftpgo/dataprovider"
  14. "github.com/drakkan/sftpgo/ftpd"
  15. "github.com/drakkan/sftpgo/kms"
  16. "github.com/drakkan/sftpgo/logger"
  17. "github.com/drakkan/sftpgo/sftpd"
  18. "github.com/drakkan/sftpgo/utils"
  19. "github.com/drakkan/sftpgo/version"
  20. "github.com/drakkan/sftpgo/vfs"
  21. "github.com/drakkan/sftpgo/webdavd"
  22. )
  23. // StartPortableMode starts the service in portable mode
  24. func (s *Service) StartPortableMode(sftpdPort, ftpPort, webdavPort int, enabledSSHCommands []string, advertiseService,
  25. advertiseCredentials bool, ftpsCert, ftpsKey, webDavCert, webDavKey string) error {
  26. if s.PortableMode != 1 {
  27. return fmt.Errorf("service is not configured for portable mode")
  28. }
  29. rand.Seed(time.Now().UnixNano())
  30. err := config.LoadConfig(s.ConfigDir, s.ConfigFile)
  31. if err != nil {
  32. fmt.Printf("error loading configuration file: %v using defaults\n", err)
  33. }
  34. kmsConfig := config.GetKMSConfig()
  35. err = kmsConfig.Initialize()
  36. if err != nil {
  37. return err
  38. }
  39. printablePassword := s.configurePortableUser()
  40. dataProviderConf := config.GetProviderConf()
  41. dataProviderConf.Driver = dataprovider.MemoryDataProviderName
  42. dataProviderConf.Name = ""
  43. dataProviderConf.PreferDatabaseCredentials = true
  44. config.SetProviderConf(dataProviderConf)
  45. httpdConf := config.GetHTTPDConfig()
  46. httpdConf.Bindings = nil
  47. config.SetHTTPDConfig(httpdConf)
  48. telemetryConf := config.GetTelemetryConfig()
  49. telemetryConf.BindPort = 0
  50. config.SetTelemetryConfig(telemetryConf)
  51. sftpdConf := config.GetSFTPDConfig()
  52. sftpdConf.MaxAuthTries = 12
  53. sftpdConf.Bindings = []sftpd.Binding{
  54. {
  55. Port: sftpdPort,
  56. },
  57. }
  58. if sftpdPort >= 0 {
  59. if sftpdPort > 0 {
  60. sftpdConf.Bindings[0].Port = sftpdPort
  61. } else {
  62. // dynamic ports starts from 49152
  63. sftpdConf.Bindings[0].Port = 49152 + rand.Intn(15000)
  64. }
  65. if utils.IsStringInSlice("*", enabledSSHCommands) {
  66. sftpdConf.EnabledSSHCommands = sftpd.GetSupportedSSHCommands()
  67. } else {
  68. sftpdConf.EnabledSSHCommands = enabledSSHCommands
  69. }
  70. }
  71. config.SetSFTPDConfig(sftpdConf)
  72. if ftpPort >= 0 {
  73. ftpConf := config.GetFTPDConfig()
  74. binding := ftpd.Binding{}
  75. if ftpPort > 0 {
  76. binding.Port = ftpPort
  77. } else {
  78. binding.Port = 49152 + rand.Intn(15000)
  79. }
  80. ftpConf.Bindings = []ftpd.Binding{binding}
  81. ftpConf.Banner = fmt.Sprintf("SFTPGo portable %v ready", version.Get().Version)
  82. ftpConf.CertificateFile = ftpsCert
  83. ftpConf.CertificateKeyFile = ftpsKey
  84. config.SetFTPDConfig(ftpConf)
  85. }
  86. if webdavPort >= 0 {
  87. webDavConf := config.GetWebDAVDConfig()
  88. binding := webdavd.Binding{}
  89. if webdavPort > 0 {
  90. binding.Port = webdavPort
  91. } else {
  92. binding.Port = 49152 + rand.Intn(15000)
  93. }
  94. webDavConf.Bindings = []webdavd.Binding{binding}
  95. webDavConf.CertificateFile = webDavCert
  96. webDavConf.CertificateKeyFile = webDavKey
  97. config.SetWebDAVDConfig(webDavConf)
  98. }
  99. err = s.Start()
  100. if err != nil {
  101. return err
  102. }
  103. s.advertiseServices(advertiseService, advertiseCredentials)
  104. logger.InfoToConsole("Portable mode ready, user: %#v, password: %#v, public keys: %v, directory: %#v, "+
  105. "permissions: %+v, enabled ssh commands: %v file patterns filters: %+v %v", s.PortableUser.Username,
  106. printablePassword, s.PortableUser.PublicKeys, s.getPortableDirToServe(), s.PortableUser.Permissions,
  107. sftpdConf.EnabledSSHCommands, s.PortableUser.Filters.FilePatterns, s.getServiceOptionalInfoString())
  108. return nil
  109. }
  110. func (s *Service) getServiceOptionalInfoString() string {
  111. var info strings.Builder
  112. if config.GetSFTPDConfig().Bindings[0].IsValid() {
  113. info.WriteString(fmt.Sprintf("SFTP port: %v ", config.GetSFTPDConfig().Bindings[0].Port))
  114. }
  115. if config.GetFTPDConfig().Bindings[0].IsValid() {
  116. info.WriteString(fmt.Sprintf("FTP port: %v ", config.GetFTPDConfig().Bindings[0].Port))
  117. }
  118. if config.GetWebDAVDConfig().Bindings[0].IsValid() {
  119. scheme := "http"
  120. if config.GetWebDAVDConfig().CertificateFile != "" && config.GetWebDAVDConfig().CertificateKeyFile != "" {
  121. scheme = "https"
  122. }
  123. info.WriteString(fmt.Sprintf("WebDAV URL: %v://<your IP>:%v/", scheme, config.GetWebDAVDConfig().Bindings[0].Port))
  124. }
  125. return info.String()
  126. }
  127. func (s *Service) advertiseServices(advertiseService, advertiseCredentials bool) {
  128. var mDNSServiceSFTP *zeroconf.Server
  129. var mDNSServiceFTP *zeroconf.Server
  130. var mDNSServiceDAV *zeroconf.Server
  131. var err error
  132. if advertiseService {
  133. meta := []string{
  134. fmt.Sprintf("version=%v", version.Get().Version),
  135. }
  136. if advertiseCredentials {
  137. logger.InfoToConsole("Advertising credentials via multicast DNS")
  138. meta = append(meta, fmt.Sprintf("user=%v", s.PortableUser.Username))
  139. if len(s.PortableUser.Password) > 0 {
  140. meta = append(meta, fmt.Sprintf("password=%v", s.PortableUser.Password))
  141. } else {
  142. logger.InfoToConsole("Unable to advertise key based credentials via multicast DNS, we don't have the private key")
  143. }
  144. }
  145. sftpdConf := config.GetSFTPDConfig()
  146. if sftpdConf.Bindings[0].IsValid() {
  147. mDNSServiceSFTP, err = zeroconf.Register(
  148. fmt.Sprintf("SFTPGo portable %v", sftpdConf.Bindings[0].Port), // service instance name
  149. "_sftp-ssh._tcp", // service type and protocol
  150. "local.", // service domain
  151. sftpdConf.Bindings[0].Port, // service port
  152. meta, // service metadata
  153. nil, // register on all network interfaces
  154. )
  155. if err != nil {
  156. mDNSServiceSFTP = nil
  157. logger.WarnToConsole("Unable to advertise SFTP service via multicast DNS: %v", err)
  158. } else {
  159. logger.InfoToConsole("SFTP service advertised via multicast DNS")
  160. }
  161. }
  162. ftpdConf := config.GetFTPDConfig()
  163. if ftpdConf.Bindings[0].IsValid() {
  164. port := ftpdConf.Bindings[0].Port
  165. mDNSServiceFTP, err = zeroconf.Register(
  166. fmt.Sprintf("SFTPGo portable %v", port),
  167. "_ftp._tcp",
  168. "local.",
  169. port,
  170. meta,
  171. nil,
  172. )
  173. if err != nil {
  174. mDNSServiceFTP = nil
  175. logger.WarnToConsole("Unable to advertise FTP service via multicast DNS: %v", err)
  176. } else {
  177. logger.InfoToConsole("FTP service advertised via multicast DNS")
  178. }
  179. }
  180. webdavConf := config.GetWebDAVDConfig()
  181. if webdavConf.Bindings[0].IsValid() {
  182. mDNSServiceDAV, err = zeroconf.Register(
  183. fmt.Sprintf("SFTPGo portable %v", webdavConf.Bindings[0].Port),
  184. "_http._tcp",
  185. "local.",
  186. webdavConf.Bindings[0].Port,
  187. meta,
  188. nil,
  189. )
  190. if err != nil {
  191. mDNSServiceDAV = nil
  192. logger.WarnToConsole("Unable to advertise WebDAV service via multicast DNS: %v", err)
  193. } else {
  194. logger.InfoToConsole("WebDAV service advertised via multicast DNS")
  195. }
  196. }
  197. }
  198. sig := make(chan os.Signal, 1)
  199. signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
  200. go func() {
  201. <-sig
  202. if mDNSServiceSFTP != nil {
  203. logger.InfoToConsole("unregistering multicast DNS SFTP service")
  204. mDNSServiceSFTP.Shutdown()
  205. }
  206. if mDNSServiceFTP != nil {
  207. logger.InfoToConsole("unregistering multicast DNS FTP service")
  208. mDNSServiceFTP.Shutdown()
  209. }
  210. if mDNSServiceDAV != nil {
  211. logger.InfoToConsole("unregistering multicast DNS WebDAV service")
  212. mDNSServiceDAV.Shutdown()
  213. }
  214. s.Stop()
  215. }()
  216. }
  217. func (s *Service) getPortableDirToServe() string {
  218. var dirToServe string
  219. if s.PortableUser.FsConfig.Provider == vfs.S3FilesystemProvider {
  220. dirToServe = s.PortableUser.FsConfig.S3Config.KeyPrefix
  221. } else if s.PortableUser.FsConfig.Provider == vfs.GCSFilesystemProvider {
  222. dirToServe = s.PortableUser.FsConfig.GCSConfig.KeyPrefix
  223. } else {
  224. dirToServe = s.PortableUser.HomeDir
  225. }
  226. return dirToServe
  227. }
  228. // configures the portable user and return the printable password if any
  229. func (s *Service) configurePortableUser() string {
  230. if s.PortableUser.Username == "" {
  231. s.PortableUser.Username = "user"
  232. }
  233. printablePassword := ""
  234. if len(s.PortableUser.Password) > 0 {
  235. printablePassword = "[redacted]"
  236. }
  237. if len(s.PortableUser.PublicKeys) == 0 && s.PortableUser.Password == "" {
  238. var b strings.Builder
  239. for i := 0; i < 8; i++ {
  240. b.WriteRune(chars[rand.Intn(len(chars))])
  241. }
  242. s.PortableUser.Password = b.String()
  243. printablePassword = s.PortableUser.Password
  244. }
  245. s.configurePortableSecrets()
  246. return printablePassword
  247. }
  248. func (s *Service) configurePortableSecrets() {
  249. // we created the user before to initialize the KMS so we need to create the secret here
  250. switch s.PortableUser.FsConfig.Provider {
  251. case vfs.S3FilesystemProvider:
  252. payload := s.PortableUser.FsConfig.S3Config.AccessSecret.GetPayload()
  253. s.PortableUser.FsConfig.S3Config.AccessSecret = kms.NewEmptySecret()
  254. if payload != "" {
  255. s.PortableUser.FsConfig.S3Config.AccessSecret = kms.NewPlainSecret(payload)
  256. }
  257. case vfs.GCSFilesystemProvider:
  258. payload := s.PortableUser.FsConfig.GCSConfig.Credentials.GetPayload()
  259. s.PortableUser.FsConfig.GCSConfig.Credentials = kms.NewEmptySecret()
  260. if payload != "" {
  261. s.PortableUser.FsConfig.GCSConfig.Credentials = kms.NewPlainSecret(payload)
  262. }
  263. case vfs.AzureBlobFilesystemProvider:
  264. payload := s.PortableUser.FsConfig.AzBlobConfig.AccountKey.GetPayload()
  265. s.PortableUser.FsConfig.AzBlobConfig.AccountKey = kms.NewEmptySecret()
  266. if payload != "" {
  267. s.PortableUser.FsConfig.AzBlobConfig.AccountKey = kms.NewPlainSecret(payload)
  268. }
  269. payload = s.PortableUser.FsConfig.AzBlobConfig.SASURL.GetPayload()
  270. s.PortableUser.FsConfig.AzBlobConfig.SASURL = kms.NewEmptySecret()
  271. if payload != "" {
  272. s.PortableUser.FsConfig.AzBlobConfig.SASURL = kms.NewPlainSecret(payload)
  273. }
  274. case vfs.CryptedFilesystemProvider:
  275. payload := s.PortableUser.FsConfig.CryptConfig.Passphrase.GetPayload()
  276. s.PortableUser.FsConfig.CryptConfig.Passphrase = kms.NewEmptySecret()
  277. if payload != "" {
  278. s.PortableUser.FsConfig.CryptConfig.Passphrase = kms.NewPlainSecret(payload)
  279. }
  280. case vfs.SFTPFilesystemProvider:
  281. payload := s.PortableUser.FsConfig.SFTPConfig.Password.GetPayload()
  282. s.PortableUser.FsConfig.SFTPConfig.Password = kms.NewEmptySecret()
  283. if payload != "" {
  284. s.PortableUser.FsConfig.SFTPConfig.Password = kms.NewPlainSecret(payload)
  285. }
  286. payload = s.PortableUser.FsConfig.SFTPConfig.PrivateKey.GetPayload()
  287. s.PortableUser.FsConfig.SFTPConfig.PrivateKey = kms.NewEmptySecret()
  288. if payload != "" {
  289. s.PortableUser.FsConfig.SFTPConfig.PrivateKey = kms.NewPlainSecret(payload)
  290. }
  291. }
  292. }