defender.go 7.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274
  1. package common
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. "net"
  6. "os"
  7. "sync"
  8. "time"
  9. "github.com/yl2chen/cidranger"
  10. "github.com/drakkan/sftpgo/v2/dataprovider"
  11. "github.com/drakkan/sftpgo/v2/logger"
  12. "github.com/drakkan/sftpgo/v2/util"
  13. )
  14. // HostEvent is the enumerable for the supported host events
  15. type HostEvent int
  16. // Supported host events
  17. const (
  18. HostEventLoginFailed HostEvent = iota
  19. HostEventUserNotFound
  20. HostEventNoLoginTried
  21. HostEventLimitExceeded
  22. )
  23. // Supported defender drivers
  24. const (
  25. DefenderDriverMemory = "memory"
  26. DefenderDriverProvider = "provider"
  27. )
  28. var (
  29. supportedDefenderDrivers = []string{DefenderDriverMemory, DefenderDriverProvider}
  30. )
  31. // Defender defines the interface that a defender must implements
  32. type Defender interface {
  33. GetHosts() ([]*dataprovider.DefenderEntry, error)
  34. GetHost(ip string) (*dataprovider.DefenderEntry, error)
  35. AddEvent(ip string, event HostEvent)
  36. IsBanned(ip string) bool
  37. GetBanTime(ip string) (*time.Time, error)
  38. GetScore(ip string) (int, error)
  39. DeleteHost(ip string) bool
  40. Reload() error
  41. }
  42. // DefenderConfig defines the "defender" configuration
  43. type DefenderConfig struct {
  44. // Set to true to enable the defender
  45. Enabled bool `json:"enabled" mapstructure:"enabled"`
  46. // Defender implementation to use, we support "memory" and "provider".
  47. // Using "provider" as driver you can share the defender events among
  48. // multiple SFTPGo instances. For a single instance "memory" provider will
  49. // be much faster
  50. Driver string `json:"driver" mapstructure:"driver"`
  51. // BanTime is the number of minutes that a host is banned
  52. BanTime int `json:"ban_time" mapstructure:"ban_time"`
  53. // Percentage increase of the ban time if a banned host tries to connect again
  54. BanTimeIncrement int `json:"ban_time_increment" mapstructure:"ban_time_increment"`
  55. // Threshold value for banning a client
  56. Threshold int `json:"threshold" mapstructure:"threshold"`
  57. // Score for invalid login attempts, eg. non-existent user accounts or
  58. // client disconnected for inactivity without authentication attempts
  59. ScoreInvalid int `json:"score_invalid" mapstructure:"score_invalid"`
  60. // Score for valid login attempts, eg. user accounts that exist
  61. ScoreValid int `json:"score_valid" mapstructure:"score_valid"`
  62. // Score for limit exceeded events, generated from the rate limiters or for max connections
  63. // per-host exceeded
  64. ScoreLimitExceeded int `json:"score_limit_exceeded" mapstructure:"score_limit_exceeded"`
  65. // Defines the time window, in minutes, for tracking client errors.
  66. // A host is banned if it has exceeded the defined threshold during
  67. // the last observation time minutes
  68. ObservationTime int `json:"observation_time" mapstructure:"observation_time"`
  69. // The number of banned IPs and host scores kept in memory will vary between the
  70. // soft and hard limit for the "memory" driver. For the "provider" driver the
  71. // soft limit is ignored and the hard limit is used to limit the number of entries
  72. // to return when you request for the entire host list from the defender
  73. EntriesSoftLimit int `json:"entries_soft_limit" mapstructure:"entries_soft_limit"`
  74. EntriesHardLimit int `json:"entries_hard_limit" mapstructure:"entries_hard_limit"`
  75. // Path to a file containing a list of ip addresses and/or networks to never ban
  76. SafeListFile string `json:"safelist_file" mapstructure:"safelist_file"`
  77. // Path to a file containing a list of ip addresses and/or networks to always ban
  78. BlockListFile string `json:"blocklist_file" mapstructure:"blocklist_file"`
  79. }
  80. type baseDefender struct {
  81. config *DefenderConfig
  82. sync.RWMutex
  83. safeList *HostList
  84. blockList *HostList
  85. }
  86. // Reload reloads block and safe lists
  87. func (d *baseDefender) Reload() error {
  88. blockList, err := loadHostListFromFile(d.config.BlockListFile)
  89. if err != nil {
  90. return err
  91. }
  92. d.Lock()
  93. d.blockList = blockList
  94. d.Unlock()
  95. safeList, err := loadHostListFromFile(d.config.SafeListFile)
  96. if err != nil {
  97. return err
  98. }
  99. d.Lock()
  100. d.safeList = safeList
  101. d.Unlock()
  102. return nil
  103. }
  104. func (d *baseDefender) isBanned(ip string) bool {
  105. if d.blockList != nil && d.blockList.isListed(ip) {
  106. // permanent ban
  107. return true
  108. }
  109. return false
  110. }
  111. func (d *baseDefender) getScore(event HostEvent) int {
  112. var score int
  113. switch event {
  114. case HostEventLoginFailed:
  115. score = d.config.ScoreValid
  116. case HostEventLimitExceeded:
  117. score = d.config.ScoreLimitExceeded
  118. case HostEventUserNotFound, HostEventNoLoginTried:
  119. score = d.config.ScoreInvalid
  120. }
  121. return score
  122. }
  123. // HostListFile defines the structure expected for safe/block list files
  124. type HostListFile struct {
  125. IPAddresses []string `json:"addresses"`
  126. CIDRNetworks []string `json:"networks"`
  127. }
  128. // HostList defines the structure used to keep the HostListFile in memory
  129. type HostList struct {
  130. IPAddresses map[string]bool
  131. Ranges cidranger.Ranger
  132. }
  133. func (h *HostList) isListed(ip string) bool {
  134. if _, ok := h.IPAddresses[ip]; ok {
  135. return true
  136. }
  137. ok, err := h.Ranges.Contains(net.ParseIP(ip))
  138. if err != nil {
  139. return false
  140. }
  141. return ok
  142. }
  143. type hostEvent struct {
  144. dateTime time.Time
  145. score int
  146. }
  147. type hostScore struct {
  148. TotalScore int
  149. Events []hostEvent
  150. }
  151. // validate returns an error if the configuration is invalid
  152. func (c *DefenderConfig) validate() error {
  153. if !c.Enabled {
  154. return nil
  155. }
  156. if c.ScoreInvalid >= c.Threshold {
  157. return fmt.Errorf("score_invalid %v cannot be greater than threshold %v", c.ScoreInvalid, c.Threshold)
  158. }
  159. if c.ScoreValid >= c.Threshold {
  160. return fmt.Errorf("score_valid %v cannot be greater than threshold %v", c.ScoreValid, c.Threshold)
  161. }
  162. if c.ScoreLimitExceeded >= c.Threshold {
  163. return fmt.Errorf("score_limit_exceeded %v cannot be greater than threshold %v", c.ScoreLimitExceeded, c.Threshold)
  164. }
  165. if c.BanTime <= 0 {
  166. return fmt.Errorf("invalid ban_time %v", c.BanTime)
  167. }
  168. if c.BanTimeIncrement <= 0 {
  169. return fmt.Errorf("invalid ban_time_increment %v", c.BanTimeIncrement)
  170. }
  171. if c.ObservationTime <= 0 {
  172. return fmt.Errorf("invalid observation_time %v", c.ObservationTime)
  173. }
  174. if c.EntriesSoftLimit <= 0 {
  175. return fmt.Errorf("invalid entries_soft_limit %v", c.EntriesSoftLimit)
  176. }
  177. if c.EntriesHardLimit <= c.EntriesSoftLimit {
  178. return fmt.Errorf("invalid entries_hard_limit %v must be > %v", c.EntriesHardLimit, c.EntriesSoftLimit)
  179. }
  180. return nil
  181. }
  182. func loadHostListFromFile(name string) (*HostList, error) {
  183. if name == "" {
  184. return nil, nil
  185. }
  186. if !util.IsFileInputValid(name) {
  187. return nil, fmt.Errorf("invalid host list file name %#v", name)
  188. }
  189. info, err := os.Stat(name)
  190. if err != nil {
  191. return nil, err
  192. }
  193. // opinionated max size, you should avoid big host lists
  194. if info.Size() > 1048576*5 { // 5MB
  195. return nil, fmt.Errorf("host list file %#v is too big: %v bytes", name, info.Size())
  196. }
  197. content, err := os.ReadFile(name)
  198. if err != nil {
  199. return nil, fmt.Errorf("unable to read input file %#v: %v", name, err)
  200. }
  201. var hostList HostListFile
  202. err = json.Unmarshal(content, &hostList)
  203. if err != nil {
  204. return nil, err
  205. }
  206. if len(hostList.CIDRNetworks) > 0 || len(hostList.IPAddresses) > 0 {
  207. result := &HostList{
  208. IPAddresses: make(map[string]bool),
  209. Ranges: cidranger.NewPCTrieRanger(),
  210. }
  211. ipCount := 0
  212. cdrCount := 0
  213. for _, ip := range hostList.IPAddresses {
  214. if net.ParseIP(ip) == nil {
  215. logger.Warn(logSender, "", "unable to parse IP %#v", ip)
  216. continue
  217. }
  218. result.IPAddresses[ip] = true
  219. ipCount++
  220. }
  221. for _, cidrNet := range hostList.CIDRNetworks {
  222. _, network, err := net.ParseCIDR(cidrNet)
  223. if err != nil {
  224. logger.Warn(logSender, "", "unable to parse CIDR network %#v", cidrNet)
  225. continue
  226. }
  227. err = result.Ranges.Insert(cidranger.NewBasicRangerEntry(*network))
  228. if err == nil {
  229. cdrCount++
  230. }
  231. }
  232. logger.Info(logSender, "", "list %#v loaded, ip addresses loaded: %v/%v networks loaded: %v/%v",
  233. name, ipCount, len(hostList.IPAddresses), cdrCount, len(hostList.CIDRNetworks))
  234. return result, nil
  235. }
  236. return nil, nil
  237. }