213 lines
5.4 KiB
Go
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)
|
|
}
|