admin.go 14 KB

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