Files
kor-elf-shield/internal/setting/analyzer/brute_force_protection.go
T

213 lines
5.4 KiB
Go

package analyzer
import (
"errors"
"fmt"
"strconv"
"strings"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/brute_force_protection"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
type BruteForceProtection struct {
Enabled bool `mapstructure:"enabled"`
Notify bool `mapstructure:"notify"`
RateLimitCount int `mapstructure:"rate_limit_count"`
RateLimitPeriod int `mapstructure:"rate_limit_period"`
RateLimitResetPeriod int `mapstructure:"rate_limit_reset_period"`
BlockingTime int `mapstructure:"blocking_time"`
SSHEnable bool `mapstructure:"ssh_enable"`
SSHNotify bool `mapstructure:"ssh_notify"`
SSHNotifyCooldown int `mapstructure:"ssh_notify_cooldown_seconds"`
SSHNotifyEvery int `mapstructure:"ssh_notify_every"`
SSHGroup string `mapstructure:"ssh_group"`
Groups []BruteForceProtectionGroup
Rules []BruteForceProtectionRule
}
func defaultBruteForceProtection() BruteForceProtection {
return BruteForceProtection{
Enabled: true,
Notify: true,
RateLimitCount: 5,
RateLimitPeriod: 3600,
RateLimitResetPeriod: 86400,
BlockingTime: 3600,
SSHEnable: true,
SSHNotify: true,
SSHNotifyCooldown: 0,
SSHNotifyEvery: 0,
SSHGroup: "",
Groups: []BruteForceProtectionGroup{},
Rules: []BruteForceProtectionRule{},
}
}
func (p *BruteForceProtection) Validate() error {
if p.RateLimitPeriod <= 0 {
return errors.New("rate limit period must be greater than 0")
}
if p.RateLimitCount <= 0 {
return errors.New("rate limit count must be greater than 0")
}
if p.RateLimitResetPeriod < 0 {
return errors.New("rate limit reset period must be positive")
}
if p.BlockingTime < 0 {
return errors.New("blocking time must be positive")
}
if p.SSHNotifyCooldown < 0 {
return errors.New("ssh notify cooldown must be positive")
}
if p.SSHNotifyEvery < 0 {
return errors.New("ssh notify every must be positive")
}
return nil
}
func (p *BruteForceProtection) ToSources() ([]*config.Source, error) {
var sources []*config.Source
if !p.Enabled {
return sources, nil
}
groups, err := p.groups()
if err != nil {
return nil, err
}
if p.SSHEnable {
sshGroup := "_default"
if p.SSHGroup != "" {
if _, ok := groups[p.SSHGroup]; !ok {
return nil, errors.New("ssh group not found")
}
sshGroup = p.SSHGroup
}
sshSources, err := config.NewBruteForceProtectionSSH(p.Notify && p.SSHNotify, p.SSHNotifyCooldown, p.SSHNotifyEvery, groups[sshGroup])
if err != nil {
return nil, err
}
sources = append(sources, sshSources...)
}
for _, rule := range p.Rules {
if !rule.Enabled {
continue
}
var group *brute_force_protection.Group
groupName := "_default"
if rule.Group != "" {
groupName = rule.Group
}
if _, ok := groups[groupName]; !ok {
return nil, fmt.Errorf("group %q not found", rule.Group)
}
group = groups[groupName]
source, err := rule.ToSource(p.Notify, group)
if err != nil {
return nil, err
}
sources = append(sources, source)
}
return sources, nil
}
func (p *BruteForceProtection) groups() (map[string]*brute_force_protection.Group, error) {
groups := make(map[string]*brute_force_protection.Group)
groups["_default"] = &brute_force_protection.Group{
Name: "_default",
Message: i18n.Lang.T("alert.bruteForceProtection.group._default.message"),
RateLimits: []brute_force_protection.RateLimit{
{
Count: uint32(p.RateLimitCount),
Period: uint32(p.RateLimitPeriod),
BlockingTimeSeconds: uint32(p.BlockingTime),
BlockConfig: brute_force_protection.NewBlockOnceIPConfig(),
},
},
RateLimitResetPeriod: uint32(p.RateLimitResetPeriod),
}
for _, group := range p.Groups {
g, err := group.ToGroup()
if err != nil {
return nil, err
}
groups[g.Name] = g
}
return groups, nil
}
func toBlockConfigBySettings(blockType string, ports []string) (brute_force_protection.Block, error) {
if blockType == "" {
return nil, errors.New("block type is empty")
}
switch blockType {
case "ip":
return brute_force_protection.NewBlockOnceIPConfig(), nil
case "ip_port":
if len(ports) == 0 {
return nil, errors.New("ports is empty")
}
var blockPorts []types.L4Port
for _, port := range ports {
l4Port, err := toL4Port(port)
if err != nil {
return nil, err
}
blockPorts = append(blockPorts, l4Port)
}
return brute_force_protection.NewBlockIPAndPortsConfig(blockPorts), nil
}
return nil, errors.New("unknown block type")
}
func toL4Port(portString string) (types.L4Port, error) {
if portString == "" {
return nil, errors.New("port is empty")
}
data := strings.Split(portString, "/")
protocol := types.ProtocolTCP
port, err := strconv.Atoi(data[0])
if err != nil {
return nil, err
}
if err := validate.Port(port, "port"); err != nil {
return nil, err
}
if len(data) == 2 {
protocol, err = ip.ToProtocol(data[1])
if err != nil {
return nil, err
}
}
return types.NewL4Port(uint16(port), protocol)
}