admin.go 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444
  1. package dataprovider
  2. import (
  3. "crypto/sha256"
  4. "encoding/base64"
  5. "encoding/json"
  6. "errors"
  7. "fmt"
  8. "net"
  9. "os"
  10. "regexp"
  11. "strings"
  12. "github.com/alexedwards/argon2id"
  13. passwordvalidator "github.com/wagslane/go-password-validator"
  14. "golang.org/x/crypto/bcrypt"
  15. "github.com/drakkan/sftpgo/v2/kms"
  16. "github.com/drakkan/sftpgo/v2/logger"
  17. "github.com/drakkan/sftpgo/v2/mfa"
  18. "github.com/drakkan/sftpgo/v2/util"
  19. )
  20. // Available permissions for SFTPGo admins
  21. const (
  22. PermAdminAny = "*"
  23. PermAdminAddUsers = "add_users"
  24. PermAdminChangeUsers = "edit_users"
  25. PermAdminDeleteUsers = "del_users"
  26. PermAdminViewUsers = "view_users"
  27. PermAdminViewConnections = "view_conns"
  28. PermAdminCloseConnections = "close_conns"
  29. PermAdminViewServerStatus = "view_status"
  30. PermAdminManageAdmins = "manage_admins"
  31. PermAdminManageGroups = "manage_groups"
  32. PermAdminManageAPIKeys = "manage_apikeys"
  33. PermAdminQuotaScans = "quota_scans"
  34. PermAdminManageSystem = "manage_system"
  35. PermAdminManageDefender = "manage_defender"
  36. PermAdminViewDefender = "view_defender"
  37. PermAdminRetentionChecks = "retention_checks"
  38. PermAdminMetadataChecks = "metadata_checks"
  39. PermAdminViewEvents = "view_events"
  40. )
  41. var (
  42. emailRegex = regexp.MustCompile("^(?:(?:(?:(?:[a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+(?:\\.([a-zA-Z]|\\d|[!#\\$%&'\\*\\+\\-\\/=\\?\\^_`{\\|}~]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])+)*)|(?:(?:\\x22)(?:(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(?:\\x20|\\x09)+)?(?:(?:[\\x01-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]|\\x21|[\\x23-\\x5b]|[\\x5d-\\x7e]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[\\x01-\\x09\\x0b\\x0c\\x0d-\\x7f]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}]))))*(?:(?:(?:\\x20|\\x09)*(?:\\x0d\\x0a))?(\\x20|\\x09)+)?(?:\\x22))))@(?:(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|\\d|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.)+(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])|(?:(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])(?:[a-zA-Z]|\\d|-|\\.|~|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])*(?:[a-zA-Z]|[\\x{00A0}-\\x{D7FF}\\x{F900}-\\x{FDCF}\\x{FDF0}-\\x{FFEF}])))\\.?$")
  43. validAdminPerms = []string{PermAdminAny, PermAdminAddUsers, PermAdminChangeUsers, PermAdminDeleteUsers,
  44. PermAdminViewUsers, PermAdminManageGroups, PermAdminViewConnections, PermAdminCloseConnections,
  45. PermAdminViewServerStatus, PermAdminManageAdmins, PermAdminManageAPIKeys, PermAdminQuotaScans,
  46. PermAdminManageSystem, PermAdminManageDefender, PermAdminViewDefender, PermAdminRetentionChecks,
  47. PermAdminMetadataChecks, PermAdminViewEvents}
  48. )
  49. // AdminTOTPConfig defines the time-based one time password configuration
  50. type AdminTOTPConfig struct {
  51. Enabled bool `json:"enabled,omitempty"`
  52. ConfigName string `json:"config_name,omitempty"`
  53. Secret *kms.Secret `json:"secret,omitempty"`
  54. }
  55. func (c *AdminTOTPConfig) validate(username string) error {
  56. if !c.Enabled {
  57. c.ConfigName = ""
  58. c.Secret = kms.NewEmptySecret()
  59. return nil
  60. }
  61. if c.ConfigName == "" {
  62. return util.NewValidationError("totp: config name is mandatory")
  63. }
  64. if !util.IsStringInSlice(c.ConfigName, mfa.GetAvailableTOTPConfigNames()) {
  65. return util.NewValidationError(fmt.Sprintf("totp: config name %#v not found", c.ConfigName))
  66. }
  67. if c.Secret.IsEmpty() {
  68. return util.NewValidationError("totp: secret is mandatory")
  69. }
  70. if c.Secret.IsPlain() {
  71. c.Secret.SetAdditionalData(username)
  72. if err := c.Secret.Encrypt(); err != nil {
  73. return util.NewValidationError(fmt.Sprintf("totp: unable to encrypt secret: %v", err))
  74. }
  75. }
  76. return nil
  77. }
  78. // AdminFilters defines additional restrictions for SFTPGo admins
  79. // TODO: rename to AdminOptions in v3
  80. type AdminFilters struct {
  81. // only clients connecting from these IP/Mask are allowed.
  82. // IP/Mask must be in CIDR notation as defined in RFC 4632 and RFC 4291
  83. // for example "192.0.2.0/24" or "2001:db8::/32"
  84. AllowList []string `json:"allow_list,omitempty"`
  85. // API key auth allows to impersonate this administrator with an API key
  86. AllowAPIKeyAuth bool `json:"allow_api_key_auth,omitempty"`
  87. // Time-based one time passwords configuration
  88. TOTPConfig AdminTOTPConfig `json:"totp_config,omitempty"`
  89. // Recovery codes to use if the user loses access to their second factor auth device.
  90. // Each code can only be used once, you should use these codes to login and disable or
  91. // reset 2FA for your account
  92. RecoveryCodes []RecoveryCode `json:"recovery_codes,omitempty"`
  93. }
  94. // Admin defines a SFTPGo admin
  95. type Admin struct {
  96. // Database unique identifier
  97. ID int64 `json:"id"`
  98. // 1 enabled, 0 disabled (login is not allowed)
  99. Status int `json:"status"`
  100. // Username
  101. Username string `json:"username"`
  102. Password string `json:"password,omitempty"`
  103. Email string `json:"email,omitempty"`
  104. Permissions []string `json:"permissions"`
  105. Filters AdminFilters `json:"filters,omitempty"`
  106. Description string `json:"description,omitempty"`
  107. AdditionalInfo string `json:"additional_info,omitempty"`
  108. // Creation time as unix timestamp in milliseconds. It will be 0 for admins created before v2.2.0
  109. CreatedAt int64 `json:"created_at"`
  110. // last update time as unix timestamp in milliseconds
  111. UpdatedAt int64 `json:"updated_at"`
  112. // Last login as unix timestamp in milliseconds
  113. LastLogin int64 `json:"last_login"`
  114. }
  115. // CountUnusedRecoveryCodes returns the number of unused recovery codes
  116. func (a *Admin) CountUnusedRecoveryCodes() int {
  117. unused := 0
  118. for _, code := range a.Filters.RecoveryCodes {
  119. if !code.Used {
  120. unused++
  121. }
  122. }
  123. return unused
  124. }
  125. func (a *Admin) hashPassword() error {
  126. if a.Password != "" && !util.IsStringPrefixInSlice(a.Password, internalHashPwdPrefixes) {
  127. if config.PasswordValidation.Admins.MinEntropy > 0 {
  128. if err := passwordvalidator.Validate(a.Password, config.PasswordValidation.Admins.MinEntropy); err != nil {
  129. return util.NewValidationError(err.Error())
  130. }
  131. }
  132. if config.PasswordHashing.Algo == HashingAlgoBcrypt {
  133. pwd, err := bcrypt.GenerateFromPassword([]byte(a.Password), config.PasswordHashing.BcryptOptions.Cost)
  134. if err != nil {
  135. return err
  136. }
  137. a.Password = string(pwd)
  138. } else {
  139. pwd, err := argon2id.CreateHash(a.Password, argon2Params)
  140. if err != nil {
  141. return err
  142. }
  143. a.Password = pwd
  144. }
  145. }
  146. return nil
  147. }
  148. func (a *Admin) hasRedactedSecret() bool {
  149. return a.Filters.TOTPConfig.Secret.IsRedacted()
  150. }
  151. func (a *Admin) validateRecoveryCodes() error {
  152. for i := 0; i < len(a.Filters.RecoveryCodes); i++ {
  153. code := &a.Filters.RecoveryCodes[i]
  154. if code.Secret.IsEmpty() {
  155. return util.NewValidationError("mfa: recovery code cannot be empty")
  156. }
  157. if code.Secret.IsPlain() {
  158. code.Secret.SetAdditionalData(a.Username)
  159. if err := code.Secret.Encrypt(); err != nil {
  160. return util.NewValidationError(fmt.Sprintf("mfa: unable to encrypt recovery code: %v", err))
  161. }
  162. }
  163. }
  164. return nil
  165. }
  166. func (a *Admin) validatePermissions() error {
  167. a.Permissions = util.RemoveDuplicates(a.Permissions)
  168. if len(a.Permissions) == 0 {
  169. return util.NewValidationError("please grant some permissions to this admin")
  170. }
  171. if util.IsStringInSlice(PermAdminAny, a.Permissions) {
  172. a.Permissions = []string{PermAdminAny}
  173. }
  174. for _, perm := range a.Permissions {
  175. if !util.IsStringInSlice(perm, validAdminPerms) {
  176. return util.NewValidationError(fmt.Sprintf("invalid permission: %#v", perm))
  177. }
  178. }
  179. return nil
  180. }
  181. func (a *Admin) validate() error {
  182. a.SetEmptySecretsIfNil()
  183. if a.Username == "" {
  184. return util.NewValidationError("username is mandatory")
  185. }
  186. if a.Password == "" {
  187. return util.NewValidationError("please set a password")
  188. }
  189. if a.hasRedactedSecret() {
  190. return util.NewValidationError("cannot save an admin with a redacted secret")
  191. }
  192. if err := a.Filters.TOTPConfig.validate(a.Username); err != nil {
  193. return err
  194. }
  195. if err := a.validateRecoveryCodes(); err != nil {
  196. return err
  197. }
  198. if config.NamingRules&1 == 0 && !usernameRegex.MatchString(a.Username) {
  199. return util.NewValidationError(fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", a.Username))
  200. }
  201. if err := a.hashPassword(); err != nil {
  202. return err
  203. }
  204. if err := a.validatePermissions(); err != nil {
  205. return err
  206. }
  207. if a.Email != "" && !emailRegex.MatchString(a.Email) {
  208. return util.NewValidationError(fmt.Sprintf("email %#v is not valid", a.Email))
  209. }
  210. a.Filters.AllowList = util.RemoveDuplicates(a.Filters.AllowList)
  211. for _, IPMask := range a.Filters.AllowList {
  212. _, _, err := net.ParseCIDR(IPMask)
  213. if err != nil {
  214. return util.NewValidationError(fmt.Sprintf("could not parse allow list entry %#v : %v", IPMask, err))
  215. }
  216. }
  217. return nil
  218. }
  219. // CheckPassword verifies the admin password
  220. func (a *Admin) CheckPassword(password string) (bool, error) {
  221. if strings.HasPrefix(a.Password, bcryptPwdPrefix) {
  222. if err := bcrypt.CompareHashAndPassword([]byte(a.Password), []byte(password)); err != nil {
  223. return false, ErrInvalidCredentials
  224. }
  225. return true, nil
  226. }
  227. match, err := argon2id.ComparePasswordAndHash(password, a.Password)
  228. if !match || err != nil {
  229. return false, ErrInvalidCredentials
  230. }
  231. return match, err
  232. }
  233. // CanLoginFromIP returns true if login from the given IP is allowed
  234. func (a *Admin) CanLoginFromIP(ip string) bool {
  235. if len(a.Filters.AllowList) == 0 {
  236. return true
  237. }
  238. parsedIP := net.ParseIP(ip)
  239. if parsedIP == nil {
  240. return len(a.Filters.AllowList) == 0
  241. }
  242. for _, ipMask := range a.Filters.AllowList {
  243. _, network, err := net.ParseCIDR(ipMask)
  244. if err != nil {
  245. continue
  246. }
  247. if network.Contains(parsedIP) {
  248. return true
  249. }
  250. }
  251. return false
  252. }
  253. // CanLogin returns an error if the login is not allowed
  254. func (a *Admin) CanLogin(ip string) error {
  255. if a.Status != 1 {
  256. return fmt.Errorf("admin %#v is disabled", a.Username)
  257. }
  258. if !a.CanLoginFromIP(ip) {
  259. return fmt.Errorf("login from IP %v not allowed", ip)
  260. }
  261. return nil
  262. }
  263. func (a *Admin) checkUserAndPass(password, ip string) error {
  264. if err := a.CanLogin(ip); err != nil {
  265. return err
  266. }
  267. if a.Password == "" || password == "" {
  268. return errors.New("credentials cannot be null or empty")
  269. }
  270. match, err := a.CheckPassword(password)
  271. if err != nil {
  272. return err
  273. }
  274. if !match {
  275. return ErrInvalidCredentials
  276. }
  277. return nil
  278. }
  279. // RenderAsJSON implements the renderer interface used within plugins
  280. func (a *Admin) RenderAsJSON(reload bool) ([]byte, error) {
  281. if reload {
  282. admin, err := provider.adminExists(a.Username)
  283. if err != nil {
  284. providerLog(logger.LevelError, "unable to reload admin before rendering as json: %v", err)
  285. return nil, err
  286. }
  287. admin.HideConfidentialData()
  288. return json.Marshal(admin)
  289. }
  290. a.HideConfidentialData()
  291. return json.Marshal(a)
  292. }
  293. // HideConfidentialData hides admin confidential data
  294. func (a *Admin) HideConfidentialData() {
  295. a.Password = ""
  296. if a.Filters.TOTPConfig.Secret != nil {
  297. a.Filters.TOTPConfig.Secret.Hide()
  298. }
  299. for _, code := range a.Filters.RecoveryCodes {
  300. if code.Secret != nil {
  301. code.Secret.Hide()
  302. }
  303. }
  304. a.SetNilSecretsIfEmpty()
  305. }
  306. // SetEmptySecretsIfNil sets the secrets to empty if nil
  307. func (a *Admin) SetEmptySecretsIfNil() {
  308. if a.Filters.TOTPConfig.Secret == nil {
  309. a.Filters.TOTPConfig.Secret = kms.NewEmptySecret()
  310. }
  311. }
  312. // SetNilSecretsIfEmpty set the secrets to nil if empty.
  313. // This is useful before rendering as JSON so the empty fields
  314. // will not be serialized.
  315. func (a *Admin) SetNilSecretsIfEmpty() {
  316. if a.Filters.TOTPConfig.Secret != nil && a.Filters.TOTPConfig.Secret.IsEmpty() {
  317. a.Filters.TOTPConfig.Secret = nil
  318. }
  319. }
  320. // HasPermission returns true if the admin has the specified permission
  321. func (a *Admin) HasPermission(perm string) bool {
  322. if util.IsStringInSlice(PermAdminAny, a.Permissions) {
  323. return true
  324. }
  325. return util.IsStringInSlice(perm, a.Permissions)
  326. }
  327. // GetPermissionsAsString returns permission as string
  328. func (a *Admin) GetPermissionsAsString() string {
  329. return strings.Join(a.Permissions, ", ")
  330. }
  331. // GetAllowedIPAsString returns the allowed IP as comma separated string
  332. func (a *Admin) GetAllowedIPAsString() string {
  333. return strings.Join(a.Filters.AllowList, ",")
  334. }
  335. // GetValidPerms returns the allowed admin permissions
  336. func (a *Admin) GetValidPerms() []string {
  337. return validAdminPerms
  338. }
  339. // GetInfoString returns admin's info as string.
  340. func (a *Admin) GetInfoString() string {
  341. var result strings.Builder
  342. if a.Email != "" {
  343. result.WriteString(fmt.Sprintf("Email: %v. ", a.Email))
  344. }
  345. if len(a.Filters.AllowList) > 0 {
  346. result.WriteString(fmt.Sprintf("Allowed IP/Mask: %v. ", len(a.Filters.AllowList)))
  347. }
  348. return result.String()
  349. }
  350. // CanManageMFA returns true if the admin can add a multi-factor authentication configuration
  351. func (a *Admin) CanManageMFA() bool {
  352. return len(mfa.GetAvailableTOTPConfigs()) > 0
  353. }
  354. // GetSignature returns a signature for this admin.
  355. // It could change after an update
  356. func (a *Admin) GetSignature() string {
  357. data := []byte(a.Username)
  358. data = append(data, []byte(a.Password)...)
  359. signature := sha256.Sum256(data)
  360. return base64.StdEncoding.EncodeToString(signature[:])
  361. }
  362. func (a *Admin) getACopy() Admin {
  363. a.SetEmptySecretsIfNil()
  364. permissions := make([]string, len(a.Permissions))
  365. copy(permissions, a.Permissions)
  366. filters := AdminFilters{}
  367. filters.AllowList = make([]string, len(a.Filters.AllowList))
  368. filters.AllowAPIKeyAuth = a.Filters.AllowAPIKeyAuth
  369. filters.TOTPConfig.Enabled = a.Filters.TOTPConfig.Enabled
  370. filters.TOTPConfig.ConfigName = a.Filters.TOTPConfig.ConfigName
  371. filters.TOTPConfig.Secret = a.Filters.TOTPConfig.Secret.Clone()
  372. copy(filters.AllowList, a.Filters.AllowList)
  373. filters.RecoveryCodes = make([]RecoveryCode, 0)
  374. for _, code := range a.Filters.RecoveryCodes {
  375. if code.Secret == nil {
  376. code.Secret = kms.NewEmptySecret()
  377. }
  378. filters.RecoveryCodes = append(filters.RecoveryCodes, RecoveryCode{
  379. Secret: code.Secret.Clone(),
  380. Used: code.Used,
  381. })
  382. }
  383. return Admin{
  384. ID: a.ID,
  385. Status: a.Status,
  386. Username: a.Username,
  387. Password: a.Password,
  388. Email: a.Email,
  389. Permissions: permissions,
  390. Filters: filters,
  391. AdditionalInfo: a.AdditionalInfo,
  392. Description: a.Description,
  393. LastLogin: a.LastLogin,
  394. CreatedAt: a.CreatedAt,
  395. UpdatedAt: a.UpdatedAt,
  396. }
  397. }
  398. func (a *Admin) setFromEnv() error {
  399. envUsername := strings.TrimSpace(os.Getenv("SFTPGO_DEFAULT_ADMIN_USERNAME"))
  400. envPassword := strings.TrimSpace(os.Getenv("SFTPGO_DEFAULT_ADMIN_PASSWORD"))
  401. if envUsername == "" || envPassword == "" {
  402. return errors.New(`to create the default admin you need to set the env vars "SFTPGO_DEFAULT_ADMIN_USERNAME" and "SFTPGO_DEFAULT_ADMIN_PASSWORD"`)
  403. }
  404. a.Username = envUsername
  405. a.Password = envPassword
  406. a.Status = 1
  407. a.Permissions = []string{PermAdminAny}
  408. return nil
  409. }