defenderdb.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157
  1. package common
  2. import (
  3. "time"
  4. "github.com/drakkan/sftpgo/v2/dataprovider"
  5. "github.com/drakkan/sftpgo/v2/logger"
  6. "github.com/drakkan/sftpgo/v2/util"
  7. )
  8. type dbDefender struct {
  9. baseDefender
  10. lastCleanup time.Time
  11. }
  12. func newDBDefender(config *DefenderConfig) (Defender, error) {
  13. err := config.validate()
  14. if err != nil {
  15. return nil, err
  16. }
  17. defender := &dbDefender{
  18. baseDefender: baseDefender{
  19. config: config,
  20. },
  21. lastCleanup: time.Time{},
  22. }
  23. if err := defender.Reload(); err != nil {
  24. return nil, err
  25. }
  26. return defender, nil
  27. }
  28. // GetHosts returns hosts that are banned or for which some violations have been detected
  29. func (d *dbDefender) GetHosts() ([]*dataprovider.DefenderEntry, error) {
  30. return dataprovider.GetDefenderHosts(d.getStartObservationTime(), d.config.EntriesHardLimit)
  31. }
  32. // GetHost returns a defender host by ip, if any
  33. func (d *dbDefender) GetHost(ip string) (*dataprovider.DefenderEntry, error) {
  34. return dataprovider.GetDefenderHostByIP(ip, d.getStartObservationTime())
  35. }
  36. // IsBanned returns true if the specified IP is banned
  37. // and increase ban time if the IP is found.
  38. // This method must be called as soon as the client connects
  39. func (d *dbDefender) IsBanned(ip string) bool {
  40. d.RLock()
  41. if d.baseDefender.isBanned(ip) {
  42. d.RUnlock()
  43. return true
  44. }
  45. d.RUnlock()
  46. _, err := dataprovider.IsDefenderHostBanned(ip)
  47. if err != nil {
  48. // not found or another error, we allow this host
  49. return false
  50. }
  51. increment := d.config.BanTime * d.config.BanTimeIncrement / 100
  52. if increment == 0 {
  53. increment++
  54. }
  55. dataprovider.UpdateDefenderBanTime(ip, increment) //nolint:errcheck
  56. return true
  57. }
  58. // DeleteHost removes the specified IP from the defender lists
  59. func (d *dbDefender) DeleteHost(ip string) bool {
  60. if _, err := d.GetHost(ip); err != nil {
  61. return false
  62. }
  63. return dataprovider.DeleteDefenderHost(ip) == nil
  64. }
  65. // AddEvent adds an event for the given IP.
  66. // This method must be called for clients not yet banned
  67. func (d *dbDefender) AddEvent(ip string, event HostEvent) {
  68. d.RLock()
  69. if d.safeList != nil && d.safeList.isListed(ip) {
  70. d.RUnlock()
  71. return
  72. }
  73. d.RUnlock()
  74. score := d.baseDefender.getScore(event)
  75. host, err := dataprovider.AddDefenderEvent(ip, score, d.getStartObservationTime())
  76. if err != nil {
  77. return
  78. }
  79. if host.Score > d.config.Threshold {
  80. banTime := time.Now().Add(time.Duration(d.config.BanTime) * time.Minute)
  81. err = dataprovider.SetDefenderBanTime(ip, util.GetTimeAsMsSinceEpoch(banTime))
  82. }
  83. if err == nil {
  84. d.cleanup()
  85. }
  86. }
  87. // GetBanTime returns the ban time for the given IP or nil if the IP is not banned
  88. func (d *dbDefender) GetBanTime(ip string) (*time.Time, error) {
  89. host, err := d.GetHost(ip)
  90. if err != nil {
  91. return nil, err
  92. }
  93. if host.BanTime.IsZero() {
  94. return nil, nil
  95. }
  96. return &host.BanTime, nil
  97. }
  98. // GetScore returns the score for the given IP
  99. func (d *dbDefender) GetScore(ip string) (int, error) {
  100. host, err := d.GetHost(ip)
  101. if err != nil {
  102. return 0, err
  103. }
  104. return host.Score, nil
  105. }
  106. func (d *dbDefender) cleanup() {
  107. lastCleanup := d.getLastCleanup()
  108. if lastCleanup.IsZero() || lastCleanup.Add(time.Duration(d.config.ObservationTime)*time.Minute*3).Before(time.Now()) {
  109. // FIXME: this could be racy in rare cases but it is better than acquire the lock for the cleanup duration
  110. // or to always acquire a read/write lock.
  111. // Concurrent cleanups could happen anyway from multiple SFTPGo instances and should not cause any issues
  112. d.setLastCleanup(time.Now())
  113. expireTime := time.Now().Add(-time.Duration(d.config.ObservationTime+1) * time.Minute)
  114. logger.Debug(logSender, "", "cleanup defender hosts before %v, last cleanup %v", expireTime, lastCleanup)
  115. if err := dataprovider.CleanupDefender(util.GetTimeAsMsSinceEpoch(expireTime)); err != nil {
  116. logger.Error(logSender, "", "defender cleanup error, reset last cleanup to %v", lastCleanup)
  117. d.setLastCleanup(lastCleanup)
  118. }
  119. }
  120. }
  121. func (d *dbDefender) getStartObservationTime() int64 {
  122. t := time.Now().Add(-time.Duration(d.config.ObservationTime) * time.Minute)
  123. return util.GetTimeAsMsSinceEpoch(t)
  124. }
  125. func (d *dbDefender) getLastCleanup() time.Time {
  126. d.RLock()
  127. defer d.RUnlock()
  128. return d.lastCleanup
  129. }
  130. func (d *dbDefender) setLastCleanup(when time.Time) {
  131. d.Lock()
  132. defer d.Unlock()
  133. d.lastCleanup = when
  134. }