config.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. // Package config manages the configuration.
  2. // Configuration is loaded from sftpgo.conf file.
  3. // If sftpgo.conf is not found or cannot be readed or decoded as json the default configuration is used.
  4. // The default configuration an be found inside the source tree:
  5. // https://github.com/drakkan/sftpgo/blob/master/sftpgo.conf
  6. package config
  7. import (
  8. "fmt"
  9. "strings"
  10. "github.com/spf13/viper"
  11. "github.com/drakkan/sftpgo/dataprovider"
  12. "github.com/drakkan/sftpgo/httpclient"
  13. "github.com/drakkan/sftpgo/httpd"
  14. "github.com/drakkan/sftpgo/logger"
  15. "github.com/drakkan/sftpgo/sftpd"
  16. "github.com/drakkan/sftpgo/utils"
  17. "github.com/drakkan/sftpgo/version"
  18. )
  19. const (
  20. logSender = "config"
  21. // DefaultConfigName defines the name for the default config file.
  22. // This is the file name without extension, we use viper and so we
  23. // support all the config files format supported by viper
  24. DefaultConfigName = "sftpgo"
  25. // ConfigEnvPrefix defines a prefix that ENVIRONMENT variables will use
  26. configEnvPrefix = "sftpgo"
  27. )
  28. var (
  29. globalConf globalConfig
  30. defaultBanner = fmt.Sprintf("SFTPGo_%v", version.Get().Version)
  31. )
  32. type globalConfig struct {
  33. SFTPD sftpd.Configuration `json:"sftpd" mapstructure:"sftpd"`
  34. ProviderConf dataprovider.Config `json:"data_provider" mapstructure:"data_provider"`
  35. HTTPDConfig httpd.Conf `json:"httpd" mapstructure:"httpd"`
  36. HTTPConfig httpclient.Config `json:"http" mapstructure:"http"`
  37. }
  38. func init() {
  39. // create a default configuration to use if no config file is provided
  40. globalConf = globalConfig{
  41. SFTPD: sftpd.Configuration{
  42. Banner: defaultBanner,
  43. BindPort: 2022,
  44. BindAddress: "",
  45. IdleTimeout: 15,
  46. MaxAuthTries: 0,
  47. Umask: "0022",
  48. UploadMode: 0,
  49. Actions: sftpd.Actions{
  50. ExecuteOn: []string{},
  51. Hook: "",
  52. },
  53. HostKeys: []string{},
  54. KexAlgorithms: []string{},
  55. Ciphers: []string{},
  56. MACs: []string{},
  57. TrustedUserCAKeys: []string{},
  58. LoginBannerFile: "",
  59. EnabledSSHCommands: sftpd.GetDefaultSSHCommands(),
  60. KeyboardInteractiveHook: "",
  61. ProxyProtocol: 0,
  62. ProxyAllowed: []string{},
  63. },
  64. ProviderConf: dataprovider.Config{
  65. Driver: "sqlite",
  66. Name: "sftpgo.db",
  67. Host: "",
  68. Port: 5432,
  69. Username: "",
  70. Password: "",
  71. ConnectionString: "",
  72. SQLTablesPrefix: "",
  73. ManageUsers: 1,
  74. SSLMode: 0,
  75. TrackQuota: 1,
  76. PoolSize: 0,
  77. UsersBaseDir: "",
  78. Actions: dataprovider.Actions{
  79. ExecuteOn: []string{},
  80. Hook: "",
  81. },
  82. ExternalAuthHook: "",
  83. ExternalAuthScope: 0,
  84. CredentialsPath: "credentials",
  85. PreLoginHook: "",
  86. },
  87. HTTPDConfig: httpd.Conf{
  88. BindPort: 8080,
  89. BindAddress: "127.0.0.1",
  90. TemplatesPath: "templates",
  91. StaticFilesPath: "static",
  92. BackupsPath: "backups",
  93. AuthUserFile: "",
  94. CertificateFile: "",
  95. CertificateKeyFile: "",
  96. },
  97. HTTPConfig: httpclient.Config{
  98. Timeout: 20,
  99. CACertificates: nil,
  100. SkipTLSVerify: false,
  101. },
  102. }
  103. viper.SetEnvPrefix(configEnvPrefix)
  104. replacer := strings.NewReplacer(".", "__")
  105. viper.SetEnvKeyReplacer(replacer)
  106. viper.SetConfigName(DefaultConfigName)
  107. viper.AutomaticEnv()
  108. viper.AllowEmptyEnv(true)
  109. }
  110. // GetSFTPDConfig returns the configuration for the SFTP server
  111. func GetSFTPDConfig() sftpd.Configuration {
  112. return globalConf.SFTPD
  113. }
  114. // SetSFTPDConfig sets the configuration for the SFTP server
  115. func SetSFTPDConfig(config sftpd.Configuration) {
  116. globalConf.SFTPD = config
  117. }
  118. // GetHTTPDConfig returns the configuration for the HTTP server
  119. func GetHTTPDConfig() httpd.Conf {
  120. return globalConf.HTTPDConfig
  121. }
  122. // SetHTTPDConfig sets the configuration for the HTTP server
  123. func SetHTTPDConfig(config httpd.Conf) {
  124. globalConf.HTTPDConfig = config
  125. }
  126. //GetProviderConf returns the configuration for the data provider
  127. func GetProviderConf() dataprovider.Config {
  128. return globalConf.ProviderConf
  129. }
  130. //SetProviderConf sets the configuration for the data provider
  131. func SetProviderConf(config dataprovider.Config) {
  132. globalConf.ProviderConf = config
  133. }
  134. // GetHTTPConfig returns the configuration for HTTP clients
  135. func GetHTTPConfig() httpclient.Config {
  136. return globalConf.HTTPConfig
  137. }
  138. func getRedactedGlobalConf() globalConfig {
  139. conf := globalConf
  140. conf.ProviderConf.Password = "[redacted]"
  141. return conf
  142. }
  143. // LoadConfig loads the configuration
  144. // configDir will be added to the configuration search paths.
  145. // The search path contains by default the current directory and on linux it contains
  146. // $HOME/.config/sftpgo and /etc/sftpgo too.
  147. // configName is the name of the configuration to search without extension
  148. func LoadConfig(configDir, configName string) error {
  149. var err error
  150. viper.AddConfigPath(configDir)
  151. setViperAdditionalConfigPaths()
  152. viper.AddConfigPath(".")
  153. viper.SetConfigName(configName)
  154. if err = viper.ReadInConfig(); err != nil {
  155. logger.Warn(logSender, "", "error loading configuration file: %v. Default configuration will be used: %+v",
  156. err, getRedactedGlobalConf())
  157. logger.WarnToConsole("error loading configuration file: %v. Default configuration will be used.", err)
  158. return err
  159. }
  160. err = viper.Unmarshal(&globalConf)
  161. if err != nil {
  162. logger.Warn(logSender, "", "error parsing configuration file: %v. Default configuration will be used: %+v",
  163. err, getRedactedGlobalConf())
  164. logger.WarnToConsole("error parsing configuration file: %v. Default configuration will be used.", err)
  165. return err
  166. }
  167. if strings.TrimSpace(globalConf.SFTPD.Banner) == "" {
  168. globalConf.SFTPD.Banner = defaultBanner
  169. }
  170. if len(globalConf.ProviderConf.UsersBaseDir) > 0 && !utils.IsFileInputValid(globalConf.ProviderConf.UsersBaseDir) {
  171. err = fmt.Errorf("invalid users base dir %#v will be ignored", globalConf.ProviderConf.UsersBaseDir)
  172. globalConf.ProviderConf.UsersBaseDir = ""
  173. logger.Warn(logSender, "", "Configuration error: %v", err)
  174. logger.WarnToConsole("Configuration error: %v", err)
  175. }
  176. if globalConf.SFTPD.UploadMode < 0 || globalConf.SFTPD.UploadMode > 2 {
  177. err = fmt.Errorf("invalid upload_mode 0, 1 and 2 are supported, configured: %v reset upload_mode to 0",
  178. globalConf.SFTPD.UploadMode)
  179. globalConf.SFTPD.UploadMode = 0
  180. logger.Warn(logSender, "", "Configuration error: %v", err)
  181. logger.WarnToConsole("Configuration error: %v", err)
  182. }
  183. if globalConf.SFTPD.ProxyProtocol < 0 || globalConf.SFTPD.ProxyProtocol > 2 {
  184. err = fmt.Errorf("invalid proxy_protocol 0, 1 and 2 are supported, configured: %v reset proxy_protocol to 0",
  185. globalConf.SFTPD.ProxyProtocol)
  186. globalConf.SFTPD.ProxyProtocol = 0
  187. logger.Warn(logSender, "", "Configuration error: %v", err)
  188. logger.WarnToConsole("Configuration error: %v", err)
  189. }
  190. if globalConf.ProviderConf.ExternalAuthScope < 0 || globalConf.ProviderConf.ExternalAuthScope > 7 {
  191. err = fmt.Errorf("invalid external_auth_scope: %v reset to 0", globalConf.ProviderConf.ExternalAuthScope)
  192. globalConf.ProviderConf.ExternalAuthScope = 0
  193. logger.Warn(logSender, "", "Configuration error: %v", err)
  194. logger.WarnToConsole("Configuration error: %v", err)
  195. }
  196. if len(globalConf.ProviderConf.CredentialsPath) == 0 {
  197. err = fmt.Errorf("invalid credentials path, reset to \"credentials\"")
  198. globalConf.ProviderConf.CredentialsPath = "credentials"
  199. logger.Warn(logSender, "", "Configuration error: %v", err)
  200. logger.WarnToConsole("Configuration error: %v", err)
  201. }
  202. checkHooksCompatibility()
  203. checkHostKeyCompatibility()
  204. logger.Debug(logSender, "", "config file used: '%#v', config loaded: %+v", viper.ConfigFileUsed(), getRedactedGlobalConf())
  205. return err
  206. }
  207. func checkHooksCompatibility() {
  208. // we copy deprecated fields to new ones to keep backward compatibility so lint is disabled
  209. if len(globalConf.ProviderConf.ExternalAuthProgram) > 0 && len(globalConf.ProviderConf.ExternalAuthHook) == 0 { //nolint:staticcheck
  210. logger.Warn(logSender, "", "external_auth_program is deprecated, please use external_auth_hook")
  211. logger.WarnToConsole("external_auth_program is deprecated, please use external_auth_hook")
  212. globalConf.ProviderConf.ExternalAuthHook = globalConf.ProviderConf.ExternalAuthProgram //nolint:staticcheck
  213. }
  214. if len(globalConf.ProviderConf.PreLoginProgram) > 0 && len(globalConf.ProviderConf.PreLoginHook) == 0 { //nolint:staticcheck
  215. logger.Warn(logSender, "", "pre_login_program is deprecated, please use pre_login_hook")
  216. logger.WarnToConsole("pre_login_program is deprecated, please use pre_login_hook")
  217. globalConf.ProviderConf.PreLoginHook = globalConf.ProviderConf.PreLoginProgram //nolint:staticcheck
  218. }
  219. if len(globalConf.SFTPD.KeyboardInteractiveProgram) > 0 && len(globalConf.SFTPD.KeyboardInteractiveHook) == 0 { //nolint:staticcheck
  220. logger.Warn(logSender, "", "keyboard_interactive_auth_program is deprecated, please use keyboard_interactive_auth_hook")
  221. logger.WarnToConsole("keyboard_interactive_auth_program is deprecated, please use keyboard_interactive_auth_hook")
  222. globalConf.SFTPD.KeyboardInteractiveHook = globalConf.SFTPD.KeyboardInteractiveProgram //nolint:staticcheck
  223. }
  224. if len(globalConf.SFTPD.Actions.Hook) == 0 {
  225. if len(globalConf.SFTPD.Actions.HTTPNotificationURL) > 0 { //nolint:staticcheck
  226. logger.Warn(logSender, "", "http_notification_url is deprecated, please use hook")
  227. logger.WarnToConsole("http_notification_url is deprecated, please use hook")
  228. globalConf.SFTPD.Actions.Hook = globalConf.SFTPD.Actions.HTTPNotificationURL //nolint:staticcheck
  229. } else if len(globalConf.SFTPD.Actions.Command) > 0 { //nolint:staticcheck
  230. logger.Warn(logSender, "", "command is deprecated, please use hook")
  231. logger.WarnToConsole("command is deprecated, please use hook")
  232. globalConf.SFTPD.Actions.Hook = globalConf.SFTPD.Actions.Command //nolint:staticcheck
  233. }
  234. }
  235. if len(globalConf.ProviderConf.Actions.Hook) == 0 {
  236. if len(globalConf.ProviderConf.Actions.HTTPNotificationURL) > 0 { //nolint:staticcheck
  237. logger.Warn(logSender, "", "http_notification_url is deprecated, please use hook")
  238. logger.WarnToConsole("http_notification_url is deprecated, please use hook")
  239. globalConf.ProviderConf.Actions.Hook = globalConf.ProviderConf.Actions.HTTPNotificationURL //nolint:staticcheck
  240. } else if len(globalConf.ProviderConf.Actions.Command) > 0 { //nolint:staticcheck
  241. logger.Warn(logSender, "", "command is deprecated, please use hook")
  242. logger.WarnToConsole("command is deprecated, please use hook")
  243. globalConf.ProviderConf.Actions.Hook = globalConf.ProviderConf.Actions.Command //nolint:staticcheck
  244. }
  245. }
  246. }
  247. func checkHostKeyCompatibility() {
  248. // we copy deprecated fields to new ones to keep backward compatibility so lint is disabled
  249. if len(globalConf.SFTPD.Keys) > 0 && len(globalConf.SFTPD.HostKeys) == 0 { //nolint:staticcheck
  250. logger.Warn(logSender, "", "keys is deprecated, please use host_keys")
  251. logger.WarnToConsole("keys is deprecated, please use host_keys")
  252. for _, k := range globalConf.SFTPD.Keys { //nolint:staticcheck
  253. globalConf.SFTPD.HostKeys = append(globalConf.SFTPD.HostKeys, k.PrivateKey)
  254. }
  255. }
  256. }