87 Commits
Author SHA1 Message Date
kor-elf b3e44359d1 Merge pull request 'v0.14.0' (#14) from develop into main
Reviewed-on: #14
2026-09-05 18:24:42 +05:00
kor-elf b782b7c975 Update: set release date for version 0.14.0 in changelog 2026-09-05 17:51:14 +05:00
kor-elf e28fafc1e9 Add: configuration testing command kor-elf-shield config test 2026-09-04 21:30:37 +05:00
kor-elf 350c78c40a Add: docker program existence check in config command 2026-09-04 21:26:36 +05:00
kor-elf 22d7f901d0 Add: journalctl program existence check in config command 2026-09-04 21:23:06 +05:00
kor-elf 1cff4fb2ba Update: improve localization for empty program path messages in config command 2026-09-04 21:23:00 +05:00
kor-elf 2d219edd23 Add: nftables program existence check in config command 2026-09-04 21:15:20 +05:00
kor-elf 1cc52ca28e Add: GeoIP configuration testing in config command 2026-09-04 21:04:38 +05:00
kor-elf 7392e753ea Add: Blocklists configuration testing in config command 2026-09-04 21:01:28 +05:00
kor-elf 6148b0a06d Refactor: reuse NewFalseLogger in NewLogger implementation 2026-09-04 20:59:13 +05:00
kor-elf 7322455ae0 Add: Notifications configuration testing in config command 2026-09-04 20:56:03 +05:00
kor-elf 232b63a207 Add: Analyzer configuration testing in config command 2026-09-04 20:46:32 +05:00
kor-elf 4d4e7272ce Add: Firewall configuration testing in config command 2026-09-04 20:44:05 +05:00
kor-elf 5c2b34b742 Add: Docker configuration testing in config command 2026-09-04 20:40:53 +05:00
kor-elf 5315a4e0ff Add: config command with validation testing for daemon configuration 2026-09-04 20:36:36 +05:00
kor-elf 0286ebc141 Refactor: extract validation logic into ValidateBeforeStart for reuse in ToDaemonOptions 2026-09-04 20:36:15 +05:00
kor-elf 9759d9805b Fix: correct typos in Russian comments within analyzer.toml 2026-09-04 20:08:19 +05:00
kor-elf 29df5f3fd8 Update: prepare v0.14.0 release notes in changelog 2026-09-03 19:41:03 +05:00
kor-elf f2a9dc41be Fix: correct ICMP rule from timestamp-request to timestamp-reply in firewall output logic 2026-09-03 19:32:07 +05:00
kor-elf f2dfeb507d Update: rename log path keys for clarity in kor-elf-shield configuration 2026-09-03 19:31:48 +05:00
kor-elf bae20cbf49 Merge pull request 'v0.12.0' (#13) from develop into main
Reviewed-on: #13
2026-08-16 14:33:52 +05:00
kor-elf f9c438c973 Update: finalize v0.13.0 release date in changelog 2026-08-16 14:21:10 +05:00
kor-elf cbf3dd6402 Add: IP exclusion support in blocklist handling settings 2026-08-12 18:35:10 +05:00
kor-elf 8495d7c4e2 Add: support for IP exclusion in blocklist processing configuration 2026-08-12 18:29:09 +05:00
kor-elf 311add0e8b Update: dependency versions in go.mod and go.sum for improved compatibility and security 2026-08-12 16:59:17 +05:00
kor-elf c26a898d05 Add: license information for golang.org/x/crypto to third-party license file 2026-08-12 16:58:56 +05:00
kor-elf 9d5b6c7d6b Update: extend brute force protection regex to match invalid user login patterns 2026-08-09 21:21:29 +05:00
kor-elf a0893ea361 Update: clarify SSH key authentication failure logging in release notes 2026-08-09 20:30:56 +05:00
kor-elf 0111304d85 Update: add SSH log verbosity note to analyzer configuration comments 2026-08-09 20:30:34 +05:00
kor-elf aa0c5a9551 Update: enhance regex pattern to include publickey authentication failures in brute force protection logs 2026-08-09 20:14:50 +05:00
kor-elf 65806d701a Update: include critical bug fix for failed SSH login processing in v0.13.0 release notes 2026-08-09 19:22:12 +05:00
kor-elf 8c10ab0e62 Update: refine regex pattern for brute force protection log parsing in analyzer configuration
Failed attempts were not recorded if the user was not registered in the system.
2026-08-09 19:16:02 +05:00
kor-elf 95aec171e0 Add: partitioning support for patterns in analyzer.toml configuration 2026-08-09 19:06:24 +05:00
kor-elf 666f563ef5 Add: pattern partitioning support for alert and brute force protection groups 2026-08-09 19:06:01 +05:00
kor-elf ed7bd905f8 Add: table presence check data to kor-elf-shield status command in release notes 2026-08-05 17:48:00 +05:00
kor-elf d06dd6e9fc Add: NFTables status check to daemon diagnostics output 2026-08-05 17:43:09 +05:00
kor-elf f650e89610 Update: draft v0.13.0 release notes with rulesGuard feature and configuration details 2026-08-05 17:29:28 +05:00
kor-elf 8c55ad1b23 Add: firewall rules guard implementation for monitoring and recovery 2026-08-05 17:29:13 +05:00
kor-elf 17efaaa36a Add: HasRules method to firewall and table interfaces to check for existing rules 2026-08-05 17:27:59 +05:00
kor-elf df469a6f6d Add: new configuration file descriptions with links to README_ENG.md 2026-07-05 01:33:55 +05:00
kor-elf 6de78c2708 Add: detailed descriptions of configuration files to README.md 2026-07-05 01:33:44 +05:00
kor-elf 87add28edd Add: path field definition for file-based configurations in analyzer.toml 2026-07-05 01:26:15 +05:00
kor-elf 7cdd761dcd Merge pull request 'v0.12.0' (#12) from develop into main
Reviewed-on: #12
2026-06-17 20:24:08 +05:00
kor-elf bdbbd303cc Update: remove outdated active development status note from README_ENG.md 2026-06-17 20:17:14 +05:00
kor-elf b7cfc756c1 Update: remove outdated development status note from README.md 2026-06-17 20:17:01 +05:00
kor-elf b396963976 Update: finalize v0.12.0 release notes with new notification features and release date 2026-06-17 20:12:18 +05:00
kor-elf eb4b0b4e4f Add: SSH notification cooldown and frequency limit configuration options 2026-06-17 20:00:08 +05:00
kor-elf 03a1d46f1b Fix: adjust brute force protection notification policy logic for cooldown and frequency handling 2026-06-17 19:59:20 +05:00
kor-elf eda1b956f3 Add brute force protection notification policy with cooldown and frequency management 2026-06-17 00:09:19 +05:00
kor-elf 14c4c504fe Update: add IP block count display to CHANGELOG for version 0.12.0 2026-06-15 22:40:33 +05:00
kor-elf 5acc8a72de Update: add blockIPCount to notifications and localizations with support for tracking blocked IP occurrences 2026-06-15 22:38:54 +05:00
kor-elf 1047c75629 Update: add version 0.12.0 entry to CHANGELOG with notification number display feature 2026-06-15 22:36:45 +05:00
kor-elf f93716e194 Update: Added display of the number of notifications in group notifications with localization. 2026-06-15 22:28:34 +05:00
kor-elf 72a0f941a1 Merge pull request 'v0.11.0' (#11) from develop into main
Reviewed-on: #11
2026-05-07 17:03:00 +05:00
kor-elf e9cd163ae7 Update: set release date for version 0.11.0 in CHANGELOG 2026-05-07 16:31:49 +05:00
kor-elf f778e25575 Update: improve notification blocking time display in CHANGELOG 2026-05-05 20:42:40 +05:00
kor-elf 21ab1802fe Update: improve blockSec message formatting with human-readable duration and adjust locale translations 2026-05-05 20:41:08 +05:00
kor-elf 62517e6f4c Update: add options.cache parameter to firewall.toml for nftables caching and improve CHANGELOG formatting 2026-05-05 20:31:20 +05:00
kor-elf 8a7608dac6 Refactor: integrate info package to manage daemon metadata and versioning, streamline dependencies, and enhance firewall reload logic 2026-05-05 20:28:51 +05:00
kor-elf 8d90a3770d Update: add cache support to firewall settings for improved nftables command handling 2026-05-05 20:27:25 +05:00
kor-elf 4acb81b5a7 Update: extend settings functionality with path-based configuration and file listing 2026-05-05 20:26:41 +05:00
kor-elf 49ab9c48c7 Refactor: remove unused Metadata implementation from firewall package 2026-05-05 20:23:08 +05:00
kor-elf 28019ec171 Update: introduce info package to manage daemon metadata, versioning, settings tracking, and uptime calculation 2026-05-05 20:22:49 +05:00
kor-elf 03b4009f96 Update: add constants for metadata keys and refactor Metadata struct 2026-05-05 20:19:02 +05:00
kor-elf 71502ff0c9 Refactor: ensure file handle is properly closed in checksum calculation logic 2026-05-05 20:17:13 +05:00
kor-elf ab59b356dc Update: add metadata management and caching support for nftables reload logic 2026-05-04 23:50:13 +05:00
kor-elf 8595c6791d Update: rework IP blocklist update logic and improve Uptime output in kor-elf-shield status 2026-05-04 20:38:51 +05:00
kor-elf c64c94dceb Refactor: use HumanDuration for uptime formatting and add utility to format durations elegantly 2026-05-04 20:38:35 +05:00
kor-elf a387e85569 Refactor: enhance blocklist management with file-based element replacements, checksum validation, and modular NFTables integration 2026-05-04 20:38:12 +05:00
kor-elf ab8466ada2 Refactor: rename loop variable in docker rule reload for improved clarity 2026-05-04 20:37:29 +05:00
kor-elf d978343f4c Refactor: improve readability in firewall rule addition by renaming variables for clarity and simplifying conditional logic 2026-05-04 20:36:46 +05:00
kor-elf 14a6b9df0b Update: rework nftables rule addition logic to use temporary files and -f execution 2026-05-03 22:39:02 +05:00
kor-elf 298c7140a4 Refactor: remove unused chain-related implementations from the firewall package 2026-05-03 22:30:54 +05:00
kor-elf 527b6c8264 Refactor: modularize firewall initialization with data directory support and enhance nftables reload logic 2026-05-03 22:30:38 +05:00
kor-elf 0d707ac3c6 Refactor: add Clear method to NFT table and update initialization to support table attributes 2026-05-03 22:30:09 +05:00
kor-elf b535195c1f Refactor: enhance blocklist management with modular NFTables blocklist implementation, adding support for rule reloading and element replacements 2026-05-03 21:50:33 +05:00
kor-elf 3c12429f0e Refactor: simplify RunBatch usage in firewall blocking implementation 2026-05-03 17:19:15 +05:00
kor-elf 25ee39c0ec Refactor: add IP and IP-with-port block list management to firewall with batch support 2026-05-03 17:05:46 +05:00
kor-elf 95e1f274f7 Refactor: remove unused chain package from Docker monitor implementation 2026-05-02 23:25:33 +05:00
kor-elf 5f72efd1bf Refactor: remove unused chainCommand method and chain import from Docker monitor 2026-05-02 23:25:13 +05:00
kor-elf e8826cb86b Refactor: integrate NFTDocker abstraction in rule strategies and update chain handling methods 2026-05-02 23:20:59 +05:00
kor-elf f12097b280 Implemented reloading of nftables rules via batch 2026-05-02 23:19:26 +05:00
kor-elf 3c040945bc Upgrade go-nftables-client to v0.2.1 and update CHANGELOG accordingly 2026-04-27 23:46:03 +05:00
kor-elf 0fdc07c0af Fix: handle error from service initialization in daemon server setup 2026-04-27 22:38:37 +05:00
kor-elf a1345bd3e1 Refactor: move firewall-related configurations to new config package and update references 2026-04-27 22:07:02 +05:00
kor-elf d2f3640b75 Update CHANGELOG for upcoming v0.11.0 release with go-nftables-client update details 2026-04-26 17:41:08 +05:00
kor-elf 1363ff4bef Upgrade go-nftables-client to v0.2.0 and update imports to reflect new contract package structure 2026-04-26 17:36:47 +05:00
130 changed files with 4689 additions and 2627 deletions
+101 -9
View File
@@ -1,4 +1,96 @@
## 0.10.0 (12.4.2026)
## 0.14.0 (05.09.2026)
#### Русский
* Исправлена ошибка в правилах исходящего ICMP: при `icmp_timestamp_drop=true` теперь корректно блокируются `timestamp-reply` пакеты (вместо `timestamp-request`).
* Добавлена новая команда `kor-elf-shield config test`. Теперь перед перезапуском демона можно проверить корректность конфигурации.
***
#### English
* Fixed an issue in outbound ICMP rules: when `icmp_timestamp_drop=true`, `timestamp-reply` packets are now correctly blocked (instead of `timestamp-request`).
* Added a new command, `kor-elf-shield config test`. Now you can check the configuration before restarting the daemon.
***
## 0.13.0 (16.08.2026)
#### Русский
* Обновления критических ошибок:
* Исправлена ошибка, из-за которой неудачные попытки авторизации по SSH не обрабатывались, если указанного пользователя не существовало в системе.
* Исправлена ошибка, из-за которой неудачные попытки аутентификации по SSH-ключу не обрабатывались.
<br>**Примечание:** По умолчанию SSH-сервер не регистрирует неудачные попытки входа с использованием ключа. Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр `LogLevel VERBOSE` в настройки `sshd_config`.
* Добавлено: поддержка исключений IP-адресов в настройках обработки blocklist.
* Теперь можно добавлять параметр `exclude_ips` в конфигурационный файл `blocklists.toml` чтобы исключать определённые диапазоны IP-адресов из обработки. По умолчанию: `["127.0.0.1/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7",]`.
* Реализована простая проверка на наличия таблицы в NFTables.
* Добавлены данные для проверки наличия таблицы в NFTables в команде `kor-elf-shield status`.
* Добавлена новая группа параметров `[rulesGuard]` в файл `firewall.toml`:
* `enabled` — Включает мониторинг правил NFTables. По умолчанию: `true`.
* `notifications` - Включает уведомления в случае проблем с правилами NFTables. По умолчанию: `true`.
* `recovery` - Восстанавливает правила NFTables в случае проблем. По умолчанию: `true`.
* `interval` - Интервал проверки правил NFTables в секундах. По умолчанию: `3600`.
* Добавлена поддержка разделения `patterns` на партиции в настройках `analyzer.toml`.
* Теперь правила анализа логов и защиты от перебора пароля могут обрабатывать шаблоны по партициям.
* Это помогает гибче группировать шаблоны и снижать пересечения между независимыми наборами правил.
***
#### English
* Critical bug fixes:
* Fixed an issue where failed SSH login attempts were not processed if the specified user did not exist on the system.
* Fixed a bug where unsuccessful SSH key authentication attempts were not processed.
<br>**Note:** By default, the SSH server does not log unsuccessful login attempts using a key. To enable tracking of unsuccessful login attempts using a key, you must add the `LogLevel VERBOSE` parameter to the `sshd_config` settings.
* Added: Support for IP address exclusions in blocklist processing settings.
* You can now add the `exclude_ips` parameter to the `blocklists.toml` configuration file to exclude specific IP address ranges from processing. Default: `["127.0.0.1/8", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "::1/128", "fc00::/7",]`.
* Implemented a simple check for the presence of a table in NFTables.
* Added check data to `kor-elf-shield status` command for the presence of a table in NFTables.
* Added a new `[rulesGuard]` parameter group to the `firewall.toml` file:
* `enabled` - Enables NFTables rules monitoring. Default: `true`.
* `notifications` - Enables notifications in case of problems with NFTables rules. Default: `true`.
* `recovery` - Recovers NFTables rules in case of problems. Default: `true`.
* `interval` - NFTables rules checking interval in seconds. Default: `3600`.
* Added partitioning support for `patterns` in the `analyzer.toml` configuration.
* Log analysis and brute-force protection rules can now process patterns by partitions.
* This helps organize patterns more flexibly and reduces overlap between independent rule sets.
***
## 0.12.0 (17.06.2026)
#### Русский
* Добавлено: отображение номера уведомления в групповых уведомлениях.
* Добавлено: теперь в уведомлениях отображается количество блокировок IP-адреса.
* Добавлены параметры для ограничения частоты уведомлений об успешной блокировке (`analyzer.toml`):
* `ssh_notify_cooldown_seconds` — задаёт минимальный интервал в секундах между уведомлениями об успешных SSH-блокировках.
* `ssh_notify_every` — отправляет следующее уведомление об успешной SSH-блокировке только после указанного количества новых сообщений о блокировке; имеет приоритет над `ssh_notify_cooldown_seconds`.
* `notify_cooldown_seconds` — задаёт минимальный интервал в секундах между уведомлениями об успешных блокировках для пользовательских правил защиты от перебора пароля.
* `notify_every` — отправляет следующее уведомление для пользовательского правила только после указанного количества новых сообщений о блокировке; имеет приоритет над `notify_cooldown_seconds`.
***
#### English
* Added: display of notification number in group notifications.
* Added: notifications now display how many times an IP address has been blocked.
* Added options to limit the frequency of notifications about successful blocking (`analyzer.toml`):
* `ssh_notify_cooldown_seconds` — Sets the minimum interval in seconds between notifications of successful SSH locks.
* `ssh_notify_every` — Sends the next successful SSH lock notification only after the specified number of new lock messages; takes precedence over `ssh_notify_cooldown_seconds`.
* `notify_cooldown_seconds` — Sets the minimum interval in seconds between notifications of successful locks for custom password attack protection rules.
* `notify_every` — Sends the next notification for the custom rule only after the specified number of new block messages; takes precedence over `notify_cooldown_seconds`.
***
## 0.11.0 (07.05.2026)
#### Русский
* В настройки файла `firewall.toml` добавлен параметр `options.cache`.
* Этот параметр включает кэширование, чтобы избежать постоянной компиляции команд 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 +111,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 +143,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 +167,7 @@
* The `kor-elf-shield block add` command has been added. This command can be used to block an IP address. See `kor-elf-shield block add --help` for details.
* The `kor-elf-shield block 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 +181,7 @@
* New parameters have been added to the analyzer.toml settings file. See the full list at: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
* 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 +203,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 +227,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 +249,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 +290,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:
+32
View File
@@ -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.
+10 -1
View File
@@ -1,6 +1,5 @@
# Kor-Elf Shield
### <strong>Русский</strong> &nbsp;&nbsp;&nbsp; <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
View File
@@ -1,6 +1,5 @@
# Kor-Elf Shield
### <strong>English</strong> &nbsp;&nbsp;&nbsp; <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>
+76 -3
View File
@@ -56,7 +56,7 @@ blocking_time = 3600
rate_limit_period = 3600
###
# Указываем в секундах, через какое время сбрасывать данные IP в групе _default если не было событий.
# Указываем в секундах, через какое время сбрасывать данные IP в группе _default если не было событий.
# Если указать 0, то не будет сбрасывать.
# По умолчанию: 86400
# ***
@@ -68,9 +68,15 @@ rate_limit_reset_period = 86400
###
# Включает защиту от перебора пароля от ssh.
# !!! Примечание: По умолчанию SSH-сервер не регистрирует на неудачные попытки входа с использованием ключа.
# Чтобы включить отслеживание неудачных попыток входа с использованием ключа, необходимо добавить параметр
# "LogLevel VERBOSE" в настройки sshd_config !!!
# По умолчанию: true
# ***
# Enables protection against brute-force attacks against ssh.
# !!! Note: By default, the SSH server does not log unsuccessful login attempts using a key.
# To enable tracking of unsuccessful login attempts using a key, you must add the "LogLevel VERBOSE" parameter
# to the sshd_config settings !!!
# Default: true
###
ssh_enable = true
@@ -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
###
# Можно указать свою группу, чтобы связать с другими правилами.
# По умолчанию: ""
@@ -101,7 +135,7 @@ ssh_group = ""
# [[bruteForceProtection.groups]]
# name = "my_name_group" # Имя группы. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
# message = "Любой текст группы" # Текст уведомления (обязательное поле)
# rate_limit_reset_period = 86400 # Указываем в секундах, через какое время сбрасывать данные в групе если не было событий. Если указать 0, то не будет сбрасывать.
# rate_limit_reset_period = 86400 # Указываем в секундах, через какое время сбрасывать данные в групе, если не было событий. Если указать 0, то не будет сбрасывать.
## block_type = "ip_port" # Указываем тип блокировки: ip, ip_port. Если ничего не укажите, будет указан тип ip.
## ports = ["22/tcp", "22/udp"] # Если тип блокировки стоит ip_port, то нужно указать порты, которые будут заблокированы после обнаружения попытки перебора пароля.
# [[bruteForceProtection.groups.rate_limits]]
@@ -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
###
+32
View File
@@ -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/
+54
View File
@@ -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
+4 -4
View File
@@ -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"]
###
+15 -14
View File
@@ -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
)
+29 -2
View File
@@ -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=
+186
View File
@@ -0,0 +1,186 @@
package daemon
import (
"context"
"errors"
"fmt"
"os/exec"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"github.com/urfave/cli/v3"
)
func CmdConfig() *cli.Command {
return &cli.Command{
Name: "config",
Usage: i18n.Lang.T("cmd.daemon.config.Usage"),
Commands: []*cli.Command{
{
Name: "test",
Usage: i18n.Lang.T("cmd.daemon.config.test.Usage"),
Description: i18n.Lang.T("cmd.daemon.config.test.Description"),
Action: CmdTestConfig,
},
},
}
}
func CmdTestConfig(_ context.Context, _ *cli.Command) error {
falseLogger := log.NewFalseLogger()
testMain := testMainConfig()
testDocker, dockerSupport := testDockerConfig()
testFirewall := testFirewallConfig(dockerSupport)
testAnalyzer := testAnalyzerConfig()
testNotifications := testNotificationsConfig()
testBlocklists := testBlocklistsConfig(falseLogger)
testGeoip := testGeoipConfig(falseLogger)
checkNft := checkProgramNFT()
checkJournalctl := checkProgramJournalctl()
checkDocker := ""
if dockerSupport {
checkDocker = "\n " + checkProgramDocker()
}
fmt.Println(
"***\n"+i18n.Lang.T("cmd.daemon.config.test.settingTitle"),
"\n", testMain,
"\n", testFirewall,
"\n", testAnalyzer,
"\n", testNotifications,
"\n", testDocker,
"\n", testBlocklists,
"\n", testGeoip,
"\n"+i18n.Lang.T("cmd.daemon.config.test.checkingPrograms"),
"\n", checkNft,
"\n", checkJournalctl,
checkDocker,
"\n***",
)
return nil
}
func testMainConfig() string {
configTitle := i18n.Lang.T("cmd.daemon.config.test.main")
if err := setting.Config.Validate(); err != nil {
return resultError(configTitle, err)
}
if err := setting.Config.ValidateBeforeStart(); err != nil {
return resultError(configTitle, err)
}
return resultOk(configTitle)
}
func testDockerConfig() (message string, dockerSupport bool) {
configTitle := "docker"
_, dockerSupport, err := setting.Config.OtherSettingsPath.ToDockerConfig(setting.Config.BinaryLocations)
if err != nil {
return resultError(configTitle, err), false
}
return resultOk(configTitle), dockerSupport
}
func testFirewallConfig(dockerSupport bool) string {
configTitle := "firewall"
if _, _, err := setting.Config.OtherSettingsPath.ToFirewallConfig(dockerSupport); err != nil {
return resultError(configTitle, err)
}
return resultOk(configTitle)
}
func testAnalyzerConfig() string {
configTitle := "analyzer"
if _, err := setting.Config.OtherSettingsPath.ToAnalyzerConfig(setting.Config.BinaryLocations); err != nil {
return resultError(configTitle, err)
}
return resultOk(configTitle)
}
func testNotificationsConfig() string {
configTitle := "notifications"
if _, err := setting.Config.OtherSettingsPath.ToNotificationsConfig(); err != nil {
return resultError(configTitle, err)
}
return resultOk(configTitle)
}
func testBlocklistsConfig(logger log.Logger) string {
configTitle := "blocklists"
if _, _, err := setting.Config.OtherSettingsPath.ToBlocklistConfig(logger); err != nil {
return resultError(configTitle, err)
}
return resultOk(configTitle)
}
func testGeoipConfig(logger log.Logger) string {
configTitle := "geoip"
if _, _, err := setting.Config.OtherSettingsPath.ToConfig(setting.Config.DataDir, logger); err != nil {
return resultError(configTitle, err)
}
return resultOk(configTitle)
}
func checkProgramNFT() string {
programTitle := "nftables"
path := setting.Config.BinaryLocations.Nftables
if path == "" {
return resultError(programTitle, errors.New(i18n.Lang.T("cmd.daemon.config.test.pathEmpty", map[string]interface{}{"Program": programTitle})))
}
cmd := exec.Command(path, "--version")
if err := cmd.Run(); err != nil {
return resultError(programTitle, err)
}
return resultOk(programTitle)
}
func checkProgramJournalctl() string {
programTitle := "journalctl"
path := setting.Config.BinaryLocations.Journalctl
if path == "" {
return resultError(programTitle, errors.New(i18n.Lang.T("cmd.daemon.config.test.pathEmpty", map[string]interface{}{"Program": programTitle})))
}
cmd := exec.Command(path, "--version")
if err := cmd.Run(); err != nil {
return resultError(programTitle, err)
}
return resultOk(programTitle)
}
func checkProgramDocker() string {
programTitle := "docker"
path := setting.Config.BinaryLocations.Docker
if path == "" {
return resultError(programTitle, errors.New(i18n.Lang.T("cmd.daemon.config.test.pathEmpty", map[string]interface{}{"Program": programTitle})))
}
cmd := exec.Command(path, "--version")
if err := cmd.Run(); err != nil {
return resultError(programTitle, err)
}
return resultOk(programTitle)
}
func resultOk(title string) string {
return fmt.Sprintf("%s: \033[32mOk\033[0m", title)
}
func resultError(title string, err error) string {
errText := i18n.Lang.T("cmd.daemon.config.test.error", map[string]interface{}{"Error": err})
return fmt.Sprintf("%s: \033[31mError\n %s\u001B[0m", title, errText)
}
+23 -6
View File
@@ -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)
+1
View File
@@ -37,6 +37,7 @@ func NewMainApp(appVer AppVersion, defaultConfigPath string) *cli.Command {
daemon.CmdStart(),
daemon.CmdStop(),
daemon.CmdStatus(),
daemon.CmdConfig(),
daemon.CmdReopenLogger(),
daemon.CmdNotifications(),
daemon.CmdBlock(),
@@ -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 {
+4 -2
View File
@@ -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
}
+3
View File
@@ -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,
+49 -13
View File
@@ -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 &notificationPolicy{
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"))
}
+103 -45
View File
@@ -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
}
+1
View File
@@ -10,6 +10,7 @@ import (
type Config struct {
BlocklistRepository repository.BlocklistRepository
Sources []*SourceConfig
PathDir string
}
type SourceConfig struct {
+7 -1
View File
@@ -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
View File
@@ -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
View File
@@ -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()
+6 -3
View File
@@ -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
}
+14
View File
@@ -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
}
+25 -3
View File
@@ -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)
})
}
+65
View File
@@ -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)
})
}
+7 -5
View File
@@ -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
}
+4 -16
View File
@@ -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())
}
}
+22 -7
View File
@@ -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
}
-67
View File
@@ -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)
}
-276
View File
@@ -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
}
-48
View File
@@ -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...)
}
-48
View File
@@ -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)
}
-48
View File
@@ -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
-72
View File
@@ -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
}
+122 -46
View File
@@ -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
}
+143
View File
@@ -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
}
+120
View File
@@ -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
}
@@ -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
}
@@ -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, " "))
}
+13
View File
@@ -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"`
}
+54
View File
@@ -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)
}
+45
View File
@@ -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
}
+126
View File
@@ -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
}
+94
View File
@@ -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
}
+324
View File
@@ -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
}
+231
View File
@@ -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
}
+215
View File
@@ -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
}
-331
View File
@@ -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
}
-244
View File
@@ -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
}
+10
View File
@@ -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)
}
+89
View File
@@ -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
}
+56
View File
@@ -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)
}

Some files were not shown because too many files have changed in this diff Show More