admin.go 9.9 KB

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