httpd.go 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640
  1. // Package httpd implements REST API and Web interface for SFTPGo.
  2. // The OpenAPI 3 schema for the exposed API can be found inside the source tree:
  3. // https://github.com/drakkan/sftpgo/blob/main/httpd/schema/openapi.yaml
  4. package httpd
  5. import (
  6. "crypto/sha256"
  7. "fmt"
  8. "net"
  9. "net/http"
  10. "net/url"
  11. "path"
  12. "path/filepath"
  13. "runtime"
  14. "strings"
  15. "sync"
  16. "time"
  17. "github.com/go-chi/chi/v5"
  18. "github.com/go-chi/jwtauth/v5"
  19. "github.com/lestrrat-go/jwx/jwa"
  20. "github.com/drakkan/sftpgo/v2/common"
  21. "github.com/drakkan/sftpgo/v2/dataprovider"
  22. "github.com/drakkan/sftpgo/v2/ftpd"
  23. "github.com/drakkan/sftpgo/v2/logger"
  24. "github.com/drakkan/sftpgo/v2/mfa"
  25. "github.com/drakkan/sftpgo/v2/sftpd"
  26. "github.com/drakkan/sftpgo/v2/util"
  27. "github.com/drakkan/sftpgo/v2/webdavd"
  28. )
  29. const (
  30. logSender = "httpd"
  31. tokenPath = "/api/v2/token"
  32. logoutPath = "/api/v2/logout"
  33. userTokenPath = "/api/v2/user/token"
  34. userLogoutPath = "/api/v2/user/logout"
  35. activeConnectionsPath = "/api/v2/connections"
  36. quotasBasePath = "/api/v2/quotas"
  37. quotaScanPath = "/api/v2/quota-scans"
  38. quotaScanVFolderPath = "/api/v2/folder-quota-scans"
  39. userPath = "/api/v2/users"
  40. versionPath = "/api/v2/version"
  41. folderPath = "/api/v2/folders"
  42. serverStatusPath = "/api/v2/status"
  43. dumpDataPath = "/api/v2/dumpdata"
  44. loadDataPath = "/api/v2/loaddata"
  45. updateUsedQuotaPath = "/api/v2/quota-update"
  46. updateFolderUsedQuotaPath = "/api/v2/folder-quota-update"
  47. defenderHosts = "/api/v2/defender/hosts"
  48. defenderBanTime = "/api/v2/defender/bantime"
  49. defenderUnban = "/api/v2/defender/unban"
  50. defenderScore = "/api/v2/defender/score"
  51. adminPath = "/api/v2/admins"
  52. adminPwdPath = "/api/v2/admin/changepwd"
  53. adminPwdCompatPath = "/api/v2/changepwd/admin"
  54. adminManageAPIKeyPath = "/api/v2/admin/apikeyauth"
  55. userPwdPath = "/api/v2/user/changepwd"
  56. userPublicKeysPath = "/api/v2/user/publickeys"
  57. userFolderPath = "/api/v2/user/folder"
  58. userDirsPath = "/api/v2/user/dirs"
  59. userFilePath = "/api/v2/user/file"
  60. userFilesPath = "/api/v2/user/files"
  61. userStreamZipPath = "/api/v2/user/streamzip"
  62. apiKeysPath = "/api/v2/apikeys"
  63. adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
  64. adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
  65. adminTOTPValidatePath = "/api/v2/admin/totp/validate"
  66. adminTOTPSavePath = "/api/v2/admin/totp/save"
  67. admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
  68. userTOTPConfigsPath = "/api/v2/user/totp/configs"
  69. userTOTPGeneratePath = "/api/v2/user/totp/generate"
  70. userTOTPValidatePath = "/api/v2/user/totp/validate"
  71. userTOTPSavePath = "/api/v2/user/totp/save"
  72. user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
  73. userManageAPIKeyPath = "/api/v2/user/apikeyauth"
  74. retentionBasePath = "/api/v2/retention/users"
  75. retentionChecksPath = "/api/v2/retention/users/checks"
  76. healthzPath = "/healthz"
  77. webRootPathDefault = "/"
  78. webBasePathDefault = "/web"
  79. webBasePathAdminDefault = "/web/admin"
  80. webBasePathClientDefault = "/web/client"
  81. webAdminSetupPathDefault = "/web/admin/setup"
  82. webLoginPathDefault = "/web/admin/login"
  83. webAdminTwoFactorPathDefault = "/web/admin/twofactor"
  84. webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
  85. webLogoutPathDefault = "/web/admin/logout"
  86. webUsersPathDefault = "/web/admin/users"
  87. webUserPathDefault = "/web/admin/user"
  88. webConnectionsPathDefault = "/web/admin/connections"
  89. webFoldersPathDefault = "/web/admin/folders"
  90. webFolderPathDefault = "/web/admin/folder"
  91. webStatusPathDefault = "/web/admin/status"
  92. webAdminsPathDefault = "/web/admin/managers"
  93. webAdminPathDefault = "/web/admin/manager"
  94. webMaintenancePathDefault = "/web/admin/maintenance"
  95. webBackupPathDefault = "/web/admin/backup"
  96. webRestorePathDefault = "/web/admin/restore"
  97. webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
  98. webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
  99. webChangeAdminPwdPathDefault = "/web/admin/changepwd"
  100. webAdminCredentialsPathDefault = "/web/admin/credentials"
  101. webAdminMFAPathDefault = "/web/admin/mfa"
  102. webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
  103. webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
  104. webAdminTOTPSavePathDefault = "/web/admin/totp/save"
  105. webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
  106. webChangeAdminAPIKeyAccessPathDefault = "/web/admin/apikeyaccess"
  107. webTemplateUserDefault = "/web/admin/template/user"
  108. webTemplateFolderDefault = "/web/admin/template/folder"
  109. webDefenderPathDefault = "/web/admin/defender"
  110. webDefenderHostsPathDefault = "/web/admin/defender/hosts"
  111. webClientLoginPathDefault = "/web/client/login"
  112. webClientTwoFactorPathDefault = "/web/client/twofactor"
  113. webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
  114. webClientFilesPathDefault = "/web/client/files"
  115. webClientDirsPathDefault = "/web/client/dirs"
  116. webClientDownloadZipPathDefault = "/web/client/downloadzip"
  117. webClientCredentialsPathDefault = "/web/client/credentials"
  118. webClientMFAPathDefault = "/web/client/mfa"
  119. webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
  120. webClientTOTPValidatePathDefault = "/web/client/totp/validate"
  121. webClientTOTPSavePathDefault = "/web/client/totp/save"
  122. webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
  123. webChangeClientPwdPathDefault = "/web/client/changepwd"
  124. webChangeClientKeysPathDefault = "/web/client/managekeys"
  125. webChangeClientAPIKeyAccessPathDefault = "/web/client/apikeyaccess"
  126. webClientLogoutPathDefault = "/web/client/logout"
  127. webStaticFilesPathDefault = "/static"
  128. // MaxRestoreSize defines the max size for the loaddata input file
  129. MaxRestoreSize = 10485760 // 10 MB
  130. maxRequestSize = 1048576 // 1MB
  131. maxLoginBodySize = 262144 // 256 KB
  132. maxMultipartMem = 8388608 // 8MB
  133. osWindows = "windows"
  134. otpHeaderCode = "X-SFTPGO-OTP"
  135. )
  136. var (
  137. backupsPath string
  138. certMgr *common.CertManager
  139. cleanupTicker *time.Ticker
  140. cleanupDone chan bool
  141. invalidatedJWTTokens sync.Map
  142. csrfTokenAuth *jwtauth.JWTAuth
  143. webRootPath string
  144. webBasePath string
  145. webBaseAdminPath string
  146. webBaseClientPath string
  147. webAdminSetupPath string
  148. webLoginPath string
  149. webAdminTwoFactorPath string
  150. webAdminTwoFactorRecoveryPath string
  151. webLogoutPath string
  152. webUsersPath string
  153. webUserPath string
  154. webConnectionsPath string
  155. webFoldersPath string
  156. webFolderPath string
  157. webStatusPath string
  158. webAdminsPath string
  159. webAdminPath string
  160. webMaintenancePath string
  161. webBackupPath string
  162. webRestorePath string
  163. webScanVFolderPath string
  164. webQuotaScanPath string
  165. webAdminCredentialsPath string
  166. webAdminMFAPath string
  167. webAdminTOTPGeneratePath string
  168. webAdminTOTPValidatePath string
  169. webAdminTOTPSavePath string
  170. webAdminRecoveryCodesPath string
  171. webChangeAdminAPIKeyAccessPath string
  172. webChangeAdminPwdPath string
  173. webTemplateUser string
  174. webTemplateFolder string
  175. webDefenderPath string
  176. webDefenderHostsPath string
  177. webClientLoginPath string
  178. webClientTwoFactorPath string
  179. webClientTwoFactorRecoveryPath string
  180. webClientFilesPath string
  181. webClientDirsPath string
  182. webClientDownloadZipPath string
  183. webClientCredentialsPath string
  184. webChangeClientPwdPath string
  185. webChangeClientKeysPath string
  186. webClientMFAPath string
  187. webClientTOTPGeneratePath string
  188. webClientTOTPValidatePath string
  189. webClientTOTPSavePath string
  190. webClientRecoveryCodesPath string
  191. webChangeClientAPIKeyAccessPath string
  192. webClientLogoutPath string
  193. webStaticFilesPath string
  194. // max upload size for http clients, 1GB by default
  195. maxUploadFileSize = int64(1048576000)
  196. )
  197. func init() {
  198. updateWebAdminURLs("")
  199. updateWebClientURLs("")
  200. }
  201. // Binding defines the configuration for a network listener
  202. type Binding struct {
  203. // The address to listen on. A blank value means listen on all available network interfaces.
  204. Address string `json:"address" mapstructure:"address"`
  205. // The port used for serving requests
  206. Port int `json:"port" mapstructure:"port"`
  207. // Enable the built-in admin interface.
  208. // You have to define TemplatesPath and StaticFilesPath for this to work
  209. EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
  210. // Enable the built-in client interface.
  211. // You have to define TemplatesPath and StaticFilesPath for this to work
  212. EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
  213. // you also need to provide a certificate for enabling HTTPS
  214. EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
  215. // set to 1 to require client certificate authentication in addition to basic auth.
  216. // You need to define at least a certificate authority for this to work
  217. ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
  218. // TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
  219. // If CipherSuites is nil/empty, a default list of secure cipher suites
  220. // is used, with a preference order based on hardware performance.
  221. // Note that TLS 1.3 ciphersuites are not configurable.
  222. // The supported ciphersuites names are defined here:
  223. //
  224. // https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L52
  225. //
  226. // any invalid name will be silently ignored.
  227. // The order matters, the ciphers listed first will be the preferred ones.
  228. TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
  229. // List of IP addresses and IP ranges allowed to set X-Forwarded-For, X-Real-IP,
  230. // X-Forwarded-Proto headers.
  231. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  232. // If both web admin and web client are enabled each login page will show a link
  233. // to the other one. This setting allows to hide this link:
  234. // - 0 login links are displayed on both admin and client login page. This is the default
  235. // - 1 the login link to the web client login page is hidden on admin login page
  236. // - 2 the login link to the web admin login page is hidden on client login page
  237. // The flags can be combined, for example 3 will disable both login links.
  238. HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
  239. allowHeadersFrom []func(net.IP) bool
  240. }
  241. func (b *Binding) parseAllowedProxy() error {
  242. allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed)
  243. if err != nil {
  244. return err
  245. }
  246. b.allowHeadersFrom = allowedFuncs
  247. return nil
  248. }
  249. // GetAddress returns the binding address
  250. func (b *Binding) GetAddress() string {
  251. return fmt.Sprintf("%s:%d", b.Address, b.Port)
  252. }
  253. // IsValid returns true if the binding is valid
  254. func (b *Binding) IsValid() bool {
  255. if b.Port > 0 {
  256. return true
  257. }
  258. if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
  259. return true
  260. }
  261. return false
  262. }
  263. func (b *Binding) showAdminLoginURL() bool {
  264. if !b.EnableWebAdmin {
  265. return false
  266. }
  267. if b.HideLoginURL&2 != 0 {
  268. return false
  269. }
  270. return true
  271. }
  272. func (b *Binding) showClientLoginURL() bool {
  273. if !b.EnableWebClient {
  274. return false
  275. }
  276. if b.HideLoginURL&1 != 0 {
  277. return false
  278. }
  279. return true
  280. }
  281. type defenderStatus struct {
  282. IsActive bool `json:"is_active"`
  283. }
  284. // ServicesStatus keep the state of the running services
  285. type ServicesStatus struct {
  286. SSH sftpd.ServiceStatus `json:"ssh"`
  287. FTP ftpd.ServiceStatus `json:"ftp"`
  288. WebDAV webdavd.ServiceStatus `json:"webdav"`
  289. DataProvider dataprovider.ProviderStatus `json:"data_provider"`
  290. Defender defenderStatus `json:"defender"`
  291. MFA mfa.ServiceStatus `json:"mfa"`
  292. }
  293. // Conf httpd daemon configuration
  294. type Conf struct {
  295. // Addresses and ports to bind to
  296. Bindings []Binding `json:"bindings" mapstructure:"bindings"`
  297. // Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
  298. TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
  299. // Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
  300. // If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
  301. StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
  302. // Path to the backup directory. This can be an absolute path or a path relative to the config dir
  303. BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
  304. // Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will
  305. // be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored.
  306. WebRoot string `json:"web_root" mapstructure:"web_root"`
  307. // If files containing a certificate and matching private key for the server are provided the server will expect
  308. // HTTPS connections.
  309. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  310. // "paramchange" request to the running service on Windows.
  311. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  312. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  313. // CACertificates defines the set of root certificate authorities to be used to verify client certificates.
  314. CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
  315. // CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
  316. // if a client certificate has been revoked
  317. CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
  318. // SigningPassphrase defines the passphrase to use to derive the signing key for JWT and CSRF tokens.
  319. // If empty a random signing key will be generated each time SFTPGo starts. If you set a
  320. // signing passphrase you should consider rotating it periodically for added security
  321. SigningPassphrase string `json:"signing_passphrase" mapstructure:"signing_passphrase"`
  322. // MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
  323. // 0 means no limit
  324. MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
  325. }
  326. type apiResponse struct {
  327. Error string `json:"error,omitempty"`
  328. Message string `json:"message"`
  329. }
  330. // ShouldBind returns true if there is at least a valid binding
  331. func (c *Conf) ShouldBind() bool {
  332. for _, binding := range c.Bindings {
  333. if binding.IsValid() {
  334. return true
  335. }
  336. }
  337. return false
  338. }
  339. func (c *Conf) isWebAdminEnabled() bool {
  340. for _, binding := range c.Bindings {
  341. if binding.EnableWebAdmin {
  342. return true
  343. }
  344. }
  345. return false
  346. }
  347. func (c *Conf) isWebClientEnabled() bool {
  348. for _, binding := range c.Bindings {
  349. if binding.EnableWebClient {
  350. return true
  351. }
  352. }
  353. return false
  354. }
  355. func (c *Conf) checkRequiredDirs(staticFilesPath, templatesPath string) error {
  356. if (c.isWebAdminEnabled() || c.isWebClientEnabled()) && (staticFilesPath == "" || templatesPath == "") {
  357. return fmt.Errorf("required directory is invalid, static file path: %#v template path: %#v",
  358. staticFilesPath, templatesPath)
  359. }
  360. return nil
  361. }
  362. func (c *Conf) getRedacted() Conf {
  363. conf := *c
  364. conf.SigningPassphrase = "[redacted]"
  365. return conf
  366. }
  367. // Initialize configures and starts the HTTP server
  368. func (c *Conf) Initialize(configDir string) error {
  369. logger.Debug(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
  370. backupsPath = getConfigPath(c.BackupsPath, configDir)
  371. staticFilesPath := getConfigPath(c.StaticFilesPath, configDir)
  372. templatesPath := getConfigPath(c.TemplatesPath, configDir)
  373. if backupsPath == "" {
  374. return fmt.Errorf("required directory is invalid, backup path %#v", backupsPath)
  375. }
  376. if err := c.checkRequiredDirs(staticFilesPath, templatesPath); err != nil {
  377. return err
  378. }
  379. certificateFile := getConfigPath(c.CertificateFile, configDir)
  380. certificateKeyFile := getConfigPath(c.CertificateKeyFile, configDir)
  381. if c.isWebAdminEnabled() {
  382. updateWebAdminURLs(c.WebRoot)
  383. loadAdminTemplates(templatesPath)
  384. } else {
  385. logger.Info(logSender, "", "built-in web admin interface disabled")
  386. }
  387. if c.isWebClientEnabled() {
  388. updateWebClientURLs(c.WebRoot)
  389. loadClientTemplates(templatesPath)
  390. } else {
  391. logger.Info(logSender, "", "built-in web client interface disabled")
  392. }
  393. if certificateFile != "" && certificateKeyFile != "" {
  394. mgr, err := common.NewCertManager(certificateFile, certificateKeyFile, configDir, logSender)
  395. if err != nil {
  396. return err
  397. }
  398. mgr.SetCACertificates(c.CACertificates)
  399. if err := mgr.LoadRootCAs(); err != nil {
  400. return err
  401. }
  402. mgr.SetCARevocationLists(c.CARevocationLists)
  403. if err := mgr.LoadCRLs(); err != nil {
  404. return err
  405. }
  406. certMgr = mgr
  407. }
  408. csrfTokenAuth = jwtauth.New(jwa.HS256.String(), getSigningKey(c.SigningPassphrase), nil)
  409. exitChannel := make(chan error, 1)
  410. for _, binding := range c.Bindings {
  411. if !binding.IsValid() {
  412. continue
  413. }
  414. if err := binding.parseAllowedProxy(); err != nil {
  415. return err
  416. }
  417. go func(b Binding) {
  418. server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase)
  419. exitChannel <- server.listenAndServe()
  420. }(binding)
  421. }
  422. maxUploadFileSize = c.MaxUploadFileSize
  423. startCleanupTicker(tokenDuration)
  424. return <-exitChannel
  425. }
  426. func isWebRequest(r *http.Request) bool {
  427. return strings.HasPrefix(r.RequestURI, webBasePath+"/")
  428. }
  429. func isWebClientRequest(r *http.Request) bool {
  430. return strings.HasPrefix(r.RequestURI, webBaseClientPath+"/")
  431. }
  432. // ReloadCertificateMgr reloads the certificate manager
  433. func ReloadCertificateMgr() error {
  434. if certMgr != nil {
  435. return certMgr.Reload()
  436. }
  437. return nil
  438. }
  439. func getConfigPath(name, configDir string) string {
  440. if !util.IsFileInputValid(name) {
  441. return ""
  442. }
  443. if name != "" && !filepath.IsAbs(name) {
  444. return filepath.Join(configDir, name)
  445. }
  446. return name
  447. }
  448. func getServicesStatus() ServicesStatus {
  449. status := ServicesStatus{
  450. SSH: sftpd.GetStatus(),
  451. FTP: ftpd.GetStatus(),
  452. WebDAV: webdavd.GetStatus(),
  453. DataProvider: dataprovider.GetProviderStatus(),
  454. Defender: defenderStatus{
  455. IsActive: common.Config.DefenderConfig.Enabled,
  456. },
  457. MFA: mfa.GetStatus(),
  458. }
  459. return status
  460. }
  461. func getURLParam(r *http.Request, key string) string {
  462. v := chi.URLParam(r, key)
  463. unescaped, err := url.PathUnescape(v)
  464. if err != nil {
  465. return v
  466. }
  467. return unescaped
  468. }
  469. func fileServer(r chi.Router, path string, root http.FileSystem) {
  470. if path != "/" && path[len(path)-1] != '/' {
  471. r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
  472. path += "/"
  473. }
  474. path += "*"
  475. r.Get(path, func(w http.ResponseWriter, r *http.Request) {
  476. rctx := chi.RouteContext(r.Context())
  477. pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
  478. fs := http.StripPrefix(pathPrefix, http.FileServer(root))
  479. fs.ServeHTTP(w, r)
  480. })
  481. }
  482. func updateWebClientURLs(baseURL string) {
  483. if !path.IsAbs(baseURL) {
  484. baseURL = "/"
  485. }
  486. webRootPath = path.Join(baseURL, webRootPathDefault)
  487. webBasePath = path.Join(baseURL, webBasePathDefault)
  488. webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
  489. webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
  490. webClientTwoFactorPath = path.Join(baseURL, webClientTwoFactorPathDefault)
  491. webClientTwoFactorRecoveryPath = path.Join(baseURL, webClientTwoFactorRecoveryPathDefault)
  492. webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
  493. webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
  494. webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
  495. webClientCredentialsPath = path.Join(baseURL, webClientCredentialsPathDefault)
  496. webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
  497. webChangeClientKeysPath = path.Join(baseURL, webChangeClientKeysPathDefault)
  498. webChangeClientAPIKeyAccessPath = path.Join(baseURL, webChangeClientAPIKeyAccessPathDefault)
  499. webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
  500. webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
  501. webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
  502. webClientTOTPValidatePath = path.Join(baseURL, webClientTOTPValidatePathDefault)
  503. webClientTOTPSavePath = path.Join(baseURL, webClientTOTPSavePathDefault)
  504. webClientRecoveryCodesPath = path.Join(baseURL, webClientRecoveryCodesPathDefault)
  505. }
  506. func updateWebAdminURLs(baseURL string) {
  507. if !path.IsAbs(baseURL) {
  508. baseURL = "/"
  509. }
  510. webRootPath = path.Join(baseURL, webRootPathDefault)
  511. webBasePath = path.Join(baseURL, webBasePathDefault)
  512. webBaseAdminPath = path.Join(baseURL, webBasePathAdminDefault)
  513. webAdminSetupPath = path.Join(baseURL, webAdminSetupPathDefault)
  514. webLoginPath = path.Join(baseURL, webLoginPathDefault)
  515. webAdminTwoFactorPath = path.Join(baseURL, webAdminTwoFactorPathDefault)
  516. webAdminTwoFactorRecoveryPath = path.Join(baseURL, webAdminTwoFactorRecoveryPathDefault)
  517. webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
  518. webUsersPath = path.Join(baseURL, webUsersPathDefault)
  519. webUserPath = path.Join(baseURL, webUserPathDefault)
  520. webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
  521. webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
  522. webFolderPath = path.Join(baseURL, webFolderPathDefault)
  523. webStatusPath = path.Join(baseURL, webStatusPathDefault)
  524. webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
  525. webAdminPath = path.Join(baseURL, webAdminPathDefault)
  526. webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
  527. webBackupPath = path.Join(baseURL, webBackupPathDefault)
  528. webRestorePath = path.Join(baseURL, webRestorePathDefault)
  529. webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
  530. webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
  531. webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
  532. webAdminCredentialsPath = path.Join(baseURL, webAdminCredentialsPathDefault)
  533. webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
  534. webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
  535. webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
  536. webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
  537. webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
  538. webChangeAdminAPIKeyAccessPath = path.Join(baseURL, webChangeAdminAPIKeyAccessPathDefault)
  539. webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
  540. webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
  541. webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
  542. webDefenderPath = path.Join(baseURL, webDefenderPathDefault)
  543. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  544. }
  545. // GetHTTPRouter returns an HTTP handler suitable to use for test cases
  546. func GetHTTPRouter() http.Handler {
  547. b := Binding{
  548. Address: "",
  549. Port: 8080,
  550. EnableWebAdmin: true,
  551. EnableWebClient: true,
  552. }
  553. server := newHttpdServer(b, "../static", "")
  554. server.initializeRouter()
  555. return server.router
  556. }
  557. // the ticker cannot be started/stopped from multiple goroutines
  558. func startCleanupTicker(duration time.Duration) {
  559. stopCleanupTicker()
  560. cleanupTicker = time.NewTicker(duration)
  561. cleanupDone = make(chan bool)
  562. go func() {
  563. for {
  564. select {
  565. case <-cleanupDone:
  566. return
  567. case <-cleanupTicker.C:
  568. cleanupExpiredJWTTokens()
  569. }
  570. }
  571. }()
  572. }
  573. func stopCleanupTicker() {
  574. if cleanupTicker != nil {
  575. cleanupTicker.Stop()
  576. cleanupDone <- true
  577. cleanupTicker = nil
  578. }
  579. }
  580. func cleanupExpiredJWTTokens() {
  581. invalidatedJWTTokens.Range(func(key, value interface{}) bool {
  582. exp, ok := value.(time.Time)
  583. if !ok || exp.Before(time.Now().UTC()) {
  584. invalidatedJWTTokens.Delete(key)
  585. }
  586. return true
  587. })
  588. }
  589. func getSigningKey(signingPassphrase string) []byte {
  590. if signingPassphrase != "" {
  591. sk := sha256.Sum256([]byte(signingPassphrase))
  592. return sk[:]
  593. }
  594. return util.GenerateRandomBytes(32)
  595. }