admin.go 13 KB

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