Compare commits
69
Commits
v0.10.0
...
29df5f3fd8
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29df5f3fd8
|
||
|
|
f2a9dc41be
|
||
|
|
f2dfeb507d
|
||
|
|
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
|
||
|
|
72a0f941a1 | ||
|
|
e9cd163ae7
|
||
|
|
f778e25575
|
||
|
|
21ab1802fe
|
||
|
|
62517e6f4c
|
||
|
|
8a7608dac6
|
||
|
|
8d90a3770d
|
||
|
|
4acb81b5a7
|
||
|
|
49ab9c48c7
|
||
|
|
28019ec171
|
||
|
|
03b4009f96
|
||
|
|
71502ff0c9
|
||
|
|
ab59b356dc
|
||
|
|
8595c6791d
|
||
|
|
c64c94dceb
|
||
|
|
a387e85569
|
||
|
|
ab8466ada2
|
||
|
|
d978343f4c
|
||
|
|
14a6b9df0b
|
||
|
|
298c7140a4
|
||
|
|
527b6c8264
|
||
|
|
0d707ac3c6
|
||
|
|
b535195c1f
|
||
|
|
3c12429f0e
|
||
|
|
25ee39c0ec
|
||
|
|
95e1f274f7
|
||
|
|
5f72efd1bf
|
||
|
|
e8826cb86b
|
||
|
|
f12097b280
|
||
|
|
3c040945bc
|
||
|
|
0fdc07c0af
|
||
|
|
a1345bd3e1
|
||
|
|
d2f3640b75
|
||
|
|
1363ff4bef
|
+99
-9
@@ -1,4 +1,94 @@
|
||||
## 0.10.0 (12.4.2026)
|
||||
## 0.14.0 (Soon)
|
||||
#### Русский
|
||||
* Исправлена ошибка в правилах исходящего ICMP: при `icmp_timestamp_drop=true` теперь корректно блокируются `timestamp-reply` пакеты (вместо `timestamp-request`).
|
||||
***
|
||||
#### English
|
||||
* Fixed an issue in outbound ICMP rules: when `icmp_timestamp_drop=true`, `timestamp-reply` packets are now correctly blocked (instead of `timestamp-request`).
|
||||
***
|
||||
## 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`.
|
||||
* Этот параметр включает кэширование, чтобы избежать постоянной компиляции команд nftables во временный файл. Файл кэша изменяется после изменения настроек или обновления версии программы. (`Включено по умолчанию`)
|
||||
* Логика добавления правил в nftables была переработана.
|
||||
* Команды теперь собираются во временном файле.
|
||||
* Запрос выполняется с использованием параметра -f.
|
||||
* Переработана логика обновления данных по блокировке IP адресов, которые получаем через другие сервисы.
|
||||
* Список IP адресов собирается в файл.
|
||||
* Запрос выполняется с использованием параметра -f.
|
||||
* Обновлена версия go-nftables-client до v0.2.1.
|
||||
* Улучшен вывод `Uptime` в команде `kor-elf-shield status`.
|
||||
* Улучшен вывод времени блокировки в уведомлениях.
|
||||
***
|
||||
#### English
|
||||
* The `options.cache` parameter has been added to the `firewall.toml` file settings.
|
||||
* This parameter enables caching to avoid constantly compiling nftables commands into a temporary file. The cache file changes after changing settings or updating the program version. (`Enabled by default`)
|
||||
* The logic for adding rules to nftables has been reworked.
|
||||
* Commands are now collected in a temporary file.
|
||||
* The query is executed using the -f parameter.
|
||||
* The logic for updating data on blocking IP addresses obtained through other services has been reworked.
|
||||
* The list of IP addresses is collected into a file.
|
||||
* The query is executed using the -f parameter.
|
||||
* Updated go-nftables-client to v0.2.1.
|
||||
* Improved `Uptime` output in `kor-elf-shield status` command.
|
||||
* Improved display of blocking time in notifications.
|
||||
***
|
||||
## 0.10.0 (12.04.2026)
|
||||
#### Русский
|
||||
* При автоматической блокировке добавил возможность получать данные об IP-адресах (континент, страна, город, часовой пояс).
|
||||
* В файл analyzer.toml добавлен параметр type к [[logAlert.rules.patterns.values]] в котором можно указать тип "ip". Это позволит для этого поля получить данные об IP-адресе при отправке оповещения.
|
||||
@@ -19,7 +109,7 @@
|
||||
* 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.
|
||||
***
|
||||
## 0.9.0 (21.3.2026)
|
||||
## 0.9.0 (21.03.2026)
|
||||
#### Русский
|
||||
* Добавилась поддержка Port knocking.
|
||||
* В firewall.toml добавился раздел Port knocking.
|
||||
@@ -51,7 +141,7 @@
|
||||
* GreenSnow Hack List
|
||||
* 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.
|
||||
## 0.8.0 (9.3.2026)
|
||||
## 0.8.0 (09.03.2026)
|
||||
***
|
||||
#### Русский
|
||||
* Теперь можно тонко настроить блокировку портов для IP адреса, который пытается подобрать пароль.
|
||||
@@ -75,7 +165,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 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)
|
||||
***
|
||||
#### Русский
|
||||
* Добавлена возможность настройки отслеживания событий в журналах.
|
||||
@@ -89,7 +179,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
|
||||
* 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)
|
||||
***
|
||||
#### Русский
|
||||
* Добавлена возможность повторной отправки уведомления, если в прошлый раз произошла ошибка.
|
||||
@@ -111,7 +201,7 @@
|
||||
* 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
|
||||
***
|
||||
## 0.5.0 (17.1.2026)
|
||||
## 0.5.0 (17.01.2026)
|
||||
***
|
||||
#### Русский
|
||||
* В настройках analyzer.toml добавил параметры local_enable и local_notify.
|
||||
@@ -135,7 +225,7 @@
|
||||
* sudo_enable = Enables tracking of logins via sudo. Off 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.
|
||||
@@ -157,7 +247,7 @@
|
||||
* The program crashed after Docker was stopped.
|
||||
* 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.
|
||||
@@ -198,7 +288,7 @@
|
||||
* 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.
|
||||
***
|
||||
## 0.1.0 (8.11.2025)
|
||||
## 0.1.0 (08.11.2025)
|
||||
***
|
||||
#### Русский
|
||||
* Реализована возможность настраивать 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
|
||||
|
||||
Copyright 2009 The Go Authors.
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
# Kor-Elf Shield
|
||||
### <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>Я решил реализовать своё решение на языке Go Lang. Это не будет полная копия CSF. Просто CSF вдохновило меня сделать, что-то похожее для защиты своего сервера.</p>
|
||||
|
||||
@@ -62,6 +61,16 @@
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/firewall.toml</strong> - тут находятся настройки, связанные с nftables. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/firewall.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/firewall.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/analyzer.toml</strong> - тут находятся настройки для анализатора логов. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/analyzer.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/analyzer.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/notifications.toml</strong> - тут находятся настройки для уведомлений. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/notifications.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/notifications.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/docker.toml</strong> - тут находятся настройки, связанные с docker. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/docker.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/docker.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/blocklists.toml</strong> - тут находятся настройки для получения IP-адресов от разных сервисов, которые надо заблокировать. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/blocklists.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/blocklists.toml</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/geoip.toml</strong> - тут находятся настройки для получения данных об IP-адресах. Информацию можно посмотреть тут: <a href="https://shield.kor-elf.net/docs/0.x/geoip.toml" target="_blank">https://shield.kor-elf.net/docs/0.x/geoip.toml</a></p>
|
||||
|
||||
***
|
||||
|
||||
<p>Программное обеспечение является MIT (см. <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE">LICENSE</a>) и использует сторонние библиотеки, которые распространяются на их собственных условиях (см. <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE-3RD-PARTY.txt">LICENSE-3RD-PARTY.txt</a>).</p>
|
||||
+10
-1
@@ -1,6 +1,5 @@
|
||||
# Kor-Elf Shield
|
||||
### <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 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>
|
||||
|
||||
@@ -62,6 +61,16 @@
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/firewall.toml</strong> - Here are the settings related to nftables. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/firewall.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/firewall.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/analyzer.toml</strong> - Contains log analyzer settings. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/analyzer.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/analyzer.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/notifications.toml</strong> - Contains notification settings. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/notifications.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/notifications.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/docker.toml</strong> - Contains Docker-related settings. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/docker.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/docker.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/blocklists.toml</strong> - Contains settings for obtaining IP addresses from various services that need to be blocked. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/blocklists.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/blocklists.toml/language/en</a></p>
|
||||
|
||||
<p><strong>/etc/kor-elf-shield/geoip.toml</strong> - This contains settings for obtaining IP address data. Information can be found here: <a href="https://shield.kor-elf.net/docs/0.x/geoip.toml/language/en" target="_blank">https://shield.kor-elf.net/docs/0.x/geoip.toml/language/en</a></p>
|
||||
|
||||
***
|
||||
|
||||
<p>The software is MIT (see <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE">LICENSE</a>) and uses third-party libraries that are distributed on their own terms (see <a href="https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/branch/main/LICENSE-3RD-PARTY.txt">LICENSE-3RD-PARTY.txt</a>).</p>
|
||||
@@ -68,9 +68,15 @@ rate_limit_reset_period = 86400
|
||||
|
||||
###
|
||||
# Включает защиту от перебора пароля от ssh.
|
||||
# !!! Примечание: По умолчанию SSH-сервер не регистрирует неудачные попытки входа с использованием ключа.
|
||||
# Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр
|
||||
# "LogLevel VERBOSE" в настройки sshd_config !!!
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables protection against brute-force attacks against ssh.
|
||||
# !!! Note: By default, the SSH server does not log unsuccessful login attempts using a key.
|
||||
# To enable tracking of unsuccessful login attempts using a key, you must add the "LogLevel VERBOSE" parameter
|
||||
# to the sshd_config settings !!!
|
||||
# Default: true
|
||||
###
|
||||
ssh_enable = true
|
||||
@@ -86,6 +92,34 @@ ssh_enable = 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
|
||||
|
||||
###
|
||||
# Можно указать свою группу, чтобы связать с другими правилами.
|
||||
# По умолчанию: ""
|
||||
@@ -162,11 +196,14 @@ ssh_group = ""
|
||||
# [[bruteForceProtection.rules]]
|
||||
# enabled = true # Включает или выключает отслеживания (обязательное поле)
|
||||
# notify = true # Включает или выключает уведомления (обязательное поле)
|
||||
# notify_cooldown_seconds = 0 # Минимальный интервал в секундах между уведомлениями об успешной блокировке (не обязательное поле)
|
||||
# notify_every = 0 # Количество успешных блокировок, которое нужно пропустить после отправки уведомления (не обязательное поле)
|
||||
# name = "my_name_rule" # Имя уведомления. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
|
||||
# message = "Ваш любой текст для уведомления" # Текст уведомления (обязательное поле)
|
||||
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
||||
# [bruteForceProtection.rules.source]
|
||||
# type = "journalctl" # journalctl или file (обязательное поле)
|
||||
# path = "" # Указываем путь к файлу (обязательное поле если type = "file")
|
||||
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
||||
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
||||
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -176,18 +213,28 @@ ssh_group = ""
|
||||
# [[bruteForceProtection.rules.patterns.values]]
|
||||
# name = "Пользователь"
|
||||
# value = 1
|
||||
#
|
||||
# [bruteForceProtection.rules.patterns.partition] # (необязательный параметр) если надо разделить по ключевым словам
|
||||
# value = 1 # Номер поля, по которому мы получаем ключевое слова
|
||||
# trim = true # Убирает у ключевого слова пробелы вначале и в конце
|
||||
# lower_case = true # Ключевое слово приводит к нижнему регистру
|
||||
# [bruteForceProtection.rules.patterns.partition.type] # (необязательный параметр)
|
||||
# type = "only" # Допустимые значения "only" или "except"
|
||||
# keywords = ["root", "user"] # Указываем ключевые слова
|
||||
# partitioned = false # Если нужно, чтобы каждое ключевое слово считалось независимо от друг-друга, то указываем true
|
||||
# ***
|
||||
# Specify the log settings that need to be monitored to protect against password brute-force attacks.
|
||||
# Examples:
|
||||
# [[bruteForceProtection.rules]]
|
||||
# enabled = true # Enables or disables tracking (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)
|
||||
# message = "Your any text for notification" # Notification text (required field)
|
||||
# group = "my_name_group" # You can specify the group name (optional field)
|
||||
# [bruteForceProtection.rules.source]
|
||||
# type = "journalctl" # journalctl or file (required)
|
||||
# path = "" # Specify the path to the file (required field if type = "file")
|
||||
# field = "systemd_unit" # systemd_unit or syslog_identifier (required if type = "journalctl")
|
||||
# match = "ssh.service" # Value (required if type = "journalctl")
|
||||
# If field = "systemd_unit", then match must end with: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -199,6 +246,14 @@ ssh_group = ""
|
||||
# [[bruteForceProtection.rules.patterns.values]]
|
||||
# name = "User"
|
||||
# value = 1
|
||||
# [bruteForceProtection.rules.patterns.partition] # (optional) if you want to split by keywords
|
||||
# value = 1 # Field number by which to get the keyword
|
||||
# trim = true # Removes leading and trailing spaces from the keyword
|
||||
# lower_case = true # Converts the keyword to lowercase
|
||||
# [bruteForceProtection.rules.patterns.partition.type] # (optional)
|
||||
# type = "only" # Acceptable values are "only" or "except"
|
||||
# keywords = ["root", "user"] # Specify the keywords
|
||||
# partitioned = false # If you want each keyword to be considered independently, specify true
|
||||
###
|
||||
|
||||
###############################################################################
|
||||
@@ -391,6 +446,7 @@ notify = true
|
||||
# group = "my_name_group" # Можно указать имя группы (не обязательное поле)
|
||||
# [logAlert.rules.source]
|
||||
# type = "journalctl" # journalctl или file (обязательное поле)
|
||||
# path = "" # Указываем путь к файлу (обязательное поле если type = "file")
|
||||
# field = "systemd_unit" # systemd_unit или syslog_identifier (обязательное поле если type = "journalctl")
|
||||
# match = "ssh.service" # Значение (обязательное поле если type = "journalctl")
|
||||
# если field = "systemd_unit", то match должен заканичваться: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -403,6 +459,14 @@ notify = true
|
||||
# name = "IP"
|
||||
# value = 3
|
||||
# type = "ip"
|
||||
# [logAlert.rules.patterns.partition] # (необязательный параметр) если надо разделить по ключевым словам
|
||||
# value = 2 # Номер поля, по которому мы получаем ключевое слова
|
||||
# trim = true # Убирает у ключевого слова пробелы вначале и в конце
|
||||
# lower_case = true # Ключевое слово приводит к нижнему регистру
|
||||
# [logAlert.rules.patterns.partition.type] # (необязательный параметр)
|
||||
# type = "only" # Допустимые значения "only" или "except"
|
||||
# keywords = ["root", "user"] # Указываем ключевые слова
|
||||
# partitioned = false # Если нужно, чтобы каждое ключевое слово считалось независимо от друг-друга, то указываем true
|
||||
#
|
||||
# ***
|
||||
# Specify the log settings to monitor for notifications.
|
||||
@@ -415,6 +479,7 @@ notify = true
|
||||
# group = "my_name_group" # You can specify the group name (optional field)
|
||||
# [logAlert.rules.source]
|
||||
# type = "journalctl" # journalctl or file (required)
|
||||
# path = "" # Specify the path to the file (required field if type = "file")
|
||||
# field = "systemd_unit" # systemd_unit or syslog_identifier (required if type = "journalctl")
|
||||
# match = "ssh.service" # Value (required if type = "journalctl")
|
||||
# If field = "systemd_unit", then match must end with: .service, .socket, .target, .mount, .timer, .path, .scope, .slice, .device
|
||||
@@ -427,4 +492,12 @@ notify = true
|
||||
# name = "IP"
|
||||
# value = 3
|
||||
# type = "ip"
|
||||
# [logAlert.rules.patterns.partition] # (optional) if you want to split by keywords
|
||||
# value = 2 # Field number by which we get the keyword
|
||||
# trim = true # Removes leading and trailing spaces from the keyword
|
||||
# lower_case = true # Converts the keyword to lowercase
|
||||
# [logAlert.rules.patterns.partition.type] # (optional)
|
||||
# type = "only" # Acceptable values are "only" or "except"
|
||||
# keywords = ["root", "user"] # Specify the keywords
|
||||
# partitioned = false # If you want each keyword to be counted independently, specify true
|
||||
###
|
||||
|
||||
@@ -17,6 +17,38 @@
|
||||
###
|
||||
enabled = false
|
||||
|
||||
###
|
||||
# Исключить IP-адреса из полученных списков.
|
||||
# Допустимые форматы: "192.168.1.1", "192.168.1.0/24", "192.168.1.0-192.168.1.255"
|
||||
# По умолчанию: [
|
||||
# "127.0.0.1/8",
|
||||
# "10.0.0.0/8",
|
||||
# "172.16.0.0/12",
|
||||
# "192.168.0.0/16",
|
||||
# "::1/128",
|
||||
# "fc00::/7",
|
||||
# ]
|
||||
# ***
|
||||
# Exclude IP addresses from retrieved lists.
|
||||
# Acceptable formats: "192.168.1.1", "192.168.1.0/24", "192.168.1.0-192.168.1.255"
|
||||
# Default: [
|
||||
# "127.0.0.1/8",
|
||||
# "10.0.0.0/8",
|
||||
# "172.16.0.0/12",
|
||||
# "192.168.0.0/16",
|
||||
# "::1/128",
|
||||
# "fc00::/7",
|
||||
# ]
|
||||
###
|
||||
exclude_ips = [
|
||||
"127.0.0.1/8",
|
||||
"10.0.0.0/8",
|
||||
"172.16.0.0/12",
|
||||
"192.168.0.0/16",
|
||||
"::1/128",
|
||||
"fc00::/7",
|
||||
]
|
||||
|
||||
###
|
||||
# Spamhaus Don't Route Or Peer Lists (DROP IPv4)
|
||||
# Details: https://www.spamhaus.org/blocklists/do-not-route-or-peer/
|
||||
|
||||
@@ -300,6 +300,17 @@ icmp_strict = false
|
||||
###############################################################################
|
||||
[options]
|
||||
|
||||
###
|
||||
# Включает кэширование, чтобы избежать постоянной компиляции команд nftables во временный файл.
|
||||
# Файл кэша изменяется после изменения настроек или обновления версии программы.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables a cache to avoid constantly compiling nftables commands into a temporary file.
|
||||
# The cache file changes after changing settings or updating the program version.
|
||||
# Default: true
|
||||
###
|
||||
cache = true
|
||||
|
||||
###
|
||||
# Переключения режима очистки фаервола nftables. Если указать "own", то может получиться конфликт в правилах.
|
||||
# Может спровоцировать проблему в безопасности. Указывайте "own" если вы уверены в своих действиях.
|
||||
@@ -610,3 +621,46 @@ chain_output_name = "output"
|
||||
# Default: "forward"
|
||||
###
|
||||
chain_forward_name = "forward"
|
||||
|
||||
###############################################################################
|
||||
# РАЗДЕЛ:Проверка и восстановление правил брандмауэра
|
||||
# ***
|
||||
# SECTION:Checking and restoring firewall rules
|
||||
###############################################################################
|
||||
[rulesGuard]
|
||||
###
|
||||
# Включает мониторинг правил NFTables.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables monitoring of NFTables rules.
|
||||
# Default: true
|
||||
###
|
||||
enabled = true
|
||||
|
||||
###
|
||||
# Включает уведомления в случае возникновения проблем с правилами NFTables.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# Enables notifications if there is a problem with NFTables rules.
|
||||
# Default: true
|
||||
###
|
||||
notifications = true
|
||||
|
||||
###
|
||||
# Если возникла проблема с правилами NFTables, следует ли перезапустить настройки правил NFTables.
|
||||
# По умолчанию: true
|
||||
# ***
|
||||
# If there is a problem with the NFTables rules, whether to restart the NFTables rules settings.
|
||||
# Default: true
|
||||
###
|
||||
recovery = true
|
||||
|
||||
###
|
||||
# Указать интервал в секундах по которому будет проверяться на наличия правил NFTables.
|
||||
# По умолчанию: 3600
|
||||
# ***
|
||||
# Specify the interval in seconds at which to check for NFTables rules.
|
||||
# Default: 3600
|
||||
###
|
||||
interval = 3600
|
||||
|
||||
|
||||
@@ -165,12 +165,12 @@ encoding = "json"
|
||||
|
||||
###
|
||||
# Список мест для основных логов.
|
||||
# Можно указать несколько путей. Например output_paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Можно указать несколько путей. Например paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Где "stdout" - вывод данных в консоль.
|
||||
# По умолчанию: ["/var/log/kor-elf-shield.log"]
|
||||
# ***
|
||||
# List of locations for main logs.
|
||||
# Multiple paths can be specified. For example, output_paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Multiple paths can be specified. For example, paths = ["stdout", "/var/log/kor-elf-shield.log"].
|
||||
# Where "stdout" is the console output.
|
||||
# Default: ["/var/log/kor-elf-shield.log"]
|
||||
###
|
||||
@@ -178,12 +178,12 @@ paths = ["/var/log/kor-elf-shield.log"]
|
||||
|
||||
###
|
||||
# Список мест для ошибок логгирования. Например журнал не смог записать какие-то данные.
|
||||
# Можно указать несколько путей. Например output_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Можно указать несколько путей. Например log_error_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Где "stderr" - вывод данных в консоль.
|
||||
# По умолчанию: ["stderr"]
|
||||
# ***
|
||||
# List of locations for logging errors. For example, the log failed to write some data.
|
||||
# Multiple paths can be specified. For example, output_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Multiple paths can be specified. For example, log_error_paths = ["stderr", "/var/log/kor-elf-shield-log-error.log"].
|
||||
# Where "stderr" is the data output to the console.
|
||||
# Default: ["stderr"]
|
||||
###
|
||||
|
||||
@@ -1,35 +1,36 @@
|
||||
module git.kor-elf.net/kor-elf-shield/kor-elf-shield
|
||||
|
||||
go 1.25
|
||||
go 1.25.0
|
||||
|
||||
require (
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.2.0
|
||||
git.kor-elf.net/kor-elf-shield/geoip2 v0.1.2
|
||||
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.1.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/nxadm/tail v1.4.11
|
||||
github.com/spf13/viper v1.21.0
|
||||
github.com/urfave/cli/v3 v3.6.2
|
||||
github.com/wneessen/go-mail v0.7.2
|
||||
go.etcd.io/bbolt v1.4.3
|
||||
go.uber.org/zap v1.27.1
|
||||
golang.org/x/sys v0.41.0
|
||||
golang.org/x/text v0.34.0
|
||||
github.com/urfave/cli/v3 v3.10.1
|
||||
github.com/wneessen/go-mail v0.8.1
|
||||
go.etcd.io/bbolt v1.5.0
|
||||
go.uber.org/zap v1.28.0
|
||||
golang.org/x/sys v0.47.0
|
||||
golang.org/x/text v0.41.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.9.0 // indirect
|
||||
github.com/fsnotify/fsnotify v1.10.1 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 // indirect
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||
github.com/oschwald/geoip2-golang/v2 v2.3.0 // indirect
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0 // indirect
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||
github.com/sagikazarmark/locafero v0.12.0 // indirect
|
||||
github.com/spf13/afero v1.15.0 // indirect
|
||||
github.com/spf13/cast v1.10.0 // indirect
|
||||
github.com/spf13/pflag v1.0.10 // indirect
|
||||
github.com/subosito/gotenv v1.6.0 // indirect
|
||||
go.uber.org/multierr v1.11.0 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.4 // indirect
|
||||
go.yaml.in/yaml/v3 v3.0.5 // indirect
|
||||
golang.org/x/crypto v0.55.0 // indirect
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
|
||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||
)
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0 h1:NS8be3TFBsUn+ft3oG5sAD56iJTGOkFH6GgjepEnS0s=
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0/go.mod h1:nNbQux5vbuoCa3wMiC2QsLb4tO1JLCssGzdljizcJUs=
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.2.0 h1:oj4P4N5sa0kh2Zob0Mf/GrjqNAQrZjuX9/RoDx3UUGk=
|
||||
git.kor-elf.net/kor-elf-shield/blocklist v1.2.0/go.mod h1:nNbQux5vbuoCa3wMiC2QsLb4tO1JLCssGzdljizcJUs=
|
||||
git.kor-elf.net/kor-elf-shield/geoip2 v0.1.2 h1:/J9U+h9H92hW6TtwCznkRANqhX5kvBpN4uV7xDbwXpM=
|
||||
git.kor-elf.net/kor-elf-shield/geoip2 v0.1.2/go.mod h1:ULMUjpd2I9ikkDDE69IlpKT4vR2/nlYT0cqoR2T95sM=
|
||||
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.1.1 h1:3oGtZ/r1YAdlvI16OkZSCaxcWztHe/33ITWfI2LaQm0=
|
||||
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.1.1/go.mod h1:a7F+XdL1pK5P3ucQRR2EK/fABAP37LLBENiA4hX7L6A=
|
||||
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/go.mod h1:a7F+XdL1pK5P3ucQRR2EK/fABAP37LLBENiA4hX7L6A=
|
||||
github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk=
|
||||
github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
@@ -13,6 +15,8 @@ github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7z
|
||||
github.com/fsnotify/fsnotify v1.6.0/go.mod h1:sl3t1tCWJFWoRz9R8WJCbQihKKwmorjAbSClcnxKAGw=
|
||||
github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k=
|
||||
github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0=
|
||||
github.com/fsnotify/fsnotify v1.10.1 h1:b0/UzAf9yR5rhf3RPm9gf3ehBPpf0oZKIjtpKrx59Ho=
|
||||
github.com/fsnotify/fsnotify v1.10.1/go.mod h1:TLheqan6HD6GBK6PrDWyDPBaEV8LspOxvPSjC+bVfgo=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0 h1:vM5IJoUAy3d7zRSVtIwQgBj7BiWtMPfmPEgAXnvj1Ro=
|
||||
github.com/go-viper/mapstructure/v2 v2.5.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
@@ -27,10 +31,16 @@ github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
|
||||
github.com/nxadm/tail v1.4.11/go.mod h1:OTaG3NK980DZzxbRq6lEuzgU+mug70nY11sMd4JXXHc=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0 h1:DjnLhNJu9WHwTrmoiQFvgmyJoczhdnm7LB23UBI2Amo=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.1.0/go.mod h1:qdVmcPgrTJ4q2eP9tHq/yldMTdp2VMr33uVdFbHBiBc=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.3.0 h1:hT8/BT137lPJXq0DXwGQUS228k8pEhgBRJ1B70eqyAk=
|
||||
github.com/oschwald/geoip2-golang/v2 v2.3.0/go.mod h1:tHUYg65ssvQSSzSCkiFR6LWJPYOvSw/85JiBp8kXz0U=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1 h1:lA8FH0oOrM4u7mLvowq8IT6a3Q/qEnqRzLQn9eH5ojc=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.1.1/go.mod h1:PLdx6PR+siSIoXqqy7C7r3SB3KZnhxWr1Dp6g0Hacl8=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0 h1:WvEHCE8HwFS5pKWhW8nvvRxNzczuRUOGBLn2L03VlEQ=
|
||||
github.com/oschwald/maxminddb-golang/v2 v2.5.0/go.mod h1:EBnvLGgY+aSckqcgyfB5LPDviqaWdMZPBDwu8c2jJbs=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4=
|
||||
github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3 h1:GTRvJQutkOSftxIFD5xw9aepkYNuPWmVJpffdDPYVpY=
|
||||
github.com/pelletier/go-toml/v2 v2.4.3/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||
@@ -51,25 +61,42 @@ github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8
|
||||
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||
github.com/urfave/cli/v3 v3.6.2 h1:lQuqiPrZ1cIz8hz+HcrG0TNZFxU70dPZ3Yl+pSrH9A8=
|
||||
github.com/urfave/cli/v3 v3.6.2/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/urfave/cli/v3 v3.10.1 h1:7Kx9H50hrHbRbyxgO1KP6/BcbiGRz0uYh5YyQ30JEEY=
|
||||
github.com/urfave/cli/v3 v3.10.1/go.mod h1:ysVLtOEmg2tOy6PknnYVhDoouyC/6N42TMeoMzskhso=
|
||||
github.com/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
|
||||
github.com/wneessen/go-mail v0.7.2/go.mod h1:+TkW6QP3EVkgTEqHtVmnAE/1MRhmzb8Y9/W3pweuS+k=
|
||||
github.com/wneessen/go-mail v0.8.1 h1:tVcncj02/QySVFw3zr/kXOzZcuFQqBNT6K+Rbgm/pcM=
|
||||
github.com/wneessen/go-mail v0.8.1/go.mod h1:dWZ61zadzCIyvB4y1/YzC5O7MrbbzBfPkARmbosdf8w=
|
||||
go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
|
||||
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
|
||||
go.etcd.io/bbolt v1.5.0 h1:S7GAl7Fxv12yohbwFfIbQCGDWbQbtDGPET4P/bD4lxU=
|
||||
go.etcd.io/bbolt v1.5.0/go.mod h1:mkltfYE5aUHQxUct9N9V+Kp7aSjFqjgrhcXIS70Lrdk=
|
||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
|
||||
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
|
||||
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
|
||||
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
|
||||
go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo=
|
||||
go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q=
|
||||
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
|
||||
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
|
||||
go.yaml.in/yaml/v3 v3.0.5 h1:N6y/pJk8buWs9NY5ERU2HSMfm+IuD/OtfdAnq6kESPw=
|
||||
go.yaml.in/yaml/v3 v3.0.5/go.mod h1:HVTZu1O7/Vkt2N+BFy8Zza+lnLsABggaTM2ZpNIGuKg=
|
||||
golang.org/x/crypto v0.55.0 h1:+KWHjbgOaAQ66dh/YlkZKHlz9ZUlq61AFirAR9ntP8M=
|
||||
golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis=
|
||||
golang.org/x/sync v0.19.0 h1:vV+1eWNmZ5geRlYjzm2adRgW2/mcpevXNg50YZtPCE4=
|
||||
golang.org/x/sync v0.19.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
|
||||
golang.org/x/sync v0.22.0 h1:SZjpbeLmrCk4xhRSZFNZW5gFUeCeFgjekvI/+gfScek=
|
||||
golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k=
|
||||
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||
golang.org/x/sys v0.47.0 h1:o7XGOvZQCADBQQ4Y7VNq2dRWQR7JmOUW8Kxx4ZsNgWs=
|
||||
golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
|
||||
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
|
||||
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
|
||||
golang.org/x/text v0.41.0 h1:vz/seA0lnX87Othu2f/0L24RcgrXD9/YFTSuGjj3rH8=
|
||||
golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
|
||||
@@ -3,6 +3,7 @@ package daemon
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist"
|
||||
@@ -10,6 +11,7 @@ import (
|
||||
"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/daemon/docker_monitor"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/geoip"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/info"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/notifications"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
@@ -83,12 +85,9 @@ func runDaemon(ctx context.Context, _ *cli.Command) error {
|
||||
_ = geoIPService.Close()
|
||||
}()
|
||||
|
||||
info := daemon.DaemonInfo{
|
||||
Ver: setting.AppVer,
|
||||
BuiltWith: setting.AppBuiltWith,
|
||||
StartTime: setting.AppStartTime,
|
||||
}
|
||||
d, err := daemon.NewDaemon(info, config, logger, notificationsService, dockerService, blocklistService, geoIPService)
|
||||
daemonInfo := newDaemonInfo(repositories.Metadata(), setting.Config.ListPathConfigFiles(), logger)
|
||||
|
||||
d, err := daemon.NewDaemon(daemonInfo, config, logger, notificationsService, dockerService, blocklistService, geoIPService)
|
||||
if err != nil {
|
||||
logger.Fatal(err.Error())
|
||||
|
||||
@@ -109,6 +108,23 @@ func runDaemon(ctx context.Context, _ *cli.Command) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func newDaemonInfo(repo repository.MetadataRepository, listPathFiles map[string]string, logger log.Logger) info.Info {
|
||||
metaFirewallFileNft := info.NewMetadataFirewallFileNft(repo)
|
||||
metadataContainer := info.NewMetadataContainer(metaFirewallFileNft)
|
||||
|
||||
return info.New(
|
||||
setting.AppVer,
|
||||
info.IsVersionChanged(repo, setting.AppVer, logger),
|
||||
|
||||
setting.AppBuiltWith,
|
||||
setting.AppStartTime,
|
||||
|
||||
info.IsSettingsChanged(repo, listPathFiles, logger),
|
||||
|
||||
metadataContainer,
|
||||
)
|
||||
}
|
||||
|
||||
func newNotificationsService(queueRepository repository.NotificationsQueueRepository, logger log.Logger) (notifications.Notifications, error) {
|
||||
config, err := setting.Config.OtherSettingsPath.ToNotificationsConfig()
|
||||
if err != nil {
|
||||
@@ -150,6 +166,7 @@ func newBlocklistService(ctx context.Context, blocklistRepository repository.Blo
|
||||
blocklistConfig := blocklist.Config{
|
||||
BlocklistRepository: blocklistRepository,
|
||||
Sources: config,
|
||||
PathDir: strings.TrimRight(setting.Config.DataDir, "/") + "/blocklists",
|
||||
}
|
||||
|
||||
blocklistService, err := blocklist.New(blocklistConfig, ctx, logger)
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"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
|
||||
|
||||
journal, err := NewSourceJournal(JournalFieldSystemdUnit, "ssh.service")
|
||||
@@ -20,12 +20,26 @@ func NewBruteForceProtectionSSH(isNotify bool, group *brute_force_protection.Gro
|
||||
Type: SourceTypeJournal,
|
||||
Journal: journal,
|
||||
BruteForceProtectionRule: &brute_force_protection.Rule{
|
||||
Name: "_ssh",
|
||||
Message: i18n.Lang.T("alert.bruteForceProtection.ssh.message"),
|
||||
IsNotification: isNotify,
|
||||
Name: "_ssh",
|
||||
Message: i18n.Lang.T("alert.bruteForceProtection.ssh.message"),
|
||||
|
||||
IsNotification: isNotify,
|
||||
NotificationCooldown: uint32(notifyCooldown),
|
||||
NotificationEvery: uint32(notifyEvery),
|
||||
|
||||
Patterns: []brute_force_protection.RegexPattern{
|
||||
{
|
||||
Regexp: regular_expression.NewLazyRegexp(`^Failed password for (\S+) from (\S+) port \S+`),
|
||||
Regexp: regular_expression.NewLazyRegexp(`^Failed (password|publickey) for (invalid user |illegal user )?(\S*) from (\S+)( port \S+ \S+\s*)`),
|
||||
Values: []brute_force_protection.PatternValue{
|
||||
{
|
||||
Name: i18n.Lang.T("user"),
|
||||
Value: 3,
|
||||
},
|
||||
},
|
||||
IP: 4,
|
||||
},
|
||||
{
|
||||
Regexp: regular_expression.NewLazyRegexp(`^Invalid user (\S*) from (\S+) port (\S+)`),
|
||||
Values: []brute_force_protection.PatternValue{
|
||||
{
|
||||
Name: i18n.Lang.T("user"),
|
||||
|
||||
@@ -1,19 +1,27 @@
|
||||
package brute_force_protection
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/regular_expression"
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/partition"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/regular_expression"
|
||||
)
|
||||
|
||||
type Rule struct {
|
||||
Name string
|
||||
Message string
|
||||
IsNotification bool
|
||||
Patterns []RegexPattern
|
||||
Group *Group
|
||||
Name string
|
||||
Message string
|
||||
|
||||
IsNotification bool
|
||||
NotificationCooldown uint32
|
||||
NotificationEvery uint32
|
||||
|
||||
Patterns []RegexPattern
|
||||
Group *Group
|
||||
}
|
||||
|
||||
type RegexPattern struct {
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
IP uint8
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
IP uint8
|
||||
Partition *partition.PatternPartition
|
||||
}
|
||||
|
||||
type RateLimit struct {
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"unicode"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/brute_force_protection"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/partition"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/regular_expression"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
|
||||
)
|
||||
@@ -112,8 +113,9 @@ type AlertRule struct {
|
||||
}
|
||||
|
||||
type AlertRegexPattern struct {
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
Regexp *regular_expression.LazyRegexp
|
||||
Values []PatternValue
|
||||
Partition *partition.PatternPartition
|
||||
}
|
||||
|
||||
type PatternValue struct {
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package partition
|
||||
|
||||
type exceptType struct {
|
||||
keywords []string
|
||||
partitioned bool
|
||||
}
|
||||
|
||||
func NewExceptType(
|
||||
keywords []string,
|
||||
partitioned bool,
|
||||
) PatternPartitionType {
|
||||
return &exceptType{
|
||||
keywords: keywords,
|
||||
partitioned: partitioned,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *exceptType) IsPartitioned() bool {
|
||||
return o.partitioned
|
||||
}
|
||||
|
||||
func (o *exceptType) Accepts(text string) (bool, string) {
|
||||
for _, keyword := range o.keywords {
|
||||
if keyword == text {
|
||||
return false, text
|
||||
}
|
||||
}
|
||||
return true, text
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
package partition
|
||||
|
||||
type onlyType struct {
|
||||
keywords []string
|
||||
partitioned bool
|
||||
}
|
||||
|
||||
func NewOnlyType(
|
||||
keywords []string,
|
||||
partitioned bool,
|
||||
) PatternPartitionType {
|
||||
return &onlyType{
|
||||
keywords: keywords,
|
||||
partitioned: partitioned,
|
||||
}
|
||||
}
|
||||
|
||||
func (o *onlyType) IsPartitioned() bool {
|
||||
return o.partitioned
|
||||
}
|
||||
|
||||
func (o *onlyType) Accepts(text string) (bool, string) {
|
||||
for _, keyword := range o.keywords {
|
||||
if keyword == text {
|
||||
return true, text
|
||||
}
|
||||
}
|
||||
return false, text
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package partition
|
||||
|
||||
import "strings"
|
||||
|
||||
type PatternPartitionType interface {
|
||||
Accepts(text string) (bool, string)
|
||||
IsPartitioned() bool
|
||||
}
|
||||
|
||||
type PatternPartition struct {
|
||||
Value uint8
|
||||
Type PatternPartitionType
|
||||
Normalize func(keyword string) string
|
||||
}
|
||||
|
||||
type Normalize interface {
|
||||
Normalize(text string) string
|
||||
}
|
||||
|
||||
type normalize struct {
|
||||
Trim bool
|
||||
LowerCase bool
|
||||
}
|
||||
|
||||
func NewNormalize(trim, lowerCase bool) Normalize {
|
||||
return &normalize{
|
||||
Trim: trim,
|
||||
LowerCase: lowerCase,
|
||||
}
|
||||
}
|
||||
|
||||
func (p *normalize) Normalize(text string) string {
|
||||
if p.LowerCase {
|
||||
text = strings.ToLower(text)
|
||||
}
|
||||
if p.Trim {
|
||||
text = strings.TrimSpace(text)
|
||||
}
|
||||
return text
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import (
|
||||
|
||||
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"
|
||||
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/db"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/geoip"
|
||||
@@ -33,6 +34,7 @@ func NewAnalysis(
|
||||
) Analysis {
|
||||
alertGroupService := alert_group.NewGroup(repositories.AlertGroup(), logger)
|
||||
bruteForceProtectionGroupService := brute_force_protection_group.NewGroup(repositories.BruteForceProtectionGroup(), logger)
|
||||
notificationPolicy := analysisBruteForceProtection.NewNotificationPolicy(repositories.BruteForceProtectionNotifyPolicy(), logger)
|
||||
|
||||
return &analysis{
|
||||
alertService: analysisServices.NewAlert(rulesIndex, alertGroupService, logger, notify, ipInfo),
|
||||
@@ -40,6 +42,7 @@ func NewAnalysis(
|
||||
rulesIndex,
|
||||
bruteForceProtectionGroupService,
|
||||
blockService,
|
||||
notificationPolicy,
|
||||
logger,
|
||||
notify,
|
||||
ipInfo,
|
||||
|
||||
@@ -26,15 +26,17 @@ type alert struct {
|
||||
}
|
||||
|
||||
type alertAnalyzeRuleReturn struct {
|
||||
found bool
|
||||
fields []*regexField
|
||||
found bool
|
||||
fields []*regexField
|
||||
partition *string
|
||||
}
|
||||
|
||||
type alertNotify struct {
|
||||
rule *config.AlertRule
|
||||
messages []string
|
||||
time time.Time
|
||||
fields []*regexField
|
||||
rule *config.AlertRule
|
||||
messages []string
|
||||
alertNumber uint64
|
||||
time time.Time
|
||||
fields []*regexField
|
||||
}
|
||||
|
||||
func NewAlert(
|
||||
@@ -64,9 +66,10 @@ func (a *alert) Analyze(entry *Entry) {
|
||||
continue
|
||||
}
|
||||
groupName := ""
|
||||
alertNumber := uint64(0)
|
||||
messages := []string{}
|
||||
if rule.Group != nil {
|
||||
alertGroup, err := a.alertGroupService.Analyze(rule.Group, entry.Time, entry.Message)
|
||||
alertGroup, err := a.alertGroupService.Analyze(rule.Group, entry.Time, entry.Message, result.partition)
|
||||
if err != nil {
|
||||
a.logger.Error(fmt.Sprintf("Failed to analyze alert group: %s", err))
|
||||
continue
|
||||
@@ -79,15 +82,17 @@ func (a *alert) Analyze(entry *Entry) {
|
||||
for _, lastLog := range alertGroup.LastLogs {
|
||||
messages = append(messages, lastLog)
|
||||
}
|
||||
alertNumber = alertGroup.AlertNumber
|
||||
} else {
|
||||
messages = append(messages, entry.Message)
|
||||
}
|
||||
a.logger.Info(fmt.Sprintf("Alert detected (%s) (group:%s): %s", rule.Name, groupName, entry.Message))
|
||||
a.sendNotify(&alertNotify{
|
||||
rule: rule,
|
||||
messages: messages,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
rule: rule,
|
||||
messages: messages,
|
||||
alertNumber: alertNumber,
|
||||
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 {
|
||||
result := alertAnalyzeRuleReturn{
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
partition: nil,
|
||||
}
|
||||
|
||||
for _, pattern := range rule.Patterns {
|
||||
@@ -112,6 +118,27 @@ func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyze
|
||||
idx := re.FindStringSubmatchIndex(message)
|
||||
|
||||
if idx != nil {
|
||||
if pattern.Partition != nil {
|
||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.Partition.Value), idx)
|
||||
if err != nil {
|
||||
a.logger.Error(fmt.Sprintf("alert. Failed to get partition value: %s", err))
|
||||
continue
|
||||
}
|
||||
partition := pattern.Partition.Normalize(message[start:end])
|
||||
if pattern.Partition.Type != nil {
|
||||
if accepts, partitionCode := pattern.Partition.Type.Accepts(partition); !accepts {
|
||||
continue
|
||||
} else {
|
||||
partition = partitionCode
|
||||
}
|
||||
if pattern.Partition.Type.IsPartitioned() {
|
||||
result.partition = &partition
|
||||
}
|
||||
} else {
|
||||
result.partition = &partition
|
||||
}
|
||||
}
|
||||
|
||||
for _, value := range pattern.Values {
|
||||
start, end, err := getValueStartEndByRegexIndex(int(value.Value), idx)
|
||||
if err != nil {
|
||||
@@ -122,6 +149,9 @@ func (a *alert) analyzeRule(rule *config.AlertRule, message string) alertAnalyze
|
||||
}
|
||||
|
||||
if len(pattern.Values) != len(result.fields) {
|
||||
a.logger.Error(fmt.Sprintf("alert. analyzeRule len(pattern.Values) != len(result.fields): %d != %d", len(pattern.Values), len(result.fields)))
|
||||
result.fields = []*regexField{}
|
||||
result.partition = nil
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -153,6 +183,7 @@ func (a *alert) sendNotify(notify *alertNotify) {
|
||||
text += i18n.Lang.T("time", map[string]any{
|
||||
"Time": notify.time,
|
||||
}) + "\n"
|
||||
|
||||
for _, field := range notify.fields {
|
||||
v := field.value
|
||||
if field.typeValue == config.PatternValueIP {
|
||||
@@ -164,6 +195,11 @@ func (a *alert) sendNotify(notify *alertNotify) {
|
||||
}
|
||||
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{
|
||||
"Count": len(notify.messages),
|
||||
}) + "\n"
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type Group interface {
|
||||
Analyze(alertGroup *config.AlertGroup, eventTime time.Time, message string) (AnalysisResult, error)
|
||||
Analyze(alertGroup *config.AlertGroup, eventTime time.Time, message string, partition *string) (AnalysisResult, error)
|
||||
ClearDBData() error
|
||||
}
|
||||
|
||||
@@ -22,8 +22,9 @@ type group struct {
|
||||
}
|
||||
|
||||
type AnalysisResult struct {
|
||||
Alerted bool
|
||||
LastLogs []string
|
||||
Alerted bool
|
||||
AlertNumber uint64
|
||||
LastLogs []string
|
||||
}
|
||||
|
||||
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{
|
||||
Alerted: false,
|
||||
}
|
||||
|
||||
g.logger.Debug(fmt.Sprintf("Analyzing alert group %s", alertGroup.Name))
|
||||
|
||||
err := g.alertGroupRepository.Update(alertGroup.Name, func(entityAlertGroup *entity.AlertGroup) (*entity.AlertGroup, error) {
|
||||
err := g.alertGroupRepository.Update(alertGroup.Name, partition, func(entityAlertGroup *entity.AlertGroup) (*entity.AlertGroup, error) {
|
||||
rateLimit, err := alertGroup.RateLimit(entityAlertGroup.CurrentLevelTriggerCount)
|
||||
if err != nil {
|
||||
return entityAlertGroup, err
|
||||
@@ -101,6 +102,8 @@ func (g *group) analysisResult(rateLimit config.RateLimit, eventTime time.Time,
|
||||
entityAlertGroup.CurrentLevelTriggerCount++
|
||||
entityAlertGroup.TriggerCount = 0
|
||||
entityAlertGroup.LastLogs = []string{}
|
||||
|
||||
analysisResult.AlertNumber = entityAlertGroup.CurrentLevelTriggerCount
|
||||
} else {
|
||||
g.logger.Debug(fmt.Sprintf("Alert not reached rate limit"))
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import (
|
||||
"time"
|
||||
|
||||
"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/firewall/blocking"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
|
||||
@@ -14,6 +15,7 @@ import (
|
||||
"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"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/format"
|
||||
)
|
||||
|
||||
type BruteForceProtection interface {
|
||||
@@ -22,46 +24,51 @@ type BruteForceProtection interface {
|
||||
}
|
||||
|
||||
type bruteForceProtection struct {
|
||||
rulesIndex *RulesIndex
|
||||
groupService brute_force_protection_group.Group
|
||||
blockService brute_force_protection_group.BlockService
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
ipInfo geoip.Info
|
||||
rulesIndex *RulesIndex
|
||||
groupService brute_force_protection_group.Group
|
||||
blockService brute_force_protection_group.BlockService
|
||||
notificationPolicy analysisBruteForceProtection.NotificationPolicy
|
||||
logger log.Logger
|
||||
notify notifications.Notifications
|
||||
ipInfo geoip.Info
|
||||
}
|
||||
|
||||
type bruteForceProtectionAnalyzeRuleReturn struct {
|
||||
found bool
|
||||
fields []*regexField
|
||||
ip net.IP
|
||||
found bool
|
||||
fields []*regexField
|
||||
ip net.IP
|
||||
partition *string
|
||||
}
|
||||
|
||||
type bruteForceProtectionNotify struct {
|
||||
rule *brute_force_protection.Rule
|
||||
messages []string
|
||||
ip net.IP
|
||||
ports []types.L4Port
|
||||
time time.Time
|
||||
fields []*regexField
|
||||
blockSec uint32
|
||||
err error
|
||||
rule *brute_force_protection.Rule
|
||||
messages []string
|
||||
blockIPCount uint64
|
||||
ip net.IP
|
||||
ports []types.L4Port
|
||||
time time.Time
|
||||
fields []*regexField
|
||||
blockSec uint32
|
||||
err error
|
||||
}
|
||||
|
||||
func NewBruteForceProtection(
|
||||
rulesIndex *RulesIndex,
|
||||
groupService brute_force_protection_group.Group,
|
||||
blockService brute_force_protection_group.BlockService,
|
||||
notificationPolicy analysisBruteForceProtection.NotificationPolicy,
|
||||
logger log.Logger,
|
||||
notify notifications.Notifications,
|
||||
ipInfo geoip.Info,
|
||||
) BruteForceProtection {
|
||||
return &bruteForceProtection{
|
||||
rulesIndex: rulesIndex,
|
||||
groupService: groupService,
|
||||
blockService: blockService,
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
ipInfo: ipInfo,
|
||||
rulesIndex: rulesIndex,
|
||||
groupService: groupService,
|
||||
blockService: blockService,
|
||||
notificationPolicy: notificationPolicy,
|
||||
logger: logger,
|
||||
notify: notify,
|
||||
ipInfo: ipInfo,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,7 +89,7 @@ func (p *bruteForceProtection) Analyze(entry *Entry) {
|
||||
continue
|
||||
}
|
||||
|
||||
groupResult, err := p.groupService.Analyze(rule.Group, entry.Time, result.ip, entry.Message)
|
||||
groupResult, err := p.groupService.Analyze(rule.Group, entry.Time, result.ip, entry.Message, result.partition)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to analyze brute force protection group: %s", err))
|
||||
continue
|
||||
@@ -121,26 +128,28 @@ func (p *bruteForceProtection) handleBlockIP(
|
||||
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.sendNotifyError(&bruteForceProtectionNotify{
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
messages: groupResult.LastLogs,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
messages: groupResult.LastLogs,
|
||||
blockIPCount: groupResult.BlockIPCount,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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{
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
messages: groupResult.LastLogs,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
messages: groupResult.LastLogs,
|
||||
blockIPCount: groupResult.BlockIPCount,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -161,36 +170,39 @@ func (p *bruteForceProtection) handleBlockIPWithPorts(
|
||||
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.sendNotifyError(&bruteForceProtectionNotify{
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
ports: l4Ports,
|
||||
messages: groupResult.LastLogs,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
ports: l4Ports,
|
||||
messages: groupResult.LastLogs,
|
||||
blockIPCount: groupResult.BlockIPCount,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
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{
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
ports: l4Ports,
|
||||
messages: groupResult.LastLogs,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
rule: rule,
|
||||
ip: result.ip,
|
||||
ports: l4Ports,
|
||||
messages: groupResult.LastLogs,
|
||||
blockIPCount: groupResult.BlockIPCount,
|
||||
time: entry.Time,
|
||||
fields: result.fields,
|
||||
blockSec: groupResult.BlockSec,
|
||||
err: err,
|
||||
})
|
||||
}
|
||||
|
||||
func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, message string) bruteForceProtectionAnalyzeRuleReturn {
|
||||
result := bruteForceProtectionAnalyzeRuleReturn{
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
ip: nil,
|
||||
found: false,
|
||||
fields: []*regexField{},
|
||||
ip: nil,
|
||||
partition: nil,
|
||||
}
|
||||
|
||||
for _, pattern := range rule.Patterns {
|
||||
@@ -206,14 +218,35 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.IP), idx)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to get ip value: %s", err))
|
||||
return result
|
||||
continue
|
||||
}
|
||||
ipText := message[start:end]
|
||||
result.ip = net.ParseIP(ipText)
|
||||
if result.ip == nil {
|
||||
ip := net.ParseIP(ipText)
|
||||
if ip == nil {
|
||||
p.logger.Error(fmt.Sprintf("Failed to parse ip: %s", ipText))
|
||||
return bruteForceProtectionAnalyzeRuleReturn{
|
||||
found: false,
|
||||
continue
|
||||
}
|
||||
result.ip = ip
|
||||
|
||||
if pattern.Partition != nil {
|
||||
start, end, err := getValueStartEndByRegexIndex(int(pattern.Partition.Value), idx)
|
||||
if err != nil {
|
||||
p.logger.Error(fmt.Sprintf("brute_force_protection. Failed to get partition value: %s", err))
|
||||
result.ip = nil
|
||||
continue
|
||||
}
|
||||
partition := pattern.Partition.Normalize(message[start:end])
|
||||
if pattern.Partition.Type != nil {
|
||||
if accepts, partitionCode := pattern.Partition.Type.Accepts(partition); !accepts {
|
||||
continue
|
||||
} else {
|
||||
partition = partitionCode
|
||||
}
|
||||
if pattern.Partition.Type.IsPartitioned() {
|
||||
result.partition = &partition
|
||||
}
|
||||
} else {
|
||||
result.partition = &partition
|
||||
}
|
||||
}
|
||||
|
||||
@@ -227,6 +260,10 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
||||
}
|
||||
|
||||
if len(pattern.Values) != len(result.fields) {
|
||||
p.logger.Error(fmt.Sprintf("brute_force_protection. analyzeRule len(pattern.Values) != len(result.fields): %d != %d", len(pattern.Values), len(result.fields)))
|
||||
result.fields = []*regexField{}
|
||||
result.partition = nil
|
||||
result.ip = nil
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -239,7 +276,7 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
|
||||
}
|
||||
|
||||
func (p *bruteForceProtection) sendNotifySuccess(notify *bruteForceProtectionNotify) {
|
||||
if !notify.rule.IsNotification {
|
||||
if !p.notificationPolicy.IsNotify(notify.rule) {
|
||||
return
|
||||
}
|
||||
|
||||
@@ -301,7 +338,7 @@ func (p *bruteForceProtection) sendNotify(subject string, notify *bruteForceProt
|
||||
}) + "\n"
|
||||
}
|
||||
text += i18n.Lang.T("blockSec", map[string]any{
|
||||
"BlockSec": notify.blockSec,
|
||||
"BlockSec": format.HumanDuration(time.Duration(notify.blockSec) * time.Second),
|
||||
}) + "\n"
|
||||
text += i18n.Lang.T("time", map[string]any{
|
||||
"Time": notify.time,
|
||||
@@ -309,6 +346,9 @@ func (p *bruteForceProtection) sendNotify(subject string, notify *bruteForceProt
|
||||
for _, field := range notify.fields {
|
||||
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{
|
||||
"Count": len(notify.messages),
|
||||
}) + "\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 {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -23,10 +23,11 @@ type group struct {
|
||||
}
|
||||
|
||||
type AnalysisResult struct {
|
||||
Block bool
|
||||
BlockSec uint32
|
||||
BlockConfig brute_force_protection.Block
|
||||
LastLogs []string
|
||||
Block bool
|
||||
BlockSec uint32
|
||||
BlockConfig brute_force_protection.Block
|
||||
BlockIPCount uint64
|
||||
LastLogs []string
|
||||
}
|
||||
|
||||
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{
|
||||
Block: false,
|
||||
}
|
||||
|
||||
g.logger.Debug(fmt.Sprintf("Analyzing brute force protection group %s IP %s", group.Name, ip.String()))
|
||||
|
||||
err := g.groupRepository.Update(group.Name, ip, func(entityGroup *entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error) {
|
||||
err := g.groupRepository.Update(group.Name, ip, partition, func(entityGroup *entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error) {
|
||||
rateLimit, err := group.RateLimit(entityGroup.CurrentLevelTriggerCount)
|
||||
if err != nil {
|
||||
return entityGroup, err
|
||||
@@ -106,6 +107,8 @@ func (g *group) analysisResult(rateLimit brute_force_protection.RateLimit, event
|
||||
entityGroup.CurrentLevelTriggerCount++
|
||||
entityGroup.TriggerCount = 0
|
||||
entityGroup.LastLogs = []string{}
|
||||
|
||||
analysisResult.BlockIPCount = entityGroup.CurrentLevelTriggerCount
|
||||
} else {
|
||||
g.logger.Debug(fmt.Sprintf("Brute force protection not reached rate limit"))
|
||||
}
|
||||
|
||||
@@ -3,19 +3,20 @@ package blocklist
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"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/daemon/firewall/chain/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/filesystem"
|
||||
)
|
||||
|
||||
type newBlocklist func(name string) (block.Blocklist, error)
|
||||
|
||||
type Blocklist interface {
|
||||
NftReload(newBlocklist newBlocklist) error
|
||||
Names() []string
|
||||
NftReload(blocks map[string]block.Blocklist) error
|
||||
Run()
|
||||
Close() error
|
||||
}
|
||||
@@ -26,7 +27,8 @@ type updateSource struct {
|
||||
}
|
||||
|
||||
type blocklist struct {
|
||||
Sources []*SourceConfig
|
||||
pathDir string
|
||||
sources []*SourceConfig
|
||||
blocklistRepository repository.BlocklistRepository
|
||||
logger log.Logger
|
||||
|
||||
@@ -41,8 +43,17 @@ type blocklist struct {
|
||||
}
|
||||
|
||||
func New(config Config, ctx context.Context, logger log.Logger) (Blocklist, error) {
|
||||
if config.PathDir == "" {
|
||||
return nil, fmt.Errorf("pathDir is empty")
|
||||
}
|
||||
|
||||
if err := filesystem.EnsureDir(config.PathDir); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &blocklist{
|
||||
Sources: config.Sources,
|
||||
pathDir: config.PathDir,
|
||||
sources: config.Sources,
|
||||
blocklistRepository: config.BlocklistRepository,
|
||||
logger: logger,
|
||||
ctx: ctx,
|
||||
@@ -54,34 +65,41 @@ func New(config Config, ctx context.Context, logger log.Logger) (Blocklist, erro
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *blocklist) NftReload(newBlocklist newBlocklist) error {
|
||||
func (b *blocklist) Names() []string {
|
||||
var names []string
|
||||
for _, source := range b.sources {
|
||||
if source.Name != "" {
|
||||
names = append(names, source.Name)
|
||||
}
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (b *blocklist) NftReload(blocks map[string]block.Blocklist) error {
|
||||
b.logger.Debug("Reload blocklist")
|
||||
for _, source := range b.Sources {
|
||||
b.logger.Debug(fmt.Sprintf("Reload blocklist from %s", source.Name))
|
||||
if source.Name == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
nftBlocklist, err := newBlocklist("blocklist_" + source.Name)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to create blocklist: %s", err))
|
||||
continue
|
||||
}
|
||||
b.mu.Lock()
|
||||
b.nftBlocklists = blocks
|
||||
b.mu.Unlock()
|
||||
|
||||
b.mu.Lock()
|
||||
b.nftBlocklists[source.Name] = nftBlocklist
|
||||
b.mu.Unlock()
|
||||
for _, source := range b.sources {
|
||||
if nftBlocklist, ok := b.nftBlocklists[source.Name]; ok {
|
||||
if listEntity, err := b.blocklistRepository.Get(source.Name); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist %s: %s", source.Name, err))
|
||||
} else if b.isFresh(source, listEntity) {
|
||||
file, err := b.pathFile(source)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist file path: %s", err))
|
||||
continue
|
||||
}
|
||||
|
||||
if listEntity, err := b.blocklistRepository.Get(source.Name); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist %s: %s", source.Name, err))
|
||||
} else if listEntity.IsFresh(source.Interval) {
|
||||
if err := nftBlocklist.ReplaceElementsIPv4(listEntity.IPsV4); len(listEntity.IPsV4) > 0 && err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv4): %s", err))
|
||||
}
|
||||
|
||||
if err := nftBlocklist.ReplaceElementsIPv6(listEntity.IPsV6); len(listEntity.IPsV6) > 0 && err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv6): %s", err))
|
||||
if err := nftBlocklist.ReplaceElementsWithFile(file); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements with file %s: %s", file, err))
|
||||
continue
|
||||
}
|
||||
}
|
||||
} else {
|
||||
b.logger.Error(fmt.Sprintf("NFTables sets blocklist %s not found", source.Name))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -98,7 +116,7 @@ func (b *blocklist) Run() {
|
||||
b.ctx, b.cancel = context.WithCancel(b.ctx)
|
||||
go b.processUpdateData(b.ctx)
|
||||
|
||||
for _, src := range b.Sources {
|
||||
for _, src := range b.sources {
|
||||
if src == nil || src.Name == "" {
|
||||
continue
|
||||
}
|
||||
@@ -158,7 +176,7 @@ func (b *blocklist) processUpdateData(ctx context.Context) {
|
||||
if listEntity, err := b.blocklistRepository.Get(updSource.source.Name); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist %s: %s", updSource.source.Name, err))
|
||||
continue
|
||||
} else if listEntity.IsFresh(updSource.source.Interval) {
|
||||
} else if b.isFresh(updSource.source, listEntity) {
|
||||
b.logger.Debug(fmt.Sprintf("blocklist %s is fresh", updSource.source.Name))
|
||||
continue
|
||||
}
|
||||
@@ -178,24 +196,24 @@ func (b *blocklist) refreshSource(sourceConfig *SourceConfig) {
|
||||
if nftBlocklist, ok := b.nftBlocklists[sourceConfig.Name]; ok {
|
||||
listEntity := &entity.Blocklist{
|
||||
UpdatedAtUnix: time.Now().Unix(),
|
||||
IPsV4: nil,
|
||||
IPsV6: nil,
|
||||
}
|
||||
|
||||
if len(ipsV4) > 0 {
|
||||
if err := nftBlocklist.ReplaceElementsIPv4(ipsV4); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv4): %s", err))
|
||||
} else {
|
||||
listEntity.IPsV4 = ipsV4
|
||||
}
|
||||
if err := filesystem.EnsureDir(b.pathDir); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to ensure dir: %s", err))
|
||||
}
|
||||
file, err := b.pathFile(sourceConfig)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist file path: %s", err))
|
||||
return
|
||||
}
|
||||
|
||||
if len(ipsV6) > 0 {
|
||||
if err := nftBlocklist.ReplaceElementsIPv6(ipsV6); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements (IPv6): %s", err))
|
||||
} else {
|
||||
listEntity.IPsV6 = ipsV6
|
||||
}
|
||||
if err := nftBlocklist.ReplaceElements(ipsV4, ipsV6, file); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to replace elements: %s", err))
|
||||
}
|
||||
listEntity.Checksum, err = filesystem.FileChecksum(file)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to calculate checksum for %s: %s", file, err))
|
||||
return
|
||||
}
|
||||
|
||||
if err := b.blocklistRepository.Update(sourceConfig.Name, listEntity); err != nil {
|
||||
@@ -220,3 +238,43 @@ func (b *blocklist) Close() error {
|
||||
close(b.launchChannel)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *blocklist) pathFile(sourceConfig *SourceConfig) (string, error) {
|
||||
if sourceConfig == nil {
|
||||
return "", fmt.Errorf("sourceConfig is nil")
|
||||
}
|
||||
|
||||
if sourceConfig.Name == "" {
|
||||
return "", fmt.Errorf("sourceConfig.Name is empty")
|
||||
}
|
||||
|
||||
return strings.TrimRight(b.pathDir, "/") + "/" + sourceConfig.Name + ".nft", nil
|
||||
}
|
||||
|
||||
func (b *blocklist) isFresh(sourceConfig *SourceConfig, listEntity *entity.Blocklist) bool {
|
||||
if !listEntity.IsFresh(sourceConfig.Interval) {
|
||||
return false
|
||||
}
|
||||
|
||||
file, err := b.pathFile(sourceConfig)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to get blocklist file path: %s", err))
|
||||
return false
|
||||
}
|
||||
if !filesystem.FileExists(file) {
|
||||
b.logger.Warn(fmt.Sprintf("Blocklist file %s not found", file))
|
||||
return false
|
||||
}
|
||||
|
||||
fileChecksum, err := filesystem.FileChecksum(file)
|
||||
if err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to calculate checksum for %s: %s", file, err))
|
||||
return false
|
||||
}
|
||||
if listEntity.Checksum != fileChecksum {
|
||||
b.logger.Error(fmt.Sprintf("Blocklist file %s checksum is not equal to database checksum", file))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ import (
|
||||
type Config struct {
|
||||
BlocklistRepository repository.BlocklistRepository
|
||||
Sources []*SourceConfig
|
||||
PathDir string
|
||||
}
|
||||
|
||||
type SourceConfig struct {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package blocklist
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
|
||||
type FalseBlocklist struct {
|
||||
}
|
||||
|
||||
@@ -7,7 +9,11 @@ func NewFalseBlocklist() Blocklist {
|
||||
return &FalseBlocklist{}
|
||||
}
|
||||
|
||||
func (b *FalseBlocklist) NftReload(_ newBlocklist) error {
|
||||
func (b *FalseBlocklist) Names() []string {
|
||||
return []string{}
|
||||
}
|
||||
|
||||
func (b *FalseBlocklist) NftReload(_ map[string]block.Blocklist) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
+29
-21
@@ -15,8 +15,10 @@ import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/blocking"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/guard"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/geoip"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/info"
|
||||
"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/pidfile"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/socket"
|
||||
@@ -32,26 +34,21 @@ type Daemon interface {
|
||||
}
|
||||
|
||||
type daemon struct {
|
||||
info DaemonInfo
|
||||
pidFile pidfile.PidFile
|
||||
socket socket.Socket
|
||||
logger log.Logger
|
||||
firewall firewall.API
|
||||
notifications notifications.Notifications
|
||||
analyzer analyzer.Analyzer
|
||||
docker docker_monitor.Docker
|
||||
blocklist blocklist.Blocklist
|
||||
geoIPService geoip.GeoIP
|
||||
info info.Info
|
||||
pidFile pidfile.PidFile
|
||||
socket socket.Socket
|
||||
logger log.Logger
|
||||
firewall firewall.API
|
||||
firewallRulesGuard guard.RulesGuard
|
||||
notifications notifications.Notifications
|
||||
analyzer analyzer.Analyzer
|
||||
docker docker_monitor.Docker
|
||||
blocklist blocklist.Blocklist
|
||||
geoIPService geoip.GeoIP
|
||||
|
||||
stopCh chan struct{}
|
||||
}
|
||||
|
||||
type DaemonInfo struct {
|
||||
Ver string
|
||||
BuiltWith string
|
||||
StartTime time.Time
|
||||
}
|
||||
|
||||
func (d *daemon) Run(ctx context.Context, isTesting bool, testingInterval uint16) error {
|
||||
if err := d.pidFile.EnsureNoOtherProcess(); err != nil {
|
||||
return err
|
||||
@@ -59,11 +56,15 @@ func (d *daemon) Run(ctx context.Context, isTesting bool, testingInterval uint16
|
||||
if err := d.socket.EnsureNoOtherProcess(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := d.firewall.Reload(); err != nil {
|
||||
if err := d.firewall.Reload(d.info); err != nil {
|
||||
d.firewall.ClearRules()
|
||||
return err
|
||||
}
|
||||
d.firewall.SavesRules()
|
||||
d.firewallRulesGuard.Run(d.info, ctx)
|
||||
defer func() {
|
||||
_ = d.firewallRulesGuard.Close()
|
||||
}()
|
||||
|
||||
if err := d.pidFile.Create(); err != nil {
|
||||
return err
|
||||
@@ -164,12 +165,18 @@ func (d *daemon) socketCommand(command string, args map[string]string, socket so
|
||||
return socket.Write("ok")
|
||||
|
||||
case "status":
|
||||
uptime := time.Since(d.info.StartTime)
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
|
||||
hasRules, err := d.firewall.HasRules()
|
||||
nftables := "\033[32mOk\033[0m"
|
||||
if err != nil || !hasRules {
|
||||
nftables = "\033[31mError\033[0m"
|
||||
}
|
||||
|
||||
text := fmt.Sprintf(
|
||||
"ok\n\n***\n"+
|
||||
"NFTables: %s\n"+
|
||||
"Version: %s\n"+
|
||||
"BuiltWith: %s\n"+
|
||||
"Uptime: %s\n"+
|
||||
@@ -180,9 +187,10 @@ func (d *daemon) socketCommand(command string, args map[string]string, socket so
|
||||
"HeapSys: %s\n"+
|
||||
"NumGC: %d\n"+
|
||||
"***\n",
|
||||
d.info.Ver,
|
||||
d.info.BuiltWith,
|
||||
uptime,
|
||||
nftables,
|
||||
d.info.Version(),
|
||||
d.info.BuiltWith(),
|
||||
format.HumanDuration(d.info.Uptime()),
|
||||
runtime.NumGoroutine(),
|
||||
format.HumanBytes(m.Alloc), // Alloc is the total bytes of allocated heap objects.
|
||||
format.HumanBytes(m.HeapAlloc), // HeapAlloc is the total bytes of heap memory obtained from the OS.
|
||||
|
||||
+24
-10
@@ -18,18 +18,22 @@ type Repositories interface {
|
||||
NotificationsQueue() repository.NotificationsQueueRepository
|
||||
AlertGroup() repository.AlertGroupRepository
|
||||
BruteForceProtectionGroup() repository.BruteForceProtectionGroupRepository
|
||||
BruteForceProtectionNotifyPolicy() repository.BruteForceProtectionNotifyPolicyRepository
|
||||
Blocking() repository.BlockingRepository
|
||||
Blocklist() repository.BlocklistRepository
|
||||
Metadata() repository.MetadataRepository
|
||||
|
||||
Close() error
|
||||
}
|
||||
|
||||
type repositories struct {
|
||||
notificationsQueue repository.NotificationsQueueRepository
|
||||
alertGroup repository.AlertGroupRepository
|
||||
bruteForceProtectionGroup repository.BruteForceProtectionGroupRepository
|
||||
blocking repository.BlockingRepository
|
||||
blocklist repository.BlocklistRepository
|
||||
notificationsQueue repository.NotificationsQueueRepository
|
||||
alertGroup repository.AlertGroupRepository
|
||||
bruteForceProtectionGroup repository.BruteForceProtectionGroupRepository
|
||||
bruteForceProtectionNotifyPolicy repository.BruteForceProtectionNotifyPolicyRepository
|
||||
blocking repository.BlockingRepository
|
||||
blocklist repository.BlocklistRepository
|
||||
metadata repository.MetadataRepository
|
||||
|
||||
db []*bbolt.DB
|
||||
}
|
||||
@@ -55,11 +59,13 @@ func New(dataDir string) (Repositories, error) {
|
||||
securityDB, err := bbolt.Open(dataDir+securityDB, 0600, &bbolt.Options{Timeout: 3 * time.Second})
|
||||
|
||||
return &repositories{
|
||||
notificationsQueue: repository.NewNotificationsQueueRepository(appDB),
|
||||
alertGroup: repository.NewAlertGroupRepository(appDB),
|
||||
bruteForceProtectionGroup: repository.NewBruteForceProtectionGroupRepository(securityDB),
|
||||
blocking: repository.NewBlockingRepository(securityDB),
|
||||
blocklist: repository.NewBlocklistRepository(securityDB),
|
||||
notificationsQueue: repository.NewNotificationsQueueRepository(appDB),
|
||||
alertGroup: repository.NewAlertGroupRepository(appDB),
|
||||
bruteForceProtectionGroup: repository.NewBruteForceProtectionGroupRepository(securityDB),
|
||||
bruteForceProtectionNotifyPolicy: repository.NewBruteForceProtectionNotifyPolicyRepository(securityDB),
|
||||
blocking: repository.NewBlockingRepository(securityDB),
|
||||
blocklist: repository.NewBlocklistRepository(securityDB),
|
||||
metadata: repository.NewMetadataRepository(appDB),
|
||||
|
||||
db: []*bbolt.DB{appDB, securityDB},
|
||||
}, nil
|
||||
@@ -77,6 +83,10 @@ func (r *repositories) BruteForceProtectionGroup() repository.BruteForceProtecti
|
||||
return r.bruteForceProtectionGroup
|
||||
}
|
||||
|
||||
func (r *repositories) BruteForceProtectionNotifyPolicy() repository.BruteForceProtectionNotifyPolicyRepository {
|
||||
return r.bruteForceProtectionNotifyPolicy
|
||||
}
|
||||
|
||||
func (r *repositories) Blocking() repository.BlockingRepository {
|
||||
return r.blocking
|
||||
}
|
||||
@@ -85,6 +95,10 @@ func (r *repositories) Blocklist() repository.BlocklistRepository {
|
||||
return r.blocklist
|
||||
}
|
||||
|
||||
func (r *repositories) Metadata() repository.MetadataRepository {
|
||||
return r.metadata
|
||||
}
|
||||
|
||||
func (r *repositories) Close() error {
|
||||
for _, db := range r.db {
|
||||
_ = db.Close()
|
||||
|
||||
@@ -5,13 +5,16 @@ import (
|
||||
)
|
||||
|
||||
type Blocklist struct {
|
||||
UpdatedAtUnix int64 `json:"UpdateAtUnix"`
|
||||
IPsV4 []string `json:"IPsV4"`
|
||||
IPsV6 []string `json:"IPsV6"`
|
||||
UpdatedAtUnix int64 `json:"UpdateAtUnix"`
|
||||
Checksum string `json:"checksum"`
|
||||
}
|
||||
|
||||
// IsFresh returns true if the blocklist is fresh.
|
||||
func (b *Blocklist) IsFresh(interval time.Duration) bool {
|
||||
if b.Checksum == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
lastUpdate := time.Unix(b.UpdatedAtUnix, 0)
|
||||
return b.UpdatedAtUnix > 0 && time.Since(lastUpdate) <= interval
|
||||
}
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
package entity
|
||||
|
||||
type BruteForceProtectionNotifyPolicy struct {
|
||||
CooldownTime int64
|
||||
Every uint32
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package entity
|
||||
|
||||
const (
|
||||
MetadataKeyVersion = "Version"
|
||||
MetadataKeyFirewallFileNft = "firewall-file-nft" // checksum of the firewall file
|
||||
)
|
||||
|
||||
type Metadata struct {
|
||||
Value string `json:"Value"`
|
||||
}
|
||||
|
||||
func KeySetting(name string) string {
|
||||
return "setting-" + name
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -11,7 +12,7 @@ import (
|
||||
)
|
||||
|
||||
type AlertGroupRepository interface {
|
||||
Update(name string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error
|
||||
Update(name string, partition *string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error
|
||||
Clear() error
|
||||
}
|
||||
|
||||
@@ -27,7 +28,7 @@ func NewAlertGroupRepository(appDB *bbolt.DB) AlertGroupRepository {
|
||||
}
|
||||
}
|
||||
|
||||
func (r *alertGroupRepository) Update(name string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error {
|
||||
func (r *alertGroupRepository) Update(name string, partition *string, f func(*entity.AlertGroup) (*entity.AlertGroup, error)) error {
|
||||
entityAlertGroup := &entity.AlertGroup{}
|
||||
entityAlertGroup.Reset()
|
||||
|
||||
@@ -36,7 +37,10 @@ func (r *alertGroupRepository) Update(name string, f func(*entity.AlertGroup) (*
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := []byte(name)
|
||||
key, err := keyGroup(name, partition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
group := b.Get(key)
|
||||
if group != nil {
|
||||
@@ -70,3 +74,21 @@ func (r *alertGroupRepository) Clear() error {
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
func keyGroup(groupID string, partition *string) ([]byte, error) {
|
||||
if len(groupID) == 0 {
|
||||
return nil, fmt.Errorf("group id cannot be empty")
|
||||
}
|
||||
|
||||
if partition == nil {
|
||||
return []byte(groupID), nil
|
||||
}
|
||||
|
||||
partitionHash := sha256.Sum256([]byte(*partition))
|
||||
|
||||
k := make([]byte, 0, len(groupID)+1+len(partitionHash))
|
||||
k = append(k, groupID...)
|
||||
k = append(k, 0x00)
|
||||
k = append(k, partitionHash[:]...)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
@@ -12,7 +13,7 @@ import (
|
||||
)
|
||||
|
||||
type BruteForceProtectionGroupRepository interface {
|
||||
Update(name string, ip net.IP, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error
|
||||
Update(name string, ip net.IP, partition *string, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error
|
||||
Clear() error
|
||||
}
|
||||
|
||||
@@ -28,7 +29,7 @@ func NewBruteForceProtectionGroupRepository(appDB *bbolt.DB) BruteForceProtectio
|
||||
}
|
||||
}
|
||||
|
||||
func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error {
|
||||
func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, partition *string, f func(*entity.BruteForceProtectionGroup) (*entity.BruteForceProtectionGroup, error)) error {
|
||||
entityGroup := &entity.BruteForceProtectionGroup{}
|
||||
entityGroup.Reset()
|
||||
|
||||
@@ -37,7 +38,7 @@ func (r *bruteForceProtectionGroupRepository) Update(name string, ip net.IP, f f
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key, err := keyGroupIP(name, ip)
|
||||
key, err := keyGroupIP(name, ip, partition)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -75,7 +76,7 @@ func (r *bruteForceProtectionGroupRepository) Clear() error {
|
||||
})
|
||||
}
|
||||
|
||||
func keyGroupIP(groupID string, ip net.IP) ([]byte, error) {
|
||||
func keyGroupIP(groupID string, ip net.IP, partition *string) ([]byte, error) {
|
||||
if ip == nil {
|
||||
return nil, fmt.Errorf("ip cannot be nil")
|
||||
}
|
||||
@@ -95,6 +96,17 @@ func keyGroupIP(groupID string, ip net.IP) ([]byte, error) {
|
||||
ipAddr = ip.To16()
|
||||
}
|
||||
|
||||
if partition != nil {
|
||||
partitionHash := sha256.Sum256([]byte(*partition))
|
||||
k := make([]byte, 0, len(groupID)+1+len(ipAddr)+1+len(partitionHash))
|
||||
k = append(k, groupID...)
|
||||
k = append(k, 0x00)
|
||||
k = append(k, ipAddr...)
|
||||
k = append(k, 0x00)
|
||||
k = append(k, partitionHash[:]...)
|
||||
return k, nil
|
||||
}
|
||||
|
||||
k := make([]byte, 0, len(groupID)+1+len(ipAddr))
|
||||
k = append(k, groupID...)
|
||||
k = append(k, 0x00)
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package repository
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/entity"
|
||||
"go.etcd.io/bbolt"
|
||||
)
|
||||
|
||||
type MetadataRepository interface {
|
||||
Get(name string) (*entity.Metadata, error)
|
||||
Update(name string, entity *entity.Metadata) error
|
||||
}
|
||||
|
||||
type metadataRepository struct {
|
||||
db *bbolt.DB
|
||||
bucket string
|
||||
}
|
||||
|
||||
func NewMetadataRepository(appDB *bbolt.DB) MetadataRepository {
|
||||
return &metadataRepository{
|
||||
db: appDB,
|
||||
bucket: metadataBucket,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *metadataRepository) Get(name string) (*entity.Metadata, error) {
|
||||
metadataEntity := &entity.Metadata{}
|
||||
|
||||
err := r.db.View(func(tx *bbolt.Tx) error {
|
||||
bucket := tx.Bucket([]byte(r.bucket))
|
||||
if bucket == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
data := bucket.Get([]byte(name))
|
||||
if data == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return json.Unmarshal(data, metadataEntity)
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return metadataEntity, err
|
||||
}
|
||||
|
||||
func (r *metadataRepository) Update(name string, entity *entity.Metadata) error {
|
||||
return r.db.Update(func(tx *bbolt.Tx) error {
|
||||
b, err := tx.CreateBucketIfNotExists([]byte(r.bucket))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
key := []byte(name)
|
||||
|
||||
data, err := json.Marshal(entity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return b.Put(key, data)
|
||||
})
|
||||
}
|
||||
@@ -8,11 +8,13 @@ import (
|
||||
)
|
||||
|
||||
const (
|
||||
notificationsQueueBucket = "notifications_queue"
|
||||
alertGroupBucket = "alert_group"
|
||||
bruteForceProtectionGroupBucket = "brute_force_protection_group"
|
||||
blockingBucket = "blocking"
|
||||
blocklistBucket = "blocklist"
|
||||
notificationsQueueBucket = "notifications_queue"
|
||||
alertGroupBucket = "alert_group"
|
||||
bruteForceProtectionGroupBucket = "brute_force_protection_group"
|
||||
bruteForceProtectionNotifyPolicyBucket = "brute_force_protection_notify_policy"
|
||||
blockingBucket = "blocking"
|
||||
blocklistBucket = "blocklist"
|
||||
metadataBucket = "metadata"
|
||||
)
|
||||
|
||||
func nextID(b *bbolt.Bucket) ([]byte, error) {
|
||||
|
||||
@@ -1,87 +0,0 @@
|
||||
package chain
|
||||
|
||||
import nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
|
||||
type Chains interface {
|
||||
ForwardFilterJump(addRule func(expr ...string) error) error
|
||||
PreroutingFilterJump(addRule func(expr ...string) error) error
|
||||
|
||||
PreroutingNatJump(addRule func(expr ...string) error) error
|
||||
OutputNatJump(addRule func(expr ...string) error) error
|
||||
PostroutingNatJump(addRule func(expr ...string) error) error
|
||||
|
||||
List() *chains
|
||||
}
|
||||
|
||||
type chains struct {
|
||||
ForwardFilter Data
|
||||
ForwardBridge Data
|
||||
ForwardCT Data
|
||||
|
||||
PreroutingFilter Data
|
||||
DockerFilter Data
|
||||
DockerFilterFirst Data
|
||||
DockerFilterSecond Data
|
||||
|
||||
DockerNat Data
|
||||
PostroutingNat Data
|
||||
}
|
||||
|
||||
type Data struct {
|
||||
chain nftChain.Chain
|
||||
name string
|
||||
}
|
||||
|
||||
func (d *chains) ForwardFilterJump(addRule func(expr ...string) error) error {
|
||||
return d.ForwardFilter.Jump(addRule, "")
|
||||
}
|
||||
|
||||
func (d *chains) PreroutingFilterJump(addRule func(expr ...string) error) error {
|
||||
return d.PreroutingFilter.Jump(addRule, "")
|
||||
}
|
||||
|
||||
func (d *chains) PreroutingNatJump(addRule func(expr ...string) error) error {
|
||||
return d.DockerNat.Jump(addRule, "fib daddr type local counter")
|
||||
}
|
||||
|
||||
func (d *chains) OutputNatJump(addRule func(expr ...string) error) error {
|
||||
if err := d.DockerNat.Jump(addRule, "ip daddr != 127.0.0.0/8 fib daddr type local counter"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return d.DockerNat.Jump(addRule, "ip6 daddr != ::1 fib daddr type local counter")
|
||||
}
|
||||
|
||||
func (d *chains) PostroutingNatJump(addRule func(expr ...string) error) error {
|
||||
return d.PostroutingNat.Jump(addRule, "")
|
||||
}
|
||||
|
||||
func (d *chains) List() *chains {
|
||||
return d
|
||||
}
|
||||
|
||||
func (d *Data) Jump(addRule func(expr ...string) error, rule string) error {
|
||||
args := []string{rule, "jump", d.name}
|
||||
return addRule(args...)
|
||||
}
|
||||
|
||||
func (d *Data) JumpTo(data *Data, rule string, comment string) error {
|
||||
args := []string{rule, "jump", d.name, comment}
|
||||
return data.AddRule(args...)
|
||||
}
|
||||
|
||||
func (d *Data) AddRule(rule ...string) error {
|
||||
return d.chain.AddRule(rule...)
|
||||
}
|
||||
|
||||
func (d *Data) RemoveRuleByHandle(handle uint64) error {
|
||||
return d.chain.RemoveRuleByHandle(handle)
|
||||
}
|
||||
|
||||
func (d *Data) ListRules() ([]nftChain.Rule, error) {
|
||||
return d.chain.ListRules()
|
||||
}
|
||||
|
||||
func (d *Data) Clear() error {
|
||||
return d.chain.Clear()
|
||||
}
|
||||
@@ -1,32 +0,0 @@
|
||||
package chain
|
||||
|
||||
type emptyChains struct {
|
||||
}
|
||||
|
||||
func NewEmptyChains() Chains {
|
||||
return &emptyChains{}
|
||||
}
|
||||
|
||||
func (c *emptyChains) ForwardFilterJump(_ func(expr ...string) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *emptyChains) PreroutingFilterJump(_ func(expr ...string) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *emptyChains) PreroutingNatJump(_ func(expr ...string) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *emptyChains) OutputNatJump(_ func(expr ...string) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *emptyChains) PostroutingNatJump(_ func(expr ...string) error) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *emptyChains) List() *chains {
|
||||
return &chains{}
|
||||
}
|
||||
@@ -1,77 +0,0 @@
|
||||
package chain
|
||||
|
||||
import nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
|
||||
func NewChains(newNoneChain func(chain string) (nftChain.Chain, error)) (Chains, error) {
|
||||
chainsData := &chains{}
|
||||
|
||||
if data, err := newChainData("docker_nat", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.DockerNat = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_postrouting_nat", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.PostroutingNat = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_prerouting_filter", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.PreroutingFilter = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_filter", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.DockerFilter = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_filter_first", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.DockerFilterFirst = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_filter_second", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.DockerFilterSecond = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_forward_filter", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.ForwardFilter = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_forward_bridge", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.ForwardBridge = data
|
||||
}
|
||||
|
||||
if data, err := newChainData("docker_forward_ct", newNoneChain); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
chainsData.ForwardCT = data
|
||||
}
|
||||
|
||||
return chainsData, nil
|
||||
}
|
||||
|
||||
func newChainData(chainName string, newNoneChain func(chain string) (nftChain.Chain, error)) (Data, error) {
|
||||
data := Data{
|
||||
name: chainName,
|
||||
}
|
||||
|
||||
newChain, err := newNoneChain(data.name)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
data.chain = newChain
|
||||
return data, nil
|
||||
}
|
||||
@@ -3,16 +3,14 @@ package docker_monitor
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/client"
|
||||
"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/rule_strategy"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type Docker interface {
|
||||
NftReload(newNoneChain func(chain string) (nftChain.Chain, error)) error
|
||||
NftChains() chain.Chains
|
||||
NftReload(nftDocker firewall.NFTDocker) error
|
||||
Run()
|
||||
Close() error
|
||||
}
|
||||
@@ -39,12 +37,8 @@ func New(config *Config, ctx context.Context, logger log.Logger) (Docker, error)
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (d *docker) NftReload(newNoneChain func(chain string) (nftChain.Chain, error)) error {
|
||||
return d.ruleStrategy.Reload(newNoneChain)
|
||||
}
|
||||
|
||||
func (d *docker) NftChains() chain.Chains {
|
||||
return d.ruleStrategy.Chains()
|
||||
func (d *docker) NftReload(nftDocker firewall.NFTDocker) error {
|
||||
return d.ruleStrategy.Reload(nftDocker)
|
||||
}
|
||||
|
||||
func (d *docker) Run() {
|
||||
@@ -66,9 +60,3 @@ func (d *docker) Run() {
|
||||
func (d *docker) Close() error {
|
||||
return d.dockerClient.EventsClose()
|
||||
}
|
||||
|
||||
func (d *docker) chainCommand(chainData chain.Data, rule string) {
|
||||
if err := chainData.AddRule(rule); err != nil {
|
||||
d.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +1,23 @@
|
||||
package docker_monitor
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/chain"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/firewall"
|
||||
)
|
||||
|
||||
type DockerNotSupport struct {
|
||||
chains chain.Chains
|
||||
}
|
||||
type dockerNotSupport struct{}
|
||||
|
||||
func NewDockerNotSupport() Docker {
|
||||
return &DockerNotSupport{
|
||||
chains: chain.NewEmptyChains(),
|
||||
}
|
||||
return &dockerNotSupport{}
|
||||
}
|
||||
|
||||
func (d *DockerNotSupport) NftReload(_ func(chain string) (nftChain.Chain, error)) error {
|
||||
func (d *dockerNotSupport) NftReload(_ firewall.NFTDocker) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *DockerNotSupport) NftChains() chain.Chains {
|
||||
return d.chains
|
||||
}
|
||||
|
||||
func (d *DockerNotSupport) Run() {
|
||||
func (d *dockerNotSupport) Run() {
|
||||
|
||||
}
|
||||
|
||||
func (d *DockerNotSupport) Close() error {
|
||||
func (d *dockerNotSupport) Close() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,128 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
nftFirewall "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/chain"
|
||||
)
|
||||
|
||||
type NFTDocker interface {
|
||||
Chains() NFTDockerChains
|
||||
NFT() nftFirewall.NFT
|
||||
}
|
||||
|
||||
type NFTDockerChains interface {
|
||||
List() []chain.Docker
|
||||
|
||||
ForwardFilter() chain.Docker
|
||||
ForwardBridge() chain.Docker
|
||||
ForwardCT() chain.Docker
|
||||
|
||||
PreroutingFilter() chain.Docker
|
||||
DockerFilter() chain.Docker
|
||||
DockerFilterFirst() chain.Docker
|
||||
DockerFilterSecond() chain.Docker
|
||||
|
||||
DockerNat() chain.Docker
|
||||
PostroutingNat() chain.Docker
|
||||
}
|
||||
|
||||
type nftDocker struct {
|
||||
chains NFTDockerChains
|
||||
nft nftFirewall.NFT
|
||||
}
|
||||
|
||||
func NewNFT(nft nftFirewall.NFT, chains NFTDockerChains) NFTDocker {
|
||||
return &nftDocker{
|
||||
chains: chains,
|
||||
nft: nft,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nftDocker) NFT() nftFirewall.NFT {
|
||||
return n.nft
|
||||
}
|
||||
|
||||
func (n *nftDocker) Chains() NFTDockerChains {
|
||||
return n.chains
|
||||
}
|
||||
|
||||
type nftDockerChains struct {
|
||||
forwardFilter chain.Docker
|
||||
forwardBridge chain.Docker
|
||||
forwardCT chain.Docker
|
||||
|
||||
preroutingFilter chain.Docker
|
||||
dockerFilter chain.Docker
|
||||
dockerFilterFirst chain.Docker
|
||||
dockerFilterSecond chain.Docker
|
||||
|
||||
dockerNat chain.Docker
|
||||
postroutingNat chain.Docker
|
||||
}
|
||||
|
||||
func NewNFTChains(nft nftFirewall.NFT, family family.Type, table string) NFTDockerChains {
|
||||
return &nftDockerChains{
|
||||
forwardFilter: chain.NewDocker(nft.NFT(), family, table, "docker_forward_filter"),
|
||||
forwardBridge: chain.NewDocker(nft.NFT(), family, table, "docker_forward_bridge"),
|
||||
forwardCT: chain.NewDocker(nft.NFT(), family, table, "docker_forward_ct"),
|
||||
|
||||
preroutingFilter: chain.NewDocker(nft.NFT(), family, table, "docker_prerouting_filter"),
|
||||
dockerFilter: chain.NewDocker(nft.NFT(), family, table, "docker_filter"),
|
||||
dockerFilterFirst: chain.NewDocker(nft.NFT(), family, table, "docker_filter_first"),
|
||||
dockerFilterSecond: chain.NewDocker(nft.NFT(), family, table, "docker_filter_second"),
|
||||
|
||||
dockerNat: chain.NewDocker(nft.NFT(), family, table, "docker_nat"),
|
||||
postroutingNat: chain.NewDocker(nft.NFT(), family, table, "docker_postrouting_nat"),
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) ForwardFilter() chain.Docker {
|
||||
return n.forwardFilter
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) ForwardBridge() chain.Docker {
|
||||
return n.forwardBridge
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) ForwardCT() chain.Docker {
|
||||
return n.forwardCT
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) PreroutingFilter() chain.Docker {
|
||||
return n.preroutingFilter
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) DockerFilter() chain.Docker {
|
||||
return n.dockerFilter
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) DockerFilterFirst() chain.Docker {
|
||||
return n.dockerFilterFirst
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) DockerFilterSecond() chain.Docker {
|
||||
return n.dockerFilterSecond
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) DockerNat() chain.Docker {
|
||||
return n.dockerNat
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) PostroutingNat() chain.Docker {
|
||||
return n.postroutingNat
|
||||
}
|
||||
|
||||
func (n *nftDockerChains) List() []chain.Docker {
|
||||
return []chain.Docker{
|
||||
n.forwardFilter,
|
||||
n.forwardBridge,
|
||||
n.forwardCT,
|
||||
n.preroutingFilter,
|
||||
n.dockerFilter,
|
||||
n.dockerFilterFirst,
|
||||
n.dockerFilterSecond,
|
||||
n.dockerNat,
|
||||
n.postroutingNat,
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ func newRuleStrategy(config *Config, dockerClient client.Docker, logger log.Logg
|
||||
|
||||
switch config.RuleStrategy {
|
||||
case RuleStrategyRebuild:
|
||||
return rule_strategy.NewRebuildStrategy(generate), nil
|
||||
return rule_strategy.NewRebuildStrategy(generate, logger), nil
|
||||
case RuleStrategyIncremental:
|
||||
return rule_strategy.NewIncrementalStrategy(generate, dockerClient, logger), nil
|
||||
}
|
||||
|
||||
@@ -3,17 +3,19 @@ package rule_strategy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/client"
|
||||
"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/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type Generator interface {
|
||||
GenerateAll(chains chain.Chains, isComment bool)
|
||||
GenerateBridge(bridge client.Bridge, chain chain.Chains, isComment bool)
|
||||
GenerateContainer(container client.Container, bridgeName string, chain chain.Chains, isComment bool)
|
||||
ClearChains(chains chain.Chains)
|
||||
AddRule(chainData chain.Data, rule string)
|
||||
GenerateAll(builder nft.BatchBuilder, chains firewall.NFTDockerChains, isComment bool)
|
||||
GenerateBridge(bridge client.Bridge, builder nft.BatchBuilder, chains firewall.NFTDockerChains, isComment bool)
|
||||
GenerateContainer(container client.Container, bridgeName string, builder nft.BatchBuilder, chains firewall.NFTDockerChains, isComment bool)
|
||||
ClearChains(builder nft.BatchBuilder, chains firewall.NFTDockerChains)
|
||||
AddRule(builder nft.BatchBuilder, chainDocker chain.Docker, rule string)
|
||||
}
|
||||
|
||||
type generator struct {
|
||||
@@ -28,19 +30,17 @@ func NewGenerator(dockerClient client.Docker, logger log.Logger) Generator {
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) GenerateAll(chains chain.Chains, isComment bool) {
|
||||
listChains := chains.List()
|
||||
|
||||
if err := listChains.ForwardCT.JumpTo(&listChains.ForwardFilter, "", ""); err != nil {
|
||||
func (g *generator) GenerateAll(builder nft.BatchBuilder, chains firewall.NFTDockerChains, isComment bool) {
|
||||
if err := chains.ForwardCT().JumpTo(builder, chains.ForwardFilter(), "", ""); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.ForwardBridge.JumpTo(&listChains.ForwardFilter, "", ""); err != nil {
|
||||
if err := chains.ForwardBridge().JumpTo(builder, chains.ForwardFilter(), "", ""); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.DockerFilterFirst.JumpTo(&listChains.DockerFilter, "", ""); err != nil {
|
||||
if err := chains.DockerFilterFirst().JumpTo(builder, chains.DockerFilter(), "", ""); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.DockerFilterSecond.JumpTo(&listChains.DockerFilter, "", ""); err != nil {
|
||||
if err := chains.DockerFilterSecond().JumpTo(builder, chains.DockerFilter(), "", ""); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
@@ -51,20 +51,18 @@ func (g *generator) GenerateAll(chains chain.Chains, isComment bool) {
|
||||
}
|
||||
|
||||
for _, bridge := range bridges {
|
||||
g.GenerateBridge(bridge, chains, isComment)
|
||||
g.GenerateBridge(bridge, builder, chains, isComment)
|
||||
|
||||
if bridge.Containers == nil {
|
||||
continue
|
||||
}
|
||||
for _, container := range bridge.Containers {
|
||||
g.GenerateContainer(container, bridge.Name, chains, isComment)
|
||||
g.GenerateContainer(container, bridge.Name, builder, chains, isComment)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) GenerateBridge(bridge client.Bridge, chain chain.Chains, isComment bool) {
|
||||
listChains := chain.List()
|
||||
|
||||
func (g *generator) GenerateBridge(bridge client.Bridge, builder nft.BatchBuilder, chains firewall.NFTDockerChains, isComment bool) {
|
||||
var rule string
|
||||
comment := ""
|
||||
if isComment {
|
||||
@@ -72,27 +70,26 @@ func (g *generator) GenerateBridge(bridge client.Bridge, chain chain.Chains, isC
|
||||
}
|
||||
|
||||
rule = fmt.Sprintf("iifname != \"%s\" oifname \"%s\" counter drop %s", bridge.Name, bridge.Name, comment)
|
||||
g.AddRule(listChains.DockerFilterSecond, rule)
|
||||
g.AddRule(builder, chains.DockerFilterSecond(), rule)
|
||||
|
||||
rule = fmt.Sprintf("iifname \"%s\" counter accept %s", bridge.Name, comment)
|
||||
g.AddRule(listChains.ForwardFilter, rule)
|
||||
g.AddRule(builder, chains.ForwardFilter(), rule)
|
||||
|
||||
rule = fmt.Sprintf("oifname \"%s\" counter", bridge.Name)
|
||||
if err := listChains.DockerFilter.JumpTo(&listChains.ForwardBridge, rule, comment); err != nil {
|
||||
if err := chains.DockerFilter().JumpTo(builder, chains.ForwardBridge(), rule, comment); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
rule = fmt.Sprintf("oifname \"%s\" ct state related,established counter accept %s", bridge.Name, comment)
|
||||
g.AddRule(listChains.ForwardCT, rule)
|
||||
g.AddRule(builder, chains.ForwardCT(), rule)
|
||||
|
||||
for _, subnet := range bridge.Subnets {
|
||||
rule = fmt.Sprintf("ip saddr %s oifname != \"%s\" counter masquerade %s", subnet, bridge.Name, comment)
|
||||
g.AddRule(listChains.PostroutingNat, rule)
|
||||
g.AddRule(builder, chains.PostroutingNat(), rule)
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) GenerateContainer(container client.Container, bridgeName string, chain chain.Chains, isComment bool) {
|
||||
listChains := chain.List()
|
||||
func (g *generator) GenerateContainer(container client.Container, bridgeName string, builder nft.BatchBuilder, chains firewall.NFTDockerChains, isComment bool) {
|
||||
var rule string
|
||||
comment := ""
|
||||
if isComment {
|
||||
@@ -101,14 +98,14 @@ func (g *generator) GenerateContainer(container client.Container, bridgeName str
|
||||
|
||||
for _, ipInfo := range container.Networks.IPAddresses {
|
||||
rule = fmt.Sprintf("%s daddr %s iifname != \"%s\" counter drop %s", ipInfo.NftPrefix(), ipInfo.Address, bridgeName, comment)
|
||||
g.AddRule(listChains.PreroutingFilter, rule)
|
||||
g.AddRule(builder, chains.PreroutingFilter(), rule)
|
||||
|
||||
for _, port := range container.Networks.Ports {
|
||||
isZeroAddress := false
|
||||
for _, hostInfo := range port.HostPort {
|
||||
if hostInfo.IP.Address != "0.0.0.0" && hostInfo.IP.Address != "::" && (hostInfo.IP.Address == "127.0.0.1" || hostInfo.IP.Address == "::1") {
|
||||
rule = fmt.Sprintf("%s daddr %s iifname != \"lo\" %s dport %s counter drop %s", hostInfo.IP.NftPrefix(), hostInfo.IP.Address, port.Protocol, hostInfo.Port, comment)
|
||||
g.AddRule(listChains.PreroutingFilter, rule)
|
||||
g.AddRule(builder, chains.PreroutingFilter(), rule)
|
||||
}
|
||||
|
||||
if hostInfo.IP.Address == "0.0.0.0" || hostInfo.IP.Address == "::" {
|
||||
@@ -117,58 +114,32 @@ func (g *generator) GenerateContainer(container client.Container, bridgeName str
|
||||
}
|
||||
isZeroAddress = true
|
||||
rule = fmt.Sprintf("iifname != \"%s\" %s dport %s counter dnat %s to %s:%s %s", bridgeName, port.Protocol, hostInfo.Port, ipInfo.NftPrefix(), ipInfo.Address, port.Port, comment)
|
||||
g.AddRule(listChains.DockerNat, rule)
|
||||
g.AddRule(builder, chains.DockerNat(), rule)
|
||||
|
||||
rule = fmt.Sprintf("%s daddr %s iifname != \"%s\" oifname \"%s\" %s dport %s counter accept %s", ipInfo.NftPrefix(), ipInfo.Address, bridgeName, bridgeName, port.Protocol, port.Port, comment)
|
||||
g.AddRule(listChains.DockerFilterFirst, rule)
|
||||
g.AddRule(builder, chains.DockerFilterFirst(), rule)
|
||||
continue
|
||||
}
|
||||
rule = fmt.Sprintf("%s daddr %s iifname != \"%s\" oifname \"%s\" %s dport %s counter accept %s", ipInfo.NftPrefix(), ipInfo.Address, bridgeName, bridgeName, port.Protocol, port.Port, comment)
|
||||
g.AddRule(listChains.DockerFilterFirst, rule)
|
||||
g.AddRule(builder, chains.DockerFilterFirst(), rule)
|
||||
|
||||
rule = fmt.Sprintf("%s daddr %s iifname != \"%s\" %s dport %s counter dnat to %s:%s %s", hostInfo.IP.NftPrefix(), hostInfo.IP.Address, bridgeName, port.Protocol, hostInfo.Port, ipInfo.Address, port.Port, comment)
|
||||
g.AddRule(listChains.DockerNat, rule)
|
||||
g.AddRule(builder, chains.DockerNat(), rule)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) ClearChains(chains chain.Chains) {
|
||||
listChains := chains.List()
|
||||
|
||||
if err := listChains.DockerNat.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.PostroutingNat.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.PreroutingFilter.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.DockerFilter.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.DockerFilterFirst.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.DockerFilterSecond.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
if err := listChains.ForwardFilter.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
if err := listChains.ForwardBridge.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
|
||||
if err := listChains.ForwardCT.Clear(); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
func (g *generator) ClearChains(builder nft.BatchBuilder, chains firewall.NFTDockerChains) {
|
||||
for _, chain := range chains.List() {
|
||||
if err := chain.Clear(builder); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (g *generator) AddRule(chainData chain.Data, rule string) {
|
||||
if err := chainData.AddRule(rule); err != nil {
|
||||
func (g *generator) AddRule(builder nft.BatchBuilder, chainDocker chain.Docker, rule string) {
|
||||
if err := chainDocker.AddRule(builder, rule); err != nil {
|
||||
g.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
package rule_strategy
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/firewall"
|
||||
)
|
||||
|
||||
type Strategy interface {
|
||||
Reload(newNoneChain func(chain string) (nftChain.Chain, error)) error
|
||||
Chains() chain.Chains
|
||||
Reload(nftDocker firewall.NFTDocker) error
|
||||
Chains() firewall.NFTDockerChains
|
||||
Event(event *client.Event)
|
||||
}
|
||||
|
||||
@@ -3,15 +3,16 @@ package rule_strategy
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"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/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
)
|
||||
|
||||
type incrementalStrategy struct {
|
||||
dockerClient client.Docker
|
||||
chains chain.Chains
|
||||
nftDocker firewall.NFTDocker
|
||||
generator Generator
|
||||
logger log.Logger
|
||||
}
|
||||
@@ -24,20 +25,26 @@ func NewIncrementalStrategy(generator Generator, dockerClient client.Docker, log
|
||||
}
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) Reload(newNoneChain func(chain string) (nftChain.Chain, error)) error {
|
||||
chains, err := chain.NewChains(newNoneChain)
|
||||
func (i *incrementalStrategy) Reload(nftDocker firewall.NFTDocker) error {
|
||||
i.nftDocker = nftDocker
|
||||
|
||||
batchBuilder, err := i.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i.chains = chains
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
i.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
i.generator.GenerateAll(i.chains, true)
|
||||
i.generator.GenerateAll(batchBuilder, i.nftDocker.Chains(), true)
|
||||
|
||||
return nil
|
||||
return i.nftDocker.NFT().RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) Chains() chain.Chains {
|
||||
return i.chains
|
||||
func (i *incrementalStrategy) Chains() firewall.NFTDockerChains {
|
||||
return i.nftDocker.Chains()
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) Event(event *client.Event) {
|
||||
@@ -54,7 +61,9 @@ func (i *incrementalStrategy) Event(event *client.Event) {
|
||||
}
|
||||
|
||||
if event.Action == "die" {
|
||||
i.eventContainerStop(event.ID)
|
||||
if err := i.eventContainerStop(event.ID); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to handle container stop event: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@@ -66,10 +75,14 @@ func (i *incrementalStrategy) Event(event *client.Event) {
|
||||
if err := i.eventNetworkCreate(event.ID); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to handle network create event: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if event.Action == "destroy" {
|
||||
i.eventNetworkDestroy(event.ID)
|
||||
if err := i.eventNetworkDestroy(event.ID); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to handle network destroy event: %s", err))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
return
|
||||
@@ -82,35 +95,55 @@ func (i *incrementalStrategy) eventContainerStart(containerId string) error {
|
||||
return err
|
||||
}
|
||||
|
||||
batchBuilder, err := i.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
i.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
for _, ipInfo := range container.Networks.IPAddresses {
|
||||
bridge, err := i.dockerClient.FetchBridge(ipInfo.NetworkID)
|
||||
if err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to fetch bridge for container %s: %s", containerId, err))
|
||||
continue
|
||||
}
|
||||
i.generator.GenerateContainer(container, bridge.Name, i.chains, true)
|
||||
i.generator.GenerateContainer(container, bridge.Name, batchBuilder, i.nftDocker.Chains(), true)
|
||||
}
|
||||
|
||||
return nil
|
||||
return i.nftDocker.NFT().RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) eventContainerStop(containerId string) {
|
||||
listChains := i.chains.List()
|
||||
func (i *incrementalStrategy) eventContainerStop(containerId string) error {
|
||||
batchBuilder, err := i.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
i.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if err := i.nftRuleDeleteContainer(containerId, &listChains.PreroutingFilter); err != nil {
|
||||
if err := i.nftRuleDeleteContainer(containerId, batchBuilder, i.nftDocker.Chains().PreroutingFilter()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete container %s rules: %s", containerId, err))
|
||||
}
|
||||
|
||||
if err := i.nftRuleDeleteContainer(containerId, &listChains.DockerNat); err != nil {
|
||||
if err := i.nftRuleDeleteContainer(containerId, batchBuilder, i.nftDocker.Chains().DockerNat()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete container %s rules: %s", containerId, err))
|
||||
}
|
||||
|
||||
if err := i.nftRuleDeleteContainer(containerId, &listChains.DockerFilterFirst); err != nil {
|
||||
if err := i.nftRuleDeleteContainer(containerId, batchBuilder, i.nftDocker.Chains().DockerFilterFirst()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete container %s rules: %s", containerId, err))
|
||||
}
|
||||
|
||||
return i.nftDocker.NFT().RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) nftRuleDeleteContainer(containerId string, chain *chain.Data) error {
|
||||
func (i *incrementalStrategy) nftRuleDeleteContainer(containerId string, builder nft.BatchBuilder, chain chain.Docker) error {
|
||||
rules, err := chain.ListRules()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -120,7 +153,7 @@ func (i *incrementalStrategy) nftRuleDeleteContainer(containerId string, chain *
|
||||
if rule.Comment != "container_id:"+containerId {
|
||||
continue
|
||||
}
|
||||
if err := chain.RemoveRuleByHandle(rule.Handle); err != nil {
|
||||
if err := chain.RemoveRuleByHandle(builder, rule.Handle); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete container %s rule: %s", containerId, err))
|
||||
}
|
||||
}
|
||||
@@ -129,40 +162,60 @@ func (i *incrementalStrategy) nftRuleDeleteContainer(containerId string, chain *
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) eventNetworkCreate(bridgeId string) error {
|
||||
batchBuilder, err := i.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
i.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
bridge, err := i.dockerClient.FetchBridge(bridgeId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
i.generator.GenerateBridge(bridge, i.chains, true)
|
||||
return nil
|
||||
i.generator.GenerateBridge(bridge, batchBuilder, i.nftDocker.Chains(), true)
|
||||
return i.nftDocker.NFT().RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) eventNetworkDestroy(bridgeId string) {
|
||||
listChains := i.chains.List()
|
||||
func (i *incrementalStrategy) eventNetworkDestroy(bridgeId string) error {
|
||||
batchBuilder, err := i.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
i.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, &listChains.DockerFilterSecond); err != nil {
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, batchBuilder, i.nftDocker.Chains().DockerFilterSecond()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete bridge %s rules: %s", bridgeId, err))
|
||||
}
|
||||
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, &listChains.ForwardFilter); err != nil {
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, batchBuilder, i.nftDocker.Chains().ForwardFilter()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete bridge %s rules: %s", bridgeId, err))
|
||||
}
|
||||
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, &listChains.ForwardBridge); err != nil {
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, batchBuilder, i.nftDocker.Chains().ForwardBridge()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete bridge %s rules: %s", bridgeId, err))
|
||||
}
|
||||
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, &listChains.ForwardCT); err != nil {
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, batchBuilder, i.nftDocker.Chains().ForwardCT()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete bridge %s rules: %s", bridgeId, err))
|
||||
}
|
||||
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, &listChains.PostroutingNat); err != nil {
|
||||
if err := i.nftRuleDeleteBridge(bridgeId, batchBuilder, i.nftDocker.Chains().PostroutingNat()); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete bridge %s rules: %s", bridgeId, err))
|
||||
}
|
||||
|
||||
return i.nftDocker.NFT().RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (i *incrementalStrategy) nftRuleDeleteBridge(bridgeId string, chain *chain.Data) error {
|
||||
func (i *incrementalStrategy) nftRuleDeleteBridge(bridgeId string, builder nft.BatchBuilder, chain chain.Docker) error {
|
||||
rules, err := chain.ListRules()
|
||||
if err != nil {
|
||||
return err
|
||||
@@ -172,7 +225,7 @@ func (i *incrementalStrategy) nftRuleDeleteBridge(bridgeId string, chain *chain.
|
||||
if rule.Comment != "bridge_id:"+bridgeId {
|
||||
continue
|
||||
}
|
||||
if err := chain.RemoveRuleByHandle(rule.Handle); err != nil {
|
||||
if err := chain.RemoveRuleByHandle(builder, rule.Handle); err != nil {
|
||||
i.logger.Error(fmt.Sprintf("failed to delete bridge %s rule: %s", bridgeId, err))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,36 +1,44 @@
|
||||
package rule_strategy
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"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/log"
|
||||
)
|
||||
|
||||
type rebuildStrategy struct {
|
||||
chains chain.Chains
|
||||
nftDocker firewall.NFTDocker
|
||||
generator Generator
|
||||
logger log.Logger
|
||||
}
|
||||
|
||||
func NewRebuildStrategy(generator Generator) Strategy {
|
||||
func NewRebuildStrategy(generator Generator, logger log.Logger) Strategy {
|
||||
return &rebuildStrategy{
|
||||
generator: generator,
|
||||
logger: logger,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *rebuildStrategy) Reload(newNoneChain func(chain string) (nftChain.Chain, error)) error {
|
||||
chains, err := chain.NewChains(newNoneChain)
|
||||
func (r *rebuildStrategy) Reload(nftDocker firewall.NFTDocker) error {
|
||||
r.nftDocker = nftDocker
|
||||
|
||||
batchBuilder, err := r.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.chains = chains
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
r.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
r.generator.GenerateAll(r.chains, false)
|
||||
r.generator.GenerateAll(batchBuilder, r.nftDocker.Chains(), false)
|
||||
|
||||
return nil
|
||||
return r.nftDocker.NFT().RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (r *rebuildStrategy) Chains() chain.Chains {
|
||||
return r.chains
|
||||
func (r *rebuildStrategy) Chains() firewall.NFTDockerChains {
|
||||
return r.nftDocker.Chains()
|
||||
}
|
||||
|
||||
func (r *rebuildStrategy) Event(event *client.Event) {
|
||||
@@ -38,6 +46,21 @@ func (r *rebuildStrategy) Event(event *client.Event) {
|
||||
return
|
||||
}
|
||||
|
||||
r.generator.ClearChains(r.chains)
|
||||
r.generator.GenerateAll(r.chains, false)
|
||||
batchBuilder, err := r.nftDocker.NFT().NewBuildBatch()
|
||||
if err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
r.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
r.generator.ClearChains(batchBuilder, r.nftDocker.Chains())
|
||||
r.generator.GenerateAll(batchBuilder, r.nftDocker.Chains(), false)
|
||||
|
||||
if err := r.nftDocker.NFT().RunBatch(batchBuilder); err != nil {
|
||||
r.logger.Error(err.Error())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,13 +9,14 @@ import (
|
||||
|
||||
"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/daemon/firewall/chain/block"
|
||||
nftFirewall "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/block"
|
||||
"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/log"
|
||||
)
|
||||
|
||||
type API interface {
|
||||
NftReload(blockListIP block.ListIP, blockListIPWithPort block.ListIPWithPort) error
|
||||
NftReload(nft nftFirewall.NFT, blockListIP block.ListIP, blockListIPWithPort block.ListIPWithPort) error
|
||||
BlockIP(block BlockIP) (bool, error)
|
||||
BlockIPWithPorts(block BlockIPWithPorts) (bool, error)
|
||||
UnblockAllIPs() error
|
||||
@@ -53,15 +54,25 @@ func New(blockingRepository repository.BlockingRepository, logger log.Logger) AP
|
||||
}
|
||||
}
|
||||
|
||||
func (b *blocking) NftReload(blockListIP block.ListIP, blockListIPWithPort block.ListIPWithPort) error {
|
||||
func (b *blocking) NftReload(nft nftFirewall.NFT, blockListIP block.ListIP, blockListIPWithPort block.ListIPWithPort) error {
|
||||
b.mu.Lock()
|
||||
b.blockListIP = blockListIP
|
||||
b.blockListIPWithPort = blockListIPWithPort
|
||||
b.mu.Unlock()
|
||||
|
||||
batchBuilder, err := nft.NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
b.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
isExpiredEntries := false
|
||||
nowUnix := time.Now().Unix()
|
||||
err := b.blockingRepository.List(func(e entity.Blocking) error {
|
||||
err = b.blockingRepository.List(func(e entity.Blocking) error {
|
||||
ip := net.ParseIP(e.IP)
|
||||
if ip == nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to parse IP address: %s", e.IP))
|
||||
@@ -83,14 +94,14 @@ func (b *blocking) NftReload(blockListIP block.ListIP, blockListIPWithPort block
|
||||
b.logger.Error(fmt.Sprintf("Failed to parse ports: %s", err))
|
||||
return nil
|
||||
}
|
||||
if err := b.blockListIPWithPort.AddIP(ip, l4Ports, blockSeconds); err != nil {
|
||||
if err := b.blockListIPWithPort.AddBatchIP(batchBuilder, ip, l4Ports, blockSeconds); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to add IP %s to block list: %s", ip.String(), err))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := b.blockListIP.AddIP(ip, blockSeconds); err != nil {
|
||||
if err := b.blockListIP.AddBatchIP(batchBuilder, ip, blockSeconds); err != nil {
|
||||
b.logger.Error(fmt.Sprintf("Failed to add IP %s to block list: %s", ip.String(), err))
|
||||
return nil
|
||||
}
|
||||
@@ -108,7 +119,11 @@ func (b *blocking) NftReload(blockListIP block.ListIP, blockListIPWithPort block
|
||||
}()
|
||||
}
|
||||
|
||||
return err
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nft.RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (b *blocking) BlockIP(block BlockIP) (bool, error) {
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type AfterLocalInput interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type afterLocalInput struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newAfterLocalInput(nft nft.NFT, family family.Type, table string) (LocalInput, error) {
|
||||
chain := "after-local-input"
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &afterLocalInput{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *afterLocalInput) AddRule(expr ...string) error {
|
||||
return l.nft.Rule().Add(l.family, l.table, l.chain, expr...)
|
||||
}
|
||||
|
||||
func (l *afterLocalInput) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + l.chain)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type BeforeLocalInput interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type beforeLocalInput struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newBeforeLocalInput(nft nft.NFT, family family.Type, table string) (LocalInput, error) {
|
||||
chain := "before-local-input"
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &beforeLocalInput{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *beforeLocalInput) AddRule(expr ...string) error {
|
||||
return l.nft.Rule().Add(l.family, l.table, l.chain, expr...)
|
||||
}
|
||||
|
||||
func (l *beforeLocalInput) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + l.chain)
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type Blocklist interface {
|
||||
// ReplaceElementsIPv4 Replacing IP addresses.
|
||||
ReplaceElementsIPv4(ips []string) error
|
||||
|
||||
// ReplaceElementsIPv6 Replacing IP addresses.
|
||||
ReplaceElementsIPv6(ips []string) error
|
||||
|
||||
// AddRuleToChain Add a rule to the parent chain.
|
||||
AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error
|
||||
}
|
||||
|
||||
type blocklist struct {
|
||||
listIPv4 List
|
||||
listIPv6 List
|
||||
}
|
||||
|
||||
func NewBlocklist(nft nft.NFT, family family.Type, table string, name string) (Blocklist, error) {
|
||||
params := "type ipv4_addr; flags interval; auto-merge;"
|
||||
listName := name + "_ip4"
|
||||
listIPv4, err := newList(nft, family, table, listName, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = "type ipv6_addr; flags interval; auto-merge;"
|
||||
listName = name + "_ip6"
|
||||
listIPv6, err := newList(nft, family, table, listName, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &blocklist{
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *blocklist) ReplaceElementsIPv4(ips []string) error {
|
||||
return l.listIPv4.ReplaceElements(ips)
|
||||
}
|
||||
|
||||
func (l *blocklist) ReplaceElementsIPv6(ips []string) error {
|
||||
return l.listIPv6.ReplaceElements(ips)
|
||||
}
|
||||
|
||||
func (l *blocklist) AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error {
|
||||
rule := "ip saddr @" + l.listIPv4.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rule = "ip6 saddr @" + l.listIPv6.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type List interface {
|
||||
Name() string
|
||||
AddElement(element string) error
|
||||
DeleteElement(element string) error
|
||||
ReplaceElements(elements []string) error
|
||||
}
|
||||
|
||||
type list struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
name string
|
||||
}
|
||||
|
||||
func newList(nft nft.NFT, family family.Type, table string, name string, params string) (List, error) {
|
||||
command := []string{
|
||||
"add set", family.String(), table, name, "{ " + params + " }",
|
||||
}
|
||||
if err := nft.Command().Run(command...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &list{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *list) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *list) AddElement(element string) error {
|
||||
command := []string{
|
||||
"add element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", element),
|
||||
}
|
||||
return l.nft.Command().Run(command...)
|
||||
}
|
||||
|
||||
func (l *list) DeleteElement(element string) error {
|
||||
command := []string{
|
||||
"delete element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", element),
|
||||
}
|
||||
return l.nft.Command().Run(command...)
|
||||
}
|
||||
|
||||
func (l *list) ReplaceElements(elements []string) error {
|
||||
if len(elements) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := l.nft.Command().Run("flush set", l.family.String(), l.table, l.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
const batchSize = 200
|
||||
for _, batch := range chunkStrings(elements, batchSize) {
|
||||
command := []string{
|
||||
"add element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", strings.Join(batch, ",")),
|
||||
}
|
||||
if err := l.nft.Command().Run(command...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func chunkStrings(items []string, size int) [][]string {
|
||||
if size <= 0 {
|
||||
size = 100
|
||||
}
|
||||
|
||||
chunks := make([][]string, 0, (len(items)+size-1)/size)
|
||||
for start := 0; start < len(items); start += size {
|
||||
end := start + size
|
||||
if end > len(items) {
|
||||
end = len(items)
|
||||
}
|
||||
chunks = append(chunks, items[start:end])
|
||||
}
|
||||
return chunks
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type ListIP interface {
|
||||
// AddIP Add an IP address to the list.
|
||||
AddIP(addr net.IP, banSeconds uint32) error
|
||||
|
||||
// DeleteIP Delete an IP address from the list.
|
||||
DeleteIP(addr net.IP) error
|
||||
|
||||
// AddRuleToChain Add a rule to the parent chain.
|
||||
AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error
|
||||
}
|
||||
|
||||
type listIP struct {
|
||||
listIPv4 List
|
||||
listIPv6 List
|
||||
}
|
||||
|
||||
func NewListIP(nft nft.NFT, family family.Type, table string, name string) (ListIP, error) {
|
||||
params := "type ipv4_addr; flags interval, timeout;"
|
||||
listName := name + "_ip4"
|
||||
listIPv4, err := newList(nft, family, table, listName, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = "type ipv6_addr; flags interval, timeout;"
|
||||
listName = name + "_ip6"
|
||||
listIPv6, err := newList(nft, family, table, listName, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listIP{
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *listIP) AddIP(addr net.IP, banSeconds uint32) error {
|
||||
el := []string{addr.String()}
|
||||
if banSeconds > 0 {
|
||||
el = append(el, "timeout", fmt.Sprintf("%ds", banSeconds))
|
||||
}
|
||||
|
||||
element := strings.Join(el, " ")
|
||||
|
||||
if addr.To4() != nil {
|
||||
return l.listIPv4.AddElement(element)
|
||||
}
|
||||
|
||||
return l.listIPv6.AddElement(element)
|
||||
}
|
||||
|
||||
func (l *listIP) DeleteIP(addr net.IP) error {
|
||||
if addr == nil {
|
||||
return fmt.Errorf("IP address cannot be nil")
|
||||
}
|
||||
if addr.To4() != nil {
|
||||
return l.listIPv4.DeleteElement(addr.String())
|
||||
}
|
||||
|
||||
return l.listIPv6.DeleteElement(addr.String())
|
||||
}
|
||||
|
||||
func (l *listIP) AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error {
|
||||
rule := "ip saddr @" + l.listIPv4.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rule = "ip6 saddr @" + l.listIPv6.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
|
||||
)
|
||||
|
||||
func NewPortKnocking(nft nft.NFT, family family.Type, table string, name string, ipVersion ip.Version, timeout uint32) error {
|
||||
params := []string{"type", ipVersion.ToNftForSet() + ";", "flags timeout; timeout", strconv.Itoa(int(timeout)) + "s;"}
|
||||
_, err := newList(nft, family, table, name, strings.Join(params, " "))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type Chain interface {
|
||||
AddRule(expr ...string) error
|
||||
ListRules() ([]Rule, error)
|
||||
RemoveRuleByHandle(handle uint64) error
|
||||
Clear() error
|
||||
}
|
||||
|
||||
type chain struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
type NftOutput struct {
|
||||
Nftables []NftElement `json:"nftables"`
|
||||
}
|
||||
type NftElement struct {
|
||||
Rule *Rule `json:"rule,omitempty"`
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Handle uint64 `json:"handle"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
|
||||
func (c *chain) AddRule(expr ...string) error {
|
||||
return c.nft.Rule().Add(c.family, c.table, c.chain, expr...)
|
||||
}
|
||||
|
||||
func (c *chain) ListRules() ([]Rule, error) {
|
||||
args := []string{"-a", "-j", "list", "chain", c.family.String(), c.table, c.chain}
|
||||
jsonData, err := c.nft.Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var output NftOutput
|
||||
if err := json.Unmarshal([]byte(jsonData), &output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rules []Rule
|
||||
for _, el := range output.Nftables {
|
||||
if el.Rule != nil {
|
||||
rules = append(rules, *el.Rule)
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (c *chain) RemoveRuleByHandle(handle uint64) error {
|
||||
return c.nft.Rule().Delete(c.family, c.table, c.chain, handle)
|
||||
}
|
||||
|
||||
func (c *chain) Clear() error {
|
||||
return c.nft.Chain().Clear(c.family, c.table, c.chain)
|
||||
}
|
||||
@@ -1,276 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nftFamily "git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain/block"
|
||||
)
|
||||
|
||||
type Chains interface {
|
||||
NewPacketFilter(enable bool) error
|
||||
PacketFilter() PacketFilter
|
||||
|
||||
NewInput(chain string, defaultAllow bool, priority int) error
|
||||
Input() Input
|
||||
|
||||
NewOutput(chain string, defaultAllow bool, priority int) error
|
||||
Output() Output
|
||||
|
||||
NewForward(chain string, defaultAllow bool, priority int) error
|
||||
Forward() Forward
|
||||
|
||||
NewBeforeLocalInput() error
|
||||
BeforeLocalInput() BeforeLocalInput
|
||||
|
||||
NewLocalInput() error
|
||||
LocalInput() LocalInput
|
||||
|
||||
NewAfterLocalInput() error
|
||||
AfterLocalInput() AfterLocalInput
|
||||
|
||||
NewLocalOutput() error
|
||||
LocalOutput() LocalOutput
|
||||
|
||||
NewLocalForward() error
|
||||
LocalForward() LocalForward
|
||||
|
||||
ClearRules() error
|
||||
|
||||
NewNoneChain(chain string) (Chain, error)
|
||||
NewChain(chain string, baseChain nftChain.ChainOptions) (Chain, error)
|
||||
NewBlockListIP(name string) (block.ListIP, error)
|
||||
NewBlockListIPWithPort(name string) (block.ListIPWithPort, error)
|
||||
NewBlocklist(name string) (block.Blocklist, error)
|
||||
NewPortKnocking(name string) (PortKnocking, error)
|
||||
}
|
||||
|
||||
type chains struct {
|
||||
input Input
|
||||
output Output
|
||||
forward Forward
|
||||
packetFilter PacketFilter
|
||||
|
||||
beforeLocalInput BeforeLocalInput
|
||||
localInput LocalInput
|
||||
afterLocalInput AfterLocalInput
|
||||
|
||||
localOutput LocalOutput
|
||||
localForward LocalForward
|
||||
|
||||
family nftFamily.Type
|
||||
table string
|
||||
nft nft.NFT
|
||||
}
|
||||
|
||||
func NewChains(nft nft.NFT, table string) (Chains, error) {
|
||||
family := nftFamily.INET
|
||||
|
||||
if err := clearRules(nft, family, table); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Table().Add(family, table); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chains{
|
||||
nft: nft,
|
||||
table: table,
|
||||
family: family,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *chains) NewPacketFilter(enable bool) error {
|
||||
filter, err := newPacketFilter(c.nft, c.family, c.table, enable)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.packetFilter = filter
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) PacketFilter() PacketFilter {
|
||||
return c.packetFilter
|
||||
}
|
||||
|
||||
func (c *chains) NewInput(chain string, defaultAllow bool, priority int) error {
|
||||
input, err := newInput(c.nft, c.family, c.table, chain, defaultAllow, priority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.input = input
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) Input() Input {
|
||||
return c.input
|
||||
}
|
||||
|
||||
func (c *chains) NewOutput(chain string, defaultAllow bool, priority int) error {
|
||||
output, err := newOutput(c.nft, c.family, c.table, chain, defaultAllow, priority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.output = output
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) Output() Output {
|
||||
return c.output
|
||||
}
|
||||
|
||||
func (c *chains) NewForward(chain string, defaultAllow bool, priority int) error {
|
||||
forward, err := newForward(c.nft, c.family, c.table, chain, defaultAllow, priority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.forward = forward
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) Forward() Forward {
|
||||
return c.forward
|
||||
}
|
||||
|
||||
func (c *chains) NewBeforeLocalInput() error {
|
||||
newChain, err := newBeforeLocalInput(c.nft, c.family, c.table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.beforeLocalInput = newChain
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) BeforeLocalInput() BeforeLocalInput {
|
||||
return c.beforeLocalInput
|
||||
}
|
||||
|
||||
func (c *chains) NewLocalInput() error {
|
||||
localInput, err := newLocalInput(c.nft, c.family, c.table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.localInput = localInput
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) LocalInput() LocalInput {
|
||||
return c.localInput
|
||||
}
|
||||
|
||||
func (c *chains) NewAfterLocalInput() error {
|
||||
newChain, err := newAfterLocalInput(c.nft, c.family, c.table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.afterLocalInput = newChain
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) AfterLocalInput() AfterLocalInput {
|
||||
return c.afterLocalInput
|
||||
}
|
||||
|
||||
func (c *chains) NewLocalOutput() error {
|
||||
localOutput, err := newLocalOutput(c.nft, c.family, c.table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.localOutput = localOutput
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) LocalOutput() LocalOutput {
|
||||
return c.localOutput
|
||||
}
|
||||
|
||||
func (c *chains) NewLocalForward() error {
|
||||
localForward, err := newLocalForward(c.nft, c.family, c.table)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.localForward = localForward
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *chains) LocalForward() LocalForward {
|
||||
return c.localForward
|
||||
}
|
||||
|
||||
func (c *chains) ClearRules() error {
|
||||
return clearRules(c.nft, c.family, c.table)
|
||||
}
|
||||
|
||||
func (c *chains) NewNoneChain(chainName string) (Chain, error) {
|
||||
return c.NewChain(chainName, nftChain.TypeNone)
|
||||
}
|
||||
|
||||
func (c *chains) NewChain(chainName string, baseChain nftChain.ChainOptions) (Chain, error) {
|
||||
if err := c.nft.Chain().Add(c.family, c.table, chainName, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &chain{
|
||||
nft: c.nft,
|
||||
family: c.family,
|
||||
table: c.table,
|
||||
chain: chainName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *chains) NewBlockListIP(name string) (block.ListIP, error) {
|
||||
blockList, err := block.NewListIP(c.nft, c.family, c.table, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blockList, nil
|
||||
}
|
||||
|
||||
func (c *chains) NewBlockListIPWithPort(name string) (block.ListIPWithPort, error) {
|
||||
blockList, err := block.NewListIPWithPort(c.nft, c.family, c.table, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blockList, nil
|
||||
}
|
||||
|
||||
func (c *chains) NewBlocklist(name string) (block.Blocklist, error) {
|
||||
blockList, err := block.NewBlocklist(c.nft, c.family, c.table, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := blockList.AddRuleToChain(c.afterLocalInput.AddRule, "drop"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return blockList, nil
|
||||
}
|
||||
|
||||
func (c *chains) NewPortKnocking(name string) (PortKnocking, error) {
|
||||
portKnocking, err := newPortKnocking(c.nft, c.family, c.table, name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return portKnocking, nil
|
||||
}
|
||||
|
||||
func clearRules(nft nft.NFT, family nftFamily.Type, table string) error {
|
||||
if err := nft.Table().Delete(family, table); err != nil {
|
||||
if !strings.Contains(string(err.Error()), "delete table "+family.String()+" "+table) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type Forward interface {
|
||||
AddRule(expr ...string) error
|
||||
}
|
||||
|
||||
type forward struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newForward(nft nft.NFT, family family.Type, table string, chain string, defaultAllow bool, priority int) (Forward, error) {
|
||||
policy := nftChain.PolicyDrop
|
||||
if defaultAllow {
|
||||
policy = nftChain.PolicyAccept
|
||||
}
|
||||
|
||||
baseChain := nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookForward,
|
||||
Priority: int32(priority),
|
||||
Policy: policy,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
if err := nft.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &forward{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *forward) AddRule(expr ...string) error {
|
||||
return c.nft.Rule().Add(c.family, c.table, c.chain, expr...)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type Input interface {
|
||||
AddRule(expr ...string) error
|
||||
}
|
||||
|
||||
type input struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newInput(nft nft.NFT, family family.Type, table string, chain string, defaultAllow bool, priority int) (Input, error) {
|
||||
policy := nftChain.PolicyDrop
|
||||
if defaultAllow {
|
||||
policy = nftChain.PolicyAccept
|
||||
}
|
||||
|
||||
baseChain := nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookInput,
|
||||
Priority: int32(priority),
|
||||
Policy: policy,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
if err := nft.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &input{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *input) AddRule(expr ...string) error {
|
||||
return c.nft.Rule().Add(c.family, c.table, c.chain, expr...)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type LocalForward interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type localForward struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newLocalForward(nft nft.NFT, family family.Type, table string) (LocalForward, error) {
|
||||
chain := "local-forward"
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &localForward{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *localForward) AddRule(expr ...string) error {
|
||||
return l.nft.Rule().Add(l.family, l.table, l.chain, expr...)
|
||||
}
|
||||
|
||||
func (l *localForward) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + l.chain)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type LocalInput interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type localInput struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newLocalInput(nft nft.NFT, family family.Type, table string) (LocalInput, error) {
|
||||
chain := "local-input"
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &localInput{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *localInput) AddRule(expr ...string) error {
|
||||
return l.nft.Rule().Add(l.family, l.table, l.chain, expr...)
|
||||
}
|
||||
|
||||
func (l *localInput) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + l.chain)
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type LocalOutput interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleOut(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type localOutput struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newLocalOutput(nft nft.NFT, family family.Type, table string) (LocalOutput, error) {
|
||||
chain := "local-output"
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &localOutput{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (l *localOutput) AddRule(expr ...string) error {
|
||||
return l.nft.Rule().Add(l.family, l.table, l.chain, expr...)
|
||||
}
|
||||
|
||||
func (l *localOutput) AddRuleOut(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("oifname != \"lo\" counter jump " + l.chain)
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type Output interface {
|
||||
AddRule(expr ...string) error
|
||||
}
|
||||
|
||||
type output struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func newOutput(nft nft.NFT, family family.Type, table string, chain string, defaultAllow bool, priority int) (Output, error) {
|
||||
policy := nftChain.PolicyDrop
|
||||
if defaultAllow {
|
||||
policy = nftChain.PolicyAccept
|
||||
}
|
||||
|
||||
baseChain := nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookOutput,
|
||||
Priority: int32(priority),
|
||||
Policy: policy,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
if err := nft.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &output{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *output) AddRule(expr ...string) error {
|
||||
return c.nft.Rule().Add(c.family, c.table, c.chain, expr...)
|
||||
}
|
||||
@@ -1,100 +0,0 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nftFamily "git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type PacketFilter interface {
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
AddRuleOut(AddRuleFunc func(expr ...string) error) error
|
||||
}
|
||||
|
||||
type packetFilter struct {
|
||||
enable bool
|
||||
invalidName string
|
||||
}
|
||||
|
||||
// newPacketFilter Drop out of order packets and packets in an INVALID state in nftables connection tracking.
|
||||
func newPacketFilter(nft nft.NFT, family nftFamily.Type, table string, enable bool) (PacketFilter, error) {
|
||||
chainInvalidName := "INVALID"
|
||||
if !enable {
|
||||
return &packetFilter{
|
||||
enable: enable,
|
||||
invalidName: chainInvalidName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
chainName := "INVDROP"
|
||||
|
||||
if err := nft.Chain().Add(family, table, chainName, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := nft.Rule().Add(family, table, chainName, "counter drop"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Chain().Add(family, table, chainInvalidName, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "ct state invalid counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags ! fin,syn,rst,psh,ack,urg counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (fin | syn | rst | psh | ack | urg) == fin | syn | rst | psh | ack | urg counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (fin | syn) == fin | syn counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (syn | rst) == syn | rst counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (fin | rst) == fin | rst counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (fin | ack) == fin counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (psh | ack) == psh counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (ack | urg) == urg counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := nft.Rule().Add(family, table, chainInvalidName, "tcp flags & (fin | syn | rst | ack) != syn ct state new counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &packetFilter{
|
||||
enable: enable,
|
||||
invalidName: chainInvalidName,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *packetFilter) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
if !f.enable {
|
||||
return nil
|
||||
}
|
||||
return AddRuleFunc("iifname != \"lo\" meta l4proto tcp counter jump " + f.invalidName)
|
||||
}
|
||||
|
||||
func (f *packetFilter) AddRuleOut(AddRuleFunc func(expr ...string) error) error {
|
||||
if !f.enable {
|
||||
return nil
|
||||
}
|
||||
return AddRuleFunc("oifname != \"lo\" meta l4proto tcp counter jump " + f.invalidName)
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package firewall
|
||||
package config
|
||||
|
||||
import (
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
|
||||
@@ -17,6 +17,7 @@ type Config struct {
|
||||
}
|
||||
|
||||
type ConfigOptions struct {
|
||||
Cache bool
|
||||
ClearMode ClearMode
|
||||
SavesRules bool
|
||||
SavesRulesPath string
|
||||
@@ -1,72 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
|
||||
func (f *firewall) reloadDocker() error {
|
||||
f.logger.Debug("Reload docker rules")
|
||||
if err := f.reloadDockerPrerouting(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadDockerPrerouting() error {
|
||||
preroutingNat, err := f.chains.NewChain("prerouting_nat", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeNat,
|
||||
Hook: nftChain.HookPrerouting,
|
||||
Priority: -100,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.docker.NftChains().PreroutingNatJump(preroutingNat.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preroutingFilter, err := f.chains.NewChain("prerouting_filter", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookPrerouting,
|
||||
Priority: -300,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.docker.NftChains().PreroutingFilterJump(preroutingFilter.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputNat, err := f.chains.NewChain("output_nat", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeNat,
|
||||
Hook: nftChain.HookOutput,
|
||||
Priority: -100,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.docker.NftChains().OutputNatJump(outputNat.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
postroutingNat, err := f.chains.NewChain("postrouting_nat", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeNat,
|
||||
Hook: nftChain.HookPostrouting,
|
||||
Priority: 300,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.docker.NftChains().PostroutingNatJump(postroutingNat.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -4,19 +4,26 @@ import (
|
||||
"fmt"
|
||||
"net"
|
||||
"os"
|
||||
|
||||
"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/firewall/blocking"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
nftables "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
"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"
|
||||
dockerFirewall "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/blocking"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/config"
|
||||
nftFirewall "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/table"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/reload"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/info"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/filesystem"
|
||||
)
|
||||
|
||||
type API interface {
|
||||
// Reload Clear all rules and set new rules.
|
||||
Reload() error
|
||||
Reload(daemonInfo info.Info) error
|
||||
|
||||
// SavesRules Save rules to file.
|
||||
SavesRules()
|
||||
@@ -41,82 +48,99 @@ type API interface {
|
||||
|
||||
// DockerSupport Return true if docker support
|
||||
DockerSupport() bool
|
||||
|
||||
// HasRules Return true if firewall has rules
|
||||
HasRules() (bool, error)
|
||||
}
|
||||
|
||||
type firewall struct {
|
||||
nft nftables.NFT
|
||||
nft nftFirewall.NFT
|
||||
table table.Table
|
||||
logger log.Logger
|
||||
config *Config
|
||||
config *config.Config
|
||||
blockingService blocking.API
|
||||
chains chain.Chains
|
||||
docker docker_monitor.Docker
|
||||
blocklist blocklist.Blocklist
|
||||
dataDir string
|
||||
|
||||
mu sync.Mutex
|
||||
}
|
||||
|
||||
func New(
|
||||
pathNFT string,
|
||||
blockingService blocking.API,
|
||||
logger log.Logger,
|
||||
config Config,
|
||||
config config.Config,
|
||||
docker docker_monitor.Docker,
|
||||
blocklist blocklist.Blocklist,
|
||||
dataDir string,
|
||||
) (API, error) {
|
||||
nft, err := nftables.NewWithPath(pathNFT)
|
||||
nftClient, err := nftables.NewWithPath(pathNFT)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to create nft client: %w %s", err, pathNFT)
|
||||
}
|
||||
|
||||
return &firewall{
|
||||
nft: nft,
|
||||
nft: nftFirewall.New(nftClient, strings.TrimRight(dataDir, "/")+"/tmp"),
|
||||
logger: logger,
|
||||
config: &config,
|
||||
blockingService: blockingService,
|
||||
docker: docker,
|
||||
blocklist: blocklist,
|
||||
dataDir: dataDir,
|
||||
|
||||
mu: sync.Mutex{},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (f *firewall) Reload() error {
|
||||
func (f *firewall) Reload(daemonInfo info.Info) error {
|
||||
f.logger.Debug("Reload nftables rules")
|
||||
if f.config.Options.ClearMode == ClearModeGlobal {
|
||||
if err := f.nft.Clear(); err != nil {
|
||||
|
||||
nftReload := reload.New(f.nft, f.logger, f.config)
|
||||
blocklistNames := f.blocklist.Names()
|
||||
|
||||
var nftTable table.Table
|
||||
var err error
|
||||
if f.config.Options.Cache {
|
||||
file := f.pathFileCacheNFT()
|
||||
nftTable, err = nftReload.RunWithCache(
|
||||
file,
|
||||
f.isValidCacheFile(daemonInfo),
|
||||
blocklistNames,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
checksum, err := filesystem.FileChecksum(file)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to calculate checksum for %s: %s", file, err))
|
||||
} else if err := daemonInfo.Metadata().FirewallFileNft().Update(checksum); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to update metadata: %s", err))
|
||||
}
|
||||
} else {
|
||||
nftTable, err = nftReload.Run(blocklistNames)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
chains, err := chain.NewChains(f.nft, f.config.MetadataNaming.TableName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
f.chains = chains
|
||||
f.mu.Lock()
|
||||
f.table = nftTable
|
||||
f.mu.Unlock()
|
||||
|
||||
if err := f.docker.NftReload(f.chains.NewNoneChain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.chains.NewPacketFilter(f.config.Options.PacketFilter); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.reloadInput(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.reloadOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.reloadForward(); err != nil {
|
||||
return err
|
||||
}
|
||||
if f.config.Options.DockerSupport {
|
||||
if err := f.reloadDocker(); err != nil {
|
||||
if f.config.Options.DockerSupport && nftTable.DockerChains() != nil {
|
||||
nftDocker := dockerFirewall.NewNFT(f.nft, nftTable.DockerChains())
|
||||
if err := f.docker.NftReload(nftDocker); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := f.reloadBlockList(); err != nil {
|
||||
if err := f.blockingService.NftReload(f.nft, nftTable.BlockList().ListIP(), nftTable.BlockList().ListIPWithPort()); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.blocklist.NftReload(f.chains.NewBlocklist); err != nil {
|
||||
if err := f.blocklist.NftReload(nftTable.BlockList().Blocks()); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to reload blocklist: %s", err))
|
||||
}
|
||||
|
||||
@@ -128,13 +152,17 @@ func (f *firewall) ClearRules() {
|
||||
f.logger.Debug("Clear nftables rules")
|
||||
|
||||
switch f.config.Options.ClearMode {
|
||||
case ClearModeGlobal:
|
||||
if err := f.nft.Clear(); err != nil {
|
||||
case config.ClearModeGlobal:
|
||||
if err := f.nft.NFT().Clear(); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to clear rules: %s", err))
|
||||
}
|
||||
break
|
||||
case ClearModeOwn:
|
||||
if err := f.chains.ClearRules(); err != nil {
|
||||
case config.ClearModeOwn:
|
||||
if f.table == nil {
|
||||
f.logger.Error("table is nil")
|
||||
return
|
||||
}
|
||||
if err := f.table.Clear(); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to clear rules: %s", err))
|
||||
}
|
||||
break
|
||||
@@ -167,7 +195,7 @@ func (f *firewall) SavesRules() {
|
||||
}
|
||||
|
||||
args := []string{"list", "ruleset"}
|
||||
output, err := f.nft.Command().RunWithOutput(args...)
|
||||
output, err := f.nft.NFT().Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
f.logger.Warn(fmt.Sprintf("Failed to save rules: %s", err))
|
||||
return
|
||||
@@ -183,6 +211,10 @@ func (f *firewall) SavesRules() {
|
||||
f.logger.Info("Save nftables rules")
|
||||
}
|
||||
|
||||
func (f *firewall) HasRules() (bool, error) {
|
||||
return f.table.HasRules()
|
||||
}
|
||||
|
||||
func (f *firewall) BlockIP(blockIP blocking.BlockIP) (bool, error) {
|
||||
isBanned, err := f.blockingService.BlockIP(blockIP)
|
||||
|
||||
@@ -204,3 +236,47 @@ func (f *firewall) BlockIPWithPorts(blockIP blocking.BlockIPWithPorts) (bool, er
|
||||
func (f *firewall) DockerSupport() bool {
|
||||
return f.config.Options.DockerSupport
|
||||
}
|
||||
|
||||
func (f *firewall) pathFileCacheNFT() string {
|
||||
return strings.TrimRight(f.dataDir, "/") + "/nftables.nft"
|
||||
}
|
||||
|
||||
func (f *firewall) isValidCacheFile(daemonInfo info.Info) bool {
|
||||
if daemonInfo.IsVersionChanged() {
|
||||
f.logger.Debug("Version changed, skip cache")
|
||||
return false
|
||||
}
|
||||
|
||||
if daemonInfo.IsSettingsChanged() {
|
||||
f.logger.Debug("Settings changed, skip cache")
|
||||
return false
|
||||
}
|
||||
|
||||
fileNFT := f.pathFileCacheNFT()
|
||||
if !filesystem.FileExists(fileNFT) {
|
||||
return false
|
||||
}
|
||||
|
||||
metadataChecksum, err := daemonInfo.Metadata().FirewallFileNft().Get()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to get checksum: %s", err))
|
||||
return false
|
||||
}
|
||||
|
||||
if metadataChecksum == "" {
|
||||
return false
|
||||
}
|
||||
|
||||
checksum, err := filesystem.FileChecksum(fileNFT)
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to calculate checksum for %s: %s", fileNFT, err))
|
||||
return false
|
||||
}
|
||||
|
||||
if checksum != metadataChecksum {
|
||||
f.logger.Warn(fmt.Sprintf("Checksum of %s is not equal to metadata checksum", fileNFT))
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
type Sets interface {
|
||||
Add(name string, params string) error
|
||||
}
|
||||
|
||||
type setBatch struct {
|
||||
builder nft.BatchBuilder
|
||||
family family.Type
|
||||
table string
|
||||
}
|
||||
|
||||
func NewBatchSet(builder nft.BatchBuilder, family family.Type, table string) Sets {
|
||||
return &setBatch{
|
||||
builder: builder,
|
||||
family: family,
|
||||
table: table,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *setBatch) Add(name string, params string) error {
|
||||
command := []string{
|
||||
"add set", b.family.String(), b.table, name, "{ " + params + " }",
|
||||
}
|
||||
return b.builder.Command().Run(command...)
|
||||
}
|
||||
|
||||
func getNamesIP(name string) (ipV4 string, ipV6 string) {
|
||||
return name + "_ip4", name + "_ip6"
|
||||
}
|
||||
@@ -0,0 +1,100 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
nftFirewall "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/rule"
|
||||
)
|
||||
|
||||
type Blocklist interface {
|
||||
// ReplaceElements Replace the elements of the list.
|
||||
ReplaceElements(ipV4 []string, ipV6 []string, pathSaveNft string) error
|
||||
|
||||
ReplaceElementsWithFile(pathNft string) error
|
||||
|
||||
// AddRuleToChain Add a rule to the parent chain.
|
||||
AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error
|
||||
}
|
||||
|
||||
type blocklist struct {
|
||||
nft nftFirewall.NFT
|
||||
|
||||
listIPv4 List
|
||||
listIPv6 List
|
||||
}
|
||||
|
||||
func NewBlocklist(nft nftFirewall.NFT, builder nft.BatchBuilder, family family.Type, table string, name string) (Blocklist, error) {
|
||||
listNameV4, listNameV6 := getNamesIP(name)
|
||||
|
||||
params := "type ipv4_addr; flags interval; auto-merge;"
|
||||
listIPv4, err := newList(nft, builder, family, table, listNameV4, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = "type ipv6_addr; flags interval; auto-merge;"
|
||||
listIPv6, err := newList(nft, builder, family, table, listNameV6, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &blocklist{
|
||||
nft: nft,
|
||||
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBlocklistWithoutCommand(nft nftFirewall.NFT, family family.Type, table string, name string) Blocklist {
|
||||
listNameV4, listNameV6 := getNamesIP(name)
|
||||
|
||||
listIPv4 := newListWithoutCommand(nft, family, table, listNameV4)
|
||||
listIPv6 := newListWithoutCommand(nft, family, table, listNameV6)
|
||||
|
||||
return &blocklist{
|
||||
nft: nft,
|
||||
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *blocklist) ReplaceElements(ipV4 []string, ipV6 []string, pathSaveNft string) error {
|
||||
batchBuilder, err := l.nft.NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = batchBuilder.Close()
|
||||
}()
|
||||
|
||||
if err := l.listIPv4.ReplaceBatchElements(batchBuilder, ipV4); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := l.listIPv6.ReplaceBatchElements(batchBuilder, ipV6); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.nft.RunBatchAndMoveFile(batchBuilder, pathSaveNft)
|
||||
}
|
||||
|
||||
func (l *blocklist) ReplaceElementsWithFile(pathNft string) error {
|
||||
args := []string{"-f", pathNft}
|
||||
return l.nft.NFT().Command().Run(args...)
|
||||
}
|
||||
|
||||
func (l *blocklist) AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error {
|
||||
addRule := "ip saddr @" + l.listIPv4.Name() + " " + action
|
||||
if err := chainAddRuleFunc(addRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
addRule = "ip6 saddr @" + l.listIPv6.Name() + " " + action
|
||||
if err := chainAddRuleFunc(addRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
nftFirewall "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft"
|
||||
)
|
||||
|
||||
type List interface {
|
||||
Name() string
|
||||
AddElement(element string) error
|
||||
AddBatchElement(builder nft.BatchBuilder, element string) error
|
||||
DeleteElement(element string) error
|
||||
ReplaceElements(elements []string) error
|
||||
ReplaceBatchElements(builder nft.BatchBuilder, elements []string) error
|
||||
}
|
||||
|
||||
type list struct {
|
||||
nft nftFirewall.NFT
|
||||
family family.Type
|
||||
table string
|
||||
name string
|
||||
}
|
||||
|
||||
func newList(nft nftFirewall.NFT, builder nft.BatchBuilder, family family.Type, table string, name string, params string) (List, error) {
|
||||
command := []string{
|
||||
"add set", family.String(), table, name, "{ " + params + " }",
|
||||
}
|
||||
if err := builder.Command().Run(command...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &list{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
name: name,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func newListWithoutCommand(nft nftFirewall.NFT, family family.Type, table string, name string) List {
|
||||
return &list{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
name: name,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *list) Name() string {
|
||||
return l.name
|
||||
}
|
||||
|
||||
func (l *list) AddElement(element string) error {
|
||||
command := []string{
|
||||
"add element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", element),
|
||||
}
|
||||
return l.nft.NFT().Command().Run(command...)
|
||||
}
|
||||
|
||||
func (l *list) AddBatchElement(builder nft.BatchBuilder, element string) error {
|
||||
command := []string{
|
||||
"add element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", element),
|
||||
}
|
||||
return builder.Command().Run(command...)
|
||||
}
|
||||
|
||||
func (l *list) DeleteElement(element string) error {
|
||||
command := []string{
|
||||
"delete element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", element),
|
||||
}
|
||||
return l.nft.NFT().Command().Run(command...)
|
||||
}
|
||||
|
||||
func (l *list) ReplaceElements(elements []string) error {
|
||||
batchBuilder, err := l.nft.NewBuildBatch()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = batchBuilder.Close()
|
||||
}()
|
||||
|
||||
if err := l.replaceElements(batchBuilder, elements); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return l.nft.RunBatch(batchBuilder)
|
||||
}
|
||||
|
||||
func (l *list) ReplaceBatchElements(builder nft.BatchBuilder, elements []string) error {
|
||||
return l.replaceElements(builder, elements)
|
||||
}
|
||||
|
||||
func (l *list) replaceElements(builder nft.BatchBuilder, elements []string) error {
|
||||
if err := builder.Command().Run("flush set", l.family.String(), l.table, l.name); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if len(elements) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
command := []string{
|
||||
"add element",
|
||||
l.family.String(), l.table, l.name,
|
||||
fmt.Sprintf("{ %s }", strings.Join(elements, ",")),
|
||||
}
|
||||
|
||||
return builder.Command().Run(command...)
|
||||
}
|
||||
@@ -0,0 +1,119 @@
|
||||
package block
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
nftFirewall "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/rule"
|
||||
)
|
||||
|
||||
type ListIP interface {
|
||||
// AddIP Add an IP address to the list.
|
||||
AddIP(addr net.IP, banSeconds uint32) error
|
||||
|
||||
// AddBatchIP Add an IP address to the list.
|
||||
AddBatchIP(builder nft.BatchBuilder, addr net.IP, banSeconds uint32) error
|
||||
|
||||
// DeleteIP Delete an IP address from the list.
|
||||
DeleteIP(addr net.IP) error
|
||||
|
||||
// AddRuleToChain Add a rule to the parent chain.
|
||||
AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error
|
||||
}
|
||||
|
||||
type listIP struct {
|
||||
listIPv4 List
|
||||
listIPv6 List
|
||||
}
|
||||
|
||||
func NewListIP(nft nftFirewall.NFT, builder nft.BatchBuilder, family family.Type, table string, name string) (ListIP, error) {
|
||||
listNameV4, listNameV6 := getNamesIP(name)
|
||||
|
||||
params := "type ipv4_addr; flags interval, timeout;"
|
||||
listIPv4, err := newList(nft, builder, family, table, listNameV4, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = "type ipv6_addr; flags interval, timeout;"
|
||||
listIPv6, err := newList(nft, builder, family, table, listNameV6, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &listIP{
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewListIPWithoutCommand(nft nftFirewall.NFT, family family.Type, table string, name string) ListIP {
|
||||
listNameV4, listNameV6 := getNamesIP(name)
|
||||
|
||||
listIPv4 := newListWithoutCommand(nft, family, table, listNameV4)
|
||||
listIPv6 := newListWithoutCommand(nft, family, table, listNameV6)
|
||||
|
||||
return &listIP{
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *listIP) AddIP(addr net.IP, banSeconds uint32) error {
|
||||
el := []string{addr.String()}
|
||||
if banSeconds > 0 {
|
||||
el = append(el, "timeout", fmt.Sprintf("%ds", banSeconds))
|
||||
}
|
||||
|
||||
element := strings.Join(el, " ")
|
||||
|
||||
if addr.To4() != nil {
|
||||
return l.listIPv4.AddElement(element)
|
||||
}
|
||||
|
||||
return l.listIPv6.AddElement(element)
|
||||
}
|
||||
|
||||
func (l *listIP) AddBatchIP(builder nft.BatchBuilder, addr net.IP, banSeconds uint32) error {
|
||||
el := []string{addr.String()}
|
||||
if banSeconds > 0 {
|
||||
el = append(el, "timeout", fmt.Sprintf("%ds", banSeconds))
|
||||
}
|
||||
|
||||
element := strings.Join(el, " ")
|
||||
|
||||
if addr.To4() != nil {
|
||||
return l.listIPv4.AddBatchElement(builder, element)
|
||||
}
|
||||
|
||||
return l.listIPv6.AddBatchElement(builder, element)
|
||||
}
|
||||
|
||||
func (l *listIP) DeleteIP(addr net.IP) error {
|
||||
if addr == nil {
|
||||
return fmt.Errorf("IP address cannot be nil")
|
||||
}
|
||||
if addr.To4() != nil {
|
||||
return l.listIPv4.DeleteElement(addr.String())
|
||||
}
|
||||
|
||||
return l.listIPv6.DeleteElement(addr.String())
|
||||
}
|
||||
|
||||
func (l *listIP) AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error {
|
||||
rule := "ip saddr @" + l.listIPv4.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
rule = "ip6 saddr @" + l.listIPv6.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
+48
-8
@@ -5,8 +5,10 @@ import (
|
||||
"net"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
nftFirewall "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/rule"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
|
||||
)
|
||||
|
||||
@@ -14,11 +16,14 @@ type ListIPWithPort interface {
|
||||
// AddIP Add an IP address to the list.
|
||||
AddIP(addr net.IP, ports []types.L4Port, banSeconds uint32) error
|
||||
|
||||
// AddBatchIP Add an IP address to the list.
|
||||
AddBatchIP(builder nft.BatchBuilder, addr net.IP, ports []types.L4Port, banSeconds uint32) error
|
||||
|
||||
// DeleteIP Delete an IP address from the list.
|
||||
DeleteIP(addr net.IP, port types.L4Port) error
|
||||
|
||||
// AddRuleToChain Add a rule to the parent chain.
|
||||
AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error
|
||||
AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error
|
||||
}
|
||||
|
||||
type listIPWithPort struct {
|
||||
@@ -26,17 +31,17 @@ type listIPWithPort struct {
|
||||
listIPv6 List
|
||||
}
|
||||
|
||||
func NewListIPWithPort(nft nft.NFT, family family.Type, table string, name string) (ListIPWithPort, error) {
|
||||
func NewListIPWithPort(nft nftFirewall.NFT, builder nft.BatchBuilder, family family.Type, table string, name string) (ListIPWithPort, error) {
|
||||
listNameV4, listNameV6 := getNamesIP(name)
|
||||
|
||||
params := "type ipv4_addr . inet_proto . inet_service; flags interval, timeout;"
|
||||
listName := name + "_ip4"
|
||||
listIPv4, err := newList(nft, family, table, listName, params)
|
||||
listIPv4, err := newList(nft, builder, family, table, listNameV4, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
params = "type ipv6_addr . inet_proto . inet_service; flags interval, timeout;"
|
||||
listName = name + "_ip6"
|
||||
listIPv6, err := newList(nft, family, table, listName, params)
|
||||
listIPv6, err := newList(nft, builder, family, table, listNameV6, params)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -47,6 +52,18 @@ func NewListIPWithPort(nft nft.NFT, family family.Type, table string, name strin
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewListIPWithPortWithoutCommand(nft nftFirewall.NFT, family family.Type, table string, name string) ListIPWithPort {
|
||||
listNameV4, listNameV6 := getNamesIP(name)
|
||||
|
||||
listIPv4 := newListWithoutCommand(nft, family, table, listNameV4)
|
||||
listIPv6 := newListWithoutCommand(nft, family, table, listNameV6)
|
||||
|
||||
return &listIPWithPort{
|
||||
listIPv4: listIPv4,
|
||||
listIPv6: listIPv6,
|
||||
}
|
||||
}
|
||||
|
||||
func (l *listIPWithPort) AddIP(addr net.IP, ports []types.L4Port, banSeconds uint32) error {
|
||||
if len(ports) == 0 {
|
||||
return fmt.Errorf("ports is empty")
|
||||
@@ -70,6 +87,29 @@ func (l *listIPWithPort) AddIP(addr net.IP, ports []types.L4Port, banSeconds uin
|
||||
return l.listIPv6.AddElement(element)
|
||||
}
|
||||
|
||||
func (l *listIPWithPort) AddBatchIP(builder nft.BatchBuilder, addr net.IP, ports []types.L4Port, banSeconds uint32) error {
|
||||
if len(ports) == 0 {
|
||||
return fmt.Errorf("ports is empty")
|
||||
}
|
||||
|
||||
var elements []string
|
||||
for _, port := range ports {
|
||||
el := []string{fmt.Sprintf("%s . %s . %d", addr.String(), port.ProtocolString(), port.Number())}
|
||||
if banSeconds > 0 {
|
||||
el = append(el, "timeout", fmt.Sprintf("%ds", banSeconds))
|
||||
}
|
||||
|
||||
elements = append(elements, strings.Join(el, " "))
|
||||
}
|
||||
|
||||
element := strings.Join(elements, ",")
|
||||
if addr.To4() != nil {
|
||||
return l.listIPv4.AddBatchElement(builder, element)
|
||||
}
|
||||
|
||||
return l.listIPv6.AddBatchElement(builder, element)
|
||||
}
|
||||
|
||||
func (l *listIPWithPort) DeleteIP(addr net.IP, port types.L4Port) error {
|
||||
if addr == nil {
|
||||
return fmt.Errorf("IP address cannot be nil")
|
||||
@@ -87,7 +127,7 @@ func (l *listIPWithPort) DeleteIP(addr net.IP, port types.L4Port) error {
|
||||
return l.listIPv6.DeleteElement(element)
|
||||
}
|
||||
|
||||
func (l *listIPWithPort) AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error {
|
||||
func (l *listIPWithPort) AddRuleToChain(chainAddRuleFunc rule.AddFunc, action string) error {
|
||||
rule := "ip saddr . meta l4proto . th dport @" + l.listIPv4.Name() + " " + action
|
||||
if err := chainAddRuleFunc(rule); err != nil {
|
||||
return err
|
||||
@@ -0,0 +1,59 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
)
|
||||
|
||||
type Chain interface {
|
||||
AddRule(expr ...string) error
|
||||
AddRuleIn(AddRuleFunc rule.AddFunc) error
|
||||
AddRuleOut(AddRuleFunc rule.AddFunc) error
|
||||
}
|
||||
|
||||
type batchChain struct {
|
||||
builder nft.BatchBuilder
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func NewBatchChain(builder nft.BatchBuilder, family family.Type, table string, chain string) (Chain, error) {
|
||||
if err := builder.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &batchChain{
|
||||
builder: builder,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func NewBatchChainWithOptions(builder nft.BatchBuilder, family family.Type, table string, chain string, baseChain nftChain.ChainOptions) (Chain, error) {
|
||||
if err := builder.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &batchChain{
|
||||
builder: builder,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (b *batchChain) AddRule(expr ...string) error {
|
||||
return b.builder.Rule().Add(b.family, b.table, b.chain, expr...)
|
||||
}
|
||||
|
||||
func (b *batchChain) AddRuleIn(AddRuleFunc rule.AddFunc) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + b.chain)
|
||||
}
|
||||
|
||||
func (b *batchChain) AddRuleOut(AddRuleFunc rule.AddFunc) error {
|
||||
return AddRuleFunc("oifname != \"lo\" counter jump " + b.chain)
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/data"
|
||||
)
|
||||
|
||||
type Docker interface {
|
||||
Name() string
|
||||
AddRule(builder nft.BatchBuilder, expr ...string) error
|
||||
JumpTo(builder nft.BatchBuilder, chain Docker, rule string, comment string) error
|
||||
ListRules() ([]data.Rule, error)
|
||||
RemoveRuleByHandle(builder nft.BatchBuilder, handle uint64) error
|
||||
Clear(builder nft.BatchBuilder) error
|
||||
}
|
||||
|
||||
type docker struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
}
|
||||
|
||||
func NewDocker(nft nft.NFT, family family.Type, table, chain string) Docker {
|
||||
return &docker{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *docker) Name() string {
|
||||
return d.chain
|
||||
}
|
||||
|
||||
func (d *docker) AddRule(builder nft.BatchBuilder, expr ...string) error {
|
||||
return builder.Rule().Add(d.family, d.table, d.chain, expr...)
|
||||
}
|
||||
|
||||
func (d *docker) JumpTo(builder nft.BatchBuilder, chain Docker, rule string, comment string) error {
|
||||
args := []string{rule, "jump", d.chain, comment}
|
||||
return chain.AddRule(builder, args...)
|
||||
}
|
||||
|
||||
func (d *docker) ListRules() ([]data.Rule, error) {
|
||||
args := []string{"-a", "-j", "list", "chain", d.family.String(), d.table, d.chain}
|
||||
jsonData, err := d.nft.Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var output data.NftOutput
|
||||
if err := json.Unmarshal([]byte(jsonData), &output); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var rules []data.Rule
|
||||
for _, el := range output.Nftables {
|
||||
if el.Rule != nil {
|
||||
rules = append(rules, *el.Rule)
|
||||
}
|
||||
}
|
||||
|
||||
return rules, nil
|
||||
}
|
||||
|
||||
func (d *docker) RemoveRuleByHandle(builder nft.BatchBuilder, handle uint64) error {
|
||||
return builder.Rule().Delete(d.family, d.table, d.chain, handle)
|
||||
}
|
||||
|
||||
func (d *docker) Clear(builder nft.BatchBuilder) error {
|
||||
return builder.Chain().Clear(d.family, d.table, d.chain)
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
func NewBatchForward(builder nft.BatchBuilder, family family.Type, table string, chain string, defaultAllow bool, priority int) (Chain, error) {
|
||||
policy := nftChain.PolicyDrop
|
||||
if defaultAllow {
|
||||
policy = nftChain.PolicyAccept
|
||||
}
|
||||
|
||||
baseChain := nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookForward,
|
||||
Priority: int32(priority),
|
||||
Policy: policy,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
if err := builder.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &batchChain{
|
||||
builder: builder,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
func NewBatchInput(builder nft.BatchBuilder, family family.Type, table string, chain string, defaultAllow bool, priority int) (Chain, error) {
|
||||
policy := nftChain.PolicyDrop
|
||||
if defaultAllow {
|
||||
policy = nftChain.PolicyAccept
|
||||
}
|
||||
|
||||
baseChain := nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookInput,
|
||||
Priority: int32(priority),
|
||||
Policy: policy,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
if err := builder.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &batchChain{
|
||||
builder: builder,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
package chain
|
||||
|
||||
import (
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
)
|
||||
|
||||
func NewBatchOutput(builder nft.BatchBuilder, family family.Type, table string, chain string, defaultAllow bool, priority int) (Chain, error) {
|
||||
policy := nftChain.PolicyDrop
|
||||
if defaultAllow {
|
||||
policy = nftChain.PolicyAccept
|
||||
}
|
||||
|
||||
baseChain := nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookOutput,
|
||||
Priority: int32(priority),
|
||||
Policy: policy,
|
||||
Device: "",
|
||||
}
|
||||
|
||||
if err := builder.Chain().Add(family, table, chain, baseChain); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &batchChain{
|
||||
builder: builder,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
}, nil
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
package chain
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
|
||||
type PacketFilter interface {
|
||||
AddRuleIn(AddRuleFunc rule.AddFunc) error
|
||||
AddRuleOut(AddRuleFunc rule.AddFunc) error
|
||||
}
|
||||
|
||||
type packetFilter struct {
|
||||
chainName string
|
||||
}
|
||||
|
||||
// NewPacketFilter drop-out-of-order packets and packets in an INVALID state in nftables connection tracking.
|
||||
func NewPacketFilter(chainName string) PacketFilter {
|
||||
return &packetFilter{
|
||||
chainName: chainName,
|
||||
}
|
||||
}
|
||||
|
||||
func (pf *packetFilter) AddRuleIn(addRuleFunc rule.AddFunc) error {
|
||||
return addRuleFunc("iifname != \"lo\" meta l4proto tcp counter jump " + pf.chainName)
|
||||
}
|
||||
|
||||
func (pf *packetFilter) AddRuleOut(addRuleFunc rule.AddFunc) error {
|
||||
return addRuleFunc("oifname != \"lo\" meta l4proto tcp counter jump " + pf.chainName)
|
||||
}
|
||||
|
||||
type packetFilterFalse struct{}
|
||||
|
||||
// NewPacketFilterFalse returns a PacketFilter that does nothing.
|
||||
func NewPacketFilterFalse() PacketFilter {
|
||||
return &packetFilterFalse{}
|
||||
}
|
||||
|
||||
func (pf *packetFilterFalse) AddRuleIn(_ rule.AddFunc) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (pf *packetFilterFalse) AddRuleOut(_ rule.AddFunc) error {
|
||||
return nil
|
||||
}
|
||||
+23
-20
@@ -2,11 +2,12 @@ package chain
|
||||
|
||||
import (
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
"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/pkg/ip"
|
||||
)
|
||||
@@ -26,31 +27,28 @@ type PortKnocking interface {
|
||||
timeout uint32,
|
||||
action types.KnockAction,
|
||||
) error
|
||||
AddRuleIn(AddRuleFunc func(expr ...string) error) error
|
||||
AddRuleIn(AddRuleFunc rule.AddFunc) error
|
||||
}
|
||||
|
||||
type portKnocking struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
table string
|
||||
chain string
|
||||
chain Chain
|
||||
sets block.Sets
|
||||
}
|
||||
|
||||
func newPortKnocking(nft nft.NFT, family family.Type, table string, chain string) (PortKnocking, error) {
|
||||
if err := nft.Chain().Add(family, table, chain, nftChain.TypeNone); err != nil {
|
||||
func NewBatchPortKnocking(builder nft.BatchBuilder, family family.Type, table string, chain string) (PortKnocking, error) {
|
||||
batchChain, err := NewBatchChain(builder, family, table, chain)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &portKnocking{
|
||||
nft: nft,
|
||||
family: family,
|
||||
table: table,
|
||||
chain: chain,
|
||||
chain: batchChain,
|
||||
sets: block.NewBatchSet(builder, family, table),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (k *portKnocking) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
|
||||
return AddRuleFunc("iifname != \"lo\" counter jump " + k.chain)
|
||||
func (k *portKnocking) AddRuleIn(AddRuleFunc rule.AddFunc) error {
|
||||
return k.chain.AddRuleIn(AddRuleFunc)
|
||||
}
|
||||
|
||||
func (k *portKnocking) AddFirstStageRule(
|
||||
@@ -60,7 +58,7 @@ func (k *portKnocking) AddFirstStageRule(
|
||||
timeout uint32,
|
||||
action types.KnockAction,
|
||||
) error {
|
||||
if err := block.NewPortKnocking(k.nft, k.family, k.table, name, ipVersion, timeout); err != nil {
|
||||
if err := k.newPortKnocking(name, ipVersion, timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -68,7 +66,7 @@ func (k *portKnocking) AddFirstStageRule(
|
||||
l4Port.ProtocolString(), "dport", l4Port.NumberString(), "add", "@" + name,
|
||||
"{", ipVersion.ToNft(), "saddr timeout", strconv.Itoa(int(timeout)) + "s", "}", action.String(),
|
||||
}
|
||||
return k.nft.Rule().Add(k.family, k.table, k.chain, expr...)
|
||||
return k.chain.AddRule(expr...)
|
||||
}
|
||||
|
||||
func (k *portKnocking) AddNextStageRule(
|
||||
@@ -78,7 +76,7 @@ func (k *portKnocking) AddNextStageRule(
|
||||
timeout uint32,
|
||||
action types.KnockAction,
|
||||
) error {
|
||||
if err := block.NewPortKnocking(k.nft, k.family, k.table, nextName, ipVersion, timeout); err != nil {
|
||||
if err := k.newPortKnocking(nextName, ipVersion, timeout); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -87,5 +85,10 @@ func (k *portKnocking) AddNextStageRule(
|
||||
l4Port.ProtocolString(), "dport", l4Port.NumberString(), "add", "@" + nextName,
|
||||
"{", ipVersion.ToNft(), "saddr}", action.String(),
|
||||
}
|
||||
return k.nft.Rule().Add(k.family, k.table, k.chain, expr...)
|
||||
return k.chain.AddRule(expr...)
|
||||
}
|
||||
|
||||
func (k *portKnocking) newPortKnocking(name string, ipVersion ip.Version, timeout uint32) error {
|
||||
params := []string{"type", ipVersion.ToNftForSet() + ";", "flags timeout; timeout", strconv.Itoa(int(timeout)) + "s;"}
|
||||
return k.sets.Add(name, strings.Join(params, " "))
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package data
|
||||
|
||||
type NftOutput struct {
|
||||
Nftables []NftElement `json:"nftables"`
|
||||
}
|
||||
type NftElement struct {
|
||||
Rule *Rule `json:"rule,omitempty"`
|
||||
}
|
||||
|
||||
type Rule struct {
|
||||
Handle uint64 `json:"handle"`
|
||||
Comment string `json:"comment"`
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
package nft
|
||||
|
||||
import (
|
||||
nftables "git.kor-elf.net/kor-elf-shield/go-nftables-client"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
)
|
||||
|
||||
type NFT interface {
|
||||
NewBuildBatch() (nft.BatchBuilder, error)
|
||||
RunBatch(batchBuilder nft.BatchBuilder) error
|
||||
RunBatchAndMoveFile(batchBuilder nft.BatchBuilder, fileDir string) error
|
||||
NFT() nft.NFT
|
||||
}
|
||||
|
||||
type nftImpl struct {
|
||||
nft nft.NFT
|
||||
tmpDir string
|
||||
}
|
||||
|
||||
func New(nft nft.NFT, tmpDir string) NFT {
|
||||
return &nftImpl{
|
||||
nft: nft,
|
||||
tmpDir: tmpDir,
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nftImpl) NFT() nft.NFT {
|
||||
return n.nft
|
||||
}
|
||||
|
||||
func (n *nftImpl) NewBuildBatch() (nft.BatchBuilder, error) {
|
||||
return nftables.NewBatchBuilder(n.tmpDir)
|
||||
}
|
||||
|
||||
func (n *nftImpl) RunBatch(batchBuilder nft.BatchBuilder) error {
|
||||
batch := batchBuilder.Build()
|
||||
defer func() {
|
||||
_ = batch.Close()
|
||||
}()
|
||||
|
||||
return n.nft.ExecuteBatchAfterCheck(batch)
|
||||
}
|
||||
|
||||
func (n *nftImpl) RunBatchAndMoveFile(batchBuilder nft.BatchBuilder, fileDir string) error {
|
||||
batch := batchBuilder.Build()
|
||||
defer func() {
|
||||
_ = batch.Close()
|
||||
}()
|
||||
if err := n.nft.ExecuteBatchAfterCheck(batch); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return batch.MoveFile(fileDir)
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package rule
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/config"
|
||||
|
||||
type AddFunc func(expr ...string) error
|
||||
|
||||
func InputAddIP(addRuleFunc AddFunc, config config.ConfigIP, ipMatch string) error {
|
||||
rule := ipMatch + " saddr " + config.IP + " iifname != \"lo\""
|
||||
if !config.OnlyIP {
|
||||
rule += " " + config.Port.ProtocolString() + " dport " + config.Port.NumberString()
|
||||
}
|
||||
if config.LimitRate != "" {
|
||||
rule += " limit rate " + config.LimitRate
|
||||
}
|
||||
rule += " counter " + config.Action.String()
|
||||
return addRuleFunc(rule)
|
||||
}
|
||||
|
||||
func OutputAddIP(addRuleFunc AddFunc, config config.ConfigIP, ipMatch string) error {
|
||||
rule := ipMatch + " daddr " + config.IP + " oifname != \"lo\""
|
||||
if !config.OnlyIP {
|
||||
rule += " " + config.Port.ProtocolString() + " dport " + config.Port.NumberString()
|
||||
}
|
||||
if config.LimitRate != "" {
|
||||
rule += " limit rate " + config.LimitRate
|
||||
}
|
||||
rule += " counter " + config.Action.String()
|
||||
return addRuleFunc(rule)
|
||||
}
|
||||
|
||||
func ForwardAddIP(addRuleFunc AddFunc, config config.ConfigIP, ipMatch string) error {
|
||||
rule := ipMatch + " saddr " + config.IP + " iifname != \"lo\""
|
||||
|
||||
// There, during routing, the port changes and then the IP blocking rule will not work.
|
||||
//if !config.OnlyIP {
|
||||
// rule += " " + config.Protocol.String() + " dport " + strconv.Itoa(int(config.Port))
|
||||
//}
|
||||
|
||||
rule += " counter " + config.Action.String()
|
||||
if err := addRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,126 @@
|
||||
package table
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
)
|
||||
|
||||
type Table interface {
|
||||
Clear() error
|
||||
DockerChains() firewall.NFTDockerChains
|
||||
BlockList() BlockList
|
||||
|
||||
// HasRules Return true if the table has rules
|
||||
HasRules() (bool, error)
|
||||
}
|
||||
|
||||
type BlockList interface {
|
||||
ListIP() block.ListIP
|
||||
ListIPWithPort() block.ListIPWithPort
|
||||
Blocks() map[string]block.Blocklist
|
||||
}
|
||||
|
||||
type table struct {
|
||||
nft nft.NFT
|
||||
family family.Type
|
||||
name string
|
||||
|
||||
dockerChains firewall.NFTDockerChains
|
||||
blockList BlockList
|
||||
}
|
||||
|
||||
func New(
|
||||
nft nft.NFT, family family.Type, name string,
|
||||
blockList BlockList, dockerChains firewall.NFTDockerChains,
|
||||
) Table {
|
||||
return &table{
|
||||
nft: nft,
|
||||
family: family,
|
||||
name: name,
|
||||
|
||||
dockerChains: dockerChains,
|
||||
blockList: blockList,
|
||||
}
|
||||
}
|
||||
|
||||
func (t *table) Clear() error {
|
||||
// clear does not clean completely
|
||||
return t.nft.NFT().Table().Delete(t.family, t.name)
|
||||
}
|
||||
|
||||
func (t *table) DockerChains() firewall.NFTDockerChains {
|
||||
return t.dockerChains
|
||||
}
|
||||
|
||||
func (t *table) BlockList() BlockList {
|
||||
return t.blockList
|
||||
}
|
||||
|
||||
func (t *table) HasRules() (bool, error) {
|
||||
if exists, err := t.exists(); err != nil {
|
||||
return false, err
|
||||
} else if !exists {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
args := []string{"list", "table", t.family.String(), t.name}
|
||||
output, err := t.nft.NFT().Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
openBrace := strings.Index(output, "{")
|
||||
closeBrace := strings.LastIndex(output, "}")
|
||||
if openBrace == -1 || closeBrace == -1 || closeBrace <= openBrace {
|
||||
return false, nil
|
||||
}
|
||||
|
||||
content := strings.TrimSpace(output[openBrace+1 : closeBrace])
|
||||
|
||||
return content != "", nil
|
||||
}
|
||||
|
||||
func (t *table) exists() (bool, error) {
|
||||
args := []string{"list", "tables", t.family.String()}
|
||||
output, err := t.nft.NFT().Command().RunWithOutput(args...)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, line := range strings.Split(output, "\n") {
|
||||
if strings.TrimSpace(line) == "table "+t.family.String()+" "+t.name {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
|
||||
return false, nil
|
||||
}
|
||||
|
||||
type blockList struct {
|
||||
listIP block.ListIP
|
||||
listIPWithPort block.ListIPWithPort
|
||||
blocks map[string]block.Blocklist
|
||||
}
|
||||
|
||||
func NewBlockList(listIP block.ListIP, listIPWithPort block.ListIPWithPort, blocks map[string]block.Blocklist) BlockList {
|
||||
return &blockList{
|
||||
listIP: listIP,
|
||||
listIPWithPort: listIPWithPort,
|
||||
blocks: blocks,
|
||||
}
|
||||
}
|
||||
|
||||
func (b *blockList) ListIP() block.ListIP {
|
||||
return b.listIP
|
||||
}
|
||||
|
||||
func (b *blockList) ListIPWithPort() block.ListIPWithPort {
|
||||
return b.listIPWithPort
|
||||
}
|
||||
|
||||
func (b *blockList) Blocks() map[string]block.Blocklist {
|
||||
return b.blocks
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/chain"
|
||||
nftTable "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/table"
|
||||
)
|
||||
|
||||
func (r *reload) blockList(builder nft.BatchBuilder, beforeLocalInput chain.Chain, blocks map[string]block.Blocklist) (nftTable.BlockList, error) {
|
||||
listBlockedIP, err := block.NewListIP(r.nft, builder, r.table.family, r.table.name, blockedIP)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := listBlockedIP.AddRuleToChain(beforeLocalInput.AddRule, "drop"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
listBlockedIPWithPort, err := block.NewListIPWithPort(r.nft, builder, r.table.family, r.table.name, blockedIPWithPort)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := listBlockedIPWithPort.AddRuleToChain(beforeLocalInput.AddRule, "drop"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return nftTable.NewBlockList(listBlockedIP, listBlockedIPWithPort, blocks), nil
|
||||
}
|
||||
|
||||
func (r *reload) moduleBlockList(builder nft.BatchBuilder, afterLocalInput chain.Chain, blockListNames []string) (blocks map[string]block.Blocklist, err error) {
|
||||
r.logger.Debug("Reload blocklist")
|
||||
blocks = make(map[string]block.Blocklist)
|
||||
for _, blockListName := range blockListNames {
|
||||
if blockListName == "" {
|
||||
continue
|
||||
}
|
||||
r.logger.Debug(fmt.Sprintf("Reload blocklist from %s", blockListName))
|
||||
blockList, err := block.NewBlocklist(r.nft, builder, r.table.family, r.table.name, getBlocklistName(blockListName))
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to create blocklist: %s", err))
|
||||
continue
|
||||
}
|
||||
if err := blockList.AddRuleToChain(afterLocalInput.AddRule, "drop"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule to chain: %s", err))
|
||||
continue
|
||||
}
|
||||
blocks[blockListName] = blockList
|
||||
}
|
||||
|
||||
return blocks, nil
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"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/chain"
|
||||
)
|
||||
|
||||
func (r *reload) docker(builder nft.BatchBuilder, batchForward chain.Chain, dockerChains firewall.NFTDockerChains) error {
|
||||
r.logger.Debug("Reload docker rules")
|
||||
|
||||
for _, dockerChain := range dockerChains.List() {
|
||||
if err := r.addChain(builder, dockerChain.Name(), nftChain.TypeNone); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := batchForward.AddRule("jump docker_forward_filter"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.dockerPrerouting(builder, dockerChains)
|
||||
}
|
||||
|
||||
func (r *reload) dockerPrerouting(builder nft.BatchBuilder, dockerChains firewall.NFTDockerChains) error {
|
||||
var rule []string
|
||||
|
||||
preroutingNat, err := r.addChainWithReturn(builder, "prerouting_nat", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeNat,
|
||||
Hook: nftChain.HookPrerouting,
|
||||
Priority: -100,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule = []string{"fib daddr type local counter jump ", dockerChains.DockerNat().Name()}
|
||||
if err := preroutingNat.AddRule(rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
preroutingFilter, err := r.addChainWithReturn(builder, "prerouting_filter", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeFilter,
|
||||
Hook: nftChain.HookPrerouting,
|
||||
Priority: -300,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule = []string{"jump ", dockerChains.PreroutingFilter().Name()}
|
||||
if err := preroutingFilter.AddRule(rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
outputNat, err := r.addChainWithReturn(builder, "output_nat", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeNat,
|
||||
Hook: nftChain.HookOutput,
|
||||
Priority: -100,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule = []string{"ip daddr != 127.0.0.0/8 fib daddr type local counter jump ", dockerChains.DockerNat().Name()}
|
||||
if err := outputNat.AddRule(rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
rule = []string{"ip6 daddr != ::1 fib daddr type local counter jump ", dockerChains.DockerNat().Name()}
|
||||
if err := outputNat.AddRule(rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
postroutingNat, err := r.addChainWithReturn(builder, "postrouting_nat", nftChain.BaseChainOptions{
|
||||
Type: nftChain.TypeNat,
|
||||
Hook: nftChain.HookPostrouting,
|
||||
Priority: 300,
|
||||
Policy: nftChain.PolicyAccept,
|
||||
Device: "",
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
rule = []string{"jump ", dockerChains.PostroutingNat().Name()}
|
||||
if err := postroutingNat.AddRule(rule...); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"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/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
|
||||
)
|
||||
|
||||
func (r *reload) forward(builder nft.BatchBuilder, dockerChains firewall.NFTDockerChains) error {
|
||||
r.logger.Debug("Reloading forward chain")
|
||||
|
||||
batchForward, err := chain.NewBatchForward(
|
||||
builder,
|
||||
r.table.family,
|
||||
r.table.name,
|
||||
r.config.MetadataNaming.ChainForwardName,
|
||||
r.config.Policy.DefaultAllowForward,
|
||||
r.config.Policy.ForwardPriority,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localForward, err := chain.NewBatchChain(builder, r.table.family, r.table.name, "local-forward")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.forwardAddIPs(batchForward, localForward); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.config.Options.DockerSupport && dockerChains != nil {
|
||||
if err := r.docker(builder, batchForward, dockerChains); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if r.config.Policy.DefaultAllowForward == false {
|
||||
drop := r.config.Policy.ForwardDrop.String()
|
||||
if err := batchForward.AddRule(drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) forwardAddIPs(batchForward chain.Chain, localForward chain.Chain) error {
|
||||
if err := localForward.AddRuleIn(batchForward.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range r.config.IP4.InIPs {
|
||||
if ipConfig.Action != types.ActionDrop && ipConfig.Action != types.ActionReject {
|
||||
continue
|
||||
}
|
||||
if err := rule.ForwardAddIP(localForward.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range r.config.IP6.InIPs {
|
||||
if ipConfig.Action != types.ActionDrop && ipConfig.Action != types.ActionReject {
|
||||
continue
|
||||
}
|
||||
if err := rule.ForwardAddIP(localForward.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,324 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
nftTable "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/table"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg"
|
||||
)
|
||||
|
||||
func (r *reload) input(builder nft.BatchBuilder, packetfilter chain.PacketFilter, blockListNames []string) (nftTable.BlockList, error) {
|
||||
r.logger.Debug("Reloading input chain")
|
||||
|
||||
batchInput, err := chain.NewBatchInput(
|
||||
builder,
|
||||
r.table.family,
|
||||
r.table.name,
|
||||
r.config.MetadataNaming.ChainInputName,
|
||||
r.config.Policy.DefaultAllowInput,
|
||||
r.config.Policy.InputPriority,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.reloadInputDnsNs(batchInput); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := batchInput.AddRule("iifname lo counter accept"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
beforeLocalInput, err := chain.NewBatchChain(builder, r.table.family, r.table.name, "before-local-input")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := beforeLocalInput.AddRuleIn(batchInput.AddRule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
localInput, err := chain.NewBatchChain(builder, r.table.family, r.table.name, "local-input")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.inputAddIPs(builder, batchInput, localInput); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
afterLocalInput, err := chain.NewBatchChain(builder, r.table.family, r.table.name, "after-local-input")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := afterLocalInput.AddRuleIn(batchInput.AddRule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := packetfilter.AddRuleIn(batchInput.AddRule); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.inputICMP(batchInput); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := batchInput.AddRule("iifname != \"lo\" ct state related,established counter accept"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.inputPorts(batchInput); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if r.config.Policy.DefaultAllowInput == false {
|
||||
drop := r.config.Policy.InputDrop.String()
|
||||
if err := batchInput.AddRule("iifname != \"lo\" " + drop); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
blocks, err := r.moduleBlockList(builder, afterLocalInput, blockListNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return r.blockList(builder, beforeLocalInput, blocks)
|
||||
}
|
||||
|
||||
func (r *reload) reloadInputDnsNs(batchInput chain.Chain) error {
|
||||
if r.config.Options.DnsStrictNs {
|
||||
return nil
|
||||
}
|
||||
|
||||
addresses, err := pkg.Resolv.Addresses()
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to get nameservers: %s", err))
|
||||
return nil
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
continue
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
if err := batchInput.AddRule("ip saddr " + addr + " iifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchInput.AddRule("ip saddr " + addr + " iifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchInput.AddRule("ip saddr " + addr + " iifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchInput.AddRule("ip saddr " + addr + " iifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.To16() != nil {
|
||||
if !r.config.IP6.Enable {
|
||||
r.logger.Warn(fmt.Sprintf("IPv6 is disabled, skipping nameserver address: %s", addr))
|
||||
continue
|
||||
}
|
||||
if err := batchInput.AddRule("ip6 saddr " + addr + " iifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchInput.AddRule("ip6 saddr " + addr + " iifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchInput.AddRule("ip6 saddr " + addr + " iifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchInput.AddRule("ip6 saddr " + addr + " iifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
r.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) inputAddIPs(builder nft.BatchBuilder, batchInput chain.Chain, localInput chain.Chain) error {
|
||||
if err := localInput.AddRuleIn(batchInput.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.inputPortKnocking(builder, localInput); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range r.config.IP4.InIPs {
|
||||
if err := rule.InputAddIP(localInput.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range r.config.IP6.InIPs {
|
||||
if err := rule.InputAddIP(localInput.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) inputPortKnocking(builder nft.BatchBuilder, localInput chain.Chain) error {
|
||||
if len(r.config.PortKnocking) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
portKnocking, err := chain.NewBatchPortKnocking(builder, r.table.family, r.table.name, "port_knocking")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, portKnockingConfig := range r.config.PortKnocking {
|
||||
var knockName, prevKnockName string
|
||||
for index, knock := range portKnockingConfig.Knocks {
|
||||
prevKnockName = knockName
|
||||
knockName = fmt.Sprintf("knock_%s_%d", portKnockingConfig.Name, index)
|
||||
if index == 0 {
|
||||
if err := portKnocking.AddFirstStageRule(knockName, portKnockingConfig.IPVersion, knock.Port, knock.Timeout, knock.Action); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := portKnocking.AddNextStageRule(prevKnockName, knockName, portKnockingConfig.IPVersion, knock.Port, knock.Timeout, knock.Action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
expr := []string{
|
||||
portKnockingConfig.IPVersion.ToNft(), "saddr", "@" + knockName,
|
||||
portKnockingConfig.Port.ProtocolString(), "dport", portKnockingConfig.Port.NumberString(), "accept",
|
||||
}
|
||||
if err := localInput.AddRule(expr...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := portKnocking.AddRuleIn(localInput.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) inputICMP(batchInput chain.Chain) error {
|
||||
drop := r.config.Policy.InputDrop.String()
|
||||
if r.config.IP4.IcmpIn == false {
|
||||
if err := batchInput.AddRule("iifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.inputICMPAfter(batchInput)
|
||||
}
|
||||
|
||||
if r.config.IP4.IcmpInRate == "0" {
|
||||
return r.inputICMPAfter(batchInput)
|
||||
}
|
||||
|
||||
if err := batchInput.AddRule("iifname != \"lo\" ip protocol icmp icmp type echo-request limit rate " + r.config.IP4.IcmpInRate + " counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.inputICMPAfter(batchInput)
|
||||
}
|
||||
|
||||
func (r *reload) inputICMPAfter(batchInput chain.Chain) error {
|
||||
if r.config.IP4.IcmpTimestampDrop == true {
|
||||
drop := r.config.Policy.InputDrop.String()
|
||||
if err := batchInput.AddRule("iifname != \"lo\" ip protocol icmp icmp type timestamp-request " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := batchInput.AddRule("iifname != \"lo\" ip protocol icmp counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.config.IP6.Enable {
|
||||
if r.config.IP6.IcmpStrict {
|
||||
return r.inputICMP6Strict(batchInput)
|
||||
} else if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) inputICMP6Strict(batchInput chain.Chain) error {
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type destination-unreachable counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type packet-too-big counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type time-exceeded counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type parameter-problem counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type echo-request counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type echo-reply counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-router-advert ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-neighbor-solicit ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-neighbor-advert ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchInput.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-redirect ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) inputPorts(batchInput chain.Chain) error {
|
||||
for _, port := range r.config.InPorts {
|
||||
protocol := port.Port.ProtocolString()
|
||||
number := port.Port.NumberString()
|
||||
|
||||
baseRule := "iifname != \"lo\" meta l4proto " + protocol + " ct state new " + protocol + " dport " + number
|
||||
|
||||
if port.LimitRate != "" {
|
||||
addRule := baseRule + " limit rate " + port.LimitRate + " counter " + port.Action.String()
|
||||
if err := batchInput.AddRule(addRule); err != nil {
|
||||
return err
|
||||
}
|
||||
addRuleDrop := baseRule + " counter " + r.config.Policy.InputDrop.String()
|
||||
if err := batchInput.AddRule(addRuleDrop); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
addRule := baseRule + " counter " + port.Action.String()
|
||||
if err := batchInput.AddRule(addRule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/rule"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg"
|
||||
)
|
||||
|
||||
func (r *reload) output(builder nft.BatchBuilder, packetfilter chain.PacketFilter) error {
|
||||
r.logger.Debug("Reloading output chain")
|
||||
|
||||
batchOutput, err := chain.NewBatchOutput(
|
||||
builder,
|
||||
r.table.family,
|
||||
r.table.name,
|
||||
r.config.MetadataNaming.ChainOutputName,
|
||||
r.config.Policy.DefaultAllowOutput,
|
||||
r.config.Policy.OutputPriority,
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.outputDnsNs(batchOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.outputDns(batchOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchOutput.AddRule("oifname lo counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
localOutput, err := chain.NewBatchChain(builder, r.table.family, r.table.name, "local-output")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := r.outputAddIPs(batchOutput, localOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := packetfilter.AddRuleOut(batchOutput.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.outputICMP(batchOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ct state related,established counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.outputPorts(batchOutput); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if r.config.Policy.DefaultAllowOutput == false {
|
||||
drop := r.config.Policy.OutputDrop.String()
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) outputDnsNs(batchOutput chain.Chain) error {
|
||||
if r.config.Options.DnsStrictNs {
|
||||
return nil
|
||||
}
|
||||
|
||||
addresses, err := pkg.Resolv.Addresses()
|
||||
if err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to get nameservers: %s", err))
|
||||
return nil
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
continue
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
if err := batchOutput.AddRule("ip daddr " + addr + " oifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchOutput.AddRule("ip daddr " + addr + " oifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchOutput.AddRule("ip daddr " + addr + " oifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchOutput.AddRule("ip daddr " + addr + " oifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.To16() != nil {
|
||||
if err := batchOutput.AddRule("ip6 daddr " + addr + " oifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchOutput.AddRule("ip6 daddr " + addr + " oifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchOutput.AddRule("ip6 daddr " + addr + " oifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := batchOutput.AddRule("ip6 daddr " + addr + " oifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
r.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
r.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) outputDns(batchOutput chain.Chain) error {
|
||||
if r.config.Options.DnsStrict {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) outputAddIPs(batchOutput chain.Chain, localOutput chain.Chain) error {
|
||||
if err := localOutput.AddRuleOut(batchOutput.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range r.config.IP4.OutIPs {
|
||||
if err := rule.OutputAddIP(localOutput.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !r.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range r.config.IP6.OutIPs {
|
||||
if err := rule.OutputAddIP(localOutput.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) outputICMP(batchOutput chain.Chain) error {
|
||||
drop := r.config.Policy.OutputDrop.String()
|
||||
if r.config.IP4.IcmpOut == false {
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
return r.outputICMPAfter(batchOutput)
|
||||
}
|
||||
|
||||
if r.config.IP4.IcmpOutRate == "0" {
|
||||
return r.outputICMPAfter(batchOutput)
|
||||
}
|
||||
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp icmp type echo-request limit rate " + r.config.IP4.IcmpInRate + " counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return r.outputICMPAfter(batchOutput)
|
||||
}
|
||||
|
||||
func (r *reload) outputICMPAfter(batchOutput chain.Chain) error {
|
||||
if r.config.IP4.IcmpTimestampDrop == true {
|
||||
drop := r.config.Policy.OutputDrop.String()
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp icmp type timestamp-reply " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := batchOutput.AddRule("oifname != \"lo\" ip protocol icmp counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) outputPorts(batchOutput chain.Chain) error {
|
||||
for _, port := range r.config.OutPorts {
|
||||
protocol := port.Port.ProtocolString()
|
||||
number := port.Port.NumberString()
|
||||
baseRule := "oifname != \"lo\" meta l4proto " + protocol + " ct state new " + protocol + " dport " + number
|
||||
|
||||
if port.LimitRate != "" {
|
||||
addRule := baseRule + " limit rate " + port.LimitRate + " counter " + port.Action.String()
|
||||
if err := batchOutput.AddRule(addRule); err != nil {
|
||||
return err
|
||||
}
|
||||
addRuleDrop := baseRule + " counter " + r.config.Policy.InputDrop.String()
|
||||
if err := batchOutput.AddRule(addRuleDrop); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
addRule := baseRule + " counter " + port.Action.String()
|
||||
if err := batchOutput.AddRule(addRule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/chain"
|
||||
)
|
||||
|
||||
func (r *reload) packetFilter(builder nft.BatchBuilder) (chain.PacketFilter, error) {
|
||||
if !r.config.Options.PacketFilter {
|
||||
return chain.NewPacketFilterFalse(), nil
|
||||
}
|
||||
|
||||
chainInvalidName := "INVALID"
|
||||
|
||||
chainName := "INVDROP"
|
||||
|
||||
if err := r.addChain(builder, chainName, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.addRule(builder, chainName, "counter drop"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addChain(builder, chainInvalidName, nftChain.TypeNone); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "ct state invalid counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags ! fin,syn,rst,psh,ack,urg counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (fin | syn | rst | psh | ack | urg) == fin | syn | rst | psh | ack | urg counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (fin | syn) == fin | syn counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (syn | rst) == syn | rst counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (fin | rst) == fin | rst counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (fin | ack) == fin counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (psh | ack) == psh counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (ack | urg) == urg counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.addRule(builder, chainInvalidName, "tcp flags & (fin | syn | rst | ack) != syn ct state new counter jump INVDROP"); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return chain.NewPacketFilter(chainInvalidName), nil
|
||||
}
|
||||
@@ -0,0 +1,215 @@
|
||||
package reload
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
nftChain "git.kor-elf.net/kor-elf-shield/go-nftables-client/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/go-nftables-client/family"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor/firewall"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/config"
|
||||
nftFirewall "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/block"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/chain"
|
||||
dataTable "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/nft/table"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
|
||||
|
||||
nft "git.kor-elf.net/kor-elf-shield/go-nftables-client/contract"
|
||||
)
|
||||
|
||||
const blockedIP = "block_ip"
|
||||
const blockedIPWithPort = "block_ip_with_port"
|
||||
|
||||
type Reload interface {
|
||||
RunWithCache(file string, isValidCacheFile bool, blockListNames []string) (dataTable.Table, error)
|
||||
Run(blockListNames []string) (dataTable.Table, error)
|
||||
}
|
||||
|
||||
type table struct {
|
||||
name string
|
||||
family family.Type
|
||||
}
|
||||
|
||||
type reload struct {
|
||||
nft nftFirewall.NFT
|
||||
logger log.Logger
|
||||
config *config.Config
|
||||
table *table
|
||||
}
|
||||
|
||||
func New(nft nftFirewall.NFT, logger log.Logger, config *config.Config) Reload {
|
||||
return &reload{
|
||||
nft: nft,
|
||||
logger: logger,
|
||||
config: config,
|
||||
table: &table{
|
||||
name: config.MetadataNaming.TableName,
|
||||
family: family.INET,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func (r *reload) RunWithCache(file string, isValidCacheFile bool, blockListNames []string) (dataTable.Table, error) {
|
||||
if file == "" {
|
||||
r.logger.Warn("file is empty, using default reload")
|
||||
return r.Run(blockListNames)
|
||||
}
|
||||
if isValidCacheFile {
|
||||
r.logger.Debug("use cache file")
|
||||
table, err := r.loadCache(file, blockListNames)
|
||||
if err == nil {
|
||||
return table, nil
|
||||
}
|
||||
r.logger.Warn(fmt.Sprintf("failed to load cache file: %s", err))
|
||||
}
|
||||
|
||||
batchBuilder, err := r.nft.NewBuildBatch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
r.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
table, err := r.reload(batchBuilder, blockListNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.nft.RunBatchAndMoveFile(batchBuilder, file); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func (r *reload) Run(blockListNames []string) (dataTable.Table, error) {
|
||||
batchBuilder, err := r.nft.NewBuildBatch()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer func() {
|
||||
if err := batchBuilder.Close(); err != nil {
|
||||
r.logger.Warn(err.Error())
|
||||
}
|
||||
}()
|
||||
|
||||
table, err := r.reload(batchBuilder, blockListNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.nft.RunBatch(batchBuilder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return table, nil
|
||||
}
|
||||
|
||||
func (r *reload) reload(batchBuilder nft.BatchBuilder, blockListNames []string) (dataTable.Table, error) {
|
||||
var dockerChains firewall.NFTDockerChains
|
||||
if r.config.Options.DockerSupport {
|
||||
dockerChains = firewall.NewNFTChains(r.nft, r.table.family, r.table.name)
|
||||
}
|
||||
|
||||
if err := r.clear(batchBuilder); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
packetFilter, err := r.packetFilter(batchBuilder)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
blockList, err := r.input(batchBuilder, packetFilter, blockListNames)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.output(batchBuilder, packetFilter); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := r.forward(batchBuilder, dockerChains); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dataTable.New(
|
||||
r.nft, r.table.family, r.table.name,
|
||||
blockList, dockerChains,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (r *reload) clear(builder nft.BatchBuilder) error {
|
||||
switch r.config.Options.ClearMode {
|
||||
case config.ClearModeGlobal:
|
||||
if err := builder.Clear(); err != nil {
|
||||
return fmt.Errorf("failed to clear global rules: %w", err)
|
||||
}
|
||||
if err := builder.Table().Add(r.table.family, r.table.name); err != nil {
|
||||
return fmt.Errorf("failed to add table: %w", err)
|
||||
}
|
||||
break
|
||||
case config.ClearModeOwn:
|
||||
if err := builder.Table().Add(r.table.family, r.table.name); err != nil {
|
||||
return fmt.Errorf("failed to add table: %w", err)
|
||||
}
|
||||
// clear does not clean completely
|
||||
if err := builder.Table().Delete(r.table.family, r.table.name); err != nil {
|
||||
return fmt.Errorf("failed to clear table: %w", err)
|
||||
}
|
||||
if err := builder.Table().Add(r.table.family, r.table.name); err != nil {
|
||||
return fmt.Errorf("failed to add table: %w", err)
|
||||
}
|
||||
break
|
||||
default:
|
||||
return fmt.Errorf("unknown clear mode: %d", r.config.Options.ClearMode)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *reload) loadCache(file string, blockListNames []string) (dataTable.Table, error) {
|
||||
args := []string{"-f", file}
|
||||
if err := r.nft.NFT().Command().Run(args...); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var dockerChains firewall.NFTDockerChains
|
||||
if r.config.Options.DockerSupport {
|
||||
dockerChains = firewall.NewNFTChains(r.nft, r.table.family, r.table.name)
|
||||
}
|
||||
|
||||
blocks := make(map[string]block.Blocklist)
|
||||
for _, blockListName := range blockListNames {
|
||||
if blockListName == "" {
|
||||
continue
|
||||
}
|
||||
blockList := block.NewBlocklistWithoutCommand(r.nft, r.table.family, r.table.name, getBlocklistName(blockListName))
|
||||
blocks[blockListName] = blockList
|
||||
}
|
||||
|
||||
listBlockedIP := block.NewListIPWithoutCommand(r.nft, r.table.family, r.table.name, blockedIP)
|
||||
listBlockedIPWithPort := block.NewListIPWithPortWithoutCommand(r.nft, r.table.family, r.table.name, blockedIPWithPort)
|
||||
|
||||
tableBlocklist := dataTable.NewBlockList(listBlockedIP, listBlockedIPWithPort, blocks)
|
||||
return dataTable.New(
|
||||
r.nft, r.table.family, r.table.name,
|
||||
tableBlocklist, dockerChains,
|
||||
), nil
|
||||
}
|
||||
|
||||
func (r *reload) addChain(builder nft.BatchBuilder, chainName string, baseChain nftChain.ChainOptions) error {
|
||||
return builder.Chain().Add(r.table.family, r.table.name, chainName, baseChain)
|
||||
}
|
||||
|
||||
func (r *reload) addRule(builder nft.BatchBuilder, chainName string, rule string) error {
|
||||
return builder.Rule().Add(r.table.family, r.table.name, chainName, rule)
|
||||
}
|
||||
|
||||
func (r *reload) addChainWithReturn(builder nft.BatchBuilder, chainName string, baseChain nftChain.ChainOptions) (chain.Chain, error) {
|
||||
return chain.NewBatchChainWithOptions(builder, r.table.family, r.table.name, chainName, baseChain)
|
||||
}
|
||||
|
||||
func getBlocklistName(blockListName string) string {
|
||||
return "blocklist_" + blockListName
|
||||
}
|
||||
@@ -1,25 +0,0 @@
|
||||
package firewall
|
||||
|
||||
func (f *firewall) reloadBlockList() error {
|
||||
listBlockedIP, err := f.chains.NewBlockListIP("blocked_ip")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := listBlockedIP.AddRuleToChain(f.chains.BeforeLocalInput().AddRule, "drop"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
listBlockedIPWithPort, err := f.chains.NewBlockListIPWithPort("blocked_ip_port")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if err := listBlockedIPWithPort.AddRuleToChain(f.chains.BeforeLocalInput().AddRule, "drop"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.blockingService.NftReload(listBlockedIP, listBlockedIPWithPort); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
|
||||
|
||||
func (f *firewall) reloadForward() error {
|
||||
f.logger.Debug("Reloading forward chain")
|
||||
err := f.chains.NewForward(f.config.MetadataNaming.ChainForwardName, f.config.Policy.DefaultAllowForward, f.config.Policy.ForwardPriority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.Forward()
|
||||
|
||||
if err := f.reloadForwardAddIPs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.config.Options.DockerSupport {
|
||||
if err := f.docker.NftChains().ForwardFilterJump(chain.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if f.config.Policy.DefaultAllowForward == false {
|
||||
drop := f.config.Policy.ForwardDrop.String()
|
||||
if err := chain.AddRule(drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadForwardAddIPs() error {
|
||||
if err := f.chains.NewLocalForward(); err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.LocalForward()
|
||||
if err := chain.AddRuleIn(f.chains.Forward().AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP4.InIPs {
|
||||
if ipConfig.Action != types.ActionDrop && ipConfig.Action != types.ActionReject {
|
||||
continue
|
||||
}
|
||||
if err := forwardAddIP(chain.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP6.InIPs {
|
||||
if ipConfig.Action != types.ActionDrop && ipConfig.Action != types.ActionReject {
|
||||
continue
|
||||
}
|
||||
if err := forwardAddIP(chain.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func forwardAddIP(addRuleFunc func(expr ...string) error, config ConfigIP, ipMatch string) error {
|
||||
rule := ipMatch + " saddr " + config.IP + " iifname != \"lo\""
|
||||
|
||||
// There, during routing, the port changes and then the IP blocking rule will not work.
|
||||
//if !config.OnlyIP {
|
||||
// rule += " " + config.Protocol.String() + " dport " + strconv.Itoa(int(config.Port))
|
||||
//}
|
||||
|
||||
rule += " counter " + config.Action.String()
|
||||
if err := addRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,331 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/chain"
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg"
|
||||
)
|
||||
|
||||
func (f *firewall) reloadInput() error {
|
||||
f.logger.Debug("Reloading input chain")
|
||||
err := f.chains.NewInput(f.config.MetadataNaming.ChainInputName, f.config.Policy.DefaultAllowInput, f.config.Policy.InputPriority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.Input()
|
||||
|
||||
if err := f.reloadInputDnsNs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chain.AddRule("iifname lo counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.chains.NewBeforeLocalInput(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.chains.BeforeLocalInput().AddRuleIn(chain.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadInputAddIPs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.chains.NewAfterLocalInput(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.chains.AfterLocalInput().AddRuleIn(chain.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.chains.PacketFilter().AddRuleIn(chain.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadInputICMP(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chain.AddRule("iifname != \"lo\" ct state related,established counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadInputPorts(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.config.Policy.DefaultAllowInput == false {
|
||||
drop := f.config.Policy.InputDrop.String()
|
||||
if err := chain.AddRule("iifname != \"lo\" " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadInputDnsNs() error {
|
||||
if f.config.Options.DnsStrictNs {
|
||||
return nil
|
||||
}
|
||||
chain := f.chains.Input()
|
||||
|
||||
addresses, err := pkg.Resolv.Addresses()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to get nameservers: %s", err))
|
||||
return nil
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
continue
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
if err := chain.AddRule("ip saddr " + addr + " iifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip saddr " + addr + " iifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip saddr " + addr + " iifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip saddr " + addr + " iifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.To16() != nil {
|
||||
if !f.config.IP6.Enable {
|
||||
f.logger.Warn(fmt.Sprintf("IPv6 is disabled, skipping nameserver address: %s", addr))
|
||||
continue
|
||||
}
|
||||
if err := chain.AddRule("ip6 saddr " + addr + " iifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip6 saddr " + addr + " iifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip6 saddr " + addr + " iifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip6 saddr " + addr + " iifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
f.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadInputICMP() error {
|
||||
chain := f.chains.Input()
|
||||
drop := f.config.Policy.InputDrop.String()
|
||||
if f.config.IP4.IcmpIn == false {
|
||||
if err := chain.AddRule("iifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.reloadInputICMPAfter()
|
||||
}
|
||||
|
||||
if f.config.IP4.IcmpInRate == "0" {
|
||||
return f.reloadInputICMPAfter()
|
||||
}
|
||||
|
||||
if err := chain.AddRule("iifname != \"lo\" ip protocol icmp icmp type echo-request limit rate " + f.config.IP4.IcmpInRate + " counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.reloadInputICMPAfter()
|
||||
}
|
||||
func (f *firewall) reloadInputICMPAfter() error {
|
||||
chain := f.chains.Input()
|
||||
|
||||
if f.config.IP4.IcmpTimestampDrop == true {
|
||||
drop := f.config.Policy.InputDrop.String()
|
||||
if err := chain.AddRule("iifname != \"lo\" ip protocol icmp icmp type timestamp-request " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := chain.AddRule("iifname != \"lo\" ip protocol icmp counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.config.IP6.Enable {
|
||||
if f.config.IP6.IcmpStrict {
|
||||
return f.reloadInputICMP6Strict()
|
||||
} else {
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadInputICMP6Strict() error {
|
||||
chain := f.chains.Input()
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type destination-unreachable counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type packet-too-big counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type time-exceeded counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type parameter-problem counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type echo-request counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type echo-reply counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-router-advert ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-neighbor-solicit ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-neighbor-advert ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("iifname != \"lo\" meta l4proto ipv6-icmp icmpv6 type nd-redirect ip6 hoplimit 255 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadInputPorts() error {
|
||||
chain := f.chains.Input()
|
||||
for _, port := range f.config.InPorts {
|
||||
protocol := port.Port.ProtocolString()
|
||||
number := port.Port.NumberString()
|
||||
|
||||
baseRule := "iifname != \"lo\" meta l4proto " + protocol + " ct state new " + protocol + " dport " + number
|
||||
|
||||
if port.LimitRate != "" {
|
||||
rule := baseRule + " limit rate " + port.LimitRate + " counter " + port.Action.String()
|
||||
if err := chain.AddRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
ruleDrop := baseRule + " counter " + f.config.Policy.InputDrop.String()
|
||||
if err := chain.AddRule(ruleDrop); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
rule := baseRule + " counter " + port.Action.String()
|
||||
if err := chain.AddRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadInputAddIPs() error {
|
||||
if err := f.chains.NewLocalInput(); err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.LocalInput()
|
||||
if err := chain.AddRuleIn(f.chains.Input().AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadPortKnocking(chain); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP4.InIPs {
|
||||
if err := inputAddIP(chain.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP6.InIPs {
|
||||
if err := inputAddIP(chain.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadPortKnocking(chain chain.LocalInput) error {
|
||||
if len(f.config.PortKnocking) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
portKnocking, err := f.chains.NewPortKnocking("port_knocking")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, portKnockingConfig := range f.config.PortKnocking {
|
||||
var knockName, prevKnockName string
|
||||
for index, knock := range portKnockingConfig.Knocks {
|
||||
prevKnockName = knockName
|
||||
knockName = fmt.Sprintf("knock_%s_%d", portKnockingConfig.Name, index)
|
||||
if index == 0 {
|
||||
if err := portKnocking.AddFirstStageRule(knockName, portKnockingConfig.IPVersion, knock.Port, knock.Timeout, knock.Action); err != nil {
|
||||
return err
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if err := portKnocking.AddNextStageRule(prevKnockName, knockName, portKnockingConfig.IPVersion, knock.Port, knock.Timeout, knock.Action); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
expr := []string{
|
||||
portKnockingConfig.IPVersion.ToNft(), "saddr", "@" + knockName,
|
||||
portKnockingConfig.Port.ProtocolString(), "dport", portKnockingConfig.Port.NumberString(), "accept",
|
||||
}
|
||||
if err := chain.AddRule(expr...); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := portKnocking.AddRuleIn(chain.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func inputAddIP(addRuleFunc func(expr ...string) error, config ConfigIP, ipMatch string) error {
|
||||
|
||||
rule := ipMatch + " saddr " + config.IP + " iifname != \"lo\""
|
||||
if !config.OnlyIP {
|
||||
rule += " " + config.Port.ProtocolString() + " dport " + config.Port.NumberString()
|
||||
}
|
||||
if config.LimitRate != "" {
|
||||
rule += " limit rate " + config.LimitRate
|
||||
}
|
||||
rule += " counter " + config.Action.String()
|
||||
if err := addRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -1,244 +0,0 @@
|
||||
package firewall
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net"
|
||||
|
||||
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg"
|
||||
)
|
||||
|
||||
func (f *firewall) reloadOutput() error {
|
||||
f.logger.Debug("Reloading output chain")
|
||||
err := f.chains.NewOutput(f.config.MetadataNaming.ChainOutputName, f.config.Policy.DefaultAllowOutput, f.config.Policy.OutputPriority)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.Output()
|
||||
|
||||
if err := f.reloadOutputDnsNs(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := f.reloadOutputDns(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("oifname lo counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadOutputAddIPs(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.chains.PacketFilter().AddRuleOut(chain.AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadOutputICMP(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := chain.AddRule("oifname != \"lo\" ct state related,established counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := f.reloadOutputPorts(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if f.config.Policy.DefaultAllowOutput == false {
|
||||
drop := f.config.Policy.OutputDrop.String()
|
||||
if err := chain.AddRule("oifname != \"lo\" " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadOutputDns() error {
|
||||
if f.config.Options.DnsStrict {
|
||||
return nil
|
||||
}
|
||||
chain := f.chains.Output()
|
||||
|
||||
if err := chain.AddRule("oifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("oifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("oifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("oifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadOutputDnsNs() error {
|
||||
if f.config.Options.DnsStrictNs {
|
||||
return nil
|
||||
}
|
||||
chain := f.chains.Output()
|
||||
|
||||
addresses, err := pkg.Resolv.Addresses()
|
||||
if err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to get nameservers: %s", err))
|
||||
return nil
|
||||
}
|
||||
for _, addr := range addresses {
|
||||
ip := net.ParseIP(addr)
|
||||
if ip == nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
continue
|
||||
}
|
||||
if ip.To4() != nil {
|
||||
if err := chain.AddRule("ip daddr " + addr + " oifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip daddr " + addr + " oifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip daddr " + addr + " oifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip daddr " + addr + " oifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
if ip.To16() != nil {
|
||||
if err := chain.AddRule("ip6 daddr " + addr + " oifname != \"lo\" tcp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip6 daddr " + addr + " oifname != \"lo\" udp dport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip6 daddr " + addr + " oifname != \"lo\" tcp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
if err := chain.AddRule("ip6 daddr " + addr + " oifname != \"lo\" udp sport 53 counter accept"); err != nil {
|
||||
f.logger.Error(fmt.Sprintf("Failed to add rule: %s", err))
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
f.logger.Error(fmt.Sprintf("Failed to parse nameserver address: %s", addr))
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadOutputICMP() error {
|
||||
chain := f.chains.Output()
|
||||
drop := f.config.Policy.OutputDrop.String()
|
||||
if f.config.IP4.IcmpOut == false {
|
||||
if err := chain.AddRule("oifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
return f.reloadOutputICMPAfter()
|
||||
}
|
||||
|
||||
if f.config.IP4.IcmpOutRate == "0" {
|
||||
return f.reloadOutputICMPAfter()
|
||||
}
|
||||
|
||||
if err := chain.AddRule("oifname != \"lo\" ip protocol icmp icmp type echo-request limit rate " + f.config.IP4.IcmpInRate + " counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := chain.AddRule("oifname != \"lo\" ip protocol icmp icmp type echo-request counter " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return f.reloadOutputICMPAfter()
|
||||
}
|
||||
|
||||
func (f *firewall) reloadOutputICMPAfter() error {
|
||||
chain := f.chains.Output()
|
||||
|
||||
if f.config.IP4.IcmpTimestampDrop == true {
|
||||
drop := f.config.Policy.OutputDrop.String()
|
||||
if err := chain.AddRule("oifname != \"lo\" ip protocol icmp icmp type timestamp-request " + drop); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if err := chain.AddRule("oifname != \"lo\" ip protocol icmp counter accept"); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadOutputPorts() error {
|
||||
chain := f.chains.Output()
|
||||
for _, port := range f.config.OutPorts {
|
||||
protocol := port.Port.ProtocolString()
|
||||
number := port.Port.NumberString()
|
||||
baseRule := "oifname != \"lo\" meta l4proto " + protocol + " ct state new " + protocol + " dport " + number
|
||||
|
||||
if port.LimitRate != "" {
|
||||
rule := baseRule + " limit rate " + port.LimitRate + " counter " + port.Action.String()
|
||||
if err := chain.AddRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
ruleDrop := baseRule + " counter " + f.config.Policy.InputDrop.String()
|
||||
if err := chain.AddRule(ruleDrop); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
rule := baseRule + " counter " + port.Action.String()
|
||||
if err := chain.AddRule(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *firewall) reloadOutputAddIPs() error {
|
||||
if err := f.chains.NewLocalOutput(); err != nil {
|
||||
return err
|
||||
}
|
||||
chain := f.chains.LocalOutput()
|
||||
if err := chain.AddRuleOut(f.chains.Output().AddRule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP4.OutIPs {
|
||||
if err := outputAddIP(chain.AddRule, ipConfig, "ip"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.config.IP6.Enable {
|
||||
return nil
|
||||
}
|
||||
|
||||
for _, ipConfig := range f.config.IP6.OutIPs {
|
||||
if err := outputAddIP(chain.AddRule, ipConfig, "ip6"); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func outputAddIP(addRuleFunc func(expr ...string) error, config ConfigIP, ipMatch string) error {
|
||||
|
||||
rule := ipMatch + " daddr " + config.IP + " oifname != \"lo\""
|
||||
if !config.OnlyIP {
|
||||
rule += " " + config.Port.ProtocolString() + " dport " + config.Port.NumberString()
|
||||
}
|
||||
if config.LimitRate != "" {
|
||||
rule += " limit rate " + config.LimitRate
|
||||
}
|
||||
rule += " counter " + config.Action.String()
|
||||
if err := addRuleFunc(rule); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func NewMetadataFirewallFileNft(repo repository.MetadataRepository) Metadata {
|
||||
return NewMetadata(repo, entity.MetadataKeyFirewallFileNft)
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type Info interface {
|
||||
// Version returns the version of the daemon.
|
||||
Version() string
|
||||
|
||||
// IsVersionChanged returns true if the version of the daemon has changed.
|
||||
IsVersionChanged() bool
|
||||
|
||||
// IsSettingsChanged returns true if the settings of the daemon has changed.
|
||||
IsSettingsChanged() bool
|
||||
|
||||
// BuiltWith returns the build information of the daemon.
|
||||
BuiltWith() string
|
||||
// StartTime returns the start time of the daemon.
|
||||
StartTime() time.Time
|
||||
// Uptime returns the uptime of the daemon.
|
||||
Uptime() time.Duration
|
||||
|
||||
// Metadata returns the metadata of the daemon.
|
||||
Metadata() MetadataContainer
|
||||
}
|
||||
|
||||
type info struct {
|
||||
version string
|
||||
isVersionChanged bool
|
||||
|
||||
isSettingsChanged bool
|
||||
|
||||
builtWith string
|
||||
startTime time.Time
|
||||
|
||||
metadata MetadataContainer
|
||||
}
|
||||
|
||||
func New(
|
||||
version string,
|
||||
isVersionChanged bool,
|
||||
|
||||
builtWith string,
|
||||
startTime time.Time,
|
||||
|
||||
isSettingsChanged bool,
|
||||
|
||||
metadata MetadataContainer,
|
||||
) Info {
|
||||
return &info{
|
||||
version: version,
|
||||
isVersionChanged: isVersionChanged,
|
||||
|
||||
isSettingsChanged: isSettingsChanged,
|
||||
|
||||
builtWith: builtWith,
|
||||
startTime: startTime,
|
||||
metadata: metadata,
|
||||
}
|
||||
}
|
||||
|
||||
func (i *info) Version() string {
|
||||
return i.version
|
||||
}
|
||||
|
||||
func (i *info) IsVersionChanged() bool {
|
||||
return i.isVersionChanged
|
||||
}
|
||||
|
||||
func (i *info) IsSettingsChanged() bool {
|
||||
return i.isSettingsChanged
|
||||
}
|
||||
|
||||
func (i *info) BuiltWith() string {
|
||||
return i.builtWith
|
||||
}
|
||||
|
||||
func (i *info) StartTime() time.Time {
|
||||
return i.startTime
|
||||
}
|
||||
|
||||
func (i *info) Uptime() time.Duration {
|
||||
return time.Since(i.StartTime())
|
||||
}
|
||||
|
||||
func (i *info) Metadata() MetadataContainer {
|
||||
return i.metadata
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
type MetadataContainer interface {
|
||||
FirewallFileNft() Metadata
|
||||
}
|
||||
|
||||
type Metadata interface {
|
||||
Get() (string, error)
|
||||
Update(data string) error
|
||||
}
|
||||
|
||||
func NewMetadataContainer(
|
||||
firewallFileNft Metadata,
|
||||
) MetadataContainer {
|
||||
return &metadataContainer{
|
||||
firewallFileNft: firewallFileNft,
|
||||
}
|
||||
}
|
||||
|
||||
func NewMetadata(repo repository.MetadataRepository, key string) Metadata {
|
||||
return &metadata{
|
||||
key: key,
|
||||
repo: repo,
|
||||
}
|
||||
}
|
||||
|
||||
type metadataContainer struct {
|
||||
firewallFileNft Metadata
|
||||
}
|
||||
|
||||
func (m *metadataContainer) FirewallFileNft() Metadata {
|
||||
return m.firewallFileNft
|
||||
}
|
||||
|
||||
type metadata struct {
|
||||
key string
|
||||
repo repository.MetadataRepository
|
||||
}
|
||||
|
||||
func (m *metadata) Get() (string, error) {
|
||||
meta, err := m.repo.Get(m.key)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return meta.Value, nil
|
||||
}
|
||||
|
||||
func (m *metadata) Update(value string) error {
|
||||
meta := &entity.Metadata{Value: value}
|
||||
return m.repo.Update(m.key, meta)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"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"
|
||||
)
|
||||
|
||||
func IsSettingsChanged(repo repository.MetadataRepository, listPathFiles map[string]string, logger log.Logger) bool {
|
||||
isChanged := false
|
||||
|
||||
for k, f := range listPathFiles {
|
||||
hash, err := fileHash(f)
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to get hash of %s: %s", f, err))
|
||||
continue
|
||||
}
|
||||
|
||||
settingMetadata := NewMetadata(repo, entity.KeySetting(k))
|
||||
metadataValue, err := settingMetadata.Get()
|
||||
if err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to get metadata value: %s", err))
|
||||
}
|
||||
|
||||
if metadataValue != hash {
|
||||
isChanged = true
|
||||
if err := settingMetadata.Update(hash); err != nil {
|
||||
logger.Error(fmt.Sprintf("Failed to update metadata: %s", err))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return isChanged
|
||||
}
|
||||
|
||||
func fileHash(path string) (string, error) {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
size := info.Size()
|
||||
modTime := info.ModTime()
|
||||
|
||||
return fmt.Sprintf("%d-%d", size, modTime.Unix()), nil
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package info
|
||||
|
||||
import (
|
||||
"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"
|
||||
)
|
||||
|
||||
func IsVersionChanged(repo repository.MetadataRepository, version string, logger log.Logger) bool {
|
||||
metadata := NewMetadata(repo, entity.MetadataKeyVersion)
|
||||
if v, err := metadata.Get(); err != nil {
|
||||
logger.Error(err.Error())
|
||||
return false
|
||||
} else if v != version {
|
||||
if err := metadata.Update(version); err != nil {
|
||||
logger.Error(err.Error())
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user