httpd.go 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424
  1. // Copyright (C) 2019 Nicola Murino
  2. //
  3. // This program is free software: you can redistribute it and/or modify
  4. // it under the terms of the GNU Affero General Public License as published
  5. // by the Free Software Foundation, version 3.
  6. //
  7. // This program is distributed in the hope that it will be useful,
  8. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. // GNU Affero General Public License for more details.
  11. //
  12. // You should have received a copy of the GNU Affero General Public License
  13. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  14. // Package httpd implements REST API and Web interface for SFTPGo.
  15. // The OpenAPI 3 schema for the supported API can be found inside the source tree:
  16. // https://github.com/drakkan/sftpgo/blob/main/openapi/openapi.yaml
  17. package httpd
  18. import (
  19. "crypto/sha256"
  20. "errors"
  21. "fmt"
  22. "net"
  23. "net/http"
  24. "os"
  25. "path"
  26. "path/filepath"
  27. "runtime"
  28. "strings"
  29. "sync"
  30. "time"
  31. "github.com/go-chi/chi/v5"
  32. "github.com/drakkan/sftpgo/v2/internal/acme"
  33. "github.com/drakkan/sftpgo/v2/internal/common"
  34. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  35. "github.com/drakkan/sftpgo/v2/internal/ftpd"
  36. "github.com/drakkan/sftpgo/v2/internal/logger"
  37. "github.com/drakkan/sftpgo/v2/internal/mfa"
  38. "github.com/drakkan/sftpgo/v2/internal/sftpd"
  39. "github.com/drakkan/sftpgo/v2/internal/util"
  40. "github.com/drakkan/sftpgo/v2/internal/webdavd"
  41. )
  42. const (
  43. logSender = "httpd"
  44. tokenPath = "/api/v2/token"
  45. logoutPath = "/api/v2/logout"
  46. userTokenPath = "/api/v2/user/token"
  47. userLogoutPath = "/api/v2/user/logout"
  48. activeConnectionsPath = "/api/v2/connections"
  49. quotasBasePath = "/api/v2/quotas"
  50. userPath = "/api/v2/users"
  51. versionPath = "/api/v2/version"
  52. folderPath = "/api/v2/folders"
  53. groupPath = "/api/v2/groups"
  54. serverStatusPath = "/api/v2/status"
  55. dumpDataPath = "/api/v2/dumpdata"
  56. loadDataPath = "/api/v2/loaddata"
  57. defenderHosts = "/api/v2/defender/hosts"
  58. adminPath = "/api/v2/admins"
  59. adminPwdPath = "/api/v2/admin/changepwd"
  60. adminProfilePath = "/api/v2/admin/profile"
  61. userPwdPath = "/api/v2/user/changepwd"
  62. userDirsPath = "/api/v2/user/dirs"
  63. userFilesPath = "/api/v2/user/files"
  64. userFileActionsPath = "/api/v2/user/file-actions"
  65. userStreamZipPath = "/api/v2/user/streamzip"
  66. userUploadFilePath = "/api/v2/user/files/upload"
  67. userFilesDirsMetadataPath = "/api/v2/user/files/metadata"
  68. apiKeysPath = "/api/v2/apikeys"
  69. adminTOTPConfigsPath = "/api/v2/admin/totp/configs"
  70. adminTOTPGeneratePath = "/api/v2/admin/totp/generate"
  71. adminTOTPValidatePath = "/api/v2/admin/totp/validate"
  72. adminTOTPSavePath = "/api/v2/admin/totp/save"
  73. admin2FARecoveryCodesPath = "/api/v2/admin/2fa/recoverycodes"
  74. userTOTPConfigsPath = "/api/v2/user/totp/configs"
  75. userTOTPGeneratePath = "/api/v2/user/totp/generate"
  76. userTOTPValidatePath = "/api/v2/user/totp/validate"
  77. userTOTPSavePath = "/api/v2/user/totp/save"
  78. user2FARecoveryCodesPath = "/api/v2/user/2fa/recoverycodes"
  79. userProfilePath = "/api/v2/user/profile"
  80. userSharesPath = "/api/v2/user/shares"
  81. retentionBasePath = "/api/v2/retention/users"
  82. retentionChecksPath = "/api/v2/retention/users/checks"
  83. fsEventsPath = "/api/v2/events/fs"
  84. providerEventsPath = "/api/v2/events/provider"
  85. logEventsPath = "/api/v2/events/logs"
  86. sharesPath = "/api/v2/shares"
  87. eventActionsPath = "/api/v2/eventactions"
  88. eventRulesPath = "/api/v2/eventrules"
  89. rolesPath = "/api/v2/roles"
  90. ipListsPath = "/api/v2/iplists"
  91. healthzPath = "/healthz"
  92. webRootPathDefault = "/"
  93. webBasePathDefault = "/web"
  94. webBasePathAdminDefault = "/web/admin"
  95. webBasePathClientDefault = "/web/client"
  96. webAdminSetupPathDefault = "/web/admin/setup"
  97. webAdminLoginPathDefault = "/web/admin/login"
  98. webAdminOIDCLoginPathDefault = "/web/admin/oidclogin"
  99. webOIDCRedirectPathDefault = "/web/oidc/redirect"
  100. webOAuth2RedirectPathDefault = "/web/oauth2/redirect"
  101. webOAuth2TokenPathDefault = "/web/admin/oauth2/token"
  102. webAdminTwoFactorPathDefault = "/web/admin/twofactor"
  103. webAdminTwoFactorRecoveryPathDefault = "/web/admin/twofactor-recovery"
  104. webLogoutPathDefault = "/web/admin/logout"
  105. webUsersPathDefault = "/web/admin/users"
  106. webUserPathDefault = "/web/admin/user"
  107. webConnectionsPathDefault = "/web/admin/connections"
  108. webFoldersPathDefault = "/web/admin/folders"
  109. webFolderPathDefault = "/web/admin/folder"
  110. webGroupsPathDefault = "/web/admin/groups"
  111. webGroupPathDefault = "/web/admin/group"
  112. webStatusPathDefault = "/web/admin/status"
  113. webAdminsPathDefault = "/web/admin/managers"
  114. webAdminPathDefault = "/web/admin/manager"
  115. webMaintenancePathDefault = "/web/admin/maintenance"
  116. webBackupPathDefault = "/web/admin/backup"
  117. webRestorePathDefault = "/web/admin/restore"
  118. webScanVFolderPathDefault = "/web/admin/quotas/scanfolder"
  119. webQuotaScanPathDefault = "/web/admin/quotas/scanuser"
  120. webChangeAdminPwdPathDefault = "/web/admin/changepwd"
  121. webAdminForgotPwdPathDefault = "/web/admin/forgot-password"
  122. webAdminResetPwdPathDefault = "/web/admin/reset-password"
  123. webAdminProfilePathDefault = "/web/admin/profile"
  124. webAdminMFAPathDefault = "/web/admin/mfa"
  125. webAdminEventRulesPathDefault = "/web/admin/eventrules"
  126. webAdminEventRulePathDefault = "/web/admin/eventrule"
  127. webAdminEventActionsPathDefault = "/web/admin/eventactions"
  128. webAdminEventActionPathDefault = "/web/admin/eventaction"
  129. webAdminRolesPathDefault = "/web/admin/roles"
  130. webAdminRolePathDefault = "/web/admin/role"
  131. webAdminTOTPGeneratePathDefault = "/web/admin/totp/generate"
  132. webAdminTOTPValidatePathDefault = "/web/admin/totp/validate"
  133. webAdminTOTPSavePathDefault = "/web/admin/totp/save"
  134. webAdminRecoveryCodesPathDefault = "/web/admin/recoverycodes"
  135. webTemplateUserDefault = "/web/admin/template/user"
  136. webTemplateFolderDefault = "/web/admin/template/folder"
  137. webDefenderPathDefault = "/web/admin/defender"
  138. webIPListsPathDefault = "/web/admin/ip-lists"
  139. webIPListPathDefault = "/web/admin/ip-list"
  140. webDefenderHostsPathDefault = "/web/admin/defender/hosts"
  141. webEventsPathDefault = "/web/admin/events"
  142. webEventsFsSearchPathDefault = "/web/admin/events/fs"
  143. webEventsProviderSearchPathDefault = "/web/admin/events/provider"
  144. webEventsLogSearchPathDefault = "/web/admin/events/logs"
  145. webConfigsPathDefault = "/web/admin/configs"
  146. webClientLoginPathDefault = "/web/client/login"
  147. webClientOIDCLoginPathDefault = "/web/client/oidclogin"
  148. webClientTwoFactorPathDefault = "/web/client/twofactor"
  149. webClientTwoFactorRecoveryPathDefault = "/web/client/twofactor-recovery"
  150. webClientFilesPathDefault = "/web/client/files"
  151. webClientFilePathDefault = "/web/client/file"
  152. webClientFileActionsPathDefault = "/web/client/file-actions"
  153. webClientSharesPathDefault = "/web/client/shares"
  154. webClientSharePathDefault = "/web/client/share"
  155. webClientEditFilePathDefault = "/web/client/editfile"
  156. webClientDirsPathDefault = "/web/client/dirs"
  157. webClientDownloadZipPathDefault = "/web/client/downloadzip"
  158. webClientProfilePathDefault = "/web/client/profile"
  159. webClientPingPathDefault = "/web/client/ping"
  160. webClientMFAPathDefault = "/web/client/mfa"
  161. webClientTOTPGeneratePathDefault = "/web/client/totp/generate"
  162. webClientTOTPValidatePathDefault = "/web/client/totp/validate"
  163. webClientTOTPSavePathDefault = "/web/client/totp/save"
  164. webClientRecoveryCodesPathDefault = "/web/client/recoverycodes"
  165. webChangeClientPwdPathDefault = "/web/client/changepwd"
  166. webClientLogoutPathDefault = "/web/client/logout"
  167. webClientPubSharesPathDefault = "/web/client/pubshares"
  168. webClientForgotPwdPathDefault = "/web/client/forgot-password"
  169. webClientResetPwdPathDefault = "/web/client/reset-password"
  170. webClientViewPDFPathDefault = "/web/client/viewpdf"
  171. webClientGetPDFPathDefault = "/web/client/getpdf"
  172. webClientExistPathDefault = "/web/client/exist"
  173. webClientTasksPathDefault = "/web/client/tasks"
  174. webStaticFilesPathDefault = "/static"
  175. webOpenAPIPathDefault = "/openapi"
  176. // MaxRestoreSize defines the max size for the loaddata input file
  177. MaxRestoreSize = 20 * 1048576 // 20 MB
  178. maxRequestSize = 1048576 // 1MB
  179. maxLoginBodySize = 262144 // 256 KB
  180. httpdMaxEditFileSize = 2 * 1048576 // 2 MB
  181. maxMultipartMem = 10 * 1048576 // 10 MB
  182. osWindows = "windows"
  183. otpHeaderCode = "X-SFTPGO-OTP"
  184. mTimeHeader = "X-SFTPGO-MTIME"
  185. acmeChallengeURI = "/.well-known/acme-challenge/"
  186. )
  187. var (
  188. certMgr *common.CertManager
  189. cleanupTicker *time.Ticker
  190. cleanupDone chan bool
  191. invalidatedJWTTokens tokenManager
  192. webRootPath string
  193. webBasePath string
  194. webBaseAdminPath string
  195. webBaseClientPath string
  196. webOIDCRedirectPath string
  197. webOAuth2RedirectPath string
  198. webOAuth2TokenPath string
  199. webAdminSetupPath string
  200. webAdminOIDCLoginPath string
  201. webAdminLoginPath string
  202. webAdminTwoFactorPath string
  203. webAdminTwoFactorRecoveryPath string
  204. webLogoutPath string
  205. webUsersPath string
  206. webUserPath string
  207. webConnectionsPath string
  208. webFoldersPath string
  209. webFolderPath string
  210. webGroupsPath string
  211. webGroupPath string
  212. webStatusPath string
  213. webAdminsPath string
  214. webAdminPath string
  215. webMaintenancePath string
  216. webBackupPath string
  217. webRestorePath string
  218. webScanVFolderPath string
  219. webQuotaScanPath string
  220. webAdminProfilePath string
  221. webAdminMFAPath string
  222. webAdminEventRulesPath string
  223. webAdminEventRulePath string
  224. webAdminEventActionsPath string
  225. webAdminEventActionPath string
  226. webAdminRolesPath string
  227. webAdminRolePath string
  228. webAdminTOTPGeneratePath string
  229. webAdminTOTPValidatePath string
  230. webAdminTOTPSavePath string
  231. webAdminRecoveryCodesPath string
  232. webChangeAdminPwdPath string
  233. webAdminForgotPwdPath string
  234. webAdminResetPwdPath string
  235. webTemplateUser string
  236. webTemplateFolder string
  237. webDefenderPath string
  238. webIPListPath string
  239. webIPListsPath string
  240. webEventsPath string
  241. webEventsFsSearchPath string
  242. webEventsProviderSearchPath string
  243. webEventsLogSearchPath string
  244. webConfigsPath string
  245. webDefenderHostsPath string
  246. webClientLoginPath string
  247. webClientOIDCLoginPath string
  248. webClientTwoFactorPath string
  249. webClientTwoFactorRecoveryPath string
  250. webClientFilesPath string
  251. webClientFilePath string
  252. webClientFileActionsPath string
  253. webClientSharesPath string
  254. webClientSharePath string
  255. webClientEditFilePath string
  256. webClientDirsPath string
  257. webClientDownloadZipPath string
  258. webClientProfilePath string
  259. webClientPingPath string
  260. webChangeClientPwdPath string
  261. webClientMFAPath string
  262. webClientTOTPGeneratePath string
  263. webClientTOTPValidatePath string
  264. webClientTOTPSavePath string
  265. webClientRecoveryCodesPath string
  266. webClientPubSharesPath string
  267. webClientLogoutPath string
  268. webClientForgotPwdPath string
  269. webClientResetPwdPath string
  270. webClientViewPDFPath string
  271. webClientGetPDFPath string
  272. webClientExistPath string
  273. webClientTasksPath string
  274. webStaticFilesPath string
  275. webOpenAPIPath string
  276. // max upload size for http clients, 1GB by default
  277. maxUploadFileSize = int64(1048576000)
  278. hideSupportLink bool
  279. installationCode string
  280. installationCodeHint string
  281. fnInstallationCodeResolver FnInstallationCodeResolver
  282. configurationDir string
  283. dbBrandingConfig brandingCache
  284. )
  285. func init() {
  286. updateWebAdminURLs("")
  287. updateWebClientURLs("")
  288. acme.SetReloadHTTPDCertsFn(ReloadCertificateMgr)
  289. common.SetUpdateBrandingFn(dbBrandingConfig.Set)
  290. }
  291. type brandingCache struct {
  292. mu sync.RWMutex
  293. configs *dataprovider.BrandingConfigs
  294. }
  295. func (b *brandingCache) Set(configs *dataprovider.BrandingConfigs) {
  296. b.mu.Lock()
  297. defer b.mu.Unlock()
  298. b.configs = configs
  299. }
  300. func (b *brandingCache) getWebAdminLogo() []byte {
  301. b.mu.RLock()
  302. defer b.mu.RUnlock()
  303. return b.configs.WebAdmin.Logo
  304. }
  305. func (b *brandingCache) getWebAdminFavicon() []byte {
  306. b.mu.RLock()
  307. defer b.mu.RUnlock()
  308. return b.configs.WebAdmin.Favicon
  309. }
  310. func (b *brandingCache) getWebClientLogo() []byte {
  311. b.mu.RLock()
  312. defer b.mu.RUnlock()
  313. return b.configs.WebClient.Logo
  314. }
  315. func (b *brandingCache) getWebClientFavicon() []byte {
  316. b.mu.RLock()
  317. defer b.mu.RUnlock()
  318. return b.configs.WebClient.Favicon
  319. }
  320. func (b *brandingCache) mergeBrandingConfig(branding UIBranding, isWebClient bool) UIBranding {
  321. b.mu.RLock()
  322. defer b.mu.RUnlock()
  323. var urlPrefix string
  324. var cfg dataprovider.BrandingConfig
  325. if isWebClient {
  326. cfg = b.configs.WebClient
  327. urlPrefix = "webclient"
  328. } else {
  329. cfg = b.configs.WebAdmin
  330. urlPrefix = "webadmin"
  331. }
  332. if cfg.Name != "" {
  333. branding.Name = cfg.Name
  334. }
  335. if cfg.ShortName != "" {
  336. branding.ShortName = cfg.ShortName
  337. }
  338. if cfg.DisclaimerName != "" {
  339. branding.DisclaimerName = cfg.DisclaimerName
  340. }
  341. if cfg.DisclaimerURL != "" {
  342. branding.DisclaimerPath = cfg.DisclaimerURL
  343. }
  344. if len(cfg.Logo) > 0 {
  345. branding.LogoPath = path.Join("/", "branding", urlPrefix, "logo.png")
  346. }
  347. if len(cfg.Favicon) > 0 {
  348. branding.FaviconPath = path.Join("/", "branding", urlPrefix, "favicon.png")
  349. }
  350. return branding
  351. }
  352. // FnInstallationCodeResolver defines a method to get the installation code.
  353. // If the installation code cannot be resolved the provided default must be returned
  354. type FnInstallationCodeResolver func(defaultInstallationCode string) string
  355. // HTTPSProxyHeader defines an HTTPS proxy header as key/value.
  356. // For example Key could be "X-Forwarded-Proto" and Value "https"
  357. type HTTPSProxyHeader struct {
  358. Key string
  359. Value string
  360. }
  361. // SecurityConf allows to add some security related headers to HTTP responses and to restrict allowed hosts
  362. type SecurityConf struct {
  363. // Set to true to enable the security configurations
  364. Enabled bool `json:"enabled" mapstructure:"enabled"`
  365. // AllowedHosts is a list of fully qualified domain names that are allowed.
  366. // Default is empty list, which allows any and all host names.
  367. AllowedHosts []string `json:"allowed_hosts" mapstructure:"allowed_hosts"`
  368. // AllowedHostsAreRegex determines if the provided allowed hosts contains valid regular expressions
  369. AllowedHostsAreRegex bool `json:"allowed_hosts_are_regex" mapstructure:"allowed_hosts_are_regex"`
  370. // HostsProxyHeaders is a set of header keys that may hold a proxied hostname value for the request.
  371. HostsProxyHeaders []string `json:"hosts_proxy_headers" mapstructure:"hosts_proxy_headers"`
  372. // Set to true to redirect HTTP requests to HTTPS
  373. HTTPSRedirect bool `json:"https_redirect" mapstructure:"https_redirect"`
  374. // HTTPSHost defines the host name that is used to redirect HTTP requests to HTTPS.
  375. // Default is "", which indicates to use the same host.
  376. HTTPSHost string `json:"https_host" mapstructure:"https_host"`
  377. // HTTPSProxyHeaders is a list of header keys with associated values that would indicate a valid https request.
  378. HTTPSProxyHeaders []HTTPSProxyHeader `json:"https_proxy_headers" mapstructure:"https_proxy_headers"`
  379. // STSSeconds is the max-age of the Strict-Transport-Security header.
  380. // Default is 0, which would NOT include the header.
  381. STSSeconds int64 `json:"sts_seconds" mapstructure:"sts_seconds"`
  382. // If STSIncludeSubdomains is set to true, the "includeSubdomains" will be appended to the
  383. // Strict-Transport-Security header. Default is false.
  384. STSIncludeSubdomains bool `json:"sts_include_subdomains" mapstructure:"sts_include_subdomains"`
  385. // If STSPreload is set to true, the `preload` flag will be appended to the
  386. // Strict-Transport-Security header. Default is false.
  387. STSPreload bool `json:"sts_preload" mapstructure:"sts_preload"`
  388. // If ContentTypeNosniff is true, adds the X-Content-Type-Options header with the value "nosniff". Default is false.
  389. ContentTypeNosniff bool `json:"content_type_nosniff" mapstructure:"content_type_nosniff"`
  390. // ContentSecurityPolicy allows to set the Content-Security-Policy header value. Default is "".
  391. ContentSecurityPolicy string `json:"content_security_policy" mapstructure:"content_security_policy"`
  392. // PermissionsPolicy allows to set the Permissions-Policy header value. Default is "".
  393. PermissionsPolicy string `json:"permissions_policy" mapstructure:"permissions_policy"`
  394. // CrossOriginOpenerPolicy allows to set the Cross-Origin-Opener-Policy header value. Default is "".
  395. CrossOriginOpenerPolicy string `json:"cross_origin_opener_policy" mapstructure:"cross_origin_opener_policy"`
  396. // CrossOriginResourcePolicy allows to set the Cross-Origin-Resource-Policy header value. Default is "".
  397. CrossOriginResourcePolicy string `json:"cross_origin_resource_policy" mapstructure:"cross_origin_resource_policy"`
  398. // CrossOriginEmbedderPolicy allows to set the Cross-Origin-Embedder-Policy header value. Default is "".
  399. CrossOriginEmbedderPolicy string `json:"cross_origin_embedder_policy" mapstructure:"cross_origin_embedder_policy"`
  400. // CacheControl allow to set the Cache-Control header value.
  401. CacheControl string `json:"cache_control" mapstructure:"cache_control"`
  402. proxyHeaders []string
  403. }
  404. func (s *SecurityConf) updateProxyHeaders() {
  405. if !s.Enabled {
  406. s.proxyHeaders = nil
  407. return
  408. }
  409. s.proxyHeaders = s.HostsProxyHeaders
  410. for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
  411. s.proxyHeaders = append(s.proxyHeaders, httpsProxyHeader.Key)
  412. }
  413. }
  414. func (s *SecurityConf) getHTTPSProxyHeaders() map[string]string {
  415. headers := make(map[string]string)
  416. for _, httpsProxyHeader := range s.HTTPSProxyHeaders {
  417. headers[httpsProxyHeader.Key] = httpsProxyHeader.Value
  418. }
  419. return headers
  420. }
  421. func (s *SecurityConf) redirectHandler(next http.Handler) http.Handler {
  422. return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  423. if !isTLS(r) && !strings.HasPrefix(r.RequestURI, acmeChallengeURI) {
  424. url := r.URL
  425. url.Scheme = "https"
  426. if s.HTTPSHost != "" {
  427. url.Host = s.HTTPSHost
  428. } else {
  429. host := r.Host
  430. for _, header := range s.HostsProxyHeaders {
  431. if h := r.Header.Get(header); h != "" {
  432. host = h
  433. break
  434. }
  435. }
  436. url.Host = host
  437. }
  438. http.Redirect(w, r, url.String(), http.StatusTemporaryRedirect)
  439. return
  440. }
  441. next.ServeHTTP(w, r)
  442. })
  443. }
  444. // UIBranding defines the supported customizations for the web UIs
  445. type UIBranding struct {
  446. // Name defines the text to show at the login page and as HTML title
  447. Name string `json:"name" mapstructure:"name"`
  448. // ShortName defines the name to show next to the logo image
  449. ShortName string `json:"short_name" mapstructure:"short_name"`
  450. // Path to your logo relative to "static_files_path".
  451. // For example, if you create a directory named "branding" inside the static dir and
  452. // put the "mylogo.png" file in it, you must set "/branding/mylogo.png" as logo path.
  453. LogoPath string `json:"logo_path" mapstructure:"logo_path"`
  454. // Path to your favicon relative to "static_files_path"
  455. FaviconPath string `json:"favicon_path" mapstructure:"favicon_path"`
  456. // DisclaimerName defines the name for the link to your optional disclaimer
  457. DisclaimerName string `json:"disclaimer_name" mapstructure:"disclaimer_name"`
  458. // Path to the HTML page for your disclaimer relative to "static_files_path"
  459. // or an absolute http/https URL.
  460. DisclaimerPath string `json:"disclaimer_path" mapstructure:"disclaimer_path"`
  461. // Path to custom CSS files, relative to "static_files_path", which replaces
  462. // the default CSS files
  463. DefaultCSS []string `json:"default_css" mapstructure:"default_css"`
  464. // Additional CSS file paths, relative to "static_files_path", to include
  465. ExtraCSS []string `json:"extra_css" mapstructure:"extra_css"`
  466. DefaultLogoPath string `json:"-" mapstructure:"-"`
  467. DefaultFaviconPath string `json:"-" mapstructure:"-"`
  468. }
  469. func (b *UIBranding) check() {
  470. b.DefaultLogoPath = "/img/logo.png"
  471. b.DefaultFaviconPath = "/favicon.png"
  472. if b.LogoPath != "" {
  473. b.LogoPath = util.CleanPath(b.LogoPath)
  474. } else {
  475. b.LogoPath = b.DefaultLogoPath
  476. }
  477. if b.FaviconPath != "" {
  478. b.FaviconPath = util.CleanPath(b.FaviconPath)
  479. } else {
  480. b.FaviconPath = b.DefaultFaviconPath
  481. }
  482. if b.DisclaimerPath != "" {
  483. if !strings.HasPrefix(b.DisclaimerPath, "https://") && !strings.HasPrefix(b.DisclaimerPath, "http://") {
  484. b.DisclaimerPath = path.Join(webStaticFilesPath, util.CleanPath(b.DisclaimerPath))
  485. }
  486. }
  487. if len(b.DefaultCSS) > 0 {
  488. for idx := range b.DefaultCSS {
  489. b.DefaultCSS[idx] = util.CleanPath(b.DefaultCSS[idx])
  490. }
  491. } else {
  492. b.DefaultCSS = []string{
  493. "/assets/plugins/global/plugins.bundle.css",
  494. "/assets/css/style.bundle.css",
  495. }
  496. }
  497. for idx := range b.ExtraCSS {
  498. b.ExtraCSS[idx] = util.CleanPath(b.ExtraCSS[idx])
  499. }
  500. }
  501. // Branding defines the branding-related customizations supported
  502. type Branding struct {
  503. WebAdmin UIBranding `json:"web_admin" mapstructure:"web_admin"`
  504. WebClient UIBranding `json:"web_client" mapstructure:"web_client"`
  505. }
  506. // WebClientIntegration defines the configuration for an external Web Client integration
  507. type WebClientIntegration struct {
  508. // Files with these extensions can be sent to the configured URL
  509. FileExtensions []string `json:"file_extensions" mapstructure:"file_extensions"`
  510. // URL that will receive the files
  511. URL string `json:"url" mapstructure:"url"`
  512. }
  513. // Binding defines the configuration for a network listener
  514. type Binding struct {
  515. // The address to listen on. A blank value means listen on all available network interfaces.
  516. Address string `json:"address" mapstructure:"address"`
  517. // The port used for serving requests
  518. Port int `json:"port" mapstructure:"port"`
  519. // Enable the built-in admin interface.
  520. // You have to define TemplatesPath and StaticFilesPath for this to work
  521. EnableWebAdmin bool `json:"enable_web_admin" mapstructure:"enable_web_admin"`
  522. // Enable the built-in client interface.
  523. // You have to define TemplatesPath and StaticFilesPath for this to work
  524. EnableWebClient bool `json:"enable_web_client" mapstructure:"enable_web_client"`
  525. // Enable REST API
  526. EnableRESTAPI bool `json:"enable_rest_api" mapstructure:"enable_rest_api"`
  527. // Defines the login methods available for the WebAdmin and WebClient UIs:
  528. //
  529. // - 0 means any configured method: username/password login form and OIDC, if enabled
  530. // - 1 means OIDC for the WebAdmin UI
  531. // - 2 means OIDC for the WebClient UI
  532. // - 4 means login form for the WebAdmin UI
  533. // - 8 means login form for the WebClient UI
  534. //
  535. // You can combine the values. For example 3 means that you can only login using OIDC on
  536. // both WebClient and WebAdmin UI.
  537. // Deprecated because it is not extensible, use DisabledLoginMethods
  538. EnabledLoginMethods int `json:"enabled_login_methods" mapstructure:"enabled_login_methods"`
  539. // Defines the login methods disabled for the WebAdmin and WebClient UIs:
  540. //
  541. // - 1 means OIDC for the WebAdmin UI
  542. // - 2 means OIDC for the WebClient UI
  543. // - 4 means login form for the WebAdmin UI
  544. // - 8 means login form for the WebClient UI
  545. // - 16 means basic auth for admin REST API
  546. // - 32 means basic auth for user REST API
  547. // - 64 means API key auth for admins
  548. // - 128 means API key auth for users
  549. // You can combine the values. For example 12 means that you can only login using OIDC on
  550. // both WebClient and WebAdmin UI.
  551. DisabledLoginMethods int `json:"disabled_login_methods" mapstructure:"disabled_login_methods"`
  552. // you also need to provide a certificate for enabling HTTPS
  553. EnableHTTPS bool `json:"enable_https" mapstructure:"enable_https"`
  554. // Certificate and matching private key for this specific binding, if empty the global
  555. // ones will be used, if any
  556. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  557. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  558. // Defines the minimum TLS version. 13 means TLS 1.3, default is TLS 1.2
  559. MinTLSVersion int `json:"min_tls_version" mapstructure:"min_tls_version"`
  560. // set to 1 to require client certificate authentication in addition to basic auth.
  561. // You need to define at least a certificate authority for this to work
  562. ClientAuthType int `json:"client_auth_type" mapstructure:"client_auth_type"`
  563. // TLSCipherSuites is a list of supported cipher suites for TLS version 1.2.
  564. // If CipherSuites is nil/empty, a default list of secure cipher suites
  565. // is used, with a preference order based on hardware performance.
  566. // Note that TLS 1.3 ciphersuites are not configurable.
  567. // The supported ciphersuites names are defined here:
  568. //
  569. // https://github.com/golang/go/blob/master/src/crypto/tls/cipher_suites.go#L53
  570. //
  571. // any invalid name will be silently ignored.
  572. // The order matters, the ciphers listed first will be the preferred ones.
  573. TLSCipherSuites []string `json:"tls_cipher_suites" mapstructure:"tls_cipher_suites"`
  574. // HTTP protocols in preference order. Supported values: http/1.1, h2
  575. Protocols []string `json:"tls_protocols" mapstructure:"tls_protocols"`
  576. // Defines whether to use the common proxy protocol configuration or the
  577. // binding-specific proxy header configuration.
  578. ProxyMode int `json:"proxy_mode" mapstructure:"proxy_mode"`
  579. // List of IP addresses and IP ranges allowed to set client IP proxy headers and
  580. // X-Forwarded-Proto header.
  581. ProxyAllowed []string `json:"proxy_allowed" mapstructure:"proxy_allowed"`
  582. // Allowed client IP proxy header such as "X-Forwarded-For", "X-Real-IP"
  583. ClientIPProxyHeader string `json:"client_ip_proxy_header" mapstructure:"client_ip_proxy_header"`
  584. // Some client IP headers such as "X-Forwarded-For" can contain multiple IP address, this setting
  585. // define the position to trust starting from the right. For example if we have:
  586. // "10.0.0.1,11.0.0.1,12.0.0.1,13.0.0.1" and the depth is 0, SFTPGo will use "13.0.0.1"
  587. // as client IP, if depth is 1, "12.0.0.1" will be used and so on
  588. ClientIPHeaderDepth int `json:"client_ip_header_depth" mapstructure:"client_ip_header_depth"`
  589. // If both web admin and web client are enabled each login page will show a link
  590. // to the other one. This setting allows to hide this link:
  591. // - 0 login links are displayed on both admin and client login page. This is the default
  592. // - 1 the login link to the web client login page is hidden on admin login page
  593. // - 2 the login link to the web admin login page is hidden on client login page
  594. // The flags can be combined, for example 3 will disable both login links.
  595. HideLoginURL int `json:"hide_login_url" mapstructure:"hide_login_url"`
  596. // Enable the built-in OpenAPI renderer
  597. RenderOpenAPI bool `json:"render_openapi" mapstructure:"render_openapi"`
  598. // Languages defines the list of enabled translations for the WebAdmin and WebClient UI.
  599. Languages []string `json:"languages" mapstructure:"languages"`
  600. // Defining an OIDC configuration the web admin and web client UI will use OpenID to authenticate users.
  601. OIDC OIDC `json:"oidc" mapstructure:"oidc"`
  602. // Security defines security headers to add to HTTP responses and allows to restrict allowed hosts
  603. Security SecurityConf `json:"security" mapstructure:"security"`
  604. // Branding defines customizations to suit your brand
  605. Branding Branding `json:"branding" mapstructure:"branding"`
  606. allowHeadersFrom []func(net.IP) bool
  607. }
  608. func (b *Binding) checkBranding() {
  609. b.Branding.WebAdmin.check()
  610. b.Branding.WebClient.check()
  611. if b.Branding.WebAdmin.Name == "" {
  612. b.Branding.WebAdmin.Name = "SFTPGo WebAdmin"
  613. }
  614. if b.Branding.WebAdmin.ShortName == "" {
  615. b.Branding.WebAdmin.ShortName = "WebAdmin"
  616. }
  617. if b.Branding.WebClient.Name == "" {
  618. b.Branding.WebClient.Name = "SFTPGo WebClient"
  619. }
  620. if b.Branding.WebClient.ShortName == "" {
  621. b.Branding.WebClient.ShortName = "WebClient"
  622. }
  623. }
  624. func (b *Binding) webAdminBranding() UIBranding {
  625. return dbBrandingConfig.mergeBrandingConfig(b.Branding.WebAdmin, false)
  626. }
  627. func (b *Binding) webClientBranding() UIBranding {
  628. return dbBrandingConfig.mergeBrandingConfig(b.Branding.WebClient, true)
  629. }
  630. func (b *Binding) languages() []string {
  631. return b.Languages
  632. }
  633. func (b *Binding) parseAllowedProxy() error {
  634. if filepath.IsAbs(b.Address) && len(b.ProxyAllowed) > 0 {
  635. // unix domain socket
  636. b.allowHeadersFrom = []func(net.IP) bool{func(_ net.IP) bool { return true }}
  637. return nil
  638. }
  639. allowedFuncs, err := util.ParseAllowedIPAndRanges(b.ProxyAllowed)
  640. if err != nil {
  641. return err
  642. }
  643. b.allowHeadersFrom = allowedFuncs
  644. return nil
  645. }
  646. // GetAddress returns the binding address
  647. func (b *Binding) GetAddress() string {
  648. return fmt.Sprintf("%s:%d", b.Address, b.Port)
  649. }
  650. // IsValid returns true if the binding is valid
  651. func (b *Binding) IsValid() bool {
  652. if !b.EnableRESTAPI && !b.EnableWebAdmin && !b.EnableWebClient {
  653. return false
  654. }
  655. if b.Port > 0 {
  656. return true
  657. }
  658. if filepath.IsAbs(b.Address) && runtime.GOOS != osWindows {
  659. return true
  660. }
  661. return false
  662. }
  663. func (b *Binding) isWebAdminOIDCLoginDisabled() bool {
  664. if b.EnableWebAdmin {
  665. return b.DisabledLoginMethods&1 != 0
  666. }
  667. return false
  668. }
  669. func (b *Binding) isWebClientOIDCLoginDisabled() bool {
  670. if b.EnableWebClient {
  671. return b.DisabledLoginMethods&2 != 0
  672. }
  673. return false
  674. }
  675. func (b *Binding) isWebAdminLoginFormDisabled() bool {
  676. if b.EnableWebAdmin {
  677. return b.DisabledLoginMethods&4 != 0
  678. }
  679. return false
  680. }
  681. func (b *Binding) isWebClientLoginFormDisabled() bool {
  682. if b.EnableWebClient {
  683. return b.DisabledLoginMethods&8 != 0
  684. }
  685. return false
  686. }
  687. func (b *Binding) isAdminTokenEndpointDisabled() bool {
  688. return b.DisabledLoginMethods&16 != 0
  689. }
  690. func (b *Binding) isUserTokenEndpointDisabled() bool {
  691. return b.DisabledLoginMethods&32 != 0
  692. }
  693. func (b *Binding) isAdminAPIKeyAuthDisabled() bool {
  694. return b.DisabledLoginMethods&64 != 0
  695. }
  696. func (b *Binding) isUserAPIKeyAuthDisabled() bool {
  697. return b.DisabledLoginMethods&128 != 0
  698. }
  699. func (b *Binding) hasLoginForAPI() bool {
  700. return !b.isAdminTokenEndpointDisabled() || !b.isUserTokenEndpointDisabled() ||
  701. !b.isAdminAPIKeyAuthDisabled() || !b.isUserAPIKeyAuthDisabled()
  702. }
  703. // convertLoginMethods checks if the deprecated EnabledLoginMethods is set and
  704. // convert the value to DisabledLoginMethods.
  705. func (b *Binding) convertLoginMethods() {
  706. if b.DisabledLoginMethods > 0 || b.EnabledLoginMethods == 0 {
  707. // DisabledLoginMethods already in use or EnabledLoginMethods not set.
  708. return
  709. }
  710. if b.EnabledLoginMethods&1 == 0 {
  711. b.DisabledLoginMethods++
  712. }
  713. if b.EnabledLoginMethods&2 == 0 {
  714. b.DisabledLoginMethods += 2
  715. }
  716. if b.EnabledLoginMethods&4 == 0 {
  717. b.DisabledLoginMethods += 4
  718. }
  719. if b.EnabledLoginMethods&8 == 0 {
  720. b.DisabledLoginMethods += 8
  721. }
  722. }
  723. func (b *Binding) checkLoginMethods() error {
  724. b.convertLoginMethods()
  725. if b.isWebAdminLoginFormDisabled() && b.isWebAdminOIDCLoginDisabled() {
  726. return errors.New("no login method available for WebAdmin UI")
  727. }
  728. if !b.isWebAdminOIDCLoginDisabled() {
  729. if b.isWebAdminLoginFormDisabled() && !b.OIDC.hasRoles() {
  730. return errors.New("no login method available for WebAdmin UI")
  731. }
  732. }
  733. if b.isWebClientLoginFormDisabled() && b.isWebClientOIDCLoginDisabled() {
  734. return errors.New("no login method available for WebClient UI")
  735. }
  736. if !b.isWebClientOIDCLoginDisabled() {
  737. if b.isWebClientLoginFormDisabled() && !b.OIDC.isEnabled() {
  738. return errors.New("no login method available for WebClient UI")
  739. }
  740. }
  741. if b.EnableRESTAPI && !b.hasLoginForAPI() {
  742. return errors.New("no login method available for REST API")
  743. }
  744. return nil
  745. }
  746. func (b *Binding) showAdminLoginURL() bool {
  747. if !b.EnableWebAdmin {
  748. return false
  749. }
  750. if b.HideLoginURL&2 != 0 {
  751. return false
  752. }
  753. return true
  754. }
  755. func (b *Binding) showClientLoginURL() bool {
  756. if !b.EnableWebClient {
  757. return false
  758. }
  759. if b.HideLoginURL&1 != 0 {
  760. return false
  761. }
  762. return true
  763. }
  764. func (b *Binding) isMutualTLSEnabled() bool {
  765. return b.ClientAuthType == 1
  766. }
  767. func (b *Binding) listenerWrapper() func(net.Listener) (net.Listener, error) {
  768. if b.ProxyMode == 1 {
  769. return common.Config.GetProxyListener
  770. }
  771. return nil
  772. }
  773. type defenderStatus struct {
  774. IsActive bool `json:"is_active"`
  775. }
  776. type allowListStatus struct {
  777. IsActive bool `json:"is_active"`
  778. }
  779. type rateLimiters struct {
  780. IsActive bool `json:"is_active"`
  781. Protocols []string `json:"protocols"`
  782. }
  783. // GetProtocolsAsString returns the enabled protocols as comma separated string
  784. func (r *rateLimiters) GetProtocolsAsString() string {
  785. return strings.Join(r.Protocols, ", ")
  786. }
  787. // ServicesStatus keep the state of the running services
  788. type ServicesStatus struct {
  789. SSH sftpd.ServiceStatus `json:"ssh"`
  790. FTP ftpd.ServiceStatus `json:"ftp"`
  791. WebDAV webdavd.ServiceStatus `json:"webdav"`
  792. DataProvider dataprovider.ProviderStatus `json:"data_provider"`
  793. Defender defenderStatus `json:"defender"`
  794. MFA mfa.ServiceStatus `json:"mfa"`
  795. AllowList allowListStatus `json:"allow_list"`
  796. RateLimiters rateLimiters `json:"rate_limiters"`
  797. }
  798. // SetupConfig defines the configuration parameters for the initial web admin setup
  799. type SetupConfig struct {
  800. // Installation code to require when creating the first admin account.
  801. // As for the other configurations, this value is read at SFTPGo startup and not at runtime
  802. // even if set using an environment variable.
  803. // This is not a license key or similar, the purpose here is to prevent anyone who can access
  804. // to the initial setup screen from creating an admin user
  805. InstallationCode string `json:"installation_code" mapstructure:"installation_code"`
  806. // Description for the installation code input field
  807. InstallationCodeHint string `json:"installation_code_hint" mapstructure:"installation_code_hint"`
  808. }
  809. // CorsConfig defines the CORS configuration
  810. type CorsConfig struct {
  811. AllowedOrigins []string `json:"allowed_origins" mapstructure:"allowed_origins"`
  812. AllowedMethods []string `json:"allowed_methods" mapstructure:"allowed_methods"`
  813. AllowedHeaders []string `json:"allowed_headers" mapstructure:"allowed_headers"`
  814. ExposedHeaders []string `json:"exposed_headers" mapstructure:"exposed_headers"`
  815. AllowCredentials bool `json:"allow_credentials" mapstructure:"allow_credentials"`
  816. Enabled bool `json:"enabled" mapstructure:"enabled"`
  817. MaxAge int `json:"max_age" mapstructure:"max_age"`
  818. OptionsPassthrough bool `json:"options_passthrough" mapstructure:"options_passthrough"`
  819. OptionsSuccessStatus int `json:"options_success_status" mapstructure:"options_success_status"`
  820. AllowPrivateNetwork bool `json:"allow_private_network" mapstructure:"allow_private_network"`
  821. }
  822. // Conf httpd daemon configuration
  823. type Conf struct {
  824. // Addresses and ports to bind to
  825. Bindings []Binding `json:"bindings" mapstructure:"bindings"`
  826. // Path to the HTML web templates. This can be an absolute path or a path relative to the config dir
  827. TemplatesPath string `json:"templates_path" mapstructure:"templates_path"`
  828. // Path to the static files for the web interface. This can be an absolute path or a path relative to the config dir.
  829. // If both TemplatesPath and StaticFilesPath are empty the built-in web interface will be disabled
  830. StaticFilesPath string `json:"static_files_path" mapstructure:"static_files_path"`
  831. // Path to the backup directory. This can be an absolute path or a path relative to the config dir
  832. //BackupsPath string `json:"backups_path" mapstructure:"backups_path"`
  833. // Path to the directory that contains the OpenAPI schema and the default renderer.
  834. // This can be an absolute path or a path relative to the config dir
  835. OpenAPIPath string `json:"openapi_path" mapstructure:"openapi_path"`
  836. // Defines a base URL for the web admin and client interfaces. If empty web admin and client resources will
  837. // be available at the root ("/") URI. If defined it must be an absolute URI or it will be ignored.
  838. WebRoot string `json:"web_root" mapstructure:"web_root"`
  839. // If files containing a certificate and matching private key for the server are provided you can enable
  840. // HTTPS connections for the configured bindings.
  841. // Certificate and key files can be reloaded on demand sending a "SIGHUP" signal on Unix based systems and a
  842. // "paramchange" request to the running service on Windows.
  843. CertificateFile string `json:"certificate_file" mapstructure:"certificate_file"`
  844. CertificateKeyFile string `json:"certificate_key_file" mapstructure:"certificate_key_file"`
  845. // CACertificates defines the set of root certificate authorities to be used to verify client certificates.
  846. CACertificates []string `json:"ca_certificates" mapstructure:"ca_certificates"`
  847. // CARevocationLists defines a set a revocation lists, one for each root CA, to be used to check
  848. // if a client certificate has been revoked
  849. CARevocationLists []string `json:"ca_revocation_lists" mapstructure:"ca_revocation_lists"`
  850. // SigningPassphrase defines the passphrase to use to derive the signing key for JWT and CSRF tokens.
  851. // If empty a random signing key will be generated each time SFTPGo starts. If you set a
  852. // signing passphrase you should consider rotating it periodically for added security
  853. SigningPassphrase string `json:"signing_passphrase" mapstructure:"signing_passphrase"`
  854. SigningPassphraseFile string `json:"signing_passphrase_file" mapstructure:"signing_passphrase_file"`
  855. // TokenValidation allows to define how to validate JWT tokens, cookies and CSRF tokens.
  856. // By default all the available security checks are enabled. Set to 1 to disable the requirement
  857. // that a token must be used by the same IP for which it was issued.
  858. TokenValidation int `json:"token_validation" mapstructure:"token_validation"`
  859. // CookieLifetime defines the duration of cookies for WebAdmin and WebClient
  860. CookieLifetime int `json:"cookie_lifetime" mapstructure:"cookie_lifetime"`
  861. // ShareCookieLifetime defines the duration of cookies for public shares
  862. ShareCookieLifetime int `json:"share_cookie_lifetime" mapstructure:"share_cookie_lifetime"`
  863. // JWTLifetime defines the duration of JWT tokens used in REST API
  864. JWTLifetime int `json:"jwt_lifetime" mapstructure:"jwt_lifetime"`
  865. // MaxUploadFileSize Defines the maximum request body size, in bytes, for Web Client/API HTTP upload requests.
  866. // 0 means no limit
  867. MaxUploadFileSize int64 `json:"max_upload_file_size" mapstructure:"max_upload_file_size"`
  868. // CORS configuration
  869. Cors CorsConfig `json:"cors" mapstructure:"cors"`
  870. // Initial setup configuration
  871. Setup SetupConfig `json:"setup" mapstructure:"setup"`
  872. // If enabled, the link to the sponsors section will not appear on the setup screen page
  873. HideSupportLink bool `json:"hide_support_link" mapstructure:"hide_support_link"`
  874. acmeDomain string
  875. }
  876. type apiResponse struct {
  877. Error string `json:"error,omitempty"`
  878. Message string `json:"message"`
  879. }
  880. // ShouldBind returns true if there is at least a valid binding
  881. func (c *Conf) ShouldBind() bool {
  882. for _, binding := range c.Bindings {
  883. if binding.IsValid() {
  884. return true
  885. }
  886. }
  887. return false
  888. }
  889. func (c *Conf) isWebAdminEnabled() bool {
  890. for _, binding := range c.Bindings {
  891. if binding.EnableWebAdmin {
  892. return true
  893. }
  894. }
  895. return false
  896. }
  897. func (c *Conf) isWebClientEnabled() bool {
  898. for _, binding := range c.Bindings {
  899. if binding.EnableWebClient {
  900. return true
  901. }
  902. }
  903. return false
  904. }
  905. func (c *Conf) checkRequiredDirs(staticFilesPath, templatesPath string) error {
  906. if (c.isWebAdminEnabled() || c.isWebClientEnabled()) && (staticFilesPath == "" || templatesPath == "") {
  907. return fmt.Errorf("required directory is invalid, static file path: %q template path: %q",
  908. staticFilesPath, templatesPath)
  909. }
  910. return nil
  911. }
  912. func (c *Conf) getRedacted() Conf {
  913. redacted := "[redacted]"
  914. conf := *c
  915. if conf.SigningPassphrase != "" {
  916. conf.SigningPassphrase = redacted
  917. }
  918. if conf.Setup.InstallationCode != "" {
  919. conf.Setup.InstallationCode = redacted
  920. }
  921. conf.Bindings = nil
  922. for _, binding := range c.Bindings {
  923. if binding.OIDC.ClientID != "" {
  924. binding.OIDC.ClientID = redacted
  925. }
  926. if binding.OIDC.ClientSecret != "" {
  927. binding.OIDC.ClientSecret = redacted
  928. }
  929. conf.Bindings = append(conf.Bindings, binding)
  930. }
  931. return conf
  932. }
  933. func (c *Conf) getKeyPairs(configDir string) []common.TLSKeyPair {
  934. var keyPairs []common.TLSKeyPair
  935. for _, binding := range c.Bindings {
  936. certificateFile := getConfigPath(binding.CertificateFile, configDir)
  937. certificateKeyFile := getConfigPath(binding.CertificateKeyFile, configDir)
  938. if certificateFile != "" && certificateKeyFile != "" {
  939. keyPairs = append(keyPairs, common.TLSKeyPair{
  940. Cert: certificateFile,
  941. Key: certificateKeyFile,
  942. ID: binding.GetAddress(),
  943. })
  944. }
  945. }
  946. var certificateFile, certificateKeyFile string
  947. if c.acmeDomain != "" {
  948. certificateFile, certificateKeyFile = util.GetACMECertificateKeyPair(c.acmeDomain)
  949. } else {
  950. certificateFile = getConfigPath(c.CertificateFile, configDir)
  951. certificateKeyFile = getConfigPath(c.CertificateKeyFile, configDir)
  952. }
  953. if certificateFile != "" && certificateKeyFile != "" {
  954. keyPairs = append(keyPairs, common.TLSKeyPair{
  955. Cert: certificateFile,
  956. Key: certificateKeyFile,
  957. ID: common.DefaultTLSKeyPaidID,
  958. })
  959. }
  960. return keyPairs
  961. }
  962. func (c *Conf) setTokenValidationMode() {
  963. tokenValidationMode = c.TokenValidation
  964. }
  965. func (c *Conf) loadFromProvider() error {
  966. configs, err := dataprovider.GetConfigs()
  967. if err != nil {
  968. return fmt.Errorf("unable to load config from provider: %w", err)
  969. }
  970. configs.SetNilsToEmpty()
  971. dbBrandingConfig.Set(configs.Branding)
  972. if configs.ACME.Domain == "" || !configs.ACME.HasProtocol(common.ProtocolHTTP) {
  973. return nil
  974. }
  975. crt, key := util.GetACMECertificateKeyPair(configs.ACME.Domain)
  976. if crt != "" && key != "" {
  977. if _, err := os.Stat(crt); err != nil {
  978. logger.Error(logSender, "", "unable to load acme cert file %q: %v", crt, err)
  979. return nil
  980. }
  981. if _, err := os.Stat(key); err != nil {
  982. logger.Error(logSender, "", "unable to load acme key file %q: %v", key, err)
  983. return nil
  984. }
  985. for idx := range c.Bindings {
  986. if c.Bindings[idx].Security.Enabled && c.Bindings[idx].Security.HTTPSRedirect {
  987. continue
  988. }
  989. c.Bindings[idx].EnableHTTPS = true
  990. }
  991. c.acmeDomain = configs.ACME.Domain
  992. logger.Info(logSender, "", "acme domain set to %q", c.acmeDomain)
  993. return nil
  994. }
  995. return nil
  996. }
  997. func (c *Conf) loadTemplates(templatesPath string) {
  998. if c.isWebAdminEnabled() {
  999. updateWebAdminURLs(c.WebRoot)
  1000. loadAdminTemplates(templatesPath)
  1001. } else {
  1002. logger.Info(logSender, "", "built-in web admin interface disabled")
  1003. }
  1004. if c.isWebClientEnabled() {
  1005. updateWebClientURLs(c.WebRoot)
  1006. loadClientTemplates(templatesPath)
  1007. } else {
  1008. logger.Info(logSender, "", "built-in web client interface disabled")
  1009. }
  1010. }
  1011. // Initialize configures and starts the HTTP server
  1012. func (c *Conf) Initialize(configDir string, isShared int) error {
  1013. if err := c.loadFromProvider(); err != nil {
  1014. return err
  1015. }
  1016. logger.Info(logSender, "", "initializing HTTP server with config %+v", c.getRedacted())
  1017. configurationDir = configDir
  1018. invalidatedJWTTokens = newTokenManager(isShared)
  1019. resetCodesMgr = newResetCodeManager(isShared)
  1020. oidcMgr = newOIDCManager(isShared)
  1021. oauth2Mgr = newOAuth2Manager(isShared)
  1022. webTaskMgr = newWebTaskManager(isShared)
  1023. staticFilesPath := util.FindSharedDataPath(c.StaticFilesPath, configDir)
  1024. templatesPath := util.FindSharedDataPath(c.TemplatesPath, configDir)
  1025. openAPIPath := util.FindSharedDataPath(c.OpenAPIPath, configDir)
  1026. if err := c.checkRequiredDirs(staticFilesPath, templatesPath); err != nil {
  1027. return err
  1028. }
  1029. c.loadTemplates(templatesPath)
  1030. keyPairs := c.getKeyPairs(configDir)
  1031. if len(keyPairs) > 0 {
  1032. mgr, err := common.NewCertManager(keyPairs, configDir, logSender)
  1033. if err != nil {
  1034. return err
  1035. }
  1036. mgr.SetCACertificates(c.CACertificates)
  1037. if err := mgr.LoadRootCAs(); err != nil {
  1038. return err
  1039. }
  1040. mgr.SetCARevocationLists(c.CARevocationLists)
  1041. if err := mgr.LoadCRLs(); err != nil {
  1042. return err
  1043. }
  1044. certMgr = mgr
  1045. }
  1046. if c.SigningPassphraseFile != "" {
  1047. passphrase, err := util.ReadConfigFromFile(c.SigningPassphraseFile, configDir)
  1048. if err != nil {
  1049. return err
  1050. }
  1051. c.SigningPassphrase = passphrase
  1052. }
  1053. hideSupportLink = c.HideSupportLink
  1054. exitChannel := make(chan error, 1)
  1055. for _, binding := range c.Bindings {
  1056. if !binding.IsValid() {
  1057. continue
  1058. }
  1059. if err := binding.parseAllowedProxy(); err != nil {
  1060. return err
  1061. }
  1062. binding.checkBranding()
  1063. binding.Security.updateProxyHeaders()
  1064. go func(b Binding) {
  1065. if err := b.OIDC.initialize(); err != nil {
  1066. exitChannel <- err
  1067. return
  1068. }
  1069. if err := b.checkLoginMethods(); err != nil {
  1070. exitChannel <- err
  1071. return
  1072. }
  1073. server := newHttpdServer(b, staticFilesPath, c.SigningPassphrase, c.Cors, openAPIPath)
  1074. server.setShared(isShared)
  1075. exitChannel <- server.listenAndServe()
  1076. }(binding)
  1077. }
  1078. maxUploadFileSize = c.MaxUploadFileSize
  1079. installationCode = c.Setup.InstallationCode
  1080. installationCodeHint = c.Setup.InstallationCodeHint
  1081. updateTokensDuration(c.JWTLifetime, c.CookieLifetime, c.ShareCookieLifetime)
  1082. startCleanupTicker(10 * time.Minute)
  1083. c.setTokenValidationMode()
  1084. return <-exitChannel
  1085. }
  1086. func isWebRequest(r *http.Request) bool {
  1087. return strings.HasPrefix(r.RequestURI, webBasePath+"/")
  1088. }
  1089. func isWebClientRequest(r *http.Request) bool {
  1090. return strings.HasPrefix(r.RequestURI, webBaseClientPath+"/")
  1091. }
  1092. // ReloadCertificateMgr reloads the certificate manager
  1093. func ReloadCertificateMgr() error {
  1094. if certMgr != nil {
  1095. return certMgr.Reload()
  1096. }
  1097. return nil
  1098. }
  1099. func getConfigPath(name, configDir string) string {
  1100. if !util.IsFileInputValid(name) {
  1101. return ""
  1102. }
  1103. if name != "" && !filepath.IsAbs(name) {
  1104. return filepath.Join(configDir, name)
  1105. }
  1106. return name
  1107. }
  1108. func getServicesStatus() *ServicesStatus {
  1109. rtlEnabled, rtlProtocols := common.Config.GetRateLimitersStatus()
  1110. status := &ServicesStatus{
  1111. SSH: sftpd.GetStatus(),
  1112. FTP: ftpd.GetStatus(),
  1113. WebDAV: webdavd.GetStatus(),
  1114. DataProvider: dataprovider.GetProviderStatus(),
  1115. Defender: defenderStatus{
  1116. IsActive: common.Config.DefenderConfig.Enabled,
  1117. },
  1118. MFA: mfa.GetStatus(),
  1119. AllowList: allowListStatus{
  1120. IsActive: common.Config.IsAllowListEnabled(),
  1121. },
  1122. RateLimiters: rateLimiters{
  1123. IsActive: rtlEnabled,
  1124. Protocols: rtlProtocols,
  1125. },
  1126. }
  1127. return status
  1128. }
  1129. func fileServer(r chi.Router, path string, root http.FileSystem, disableDirectoryIndex bool) {
  1130. if path != "/" && path[len(path)-1] != '/' {
  1131. r.Get(path, http.RedirectHandler(path+"/", http.StatusMovedPermanently).ServeHTTP)
  1132. path += "/"
  1133. }
  1134. path += "*"
  1135. r.Get(path, func(w http.ResponseWriter, r *http.Request) {
  1136. rctx := chi.RouteContext(r.Context())
  1137. pathPrefix := strings.TrimSuffix(rctx.RoutePattern(), "/*")
  1138. if disableDirectoryIndex {
  1139. root = neuteredFileSystem{root}
  1140. }
  1141. fs := http.StripPrefix(pathPrefix, http.FileServer(root))
  1142. fs.ServeHTTP(w, r)
  1143. })
  1144. }
  1145. func updateWebClientURLs(baseURL string) {
  1146. if !path.IsAbs(baseURL) {
  1147. baseURL = "/"
  1148. }
  1149. webRootPath = path.Join(baseURL, webRootPathDefault)
  1150. webBasePath = path.Join(baseURL, webBasePathDefault)
  1151. webBaseClientPath = path.Join(baseURL, webBasePathClientDefault)
  1152. webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
  1153. webClientLoginPath = path.Join(baseURL, webClientLoginPathDefault)
  1154. webClientOIDCLoginPath = path.Join(baseURL, webClientOIDCLoginPathDefault)
  1155. webClientTwoFactorPath = path.Join(baseURL, webClientTwoFactorPathDefault)
  1156. webClientTwoFactorRecoveryPath = path.Join(baseURL, webClientTwoFactorRecoveryPathDefault)
  1157. webClientFilesPath = path.Join(baseURL, webClientFilesPathDefault)
  1158. webClientFilePath = path.Join(baseURL, webClientFilePathDefault)
  1159. webClientFileActionsPath = path.Join(baseURL, webClientFileActionsPathDefault)
  1160. webClientSharesPath = path.Join(baseURL, webClientSharesPathDefault)
  1161. webClientPubSharesPath = path.Join(baseURL, webClientPubSharesPathDefault)
  1162. webClientSharePath = path.Join(baseURL, webClientSharePathDefault)
  1163. webClientEditFilePath = path.Join(baseURL, webClientEditFilePathDefault)
  1164. webClientDirsPath = path.Join(baseURL, webClientDirsPathDefault)
  1165. webClientDownloadZipPath = path.Join(baseURL, webClientDownloadZipPathDefault)
  1166. webClientProfilePath = path.Join(baseURL, webClientProfilePathDefault)
  1167. webClientPingPath = path.Join(baseURL, webClientPingPathDefault)
  1168. webChangeClientPwdPath = path.Join(baseURL, webChangeClientPwdPathDefault)
  1169. webClientLogoutPath = path.Join(baseURL, webClientLogoutPathDefault)
  1170. webClientMFAPath = path.Join(baseURL, webClientMFAPathDefault)
  1171. webClientTOTPGeneratePath = path.Join(baseURL, webClientTOTPGeneratePathDefault)
  1172. webClientTOTPValidatePath = path.Join(baseURL, webClientTOTPValidatePathDefault)
  1173. webClientTOTPSavePath = path.Join(baseURL, webClientTOTPSavePathDefault)
  1174. webClientRecoveryCodesPath = path.Join(baseURL, webClientRecoveryCodesPathDefault)
  1175. webClientForgotPwdPath = path.Join(baseURL, webClientForgotPwdPathDefault)
  1176. webClientResetPwdPath = path.Join(baseURL, webClientResetPwdPathDefault)
  1177. webClientViewPDFPath = path.Join(baseURL, webClientViewPDFPathDefault)
  1178. webClientGetPDFPath = path.Join(baseURL, webClientGetPDFPathDefault)
  1179. webClientExistPath = path.Join(baseURL, webClientExistPathDefault)
  1180. webClientTasksPath = path.Join(baseURL, webClientTasksPathDefault)
  1181. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  1182. webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
  1183. }
  1184. func updateWebAdminURLs(baseURL string) {
  1185. if !path.IsAbs(baseURL) {
  1186. baseURL = "/"
  1187. }
  1188. webRootPath = path.Join(baseURL, webRootPathDefault)
  1189. webBasePath = path.Join(baseURL, webBasePathDefault)
  1190. webBaseAdminPath = path.Join(baseURL, webBasePathAdminDefault)
  1191. webOIDCRedirectPath = path.Join(baseURL, webOIDCRedirectPathDefault)
  1192. webOAuth2RedirectPath = path.Join(baseURL, webOAuth2RedirectPathDefault)
  1193. webOAuth2TokenPath = path.Join(baseURL, webOAuth2TokenPathDefault)
  1194. webAdminSetupPath = path.Join(baseURL, webAdminSetupPathDefault)
  1195. webAdminLoginPath = path.Join(baseURL, webAdminLoginPathDefault)
  1196. webAdminOIDCLoginPath = path.Join(baseURL, webAdminOIDCLoginPathDefault)
  1197. webAdminTwoFactorPath = path.Join(baseURL, webAdminTwoFactorPathDefault)
  1198. webAdminTwoFactorRecoveryPath = path.Join(baseURL, webAdminTwoFactorRecoveryPathDefault)
  1199. webLogoutPath = path.Join(baseURL, webLogoutPathDefault)
  1200. webUsersPath = path.Join(baseURL, webUsersPathDefault)
  1201. webUserPath = path.Join(baseURL, webUserPathDefault)
  1202. webConnectionsPath = path.Join(baseURL, webConnectionsPathDefault)
  1203. webFoldersPath = path.Join(baseURL, webFoldersPathDefault)
  1204. webFolderPath = path.Join(baseURL, webFolderPathDefault)
  1205. webGroupsPath = path.Join(baseURL, webGroupsPathDefault)
  1206. webGroupPath = path.Join(baseURL, webGroupPathDefault)
  1207. webStatusPath = path.Join(baseURL, webStatusPathDefault)
  1208. webAdminsPath = path.Join(baseURL, webAdminsPathDefault)
  1209. webAdminPath = path.Join(baseURL, webAdminPathDefault)
  1210. webMaintenancePath = path.Join(baseURL, webMaintenancePathDefault)
  1211. webBackupPath = path.Join(baseURL, webBackupPathDefault)
  1212. webRestorePath = path.Join(baseURL, webRestorePathDefault)
  1213. webScanVFolderPath = path.Join(baseURL, webScanVFolderPathDefault)
  1214. webQuotaScanPath = path.Join(baseURL, webQuotaScanPathDefault)
  1215. webChangeAdminPwdPath = path.Join(baseURL, webChangeAdminPwdPathDefault)
  1216. webAdminForgotPwdPath = path.Join(baseURL, webAdminForgotPwdPathDefault)
  1217. webAdminResetPwdPath = path.Join(baseURL, webAdminResetPwdPathDefault)
  1218. webAdminProfilePath = path.Join(baseURL, webAdminProfilePathDefault)
  1219. webAdminMFAPath = path.Join(baseURL, webAdminMFAPathDefault)
  1220. webAdminEventRulesPath = path.Join(baseURL, webAdminEventRulesPathDefault)
  1221. webAdminEventRulePath = path.Join(baseURL, webAdminEventRulePathDefault)
  1222. webAdminEventActionsPath = path.Join(baseURL, webAdminEventActionsPathDefault)
  1223. webAdminEventActionPath = path.Join(baseURL, webAdminEventActionPathDefault)
  1224. webAdminRolesPath = path.Join(baseURL, webAdminRolesPathDefault)
  1225. webAdminRolePath = path.Join(baseURL, webAdminRolePathDefault)
  1226. webAdminTOTPGeneratePath = path.Join(baseURL, webAdminTOTPGeneratePathDefault)
  1227. webAdminTOTPValidatePath = path.Join(baseURL, webAdminTOTPValidatePathDefault)
  1228. webAdminTOTPSavePath = path.Join(baseURL, webAdminTOTPSavePathDefault)
  1229. webAdminRecoveryCodesPath = path.Join(baseURL, webAdminRecoveryCodesPathDefault)
  1230. webTemplateUser = path.Join(baseURL, webTemplateUserDefault)
  1231. webTemplateFolder = path.Join(baseURL, webTemplateFolderDefault)
  1232. webDefenderHostsPath = path.Join(baseURL, webDefenderHostsPathDefault)
  1233. webDefenderPath = path.Join(baseURL, webDefenderPathDefault)
  1234. webIPListPath = path.Join(baseURL, webIPListPathDefault)
  1235. webIPListsPath = path.Join(baseURL, webIPListsPathDefault)
  1236. webEventsPath = path.Join(baseURL, webEventsPathDefault)
  1237. webEventsFsSearchPath = path.Join(baseURL, webEventsFsSearchPathDefault)
  1238. webEventsProviderSearchPath = path.Join(baseURL, webEventsProviderSearchPathDefault)
  1239. webEventsLogSearchPath = path.Join(baseURL, webEventsLogSearchPathDefault)
  1240. webConfigsPath = path.Join(baseURL, webConfigsPathDefault)
  1241. webStaticFilesPath = path.Join(baseURL, webStaticFilesPathDefault)
  1242. webOpenAPIPath = path.Join(baseURL, webOpenAPIPathDefault)
  1243. }
  1244. // GetHTTPRouter returns an HTTP handler suitable to use for test cases
  1245. func GetHTTPRouter(b Binding) http.Handler {
  1246. server := newHttpdServer(b, filepath.Join("..", "..", "static"), "", CorsConfig{}, filepath.Join("..", "..", "openapi"))
  1247. server.initializeRouter()
  1248. return server.router
  1249. }
  1250. // the ticker cannot be started/stopped from multiple goroutines
  1251. func startCleanupTicker(duration time.Duration) {
  1252. stopCleanupTicker()
  1253. cleanupTicker = time.NewTicker(duration)
  1254. cleanupDone = make(chan bool)
  1255. go func() {
  1256. counter := int64(0)
  1257. for {
  1258. select {
  1259. case <-cleanupDone:
  1260. return
  1261. case <-cleanupTicker.C:
  1262. counter++
  1263. invalidatedJWTTokens.Cleanup()
  1264. resetCodesMgr.Cleanup()
  1265. webTaskMgr.Cleanup()
  1266. if counter%2 == 0 {
  1267. oidcMgr.cleanup()
  1268. oauth2Mgr.cleanup()
  1269. }
  1270. }
  1271. }
  1272. }()
  1273. }
  1274. func stopCleanupTicker() {
  1275. if cleanupTicker != nil {
  1276. cleanupTicker.Stop()
  1277. cleanupDone <- true
  1278. cleanupTicker = nil
  1279. }
  1280. }
  1281. func getSigningKey(signingPassphrase string) []byte {
  1282. var key []byte
  1283. if signingPassphrase != "" {
  1284. key = []byte(signingPassphrase)
  1285. } else {
  1286. key = util.GenerateRandomBytes(32)
  1287. }
  1288. sk := sha256.Sum256(key)
  1289. return sk[:]
  1290. }
  1291. // SetInstallationCodeResolver sets a function to call to resolve the installation code
  1292. func SetInstallationCodeResolver(fn FnInstallationCodeResolver) {
  1293. fnInstallationCodeResolver = fn
  1294. }
  1295. func resolveInstallationCode() string {
  1296. if fnInstallationCodeResolver != nil {
  1297. return fnInstallationCodeResolver(installationCode)
  1298. }
  1299. return installationCode
  1300. }
  1301. type neuteredFileSystem struct {
  1302. fs http.FileSystem
  1303. }
  1304. func (nfs neuteredFileSystem) Open(name string) (http.File, error) {
  1305. f, err := nfs.fs.Open(name)
  1306. if err != nil {
  1307. return nil, err
  1308. }
  1309. s, err := f.Stat()
  1310. if err != nil {
  1311. return nil, err
  1312. }
  1313. if s.IsDir() {
  1314. index := path.Join(name, "index.html")
  1315. if _, err := nfs.fs.Open(index); err != nil {
  1316. defer f.Close()
  1317. return nil, err
  1318. }
  1319. }
  1320. return f, nil
  1321. }