portable.go 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392
  1. // +build !noportable
  2. package cmd
  3. import (
  4. "fmt"
  5. "io/ioutil"
  6. "os"
  7. "path"
  8. "path/filepath"
  9. "strings"
  10. "github.com/spf13/cobra"
  11. "github.com/drakkan/sftpgo/common"
  12. "github.com/drakkan/sftpgo/dataprovider"
  13. "github.com/drakkan/sftpgo/kms"
  14. "github.com/drakkan/sftpgo/service"
  15. "github.com/drakkan/sftpgo/sftpd"
  16. "github.com/drakkan/sftpgo/version"
  17. "github.com/drakkan/sftpgo/vfs"
  18. )
  19. var (
  20. directoryToServe string
  21. portableSFTPDPort int
  22. portableAdvertiseService bool
  23. portableAdvertiseCredentials bool
  24. portableUsername string
  25. portablePassword string
  26. portableLogFile string
  27. portableLogVerbose bool
  28. portablePublicKeys []string
  29. portablePermissions []string
  30. portableSSHCommands []string
  31. portableAllowedPatterns []string
  32. portableDeniedPatterns []string
  33. portableFsProvider int
  34. portableS3Bucket string
  35. portableS3Region string
  36. portableS3AccessKey string
  37. portableS3AccessSecret string
  38. portableS3Endpoint string
  39. portableS3StorageClass string
  40. portableS3KeyPrefix string
  41. portableS3ULPartSize int
  42. portableS3ULConcurrency int
  43. portableGCSBucket string
  44. portableGCSCredentialsFile string
  45. portableGCSAutoCredentials int
  46. portableGCSStorageClass string
  47. portableGCSKeyPrefix string
  48. portableFTPDPort int
  49. portableFTPSCert string
  50. portableFTPSKey string
  51. portableWebDAVPort int
  52. portableWebDAVCert string
  53. portableWebDAVKey string
  54. portableAzContainer string
  55. portableAzAccountName string
  56. portableAzAccountKey string
  57. portableAzEndpoint string
  58. portableAzAccessTier string
  59. portableAzSASURL string
  60. portableAzKeyPrefix string
  61. portableAzULPartSize int
  62. portableAzULConcurrency int
  63. portableAzUseEmulator bool
  64. portableCryptPassphrase string
  65. portableSFTPEndpoint string
  66. portableSFTPUsername string
  67. portableSFTPPassword string
  68. portableSFTPPrivateKeyPath string
  69. portableSFTPFingerprints []string
  70. portableSFTPPrefix string
  71. portableCmd = &cobra.Command{
  72. Use: "portable",
  73. Short: "Serve a single directory",
  74. Long: `To serve the current working directory with auto generated credentials simply
  75. use:
  76. $ sftpgo portable
  77. Please take a look at the usage below to customize the serving parameters`,
  78. Run: func(cmd *cobra.Command, args []string) {
  79. portableDir := directoryToServe
  80. fsProvider := dataprovider.FilesystemProvider(portableFsProvider)
  81. if !filepath.IsAbs(portableDir) {
  82. if fsProvider == dataprovider.LocalFilesystemProvider {
  83. portableDir, _ = filepath.Abs(portableDir)
  84. } else {
  85. portableDir = os.TempDir()
  86. }
  87. }
  88. permissions := make(map[string][]string)
  89. permissions["/"] = portablePermissions
  90. portableGCSCredentials := ""
  91. if fsProvider == dataprovider.GCSFilesystemProvider && portableGCSCredentialsFile != "" {
  92. contents, err := getFileContents(portableGCSCredentialsFile)
  93. if err != nil {
  94. fmt.Printf("Unable to get GCS credentials: %v\n", err)
  95. os.Exit(1)
  96. }
  97. portableGCSCredentials = contents
  98. portableGCSAutoCredentials = 0
  99. }
  100. portableSFTPPrivateKey := ""
  101. if fsProvider == dataprovider.SFTPFilesystemProvider && portableSFTPPrivateKeyPath != "" {
  102. contents, err := getFileContents(portableSFTPPrivateKeyPath)
  103. if err != nil {
  104. fmt.Printf("Unable to get SFTP private key: %v\n", err)
  105. os.Exit(1)
  106. }
  107. portableSFTPPrivateKey = contents
  108. }
  109. if portableFTPDPort >= 0 && len(portableFTPSCert) > 0 && len(portableFTPSKey) > 0 {
  110. _, err := common.NewCertManager(portableFTPSCert, portableFTPSKey, filepath.Clean(defaultConfigDir),
  111. "FTP portable")
  112. if err != nil {
  113. fmt.Printf("Unable to load FTPS key pair, cert file %#v key file %#v error: %v\n",
  114. portableFTPSCert, portableFTPSKey, err)
  115. os.Exit(1)
  116. }
  117. }
  118. if portableWebDAVPort > 0 && len(portableWebDAVCert) > 0 && len(portableWebDAVKey) > 0 {
  119. _, err := common.NewCertManager(portableWebDAVCert, portableWebDAVKey, filepath.Clean(defaultConfigDir),
  120. "WebDAV portable")
  121. if err != nil {
  122. fmt.Printf("Unable to load WebDAV key pair, cert file %#v key file %#v error: %v\n",
  123. portableWebDAVCert, portableWebDAVKey, err)
  124. os.Exit(1)
  125. }
  126. }
  127. service := service.Service{
  128. ConfigDir: filepath.Clean(defaultConfigDir),
  129. ConfigFile: defaultConfigFile,
  130. LogFilePath: portableLogFile,
  131. LogMaxSize: defaultLogMaxSize,
  132. LogMaxBackups: defaultLogMaxBackup,
  133. LogMaxAge: defaultLogMaxAge,
  134. LogCompress: defaultLogCompress,
  135. LogVerbose: portableLogVerbose,
  136. Shutdown: make(chan bool),
  137. PortableMode: 1,
  138. PortableUser: dataprovider.User{
  139. Username: portableUsername,
  140. Password: portablePassword,
  141. PublicKeys: portablePublicKeys,
  142. Permissions: permissions,
  143. HomeDir: portableDir,
  144. Status: 1,
  145. FsConfig: dataprovider.Filesystem{
  146. Provider: dataprovider.FilesystemProvider(portableFsProvider),
  147. S3Config: vfs.S3FsConfig{
  148. Bucket: portableS3Bucket,
  149. Region: portableS3Region,
  150. AccessKey: portableS3AccessKey,
  151. AccessSecret: kms.NewPlainSecret(portableS3AccessSecret),
  152. Endpoint: portableS3Endpoint,
  153. StorageClass: portableS3StorageClass,
  154. KeyPrefix: portableS3KeyPrefix,
  155. UploadPartSize: int64(portableS3ULPartSize),
  156. UploadConcurrency: portableS3ULConcurrency,
  157. },
  158. GCSConfig: vfs.GCSFsConfig{
  159. Bucket: portableGCSBucket,
  160. Credentials: kms.NewPlainSecret(portableGCSCredentials),
  161. AutomaticCredentials: portableGCSAutoCredentials,
  162. StorageClass: portableGCSStorageClass,
  163. KeyPrefix: portableGCSKeyPrefix,
  164. },
  165. AzBlobConfig: vfs.AzBlobFsConfig{
  166. Container: portableAzContainer,
  167. AccountName: portableAzAccountName,
  168. AccountKey: kms.NewPlainSecret(portableAzAccountKey),
  169. Endpoint: portableAzEndpoint,
  170. AccessTier: portableAzAccessTier,
  171. SASURL: portableAzSASURL,
  172. KeyPrefix: portableAzKeyPrefix,
  173. UseEmulator: portableAzUseEmulator,
  174. UploadPartSize: int64(portableAzULPartSize),
  175. UploadConcurrency: portableAzULConcurrency,
  176. },
  177. CryptConfig: vfs.CryptFsConfig{
  178. Passphrase: kms.NewPlainSecret(portableCryptPassphrase),
  179. },
  180. SFTPConfig: vfs.SFTPFsConfig{
  181. Endpoint: portableSFTPEndpoint,
  182. Username: portableSFTPUsername,
  183. Password: kms.NewPlainSecret(portableSFTPPassword),
  184. PrivateKey: kms.NewPlainSecret(portableSFTPPrivateKey),
  185. Fingerprints: portableSFTPFingerprints,
  186. Prefix: portableSFTPPrefix,
  187. },
  188. },
  189. Filters: dataprovider.UserFilters{
  190. FilePatterns: parsePatternsFilesFilters(),
  191. },
  192. },
  193. }
  194. if err := service.StartPortableMode(portableSFTPDPort, portableFTPDPort, portableWebDAVPort, portableSSHCommands, portableAdvertiseService,
  195. portableAdvertiseCredentials, portableFTPSCert, portableFTPSKey, portableWebDAVCert, portableWebDAVKey); err == nil {
  196. service.Wait()
  197. if service.Error == nil {
  198. os.Exit(0)
  199. }
  200. }
  201. os.Exit(1)
  202. },
  203. }
  204. )
  205. func init() {
  206. version.AddFeature("+portable")
  207. portableCmd.Flags().StringVarP(&directoryToServe, "directory", "d", ".", `Path to the directory to serve.
  208. This can be an absolute path or a path
  209. relative to the current directory
  210. `)
  211. portableCmd.Flags().IntVarP(&portableSFTPDPort, "sftpd-port", "s", 0, `0 means a random unprivileged port,
  212. < 0 disabled`)
  213. portableCmd.Flags().IntVar(&portableFTPDPort, "ftpd-port", -1, `0 means a random unprivileged port,
  214. < 0 disabled`)
  215. portableCmd.Flags().IntVar(&portableWebDAVPort, "webdav-port", -1, `0 means a random unprivileged port,
  216. < 0 disabled`)
  217. portableCmd.Flags().StringSliceVarP(&portableSSHCommands, "ssh-commands", "c", sftpd.GetDefaultSSHCommands(),
  218. `SSH commands to enable.
  219. "*" means any supported SSH command
  220. including scp
  221. `)
  222. portableCmd.Flags().StringVarP(&portableUsername, "username", "u", "", `Leave empty to use an auto generated
  223. value`)
  224. portableCmd.Flags().StringVarP(&portablePassword, "password", "p", "", `Leave empty to use an auto generated
  225. value`)
  226. portableCmd.Flags().StringVarP(&portableLogFile, logFilePathFlag, "l", "", "Leave empty to disable logging")
  227. portableCmd.Flags().BoolVarP(&portableLogVerbose, logVerboseFlag, "v", false, "Enable verbose logs")
  228. portableCmd.Flags().StringSliceVarP(&portablePublicKeys, "public-key", "k", []string{}, "")
  229. portableCmd.Flags().StringSliceVarP(&portablePermissions, "permissions", "g", []string{"list", "download"},
  230. `User's permissions. "*" means any
  231. permission`)
  232. portableCmd.Flags().StringArrayVar(&portableAllowedPatterns, "allowed-patterns", []string{},
  233. `Allowed file patterns case insensitive.
  234. The format is:
  235. /dir::pattern1,pattern2.
  236. For example: "/somedir::*.jpg,a*b?.png"`)
  237. portableCmd.Flags().StringArrayVar(&portableDeniedPatterns, "denied-patterns", []string{},
  238. `Denied file patterns case insensitive.
  239. The format is:
  240. /dir::pattern1,pattern2.
  241. For example: "/somedir::*.jpg,a*b?.png"`)
  242. portableCmd.Flags().BoolVarP(&portableAdvertiseService, "advertise-service", "S", false,
  243. `Advertise configured services using
  244. multicast DNS`)
  245. portableCmd.Flags().BoolVarP(&portableAdvertiseCredentials, "advertise-credentials", "C", false,
  246. `If the SFTP/FTP service is
  247. advertised via multicast DNS, this
  248. flag allows to put username/password
  249. inside the advertised TXT record`)
  250. portableCmd.Flags().IntVarP(&portableFsProvider, "fs-provider", "f", int(dataprovider.LocalFilesystemProvider), `0 => local filesystem
  251. 1 => AWS S3 compatible
  252. 2 => Google Cloud Storage
  253. 3 => Azure Blob Storage
  254. 4 => Encrypted local filesystem
  255. 5 => SFTP`)
  256. portableCmd.Flags().StringVar(&portableS3Bucket, "s3-bucket", "", "")
  257. portableCmd.Flags().StringVar(&portableS3Region, "s3-region", "", "")
  258. portableCmd.Flags().StringVar(&portableS3AccessKey, "s3-access-key", "", "")
  259. portableCmd.Flags().StringVar(&portableS3AccessSecret, "s3-access-secret", "", "")
  260. portableCmd.Flags().StringVar(&portableS3Endpoint, "s3-endpoint", "", "")
  261. portableCmd.Flags().StringVar(&portableS3StorageClass, "s3-storage-class", "", "")
  262. portableCmd.Flags().StringVar(&portableS3KeyPrefix, "s3-key-prefix", "", `Allows to restrict access to the
  263. virtual folder identified by this
  264. prefix and its contents`)
  265. portableCmd.Flags().IntVar(&portableS3ULPartSize, "s3-upload-part-size", 5, `The buffer size for multipart uploads
  266. (MB)`)
  267. portableCmd.Flags().IntVar(&portableS3ULConcurrency, "s3-upload-concurrency", 2, `How many parts are uploaded in
  268. parallel`)
  269. portableCmd.Flags().StringVar(&portableGCSBucket, "gcs-bucket", "", "")
  270. portableCmd.Flags().StringVar(&portableGCSStorageClass, "gcs-storage-class", "", "")
  271. portableCmd.Flags().StringVar(&portableGCSKeyPrefix, "gcs-key-prefix", "", `Allows to restrict access to the
  272. virtual folder identified by this
  273. prefix and its contents`)
  274. portableCmd.Flags().StringVar(&portableGCSCredentialsFile, "gcs-credentials-file", "", `Google Cloud Storage JSON credentials
  275. file`)
  276. portableCmd.Flags().IntVar(&portableGCSAutoCredentials, "gcs-automatic-credentials", 1, `0 means explicit credentials using
  277. a JSON credentials file, 1 automatic
  278. `)
  279. portableCmd.Flags().StringVar(&portableFTPSCert, "ftpd-cert", "", "Path to the certificate file for FTPS")
  280. portableCmd.Flags().StringVar(&portableFTPSKey, "ftpd-key", "", "Path to the key file for FTPS")
  281. portableCmd.Flags().StringVar(&portableWebDAVCert, "webdav-cert", "", `Path to the certificate file for WebDAV
  282. over HTTPS`)
  283. portableCmd.Flags().StringVar(&portableWebDAVKey, "webdav-key", "", `Path to the key file for WebDAV over
  284. HTTPS`)
  285. portableCmd.Flags().StringVar(&portableAzContainer, "az-container", "", "")
  286. portableCmd.Flags().StringVar(&portableAzAccountName, "az-account-name", "", "")
  287. portableCmd.Flags().StringVar(&portableAzAccountKey, "az-account-key", "", "")
  288. portableCmd.Flags().StringVar(&portableAzSASURL, "az-sas-url", "", `Shared access signature URL`)
  289. portableCmd.Flags().StringVar(&portableAzEndpoint, "az-endpoint", "", `Leave empty to use the default:
  290. "blob.core.windows.net"`)
  291. portableCmd.Flags().StringVar(&portableAzAccessTier, "az-access-tier", "", `Leave empty to use the default
  292. container setting`)
  293. portableCmd.Flags().StringVar(&portableAzKeyPrefix, "az-key-prefix", "", `Allows to restrict access to the
  294. virtual folder identified by this
  295. prefix and its contents`)
  296. portableCmd.Flags().IntVar(&portableAzULPartSize, "az-upload-part-size", 4, `The buffer size for multipart uploads
  297. (MB)`)
  298. portableCmd.Flags().IntVar(&portableAzULConcurrency, "az-upload-concurrency", 2, `How many parts are uploaded in
  299. parallel`)
  300. portableCmd.Flags().BoolVar(&portableAzUseEmulator, "az-use-emulator", false, "")
  301. portableCmd.Flags().StringVar(&portableCryptPassphrase, "crypto-passphrase", "", `Passphrase for encryption/decryption`)
  302. portableCmd.Flags().StringVar(&portableSFTPEndpoint, "sftp-endpoint", "", `SFTP endpoint as host:port for SFTP
  303. provider`)
  304. portableCmd.Flags().StringVar(&portableSFTPUsername, "sftp-username", "", `SFTP user for SFTP provider`)
  305. portableCmd.Flags().StringVar(&portableSFTPPassword, "sftp-password", "", `SFTP password for SFTP provider`)
  306. portableCmd.Flags().StringVar(&portableSFTPPrivateKeyPath, "sftp-key-path", "", `SFTP private key path for SFTP provider`)
  307. portableCmd.Flags().StringSliceVar(&portableSFTPFingerprints, "sftp-fingerprints", []string{}, `SFTP fingerprints to verify remote host
  308. key for SFTP provider`)
  309. portableCmd.Flags().StringVar(&portableSFTPPrefix, "sftp-prefix", "", `SFTP prefix allows restrict all
  310. operations to a given path within the
  311. remote SFTP server`)
  312. rootCmd.AddCommand(portableCmd)
  313. }
  314. func parsePatternsFilesFilters() []dataprovider.PatternsFilter {
  315. var patterns []dataprovider.PatternsFilter
  316. for _, val := range portableAllowedPatterns {
  317. p, exts := getPatternsFilterValues(strings.TrimSpace(val))
  318. if p != "" {
  319. patterns = append(patterns, dataprovider.PatternsFilter{
  320. Path: path.Clean(p),
  321. AllowedPatterns: exts,
  322. DeniedPatterns: []string{},
  323. })
  324. }
  325. }
  326. for _, val := range portableDeniedPatterns {
  327. p, exts := getPatternsFilterValues(strings.TrimSpace(val))
  328. if p != "" {
  329. found := false
  330. for index, e := range patterns {
  331. if path.Clean(e.Path) == path.Clean(p) {
  332. patterns[index].DeniedPatterns = append(patterns[index].DeniedPatterns, exts...)
  333. found = true
  334. break
  335. }
  336. }
  337. if !found {
  338. patterns = append(patterns, dataprovider.PatternsFilter{
  339. Path: path.Clean(p),
  340. AllowedPatterns: []string{},
  341. DeniedPatterns: exts,
  342. })
  343. }
  344. }
  345. }
  346. return patterns
  347. }
  348. func getPatternsFilterValues(value string) (string, []string) {
  349. if strings.Contains(value, "::") {
  350. dirExts := strings.Split(value, "::")
  351. if len(dirExts) > 1 {
  352. dir := strings.TrimSpace(dirExts[0])
  353. exts := []string{}
  354. for _, e := range strings.Split(dirExts[1], ",") {
  355. cleanedExt := strings.TrimSpace(e)
  356. if cleanedExt != "" {
  357. exts = append(exts, cleanedExt)
  358. }
  359. }
  360. if dir != "" && len(exts) > 0 {
  361. return dir, exts
  362. }
  363. }
  364. }
  365. return "", nil
  366. }
  367. func getFileContents(name string) (string, error) {
  368. fi, err := os.Stat(name)
  369. if err != nil {
  370. return "", err
  371. }
  372. if fi.Size() > 1048576 {
  373. return "", fmt.Errorf("%#v is too big %v/1048576 bytes", name, fi.Size())
  374. }
  375. contents, err := ioutil.ReadFile(name)
  376. if err != nil {
  377. return "", err
  378. }
  379. return string(contents), nil
  380. }