defender.go 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371
  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 common
  15. import (
  16. "encoding/json"
  17. "fmt"
  18. "net"
  19. "os"
  20. "strings"
  21. "sync"
  22. "time"
  23. "github.com/yl2chen/cidranger"
  24. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  25. "github.com/drakkan/sftpgo/v2/internal/logger"
  26. "github.com/drakkan/sftpgo/v2/internal/util"
  27. )
  28. // HostEvent is the enumerable for the supported host events
  29. type HostEvent int
  30. // Supported host events
  31. const (
  32. HostEventLoginFailed HostEvent = iota
  33. HostEventUserNotFound
  34. HostEventNoLoginTried
  35. HostEventLimitExceeded
  36. )
  37. // Supported defender drivers
  38. const (
  39. DefenderDriverMemory = "memory"
  40. DefenderDriverProvider = "provider"
  41. )
  42. var (
  43. supportedDefenderDrivers = []string{DefenderDriverMemory, DefenderDriverProvider}
  44. )
  45. // Defender defines the interface that a defender must implements
  46. type Defender interface {
  47. GetHosts() ([]dataprovider.DefenderEntry, error)
  48. GetHost(ip string) (dataprovider.DefenderEntry, error)
  49. AddEvent(ip string, event HostEvent)
  50. IsBanned(ip string) bool
  51. GetBanTime(ip string) (*time.Time, error)
  52. GetScore(ip string) (int, error)
  53. DeleteHost(ip string) bool
  54. Reload() error
  55. }
  56. // DefenderConfig defines the "defender" configuration
  57. type DefenderConfig struct {
  58. // Set to true to enable the defender
  59. Enabled bool `json:"enabled" mapstructure:"enabled"`
  60. // Defender implementation to use, we support "memory" and "provider".
  61. // Using "provider" as driver you can share the defender events among
  62. // multiple SFTPGo instances. For a single instance "memory" provider will
  63. // be much faster
  64. Driver string `json:"driver" mapstructure:"driver"`
  65. // BanTime is the number of minutes that a host is banned
  66. BanTime int `json:"ban_time" mapstructure:"ban_time"`
  67. // Percentage increase of the ban time if a banned host tries to connect again
  68. BanTimeIncrement int `json:"ban_time_increment" mapstructure:"ban_time_increment"`
  69. // Threshold value for banning a client
  70. Threshold int `json:"threshold" mapstructure:"threshold"`
  71. // Score for invalid login attempts, eg. non-existent user accounts
  72. ScoreInvalid int `json:"score_invalid" mapstructure:"score_invalid"`
  73. // Score for valid login attempts, eg. user accounts that exist
  74. ScoreValid int `json:"score_valid" mapstructure:"score_valid"`
  75. // Score for limit exceeded events, generated from the rate limiters or for max connections
  76. // per-host exceeded
  77. ScoreLimitExceeded int `json:"score_limit_exceeded" mapstructure:"score_limit_exceeded"`
  78. // ScoreNoAuth defines the score for clients disconnected without authentication
  79. // attempts
  80. ScoreNoAuth int `json:"score_no_auth" mapstructure:"score_no_auth"`
  81. // Defines the time window, in minutes, for tracking client errors.
  82. // A host is banned if it has exceeded the defined threshold during
  83. // the last observation time minutes
  84. ObservationTime int `json:"observation_time" mapstructure:"observation_time"`
  85. // The number of banned IPs and host scores kept in memory will vary between the
  86. // soft and hard limit for the "memory" driver. For the "provider" driver the
  87. // soft limit is ignored and the hard limit is used to limit the number of entries
  88. // to return when you request for the entire host list from the defender
  89. EntriesSoftLimit int `json:"entries_soft_limit" mapstructure:"entries_soft_limit"`
  90. EntriesHardLimit int `json:"entries_hard_limit" mapstructure:"entries_hard_limit"`
  91. // Path to a file containing a list of IP addresses and/or networks to never ban
  92. SafeListFile string `json:"safelist_file" mapstructure:"safelist_file"`
  93. // Path to a file containing a list of IP addresses and/or networks to always ban
  94. BlockListFile string `json:"blocklist_file" mapstructure:"blocklist_file"`
  95. // List of IP addresses and/or networks to never ban.
  96. // For large lists prefer SafeListFile
  97. SafeList []string `json:"safelist" mapstructure:"safelist"`
  98. // List of IP addresses and/or networks to always ban.
  99. // For large lists prefer BlockListFile
  100. BlockList []string `json:"blocklist" mapstructure:"blocklist"`
  101. }
  102. type baseDefender struct {
  103. config *DefenderConfig
  104. sync.RWMutex
  105. safeList *HostList
  106. blockList *HostList
  107. }
  108. // Reload reloads block and safe lists
  109. func (d *baseDefender) Reload() error {
  110. blockList, err := loadHostListFromFile(d.config.BlockListFile)
  111. if err != nil {
  112. return err
  113. }
  114. blockList = addEntriesToList(d.config.BlockList, blockList, "blocklist")
  115. d.Lock()
  116. d.blockList = blockList
  117. d.Unlock()
  118. safeList, err := loadHostListFromFile(d.config.SafeListFile)
  119. if err != nil {
  120. return err
  121. }
  122. safeList = addEntriesToList(d.config.SafeList, safeList, "safelist")
  123. d.Lock()
  124. d.safeList = safeList
  125. d.Unlock()
  126. return nil
  127. }
  128. func (d *baseDefender) isBanned(ip string) bool {
  129. if d.blockList != nil && d.blockList.isListed(ip) {
  130. // permanent ban
  131. return true
  132. }
  133. return false
  134. }
  135. func (d *baseDefender) getScore(event HostEvent) int {
  136. var score int
  137. switch event {
  138. case HostEventLoginFailed:
  139. score = d.config.ScoreValid
  140. case HostEventLimitExceeded:
  141. score = d.config.ScoreLimitExceeded
  142. case HostEventUserNotFound:
  143. score = d.config.ScoreInvalid
  144. case HostEventNoLoginTried:
  145. score = d.config.ScoreNoAuth
  146. }
  147. return score
  148. }
  149. // HostListFile defines the structure expected for safe/block list files
  150. type HostListFile struct {
  151. IPAddresses []string `json:"addresses"`
  152. CIDRNetworks []string `json:"networks"`
  153. }
  154. // HostList defines the structure used to keep the HostListFile in memory
  155. type HostList struct {
  156. IPAddresses map[string]bool
  157. Ranges cidranger.Ranger
  158. }
  159. func (h *HostList) isListed(ip string) bool {
  160. if _, ok := h.IPAddresses[ip]; ok {
  161. return true
  162. }
  163. ok, err := h.Ranges.Contains(net.ParseIP(ip))
  164. if err != nil {
  165. return false
  166. }
  167. return ok
  168. }
  169. type hostEvent struct {
  170. dateTime time.Time
  171. score int
  172. }
  173. type hostScore struct {
  174. TotalScore int
  175. Events []hostEvent
  176. }
  177. func (c *DefenderConfig) checkScores() error {
  178. if c.ScoreInvalid < 0 {
  179. c.ScoreInvalid = 0
  180. }
  181. if c.ScoreValid < 0 {
  182. c.ScoreValid = 0
  183. }
  184. if c.ScoreLimitExceeded < 0 {
  185. c.ScoreLimitExceeded = 0
  186. }
  187. if c.ScoreNoAuth < 0 {
  188. c.ScoreNoAuth = 0
  189. }
  190. if c.ScoreInvalid == 0 && c.ScoreValid == 0 && c.ScoreLimitExceeded == 0 && c.ScoreNoAuth == 0 {
  191. return fmt.Errorf("invalid defender configuration: all scores are disabled")
  192. }
  193. return nil
  194. }
  195. // validate returns an error if the configuration is invalid
  196. func (c *DefenderConfig) validate() error {
  197. if !c.Enabled {
  198. return nil
  199. }
  200. if err := c.checkScores(); err != nil {
  201. return err
  202. }
  203. if c.ScoreInvalid >= c.Threshold {
  204. return fmt.Errorf("score_invalid %v cannot be greater than threshold %v", c.ScoreInvalid, c.Threshold)
  205. }
  206. if c.ScoreValid >= c.Threshold {
  207. return fmt.Errorf("score_valid %v cannot be greater than threshold %v", c.ScoreValid, c.Threshold)
  208. }
  209. if c.ScoreLimitExceeded >= c.Threshold {
  210. return fmt.Errorf("score_limit_exceeded %v cannot be greater than threshold %v", c.ScoreLimitExceeded, c.Threshold)
  211. }
  212. if c.ScoreNoAuth >= c.Threshold {
  213. return fmt.Errorf("score_no_auth %d cannot be greater than threshold %d", c.ScoreNoAuth, c.Threshold)
  214. }
  215. if c.BanTime <= 0 {
  216. return fmt.Errorf("invalid ban_time %v", c.BanTime)
  217. }
  218. if c.BanTimeIncrement <= 0 {
  219. return fmt.Errorf("invalid ban_time_increment %v", c.BanTimeIncrement)
  220. }
  221. if c.ObservationTime <= 0 {
  222. return fmt.Errorf("invalid observation_time %v", c.ObservationTime)
  223. }
  224. if c.EntriesSoftLimit <= 0 {
  225. return fmt.Errorf("invalid entries_soft_limit %v", c.EntriesSoftLimit)
  226. }
  227. if c.EntriesHardLimit <= c.EntriesSoftLimit {
  228. return fmt.Errorf("invalid entries_hard_limit %v must be > %v", c.EntriesHardLimit, c.EntriesSoftLimit)
  229. }
  230. return nil
  231. }
  232. func loadHostListFromFile(name string) (*HostList, error) {
  233. if name == "" {
  234. return nil, nil
  235. }
  236. if !util.IsFileInputValid(name) {
  237. return nil, fmt.Errorf("invalid host list file name %#v", name)
  238. }
  239. info, err := os.Stat(name)
  240. if err != nil {
  241. return nil, err
  242. }
  243. // opinionated max size, you should avoid big host lists
  244. if info.Size() > 1048576*5 { // 5MB
  245. return nil, fmt.Errorf("host list file %#v is too big: %v bytes", name, info.Size())
  246. }
  247. content, err := os.ReadFile(name)
  248. if err != nil {
  249. return nil, fmt.Errorf("unable to read input file %#v: %v", name, err)
  250. }
  251. var hostList HostListFile
  252. err = json.Unmarshal(content, &hostList)
  253. if err != nil {
  254. return nil, err
  255. }
  256. if len(hostList.CIDRNetworks) > 0 || len(hostList.IPAddresses) > 0 {
  257. result := &HostList{
  258. IPAddresses: make(map[string]bool),
  259. Ranges: cidranger.NewPCTrieRanger(),
  260. }
  261. ipCount := 0
  262. cdrCount := 0
  263. for _, ip := range hostList.IPAddresses {
  264. if net.ParseIP(ip) == nil {
  265. logger.Warn(logSender, "", "unable to parse IP %#v", ip)
  266. continue
  267. }
  268. result.IPAddresses[ip] = true
  269. ipCount++
  270. }
  271. for _, cidrNet := range hostList.CIDRNetworks {
  272. _, network, err := net.ParseCIDR(cidrNet)
  273. if err != nil {
  274. logger.Warn(logSender, "", "unable to parse CIDR network %#v: %v", cidrNet, err)
  275. continue
  276. }
  277. err = result.Ranges.Insert(cidranger.NewBasicRangerEntry(*network))
  278. if err == nil {
  279. cdrCount++
  280. }
  281. }
  282. logger.Info(logSender, "", "list %#v loaded, ip addresses loaded: %v/%v networks loaded: %v/%v",
  283. name, ipCount, len(hostList.IPAddresses), cdrCount, len(hostList.CIDRNetworks))
  284. return result, nil
  285. }
  286. return nil, nil
  287. }
  288. func addEntriesToList(entries []string, hostList *HostList, listName string) *HostList {
  289. if len(entries) == 0 {
  290. return hostList
  291. }
  292. if hostList == nil {
  293. hostList = &HostList{
  294. IPAddresses: make(map[string]bool),
  295. Ranges: cidranger.NewPCTrieRanger(),
  296. }
  297. }
  298. ipCount := 0
  299. ipLoaded := 0
  300. cdrCount := 0
  301. cdrLoaded := 0
  302. for _, entry := range entries {
  303. entry = strings.TrimSpace(entry)
  304. if strings.LastIndex(entry, "/") > 0 {
  305. cdrCount++
  306. _, network, err := net.ParseCIDR(entry)
  307. if err != nil {
  308. logger.Warn(logSender, "", "unable to parse CIDR network %#v: %v", entry, err)
  309. continue
  310. }
  311. err = hostList.Ranges.Insert(cidranger.NewBasicRangerEntry(*network))
  312. if err == nil {
  313. cdrLoaded++
  314. }
  315. } else {
  316. ipCount++
  317. if net.ParseIP(entry) == nil {
  318. logger.Warn(logSender, "", "unable to parse IP %#v", entry)
  319. continue
  320. }
  321. hostList.IPAddresses[entry] = true
  322. ipLoaded++
  323. }
  324. }
  325. logger.Info(logSender, "", "%s from config loaded, ip addresses loaded: %v/%v networks loaded: %v/%v",
  326. listName, ipLoaded, ipCount, cdrLoaded, cdrCount)
  327. return hostList
  328. }