admin.go 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252
  1. package dataprovider
  2. import (
  3. "crypto/sha256"
  4. "encoding/base64"
  5. "errors"
  6. "fmt"
  7. "net"
  8. "regexp"
  9. "strings"
  10. "github.com/alexedwards/argon2id"
  11. "golang.org/x/crypto/bcrypt"
  12. "github.com/drakkan/sftpgo/utils"
  13. )
  14. // Available permissions for SFTPGo admins
  15. const (
  16. PermAdminAny = "*"
  17. PermAdminAddUsers = "add_users"
  18. PermAdminChangeUsers = "edit_users"
  19. PermAdminDeleteUsers = "del_users"
  20. PermAdminViewUsers = "view_users"
  21. PermAdminViewConnections = "view_conns"
  22. PermAdminCloseConnections = "close_conns"
  23. PermAdminViewServerStatus = "view_status"
  24. PermAdminManageAdmins = "manage_admins"
  25. PermAdminQuotaScans = "quota_scans"
  26. PermAdminManageSystem = "manage_system"
  27. PermAdminManageDefender = "manage_defender"
  28. PermAdminViewDefender = "view_defender"
  29. )
  30. var (
  31. 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}])))\\.?$")
  32. validAdminPerms = []string{PermAdminAny, PermAdminAddUsers, PermAdminChangeUsers, PermAdminDeleteUsers,
  33. PermAdminViewUsers, PermAdminViewConnections, PermAdminCloseConnections, PermAdminViewServerStatus,
  34. PermAdminManageAdmins, PermAdminQuotaScans, PermAdminManageSystem, PermAdminManageDefender,
  35. PermAdminViewDefender}
  36. )
  37. // AdminFilters defines additional restrictions for SFTPGo admins
  38. type AdminFilters struct {
  39. // only clients connecting from these IP/Mask are allowed.
  40. // IP/Mask must be in CIDR notation as defined in RFC 4632 and RFC 4291
  41. // for example "192.0.2.0/24" or "2001:db8::/32"
  42. AllowList []string `json:"allow_list,omitempty"`
  43. }
  44. // Admin defines a SFTPGo admin
  45. type Admin struct {
  46. // Database unique identifier
  47. ID int64 `json:"id"`
  48. // 1 enabled, 0 disabled (login is not allowed)
  49. Status int `json:"status"`
  50. // Username
  51. Username string `json:"username"`
  52. Password string `json:"password,omitempty"`
  53. Email string `json:"email"`
  54. Permissions []string `json:"permissions"`
  55. Filters AdminFilters `json:"filters,omitempty"`
  56. Description string `json:"description,omitempty"`
  57. AdditionalInfo string `json:"additional_info,omitempty"`
  58. }
  59. func (a *Admin) checkPassword() error {
  60. if a.Password != "" && !strings.HasPrefix(a.Password, argonPwdPrefix) {
  61. if config.PasswordHashing.Algo == HashingAlgoBcrypt {
  62. pwd, err := bcrypt.GenerateFromPassword([]byte(a.Password), config.PasswordHashing.BcryptOptions.Cost)
  63. if err != nil {
  64. return err
  65. }
  66. a.Password = string(pwd)
  67. } else {
  68. pwd, err := argon2id.CreateHash(a.Password, argon2Params)
  69. if err != nil {
  70. return err
  71. }
  72. a.Password = pwd
  73. }
  74. }
  75. return nil
  76. }
  77. func (a *Admin) validate() error {
  78. if a.Username == "" {
  79. return &ValidationError{err: "username is mandatory"}
  80. }
  81. if a.Password == "" {
  82. return &ValidationError{err: "please set a password"}
  83. }
  84. if !config.SkipNaturalKeysValidation && !usernameRegex.MatchString(a.Username) {
  85. return &ValidationError{err: fmt.Sprintf("username %#v is not valid, the following characters are allowed: a-zA-Z0-9-_.~", a.Username)}
  86. }
  87. if err := a.checkPassword(); err != nil {
  88. return err
  89. }
  90. a.Permissions = utils.RemoveDuplicates(a.Permissions)
  91. if len(a.Permissions) == 0 {
  92. return &ValidationError{err: "please grant some permissions to this admin"}
  93. }
  94. if utils.IsStringInSlice(PermAdminAny, a.Permissions) {
  95. a.Permissions = []string{PermAdminAny}
  96. }
  97. for _, perm := range a.Permissions {
  98. if !utils.IsStringInSlice(perm, validAdminPerms) {
  99. return &ValidationError{err: fmt.Sprintf("invalid permission: %#v", perm)}
  100. }
  101. }
  102. if a.Email != "" && !emailRegex.MatchString(a.Email) {
  103. return &ValidationError{err: fmt.Sprintf("email %#v is not valid", a.Email)}
  104. }
  105. for _, IPMask := range a.Filters.AllowList {
  106. _, _, err := net.ParseCIDR(IPMask)
  107. if err != nil {
  108. return &ValidationError{err: fmt.Sprintf("could not parse allow list entry %#v : %v", IPMask, err)}
  109. }
  110. }
  111. return nil
  112. }
  113. // CheckPassword verifies the admin password
  114. func (a *Admin) CheckPassword(password string) (bool, error) {
  115. if strings.HasPrefix(a.Password, bcryptPwdPrefix) {
  116. if err := bcrypt.CompareHashAndPassword([]byte(a.Password), []byte(password)); err != nil {
  117. return false, err
  118. }
  119. return true, nil
  120. }
  121. return argon2id.ComparePasswordAndHash(password, a.Password)
  122. }
  123. // CanLoginFromIP returns true if login from the given IP is allowed
  124. func (a *Admin) CanLoginFromIP(ip string) bool {
  125. if len(a.Filters.AllowList) == 0 {
  126. return true
  127. }
  128. parsedIP := net.ParseIP(ip)
  129. if parsedIP == nil {
  130. return len(a.Filters.AllowList) == 0
  131. }
  132. for _, ipMask := range a.Filters.AllowList {
  133. _, network, err := net.ParseCIDR(ipMask)
  134. if err != nil {
  135. continue
  136. }
  137. if network.Contains(parsedIP) {
  138. return true
  139. }
  140. }
  141. return false
  142. }
  143. func (a *Admin) checkUserAndPass(password, ip string) error {
  144. if a.Status != 1 {
  145. return fmt.Errorf("admin %#v is disabled", a.Username)
  146. }
  147. if a.Password == "" || password == "" {
  148. return errors.New("credentials cannot be null or empty")
  149. }
  150. match, err := a.CheckPassword(password)
  151. if err != nil {
  152. return err
  153. }
  154. if !match {
  155. return ErrInvalidCredentials
  156. }
  157. if !a.CanLoginFromIP(ip) {
  158. return fmt.Errorf("login from IP %v not allowed", ip)
  159. }
  160. return nil
  161. }
  162. // HideConfidentialData hides admin confidential data
  163. func (a *Admin) HideConfidentialData() {
  164. a.Password = ""
  165. }
  166. // HasPermission returns true if the admin has the specified permission
  167. func (a *Admin) HasPermission(perm string) bool {
  168. if utils.IsStringInSlice(PermAdminAny, a.Permissions) {
  169. return true
  170. }
  171. return utils.IsStringInSlice(perm, a.Permissions)
  172. }
  173. // GetPermissionsAsString returns permission as string
  174. func (a *Admin) GetPermissionsAsString() string {
  175. return strings.Join(a.Permissions, ", ")
  176. }
  177. // GetAllowedIPAsString returns the allowed IP as comma separated string
  178. func (a *Admin) GetAllowedIPAsString() string {
  179. return strings.Join(a.Filters.AllowList, ",")
  180. }
  181. // GetValidPerms returns the allowed admin permissions
  182. func (a *Admin) GetValidPerms() []string {
  183. return validAdminPerms
  184. }
  185. // GetInfoString returns admin's info as string.
  186. func (a *Admin) GetInfoString() string {
  187. var result string
  188. if a.Email != "" {
  189. result = fmt.Sprintf("Email: %v. ", a.Email)
  190. }
  191. if len(a.Filters.AllowList) > 0 {
  192. result += fmt.Sprintf("Allowed IP/Mask: %v. ", len(a.Filters.AllowList))
  193. }
  194. return result
  195. }
  196. // GetSignature returns a signature for this admin.
  197. // It could change after an update
  198. func (a *Admin) GetSignature() string {
  199. data := []byte(a.Username)
  200. data = append(data, []byte(a.Password)...)
  201. signature := sha256.Sum256(data)
  202. return base64.StdEncoding.EncodeToString(signature[:])
  203. }
  204. func (a *Admin) getACopy() Admin {
  205. permissions := make([]string, len(a.Permissions))
  206. copy(permissions, a.Permissions)
  207. filters := AdminFilters{}
  208. filters.AllowList = make([]string, len(a.Filters.AllowList))
  209. copy(filters.AllowList, a.Filters.AllowList)
  210. return Admin{
  211. ID: a.ID,
  212. Status: a.Status,
  213. Username: a.Username,
  214. Password: a.Password,
  215. Email: a.Email,
  216. Permissions: permissions,
  217. Filters: filters,
  218. AdditionalInfo: a.AdditionalInfo,
  219. Description: a.Description,
  220. }
  221. }
  222. // setDefaults sets the appropriate value for the default admin
  223. func (a *Admin) setDefaults() {
  224. a.Username = "admin"
  225. a.Password = "password"
  226. a.Status = 1
  227. a.Permissions = []string{PermAdminAny}
  228. }