Compare commits
18
Commits
29df5f3fd8
...
v0.14.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e44359d1 | ||
|
|
b782b7c975
|
||
|
|
e28fafc1e9
|
||
|
|
350c78c40a
|
||
|
|
22d7f901d0
|
||
|
|
1cff4fb2ba
|
||
|
|
2d219edd23
|
||
|
|
1cc52ca28e
|
||
|
|
7392e753ea
|
||
|
|
6148b0a06d
|
||
|
|
7322455ae0
|
||
|
|
232b63a207
|
||
|
|
4d4e7272ce
|
||
|
|
5c2b34b742
|
||
|
|
5315a4e0ff
|
||
|
|
0286ebc141
|
||
|
|
9759d9805b
|
||
|
|
bae20cbf49 |
+3
-1
@@ -1,9 +1,11 @@
|
||||
## 0.14.0 (Soon)
|
||||
## 0.14.0 (05.09.2026)
|
||||
#### Русский
|
||||
* Исправлена ошибка в правилах исходящего ICMP: при `icmp_timestamp_drop=true` теперь корректно блокируются `timestamp-reply` пакеты (вместо `timestamp-request`).
|
||||
* Добавлена новая команда `kor-elf-shield config test`. Теперь перед перезапуском демона можно проверить корректность конфигурации.
|
||||
***
|
||||
#### English
|
||||
* Fixed an issue in outbound ICMP rules: when `icmp_timestamp_drop=true`, `timestamp-reply` packets are now correctly blocked (instead of `timestamp-request`).
|
||||
* Added a new command, `kor-elf-shield config test`. Now you can check the configuration before restarting the daemon.
|
||||
***
|
||||
## 0.13.0 (16.08.2026)
|
||||
#### Русский
|
||||
|
||||
@@ -56,7 +56,7 @@ blocking_time = 3600
|
||||
rate_limit_period = 3600
|
||||
|
||||
###
|
||||
# Указываем в секундах, через какое время сбрасывать данные IP в групе _default если не было событий.
|
||||
# Указываем в секундах, через какое время сбрасывать данные IP в группе _default если не было событий.
|
||||
# Если указать 0, то не будет сбрасывать.
|
||||
# По умолчанию: 86400
|
||||
# ***
|
||||
@@ -68,7 +68,7 @@ rate_limit_reset_period = 86400
|
||||
|
||||
###
|
||||
# Включает защиту от перебора пароля от ssh.
|
||||
# !!! Примечание: По умолчанию SSH-сервер не регистрирует неудачные попытки входа с использованием ключа.
|
||||
# !!! Примечание: По умолчанию SSH-сервер не регистрирует на неудачные попытки входа с использованием ключа.
|
||||
# Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр
|
||||
# "LogLevel VERBOSE" в настройки sshd_config !!!
|
||||
# По умолчанию: true
|
||||
@@ -135,7 +135,7 @@ ssh_group = ""
|
||||
# [[bruteForceProtection.groups]]
|
||||
# name = "my_name_group" # Имя группы. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
|
||||
# message = "Любой текст группы" # Текст уведомления (обязательное поле)
|
||||
# rate_limit_reset_period = 86400 # Указываем в секундах, через какое время сбрасывать данные в групе если не было событий. Если указать 0, то не будет сбрасывать.
|
||||
# rate_limit_reset_period = 86400 # Указываем в секундах, через какое время сбрасывать данные в групе, если не было событий. Если указать 0, то не будет сбрасывать.
|
||||
## block_type = "ip_port" # Указываем тип блокировки: ip, ip_port. Если ничего не укажите, будет указан тип ip.
|
||||
## ports = ["22/tcp", "22/udp"] # Если тип блокировки стоит ip_port, то нужно указать порты, которые будут заблокированы после обнаружения попытки перебора пароля.
|
||||
# [[bruteForceProtection.groups.rate_limits]]
|
||||
|
||||
@@ -0,0 +1,186 @@
|
||||
package daemon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os/exec"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
|
||||
"github.com/urfave/cli/v3"
|
||||
)
|
||||
|
||||
func CmdConfig() *cli.Command {
|
||||
return &cli.Command{
|
||||
Name: "config",
|
||||
Usage: i18n.Lang.T("cmd.daemon.config.Usage"),
|
||||
Commands: []*cli.Command{
|
||||
{
|
||||
Name: "test",
|
||||
Usage: i18n.Lang.T("cmd.daemon.config.test.Usage"),
|
||||
Description: i18n.Lang.T("cmd.daemon.config.test.Description"),
|
||||
Action: CmdTestConfig,
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func CmdTestConfig(_ context.Context, _ *cli.Command) error {
|
||||
falseLogger := log.NewFalseLogger()
|
||||
|
||||
testMain := testMainConfig()
|
||||
testDocker, dockerSupport := testDockerConfig()
|
||||
testFirewall := testFirewallConfig(dockerSupport)
|
||||
testAnalyzer := testAnalyzerConfig()
|
||||
testNotifications := testNotificationsConfig()
|
||||
testBlocklists := testBlocklistsConfig(falseLogger)
|
||||
testGeoip := testGeoipConfig(falseLogger)
|
||||
|
||||
checkNft := checkProgramNFT()
|
||||
checkJournalctl := checkProgramJournalctl()
|
||||
checkDocker := ""
|
||||
if dockerSupport {
|
||||
checkDocker = "\n " + checkProgramDocker()
|
||||
}
|
||||
|
||||
fmt.Println(
|
||||
"***\n"+i18n.Lang.T("cmd.daemon.config.test.settingTitle"),
|
||||
"\n", testMain,
|
||||
"\n", testFirewall,
|
||||
"\n", testAnalyzer,
|
||||
"\n", testNotifications,
|
||||
"\n", testDocker,
|
||||
"\n", testBlocklists,
|
||||
"\n", testGeoip,
|
||||
"\n"+i18n.Lang.T("cmd.daemon.config.test.checkingPrograms"),
|
||||
"\n", checkNft,
|
||||
"\n", checkJournalctl,
|
||||
checkDocker,
|
||||
"\n***",
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
func testMainConfig() string {
|
||||
configTitle := i18n.Lang.T("cmd.daemon.config.test.main")
|
||||
if err := setting.Config.Validate(); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
if err := setting.Config.ValidateBeforeStart(); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(configTitle)
|
||||
}
|
||||
|
||||
func testDockerConfig() (message string, dockerSupport bool) {
|
||||
configTitle := "docker"
|
||||
_, dockerSupport, err := setting.Config.OtherSettingsPath.ToDockerConfig(setting.Config.BinaryLocations)
|
||||
if err != nil {
|
||||
return resultError(configTitle, err), false
|
||||
}
|
||||
|
||||
return resultOk(configTitle), dockerSupport
|
||||
}
|
||||
|
||||
func testFirewallConfig(dockerSupport bool) string {
|
||||
configTitle := "firewall"
|
||||
if _, _, err := setting.Config.OtherSettingsPath.ToFirewallConfig(dockerSupport); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(configTitle)
|
||||
}
|
||||
|
||||
func testAnalyzerConfig() string {
|
||||
configTitle := "analyzer"
|
||||
if _, err := setting.Config.OtherSettingsPath.ToAnalyzerConfig(setting.Config.BinaryLocations); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(configTitle)
|
||||
}
|
||||
|
||||
func testNotificationsConfig() string {
|
||||
configTitle := "notifications"
|
||||
if _, err := setting.Config.OtherSettingsPath.ToNotificationsConfig(); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(configTitle)
|
||||
}
|
||||
|
||||
func testBlocklistsConfig(logger log.Logger) string {
|
||||
configTitle := "blocklists"
|
||||
if _, _, err := setting.Config.OtherSettingsPath.ToBlocklistConfig(logger); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(configTitle)
|
||||
}
|
||||
|
||||
func testGeoipConfig(logger log.Logger) string {
|
||||
configTitle := "geoip"
|
||||
if _, _, err := setting.Config.OtherSettingsPath.ToConfig(setting.Config.DataDir, logger); err != nil {
|
||||
return resultError(configTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(configTitle)
|
||||
}
|
||||
|
||||
func checkProgramNFT() string {
|
||||
programTitle := "nftables"
|
||||
path := setting.Config.BinaryLocations.Nftables
|
||||
if path == "" {
|
||||
return resultError(programTitle, errors.New(i18n.Lang.T("cmd.daemon.config.test.pathEmpty", map[string]interface{}{"Program": programTitle})))
|
||||
}
|
||||
|
||||
cmd := exec.Command(path, "--version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return resultError(programTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(programTitle)
|
||||
}
|
||||
|
||||
func checkProgramJournalctl() string {
|
||||
programTitle := "journalctl"
|
||||
path := setting.Config.BinaryLocations.Journalctl
|
||||
if path == "" {
|
||||
return resultError(programTitle, errors.New(i18n.Lang.T("cmd.daemon.config.test.pathEmpty", map[string]interface{}{"Program": programTitle})))
|
||||
}
|
||||
|
||||
cmd := exec.Command(path, "--version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return resultError(programTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(programTitle)
|
||||
}
|
||||
|
||||
func checkProgramDocker() string {
|
||||
programTitle := "docker"
|
||||
path := setting.Config.BinaryLocations.Docker
|
||||
if path == "" {
|
||||
return resultError(programTitle, errors.New(i18n.Lang.T("cmd.daemon.config.test.pathEmpty", map[string]interface{}{"Program": programTitle})))
|
||||
}
|
||||
|
||||
cmd := exec.Command(path, "--version")
|
||||
if err := cmd.Run(); err != nil {
|
||||
return resultError(programTitle, err)
|
||||
}
|
||||
|
||||
return resultOk(programTitle)
|
||||
}
|
||||
|
||||
func resultOk(title string) string {
|
||||
return fmt.Sprintf("%s: \033[32mOk\033[0m", title)
|
||||
}
|
||||
|
||||
func resultError(title string, err error) string {
|
||||
errText := i18n.Lang.T("cmd.daemon.config.test.error", map[string]interface{}{"Error": err})
|
||||
return fmt.Sprintf("%s: \033[31mError\n %s\u001B[0m", title, errText)
|
||||
}
|
||||
@@ -37,6 +37,7 @@ func NewMainApp(appVer AppVersion, defaultConfigPath string) *cli.Command {
|
||||
daemon.CmdStart(),
|
||||
daemon.CmdStop(),
|
||||
daemon.CmdStatus(),
|
||||
daemon.CmdConfig(),
|
||||
daemon.CmdReopenLogger(),
|
||||
daemon.CmdNotifications(),
|
||||
daemon.CmdBlock(),
|
||||
|
||||
@@ -52,6 +52,16 @@
|
||||
"cmd.daemon.geoip.refresh.Description": "GeoIP database update. Must be enabled and configured in geoip.toml. \nExample: \nkor-elf-shield geoip refresh",
|
||||
"geoip_refresh_success": "GeoIP database updated successfully",
|
||||
|
||||
"cmd.daemon.config.Usage": "Configuration",
|
||||
"cmd.daemon.config.test.Usage": "Checking the daemon configuration",
|
||||
"cmd.daemon.config.test.Description": "Checks the daemon configuration.",
|
||||
|
||||
"cmd.daemon.config.test.error": "Error: {{.Error}}",
|
||||
"cmd.daemon.config.test.settingTitle": "Settings:",
|
||||
"cmd.daemon.config.test.main": "Main",
|
||||
"cmd.daemon.config.test.checkingPrograms": "Checking programs:",
|
||||
"cmd.daemon.config.test.pathEmpty": "{{.Program}} path is empty",
|
||||
|
||||
"Command error": "Command error",
|
||||
"invalid log level": "The log level specified in the settings is invalid. It is currently set to: {{.Level}}. Valid values: {{.Levels}}",
|
||||
"invalid log encoding": "Invalid encoding setting. Currently set to: {{.Encoding}}. Valid values: {{.Encodings}}",
|
||||
|
||||
@@ -52,6 +52,16 @@
|
||||
"cmd.daemon.geoip.refresh.Description": "GeoIP дерекқорын жаңарту. geoip.toml файлында қосулы және конфигурацияланған болуы керек. \nМысал: \nkor-elf-shield geoip refresh",
|
||||
"geoip_refresh_success": "GeoIP дерекқоры сәтті жаңартылды",
|
||||
|
||||
"cmd.daemon.config.Usage": "Конфигурация",
|
||||
"cmd.daemon.config.test.Usage": "Демон конфигурациясын тексеру",
|
||||
"cmd.daemon.config.test.Description": "Демон конфигурациясын тексереді.",
|
||||
|
||||
"cmd.daemon.config.test.error": "Қате: {{.Error}}",
|
||||
"cmd.daemon.config.test.settingTitle": "Параметрлер:",
|
||||
"cmd.daemon.config.test.main": "Негізгі",
|
||||
"cmd.daemon.config.test.checkingPrograms": "Бағдарламаларды тексеру:",
|
||||
"cmd.daemon.config.test.pathEmpty": "{{.Program}} жолы бос",
|
||||
|
||||
"Command error": "Командалық қате",
|
||||
"invalid log level": "Параметрлерде көрсетілген журнал деңгейі жарамсыз. Ол қазір мына күйге орнатылған: {{.Level}}. Жарамды мәндер: {{.Levels}}",
|
||||
"invalid log encoding": "Жарамсыз кодтау параметрі. Қазіргі уақытта орнатылған: {{.Encoding}}. Жарамды мәндер: {{.Encodings}}",
|
||||
|
||||
@@ -52,6 +52,16 @@
|
||||
"cmd.daemon.geoip.refresh.Description": "Обновление базу данных GeoIP. Должно быть включено и настроено в geoip.toml. \nПример: \nkor-elf-shield geoip refresh",
|
||||
"geoip_refresh_success": "База данных GeoIP успешно обновлена",
|
||||
|
||||
"cmd.daemon.config.Usage": "Конфигурация",
|
||||
"cmd.daemon.config.test.Usage": "Проверка конфигурации демона",
|
||||
"cmd.daemon.config.test.Description": "Проверяет конфигурацию демона.",
|
||||
|
||||
"cmd.daemon.config.test.error": "Ошибка: {{.Error}}",
|
||||
"cmd.daemon.config.test.settingTitle": "Настройки:",
|
||||
"cmd.daemon.config.test.main": "Главные",
|
||||
"cmd.daemon.config.test.checkingPrograms": "Проверка программ:",
|
||||
"cmd.daemon.config.test.pathEmpty": "Путь к {{.Program}} пустой",
|
||||
|
||||
"Command error": "Ошибка команды",
|
||||
"invalid log level": "В настройках указан не верный уровень log. Сейчас указан: {{.Level}}. Допустимые значения: {{.Levels}}",
|
||||
"invalid log encoding": "Неверная настройка encoding. Сейчас указан: {{.Encoding}}. Допустимые значения: {{.Encodings}}",
|
||||
|
||||
@@ -17,3 +17,7 @@ func (l *falseLogger) Sync() error { return nil }
|
||||
func (l *falseLogger) ReOpen() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewFalseLogger() Logger {
|
||||
return &falseLogger{}
|
||||
}
|
||||
|
||||
@@ -59,7 +59,7 @@ func (l *logger) ReOpen() error {
|
||||
|
||||
func NewLogger(opts LoggerOptions) (Logger, error) {
|
||||
if !opts.Enabled {
|
||||
return &falseLogger{}, nil
|
||||
return NewFalseLogger(), nil
|
||||
}
|
||||
|
||||
return &logger{
|
||||
|
||||
+30
-22
@@ -44,28 +44,8 @@ func settingDefault(configPath string) *setting {
|
||||
}
|
||||
|
||||
func (s setting) ToDaemonOptions(dockerSupport bool) (daemon.DaemonOptions, error) {
|
||||
if s.PidFile == "" {
|
||||
return daemon.DaemonOptions{}, errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "pid_file",
|
||||
}))
|
||||
}
|
||||
|
||||
if s.SocketFile == "" {
|
||||
return daemon.DaemonOptions{}, errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "socket_file",
|
||||
}))
|
||||
}
|
||||
|
||||
if s.BinaryLocations.Nftables == "" {
|
||||
return daemon.DaemonOptions{}, errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "binaryLocations.nftables",
|
||||
}))
|
||||
}
|
||||
|
||||
if s.DataDir == "" {
|
||||
return daemon.DaemonOptions{}, errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "data_dir",
|
||||
}))
|
||||
if err := s.ValidateBeforeStart(); err != nil {
|
||||
return daemon.DaemonOptions{}, err
|
||||
}
|
||||
|
||||
firewallConfig, guardConfig, err := s.OtherSettingsPath.ToFirewallConfig(dockerSupport)
|
||||
@@ -89,6 +69,34 @@ func (s setting) ToDaemonOptions(dockerSupport bool) (daemon.DaemonOptions, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s setting) ValidateBeforeStart() error {
|
||||
if s.PidFile == "" {
|
||||
return errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "pid_file",
|
||||
}))
|
||||
}
|
||||
|
||||
if s.SocketFile == "" {
|
||||
return errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "socket_file",
|
||||
}))
|
||||
}
|
||||
|
||||
if s.BinaryLocations.Nftables == "" {
|
||||
return errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "binaryLocations.nftables",
|
||||
}))
|
||||
}
|
||||
|
||||
if s.DataDir == "" {
|
||||
return errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
||||
"Parameter": "data_dir",
|
||||
}))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s setting) Validate() error {
|
||||
if err := s.validationTestingInterval(); err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user