mirror of
https://github.com/drakkan/sftpgo.git
synced 2025-12-07 14:50:55 +03:00
add support for a basic built-in defender
It can help to prevent DoS and brute force password guessing
This commit is contained in:
@@ -106,13 +106,41 @@ var (
|
||||
)
|
||||
|
||||
// Initialize sets the common configuration
|
||||
func Initialize(c Configuration) {
|
||||
func Initialize(c Configuration) error {
|
||||
Config = c
|
||||
Config.idleLoginTimeout = 2 * time.Minute
|
||||
Config.idleTimeoutAsDuration = time.Duration(Config.IdleTimeout) * time.Minute
|
||||
if Config.IdleTimeout > 0 {
|
||||
startIdleTimeoutTicker(idleTimeoutCheckInterval)
|
||||
}
|
||||
Config.defender = nil
|
||||
if c.DefenderConfig.Enabled {
|
||||
defender, err := newInMemoryDefender(&c.DefenderConfig)
|
||||
if err != nil {
|
||||
return fmt.Errorf("defender initialization error: %v", err)
|
||||
}
|
||||
logger.Info(logSender, "", "defender initialized with config %+v", c.DefenderConfig)
|
||||
Config.defender = defender
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsBanned returns true if the specified IP address is banned
|
||||
func IsBanned(ip string) bool {
|
||||
if Config.defender == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return Config.defender.IsBanned(ip)
|
||||
}
|
||||
|
||||
// AddDefenderEvent adds the specified defender event for the given IP
|
||||
func AddDefenderEvent(ip string, event HostEvent) {
|
||||
if Config.defender == nil {
|
||||
return
|
||||
}
|
||||
|
||||
Config.defender.AddEvent(ip, event)
|
||||
}
|
||||
|
||||
func startIdleTimeoutTicker(duration time.Duration) {
|
||||
@@ -250,9 +278,12 @@ type Configuration struct {
|
||||
// ip address. Leave empty do disable.
|
||||
PostConnectHook string `json:"post_connect_hook" mapstructure:"post_connect_hook"`
|
||||
// Maximum number of concurrent client connections. 0 means unlimited
|
||||
MaxTotalConnections int `json:"max_total_connections" mapstructure:"max_total_connections"`
|
||||
MaxTotalConnections int `json:"max_total_connections" mapstructure:"max_total_connections"`
|
||||
// Defender configuration
|
||||
DefenderConfig DefenderConfig `json:"defender" mapstructure:"defender"`
|
||||
idleTimeoutAsDuration time.Duration
|
||||
idleLoginTimeout time.Duration
|
||||
defender Defender
|
||||
}
|
||||
|
||||
// IsAtomicUploadEnabled returns true if atomic upload is enabled
|
||||
@@ -294,51 +325,50 @@ func (c *Configuration) GetProxyListener(listener net.Listener) (*proxyproto.Lis
|
||||
}
|
||||
|
||||
// ExecutePostConnectHook executes the post connect hook if defined
|
||||
func (c *Configuration) ExecutePostConnectHook(remoteAddr, protocol string) error {
|
||||
func (c *Configuration) ExecutePostConnectHook(ipAddr, protocol string) error {
|
||||
if len(c.PostConnectHook) == 0 {
|
||||
return nil
|
||||
}
|
||||
ip := utils.GetIPFromRemoteAddress(remoteAddr)
|
||||
if strings.HasPrefix(c.PostConnectHook, "http") {
|
||||
var url *url.URL
|
||||
url, err := url.Parse(c.PostConnectHook)
|
||||
if err != nil {
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, invalid post connect hook %#v: %v",
|
||||
ip, c.PostConnectHook, err)
|
||||
ipAddr, c.PostConnectHook, err)
|
||||
return err
|
||||
}
|
||||
httpClient := httpclient.GetHTTPClient()
|
||||
q := url.Query()
|
||||
q.Add("ip", ip)
|
||||
q.Add("ip", ipAddr)
|
||||
q.Add("protocol", protocol)
|
||||
url.RawQuery = q.Encode()
|
||||
|
||||
resp, err := httpClient.Get(url.String())
|
||||
if err != nil {
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, error executing post connect hook: %v", ip, err)
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, error executing post connect hook: %v", ipAddr, err)
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, post connect hook response code: %v", ip, resp.StatusCode)
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, post connect hook response code: %v", ipAddr, resp.StatusCode)
|
||||
return errUnexpectedHTTResponse
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !filepath.IsAbs(c.PostConnectHook) {
|
||||
err := fmt.Errorf("invalid post connect hook %#v", c.PostConnectHook)
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied: %v", ip, err)
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied: %v", ipAddr, err)
|
||||
return err
|
||||
}
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 20*time.Second)
|
||||
defer cancel()
|
||||
cmd := exec.CommandContext(ctx, c.PostConnectHook)
|
||||
cmd.Env = append(os.Environ(),
|
||||
fmt.Sprintf("SFTPGO_CONNECTION_IP=%v", ip),
|
||||
fmt.Sprintf("SFTPGO_CONNECTION_IP=%v", ipAddr),
|
||||
fmt.Sprintf("SFTPGO_CONNECTION_PROTOCOL=%v", protocol))
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, connect hook error: %v", ip, err)
|
||||
logger.Warn(protocol, "", "Login from ip %#v denied, connect hook error: %v", ipAddr, err)
|
||||
}
|
||||
return err
|
||||
}
|
||||
@@ -537,6 +567,7 @@ func (conns *ActiveConnections) checkIdles() {
|
||||
ip := utils.GetIPFromRemoteAddress(c.GetRemoteAddress())
|
||||
logger.ConnectionFailedLog("", ip, dataprovider.LoginMethodNoAuthTryed, c.GetProtocol(), "client idle")
|
||||
metrics.AddNoAuthTryed()
|
||||
AddDefenderEvent(ip, HostEventNoLoginTried)
|
||||
dataprovider.ExecutePostLoginHook("", dataprovider.LoginMethodNoAuthTryed, ip, c.GetProtocol(),
|
||||
dataprovider.ErrNoAuthTryed)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user