Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
14d55835e8
|
||
|
|
e713fc1086
|
||
|
|
bef37d7344
|
||
|
|
0f28fe05fc
|
||
|
|
92c0108493
|
||
|
|
e72d8181f7
|
||
|
|
6f0cb17436
|
||
|
|
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
|
||
|
|
7cdd761dcd | ||
|
|
bdbbd303cc
|
||
|
|
b7cfc756c1
|
||
|
|
b396963976
|
||
|
|
eb4b0b4e4f
|
||
|
|
03a1d46f1b
|
||
|
|
eda1b956f3
|
||
|
|
14c4c504fe
|
||
|
|
5acc8a72de
|
||
|
|
1047c75629
|
||
|
|
f93716e194
|
+75
-10
@@ -1,4 +1,69 @@
|
|||||||
## 0.11.0 (7.5.2026)
|
## 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)
|
||||||
|
#### Русский
|
||||||
|
* Добавлено: отображение номера уведомления в групповых уведомлениях.
|
||||||
|
* Добавлено: теперь в уведомлениях отображается количество блокировок IP-адреса.
|
||||||
|
* Добавлены параметры для ограничения частоты уведомлений об успешной блокировке (`analyzer.toml`):
|
||||||
|
* `ssh_notify_cooldown_seconds` — задаёт минимальный интервал в секундах между уведомлениями об успешных SSH-блокировках.
|
||||||
|
* `ssh_notify_every` — отправляет следующее уведомление об успешной SSH-блокировке только после указанного количества новых сообщений о блокировке; имеет приоритет над `ssh_notify_cooldown_seconds`.
|
||||||
|
* `notify_cooldown_seconds` — задаёт минимальный интервал в секундах между уведомлениями об успешных блокировках для пользовательских правил защиты от перебора пароля.
|
||||||
|
* `notify_every` — отправляет следующее уведомление для пользовательского правила только после указанного количества новых сообщений о блокировке; имеет приоритет над `notify_cooldown_seconds`.
|
||||||
|
***
|
||||||
|
#### English
|
||||||
|
* Added: display of notification number in group notifications.
|
||||||
|
* Added: notifications now display how many times an IP address has been blocked.
|
||||||
|
* Added options to limit the frequency of notifications about successful blocking (`analyzer.toml`):
|
||||||
|
* `ssh_notify_cooldown_seconds` — Sets the minimum interval in seconds between notifications of successful SSH locks.
|
||||||
|
* `ssh_notify_every` — Sends the next successful SSH lock notification only after the specified number of new lock messages; takes precedence over `ssh_notify_cooldown_seconds`.
|
||||||
|
* `notify_cooldown_seconds` — Sets the minimum interval in seconds between notifications of successful locks for custom password attack protection rules.
|
||||||
|
* `notify_every` — Sends the next notification for the custom rule only after the specified number of new block messages; takes precedence over `notify_cooldown_seconds`.
|
||||||
|
***
|
||||||
|
## 0.11.0 (07.05.2026)
|
||||||
#### Русский
|
#### Русский
|
||||||
* В настройки файла `firewall.toml` добавлен параметр `options.cache`.
|
* В настройки файла `firewall.toml` добавлен параметр `options.cache`.
|
||||||
* Этот параметр включает кэширование, чтобы избежать постоянной компиляции команд nftables во временный файл. Файл кэша изменяется после изменения настроек или обновления версии программы. (`Включено по умолчанию`)
|
* Этот параметр включает кэширование, чтобы избежать постоянной компиляции команд nftables во временный файл. Файл кэша изменяется после изменения настроек или обновления версии программы. (`Включено по умолчанию`)
|
||||||
@@ -25,7 +90,7 @@
|
|||||||
* Improved `Uptime` output in `kor-elf-shield status` command.
|
* Improved `Uptime` output in `kor-elf-shield status` command.
|
||||||
* Improved display of blocking time in notifications.
|
* Improved display of blocking time in notifications.
|
||||||
***
|
***
|
||||||
## 0.10.0 (12.4.2026)
|
## 0.10.0 (12.04.2026)
|
||||||
#### Русский
|
#### Русский
|
||||||
* При автоматической блокировке добавил возможность получать данные об IP-адресах (континент, страна, город, часовой пояс).
|
* При автоматической блокировке добавил возможность получать данные об IP-адресах (континент, страна, город, часовой пояс).
|
||||||
* В файл analyzer.toml добавлен параметр type к [[logAlert.rules.patterns.values]] в котором можно указать тип "ip". Это позволит для этого поля получить данные об IP-адресе при отправке оповещения.
|
* В файл analyzer.toml добавлен параметр type к [[logAlert.rules.patterns.values]] в котором можно указать тип "ip". Это позволит для этого поля получить данные об IP-адресе при отправке оповещения.
|
||||||
@@ -46,7 +111,7 @@
|
|||||||
* The otherSettingsPath.geoip parameter has been added to the kor-elf-shield.toml file.
|
* The otherSettingsPath.geoip parameter has been added to the kor-elf-shield.toml file.
|
||||||
* A new geoip.toml settings file has been added. This file configures parameters for retrieving IP address data.
|
* A new geoip.toml settings file has been added. This file configures parameters for retrieving IP address data.
|
||||||
***
|
***
|
||||||
## 0.9.0 (21.3.2026)
|
## 0.9.0 (21.03.2026)
|
||||||
#### Русский
|
#### Русский
|
||||||
* Добавилась поддержка Port knocking.
|
* Добавилась поддержка Port knocking.
|
||||||
* В firewall.toml добавился раздел Port knocking.
|
* В firewall.toml добавился раздел Port knocking.
|
||||||
@@ -78,7 +143,7 @@
|
|||||||
* GreenSnow Hack List
|
* GreenSnow Hack List
|
||||||
* Added the otherSettingsPath.blocklists parameter to the kor-elf-shield.toml settings.
|
* Added the otherSettingsPath.blocklists parameter to the kor-elf-shield.toml settings.
|
||||||
* Added a new settings file, blocklists.toml. It contains settings for obtaining a list of IP addresses to block.
|
* Added a new settings file, blocklists.toml. It contains settings for obtaining a list of IP addresses to block.
|
||||||
## 0.8.0 (9.3.2026)
|
## 0.8.0 (09.03.2026)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* Теперь можно тонко настроить блокировку портов для IP адреса, который пытается подобрать пароль.
|
* Теперь можно тонко настроить блокировку портов для IP адреса, который пытается подобрать пароль.
|
||||||
@@ -102,7 +167,7 @@
|
|||||||
* The `kor-elf-shield block add` command has been added. This command can be used to block an IP address. See `kor-elf-shield block add --help` for details.
|
* The `kor-elf-shield block add` command has been added. This command can be used to block an IP address. See `kor-elf-shield block add --help` for details.
|
||||||
* The `kor-elf-shield block delete` command has been added. This command can be used to delete a blocked IP address. See `kor-elf-shield block delete --help` for details.
|
* The `kor-elf-shield block delete` command has been added. This command can be used to delete a blocked IP address. See `kor-elf-shield block delete --help` for details.
|
||||||
***
|
***
|
||||||
## 0.7.0 (28.2.2026)
|
## 0.7.0 (28.02.2026)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* Добавлена возможность настройки отслеживания событий в журналах.
|
* Добавлена возможность настройки отслеживания событий в журналах.
|
||||||
@@ -116,7 +181,7 @@
|
|||||||
* New parameters have been added to the analyzer.toml settings file. See the full list at: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
|
* New parameters have been added to the analyzer.toml settings file. See the full list at: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
|
||||||
* Added the `kor-elf-shield ban clear` command, which unbans all banned IP addresses.
|
* Added the `kor-elf-shield ban clear` command, which unbans all banned IP addresses.
|
||||||
***
|
***
|
||||||
## 0.6.0 (8.2.2026)
|
## 0.6.0 (08.02.2026)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* Добавлена возможность повторной отправки уведомления, если в прошлый раз произошла ошибка.
|
* Добавлена возможность повторной отправки уведомления, если в прошлый раз произошла ошибка.
|
||||||
@@ -138,7 +203,7 @@
|
|||||||
* enable_retries = Enables repeated attempts to send a notification if the first attempt fails. Default: true
|
* enable_retries = Enables repeated attempts to send a notification if the first attempt fails. Default: true
|
||||||
* retry_interval = The time interval in seconds between attempts. Default: 600
|
* retry_interval = The time interval in seconds between attempts. Default: 600
|
||||||
***
|
***
|
||||||
## 0.5.0 (17.1.2026)
|
## 0.5.0 (17.01.2026)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* В настройках analyzer.toml добавил параметры local_enable и local_notify.
|
* В настройках analyzer.toml добавил параметры local_enable и local_notify.
|
||||||
@@ -162,7 +227,7 @@
|
|||||||
* sudo_enable = Enables tracking of logins via sudo. Off by default.
|
* sudo_enable = Enables tracking of logins via sudo. Off by default.
|
||||||
* sudo_notify = Enables notifications about logins via sudo. Enabled by default.
|
* sudo_notify = Enables notifications about logins via sudo. Enabled by default.
|
||||||
***
|
***
|
||||||
## 0.4.0 (11.1.2026)
|
## 0.4.0 (11.01.2026)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* Удалён параметр options.docker_support из файла firewall.toml. Настройки от Docker перенесены в файл docker.toml.
|
* Удалён параметр options.docker_support из файла firewall.toml. Настройки от Docker перенесены в файл docker.toml.
|
||||||
@@ -184,7 +249,7 @@
|
|||||||
* The program crashed after Docker was stopped.
|
* The program crashed after Docker was stopped.
|
||||||
* The IP addresses specified in the settings were not blocked during redirection to the Docker container.
|
* The IP addresses specified in the settings were not blocked during redirection to the Docker container.
|
||||||
***
|
***
|
||||||
## 0.3.0 (4.1.2026)
|
## 0.3.0 (04.01.2026)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* Добавлена частичная поддержка Docker.
|
* Добавлена частичная поддержка Docker.
|
||||||
@@ -225,7 +290,7 @@
|
|||||||
* Added the output_priority parameter to firewall.toml. You can specify a priority from -50 to 50 for chain output.
|
* Added the output_priority parameter to firewall.toml. You can specify a priority from -50 to 50 for chain output.
|
||||||
* Added the forward_priority parameter to firewall.toml. You can specify a priority from -50 to 50 for chain forward.
|
* Added the forward_priority parameter to firewall.toml. You can specify a priority from -50 to 50 for chain forward.
|
||||||
***
|
***
|
||||||
## 0.1.0 (8.11.2025)
|
## 0.1.0 (08.11.2025)
|
||||||
***
|
***
|
||||||
#### Русский
|
#### Русский
|
||||||
* Реализована возможность настраивать nftables:
|
* Реализована возможность настраивать nftables:
|
||||||
|
|||||||
@@ -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
|
golang.org/x/sys
|
||||||
|
|
||||||
Copyright 2009 The Go Authors.
|
Copyright 2009 The Go Authors.
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
# Kor-Elf Shield
|
# Kor-Elf Shield
|
||||||
### <strong>Русский</strong> <a href="README_ENG.md">English</a>
|
### <strong>Русский</strong> <a href="README_ENG.md">English</a>
|
||||||
<p style="color: red; font-weight: bold">ВНИМАНИЕ: Программа на стадии активной разработки и на данный момент NOT PRODUCTION READY</p>
|
|
||||||
<p>Я больше 10-ти лет использовал ConfigServer Security and Firewall (csf) для защиты своего сервера. Но, к сожалению, в сентябре узнал, что компания, которая поддерживала этот великолепный продукт, закрылась 31 августа 2025 года. CSF написан на языке PERL. И компания выложила все исходные коды в свой репозиторий под лицензией GPLv3. Но я не знаю язык PERL. И мне его тяжело читать. :)</p>
|
<p>Я больше 10-ти лет использовал ConfigServer Security and Firewall (csf) для защиты своего сервера. Но, к сожалению, в сентябре узнал, что компания, которая поддерживала этот великолепный продукт, закрылась 31 августа 2025 года. CSF написан на языке PERL. И компания выложила все исходные коды в свой репозиторий под лицензией GPLv3. Но я не знаю язык PERL. И мне его тяжело читать. :)</p>
|
||||||
<p>Я решил реализовать своё решение на языке Go Lang. Это не будет полная копия CSF. Просто CSF вдохновило меня сделать, что-то похожее для защиты своего сервера.</p>
|
<p>Я решил реализовать своё решение на языке Go Lang. Это не будет полная копия CSF. Просто CSF вдохновило меня сделать, что-то похожее для защиты своего сервера.</p>
|
||||||
|
|
||||||
@@ -57,11 +56,22 @@
|
|||||||
***
|
***
|
||||||
|
|
||||||
## Настройки:
|
## Настройки:
|
||||||
|
<p><a href="https://configurator.shield.kor-elf.net/" target="_blank">Вы можете воспользоваться конфигуратором.</a></p>
|
||||||
|
|
||||||
<p><strong>/etc/kor-elf-shield/kor-elf-shield.toml</strong> - тут находятся общие настройки. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml</a></p>
|
<p><strong>/etc/kor-elf-shield/kor-elf-shield.toml</strong> - тут находятся общие настройки. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml</a></p>
|
||||||
|
|
||||||
<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/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>
|
<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>
|
||||||
+11
-1
@@ -1,6 +1,5 @@
|
|||||||
# Kor-Elf Shield
|
# Kor-Elf Shield
|
||||||
### <strong>English</strong> <a href="README.md">Русский</a>
|
### <strong>English</strong> <a href="README.md">Русский</a>
|
||||||
<p style="color: red; font-weight: bold">ATTENTION: The program is under active development and is NOT CURRENTLY PRODUCTION READY</p>
|
|
||||||
<p>I have been using ConfigServer Security and Firewall (csf) for over 10 years to protect my server. But unfortunately, in September I found out that the company that supported this great product closed on August 31, 2025. CSF is written in PERL. And the company uploaded all the source codes to its repository under the GPLv3 license. But I do not know the PERL language. And it's hard for me to read it. :)</p>
|
<p>I have been using ConfigServer Security and Firewall (csf) for over 10 years to protect my server. But unfortunately, in September I found out that the company that supported this great product closed on August 31, 2025. CSF is written in PERL. And the company uploaded all the source codes to its repository under the GPLv3 license. But I do not know the PERL language. And it's hard for me to read it. :)</p>
|
||||||
<p>I decided to implement my solution in the Go Lang language. It will not be a complete copy of CSF. CSF just inspired me to do something similar to protect my server.</p>
|
<p>I decided to implement my solution in the Go Lang language. It will not be a complete copy of CSF. CSF just inspired me to do something similar to protect my server.</p>
|
||||||
|
|
||||||
@@ -57,11 +56,22 @@
|
|||||||
***
|
***
|
||||||
|
|
||||||
## Settings:
|
## Settings:
|
||||||
|
<p><a href="https://configurator.shield.kor-elf.net/" target="_blank">You can use the configurator.</a></p>
|
||||||
|
|
||||||
<p><strong>/etc/kor-elf-shield/kor-elf-shield.toml</strong> - General settings are located here. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml/language/en</a></p>
|
<p><strong>/etc/kor-elf-shield/kor-elf-shield.toml</strong> - General settings are located here. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/kor-elf-shield.toml/language/en</a></p>
|
||||||
|
|
||||||
<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/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>
|
<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
|
rate_limit_period = 3600
|
||||||
|
|
||||||
###
|
###
|
||||||
# Указываем в секундах, через какое время сбрасывать данные IP в групе _default если не было событий.
|
# Указываем в секундах, через какое время сбрасывать данные IP в группе _default если не было событий.
|
||||||
# Если указать 0, то не будет сбрасывать.
|
# Если указать 0, то не будет сбрасывать.
|
||||||
# По умолчанию: 86400
|
# По умолчанию: 86400
|
||||||
# ***
|
# ***
|
||||||
@@ -68,9 +68,15 @@ rate_limit_reset_period = 86400
|
|||||||
|
|
||||||
###
|
###
|
||||||
# Включает защиту от перебора пароля от ssh.
|
# Включает защиту от перебора пароля от ssh.
|
||||||
|
# !!! Примечание: По умолчанию SSH-сервер не регистрирует на неудачные попытки входа с использованием ключа.
|
||||||
|
# Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр
|
||||||
|
# "LogLevel VERBOSE" в настройки sshd_config !!!
|
||||||
# По умолчанию: true
|
# По умолчанию: true
|
||||||
# ***
|
# ***
|
||||||
# Enables protection against brute-force attacks against ssh.
|
# 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
|
# Default: true
|
||||||
###
|
###
|
||||||
ssh_enable = true
|
ssh_enable = true
|
||||||
@@ -86,6 +92,34 @@ ssh_enable = true
|
|||||||
###
|
###
|
||||||
ssh_notify = true
|
ssh_notify = true
|
||||||
|
|
||||||
|
###
|
||||||
|
# Минимальный интервал в секундах между уведомлениями об успешной SSH-блокировке.
|
||||||
|
# После отправки уведомления повторные уведомления не будут отправляться в течение указанного времени.
|
||||||
|
# Если указать 0, ограничение по времени отключено.
|
||||||
|
# По умолчанию: 0
|
||||||
|
# ***
|
||||||
|
# Minimum interval in seconds between notifications about successful SSH blocks.
|
||||||
|
# After a notification is sent, repeated notifications will not be sent for the specified time.
|
||||||
|
# If you specify 0, the time limit is disabled.
|
||||||
|
# Default: 0
|
||||||
|
###
|
||||||
|
ssh_notify_cooldown_seconds = 0
|
||||||
|
|
||||||
|
###
|
||||||
|
# Количество успешных SSH-блокировок, которое нужно пропустить после отправки уведомления.
|
||||||
|
# Следующее уведомление будет отправлено после указанного количества новых сообщений о блокировке.
|
||||||
|
# Этот параметр имеет приоритет над ssh_notify_cooldown_seconds.
|
||||||
|
# Если указать 0, ограничение по количеству сообщений отключено.
|
||||||
|
# По умолчанию: 0
|
||||||
|
# ***
|
||||||
|
# Number of successful SSH block messages to skip after sending a notification.
|
||||||
|
# The next notification will be sent after the specified number of new block messages.
|
||||||
|
# This parameter has priority over ssh_notify_cooldown_seconds.
|
||||||
|
# If you specify 0, the message-count limit is disabled.
|
||||||
|
# Default: 0
|
||||||
|
###
|
||||||
|
ssh_notify_every = 0
|
||||||
|
|
||||||
###
|
###
|
||||||
# Можно указать свою группу, чтобы связать с другими правилами.
|
# Можно указать свою группу, чтобы связать с другими правилами.
|
||||||
# По умолчанию: ""
|
# По умолчанию: ""
|
||||||
@@ -101,7 +135,7 @@ ssh_group = ""
|
|||||||
# [[bruteForceProtection.groups]]
|
# [[bruteForceProtection.groups]]
|
||||||
# name = "my_name_group" # Имя группы. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
|
# name = "my_name_group" # Имя группы. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
|
||||||
# message = "Любой текст группы" # Текст уведомления (обязательное поле)
|
# message = "Любой текст группы" # Текст уведомления (обязательное поле)
|
||||||
# rate_limit_reset_period = 86400 # Указываем в секундах, через какое время сбрасывать данные в групе если не было событий. Если указать 0, то не будет сбрасывать.
|
# rate_limit_reset_period = 86400 # Указываем в секундах, через какое время сбрасывать данные в групе, если не было событий. Если указать 0, то не будет сбрасывать.
|
||||||
## block_type = "ip_port" # Указываем тип блокировки: ip, ip_port. Если ничего не укажите, будет указан тип ip.
|
## block_type = "ip_port" # Указываем тип блокировки: ip, ip_port. Если ничего не укажите, будет указан тип ip.
|
||||||
## ports = ["22/tcp", "22/udp"] # Если тип блокировки стоит ip_port, то нужно указать порты, которые будут заблокированы после обнаружения попытки перебора пароля.
|
## ports = ["22/tcp", "22/udp"] # Если тип блокировки стоит ip_port, то нужно указать порты, которые будут заблокированы после обнаружения попытки перебора пароля.
|
||||||
# [[bruteForceProtection.groups.rate_limits]]
|
# [[bruteForceProtection.groups.rate_limits]]
|
||||||
@@ -162,11 +196,14 @@ ssh_group = ""
|
|||||||
# [[bruteForceProtection.rules]]
|
# [[bruteForceProtection.rules]]
|
||||||
# enabled = true # Включает или выключает отслеживания (обязательное поле)
|
# enabled = true # Включает или выключает отслеживания (обязательное поле)
|
||||||
# notify = true # Включает или выключает уведомления (обязательное поле)
|
# notify = true # Включает или выключает уведомления (обязательное поле)
|
||||||
|
# notify_cooldown_seconds = 0 # Минимальный интервал в секундах между уведомлениями об успешной блокировке (не обязательное поле)
|
||||||
|
# notify_every = 0 # Количество успешных блокировок, которое нужно пропустить после отправки уведомления (не обязательное поле)
|
||||||
# name = "my_name_rule" # Имя уведомления. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
|
# name = "my_name_rule" # Имя уведомления. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
|
||||||
# message = "Ваш любой текст для уведомления" # Текст уведомления (обязательное поле)
|
# message = "Ваш любой текст для уведомления" # Текст уведомления (обязательное поле)
|
||||||
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
||||||
# [bruteForceProtection.rules.source]
|
# [bruteForceProtection.rules.source]
|
||||||
# type = "journalctl" # journalctl или file (обязательное поле)
|
# type = "journalctl" # journalctl или file (обязательное поле)
|
||||||
|
# path = "" # Указываем путь к файлу (обязательное поле если type = "file")
|
||||||
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
||||||
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
||||||
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||||
@@ -176,18 +213,28 @@ ssh_group = ""
|
|||||||
# [[bruteForceProtection.rules.patterns.values]]
|
# [[bruteForceProtection.rules.patterns.values]]
|
||||||
# name = "Пользователь"
|
# name = "Пользователь"
|
||||||
# value = 1
|
# 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.
|
# Specify the log settings that need to be monitored to protect against password brute-force attacks.
|
||||||
# Examples:
|
# Examples:
|
||||||
# [[bruteForceProtection.rules]]
|
# [[bruteForceProtection.rules]]
|
||||||
# enabled = true # Enables or disables tracking (required)
|
# enabled = true # Enables or disables tracking (required)
|
||||||
# notify = true # Enables or disables notifications (required)
|
# notify = true # Enables or disables notifications (required)
|
||||||
|
# notify_cooldown_seconds = 0 # Minimum interval in seconds between notifications about successful blocks (optional field)
|
||||||
|
# notify_every = 0 # Number of successful block messages to skip after sending a notification (optional field)
|
||||||
# name = "my_name_rule" # Notification name. Allowed characters are "a-z, A-Z, -, _". The first character must be a letter (required field)
|
# name = "my_name_rule" # Notification name. Allowed characters are "a-z, A-Z, -, _". The first character must be a letter (required field)
|
||||||
# message = "Your any text for notification" # Notification text (required field)
|
# message = "Your any text for notification" # Notification text (required field)
|
||||||
# group = "my_name_group" # You can specify the group name (optional field)
|
# group = "my_name_group" # You can specify the group name (optional field)
|
||||||
# [bruteForceProtection.rules.source]
|
# [bruteForceProtection.rules.source]
|
||||||
# type = "journalctl" # journalctl or file (required)
|
# 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")
|
# field = "systemd_unit" # systemd_unit or syslog_identifier (required if type = "journalctl")
|
||||||
# match = "ssh.service" # Value (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
|
# If field = "systemd_unit", then match must end with: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||||
@@ -199,6 +246,14 @@ ssh_group = ""
|
|||||||
# [[bruteForceProtection.rules.patterns.values]]
|
# [[bruteForceProtection.rules.patterns.values]]
|
||||||
# name = "User"
|
# name = "User"
|
||||||
# value = 1
|
# 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
|
||||||
###
|
###
|
||||||
|
|
||||||
###############################################################################
|
###############################################################################
|
||||||
@@ -391,6 +446,7 @@ notify = true
|
|||||||
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
||||||
# [logAlert.rules.source]
|
# [logAlert.rules.source]
|
||||||
# type = "journalctl" # journalctl или file (обязательное поле)
|
# type = "journalctl" # journalctl или file (обязательное поле)
|
||||||
|
# path = "" # Указываем путь к файлу (обязательное поле если type = "file")
|
||||||
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
||||||
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
||||||
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||||
@@ -403,6 +459,14 @@ notify = true
|
|||||||
# name = "IP"
|
# name = "IP"
|
||||||
# value = 3
|
# value = 3
|
||||||
# type = "ip"
|
# 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.
|
# Specify the log settings to monitor for notifications.
|
||||||
@@ -415,6 +479,7 @@ notify = true
|
|||||||
# group = "my_name_group" # You can specify the group name (optional field)
|
# group = "my_name_group" # You can specify the group name (optional field)
|
||||||
# [logAlert.rules.source]
|
# [logAlert.rules.source]
|
||||||
# type = "journalctl" # journalctl or file (required)
|
# 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")
|
# field = "systemd_unit" # systemd_unit or syslog_identifier (required if type = "journalctl")
|
||||||
# match = "ssh.service" # Value (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
|
# If field = "systemd_unit", then match must end with: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||||
@@ -427,4 +492,12 @@ notify = true
|
|||||||
# name = "IP"
|
# name = "IP"
|
||||||
# value = 3
|
# value = 3
|
||||||
# type = "ip"
|
# 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
|
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)
|
# Spamhaus Don't Route Or Peer Lists (DROP IPv4)
|
||||||
# Details: https://www.spamhaus.org/blocklists/do-not-route-or-peer/
|
# Details: https://www.spamhaus.org/blocklists/do-not-route-or-peer/
|
||||||
@@ -75,7 +107,7 @@ txt_field_ip = 0
|
|||||||
txt_field_ip2 = 1
|
txt_field_ip2 = 1
|
||||||
txt_separator = "\t"
|
txt_separator = "\t"
|
||||||
limit = 0
|
limit = 0
|
||||||
interval = 600
|
interval = 86400
|
||||||
|
|
||||||
###
|
###
|
||||||
# TOR Exit Nodes List
|
# TOR Exit Nodes List
|
||||||
|
|||||||
@@ -1,3 +1,76 @@
|
|||||||
|
#################
|
||||||
|
### Внимание: ###
|
||||||
|
#################
|
||||||
|
#
|
||||||
|
# Для корректной работы необходимо добавить настройки "iptables": false в Docker.
|
||||||
|
# Если не меняли настройки в Docker, то надо создать файл /etc/docker/daemon.json с содержимым:
|
||||||
|
# {
|
||||||
|
# "iptables": false
|
||||||
|
# }
|
||||||
|
# После не забудьте перезагрузить Docker или ОС.
|
||||||
|
#
|
||||||
|
# Так же для корректной работы Docker необходимо добавить соответствующие настройки в файл firewall.toml:
|
||||||
|
# [[ips]]
|
||||||
|
# ips = ["172.16.0.0/12"]
|
||||||
|
# action = "accept"
|
||||||
|
# directions = ["out", "in"]
|
||||||
|
#
|
||||||
|
# Или если вы хотите более строгие правила, то можно отдельно для каждого контейнера настраивать firewall.toml.
|
||||||
|
# Главное помните, что порт, к которому идёт обращение, будет не in, а out.
|
||||||
|
# А если будет взаимодействие между контейнерами, то надо указать in . Например:
|
||||||
|
# # backend
|
||||||
|
# [[ips]]
|
||||||
|
# ips = ["172.16.0.0/12"]
|
||||||
|
# action = "accept"
|
||||||
|
# directions = ["out"]
|
||||||
|
# ports = ["3000", "5000", "9000", "8080"]
|
||||||
|
#
|
||||||
|
# # mysql
|
||||||
|
# [[ips]]
|
||||||
|
# ips = ["172.16.0.0/12"]
|
||||||
|
# action = "accept"
|
||||||
|
# directions = ["in"]
|
||||||
|
# ports = ["3306"]
|
||||||
|
#
|
||||||
|
# ***
|
||||||
|
#
|
||||||
|
################
|
||||||
|
### Attention###
|
||||||
|
################
|
||||||
|
#
|
||||||
|
# For proper operation, you need to add the setting `"iptables": false` to Docker.
|
||||||
|
# If you haven't changed the Docker settings, you need to create the file `/etc/docker/daemon.json`
|
||||||
|
# with the following content:
|
||||||
|
# {
|
||||||
|
# "iptables": false
|
||||||
|
# }
|
||||||
|
# Afterwards, don't forget to restart Docker or the operating system.
|
||||||
|
#
|
||||||
|
# Additionally, for Docker to function correctly, you need to add the appropriate settings to the `firewall.toml` file:
|
||||||
|
# [[ips]]
|
||||||
|
# ips = ["172.16.0.0/12"]
|
||||||
|
# action = "accept"
|
||||||
|
# directions = ["out", "in"]
|
||||||
|
#
|
||||||
|
# Alternatively, if you want stricter rules, you can configure `firewall.toml` separately for each container.
|
||||||
|
# The main thing to remember is that the port being accessed will be `out`, not `in`.
|
||||||
|
# And if there is interaction between containers, you need to specify it in . For example:
|
||||||
|
# # backend
|
||||||
|
# [[ips]]
|
||||||
|
# ips = ["172.16.0.0/12"]
|
||||||
|
# action = "accept"
|
||||||
|
# directions = ["out"]
|
||||||
|
# ports = ["3000", "5000", "9000", "8080"]
|
||||||
|
#
|
||||||
|
# # mysql
|
||||||
|
# [[ips]]
|
||||||
|
# ips = ["172.16.0.0/12"]
|
||||||
|
# action = "accept"
|
||||||
|
# directions = ["in"]
|
||||||
|
# ports = ["3306"]
|
||||||
|
#
|
||||||
|
###
|
||||||
|
|
||||||
###
|
###
|
||||||
# Включает поддержку docker.
|
# Включает поддержку docker.
|
||||||
# По умолчанию: false
|
# По умолчанию: false
|
||||||
|
|||||||
@@ -621,3 +621,46 @@ chain_output_name = "output"
|
|||||||
# Default: "forward"
|
# Default: "forward"
|
||||||
###
|
###
|
||||||
chain_forward_name = "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
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ service = "maxmind"
|
|||||||
|
|
||||||
###
|
###
|
||||||
# ID пользователя.
|
# ID пользователя.
|
||||||
# Чтобы получить, нужно вначале зарегестрироватья тут: https://www.maxmind.com/en/geolite2/signup
|
# Чтобы получить, нужно вначале зарегистрироваться тут: https://www.maxmind.com/en/geolite2/signup
|
||||||
# ***
|
# ***
|
||||||
# User ID.
|
# User ID.
|
||||||
# To receive it, you must first register here: https://www.maxmind.com/en/geolite2/signup
|
# To receive it, you must first register here: https://www.maxmind.com/en/geolite2/signup
|
||||||
|
|||||||
@@ -165,12 +165,12 @@ encoding = "json"
|
|||||||
|
|
||||||
###
|
###
|
||||||
# Список мест для основных логов.
|
# Список мест для основных логов.
|
||||||
# Можно указать несколько путей. Например output_paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
# Можно указать несколько путей. Например paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||||
# Где "stdout" - вывод данных в консоль.
|
# Где "stdout" - вывод данных в консоль.
|
||||||
# По умолчанию: ["/var/log/kor-elf-shield.log"]
|
# По умолчанию: ["/var/log/kor-elf-shield.log"]
|
||||||
# ***
|
# ***
|
||||||
# List of locations for main logs.
|
# 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.
|
# Where "stdout" is the console output.
|
||||||
# Default: ["/var/log/kor-elf-shield.log"]
|
# 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" - вывод данных в консоль.
|
||||||
# По умолчанию: ["stderr"]
|
# По умолчанию: ["stderr"]
|
||||||
# ***
|
# ***
|
||||||
# List of locations for logging errors. For example, the log failed to write some data.
|
# 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.
|
# Where "stderr" is the data output to the console.
|
||||||
# Default: ["stderr"]
|
# Default: ["stderr"]
|
||||||
###
|
###
|
||||||
|
|||||||
@@ -1,35 +1,36 @@
|
|||||||
module git.kor-elf.net/kor-elf-shield/kor-elf-shield
|
module git.kor-elf.net/kor-elf-shield/kor-elf-shield
|
||||||
|
|
||||||
go 1.25
|
go 1.25.0
|
||||||
|
|
||||||
require (
|
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/geoip2 v0.1.2
|
||||||
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.2.1
|
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.2.1
|
||||||
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
github.com/nicksnyder/go-i18n/v2 v2.6.1
|
||||||
github.com/nxadm/tail v1.4.11
|
github.com/nxadm/tail v1.4.11
|
||||||
github.com/spf13/viper v1.21.0
|
github.com/spf13/viper v1.21.0
|
||||||
github.com/urfave/cli/v3 v3.6.2
|
github.com/urfave/cli/v3 v3.10.1
|
||||||
github.com/wneessen/go-mail v0.7.2
|
github.com/wneessen/go-mail v0.8.1
|
||||||
go.etcd.io/bbolt v1.4.3
|
go.etcd.io/bbolt v1.5.0
|
||||||
go.uber.org/zap v1.27.1
|
go.uber.org/zap v1.28.0
|
||||||
golang.org/x/sys v0.41.0
|
golang.org/x/sys v0.47.0
|
||||||
golang.org/x/text v0.34.0
|
golang.org/x/text v0.41.0
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
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/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||||
github.com/oschwald/geoip2-golang/v2 v2.1.0 // indirect
|
github.com/oschwald/geoip2-golang/v2 v2.3.0 // indirect
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1 // indirect
|
github.com/oschwald/maxminddb-golang/v2 v2.5.0 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||||
github.com/spf13/afero v1.15.0 // indirect
|
github.com/spf13/afero v1.15.0 // indirect
|
||||||
github.com/spf13/cast v1.10.0 // indirect
|
github.com/spf13/cast v1.10.0 // indirect
|
||||||
github.com/spf13/pflag v1.0.10 // indirect
|
github.com/spf13/pflag v1.0.10 // indirect
|
||||||
github.com/subosito/gotenv v1.6.0 // indirect
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
go.uber.org/multierr v1.11.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/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // 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 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.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 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/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=
|
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.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
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.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 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
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/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 h1:DjnLhNJu9WHwTrmoiQFvgmyJoczhdnm7LB23UBI2Amo=
|
||||||
github.com/oschwald/geoip2-golang/v2 v2.1.0/go.mod h1:qdVmcPgrTJ4q2eP9tHq/yldMTdp2VMr33uVdFbHBiBc=
|
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 h1:lA8FH0oOrM4u7mLvowq8IT6a3Q/qEnqRzLQn9eH5ojc=
|
||||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1/go.mod h1:PLdx6PR+siSIoXqqy7C7r3SB3KZnhxWr1Dp6g0Hacl8=
|
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 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.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 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
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=
|
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/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 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
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 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
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 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
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 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
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 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
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 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
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 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
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 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
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.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 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
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 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
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 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 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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.CmdStart(),
|
||||||
daemon.CmdStop(),
|
daemon.CmdStop(),
|
||||||
daemon.CmdStatus(),
|
daemon.CmdStatus(),
|
||||||
|
daemon.CmdConfig(),
|
||||||
daemon.CmdReopenLogger(),
|
daemon.CmdReopenLogger(),
|
||||||
daemon.CmdNotifications(),
|
daemon.CmdNotifications(),
|
||||||
daemon.CmdBlock(),
|
daemon.CmdBlock(),
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import (
|
|||||||
"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/pkg/regular_expression"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewBruteForceProtectionSSH(isNotify bool, group *brute_force_protection.Group) ([]*Source, error) {
|
func NewBruteForceProtectionSSH(isNotify bool, notifyCooldown int, notifyEvery int, group *brute_force_protection.Group) ([]*Source, error) {
|
||||||
var sources []*Source
|
var sources []*Source
|
||||||
|
|
||||||
journal, err := NewSourceJournal(JournalFieldSystemdUnit, "ssh.service")
|
journal, err := NewSourceJournal(JournalFieldSystemdUnit, "ssh.service")
|
||||||
@@ -20,12 +20,26 @@ func NewBruteForceProtectionSSH(isNotify bool, group *brute_force_protection.Gro
|
|||||||
Type: SourceTypeJournal,
|
Type: SourceTypeJournal,
|
||||||
Journal: journal,
|
Journal: journal,
|
||||||
BruteForceProtectionRule: &brute_force_protection.Rule{
|
BruteForceProtectionRule: &brute_force_protection.Rule{
|
||||||
Name: "_ssh",
|
Name: "_ssh",
|
||||||
Message: i18n.Lang.T("alert.bruteForceProtection.ssh.message"),
|
Message: i18n.Lang.T("alert.bruteForceProtection.ssh.message"),
|
||||||
IsNotification: isNotify,
|
|
||||||
|
IsNotification: isNotify,
|
||||||
|
NotificationCooldown: uint32(notifyCooldown),
|
||||||
|
NotificationEvery: uint32(notifyEvery),
|
||||||
|
|
||||||
Patterns: []brute_force_protection.RegexPattern{
|
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{
|
Values: []brute_force_protection.PatternValue{
|
||||||
{
|
{
|
||||||
Name: i18n.Lang.T("user"),
|
Name: i18n.Lang.T("user"),
|
||||||
|
|||||||
@@ -1,19 +1,27 @@
|
|||||||
package brute_force_protection
|
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 {
|
type Rule struct {
|
||||||
Name string
|
Name string
|
||||||
Message string
|
Message string
|
||||||
IsNotification bool
|
|
||||||
Patterns []RegexPattern
|
IsNotification bool
|
||||||
Group *Group
|
NotificationCooldown uint32
|
||||||
|
NotificationEvery uint32
|
||||||
|
|
||||||
|
Patterns []RegexPattern
|
||||||
|
Group *Group
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegexPattern struct {
|
type RegexPattern struct {
|
||||||
Regexp *regular_expression.LazyRegexp
|
Regexp *regular_expression.LazyRegexp
|
||||||
Values []PatternValue
|
Values []PatternValue
|
||||||
IP uint8
|
IP uint8
|
||||||
|
Partition *partition.PatternPartition
|
||||||
}
|
}
|
||||||
|
|
||||||
type RateLimit struct {
|
type RateLimit struct {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"unicode"
|
"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/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/pkg/regular_expression"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
||||||
)
|
)
|
||||||
@@ -112,8 +113,9 @@ type AlertRule struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AlertRegexPattern struct {
|
type AlertRegexPattern struct {
|
||||||
Regexp *regular_expression.LazyRegexp
|
Regexp *regular_expression.LazyRegexp
|
||||||
Values []PatternValue
|
Values []PatternValue
|
||||||
|
Partition *partition.PatternPartition
|
||||||
}
|
}
|
||||||
|
|
||||||
type PatternValue struct {
|
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
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
analysisServices "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis"
|
analysisServices "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/alert_group"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/alert_group"
|
||||||
|
analysisBruteForceProtection "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/brute_force_protection"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/brute_force_protection_group"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/brute_force_protection_group"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db"
|
||||||
"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/geoip"
|
||||||
@@ -33,6 +34,7 @@ func NewAnalysis(
|
|||||||
) Analysis {
|
) Analysis {
|
||||||
alertGroupService := alert_group.NewGroup(repositories.AlertGroup(), logger)
|
alertGroupService := alert_group.NewGroup(repositories.AlertGroup(), logger)
|
||||||
bruteForceProtectionGroupService := brute_force_protection_group.NewGroup(repositories.BruteForceProtectionGroup(), logger)
|
bruteForceProtectionGroupService := brute_force_protection_group.NewGroup(repositories.BruteForceProtectionGroup(), logger)
|
||||||
|
notificationPolicy := analysisBruteForceProtection.NewNotificationPolicy(repositories.BruteForceProtectionNotifyPolicy(), logger)
|
||||||
|
|
||||||
return &analysis{
|
return &analysis{
|
||||||
alertService: analysisServices.NewAlert(rulesIndex, alertGroupService, logger, notify, ipInfo),
|
alertService: analysisServices.NewAlert(rulesIndex, alertGroupService, logger, notify, ipInfo),
|
||||||
@@ -40,6 +42,7 @@ func NewAnalysis(
|
|||||||
rulesIndex,
|
rulesIndex,
|
||||||
bruteForceProtectionGroupService,
|
bruteForceProtectionGroupService,
|
||||||
blockService,
|
blockService,
|
||||||
|
notificationPolicy,
|
||||||
logger,
|
logger,
|
||||||
notify,
|
notify,
|
||||||
ipInfo,
|
ipInfo,
|
||||||
|
|||||||
@@ -26,15 +26,17 @@ type alert struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type alertAnalyzeRuleReturn struct {
|
type alertAnalyzeRuleReturn struct {
|
||||||
found bool
|
found bool
|
||||||
fields []*regexField
|
fields []*regexField
|
||||||
|
partition *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type alertNotify struct {
|
type alertNotify struct {
|
||||||
rule *config.AlertRule
|
rule *config.AlertRule
|
||||||
messages []string
|
messages []string
|
||||||
time time.Time
|
alertNumber uint64
|
||||||
fields []*regexField
|
time time.Time
|
||||||
|
fields []*regexField
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAlert(
|
func NewAlert(
|
||||||
@@ -64,9 +66,10 @@ func (a *alert) Analyze(entry *Entry) {
|
|||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
groupName := ""
|
groupName := ""
|
||||||
|
alertNumber := uint64(0)
|
||||||
messages := []string{}
|
messages := []string{}
|
||||||
if rule.Group != nil {
|
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 {
|
if err != nil {
|
||||||
a.logger.Error(fmt.Sprintf("Failed to analyze alert group: %s", err))
|
a.logger.Error(fmt.Sprintf("Failed to analyze alert group: %s", err))
|
||||||
continue
|
continue
|
||||||
@@ -79,15 +82,17 @@ func (a *alert) Analyze(entry *Entry) {
|
|||||||
for _, lastLog := range alertGroup.LastLogs {
|
for _, lastLog := range alertGroup.LastLogs {
|
||||||
messages = append(messages, lastLog)
|
messages = append(messages, lastLog)
|
||||||
}
|
}
|
||||||
|
alertNumber = alertGroup.AlertNumber
|
||||||
} else {
|
} else {
|
||||||
messages = append(messages, entry.Message)
|
messages = append(messages, entry.Message)
|
||||||
}
|
}
|
||||||
a.logger.Info(fmt.Sprintf("Alert detected (%s) (group:%s): %s", rule.Name, groupName, entry.Message))
|
a.logger.Info(fmt.Sprintf("Alert detected (%s) (group:%s): %s", rule.Name, groupName, entry.Message))
|
||||||
a.sendNotify(&alertNotify{
|
a.sendNotify(&alertNotify{
|
||||||
rule: rule,
|
rule: rule,
|
||||||
messages: messages,
|
messages: messages,
|
||||||
time: entry.Time,
|
alertNumber: alertNumber,
|
||||||
fields: result.fields,
|
time: entry.Time,
|
||||||
|
fields: result.fields,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -98,8 +103,9 @@ func (a *alert) ClearDBData() error {
|
|||||||
|
|
||||||
func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyzeRuleReturn {
|
func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyzeRuleReturn {
|
||||||
result := alertAnalyzeRuleReturn{
|
result := alertAnalyzeRuleReturn{
|
||||||
found: false,
|
found: false,
|
||||||
fields: []*regexField{},
|
fields: []*regexField{},
|
||||||
|
partition: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pattern := range rule.Patterns {
|
for _, pattern := range rule.Patterns {
|
||||||
@@ -112,6 +118,27 @@ func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyze
|
|||||||
idx := re.FindStringSubmatchIndex(message)
|
idx := re.FindStringSubmatchIndex(message)
|
||||||
|
|
||||||
if idx != nil {
|
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 {
|
for _, value := range pattern.Values {
|
||||||
start, end, err := getValueStartEndByRegexIndex(int(value.Value), idx)
|
start, end, err := getValueStartEndByRegexIndex(int(value.Value), idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -122,6 +149,9 @@ func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyze
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(pattern.Values) != len(result.fields) {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -153,6 +183,7 @@ func (a *alert) sendNotify(notify *alertNotify) {
|
|||||||
text += i18n.Lang.T("time", map[string]any{
|
text += i18n.Lang.T("time", map[string]any{
|
||||||
"Time": notify.time,
|
"Time": notify.time,
|
||||||
}) + "\n"
|
}) + "\n"
|
||||||
|
|
||||||
for _, field := range notify.fields {
|
for _, field := range notify.fields {
|
||||||
v := field.value
|
v := field.value
|
||||||
if field.typeValue == config.PatternValueIP {
|
if field.typeValue == config.PatternValueIP {
|
||||||
@@ -164,6 +195,11 @@ func (a *alert) sendNotify(notify *alertNotify) {
|
|||||||
}
|
}
|
||||||
text += fmt.Sprintf("%s: %s\n", field.name, v)
|
text += fmt.Sprintf("%s: %s\n", field.name, v)
|
||||||
}
|
}
|
||||||
|
if notify.alertNumber > 0 {
|
||||||
|
text += i18n.Lang.T("alertNumber", map[string]any{
|
||||||
|
"Count": notify.alertNumber,
|
||||||
|
}) + "\n"
|
||||||
|
}
|
||||||
text += "\n" + i18n.Lang.T("log", map[string]any{
|
text += "\n" + i18n.Lang.T("log", map[string]any{
|
||||||
"Count": len(notify.messages),
|
"Count": len(notify.messages),
|
||||||
}) + "\n"
|
}) + "\n"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Group interface {
|
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
|
ClearDBData() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -22,8 +22,9 @@ type group struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AnalysisResult struct {
|
type AnalysisResult struct {
|
||||||
Alerted bool
|
Alerted bool
|
||||||
LastLogs []string
|
AlertNumber uint64
|
||||||
|
LastLogs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup(alertGroupRepository repository.AlertGroupRepository, logger log.Logger) Group {
|
func NewGroup(alertGroupRepository repository.AlertGroupRepository, logger log.Logger) Group {
|
||||||
@@ -33,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{
|
analysisResult := AnalysisResult{
|
||||||
Alerted: false,
|
Alerted: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
g.logger.Debug(fmt.Sprintf("Analyzing alert group %s", alertGroup.Name))
|
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)
|
rateLimit, err := alertGroup.RateLimit(entityAlertGroup.CurrentLevelTriggerCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entityAlertGroup, err
|
return entityAlertGroup, err
|
||||||
@@ -101,6 +102,8 @@ func (g *group) analysisResult(rateLimit config.RateLimit, eventTime time.Time,
|
|||||||
entityAlertGroup.CurrentLevelTriggerCount++
|
entityAlertGroup.CurrentLevelTriggerCount++
|
||||||
entityAlertGroup.TriggerCount = 0
|
entityAlertGroup.TriggerCount = 0
|
||||||
entityAlertGroup.LastLogs = []string{}
|
entityAlertGroup.LastLogs = []string{}
|
||||||
|
|
||||||
|
analysisResult.AlertNumber = entityAlertGroup.CurrentLevelTriggerCount
|
||||||
} else {
|
} else {
|
||||||
g.logger.Debug(fmt.Sprintf("Alert not reached rate limit"))
|
g.logger.Debug(fmt.Sprintf("Alert not reached rate limit"))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
|
|
||||||
"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/brute_force_protection"
|
||||||
|
analysisBruteForceProtection "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/brute_force_protection"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/brute_force_protection_group"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log/analysis/brute_force_protection_group"
|
||||||
"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/blocking"
|
||||||
"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/firewall/types"
|
||||||
@@ -23,46 +24,51 @@ type BruteForceProtection interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type bruteForceProtection struct {
|
type bruteForceProtection struct {
|
||||||
rulesIndex *RulesIndex
|
rulesIndex *RulesIndex
|
||||||
groupService brute_force_protection_group.Group
|
groupService brute_force_protection_group.Group
|
||||||
blockService brute_force_protection_group.BlockService
|
blockService brute_force_protection_group.BlockService
|
||||||
logger log.Logger
|
notificationPolicy analysisBruteForceProtection.NotificationPolicy
|
||||||
notify notifications.Notifications
|
logger log.Logger
|
||||||
ipInfo geoip.Info
|
notify notifications.Notifications
|
||||||
|
ipInfo geoip.Info
|
||||||
}
|
}
|
||||||
|
|
||||||
type bruteForceProtectionAnalyzeRuleReturn struct {
|
type bruteForceProtectionAnalyzeRuleReturn struct {
|
||||||
found bool
|
found bool
|
||||||
fields []*regexField
|
fields []*regexField
|
||||||
ip net.IP
|
ip net.IP
|
||||||
|
partition *string
|
||||||
}
|
}
|
||||||
|
|
||||||
type bruteForceProtectionNotify struct {
|
type bruteForceProtectionNotify struct {
|
||||||
rule *brute_force_protection.Rule
|
rule *brute_force_protection.Rule
|
||||||
messages []string
|
messages []string
|
||||||
ip net.IP
|
blockIPCount uint64
|
||||||
ports []types.L4Port
|
ip net.IP
|
||||||
time time.Time
|
ports []types.L4Port
|
||||||
fields []*regexField
|
time time.Time
|
||||||
blockSec uint32
|
fields []*regexField
|
||||||
err error
|
blockSec uint32
|
||||||
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBruteForceProtection(
|
func NewBruteForceProtection(
|
||||||
rulesIndex *RulesIndex,
|
rulesIndex *RulesIndex,
|
||||||
groupService brute_force_protection_group.Group,
|
groupService brute_force_protection_group.Group,
|
||||||
blockService brute_force_protection_group.BlockService,
|
blockService brute_force_protection_group.BlockService,
|
||||||
|
notificationPolicy analysisBruteForceProtection.NotificationPolicy,
|
||||||
logger log.Logger,
|
logger log.Logger,
|
||||||
notify notifications.Notifications,
|
notify notifications.Notifications,
|
||||||
ipInfo geoip.Info,
|
ipInfo geoip.Info,
|
||||||
) BruteForceProtection {
|
) BruteForceProtection {
|
||||||
return &bruteForceProtection{
|
return &bruteForceProtection{
|
||||||
rulesIndex: rulesIndex,
|
rulesIndex: rulesIndex,
|
||||||
groupService: groupService,
|
groupService: groupService,
|
||||||
blockService: blockService,
|
blockService: blockService,
|
||||||
logger: logger,
|
notificationPolicy: notificationPolicy,
|
||||||
notify: notify,
|
logger: logger,
|
||||||
ipInfo: ipInfo,
|
notify: notify,
|
||||||
|
ipInfo: ipInfo,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -83,7 +89,7 @@ func (p *bruteForceProtection) Analyze(entry *Entry) {
|
|||||||
continue
|
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 {
|
if err != nil {
|
||||||
p.logger.Error(fmt.Sprintf("Failed to analyze brute force protection group: %s", err))
|
p.logger.Error(fmt.Sprintf("Failed to analyze brute force protection group: %s", err))
|
||||||
continue
|
continue
|
||||||
@@ -122,26 +128,28 @@ func (p *bruteForceProtection) handleBlockIP(
|
|||||||
if isBanned == false {
|
if isBanned == false {
|
||||||
p.logger.Info(fmt.Sprintf("IP %s are not blocked (%s) (group:%s): %s. Err: %s", result.ip, rule.Name, rule.Group.Name, entry.Message, err.Error()))
|
p.logger.Info(fmt.Sprintf("IP %s are not blocked (%s) (group:%s): %s. Err: %s", result.ip, rule.Name, rule.Group.Name, entry.Message, err.Error()))
|
||||||
p.sendNotifyError(&bruteForceProtectionNotify{
|
p.sendNotifyError(&bruteForceProtectionNotify{
|
||||||
rule: rule,
|
rule: rule,
|
||||||
ip: result.ip,
|
ip: result.ip,
|
||||||
messages: groupResult.LastLogs,
|
messages: groupResult.LastLogs,
|
||||||
time: entry.Time,
|
blockIPCount: groupResult.BlockIPCount,
|
||||||
fields: result.fields,
|
time: entry.Time,
|
||||||
blockSec: groupResult.BlockSec,
|
fields: result.fields,
|
||||||
err: err,
|
blockSec: groupResult.BlockSec,
|
||||||
|
err: err,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.logger.Info(fmt.Sprintf("Block IP %s detected (%s) (group:%s): %s", result.ip, rule.Name, rule.Group.Name, entry.Message))
|
p.logger.Info(fmt.Sprintf("Block IP %s detected (%s) (group:%s): %s", result.ip, rule.Name, rule.Group.Name, entry.Message))
|
||||||
p.sendNotifySuccess(&bruteForceProtectionNotify{
|
p.sendNotifySuccess(&bruteForceProtectionNotify{
|
||||||
rule: rule,
|
rule: rule,
|
||||||
ip: result.ip,
|
ip: result.ip,
|
||||||
messages: groupResult.LastLogs,
|
messages: groupResult.LastLogs,
|
||||||
time: entry.Time,
|
blockIPCount: groupResult.BlockIPCount,
|
||||||
fields: result.fields,
|
time: entry.Time,
|
||||||
blockSec: groupResult.BlockSec,
|
fields: result.fields,
|
||||||
err: err,
|
blockSec: groupResult.BlockSec,
|
||||||
|
err: err,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,36 +170,39 @@ func (p *bruteForceProtection) handleBlockIPWithPorts(
|
|||||||
if isBanned == false {
|
if isBanned == false {
|
||||||
p.logger.Info(fmt.Sprintf("IP %s are not blocked (%s) (group:%s): %s. Err: %s", result.ip, rule.Name, rule.Group.Name, entry.Message, err.Error()))
|
p.logger.Info(fmt.Sprintf("IP %s are not blocked (%s) (group:%s): %s. Err: %s", result.ip, rule.Name, rule.Group.Name, entry.Message, err.Error()))
|
||||||
p.sendNotifyError(&bruteForceProtectionNotify{
|
p.sendNotifyError(&bruteForceProtectionNotify{
|
||||||
rule: rule,
|
rule: rule,
|
||||||
ip: result.ip,
|
ip: result.ip,
|
||||||
ports: l4Ports,
|
ports: l4Ports,
|
||||||
messages: groupResult.LastLogs,
|
messages: groupResult.LastLogs,
|
||||||
time: entry.Time,
|
blockIPCount: groupResult.BlockIPCount,
|
||||||
fields: result.fields,
|
time: entry.Time,
|
||||||
blockSec: groupResult.BlockSec,
|
fields: result.fields,
|
||||||
err: err,
|
blockSec: groupResult.BlockSec,
|
||||||
|
err: err,
|
||||||
})
|
})
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
p.logger.Info(fmt.Sprintf("Block IP %s detected (%s) (group:%s): %s", result.ip, rule.Name, rule.Group.Name, entry.Message))
|
p.logger.Info(fmt.Sprintf("Block IP %s detected (%s) (group:%s): %s", result.ip, rule.Name, rule.Group.Name, entry.Message))
|
||||||
p.sendNotifySuccess(&bruteForceProtectionNotify{
|
p.sendNotifySuccess(&bruteForceProtectionNotify{
|
||||||
rule: rule,
|
rule: rule,
|
||||||
ip: result.ip,
|
ip: result.ip,
|
||||||
ports: l4Ports,
|
ports: l4Ports,
|
||||||
messages: groupResult.LastLogs,
|
messages: groupResult.LastLogs,
|
||||||
time: entry.Time,
|
blockIPCount: groupResult.BlockIPCount,
|
||||||
fields: result.fields,
|
time: entry.Time,
|
||||||
blockSec: groupResult.BlockSec,
|
fields: result.fields,
|
||||||
err: err,
|
blockSec: groupResult.BlockSec,
|
||||||
|
err: err,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, message string) bruteForceProtectionAnalyzeRuleReturn {
|
func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, message string) bruteForceProtectionAnalyzeRuleReturn {
|
||||||
result := bruteForceProtectionAnalyzeRuleReturn{
|
result := bruteForceProtectionAnalyzeRuleReturn{
|
||||||
found: false,
|
found: false,
|
||||||
fields: []*regexField{},
|
fields: []*regexField{},
|
||||||
ip: nil,
|
ip: nil,
|
||||||
|
partition: nil,
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, pattern := range rule.Patterns {
|
for _, pattern := range rule.Patterns {
|
||||||
@@ -207,14 +218,35 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
|||||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.IP), idx)
|
start, end, err := getValueStartEndByRegexIndex(int(pattern.IP), idx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.logger.Error(fmt.Sprintf("Failed to get ip value: %s", err))
|
p.logger.Error(fmt.Sprintf("Failed to get ip value: %s", err))
|
||||||
return result
|
continue
|
||||||
}
|
}
|
||||||
ipText := message[start:end]
|
ipText := message[start:end]
|
||||||
result.ip = net.ParseIP(ipText)
|
ip := net.ParseIP(ipText)
|
||||||
if result.ip == nil {
|
if ip == nil {
|
||||||
p.logger.Error(fmt.Sprintf("Failed to parse ip: %s", ipText))
|
p.logger.Error(fmt.Sprintf("Failed to parse ip: %s", ipText))
|
||||||
return bruteForceProtectionAnalyzeRuleReturn{
|
continue
|
||||||
found: false,
|
}
|
||||||
|
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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -228,6 +260,10 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(pattern.Values) != len(result.fields) {
|
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
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -240,7 +276,7 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (p *bruteForceProtection) sendNotifySuccess(notify *bruteForceProtectionNotify) {
|
func (p *bruteForceProtection) sendNotifySuccess(notify *bruteForceProtectionNotify) {
|
||||||
if !notify.rule.IsNotification {
|
if !p.notificationPolicy.IsNotify(notify.rule) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -310,6 +346,9 @@ func (p *bruteForceProtection) sendNotify(subject string, notify *bruteForceProt
|
|||||||
for _, field := range notify.fields {
|
for _, field := range notify.fields {
|
||||||
text += fmt.Sprintf("%s: %s\n", field.name, field.value)
|
text += fmt.Sprintf("%s: %s\n", field.name, field.value)
|
||||||
}
|
}
|
||||||
|
text += i18n.Lang.T("blockIPCount", map[string]any{
|
||||||
|
"Count": notify.blockIPCount,
|
||||||
|
}) + "\n"
|
||||||
text += "\n" + i18n.Lang.T("log", map[string]any{
|
text += "\n" + i18n.Lang.T("log", map[string]any{
|
||||||
"Count": len(notify.messages),
|
"Count": len(notify.messages),
|
||||||
}) + "\n"
|
}) + "\n"
|
||||||
|
|||||||
@@ -0,0 +1,82 @@
|
|||||||
|
package brute_force_protection
|
||||||
|
|
||||||
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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/db/entity"
|
||||||
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/repository"
|
||||||
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||||
|
)
|
||||||
|
|
||||||
|
type NotificationPolicy interface {
|
||||||
|
IsNotify(rule *brute_force_protection.Rule) bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type notificationPolicy struct {
|
||||||
|
notifyPolicyRepository repository.BruteForceProtectionNotifyPolicyRepository
|
||||||
|
logger log.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotificationPolicy(notifyPolicyRepository repository.BruteForceProtectionNotifyPolicyRepository, logger log.Logger) NotificationPolicy {
|
||||||
|
return ¬ificationPolicy{
|
||||||
|
notifyPolicyRepository: notifyPolicyRepository,
|
||||||
|
logger: logger,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *notificationPolicy) IsNotify(rule *brute_force_protection.Rule) bool {
|
||||||
|
if !rule.IsNotification {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if rule.NotificationCooldown == 0 && rule.NotificationEvery == 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
isNotify := false
|
||||||
|
|
||||||
|
err := s.notifyPolicyRepository.Update(rule.Name, func(notify *entity.BruteForceProtectionNotifyPolicy) (*entity.BruteForceProtectionNotifyPolicy, error) {
|
||||||
|
if isEvery(rule, notify) {
|
||||||
|
isNotify = true
|
||||||
|
return resetNotifyPolicy(notify), nil
|
||||||
|
}
|
||||||
|
notify.Every++
|
||||||
|
|
||||||
|
if rule.NotificationCooldown > 0 {
|
||||||
|
lastNotifyAt := time.Unix(notify.CooldownTime, 0)
|
||||||
|
cooldown := time.Duration(rule.NotificationCooldown) * time.Second
|
||||||
|
if time.Since(lastNotifyAt) >= cooldown {
|
||||||
|
isNotify = true
|
||||||
|
return resetNotifyPolicy(notify), nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return notify, nil
|
||||||
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
s.logger.Error(err.Error())
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return isNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
func isEvery(rule *brute_force_protection.Rule, entity *entity.BruteForceProtectionNotifyPolicy) bool {
|
||||||
|
if rule.NotificationEvery == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity.Every >= rule.NotificationEvery {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetNotifyPolicy(entity *entity.BruteForceProtectionNotifyPolicy) *entity.BruteForceProtectionNotifyPolicy {
|
||||||
|
entity.Every = 0
|
||||||
|
entity.CooldownTime = time.Now().Unix()
|
||||||
|
return entity
|
||||||
|
}
|
||||||
@@ -13,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Group interface {
|
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
|
ClearDBData() error
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -23,10 +23,11 @@ type group struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type AnalysisResult struct {
|
type AnalysisResult struct {
|
||||||
Block bool
|
Block bool
|
||||||
BlockSec uint32
|
BlockSec uint32
|
||||||
BlockConfig brute_force_protection.Block
|
BlockConfig brute_force_protection.Block
|
||||||
LastLogs []string
|
BlockIPCount uint64
|
||||||
|
LastLogs []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewGroup(groupRepository repository.BruteForceProtectionGroupRepository, logger log.Logger) Group {
|
func NewGroup(groupRepository repository.BruteForceProtectionGroupRepository, logger log.Logger) Group {
|
||||||
@@ -36,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{
|
analysisResult := AnalysisResult{
|
||||||
Block: false,
|
Block: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
g.logger.Debug(fmt.Sprintf("Analyzing brute force protection group %s IP %s", group.Name, ip.String()))
|
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)
|
rateLimit, err := group.RateLimit(entityGroup.CurrentLevelTriggerCount)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return entityGroup, err
|
return entityGroup, err
|
||||||
@@ -106,6 +107,8 @@ func (g *group) analysisResult(rateLimit brute_force_protection.RateLimit, event
|
|||||||
entityGroup.CurrentLevelTriggerCount++
|
entityGroup.CurrentLevelTriggerCount++
|
||||||
entityGroup.TriggerCount = 0
|
entityGroup.TriggerCount = 0
|
||||||
entityGroup.LastLogs = []string{}
|
entityGroup.LastLogs = []string{}
|
||||||
|
|
||||||
|
analysisResult.BlockIPCount = entityGroup.CurrentLevelTriggerCount
|
||||||
} else {
|
} else {
|
||||||
g.logger.Debug(fmt.Sprintf("Brute force protection not reached rate limit"))
|
g.logger.Debug(fmt.Sprintf("Brute force protection not reached rate limit"))
|
||||||
}
|
}
|
||||||
|
|||||||
+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/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"
|
||||||
"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/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/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/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/info"
|
||||||
@@ -33,16 +34,17 @@ type Daemon interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type daemon struct {
|
type daemon struct {
|
||||||
info info.Info
|
info info.Info
|
||||||
pidFile pidfile.PidFile
|
pidFile pidfile.PidFile
|
||||||
socket socket.Socket
|
socket socket.Socket
|
||||||
logger log.Logger
|
logger log.Logger
|
||||||
firewall firewall.API
|
firewall firewall.API
|
||||||
notifications notifications.Notifications
|
firewallRulesGuard guard.RulesGuard
|
||||||
analyzer analyzer.Analyzer
|
notifications notifications.Notifications
|
||||||
docker docker_monitor.Docker
|
analyzer analyzer.Analyzer
|
||||||
blocklist blocklist.Blocklist
|
docker docker_monitor.Docker
|
||||||
geoIPService geoip.GeoIP
|
blocklist blocklist.Blocklist
|
||||||
|
geoIPService geoip.GeoIP
|
||||||
|
|
||||||
stopCh chan struct{}
|
stopCh chan struct{}
|
||||||
}
|
}
|
||||||
@@ -59,6 +61,10 @@ func (d *daemon) Run(ctx context.Context, isTesting bool, testingInterval uint16
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
d.firewall.SavesRules()
|
d.firewall.SavesRules()
|
||||||
|
d.firewallRulesGuard.Run(d.info, ctx)
|
||||||
|
defer func() {
|
||||||
|
_ = d.firewallRulesGuard.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
if err := d.pidFile.Create(); err != nil {
|
if err := d.pidFile.Create(); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -162,8 +168,15 @@ func (d *daemon) socketCommand(command string, args map[string]string, socket so
|
|||||||
var m runtime.MemStats
|
var m runtime.MemStats
|
||||||
runtime.ReadMemStats(&m)
|
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(
|
text := fmt.Sprintf(
|
||||||
"ok\n\n***\n"+
|
"ok\n\n***\n"+
|
||||||
|
"NFTables: %s\n"+
|
||||||
"Version: %s\n"+
|
"Version: %s\n"+
|
||||||
"BuiltWith: %s\n"+
|
"BuiltWith: %s\n"+
|
||||||
"Uptime: %s\n"+
|
"Uptime: %s\n"+
|
||||||
@@ -174,6 +187,7 @@ func (d *daemon) socketCommand(command string, args map[string]string, socket so
|
|||||||
"HeapSys: %s\n"+
|
"HeapSys: %s\n"+
|
||||||
"NumGC: %d\n"+
|
"NumGC: %d\n"+
|
||||||
"***\n",
|
"***\n",
|
||||||
|
nftables,
|
||||||
d.info.Version(),
|
d.info.Version(),
|
||||||
d.info.BuiltWith(),
|
d.info.BuiltWith(),
|
||||||
format.HumanDuration(d.info.Uptime()),
|
format.HumanDuration(d.info.Uptime()),
|
||||||
|
|||||||
+19
-12
@@ -18,6 +18,7 @@ type Repositories interface {
|
|||||||
NotificationsQueue() repository.NotificationsQueueRepository
|
NotificationsQueue() repository.NotificationsQueueRepository
|
||||||
AlertGroup() repository.AlertGroupRepository
|
AlertGroup() repository.AlertGroupRepository
|
||||||
BruteForceProtectionGroup() repository.BruteForceProtectionGroupRepository
|
BruteForceProtectionGroup() repository.BruteForceProtectionGroupRepository
|
||||||
|
BruteForceProtectionNotifyPolicy() repository.BruteForceProtectionNotifyPolicyRepository
|
||||||
Blocking() repository.BlockingRepository
|
Blocking() repository.BlockingRepository
|
||||||
Blocklist() repository.BlocklistRepository
|
Blocklist() repository.BlocklistRepository
|
||||||
Metadata() repository.MetadataRepository
|
Metadata() repository.MetadataRepository
|
||||||
@@ -26,12 +27,13 @@ type Repositories interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type repositories struct {
|
type repositories struct {
|
||||||
notificationsQueue repository.NotificationsQueueRepository
|
notificationsQueue repository.NotificationsQueueRepository
|
||||||
alertGroup repository.AlertGroupRepository
|
alertGroup repository.AlertGroupRepository
|
||||||
bruteForceProtectionGroup repository.BruteForceProtectionGroupRepository
|
bruteForceProtectionGroup repository.BruteForceProtectionGroupRepository
|
||||||
blocking repository.BlockingRepository
|
bruteForceProtectionNotifyPolicy repository.BruteForceProtectionNotifyPolicyRepository
|
||||||
blocklist repository.BlocklistRepository
|
blocking repository.BlockingRepository
|
||||||
metadata repository.MetadataRepository
|
blocklist repository.BlocklistRepository
|
||||||
|
metadata repository.MetadataRepository
|
||||||
|
|
||||||
db []*bbolt.DB
|
db []*bbolt.DB
|
||||||
}
|
}
|
||||||
@@ -57,12 +59,13 @@ func New(dataDir string) (Repositories, error) {
|
|||||||
securityDB, err := bbolt.Open(dataDir+securityDB, 0600, &bbolt.Options{Timeout: 3 * time.Second})
|
securityDB, err := bbolt.Open(dataDir+securityDB, 0600, &bbolt.Options{Timeout: 3 * time.Second})
|
||||||
|
|
||||||
return &repositories{
|
return &repositories{
|
||||||
notificationsQueue: repository.NewNotificationsQueueRepository(appDB),
|
notificationsQueue: repository.NewNotificationsQueueRepository(appDB),
|
||||||
alertGroup: repository.NewAlertGroupRepository(appDB),
|
alertGroup: repository.NewAlertGroupRepository(appDB),
|
||||||
bruteForceProtectionGroup: repository.NewBruteForceProtectionGroupRepository(securityDB),
|
bruteForceProtectionGroup: repository.NewBruteForceProtectionGroupRepository(securityDB),
|
||||||
blocking: repository.NewBlockingRepository(securityDB),
|
bruteForceProtectionNotifyPolicy: repository.NewBruteForceProtectionNotifyPolicyRepository(securityDB),
|
||||||
blocklist: repository.NewBlocklistRepository(securityDB),
|
blocking: repository.NewBlockingRepository(securityDB),
|
||||||
metadata: repository.NewMetadataRepository(appDB),
|
blocklist: repository.NewBlocklistRepository(securityDB),
|
||||||
|
metadata: repository.NewMetadataRepository(appDB),
|
||||||
|
|
||||||
db: []*bbolt.DB{appDB, securityDB},
|
db: []*bbolt.DB{appDB, securityDB},
|
||||||
}, nil
|
}, nil
|
||||||
@@ -80,6 +83,10 @@ func (r *repositories) BruteForceProtectionGroup() repository.BruteForceProtecti
|
|||||||
return r.bruteForceProtectionGroup
|
return r.bruteForceProtectionGroup
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *repositories) BruteForceProtectionNotifyPolicy() repository.BruteForceProtectionNotifyPolicyRepository {
|
||||||
|
return r.bruteForceProtectionNotifyPolicy
|
||||||
|
}
|
||||||
|
|
||||||
func (r *repositories) Blocking() repository.BlockingRepository {
|
func (r *repositories) Blocking() repository.BlockingRepository {
|
||||||
return r.blocking
|
return r.blocking
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package entity
|
||||||
|
|
||||||
|
type BruteForceProtectionNotifyPolicy struct {
|
||||||
|
CooldownTime int64
|
||||||
|
Every uint32
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package repository
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -11,7 +12,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type AlertGroupRepository interface {
|
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
|
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 := &entity.AlertGroup{}
|
||||||
entityAlertGroup.Reset()
|
entityAlertGroup.Reset()
|
||||||
|
|
||||||
@@ -36,7 +37,10 @@ func (r *alertGroupRepository) Update(name string, f func(*entity.AlertGroup) (*
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
key := []byte(name)
|
key, err := keyGroup(name, partition)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
group := b.Get(key)
|
group := b.Get(key)
|
||||||
if group != nil {
|
if group != nil {
|
||||||
@@ -70,3 +74,21 @@ func (r *alertGroupRepository) Clear() error {
|
|||||||
return err
|
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
|
package repository
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/sha256"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@@ -12,7 +13,7 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BruteForceProtectionGroupRepository interface {
|
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
|
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 := &entity.BruteForceProtectionGroup{}
|
||||||
entityGroup.Reset()
|
entityGroup.Reset()
|
||||||
|
|
||||||
@@ -37,7 +38,7 @@ func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, f f
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
key, err := keyGroupIP(name, ip)
|
key, err := keyGroupIP(name, ip, partition)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if ip == nil {
|
||||||
return nil, fmt.Errorf("ip cannot be 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()
|
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 := make([]byte, 0, len(groupID)+1+len(ipAddr))
|
||||||
k = append(k, groupID...)
|
k = append(k, groupID...)
|
||||||
k = append(k, 0x00)
|
k = append(k, 0x00)
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package repository
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/entity"
|
||||||
|
"go.etcd.io/bbolt"
|
||||||
|
)
|
||||||
|
|
||||||
|
type BruteForceProtectionNotifyPolicyRepository interface {
|
||||||
|
Update(ruleName string, f func(*entity.BruteForceProtectionNotifyPolicy) (*entity.BruteForceProtectionNotifyPolicy, error)) error
|
||||||
|
}
|
||||||
|
|
||||||
|
type bruteForceProtectionNotifyPolicyRepository struct {
|
||||||
|
db *bbolt.DB
|
||||||
|
bucket string
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewBruteForceProtectionNotifyPolicyRepository(appDB *bbolt.DB) BruteForceProtectionNotifyPolicyRepository {
|
||||||
|
return &bruteForceProtectionNotifyPolicyRepository{
|
||||||
|
db: appDB,
|
||||||
|
bucket: bruteForceProtectionNotifyPolicyBucket,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *bruteForceProtectionNotifyPolicyRepository) Update(ruleName string, f func(*entity.BruteForceProtectionNotifyPolicy) (*entity.BruteForceProtectionNotifyPolicy, error)) error {
|
||||||
|
entityNotify := &entity.BruteForceProtectionNotifyPolicy{}
|
||||||
|
|
||||||
|
return r.db.Update(func(tx *bbolt.Tx) error {
|
||||||
|
b, err := tx.CreateBucketIfNotExists([]byte(r.bucket))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
key := []byte(ruleName)
|
||||||
|
|
||||||
|
notify := b.Get(key)
|
||||||
|
if notify != nil {
|
||||||
|
err = json.Unmarshal(notify, entityNotify)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("failed to unmarshal brute force protection notify policy: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entityNotify, err = f(entityNotify)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := json.Marshal(entityNotify)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return b.Put(key, data)
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -8,12 +8,13 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
notificationsQueueBucket = "notifications_queue"
|
notificationsQueueBucket = "notifications_queue"
|
||||||
alertGroupBucket = "alert_group"
|
alertGroupBucket = "alert_group"
|
||||||
bruteForceProtectionGroupBucket = "brute_force_protection_group"
|
bruteForceProtectionGroupBucket = "brute_force_protection_group"
|
||||||
blockingBucket = "blocking"
|
bruteForceProtectionNotifyPolicyBucket = "brute_force_protection_notify_policy"
|
||||||
blocklistBucket = "blocklist"
|
blockingBucket = "blocking"
|
||||||
metadataBucket = "metadata"
|
blocklistBucket = "blocklist"
|
||||||
|
metadataBucket = "metadata"
|
||||||
)
|
)
|
||||||
|
|
||||||
func nextID(b *bbolt.Bucket) ([]byte, error) {
|
func nextID(b *bbolt.Bucket) ([]byte, error) {
|
||||||
|
|||||||
@@ -48,6 +48,9 @@ type API interface {
|
|||||||
|
|
||||||
// DockerSupport Return true if docker support
|
// DockerSupport Return true if docker support
|
||||||
DockerSupport() bool
|
DockerSupport() bool
|
||||||
|
|
||||||
|
// HasRules Return true if firewall has rules
|
||||||
|
HasRules() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type firewall struct {
|
type firewall struct {
|
||||||
@@ -208,6 +211,10 @@ func (f *firewall) SavesRules() {
|
|||||||
f.logger.Info("Save nftables rules")
|
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) {
|
func (f *firewall) BlockIP(blockIP blocking.BlockIP) (bool, error) {
|
||||||
isBanned, err := f.blockingService.BlockIP(blockIP)
|
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
|
package table
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
"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/docker_monitor/firewall"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft"
|
||||||
@@ -11,6 +13,9 @@ type Table interface {
|
|||||||
Clear() error
|
Clear() error
|
||||||
DockerChains() firewall.NFTDockerChains
|
DockerChains() firewall.NFTDockerChains
|
||||||
BlockList() BlockList
|
BlockList() BlockList
|
||||||
|
|
||||||
|
// HasRules Return true if the table has rules
|
||||||
|
HasRules() (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type BlockList interface {
|
type BlockList interface {
|
||||||
@@ -55,6 +60,45 @@ func (t *table) BlockList() BlockList {
|
|||||||
return t.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 {
|
type blockList struct {
|
||||||
listIP block.ListIP
|
listIP block.ListIP
|
||||||
listIPWithPort block.ListIPWithPort
|
listIPWithPort block.ListIPWithPort
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ func (r *reload) outputICMP(batchOutput chain.Chain) error {
|
|||||||
func (r *reload) outputICMPAfter(batchOutput chain.Chain) error {
|
func (r *reload) outputICMPAfter(batchOutput chain.Chain) error {
|
||||||
if r.config.IP4.IcmpTimestampDrop == true {
|
if r.config.IP4.IcmpTimestampDrop == true {
|
||||||
drop := r.config.Policy.OutputDrop.String()
|
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
|
return err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,14 +4,16 @@ import (
|
|||||||
analyzerConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
|
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"
|
"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"
|
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 {
|
type DaemonOptions struct {
|
||||||
PathPidFile string
|
PathPidFile string
|
||||||
PathSocketFile string
|
PathSocketFile string
|
||||||
DataDir string
|
DataDir string
|
||||||
PathNftables string
|
PathNftables string
|
||||||
ConfigFirewall firewallConfig.Config
|
ConfigFirewall firewallConfig.Config
|
||||||
ConfigAnalyzer analyzerConfig.Config
|
ConfigFirewallGuard GuardConfig.GuardConfig
|
||||||
Repositories db.Repositories
|
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"
|
"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"
|
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/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/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/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/daemon/notifications"
|
||||||
@@ -57,20 +58,22 @@ func NewDaemon(
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
firewallRulesGuard := guard.NewRulesGuard(&opts.ConfigFirewallGuard, firewall, notifications, logger)
|
||||||
|
|
||||||
blockService := brute_force_protection_group.NewBlockService(firewall.BlockIP, firewall.BlockIPWithPorts)
|
blockService := brute_force_protection_group.NewBlockService(firewall.BlockIP, firewall.BlockIPWithPorts)
|
||||||
analyzerService := analyzer.New(opts.ConfigAnalyzer, blockService, opts.Repositories, logger, notifications, geoIPService.Info)
|
analyzerService := analyzer.New(opts.ConfigAnalyzer, blockService, opts.Repositories, logger, notifications, geoIPService.Info)
|
||||||
|
|
||||||
return &daemon{
|
return &daemon{
|
||||||
info: info,
|
info: info,
|
||||||
pidFile: pidFile,
|
pidFile: pidFile,
|
||||||
socket: sock,
|
socket: sock,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
firewall: firewall,
|
firewall: firewall,
|
||||||
notifications: notifications,
|
firewallRulesGuard: firewallRulesGuard,
|
||||||
analyzer: analyzerService,
|
notifications: notifications,
|
||||||
docker: docker,
|
analyzer: analyzerService,
|
||||||
blocklist: blocklist,
|
docker: docker,
|
||||||
geoIPService: geoIPService,
|
blocklist: blocklist,
|
||||||
|
geoIPService: geoIPService,
|
||||||
}, nil
|
}, 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",
|
"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",
|
"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",
|
"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 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}}",
|
"invalid log encoding": "Invalid encoding setting. Currently set to: {{.Encoding}}. Valid values: {{.Encodings}}",
|
||||||
@@ -71,6 +81,8 @@
|
|||||||
"unknown": "unknown",
|
"unknown": "unknown",
|
||||||
"blockSec": "Blocked for {{.BlockSec}}",
|
"blockSec": "Blocked for {{.BlockSec}}",
|
||||||
"ports": "Ports: {{.Ports}}",
|
"ports": "Ports: {{.Ports}}",
|
||||||
|
"alertNumber": "Alert number: {{.Count}}",
|
||||||
|
"blockIPCount": "The number of times the IP address was blocked: {{.Count}}",
|
||||||
|
|
||||||
"alert.subject": "Alert detected ({{.Name}}) (group:{{.GroupName}})",
|
"alert.subject": "Alert detected ({{.Name}}) (group:{{.GroupName}})",
|
||||||
"alert.login.ssh.message": "Logged into the OS via ssh.",
|
"alert.login.ssh.message": "Logged into the OS via ssh.",
|
||||||
@@ -84,5 +96,11 @@
|
|||||||
"alert.bruteForceProtection.ssh.message": "An attempt to brute-force SSH was detected.",
|
"alert.bruteForceProtection.ssh.message": "An attempt to brute-force SSH was detected.",
|
||||||
"alert.bruteForceProtection.group._default.message": "Default group.",
|
"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",
|
"cmd.daemon.geoip.refresh.Description": "GeoIP дерекқорын жаңарту. geoip.toml файлында қосулы және конфигурацияланған болуы керек. \nМысал: \nkor-elf-shield geoip refresh",
|
||||||
"geoip_refresh_success": "GeoIP дерекқоры сәтті жаңартылды",
|
"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": "Командалық қате",
|
"Command error": "Командалық қате",
|
||||||
"invalid log level": "Параметрлерде көрсетілген журнал деңгейі жарамсыз. Ол қазір мына күйге орнатылған: {{.Level}}. Жарамды мәндер: {{.Levels}}",
|
"invalid log level": "Параметрлерде көрсетілген журнал деңгейі жарамсыз. Ол қазір мына күйге орнатылған: {{.Level}}. Жарамды мәндер: {{.Levels}}",
|
||||||
"invalid log encoding": "Жарамсыз кодтау параметрі. Қазіргі уақытта орнатылған: {{.Encoding}}. Жарамды мәндер: {{.Encodings}}",
|
"invalid log encoding": "Жарамсыз кодтау параметрі. Қазіргі уақытта орнатылған: {{.Encoding}}. Жарамды мәндер: {{.Encodings}}",
|
||||||
@@ -71,6 +81,8 @@
|
|||||||
"unknown": "белгісіз",
|
"unknown": "белгісіз",
|
||||||
"blockSec": "{{.BlockSec}} блокталды",
|
"blockSec": "{{.BlockSec}} блокталды",
|
||||||
"ports": "Порттар: {{.Ports}}",
|
"ports": "Порттар: {{.Ports}}",
|
||||||
|
"alertNumber": "Ескерту нөмірі: {{.Count}}",
|
||||||
|
"blockIPCount": "IP мекенжайының бұғатталған саны: {{.Count}}",
|
||||||
|
|
||||||
"alert.subject": "Ескерту анықталды ({{.Name}}) (топ:{{.GroupName}})",
|
"alert.subject": "Ескерту анықталды ({{.Name}}) (топ:{{.GroupName}})",
|
||||||
"alert.login.ssh.message": "ОС-қа ssh арқылы кірді.",
|
"alert.login.ssh.message": "ОС-қа ssh арқылы кірді.",
|
||||||
@@ -84,5 +96,11 @@
|
|||||||
"alert.bruteForceProtection.ssh.message": "SSH-ті күштеп қолдану әрекеті анықталды.",
|
"alert.bruteForceProtection.ssh.message": "SSH-ті күштеп қолдану әрекеті анықталды.",
|
||||||
"alert.bruteForceProtection.group._default.message": "Әдепкі топ.",
|
"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",
|
"cmd.daemon.geoip.refresh.Description": "Обновление базу данных GeoIP. Должно быть включено и настроено в geoip.toml. \nПример: \nkor-elf-shield geoip refresh",
|
||||||
"geoip_refresh_success": "База данных GeoIP успешно обновлена",
|
"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": "Ошибка команды",
|
"Command error": "Ошибка команды",
|
||||||
"invalid log level": "В настройках указан не верный уровень log. Сейчас указан: {{.Level}}. Допустимые значения: {{.Levels}}",
|
"invalid log level": "В настройках указан не верный уровень log. Сейчас указан: {{.Level}}. Допустимые значения: {{.Levels}}",
|
||||||
"invalid log encoding": "Неверная настройка encoding. Сейчас указан: {{.Encoding}}. Допустимые значения: {{.Encodings}}",
|
"invalid log encoding": "Неверная настройка encoding. Сейчас указан: {{.Encoding}}. Допустимые значения: {{.Encodings}}",
|
||||||
@@ -71,6 +81,8 @@
|
|||||||
"unknown": "неизвестный",
|
"unknown": "неизвестный",
|
||||||
"blockSec": "Блокировка на {{.BlockSec}}",
|
"blockSec": "Блокировка на {{.BlockSec}}",
|
||||||
"ports": "Порты: {{.Ports}}",
|
"ports": "Порты: {{.Ports}}",
|
||||||
|
"alertNumber": "Номер оповещения: {{.Count}}",
|
||||||
|
"blockIPCount": "Количество раз, когда IP-адрес был заблокирован: {{.Count}}",
|
||||||
|
|
||||||
"alert.subject": "Обнаружено оповещение ({{.Name}}) (группа:{{.GroupName}})",
|
"alert.subject": "Обнаружено оповещение ({{.Name}}) (группа:{{.GroupName}})",
|
||||||
"alert.login.ssh.message": "Вошли в ОС через ssh.",
|
"alert.login.ssh.message": "Вошли в ОС через ssh.",
|
||||||
@@ -84,5 +96,11 @@
|
|||||||
"alert.bruteForceProtection.ssh.message": "Обнаружена попытка атаки на SSH методом перебора паролей.",
|
"alert.bruteForceProtection.ssh.message": "Обнаружена попытка атаки на SSH методом перебора паролей.",
|
||||||
"alert.bruteForceProtection.group._default.message": "Группа по умолчанию.",
|
"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 {
|
func (l *falseLogger) ReOpen() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func NewFalseLogger() Logger {
|
||||||
|
return &falseLogger{}
|
||||||
|
}
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ func (l *logger) ReOpen() error {
|
|||||||
|
|
||||||
func NewLogger(opts LoggerOptions) (Logger, error) {
|
func NewLogger(opts LoggerOptions) (Logger, error) {
|
||||||
if !opts.Enabled {
|
if !opts.Enabled {
|
||||||
return &falseLogger{}, nil
|
return NewFalseLogger(), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return &logger{
|
return &logger{
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ type BruteForceProtection struct {
|
|||||||
BlockingTime int `mapstructure:"blocking_time"`
|
BlockingTime int `mapstructure:"blocking_time"`
|
||||||
SSHEnable bool `mapstructure:"ssh_enable"`
|
SSHEnable bool `mapstructure:"ssh_enable"`
|
||||||
SSHNotify bool `mapstructure:"ssh_notify"`
|
SSHNotify bool `mapstructure:"ssh_notify"`
|
||||||
|
SSHNotifyCooldown int `mapstructure:"ssh_notify_cooldown_seconds"`
|
||||||
|
SSHNotifyEvery int `mapstructure:"ssh_notify_every"`
|
||||||
SSHGroup string `mapstructure:"ssh_group"`
|
SSHGroup string `mapstructure:"ssh_group"`
|
||||||
|
|
||||||
Groups []BruteForceProtectionGroup
|
Groups []BruteForceProtectionGroup
|
||||||
@@ -39,6 +41,8 @@ func defaultBruteForceProtection() BruteForceProtection {
|
|||||||
BlockingTime: 3600,
|
BlockingTime: 3600,
|
||||||
SSHEnable: true,
|
SSHEnable: true,
|
||||||
SSHNotify: true,
|
SSHNotify: true,
|
||||||
|
SSHNotifyCooldown: 0,
|
||||||
|
SSHNotifyEvery: 0,
|
||||||
SSHGroup: "",
|
SSHGroup: "",
|
||||||
|
|
||||||
Groups: []BruteForceProtectionGroup{},
|
Groups: []BruteForceProtectionGroup{},
|
||||||
@@ -62,6 +66,15 @@ func (p *BruteForceProtection) Validate() error {
|
|||||||
if p.BlockingTime < 0 {
|
if p.BlockingTime < 0 {
|
||||||
return errors.New("blocking time must be positive")
|
return errors.New("blocking time must be positive")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if p.SSHNotifyCooldown < 0 {
|
||||||
|
return errors.New("ssh notify cooldown must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if p.SSHNotifyEvery < 0 {
|
||||||
|
return errors.New("ssh notify every must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +98,7 @@ func (p *BruteForceProtection) ToSources() ([]*config.Source, error) {
|
|||||||
}
|
}
|
||||||
sshGroup = p.SSHGroup
|
sshGroup = p.SSHGroup
|
||||||
}
|
}
|
||||||
sshSources, err := config.NewBruteForceProtectionSSH(p.Notify && p.SSHNotify, groups[sshGroup])
|
sshSources, err := config.NewBruteForceProtectionSSH(p.Notify && p.SSHNotify, p.SSHNotifyCooldown, p.SSHNotifyEvery, groups[sshGroup])
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,10 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BruteForceProtectionPattern struct {
|
type BruteForceProtectionPattern struct {
|
||||||
Regexp string `mapstructure:"regexp"`
|
Regexp string `mapstructure:"regexp"`
|
||||||
IP int `mapstructure:"ip"`
|
IP int `mapstructure:"ip"`
|
||||||
Values []PatternValue
|
Values []PatternValue
|
||||||
|
Partition *PatternPartition `mapstructure:"partition"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *BruteForceProtectionPattern) ToPattern() (brute_force_protection.RegexPattern, error) {
|
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)
|
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
|
return pattern, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type BruteForceProtectionRule struct {
|
type BruteForceProtectionRule struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Notify bool `mapstructure:"notify"`
|
Notify bool `mapstructure:"notify"`
|
||||||
Name string `mapstructure:"name"`
|
NotifyCooldown int `mapstructure:"notify_cooldown_seconds"`
|
||||||
Message string `mapstructure:"message"`
|
NotifyEvery int `mapstructure:"notify_every"`
|
||||||
Group string `mapstructure:"group"`
|
Name string `mapstructure:"name"`
|
||||||
Source Source
|
Message string `mapstructure:"message"`
|
||||||
Patterns []BruteForceProtectionPattern
|
Group string `mapstructure:"group"`
|
||||||
|
Source Source
|
||||||
|
Patterns []BruteForceProtectionPattern
|
||||||
}
|
}
|
||||||
|
|
||||||
func (l *BruteForceProtectionRule) ToSource(isNotify bool, group *brute_force_protection.Group) (*config.Source, error) {
|
func (l *BruteForceProtectionRule) ToSource(isNotify bool, group *brute_force_protection.Group) (*config.Source, error) {
|
||||||
@@ -46,11 +48,13 @@ func (l *BruteForceProtectionRule) ToSource(isNotify bool, group *brute_force_pr
|
|||||||
}
|
}
|
||||||
|
|
||||||
source.BruteForceProtectionRule = &brute_force_protection.Rule{
|
source.BruteForceProtectionRule = &brute_force_protection.Rule{
|
||||||
Name: l.Name,
|
Name: l.Name,
|
||||||
Message: l.Message,
|
Message: l.Message,
|
||||||
IsNotification: isNotify && l.Notify,
|
IsNotification: isNotify && l.Notify,
|
||||||
Patterns: patterns,
|
NotificationCooldown: uint32(l.NotifyCooldown),
|
||||||
Group: group,
|
NotificationEvery: uint32(l.NotifyEvery),
|
||||||
|
Patterns: patterns,
|
||||||
|
Group: group,
|
||||||
}
|
}
|
||||||
|
|
||||||
return source, nil
|
return source, nil
|
||||||
@@ -65,5 +69,13 @@ func (l *BruteForceProtectionRule) validate() error {
|
|||||||
return fmt.Errorf("brute force protection invalid name: %s", l.Name)
|
return fmt.Errorf("brute force protection invalid name: %s", l.Name)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if l.NotifyCooldown < 0 {
|
||||||
|
return fmt.Errorf("brute force protection notify cooldown must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
|
if l.NotifyEvery < 0 {
|
||||||
|
return fmt.Errorf("brute force protection notify every must be positive")
|
||||||
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type LogAlertPattern struct {
|
type LogAlertPattern struct {
|
||||||
Regexp string `mapstructure:"regexp"`
|
Regexp string `mapstructure:"regexp"`
|
||||||
Values []PatternValue
|
Values []PatternValue
|
||||||
|
Partition *PatternPartition `mapstructure:"partition"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *LogAlertPattern) ToPattern() (config.AlertRegexPattern, error) {
|
func (p *LogAlertPattern) ToPattern() (config.AlertRegexPattern, error) {
|
||||||
@@ -24,5 +25,13 @@ func (p *LogAlertPattern) ToPattern() (config.AlertRegexPattern, error) {
|
|||||||
pattern.Values = append(pattern.Values, v)
|
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
|
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
|
||||||
|
}
|
||||||
@@ -9,7 +9,7 @@ import (
|
|||||||
|
|
||||||
type PatternValue struct {
|
type PatternValue struct {
|
||||||
Name string `mapstructure:"name"`
|
Name string `mapstructure:"name"`
|
||||||
Value int8 `mapstructure:"value"`
|
Value int `mapstructure:"value"`
|
||||||
Type string `mapstructure:"type"`
|
Type string `mapstructure:"type"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package blocklists
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"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/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/log"
|
||||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
||||||
@@ -10,8 +11,9 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Setting struct {
|
type Setting struct {
|
||||||
Enabled bool `mapstructure:"enabled"`
|
Enabled bool `mapstructure:"enabled"`
|
||||||
Sources []Sources
|
ExcludeIPs []string `mapstructure:"exclude_ips"`
|
||||||
|
Sources []Sources
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitSetting(path string) (Setting, error) {
|
func InitSetting(path string) (Setting, error) {
|
||||||
@@ -42,6 +44,14 @@ func InitSetting(path string) (Setting, error) {
|
|||||||
func settingDefault() Setting {
|
func settingDefault() Setting {
|
||||||
return Setting{
|
return Setting{
|
||||||
Enabled: false,
|
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{},
|
Sources: []Sources{},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -52,6 +62,17 @@ func (b *Setting) ToSources(logger log.Logger) []*blocklist.SourceConfig {
|
|||||||
return sources
|
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)
|
sourceNames := make(map[string]string)
|
||||||
|
|
||||||
for _, source := range b.Sources {
|
for _, source := range b.Sources {
|
||||||
@@ -65,7 +86,7 @@ func (b *Setting) ToSources(logger log.Logger) []*blocklist.SourceConfig {
|
|||||||
}
|
}
|
||||||
sourceNames[source.Name] = source.Name
|
sourceNames[source.Name] = source.Name
|
||||||
|
|
||||||
sourceConfig, err := source.ToSourceConfig()
|
sourceConfig, err := source.ToSourceConfig(exclusionChecker)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Warn(fmt.Sprintf("failed to convert source: %s", err))
|
logger.Warn(fmt.Sprintf("failed to convert source: %s", err))
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ type Sources struct {
|
|||||||
RssFieldSeparator string `mapstructure:"rss_field_separator"`
|
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 {
|
if err := s.Validate(); err != nil {
|
||||||
return &daemonBlocklist.SourceConfig{}, err
|
return &daemonBlocklist.SourceConfig{}, err
|
||||||
}
|
}
|
||||||
@@ -49,9 +49,11 @@ func (s *Sources) ToSourceConfig() (*daemonBlocklist.SourceConfig, error) {
|
|||||||
return &daemonBlocklist.SourceConfig{}, err
|
return &daemonBlocklist.SourceConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
config := blocklist.NewConfig(uint(s.Limit))
|
config := blocklist.NewConfigWithExclusionChecker(uint(s.Limit), exclusionChecker)
|
||||||
if s.TxtType == "interval" {
|
if s.TxtType == "interval" {
|
||||||
config.Validator = &parser.IPRangeValidator{}
|
config.Validator = &parser.IPRangeValidator{
|
||||||
|
ExclusionChecker: exclusionChecker,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.Zip {
|
if s.Zip {
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ type Setting struct {
|
|||||||
MetadataNaming metadataNaming
|
MetadataNaming metadataNaming
|
||||||
Policy policy
|
Policy policy
|
||||||
PortKnocking []portKnocking
|
PortKnocking []portKnocking
|
||||||
|
RulesGuard RulesGuard
|
||||||
}
|
}
|
||||||
|
|
||||||
func InitSetting(path string) (Setting, error) {
|
func InitSetting(path string) (Setting, error) {
|
||||||
@@ -53,6 +54,7 @@ func settingDefault() Setting {
|
|||||||
MetadataNaming: defaultMetadataNaming(),
|
MetadataNaming: defaultMetadataNaming(),
|
||||||
Policy: defaultPolicy(),
|
Policy: defaultPolicy(),
|
||||||
PortKnocking: defaultPortKnocking(),
|
PortKnocking: defaultPortKnocking(),
|
||||||
|
RulesGuard: defaultRulesGuard(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -72,6 +74,9 @@ func (s Setting) Validate() error {
|
|||||||
if err := s.Options.Validate(); err != nil {
|
if err := s.Options.Validate(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
if err := s.RulesGuard.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
return nil
|
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/blocklist"
|
||||||
"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/docker_monitor"
|
||||||
firewallConfig "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/config"
|
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/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/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/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)
|
setting, err := firewallSetting.InitSetting(o.Firewall)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewallConfig.Config{}, err
|
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
configPolicy, err := setting.Policy.ToConfigPolicy()
|
configPolicy, err := setting.Policy.ToConfigPolicy()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewallConfig.Config{}, err
|
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
inPorts, outPorts, err := setting.ToPorts()
|
inPorts, outPorts, err := setting.ToPorts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewallConfig.Config{}, err
|
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
IPs, err := setting.ToIPs()
|
IPs, err := setting.ToIPs()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewallConfig.Config{}, err
|
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
optionClearMode, err := setting.Options.ToClearMode()
|
optionClearMode, err := setting.Options.ToClearMode()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewallConfig.Config{}, err
|
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
portKnocking, err := setting.ToConfigPortKnocking()
|
portKnocking, err := setting.ToConfigPortKnocking()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return firewallConfig.Config{}, err
|
return firewallConfig.Config{}, GuardConfig.GuardConfig{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return firewallConfig.Config{
|
firewall := firewallConfig.Config{
|
||||||
InPorts: inPorts,
|
InPorts: inPorts,
|
||||||
OutPorts: outPorts,
|
OutPorts: outPorts,
|
||||||
IP4: firewallConfig.ConfigIP4{
|
IP4: firewallConfig.ConfigIP4{
|
||||||
@@ -109,7 +110,11 @@ func (o *otherSettingsPath) ToFirewallConfig(dockerSupport bool) (firewallConfig
|
|||||||
},
|
},
|
||||||
Policy: configPolicy,
|
Policy: configPolicy,
|
||||||
PortKnocking: portKnocking,
|
PortKnocking: portKnocking,
|
||||||
}, nil
|
}
|
||||||
|
|
||||||
|
rulesGuard := setting.RulesGuard.ToGuardConfig()
|
||||||
|
|
||||||
|
return firewall, rulesGuard, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (o *otherSettingsPath) ToNotificationsConfig() (notifications.Config, error) {
|
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) {
|
func (s setting) ToDaemonOptions(dockerSupport bool) (daemon.DaemonOptions, error) {
|
||||||
if s.PidFile == "" {
|
if err := s.ValidateBeforeStart(); err != nil {
|
||||||
return daemon.DaemonOptions{}, errors.New(i18n.Lang.T("parameter is not specified", map[string]any{
|
return daemon.DaemonOptions{}, err
|
||||||
"Parameter": "pid_file",
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.SocketFile == "" {
|
firewallConfig, guardConfig, err := s.OtherSettingsPath.ToFirewallConfig(dockerSupport)
|
||||||
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)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return daemon.DaemonOptions{}, err
|
return daemon.DaemonOptions{}, err
|
||||||
}
|
}
|
||||||
@@ -79,15 +59,44 @@ func (s setting) ToDaemonOptions(dockerSupport bool) (daemon.DaemonOptions, erro
|
|||||||
}
|
}
|
||||||
|
|
||||||
return daemon.DaemonOptions{
|
return daemon.DaemonOptions{
|
||||||
PathPidFile: s.PidFile,
|
PathPidFile: s.PidFile,
|
||||||
PathSocketFile: s.SocketFile,
|
PathSocketFile: s.SocketFile,
|
||||||
DataDir: s.DataDir,
|
DataDir: s.DataDir,
|
||||||
PathNftables: s.BinaryLocations.Nftables,
|
PathNftables: s.BinaryLocations.Nftables,
|
||||||
ConfigFirewall: firewallConfig,
|
ConfigFirewall: firewallConfig,
|
||||||
ConfigAnalyzer: analyzerConfig,
|
ConfigFirewallGuard: guardConfig,
|
||||||
|
ConfigAnalyzer: analyzerConfig,
|
||||||
}, nil
|
}, 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 {
|
func (s setting) Validate() error {
|
||||||
if err := s.validationTestingInterval(); err != nil {
|
if err := s.validationTestingInterval(); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in New Issue
Block a user