Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b3e44359d1 | ||
|
|
b782b7c975
|
||
|
|
e28fafc1e9
|
||
|
|
350c78c40a
|
||
|
|
22d7f901d0
|
||
|
|
1cff4fb2ba
|
||
|
|
2d219edd23
|
||
|
|
1cc52ca28e
|
||
|
|
7392e753ea
|
||
|
|
6148b0a06d
|
||
|
|
7322455ae0
|
||
|
|
232b63a207
|
||
|
|
4d4e7272ce
|
||
|
|
5c2b34b742
|
||
|
|
5315a4e0ff
|
||
|
|
0286ebc141
|
||
|
|
9759d9805b
|
||
|
|
29df5f3fd8
|
||
|
|
f2a9dc41be
|
||
|
|
f2dfeb507d
|
||
|
|
bae20cbf49 | ||
|
|
f9c438c973
|
||
|
|
cbf3dd6402
|
||
|
|
8495d7c4e2
|
||
|
|
311add0e8b
|
||
|
|
c26a898d05
|
||
|
|
9d5b6c7d6b
|
||
|
|
a0893ea361
|
||
|
|
0111304d85
|
||
|
|
aa0c5a9551
|
||
|
|
65806d701a
|
||
|
|
8c10ab0e62
|
||
|
|
95aec171e0
|
||
|
|
666f563ef5
|
||
|
|
ed7bd905f8
|
||
|
|
d06dd6e9fc
|
||
|
|
f650e89610
|
||
|
|
8c55ad1b23
|
||
|
|
17efaaa36a
|
||
|
|
df469a6f6d
|
||
|
|
6de78c2708
|
||
|
|
87add28edd
|
@@ -1,3 +1,49 @@
|
||||
## 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)
|
||||
#### Русский
|
||||
* Обновления критических ошибок:
|
||||
* Исправлена ошибка, из-за которой неудачные попытки авторизации по SSH не обрабатывались, если указанного пользователя не существовало в системе.
|
||||
* Исправлена ошибка, из-за которой неудачные попытки аутентификации по SSH-ключу не обрабатывались.
|
||||
<br>**Примечание:** По умолчанию SSH-сервер не регистрирует неудачные попытки входа с использованием ключа. Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр `LogLevel VERBOSE` в настройки `sshd_config`.
|
||||
* Добавлено: поддержка исключений IP-адресов в настройках обработки blocklist.
|
||||
* Теперь можно добавлять параметр `exclude_ips` в конфигурационный файл `blocklists.toml` чтобы исключать определённые диапазоны IP-адресов из обработки. По умолчанию: `["127.0.0.1/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7",]`.
|
||||
* Реализована простая проверка на наличия таблицы в NFTables.
|
||||
* Добавлены данные для проверки наличия таблицы в NFTables в команде `kor-elf-shield status`.
|
||||
* Добавлена новая группа параметров `[rulesGuard]` в файл `firewall.toml`:
|
||||
* `enabled` — Включает мониторинг правил NFTables. По умолчанию: `true`.
|
||||
* `notifications` - Включает уведомления в случае проблем с правилами NFTables. По умолчанию: `true`.
|
||||
* `recovery` - Восстанавливает правила NFTables в случае проблем. По умолчанию: `true`.
|
||||
* `interval` - Интервал проверки правил NFTables в секундах. По умолчанию: `3600`.
|
||||
* Добавлена поддержка разделения `patterns` на партиции в настройках `analyzer.toml`.
|
||||
* Теперь правила анализа логов и защиты от перебора пароля могут обрабатывать шаблоны по партициям.
|
||||
* Это помогает гибче группировать шаблоны и снижать пересечения между независимыми наборами правил.
|
||||
***
|
||||
#### English
|
||||
* Critical bug fixes:
|
||||
* Fixed an issue where failed SSH login attempts were not processed if the specified user did not exist on the system.
|
||||
* Fixed a bug where unsuccessful SSH key authentication attempts were not processed.
|
||||
<br>**Note:** By default, the SSH server does not log unsuccessful login attempts using a key. To enable tracking of unsuccessful login attempts using a key, you must add the `LogLevel VERBOSE` parameter to the `sshd_config` settings.
|
||||
* Added: Support for IP address exclusions in blocklist processing settings.
|
||||
* You can now add the `exclude_ips` parameter to the `blocklists.toml` configuration file to exclude specific IP address ranges from processing. Default: `["127.0.0.1/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7",]`.
|
||||
* Implemented a simple check for the presence of a table in NFTables.
|
||||
* Added check data to `kor-elf-shield status` command for the presence of a table in NFTables.
|
||||
* Added a new `[rulesGuard]` parameter group to the `firewall.toml` file:
|
||||
* `enabled` - Enables NFTables rules monitoring. Default: `true`.
|
||||
* `notifications` - Enables notifications in case of problems with NFTables rules. Default: `true`.
|
||||
* `recovery` - Recovers NFTables rules in case of problems. Default: `true`.
|
||||
* `interval` - NFTables rules checking interval in seconds. Default: `3600`.
|
||||
* Added partitioning support for `patterns` in the `analyzer.toml` configuration.
|
||||
* Log analysis and brute-force protection rules can now process patterns by partitions.
|
||||
* This helps organize patterns more flexibly and reduces overlap between independent rule sets.
|
||||
***
|
||||
## 0.12.0 (17.06.2026)
|
||||
#### Русский
|
||||
* Добавлено: отображение номера уведомления в групповых уведомлениях.
|
||||
|
||||
@@ -903,6 +903,38 @@ http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
golang.org/x/crypto
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are
|
||||
met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright
|
||||
notice, this list of conditions and the following disclaimer.
|
||||
* Redistributions in binary form must reproduce the above
|
||||
copyright notice, this list of conditions and the following disclaimer
|
||||
in the documentation and/or other materials provided with the
|
||||
distribution.
|
||||
* Neither the name of Google LLC nor the names of its
|
||||
contributors may be used to endorse or promote products derived from
|
||||
this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
golang.org/x/sys
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
@@ -61,6 +61,16 @@
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/firewall.toml</strong> - тут находятся настройки, связанные с nftables. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/firewall.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/firewall.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/analyzer.toml</strong> - тут находятся настройки для анализатора логов. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/analyzer.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/analyzer.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/notifications.toml</strong> - тут находятся настройки для уведомлений. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/notifications.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/notifications.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/docker.toml</strong> - тут находятся настройки, связанные с docker. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/docker.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/docker.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/blocklists.toml</strong> - тут находятся настройки для получения IP-адресов от разных сервисов, которые надо заблокировать. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/blocklists.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/blocklists.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/geoip.toml</strong> - тут находятся настройки для получения данных об IP-адресах. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/geoip.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/geoip.toml</a></p>
|
||||
|
||||
***
|
||||
|
||||
<p>Программное обеспечение является MIT (см. <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE">LICENSE</a>) и использует сторонние библиотеки, которые распространяются на их собственных условиях (см. <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE-3RD-PARTY.txt">LICENSE-3RD-PARTY.txt</a>).</p>
|
||||
@@ -61,6 +61,16 @@
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/firewall.toml</strong> - Here are the settings related to nftables. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/firewall.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/firewall.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/analyzer.toml</strong> - Contains log analyzer settings. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/analyzer.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/analyzer.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/notifications.toml</strong> - Contains notification settings. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/notifications.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/notifications.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/docker.toml</strong> - Contains Docker-related settings. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/docker.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/docker.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/blocklists.toml</strong> - Contains settings for obtaining IP addresses from various services that need to be blocked. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/blocklists.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/blocklists.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/geoip.toml</strong> - This contains settings for obtaining IP address data. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/geoip.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/geoip.toml/language/en</a></p>
|
||||
|
||||
***
|
||||
|
||||
<p>The software is MIT (see <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE">LICENSE</a>) and uses third-party libraries that are distributed on their own terms (see <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE-3RD-PARTY.txt">LICENSE-3RD-PARTY.txt</a>).</p>
|
||||
@@ -56,7 +56,7 @@ blocking_time = 3600
|
||||
rate_limit_period = 3600
|
||||
|
||||
###
|
||||
# Указываем в секундах, через какое время сбрасывать данные IP в групе _default если не было событий.
|
||||
# Указываем в секундах, через какое время сбрасывать данные IP в группе _default если не было событий.
|
||||
# Если указать 0, то не будет сбрасывать.
|
||||
# По умолчанию: 86400
|
||||
# ***
|
||||
@@ -68,9 +68,15 @@ rate_limit_reset_period = 86400
|
||||
|
||||
###
|
||||
# Включает защиту от перебора пароля от ssh.
|
||||
# !!! Примечание: По умолчанию SSH-сервер не регистрирует на неудачные попытки входа с использованием ключа.
|
||||
# Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр
|
||||
# "LogLevel VERBOSE" в настройки sshd_config !!!
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables protection against brute-force attacks against ssh.
|
||||
# !!! Note: By default, the SSH server does not log unsuccessful login attempts using a key.
|
||||
# To enable tracking of unsuccessful login attempts using a key, you must add the "LogLevel VERBOSE" parameter
|
||||
# to the sshd_config settings !!!
|
||||
# Default: true
|
||||
###
|
||||
ssh_enable = true
|
||||
@@ -129,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]]
|
||||
@@ -197,6 +203,7 @@ ssh_group = ""
|
||||
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
||||
# [bruteForceProtection.rules.source]
|
||||
# type = "journalctl" # journalctl или file (обязательное поле)
|
||||
# path = "" # Указываем путь к файлу (обязательное поле если type = "file")
|
||||
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
||||
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
||||
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -206,7 +213,14 @@ ssh_group = ""
|
||||
# [[bruteForceProtection.rules.patterns.values]]
|
||||
# name = "Пользователь"
|
||||
# value = 1
|
||||
#
|
||||
# [bruteForceProtection.rules.patterns.partition] # (необязательный параметр) если надо разделить по ключевым словам
|
||||
# value = 1 # Номер поля, по которому мы получаем ключевое слова
|
||||
# trim = true # Убирает у ключевого слова пробелы вначале и в конце
|
||||
# lower_case = true # Ключевое слово приводит к нижнему регистру
|
||||
# [bruteForceProtection.rules.patterns.partition.type] # (необязательный параметр)
|
||||
# type = "only" # Допустимые значения "only" или "except"
|
||||
# keywords = ["root", "user"] # Указываем ключевые слова
|
||||
# partitioned = false # Если нужно, чтобы каждое ключевое слово считалось независимо от друг-друга, то указываем true
|
||||
# ***
|
||||
# Specify the log settings that need to be monitored to protect against password brute-force attacks.
|
||||
# Examples:
|
||||
@@ -220,6 +234,7 @@ ssh_group = ""
|
||||
# group = "my_name_group" # You can specify the group name (optional field)
|
||||
# [bruteForceProtection.rules.source]
|
||||
# type = "journalctl" # journalctl or file (required)
|
||||
# path = "" # Specify the path to the file (required field if type = "file")
|
||||
# field = "systemd_unit" # systemd_unit or syslog_identifier (required if type = "journalctl")
|
||||
# match = "ssh.service" # Value (required if type = "journalctl")
|
||||
# If field = "systemd_unit", then match must end with: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -231,6 +246,14 @@ ssh_group = ""
|
||||
# [[bruteForceProtection.rules.patterns.values]]
|
||||
# name = "User"
|
||||
# value = 1
|
||||
# [bruteForceProtection.rules.patterns.partition] # (optional) if you want to split by keywords
|
||||
# value = 1 # Field number by which to get the keyword
|
||||
# trim = true # Removes leading and trailing spaces from the keyword
|
||||
# lower_case = true # Converts the keyword to lowercase
|
||||
# [bruteForceProtection.rules.patterns.partition.type] # (optional)
|
||||
# type = "only" # Acceptable values are "only" or "except"
|
||||
# keywords = ["root", "user"] # Specify the keywords
|
||||
# partitioned = false # If you want each keyword to be considered independently, specify true
|
||||
###
|
||||
|
||||
###############################################################################
|
||||
@@ -423,6 +446,7 @@ notify = true
|
||||
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
||||
# [logAlert.rules.source]
|
||||
# type = "journalctl" # journalctl или file (обязательное поле)
|
||||
# path = "" # Указываем путь к файлу (обязательное поле если type = "file")
|
||||
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
||||
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
||||
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -435,6 +459,14 @@ notify = true
|
||||
# name = "IP"
|
||||
# value = 3
|
||||
# type = "ip"
|
||||
# [logAlert.rules.patterns.partition] # (необязательный параметр) если надо разделить по ключевым словам
|
||||
# value = 2 # Номер поля, по которому мы получаем ключевое слова
|
||||
# trim = true # Убирает у ключевого слова пробелы вначале и в конце
|
||||
# lower_case = true # Ключевое слово приводит к нижнему регистру
|
||||
# [logAlert.rules.patterns.partition.type] # (необязательный параметр)
|
||||
# type = "only" # Допустимые значения "only" или "except"
|
||||
# keywords = ["root", "user"] # Указываем ключевые слова
|
||||
# partitioned = false # Если нужно, чтобы каждое ключевое слово считалось независимо от друг-друга, то указываем true
|
||||
#
|
||||
# ***
|
||||
# Specify the log settings to monitor for notifications.
|
||||
@@ -447,6 +479,7 @@ notify = true
|
||||
# group = "my_name_group" # You can specify the group name (optional field)
|
||||
# [logAlert.rules.source]
|
||||
# type = "journalctl" # journalctl or file (required)
|
||||
# path = "" # Specify the path to the file (required field if type = "file")
|
||||
# field = "systemd_unit" # systemd_unit or syslog_identifier (required if type = "journalctl")
|
||||
# match = "ssh.service" # Value (required if type = "journalctl")
|
||||
# If field = "systemd_unit", then match must end with: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -459,4 +492,12 @@ notify = true
|
||||
# name = "IP"
|
||||
# value = 3
|
||||
# type = "ip"
|
||||
# [logAlert.rules.patterns.partition] # (optional) if you want to split by keywords
|
||||
# value = 2 # Field number by which we get the keyword
|
||||
# trim = true # Removes leading and trailing spaces from the keyword
|
||||
# lower_case = true # Converts the keyword to lowercase
|
||||
# [logAlert.rules.patterns.partition.type] # (optional)
|
||||
# type = "only" # Acceptable values are "only" or "except"
|
||||
# keywords = ["root", "user"] # Specify the keywords
|
||||
# partitioned = false # If you want each keyword to be counted independently, specify true
|
||||
###
|
||||
|
||||
@@ -17,6 +17,38 @@
|
||||
###
|
||||
enabled = false
|
||||
|
||||
###
|
||||
# Исключить IP-адреса из полученных списков.
|
||||
# Допустимые форматы: "192.168.1.1", "192.168.1.0/24", "192.168.1.0-192.168.1.255"
|
||||
# По умолчанию: [
|
||||
# "127.0.0.1/8",
|
||||
# "10.0.0.0/8",
|
||||
# "172.16.0.0/12",
|
||||
# "192.168.0.0/16",
|
||||
# "::1/128",
|
||||
# "fc00::/7",
|
||||
# ]
|
||||
# ***
|
||||
# Exclude IP addresses from retrieved lists.
|
||||
# Acceptable formats: "192.168.1.1", "192.168.1.0/24", "192.168.1.0-192.168.1.255"
|
||||
# Default: [
|
||||
# "127.0.0.1/8",
|
||||
# "10.0.0.0/8",
|
||||
# "172.16.0.0/12",
|
||||
# "192.168.0.0/16",
|
||||
# "::1/128",
|
||||
# "fc00::/7",
|
||||
# ]
|
||||
###
|
||||
exclude_ips = [
|
||||
"127.0.0.1/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"::1/128",
|
||||
"fc00::/7",
|
||||
]
|
||||
|
||||
###
|
||||
# Spamhaus Don't Route Or Peer Lists (DROP IPv4)
|
||||
# Details: https://www.spamhaus.org/blocklists/do-not-route-or-peer/
|
||||
|
||||
@@ -621,3 +621,46 @@ chain_output_name = "output"
|
||||
# Default: "forward"
|
||||
###
|
||||
chain_forward_name = "forward"
|
||||
|
||||
###############################################################################
|
||||
# РАЗДЕЛ:Проверка и восстановление правил брандмауэра
|
||||
# ***
|
||||
# SECTION:Checking and restoring firewall rules
|
||||
###############################################################################
|
||||
[rulesGuard]
|
||||
###
|
||||
# Включает мониторинг правил NFTables.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables monitoring of NFTables rules.
|
||||
# Default: true
|
||||
###
|
||||
enabled = true
|
||||
|
||||
###
|
||||
# Включает уведомления в случае возникновения проблем с правилами NFTables.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables notifications if there is a problem with NFTables rules.
|
||||
# Default: true
|
||||
###
|
||||
notifications = true
|
||||
|
||||
###
|
||||
# Если возникла проблема с правилами NFTables, следует ли перезапустить настройки правил NFTables.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# If there is a problem with the NFTables rules, whether to restart the NFTables rules settings.
|
||||
# Default: true
|
||||
###
|
||||
recovery = true
|
||||
|
||||
###
|
||||
# Указать интервал в секундах по которому будет проверяться на наличия правил NFTables.
|
||||
# По умолчанию: 3600
|
||||
# ***
|
||||
# Specify the interval in seconds at which to check for NFTables rules.
|
||||
# Default: 3600
|
||||
###
|
||||
interval = 3600
|
||||
|
||||
|
||||
@@ -165,12 +165,12 @@ encoding = "json"
|
||||
|
||||
###
|
||||
# Список мест для основных логов.
|
||||
# Можно указать несколько путей. Например output_paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Можно указать несколько путей. Например paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Где "stdout" - вывод данных в консоль.
|
||||
# По умолчанию: ["/var/log/kor-elf-shield.log"]
|
||||
# ***
|
||||
# List of locations for main logs.
|
||||
# Multiple paths can be specified. For example, output_paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Multiple paths can be specified. For example, paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Where "stdout" is the console output.
|
||||
# Default: ["/var/log/kor-elf-shield.log"]
|
||||
###
|
||||
@@ -178,12 +178,12 @@ paths = ["/var/log/kor-elf-shield.log"]
|
||||
|
||||
###
|
||||
# Список мест для ошибок логгирования. Например журнал не смог записать какие-то данные.
|
||||
# Можно указать несколько путей. Например output_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Можно указать несколько путей. Например log_error_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Где "stderr" - вывод данных в консоль.
|
||||
# По умолчанию: ["stderr"]
|
||||
# ***
|
||||
# List of locations for logging errors. For example, the log failed to write some data.
|
||||
# Multiple paths can be specified. For example, output_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Multiple paths can be specified. For example, log_error_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Where "stderr" is the data output to the console.
|
||||
# Default: ["stderr"]
|
||||
###
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
module git.kor-elf.net/kor-elf-shield/kor-elf-shield
|
||||
|
||||
go 1.25
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.2.0
|
||||
git.kor-elf.net/kor-elf-shield/geoip2 v0.1.2
|
||||
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.2.1
|
||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||
github.com/nxadm/tail v1.4.11
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/urfave/cli/v3 v3.6.2
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/text v0.34.0
|
||||
github.com/urfave/cli/v3 v3.10.1
|
||||
github.com/wneessen/go-mail v0.8.1
|
||||
go.etcd.io/bbolt v1.5.0
|
||||
go.uber.org/zap v1.28.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/text v0.41.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/oschwald/geoip2-golang/v2 v2.3.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/crypto v0.55.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0 h1:NS8be3TFBsUn+ft3oG5sAD56iJTGOkFH6GgjepEnS0s=
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0/go.mod h1:nNbQux5vbuoCa3wMiC2QsLb4tO1JLCssGzdljizcJUs=
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.2.0 h1:oj4P4N5sa0kh2Zob0Mf/GrjqNAQrZjuX9/RoDx3UUGk=
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.2.0/go.mod h1:nNbQux5vbuoCa3wMiC2QsLb4tO1JLCssGzdljizcJUs=
|
||||
git.kor-elf.net/kor-elf-shield/geoip2 v0.1.2 h1:/J9U+h9H92hW6TtwCznkRANqhX5kvBpN4uV7xDbwXpM=
|
||||
git.kor-elf.net/kor-elf-shield/geoip2 v0.1.2/go.mod h1:ULMUjpd2I9ikkDDE69IlpKT4vR2/nlYT0cqoR2T95sM=
|
||||
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.2.1 h1:B5u1uCYyrDlDlCSA03o/Djt/T0A3SgCeFsfZkq25Hwg=
|
||||
@@ -13,6 +15,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -27,10 +31,16 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0 h1:DjnLhNJu9WHwTrmoiQFvgmyJoczhdnm7LB23UBI2Amo=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0/go.mod h1:qdVmcPgrTJ4q2eP9tHq/yldMTdp2VMr33uVdFbHBiBc=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.3.0 h1:hT8/BT137lPJXq0DXwGQUS228k8pEhgBRJ1B70eqyAk=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.3.0/go.mod h1:tHUYg65ssvQSSzSCkiFR6LWJPYOvSw/85JiBp8kXz0U=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1 h1:lA8FH0oOrM4u7mLvowq8IT6a3Q/qEnqRzLQn9eH5ojc=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1/go.mod h1:PLdx6PR+siSIoXqqy7C7r3SB3KZnhxWr1Dp6g0Hacl8=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0 h1:WvEHCE8HwFS5pKWhW8nvvRxNzczuRUOGBLn2L03VlEQ=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0/go.mod h1:EBnvLGgY+aSckqcgyfB5LPDviqaWdMZPBDwu8c2jJbs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
@@ -51,25 +61,42 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY=
|
||||
github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
|
||||
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
||||
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -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(),
|
||||
|
||||
@@ -29,7 +29,17 @@ func NewBruteForceProtectionSSH(isNotify bool, notifyCooldown int, notifyEvery i
|
||||
|
||||
Patterns: []brute_force_protection.RegexPattern{
|
||||
{
|
||||
Regexp: regular_expression.NewLazyRegexp(`^Failed password for (\S+) from (\S+) port \S+`),
|
||||
Regexp: regular_expression.NewLazyRegexp(`^Failed (password|publickey) for (invalid user |illegal user )?(\S*) from (\S+)( port \S+ \S+\s*)`),
|
||||
Values: []brute_force_protection.PatternValue{
|
||||
{
|
||||
Name: i18n.Lang.T("user"),
|
||||
Value: 3,
|
||||
},
|
||||
},
|
||||
IP: 4,
|
||||
},
|
||||
{
|
||||
Regexp: regular_expression.NewLazyRegexp(`^Invalid user (\S*) from (\S+) port (\S+)`),
|
||||
Values: []brute_force_protection.PatternValue{
|
||||
{
|
||||
Name: i18n.Lang.T("user"),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
package brute_force_protection
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/regular_expression"
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/partition"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/regular_expression"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Name string
|
||||
@@ -15,9 +18,10 @@ type Rule struct {
|
||||
}
|
||||
|
||||
type RegexPattern struct {
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
IP uint8
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
IP uint8
|
||||
Partition *partition.PatternPartition
|
||||
}
|
||||
|
||||
type RateLimit struct {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"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/analyzer/config/partition"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/regular_expression"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
||||
)
|
||||
@@ -112,8 +113,9 @@ type AlertRule struct {
|
||||
}
|
||||
|
||||
type AlertRegexPattern struct {
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
Partition *partition.PatternPartition
|
||||
}
|
||||
|
||||
type PatternValue struct {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package partition
|
||||
|
||||
type exceptType struct {
|
||||
keywords []string
|
||||
partitioned bool
|
||||
}
|
||||
|
||||
func NewExceptType(
|
||||
keywords []string,
|
||||
partitioned bool,
|
||||
) PatternPartitionType {
|
||||
return &exceptType{
|
||||
keywords: keywords,
|
||||
partitioned: partitioned,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *exceptType) IsPartitioned() bool {
|
||||
return o.partitioned
|
||||
}
|
||||
|
||||
func (o *exceptType) Accepts(text string) (bool, string) {
|
||||
for _, keyword := range o.keywords {
|
||||
if keyword == text {
|
||||
return false, text
|
||||
}
|
||||
}
|
||||
return true, text
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package partition
|
||||
|
||||
type onlyType struct {
|
||||
keywords []string
|
||||
partitioned bool
|
||||
}
|
||||
|
||||
func NewOnlyType(
|
||||
keywords []string,
|
||||
partitioned bool,
|
||||
) PatternPartitionType {
|
||||
return &onlyType{
|
||||
keywords: keywords,
|
||||
partitioned: partitioned,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *onlyType) IsPartitioned() bool {
|
||||
return o.partitioned
|
||||
}
|
||||
|
||||
func (o *onlyType) Accepts(text string) (bool, string) {
|
||||
for _, keyword := range o.keywords {
|
||||
if keyword == text {
|
||||
return true, text
|
||||
}
|
||||
}
|
||||
return false, text
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package partition
|
||||
|
||||
import "strings"
|
||||
|
||||
type PatternPartitionType interface {
|
||||
Accepts(text string) (bool, string)
|
||||
IsPartitioned() bool
|
||||
}
|
||||
|
||||
type PatternPartition struct {
|
||||
Value uint8
|
||||
Type PatternPartitionType
|
||||
Normalize func(keyword string) string
|
||||
}
|
||||
|
||||
type Normalize interface {
|
||||
Normalize(text string) string
|
||||
}
|
||||
|
||||
type normalize struct {
|
||||
Trim bool
|
||||
LowerCase bool
|
||||
}
|
||||
|
||||
func NewNormalize(trim, lowerCase bool) Normalize {
|
||||
return &normalize{
|
||||
Trim: trim,
|
||||
LowerCase: lowerCase,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *normalize) Normalize(text string) string {
|
||||
if p.LowerCase {
|
||||
text = strings.ToLower(text)
|
||||
}
|
||||
if p.Trim {
|
||||
text = strings.TrimSpace(text)
|
||||
}
|
||||
return text
|
||||
}
|
||||
@@ -26,8 +26,9 @@ type alert struct {
|
||||
}
|
||||
|
||||
type alertAnalyzeRuleReturn struct {
|
||||
found bool
|
||||
fields []*regexField
|
||||
found bool
|
||||
fields []*regexField
|
||||
partition *string
|
||||
}
|
||||
|
||||
type alertNotify struct {
|
||||
@@ -68,7 +69,7 @@ func (a *alert) Analyze(entry *Entry) {
|
||||
alertNumber := uint64(0)
|
||||
messages := []string{}
|
||||
if rule.Group != nil {
|
||||
alertGroup, err := a.alertGroupService.Analyze(rule.Group, entry.Time, entry.Message)
|
||||
alertGroup, err := a.alertGroupService.Analyze(rule.Group, entry.Time, entry.Message, result.partition)
|
||||
if err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze alert group: %s", err))
|
||||
continue
|
||||
@@ -102,8 +103,9 @@ func (a *alert) ClearDBData() error {
|
||||
|
||||
func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyzeRuleReturn {
|
||||
result := alertAnalyzeRuleReturn{
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
partition: nil,
|
||||
}
|
||||
|
||||
for _, pattern := range rule.Patterns {
|
||||
@@ -116,6 +118,27 @@ func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyze
|
||||
idx := re.FindStringSubmatchIndex(message)
|
||||
|
||||
if idx != nil {
|
||||
if pattern.Partition != nil {
|
||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.Partition.Value), idx)
|
||||
if err != nil {
|
||||
a.logger.Error(fmt.Sprintf("alert. Failed to get partition value: %s", err))
|
||||
continue
|
||||
}
|
||||
partition := pattern.Partition.Normalize(message[start:end])
|
||||
if pattern.Partition.Type != nil {
|
||||
if accepts, partitionCode := pattern.Partition.Type.Accepts(partition); !accepts {
|
||||
continue
|
||||
} else {
|
||||
partition = partitionCode
|
||||
}
|
||||
if pattern.Partition.Type.IsPartitioned() {
|
||||
result.partition = &partition
|
||||
}
|
||||
} else {
|
||||
result.partition = &partition
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range pattern.Values {
|
||||
start, end, err := getValueStartEndByRegexIndex(int(value.Value), idx)
|
||||
if err != nil {
|
||||
@@ -126,6 +149,9 @@ func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyze
|
||||
}
|
||||
|
||||
if len(pattern.Values) != len(result.fields) {
|
||||
a.logger.Error(fmt.Sprintf("alert. analyzeRule len(pattern.Values) != len(result.fields): %d != %d", len(pattern.Values), len(result.fields)))
|
||||
result.fields = []*regexField{}
|
||||
result.partition = nil
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type Group interface {
|
||||
Analyze(alertGroup *config.AlertGroup, eventTime time.Time, message string) (AnalysisResult, error)
|
||||
Analyze(alertGroup *config.AlertGroup, eventTime time.Time, message string, partition *string) (AnalysisResult, error)
|
||||
ClearDBData() error
|
||||
}
|
||||
|
||||
@@ -34,14 +34,14 @@ func NewGroup(alertGroupRepository repository.AlertGroupRepository, logger log.L
|
||||
}
|
||||
}
|
||||
|
||||
func (g *group) Analyze(alertGroup *config.AlertGroup, eventTime time.Time, message string) (AnalysisResult, error) {
|
||||
func (g *group) Analyze(alertGroup *config.AlertGroup, eventTime time.Time, message string, partition *string) (AnalysisResult, error) {
|
||||
analysisResult := AnalysisResult{
|
||||
Alerted: false,
|
||||
}
|
||||
|
||||
g.logger.Debug(fmt.Sprintf("Analyzing alert group %s", alertGroup.Name))
|
||||
|
||||
err := g.alertGroupRepository.Update(alertGroup.Name, func(entityAlertGroup *entity.AlertGroup) (*entity.AlertGroup, error) {
|
||||
err := g.alertGroupRepository.Update(alertGroup.Name, partition, func(entityAlertGroup *entity.AlertGroup) (*entity.AlertGroup, error) {
|
||||
rateLimit, err := alertGroup.RateLimit(entityAlertGroup.CurrentLevelTriggerCount)
|
||||
if err != nil {
|
||||
return entityAlertGroup, err
|
||||
|
||||
@@ -34,9 +34,10 @@ type bruteForceProtection struct {
|
||||
}
|
||||
|
||||
type bruteForceProtectionAnalyzeRuleReturn struct {
|
||||
found bool
|
||||
fields []*regexField
|
||||
ip net.IP
|
||||
found bool
|
||||
fields []*regexField
|
||||
ip net.IP
|
||||
partition *string
|
||||
}
|
||||
|
||||
type bruteForceProtectionNotify struct {
|
||||
@@ -88,7 +89,7 @@ func (p *bruteForceProtection) Analyze(entry *Entry) {
|
||||
continue
|
||||
}
|
||||
|
||||
groupResult, err := p.groupService.Analyze(rule.Group, entry.Time, result.ip, entry.Message)
|
||||
groupResult, err := p.groupService.Analyze(rule.Group, entry.Time, result.ip, entry.Message, result.partition)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to analyze brute force protection group: %s", err))
|
||||
continue
|
||||
@@ -198,9 +199,10 @@ func (p *bruteForceProtection) handleBlockIPWithPorts(
|
||||
|
||||
func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, message string) bruteForceProtectionAnalyzeRuleReturn {
|
||||
result := bruteForceProtectionAnalyzeRuleReturn{
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
ip: nil,
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
ip: nil,
|
||||
partition: nil,
|
||||
}
|
||||
|
||||
for _, pattern := range rule.Patterns {
|
||||
@@ -216,14 +218,35 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.IP), idx)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to get ip value: %s", err))
|
||||
return result
|
||||
continue
|
||||
}
|
||||
ipText := message[start:end]
|
||||
result.ip = net.ParseIP(ipText)
|
||||
if result.ip == nil {
|
||||
ip := net.ParseIP(ipText)
|
||||
if ip == nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to parse ip: %s", ipText))
|
||||
return bruteForceProtectionAnalyzeRuleReturn{
|
||||
found: false,
|
||||
continue
|
||||
}
|
||||
result.ip = ip
|
||||
|
||||
if pattern.Partition != nil {
|
||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.Partition.Value), idx)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("brute_force_protection. Failed to get partition value: %s", err))
|
||||
result.ip = nil
|
||||
continue
|
||||
}
|
||||
partition := pattern.Partition.Normalize(message[start:end])
|
||||
if pattern.Partition.Type != nil {
|
||||
if accepts, partitionCode := pattern.Partition.Type.Accepts(partition); !accepts {
|
||||
continue
|
||||
} else {
|
||||
partition = partitionCode
|
||||
}
|
||||
if pattern.Partition.Type.IsPartitioned() {
|
||||
result.partition = &partition
|
||||
}
|
||||
} else {
|
||||
result.partition = &partition
|
||||
}
|
||||
}
|
||||
|
||||
@@ -237,6 +260,10 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
||||
}
|
||||
|
||||
if len(pattern.Values) != len(result.fields) {
|
||||
p.logger.Error(fmt.Sprintf("brute_force_protection. analyzeRule len(pattern.Values) != len(result.fields): %d != %d", len(pattern.Values), len(result.fields)))
|
||||
result.fields = []*regexField{}
|
||||
result.partition = nil
|
||||
result.ip = nil
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type Group interface {
|
||||
Analyze(group *brute_force_protection.Group, eventTime time.Time, ip net.IP, message string) (AnalysisResult, error)
|
||||
Analyze(group *brute_force_protection.Group, eventTime time.Time, ip net.IP, message string, partition *string) (AnalysisResult, error)
|
||||
ClearDBData() error
|
||||
}
|
||||
|
||||
@@ -37,14 +37,14 @@ func NewGroup(groupRepository repository.BruteForceProtectionGroupRepository, lo
|
||||
}
|
||||
}
|
||||
|
||||
func (g *group) Analyze(group *brute_force_protection.Group, eventTime time.Time, ip net.IP, message string) (AnalysisResult, error) {
|
||||
func (g *group) Analyze(group *brute_force_protection.Group, eventTime time.Time, ip net.IP, message string, partition *string) (AnalysisResult, error) {
|
||||
analysisResult := AnalysisResult{
|
||||
Block: false,
|
||||
}
|
||||
|
||||
g.logger.Debug(fmt.Sprintf("Analyzing brute force protection group %s IP %s", group.Name, ip.String()))
|
||||
|
||||
err := g.groupRepository.Update(group.Name, ip, func(entityGroup *entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error) {
|
||||
err := g.groupRepository.Update(group.Name, ip, partition, func(entityGroup *entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error) {
|
||||
rateLimit, err := group.RateLimit(entityGroup.CurrentLevelTriggerCount)
|
||||
if err != nil {
|
||||
return entityGroup, err
|
||||
|
||||
+24
-10
@@ -15,6 +15,7 @@ import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/blocking"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard"
|
||||
"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/daemon/geoip"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/info"
|
||||
@@ -33,16 +34,17 @@ type Daemon interface {
|
||||
}
|
||||
|
||||
type daemon struct {
|
||||
info info.Info
|
||||
pidFile pidfile.PidFile
|
||||
socket socket.Socket
|
||||
logger log.Logger
|
||||
firewall firewall.API
|
||||
notifications notifications.Notifications
|
||||
analyzer analyzer.Analyzer
|
||||
docker docker_monitor.Docker
|
||||
blocklist blocklist.Blocklist
|
||||
geoIPService geoip.GeoIP
|
||||
info info.Info
|
||||
pidFile pidfile.PidFile
|
||||
socket socket.Socket
|
||||
logger log.Logger
|
||||
firewall firewall.API
|
||||
firewallRulesGuard guard.RulesGuard
|
||||
notifications notifications.Notifications
|
||||
analyzer analyzer.Analyzer
|
||||
docker docker_monitor.Docker
|
||||
blocklist blocklist.Blocklist
|
||||
geoIPService geoip.GeoIP
|
||||
|
||||
stopCh chan struct{}
|
||||
}
|
||||
@@ -59,6 +61,10 @@ func (d *daemon) Run(ctx context.Context, isTesting bool, testingInterval uint16
|
||||
return err
|
||||
}
|
||||
d.firewall.SavesRules()
|
||||
d.firewallRulesGuard.Run(d.info, ctx)
|
||||
defer func() {
|
||||
_ = d.firewallRulesGuard.Close()
|
||||
}()
|
||||
|
||||
if err := d.pidFile.Create(); err != nil {
|
||||
return err
|
||||
@@ -162,8 +168,15 @@ func (d *daemon) socketCommand(command string, args map[string]string, socket so
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
hasRules, err := d.firewall.HasRules()
|
||||
nftables := "\033[32mOk\033[0m"
|
||||
if err != nil || !hasRules {
|
||||
nftables = "\033[31mError\033[0m"
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"ok\n\n***\n"+
|
||||
"NFTables: %s\n"+
|
||||
"Version: %s\n"+
|
||||
"BuiltWith: %s\n"+
|
||||
"Uptime: %s\n"+
|
||||
@@ -174,6 +187,7 @@ func (d *daemon) socketCommand(command string, args map[string]string, socket so
|
||||
"HeapSys: %s\n"+
|
||||
"NumGC: %d\n"+
|
||||
"***\n",
|
||||
nftables,
|
||||
d.info.Version(),
|
||||
d.info.BuiltWith(),
|
||||
format.HumanDuration(d.info.Uptime()),
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type AlertGroupRepository interface {
|
||||
Update(name string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error
|
||||
Update(name string, partition *string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error
|
||||
Clear() error
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ func NewAlertGroupRepository(appDB *bbolt.DB) AlertGroupRepository {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *alertGroupRepository) Update(name string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error {
|
||||
func (r *alertGroupRepository) Update(name string, partition *string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error {
|
||||
entityAlertGroup := &entity.AlertGroup{}
|
||||
entityAlertGroup.Reset()
|
||||
|
||||
@@ -36,7 +37,10 @@ func (r *alertGroupRepository) Update(name string, f func(*entity.AlertGroup) (*
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := []byte(name)
|
||||
key, err := keyGroup(name, partition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group := b.Get(key)
|
||||
if group != nil {
|
||||
@@ -70,3 +74,21 @@ func (r *alertGroupRepository) Clear() error {
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func keyGroup(groupID string, partition *string) ([]byte, error) {
|
||||
if len(groupID) == 0 {
|
||||
return nil, fmt.Errorf("group id cannot be empty")
|
||||
}
|
||||
|
||||
if partition == nil {
|
||||
return []byte(groupID), nil
|
||||
}
|
||||
|
||||
partitionHash := sha256.Sum256([]byte(*partition))
|
||||
|
||||
k := make([]byte, 0, len(groupID)+1+len(partitionHash))
|
||||
k = append(k, groupID...)
|
||||
k = append(k, 0x00)
|
||||
k = append(k, partitionHash[:]...)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type BruteForceProtectionGroupRepository interface {
|
||||
Update(name string, ip net.IP, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error
|
||||
Update(name string, ip net.IP, partition *string, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error
|
||||
Clear() error
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ func NewBruteForceProtectionGroupRepository(appDB *bbolt.DB) BruteForceProtectio
|
||||
}
|
||||
}
|
||||
|
||||
func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error {
|
||||
func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, partition *string, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error {
|
||||
entityGroup := &entity.BruteForceProtectionGroup{}
|
||||
entityGroup.Reset()
|
||||
|
||||
@@ -37,7 +38,7 @@ func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, f f
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := keyGroupIP(name, ip)
|
||||
key, err := keyGroupIP(name, ip, partition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,7 +76,7 @@ func (r *bruteForceProtectionGroupRepository) Clear() error {
|
||||
})
|
||||
}
|
||||
|
||||
func keyGroupIP(groupID string, ip net.IP) ([]byte, error) {
|
||||
func keyGroupIP(groupID string, ip net.IP, partition *string) ([]byte, error) {
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("ip cannot be nil")
|
||||
}
|
||||
@@ -95,6 +96,17 @@ func keyGroupIP(groupID string, ip net.IP) ([]byte, error) {
|
||||
ipAddr = ip.To16()
|
||||
}
|
||||
|
||||
if partition != nil {
|
||||
partitionHash := sha256.Sum256([]byte(*partition))
|
||||
k := make([]byte, 0, len(groupID)+1+len(ipAddr)+1+len(partitionHash))
|
||||
k = append(k, groupID...)
|
||||
k = append(k, 0x00)
|
||||
k = append(k, ipAddr...)
|
||||
k = append(k, 0x00)
|
||||
k = append(k, partitionHash[:]...)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
k := make([]byte, 0, len(groupID)+1+len(ipAddr))
|
||||
k = append(k, groupID...)
|
||||
k = append(k, 0x00)
|
||||
|
||||
@@ -48,6 +48,9 @@ type API interface {
|
||||
|
||||
// DockerSupport Return true if docker support
|
||||
DockerSupport() bool
|
||||
|
||||
// HasRules Return true if firewall has rules
|
||||
HasRules() (bool, error)
|
||||
}
|
||||
|
||||
type firewall struct {
|
||||
@@ -208,6 +211,10 @@ func (f *firewall) SavesRules() {
|
||||
f.logger.Info("Save nftables rules")
|
||||
}
|
||||
|
||||
func (f *firewall) HasRules() (bool, error) {
|
||||
return f.table.HasRules()
|
||||
}
|
||||
|
||||
func (f *firewall) BlockIP(blockIP blocking.BlockIP) (bool, error) {
|
||||
isBanned, err := f.blockingService.BlockIP(blockIP)
|
||||
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
package config
|
||||
|
||||
type GuardConfig struct {
|
||||
Enable bool
|
||||
Notifications bool
|
||||
Recovery bool
|
||||
Interval uint32
|
||||
}
|
||||
@@ -0,0 +1,143 @@
|
||||
package guard
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard/config"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/info"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type firewallGuardTarget interface {
|
||||
HasRules() (bool, error)
|
||||
Reload(daemonInfo info.Info) error
|
||||
}
|
||||
|
||||
type RulesGuard interface {
|
||||
Run(daemonInfo info.Info, ctx context.Context)
|
||||
Close() error
|
||||
}
|
||||
|
||||
type rulesGuard struct {
|
||||
config *config.GuardConfig
|
||||
firewall firewallGuardTarget
|
||||
notify notifications.Notifications
|
||||
logger log.Logger
|
||||
|
||||
mu sync.Mutex
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func NewRulesGuard(config *config.GuardConfig, firewall firewallGuardTarget, notify notifications.Notifications, logger log.Logger) RulesGuard {
|
||||
return &rulesGuard{
|
||||
config: config,
|
||||
firewall: firewall,
|
||||
notify: notify,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (g *rulesGuard) Run(daemonInfo info.Info, ctx context.Context) {
|
||||
if !g.config.Enable {
|
||||
g.logger.Debug("firewall rules guard is disabled")
|
||||
return
|
||||
}
|
||||
|
||||
g.logger.Debug("firewall rules guard is enabled")
|
||||
guardCtx, cancel := context.WithCancel(ctx)
|
||||
|
||||
g.mu.Lock()
|
||||
g.cancel = cancel
|
||||
g.mu.Unlock()
|
||||
|
||||
go g.run(daemonInfo, guardCtx)
|
||||
}
|
||||
|
||||
func (g *rulesGuard) Close() error {
|
||||
g.mu.Lock()
|
||||
cancel := g.cancel
|
||||
g.cancel = nil
|
||||
g.mu.Unlock()
|
||||
|
||||
if cancel != nil {
|
||||
cancel()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *rulesGuard) run(daemonInfo info.Info, ctx context.Context) {
|
||||
interval := time.Duration(g.config.Interval) * time.Second
|
||||
|
||||
ticker := time.NewTicker(interval)
|
||||
defer ticker.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
g.checkRules(daemonInfo)
|
||||
|
||||
case <-ctx.Done():
|
||||
g.logger.Debug("firewall rules guard stopped")
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *rulesGuard) checkRules(daemonInfo info.Info) {
|
||||
hasRules, err := g.firewall.HasRules()
|
||||
if err != nil {
|
||||
g.logger.Error(fmt.Sprintf("failed to check firewall rules: %s", err))
|
||||
if g.config.Notifications {
|
||||
message := notifications.Message{
|
||||
Subject: i18n.Lang.T("firewall_rules_not_found"),
|
||||
Body: i18n.Lang.T("firewall_rules_not_found_body_check_error", map[string]interface{}{
|
||||
"Error": err.Error(),
|
||||
}),
|
||||
}
|
||||
g.notify.SendAsync(message)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if hasRules {
|
||||
g.logger.Debug("firewall rules exists")
|
||||
return
|
||||
}
|
||||
|
||||
g.logger.Warn("firewall rules not found")
|
||||
if g.config.Recovery {
|
||||
if err := g.firewall.Reload(daemonInfo); err != nil {
|
||||
g.logger.Error(fmt.Sprintf("failed to recover firewall rules: %s", err))
|
||||
if g.config.Notifications {
|
||||
message := notifications.Message{
|
||||
Subject: i18n.Lang.T("firewall_rules_not_found"),
|
||||
Body: i18n.Lang.T("firewall_rules_not_found_body_recover_error", map[string]interface{}{
|
||||
"Error": err.Error(),
|
||||
}),
|
||||
}
|
||||
g.notify.SendAsync(message)
|
||||
}
|
||||
return
|
||||
}
|
||||
g.logger.Warn("firewall rules recovered")
|
||||
if g.config.Notifications {
|
||||
message := notifications.Message{
|
||||
Subject: i18n.Lang.T("firewall_rules_not_found"),
|
||||
Body: i18n.Lang.T("firewall_rules_not_found_body_recover_success"),
|
||||
}
|
||||
g.notify.SendAsync(message)
|
||||
}
|
||||
} else if g.config.Notifications {
|
||||
message := notifications.Message{
|
||||
Subject: i18n.Lang.T("firewall_rules_not_found"),
|
||||
Body: i18n.Lang.T("firewall_rules_not_found_body"),
|
||||
}
|
||||
g.notify.SendAsync(message)
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft"
|
||||
@@ -11,6 +13,9 @@ type Table interface {
|
||||
Clear() error
|
||||
DockerChains() firewall.NFTDockerChains
|
||||
BlockList() BlockList
|
||||
|
||||
// HasRules Return true if the table has rules
|
||||
HasRules() (bool, error)
|
||||
}
|
||||
|
||||
type BlockList interface {
|
||||
@@ -55,6 +60,45 @@ func (t *table) BlockList() BlockList {
|
||||
return t.blockList
|
||||
}
|
||||
|
||||
func (t *table) HasRules() (bool, error) {
|
||||
if exists, err := t.exists(); err != nil {
|
||||
return false, err
|
||||
} else if !exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
args := []string{"list", "table", t.family.String(), t.name}
|
||||
output, err := t.nft.NFT().Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
openBrace := strings.Index(output, "{")
|
||||
closeBrace := strings.LastIndex(output, "}")
|
||||
if openBrace == -1 || closeBrace == -1 || closeBrace <= openBrace {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(output[openBrace+1 : closeBrace])
|
||||
|
||||
return content != "", nil
|
||||
}
|
||||
|
||||
func (t *table) exists() (bool, error) {
|
||||
args := []string{"list", "tables", t.family.String()}
|
||||
output, err := t.nft.NFT().Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.TrimSpace(line) == "table "+t.family.String()+" "+t.name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type blockList struct {
|
||||
listIP block.ListIP
|
||||
listIPWithPort block.ListIPWithPort
|
||||
|
||||
@@ -194,7 +194,7 @@ func (r *reload) outputICMP(batchOutput chain.Chain) error {
|
||||
func (r *reload) outputICMPAfter(batchOutput chain.Chain) error {
|
||||
if r.config.IP4.IcmpTimestampDrop == true {
|
||||
drop := r.config.Policy.OutputDrop.String()
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp icmp type timestamp-request " + drop); err != nil {
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp icmp type timestamp-reply " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,14 +4,16 @@ import (
|
||||
analyzerConfig "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/db"
|
||||
firewallConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/config"
|
||||
GuardConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard/config"
|
||||
)
|
||||
|
||||
type DaemonOptions struct {
|
||||
PathPidFile string
|
||||
PathSocketFile string
|
||||
DataDir string
|
||||
PathNftables string
|
||||
ConfigFirewall firewallConfig.Config
|
||||
ConfigAnalyzer analyzerConfig.Config
|
||||
Repositories db.Repositories
|
||||
PathPidFile string
|
||||
PathSocketFile string
|
||||
DataDir string
|
||||
PathNftables string
|
||||
ConfigFirewall firewallConfig.Config
|
||||
ConfigFirewallGuard GuardConfig.GuardConfig
|
||||
ConfigAnalyzer analyzerConfig.Config
|
||||
Repositories db.Repositories
|
||||
}
|
||||
|
||||
+13
-10
@@ -10,6 +10,7 @@ import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor"
|
||||
firewall2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/blocking"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/geoip"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/info"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
@@ -57,20 +58,22 @@ func NewDaemon(
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
firewallRulesGuard := guard.NewRulesGuard(&opts.ConfigFirewallGuard, firewall, notifications, logger)
|
||||
|
||||
blockService := brute_force_protection_group.NewBlockService(firewall.BlockIP, firewall.BlockIPWithPorts)
|
||||
analyzerService := analyzer.New(opts.ConfigAnalyzer, blockService, opts.Repositories, logger, notifications, geoIPService.Info)
|
||||
|
||||
return &daemon{
|
||||
info: info,
|
||||
pidFile: pidFile,
|
||||
socket: sock,
|
||||
logger: logger,
|
||||
firewall: firewall,
|
||||
notifications: notifications,
|
||||
analyzer: analyzerService,
|
||||
docker: docker,
|
||||
blocklist: blocklist,
|
||||
geoIPService: geoIPService,
|
||||
info: info,
|
||||
pidFile: pidFile,
|
||||
socket: sock,
|
||||
logger: logger,
|
||||
firewall: firewall,
|
||||
firewallRulesGuard: firewallRulesGuard,
|
||||
notifications: notifications,
|
||||
analyzer: analyzerService,
|
||||
docker: docker,
|
||||
blocklist: blocklist,
|
||||
geoIPService: geoIPService,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -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}}",
|
||||
@@ -86,5 +96,11 @@
|
||||
"alert.bruteForceProtection.ssh.message": "An attempt to brute-force SSH was detected.",
|
||||
"alert.bruteForceProtection.group._default.message": "Default group.",
|
||||
|
||||
"cmd.error": "Command error: {{.Error}}"
|
||||
"cmd.error": "Command error: {{.Error}}",
|
||||
|
||||
"firewall_rules_not_found": "No firewall rules found",
|
||||
"firewall_rules_not_found_body": "No firewall rules found. The \"Restore Rules\" setting is disabled. You are currently unprotected!!!",
|
||||
"firewall_rules_not_found_body_recover_error": "Firewall rules not found. We couldn't restore firewall rules. You are currently unprotected!!! Error restoring firewall rules: {{.Error}}",
|
||||
"firewall_rules_not_found_body_recover_success": "No firewall rules were found. However, the firewall rules were successfully restored!",
|
||||
"firewall_rules_not_found_body_check_error": "Error checking firewall rules: {{.Error}}"
|
||||
}
|
||||
|
||||
@@ -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}}",
|
||||
@@ -86,5 +96,11 @@
|
||||
"alert.bruteForceProtection.ssh.message": "SSH-ті күштеп қолдану әрекеті анықталды.",
|
||||
"alert.bruteForceProtection.group._default.message": "Әдепкі топ.",
|
||||
|
||||
"cmd.error": "Команда қатесі: {{.Error}}"
|
||||
"cmd.error": "Команда қатесі: {{.Error}}",
|
||||
|
||||
"firewall_rules_not_found": "Брандмауэр ережелері табылмады",
|
||||
"firewall_rules_not_found_body": "Брандмауэр ережелері табылмады. \"Ережелерді қалпына келтіру\" опциясы өшірілген. Сіз қазіргі уақытта қорғалмағансыз!!!",
|
||||
"firewall_rules_not_found_body_recover_error": "Брандмауэр ережелері табылмады. Брандмауэр ережелерін қалпына келтіре алмадық. Сіз қазіргі уақытта қорғалмағансыз!!! Брандмауэр ережелерін қалпына келтіруде қате пайда болды: {{.Error}}",
|
||||
"firewall_rules_not_found_body_recover_success": "Брандмауэр ережелері табылмады. Дегенмен, брандмауэр ережелері сәтті қалпына келтірілді!",
|
||||
"firewall_rules_not_found_body_check_error": "Брандмауэр ережелерін тексеру кезінде қате пайда болды: {{.Error}}"
|
||||
}
|
||||
@@ -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}}",
|
||||
@@ -86,5 +96,11 @@
|
||||
"alert.bruteForceProtection.ssh.message": "Обнаружена попытка атаки на SSH методом перебора паролей.",
|
||||
"alert.bruteForceProtection.group._default.message": "Группа по умолчанию.",
|
||||
|
||||
"cmd.error": "Ошибка команды: {{.Error}}"
|
||||
"cmd.error": "Ошибка команды: {{.Error}}",
|
||||
|
||||
"firewall_rules_not_found": "Не найдены правила брандмауэра",
|
||||
"firewall_rules_not_found_body": "Правила брандмауэра не найдены. Параметр «Восстановить правила» отключен. В данный момент вы незащищены!!!",
|
||||
"firewall_rules_not_found_body_recover_error": "Правила брандмауэра не найдены. Восстановить правила брандмауэра не удалось. В данный момент вы незащищены!!! Ошибка при восстановлении правил брандмауэра: {{.Error}}",
|
||||
"firewall_rules_not_found_body_recover_success": "Правила брандмауэра не были обнаружены. Однако правила брандмауэра были успешно восстановлены!",
|
||||
"firewall_rules_not_found_body_check_error": "Ошибка при проверке правил брандмауэра: {{.Error}}"
|
||||
}
|
||||
@@ -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{
|
||||
|
||||
@@ -8,9 +8,10 @@ import (
|
||||
)
|
||||
|
||||
type BruteForceProtectionPattern struct {
|
||||
Regexp string `mapstructure:"regexp"`
|
||||
IP int `mapstructure:"ip"`
|
||||
Values []PatternValue
|
||||
Regexp string `mapstructure:"regexp"`
|
||||
IP int `mapstructure:"ip"`
|
||||
Values []PatternValue
|
||||
Partition *PatternPartition `mapstructure:"partition"`
|
||||
}
|
||||
|
||||
func (p *BruteForceProtectionPattern) ToPattern() (brute_force_protection.RegexPattern, error) {
|
||||
@@ -32,6 +33,14 @@ func (p *BruteForceProtectionPattern) ToPattern() (brute_force_protection.RegexP
|
||||
pattern.Values = append(pattern.Values, v)
|
||||
}
|
||||
|
||||
if p.Partition != nil {
|
||||
partition, err := p.Partition.ToPatternPartition()
|
||||
if err != nil {
|
||||
return brute_force_protection.RegexPattern{}, err
|
||||
}
|
||||
pattern.Partition = partition
|
||||
}
|
||||
|
||||
return pattern, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -6,8 +6,9 @@ import (
|
||||
)
|
||||
|
||||
type LogAlertPattern struct {
|
||||
Regexp string `mapstructure:"regexp"`
|
||||
Values []PatternValue
|
||||
Regexp string `mapstructure:"regexp"`
|
||||
Values []PatternValue
|
||||
Partition *PatternPartition `mapstructure:"partition"`
|
||||
}
|
||||
|
||||
func (p *LogAlertPattern) ToPattern() (config.AlertRegexPattern, error) {
|
||||
@@ -24,5 +25,13 @@ func (p *LogAlertPattern) ToPattern() (config.AlertRegexPattern, error) {
|
||||
pattern.Values = append(pattern.Values, v)
|
||||
}
|
||||
|
||||
if p.Partition != nil {
|
||||
partition, err := p.Partition.ToPatternPartition()
|
||||
if err != nil {
|
||||
return config.AlertRegexPattern{}, err
|
||||
}
|
||||
pattern.Partition = partition
|
||||
}
|
||||
|
||||
return pattern, nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/partition"
|
||||
)
|
||||
|
||||
type PatternPartition struct {
|
||||
Value int `mapstructure:"value"`
|
||||
Trim bool `mapstructure:"trim"`
|
||||
LowerCase bool `mapstructure:"lower_case"`
|
||||
|
||||
Type *PatternPartitionType `mapstructure:"type"`
|
||||
}
|
||||
|
||||
func (p *PatternPartition) ToPatternPartition() (*partition.PatternPartition, error) {
|
||||
if err := p.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
normalize := partition.NewNormalize(p.Trim, p.LowerCase)
|
||||
|
||||
var patternPartitionType partition.PatternPartitionType
|
||||
if p.Type != nil {
|
||||
if partitionType, err := p.Type.ToType(normalize); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
patternPartitionType = partitionType
|
||||
}
|
||||
}
|
||||
|
||||
return &partition.PatternPartition{
|
||||
Value: uint8(p.Value),
|
||||
Type: patternPartitionType,
|
||||
Normalize: normalize.Normalize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *PatternPartition) validate() error {
|
||||
if p.Value <= 0 || p.Value > 255 {
|
||||
return errors.New("invalid partition value. min: 1, max: 255")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package analyzer
|
||||
|
||||
import (
|
||||
"errors"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/partition"
|
||||
)
|
||||
|
||||
type PatternPartitionType struct {
|
||||
Type string `mapstructure:"type"`
|
||||
Keywords []string `mapstructure:"keywords"`
|
||||
Partitioned bool `mapstructure:"partitioned"`
|
||||
}
|
||||
|
||||
func (p *PatternPartitionType) ToType(normalize partition.Normalize) (partition.PatternPartitionType, error) {
|
||||
if err := p.validate(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var keywords []string
|
||||
for _, keyword := range p.Keywords {
|
||||
keywords = append(keywords, normalize.Normalize(keyword))
|
||||
}
|
||||
|
||||
if p.Type == "except" {
|
||||
return partition.NewExceptType(keywords, p.Partitioned), nil
|
||||
}
|
||||
|
||||
return partition.NewOnlyType(keywords, p.Partitioned), nil
|
||||
}
|
||||
|
||||
func (p *PatternPartitionType) validate() error {
|
||||
if p.Type != "only" && p.Type != "except" {
|
||||
return errors.New("invalid partition type. only 'only' and 'except' are supported")
|
||||
}
|
||||
|
||||
if len(p.Keywords) == 0 {
|
||||
return errors.New("invalid partition type. keywords are required")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -3,6 +3,7 @@ package blocklists
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
||||
@@ -10,8 +11,9 @@ import (
|
||||
)
|
||||
|
||||
type Setting struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Sources []Sources
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
ExcludeIPs []string `mapstructure:"exclude_ips"`
|
||||
Sources []Sources
|
||||
}
|
||||
|
||||
func InitSetting(path string) (Setting, error) {
|
||||
@@ -42,6 +44,14 @@ func InitSetting(path string) (Setting, error) {
|
||||
func settingDefault() Setting {
|
||||
return Setting{
|
||||
Enabled: false,
|
||||
ExcludeIPs: []string{
|
||||
"127.0.0.1/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"::1/128",
|
||||
"fc00::/7",
|
||||
},
|
||||
Sources: []Sources{},
|
||||
}
|
||||
}
|
||||
@@ -52,6 +62,17 @@ func (b *Setting) ToSources(logger log.Logger) []*blocklist.SourceConfig {
|
||||
return sources
|
||||
}
|
||||
|
||||
logger.Debug(fmt.Sprintf("exclude IPs: %v", b.ExcludeIPs))
|
||||
|
||||
var exclusionChecker parser.ExclusionChecker
|
||||
if len(b.ExcludeIPs) > 0 {
|
||||
if checker, err := parser.NewExclusionChecker(b.ExcludeIPs); err != nil {
|
||||
logger.Warn(fmt.Sprintf("failed to create exclusion checker: %s", err))
|
||||
} else {
|
||||
exclusionChecker = checker
|
||||
}
|
||||
}
|
||||
|
||||
sourceNames := make(map[string]string)
|
||||
|
||||
for _, source := range b.Sources {
|
||||
@@ -65,7 +86,7 @@ func (b *Setting) ToSources(logger log.Logger) []*blocklist.SourceConfig {
|
||||
}
|
||||
sourceNames[source.Name] = source.Name
|
||||
|
||||
sourceConfig, err := source.ToSourceConfig()
|
||||
sourceConfig, err := source.ToSourceConfig(exclusionChecker)
|
||||
if err != nil {
|
||||
logger.Warn(fmt.Sprintf("failed to convert source: %s", err))
|
||||
continue
|
||||
|
||||
@@ -39,7 +39,7 @@ type Sources struct {
|
||||
RssFieldSeparator string `mapstructure:"rss_field_separator"`
|
||||
}
|
||||
|
||||
func (s *Sources) ToSourceConfig() (*daemonBlocklist.SourceConfig, error) {
|
||||
func (s *Sources) ToSourceConfig(exclusionChecker parser.ExclusionChecker) (*daemonBlocklist.SourceConfig, error) {
|
||||
if err := s.Validate(); err != nil {
|
||||
return &daemonBlocklist.SourceConfig{}, err
|
||||
}
|
||||
@@ -49,9 +49,11 @@ func (s *Sources) ToSourceConfig() (*daemonBlocklist.SourceConfig, error) {
|
||||
return &daemonBlocklist.SourceConfig{}, err
|
||||
}
|
||||
|
||||
config := blocklist.NewConfig(uint(s.Limit))
|
||||
config := blocklist.NewConfigWithExclusionChecker(uint(s.Limit), exclusionChecker)
|
||||
if s.TxtType == "interval" {
|
||||
config.Validator = &parser.IPRangeValidator{}
|
||||
config.Validator = &parser.IPRangeValidator{
|
||||
ExclusionChecker: exclusionChecker,
|
||||
}
|
||||
}
|
||||
|
||||
if s.Zip {
|
||||
|
||||
@@ -17,6 +17,7 @@ type Setting struct {
|
||||
MetadataNaming metadataNaming
|
||||
Policy policy
|
||||
PortKnocking []portKnocking
|
||||
RulesGuard RulesGuard
|
||||
}
|
||||
|
||||
func InitSetting(path string) (Setting, error) {
|
||||
@@ -53,6 +54,7 @@ func settingDefault() Setting {
|
||||
MetadataNaming: defaultMetadataNaming(),
|
||||
Policy: defaultPolicy(),
|
||||
PortKnocking: defaultPortKnocking(),
|
||||
RulesGuard: defaultRulesGuard(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +74,9 @@ func (s Setting) Validate() error {
|
||||
if err := s.Options.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.RulesGuard.Validate(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
GuardConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard/config"
|
||||
)
|
||||
|
||||
type RulesGuard struct {
|
||||
Enabled bool `mapstructure:"enabled"`
|
||||
Notifications bool `mapstructure:"notifications"`
|
||||
Recovery bool `mapstructure:"recovery"`
|
||||
Interval int32 `mapstructure:"interval"`
|
||||
}
|
||||
|
||||
func defaultRulesGuard() RulesGuard {
|
||||
return RulesGuard{
|
||||
Enabled: true,
|
||||
Notifications: true,
|
||||
Recovery: true,
|
||||
Interval: 3600,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RulesGuard) Validate() error {
|
||||
if r.Interval < 60 {
|
||||
return fmt.Errorf("interval must be greater than 60")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RulesGuard) ToGuardConfig() GuardConfig.GuardConfig {
|
||||
return GuardConfig.GuardConfig{
|
||||
Enable: r.Enabled,
|
||||
Notifications: r.Notifications,
|
||||
Recovery: r.Recovery,
|
||||
Interval: uint32(r.Interval),
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor"
|
||||
firewallConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/config"
|
||||
GuardConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard/config"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/geoip"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
|
||||
@@ -42,38 +43,38 @@ func otherSettingsPathDefault() *otherSettingsPath {
|
||||
}
|
||||
}
|
||||
|
||||
func (o *otherSettingsPath) ToFirewallConfig(dockerSupport bool) (firewallConfig.Config, error) {
|
||||
func (o *otherSettingsPath) ToFirewallConfig(dockerSupport bool) (firewallConfig.Config, GuardConfig.GuardConfig, error) {
|
||||
setting, err := firewallSetting.InitSetting(o.Firewall)
|
||||
if err != nil {
|
||||
return firewallConfig.Config{}, err
|
||||
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||
}
|
||||
|
||||
configPolicy, err := setting.Policy.ToConfigPolicy()
|
||||
if err != nil {
|
||||
return firewallConfig.Config{}, err
|
||||
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||
}
|
||||
|
||||
inPorts, outPorts, err := setting.ToPorts()
|
||||
if err != nil {
|
||||
return firewallConfig.Config{}, err
|
||||
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||
}
|
||||
|
||||
IPs, err := setting.ToIPs()
|
||||
if err != nil {
|
||||
return firewallConfig.Config{}, err
|
||||
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||
}
|
||||
|
||||
optionClearMode, err := setting.Options.ToClearMode()
|
||||
if err != nil {
|
||||
return firewallConfig.Config{}, err
|
||||
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||
}
|
||||
|
||||
portKnocking, err := setting.ToConfigPortKnocking()
|
||||
if err != nil {
|
||||
return firewallConfig.Config{}, err
|
||||
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||
}
|
||||
|
||||
return firewallConfig.Config{
|
||||
firewall := firewallConfig.Config{
|
||||
InPorts: inPorts,
|
||||
OutPorts: outPorts,
|
||||
IP4: firewallConfig.ConfigIP4{
|
||||
@@ -109,7 +110,11 @@ func (o *otherSettingsPath) ToFirewallConfig(dockerSupport bool) (firewallConfig
|
||||
},
|
||||
Policy: configPolicy,
|
||||
PortKnocking: portKnocking,
|
||||
}, nil
|
||||
}
|
||||
|
||||
rulesGuard := setting.RulesGuard.ToGuardConfig()
|
||||
|
||||
return firewall, rulesGuard, nil
|
||||
}
|
||||
|
||||
func (o *otherSettingsPath) ToNotificationsConfig() (notifications.Config, error) {
|
||||
|
||||
+38
-29
@@ -44,31 +44,11 @@ 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 err := s.ValidateBeforeStart(); err != nil {
|
||||
return daemon.DaemonOptions{}, err
|
||||
}
|
||||
|
||||
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",
|
||||
}))
|
||||
}
|
||||
|
||||
firewallConfig, err := s.OtherSettingsPath.ToFirewallConfig(dockerSupport)
|
||||
firewallConfig, guardConfig, err := s.OtherSettingsPath.ToFirewallConfig(dockerSupport)
|
||||
if err != nil {
|
||||
return daemon.DaemonOptions{}, err
|
||||
}
|
||||
@@ -79,15 +59,44 @@ func (s setting) ToDaemonOptions(dockerSupport bool) (daemon.DaemonOptions, erro
|
||||
}
|
||||
|
||||
return daemon.DaemonOptions{
|
||||
PathPidFile: s.PidFile,
|
||||
PathSocketFile: s.SocketFile,
|
||||
DataDir: s.DataDir,
|
||||
PathNftables: s.BinaryLocations.Nftables,
|
||||
ConfigFirewall: firewallConfig,
|
||||
ConfigAnalyzer: analyzerConfig,
|
||||
PathPidFile: s.PidFile,
|
||||
PathSocketFile: s.SocketFile,
|
||||
DataDir: s.DataDir,
|
||||
PathNftables: s.BinaryLocations.Nftables,
|
||||
ConfigFirewall: firewallConfig,
|
||||
ConfigFirewallGuard: guardConfig,
|
||||
ConfigAnalyzer: analyzerConfig,
|
||||
}, 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