defender.go 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. // Copyright (C) 2019-2023 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. "fmt"
  17. "time"
  18. "github.com/drakkan/sftpgo/v2/internal/dataprovider"
  19. )
  20. // HostEvent is the enumerable for the supported host events
  21. type HostEvent int
  22. // Supported host events
  23. const (
  24. HostEventLoginFailed HostEvent = iota
  25. HostEventUserNotFound
  26. HostEventNoLoginTried
  27. HostEventLimitExceeded
  28. )
  29. // Supported defender drivers
  30. const (
  31. DefenderDriverMemory = "memory"
  32. DefenderDriverProvider = "provider"
  33. )
  34. var (
  35. supportedDefenderDrivers = []string{DefenderDriverMemory, DefenderDriverProvider}
  36. )
  37. // Defender defines the interface that a defender must implements
  38. type Defender interface {
  39. GetHosts() ([]dataprovider.DefenderEntry, error)
  40. GetHost(ip string) (dataprovider.DefenderEntry, error)
  41. AddEvent(ip, protocol string, event HostEvent)
  42. IsBanned(ip, protocol string) bool
  43. IsSafe(ip, protocol string) bool
  44. GetBanTime(ip string) (*time.Time, error)
  45. GetScore(ip string) (int, error)
  46. DeleteHost(ip string) bool
  47. }
  48. // DefenderConfig defines the "defender" configuration
  49. type DefenderConfig struct {
  50. // Set to true to enable the defender
  51. Enabled bool `json:"enabled" mapstructure:"enabled"`
  52. // Defender implementation to use, we support "memory" and "provider".
  53. // Using "provider" as driver you can share the defender events among
  54. // multiple SFTPGo instances. For a single instance "memory" provider will
  55. // be much faster
  56. Driver string `json:"driver" mapstructure:"driver"`
  57. // BanTime is the number of minutes that a host is banned
  58. BanTime int `json:"ban_time" mapstructure:"ban_time"`
  59. // Percentage increase of the ban time if a banned host tries to connect again
  60. BanTimeIncrement int `json:"ban_time_increment" mapstructure:"ban_time_increment"`
  61. // Threshold value for banning a client
  62. Threshold int `json:"threshold" mapstructure:"threshold"`
  63. // Score for invalid login attempts, eg. non-existent user accounts
  64. ScoreInvalid int `json:"score_invalid" mapstructure:"score_invalid"`
  65. // Score for valid login attempts, eg. user accounts that exist
  66. ScoreValid int `json:"score_valid" mapstructure:"score_valid"`
  67. // Score for limit exceeded events, generated from the rate limiters or for max connections
  68. // per-host exceeded
  69. ScoreLimitExceeded int `json:"score_limit_exceeded" mapstructure:"score_limit_exceeded"`
  70. // ScoreNoAuth defines the score for clients disconnected without authentication
  71. // attempts
  72. ScoreNoAuth int `json:"score_no_auth" mapstructure:"score_no_auth"`
  73. // Defines the time window, in minutes, for tracking client errors.
  74. // A host is banned if it has exceeded the defined threshold during
  75. // the last observation time minutes
  76. ObservationTime int `json:"observation_time" mapstructure:"observation_time"`
  77. // The number of banned IPs and host scores kept in memory will vary between the
  78. // soft and hard limit for the "memory" driver. For the "provider" driver the
  79. // soft limit is ignored and the hard limit is used to limit the number of entries
  80. // to return when you request for the entire host list from the defender
  81. EntriesSoftLimit int `json:"entries_soft_limit" mapstructure:"entries_soft_limit"`
  82. EntriesHardLimit int `json:"entries_hard_limit" mapstructure:"entries_hard_limit"`
  83. }
  84. type baseDefender struct {
  85. config *DefenderConfig
  86. ipList *dataprovider.IPList
  87. }
  88. func (d *baseDefender) isBanned(ip, protocol string) bool {
  89. isListed, mode, err := d.ipList.IsListed(ip, protocol)
  90. if err != nil {
  91. return false
  92. }
  93. if isListed && mode == dataprovider.ListModeDeny {
  94. return true
  95. }
  96. return false
  97. }
  98. func (d *baseDefender) IsSafe(ip, protocol string) bool {
  99. isListed, mode, err := d.ipList.IsListed(ip, protocol)
  100. if err == nil && isListed && mode == dataprovider.ListModeAllow {
  101. return true
  102. }
  103. return false
  104. }
  105. func (d *baseDefender) getScore(event HostEvent) int {
  106. var score int
  107. switch event {
  108. case HostEventLoginFailed:
  109. score = d.config.ScoreValid
  110. case HostEventLimitExceeded:
  111. score = d.config.ScoreLimitExceeded
  112. case HostEventUserNotFound:
  113. score = d.config.ScoreInvalid
  114. case HostEventNoLoginTried:
  115. score = d.config.ScoreNoAuth
  116. }
  117. return score
  118. }
  119. type hostEvent struct {
  120. dateTime time.Time
  121. score int
  122. }
  123. type hostScore struct {
  124. TotalScore int
  125. Events []hostEvent
  126. }
  127. func (c *DefenderConfig) checkScores() error {
  128. if c.ScoreInvalid < 0 {
  129. c.ScoreInvalid = 0
  130. }
  131. if c.ScoreValid < 0 {
  132. c.ScoreValid = 0
  133. }
  134. if c.ScoreLimitExceeded < 0 {
  135. c.ScoreLimitExceeded = 0
  136. }
  137. if c.ScoreNoAuth < 0 {
  138. c.ScoreNoAuth = 0
  139. }
  140. if c.ScoreInvalid == 0 && c.ScoreValid == 0 && c.ScoreLimitExceeded == 0 && c.ScoreNoAuth == 0 {
  141. return fmt.Errorf("invalid defender configuration: all scores are disabled")
  142. }
  143. return nil
  144. }
  145. // validate returns an error if the configuration is invalid
  146. func (c *DefenderConfig) validate() error {
  147. if !c.Enabled {
  148. return nil
  149. }
  150. if err := c.checkScores(); err != nil {
  151. return err
  152. }
  153. if c.ScoreInvalid >= c.Threshold {
  154. return fmt.Errorf("score_invalid %d cannot be greater than threshold %d", c.ScoreInvalid, c.Threshold)
  155. }
  156. if c.ScoreValid >= c.Threshold {
  157. return fmt.Errorf("score_valid %d cannot be greater than threshold %d", c.ScoreValid, c.Threshold)
  158. }
  159. if c.ScoreLimitExceeded >= c.Threshold {
  160. return fmt.Errorf("score_limit_exceeded %d cannot be greater than threshold %d", c.ScoreLimitExceeded, c.Threshold)
  161. }
  162. if c.ScoreNoAuth >= c.Threshold {
  163. return fmt.Errorf("score_no_auth %d cannot be greater than threshold %d", c.ScoreNoAuth, 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. }