45 Commits

Author SHA1 Message Date
kor-elf 0ae0399aec Merge pull request 'v0.9.0' (#9) from develop into main
Reviewed-on: #9
2026-03-21 19:00:58 +05:00
kor-elf a87eade21c Update CHANGELOG with release date for version 0.9.0 2026-03-21 18:49:32 +05:00
kor-elf 447755dcc0 Update CHANGELOG for port-knocking support and related configuration changes 2026-03-20 21:51:31 +05:00
kor-elf 6271682e4b Add port-knocking configuration example to firewall settings 2026-03-20 21:38:55 +05:00
kor-elf c3571259a0 Add PortKnocking support to settings and configuration logic 2026-03-20 21:36:38 +05:00
kor-elf 9a406bedb6 Integrate port-knocking reload logic into firewall's input processing 2026-03-20 21:24:25 +05:00
kor-elf d25932ef7d Add KnockAction type with constants and String method for action representation 2026-03-20 21:24:00 +05:00
kor-elf a31386ed10 Add ToKnockAction method to map string actions to KnockAction types 2026-03-20 21:23:39 +05:00
kor-elf 9de460d2c9 Add PortKnocking interface and implementation to manage port-knocking rules and sequences 2026-03-20 21:23:15 +05:00
kor-elf 2a2ec666e6 Add NewPortKnocking method to block for port knocking rule initialization 2026-03-20 21:22:08 +05:00
kor-elf f198ec2c2c Add PortKnocking configuration to firewall with support for knock sequences 2026-03-20 21:20:58 +05:00
kor-elf 12bdd9ca3e Add ToNftForSet method to map IP versions to nftables set types 2026-03-20 21:20:06 +05:00
kor-elf d796b3a61b Update CHANGELOG for v0.9.0 with new blocklist features and configuration options 2026-03-19 20:24:57 +05:00
kor-elf a49bf15023 Add default blocklists.toml configuration file with predefined blocklist sources and parameters 2026-03-19 19:54:20 +05:00
kor-elf 9597257a07 Add blocklists path setting to kor-elf-shield configuration file 2026-03-19 19:54:04 +05:00
kor-elf bfcaca27a9 Integrate blocklist into daemon lifecycle and initialize blocklist service in NewDaemon 2026-03-19 19:53:47 +05:00
kor-elf 14168d3765 Integrate blocklist into firewall with support for blocklist reloading during nftables rule reloads 2026-03-19 19:53:06 +05:00
kor-elf 4587b522be Add ToBlocklistConfig method to map blocklist settings and sources
- Updated `otherSettingsPath` struct to include `Blocklists` field.
- Added `/etc/kor-elf-shield/blocklists.toml` as the default blocklist path.
- Implemented `ToBlocklistConfig` for parsing blocklist settings, validating sources, and enabling blocklist support.
2026-03-19 19:52:43 +05:00
kor-elf d2e5db7f66 Add NewBlocklist method to chains for blocklist creation and rule initialization 2026-03-19 19:52:07 +05:00
kor-elf eaa3513e03 Add blocklists package for managing blocklist settings and sources
- Introduced `blocklists` package to handle blocklist configuration and source processing.
- Added `Setting` struct with TOML file validation, default values, and configuration parsing.
- Implemented `Sources` struct for defining blocklist source attributes and validation.
- Added support for JSON, TXT, and RSS source types with respective parsers.
2026-03-19 19:51:47 +05:00
kor-elf 6d35c3e5bf Add FalseBlocklist implementation for no-op blocklist operations
- Introduced `FalseBlocklist` struct as a placeholder implementation of the `Blocklist` interface.
- Added `NftReload`, `Run`, and `Close` methods with no-op behavior.
2026-03-19 19:50:53 +05:00
kor-elf 3061d0f31e Add blocklist package with implementation of Blocklist interface
- Introduced `blocklist` package for managing blocklist lifecycle and operations.
- Implemented methods for reloading, running, and closing blocklists.
- Added `refreshSource` and `processUpdateData` for handling source updates.
- Integrated lock mechanisms for thread-safe operations.
2026-03-19 19:50:19 +05:00
kor-elf 5e0fb5787a Add BlocklistSource interface and implementations for fetching blocklist data
- Introduced `BlocklistSource` interface with `Get` method to retrieve IPv4/IPv6 blocklists.
- Added `blocklistSource` and `blocklistSourceZip` structures to support standard and ZIP-configured data sources.
2026-03-19 19:49:21 +05:00
kor-elf c0573c4e36 Add Config struct for blocklist configuration management
- Introduced `Config` and `SourceConfig` structs to manage blocklist repository and source settings.
2026-03-19 19:48:37 +05:00
kor-elf 8468fe851b Add blocklist implementation for managing IPv4/IPv6 blocklists
- Implemented `Blocklist` interface with methods for replacing blocklist elements and adding rules to chains.
- Added support for both IPv4 and IPv6 address handling.
- Introduced `NewBlocklist` for initializing blocklist instances.
2026-03-19 19:47:13 +05:00
kor-elf 6438358a53 Add ReplaceElements method for batch updating firewall blocklist
- Introduced `ReplaceElements` to allow bulk replacement of blocklist entries.
- Implemented `chunkStrings` helper for batching updates efficiently.
2026-03-19 19:45:38 +05:00
kor-elf 489c5c0cbe Add blocklistBucket for Blocklist handling in repository 2026-03-19 19:35:47 +05:00
kor-elf 7c15813b0e Add Blocklist repository with CRUD operations
- Implemented `Blocklist` entity.
- Introduced `BlocklistRepository` with `Get` and `Update` methods.
- Updated repository interface and initialization to include blocklist handling.
2026-03-19 19:35:19 +05:00
kor-elf 3515b66dc7 Update dependencies: add kor-elf-shield/blocklist v1.1.0. 2026-03-19 19:34:00 +05:00
kor-elf f5ff0c1afd Merge pull request 'v0.8.0' (#8) from develop into main
Reviewed-on: #8
2026-03-09 21:35:22 +05:00
kor-elf 63bc845b8b Update CHANGELOG.md to set release date for version 0.8.0 2026-03-09 21:26:14 +05:00
kor-elf a44f9b4e75 Update CHANGELOG.md to fix release date for version 0.7.0 2026-03-09 21:24:25 +05:00
kor-elf 4647d1303e Update CHANGELOG.md to document new kor-elf-shield block delete command for IP unblocking 2026-03-09 21:23:28 +05:00
kor-elf 221fdb8d3b Add command for removing IP addresses from the block list
- Introduced `block delete` command to remove IPs from the block list.
- Added `UnblockIP` method to support IP removal in the firewall.
- Updated internationalization files for delete command descriptions.
- Enhanced repository with `DeleteByIP` for targeted IP removal.
2026-03-09 21:21:28 +05:00
kor-elf a7e4c7d750 Update CHANGELOG.md to document new kor-elf-shield block add command for IP blocking 2026-03-09 17:47:53 +05:00
kor-elf 75c8eba0cd Add a command to add IP address blocking 2026-03-09 17:45:14 +05:00
kor-elf bf8711aadd Add support for structured socket commands with JSON and argument parsing
- Introduced `SendCommand` for sending commands with arguments over sockets.
- Updated socket communication to encode/decode commands as JSON.
- Refactored daemon handlers to process commands with arguments.
- Added `Message` struct and `parseCommand` function for improved command handling.
2026-03-09 16:16:50 +05:00
kor-elf 1dbb4d0bff Refactor daemon commands to use newSocket helper for socket initialization. 2026-03-09 12:52:51 +05:00
kor-elf 993f48f541 Rename CmdBlockClear to cmdBlockClear for consistency with Go naming conventions. 2026-03-09 11:08:55 +05:00
kor-elf 286f32b618 Rename kor-elf-shield ban clear command to kor-elf-shield block clear and update CHANGELOG.md. 2026-03-09 11:05:34 +05:00
kor-elf 42e4a8cf40 Rename ban_clear command and related references to block_clear for improved clarity and consistency. 2026-03-09 11:02:49 +05:00
kor-elf be3861ee6e Update CHANGELOG.md with 0.8.0 release details and added configuration parameters for brute force protection 2026-03-09 11:02:12 +05:00
kor-elf d0a358a445 Expand analyzer configuration with block type and port settings
- Added support for specifying `block_type` and `ports` in brute force protection groups.
- Enhanced rate limit configuration to override `block_type` and define specific ports for blocking.
- Updated documentation in `analyzer.toml` with examples for new settings.
2026-03-09 10:47:14 +05:00
kor-elf 39cfb8a7b6 Add support for IP and port-based brute force protection
- Introduced `Block` interface to handle IP and port blocking configurations.
- Added `BlockIPWithPorts` functionality for enhanced blocking with ports.
- Enhanced brute force protection to support IP and port-based rules.
- Updated `Blocking` entity and repository for port-specific blocking.
- Added internationalization for port-based brute force notifications.
- Refactored the analyzer to accommodate new block configurations.
2026-03-05 01:10:02 +05:00
kor-elf 65eaa37637 Refactor firewall type handling and port configuration
- Introduced `types` package with structured definitions (`Protocol`, `Action`, `Direction`, `PolicyDrop`, etc.).
- Replaced primitive types for port-related logic with `L4Port` interface for improved encapsulation.
- Updated firewall methods to use `L4Port`, enhancing readability and reducing direct type handling.
- Adjusted validation and configuration logic to leverage new `types`.
2026-03-02 22:20:06 +05:00
70 changed files with 3095 additions and 364 deletions
+59 -3
View File
@@ -1,15 +1,71 @@
## 0.7.0 (8.2.2026)
## 0.9.0 (21.3.2026)
#### Русский
* Добавилась поддержка Port knocking.
* В firewall.toml добавился раздел Port knocking.
* Теперь вы можете получить список IP-адресов от различных сервисов для блокировки доступа:
* Spamhaus Don't Route Or Peer Lists
* DShield.org Recommended Block List
* TOR Exit Nodes List
* Project Honey Pot Directory of Dictionary Attacker IPs
* C.I. Army Malicious IP List
* BruteForceBlocker IP List
* Blocklist.de
* Stop Forum Spam
* GreenSnow Hack List
* В настройки файла kor-elf-shield.toml добавлен параметр otherSettingsPath.blocklists.
* Добавлен новый файл настроек, blocklists.toml. Он содержит параметры для получения списка IP-адресов для блокировки.
***
#### English
* Added support for port knocking.
* A Port knocking section has been added to firewall.toml.
* Now you can get a list of IP addresses from various services to block access:
* Spamhaus Don't Route Or Peer Lists
* DShield.org Recommended Block List
* TOR Exit Nodes List
* Project Honey Pot Directory of Dictionary Attacker IPs
* C.I. Army Malicious IP List
* BruteForceBlocker IP List
* Blocklist.de
* Stop Forum Spam
* 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)
***
#### Русский
* Теперь можно тонко настроить блокировку портов для IP адреса, который пытается подобрать пароль.
* В файл настроек analyzer.toml в [[bruteForceProtection.groups]] добавлен новый параметр "block_type".
* В файл настроек analyzer.toml в [[bruteForceProtection.groups]] добавлен новый параметр "ports".
* В файл настроек analyzer.toml в [[bruteForceProtection.groups.rate_limits]] добавлен новый параметр "block_type".
* В файл настроек analyzer.toml в [[bruteForceProtection.groups.rate_limits]] добавлен новый параметр "ports".
* Смотрите полный список по ссылке: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/d0a358a445b1dec850d8b84c06e86bd6872796cf/assets/configs/analyzer.toml
* Команда `kor-elf-shield ban clear` была переименованна в `kor-elf-shield block clear`.
* Добавлена команда `kor-elf-shield block add`. Через эту команду можно заблокировать IP адрес. Смотрите подробно в `kor-elf-shield block add --help`.
* Добавлена команда `kor-elf-shield block delete`. Через эту команду можно удалить заблокированный IP адрес. Смотрите подробно в `kor-elf-shield block delete --help`.
***
#### English
* You can now fine-tune port blocking for the IP address attempting to brute-force a password.
* A new "block_type" parameter has been added to the analyzer.toml settings file in [[bruteForceProtection.groups]].
* A new "ports" parameter has been added to the analyzer.toml settings file in [[bruteForceProtection.groups]].
* A new "block_type" parameter has been added to the analyzer.toml settings file in [[bruteForceProtection.groups.rate_limits]].
* A new "ports" parameter has been added to the analyzer.toml settings file in [[bruteForceProtection.groups.rate_limits]].
* See the full list at: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/d0a358a445b1dec850d8b84c06e86bd6872796cf/assets/configs/analyzer.toml
* The `kor-elf-shield ban clear` command has been renamed to `kor-elf-shield block clear`.
* 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)
***
#### Русский
* Добавлена возможность настройки отслеживания событий в журналах.
* Добавлены настройки для защиты от перебора паролей.
* В файл настроек analyzer.toml добавлены новые параметры. Смотрите полный список по ссылке https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
* В файл настроек analyzer.toml добавлены новые параметры. Смотрите полный список по ссылке: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
* Добавлена команда `kor-elf-shield ban clear`, которая разблокирует все IP адреса. Которые были забанены.
***
#### English
* Added the ability to customize event tracking in logs.
* Added settings to protect against password guessing.
* New parameters have been added to the analyzer.toml settings file. See the full list at https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
* New parameters have been added to the analyzer.toml settings file. See the full list at: https://git.kor-elf.net/kor-elf-shield/kor-elf-shield/src/commit/187c447301b9c0bfa41ec2b2c9435ab0ce44bed6/assets/configs/analyzer.toml
* Added the `kor-elf-shield ban clear` command, which unbans all banned IP addresses.
***
## 0.6.0 (8.2.2026)
+11 -1
View File
@@ -87,7 +87,7 @@ ssh_enable = true
ssh_notify = true
###
# Можно указать свою группу, чтобы по связать с другими правилами.
# Можно указать свою группу, чтобы связать с другими правилами.
# По умолчанию: ""
# ***
# You can specify your own group to link it to other rules.
@@ -102,12 +102,17 @@ ssh_group = ""
# name = "my_name_group" # Имя группы. Разрешены символы "a-z, A-Z, -, _". Первый символ обязательно буква (обязательное поле)
# message = "Любой текст группы" # Текст уведомления (обязательное поле)
# 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]]
## Через сколько будет срабатывать блокировка. В данном случае в течение часа, если было 5 обнаружений, то сработает блокировка.
## И заблокирует на 10 минут.
# count = 5
# period = 3600
# blocking_time = 600
## Внутри bruteForceProtection.groups.rate_limits можно переопределить настройки block_type и ports.
## block_type = "ip_port" # Указываем тип блокировки: ip, ip_port. Если ничего не укажите, будет указан тип ip.
## ports = ["22/tcp", "22/udp", "80/tcp", "443/tcp"] # Если тип блокировки стоит ip_port, то нужно указать порты, которые будут заблокированы после обнаружения попытки перебора пароля.
# [[bruteForceProtection.groups.rate_limits]]
## После срабатывания блокировки, переходим на второй уровень, тепер если в течение часа было 3 обнаружений, то сработает блокировка.
## И теперь заблокирует на час.
@@ -127,12 +132,17 @@ ssh_group = ""
# name = "my_name_group" # Group name. Allowed characters are "a-z, A-Z, -, _". The first character must be a letter (required)
# message = "Any group text" # Notification text (required)
# rate_limit_reset_period = 86400 # Specify, in seconds, how long to reset group data if there have been no events. Specifying 0 means no reset.
## block_type = "ip_port" # Specify the blocking type: IP, IP_port. If you don't specify anything, the IP type will be used.
## ports = ["22/tcp", "22/udp"] # If the blocking type is ip_port, then you need to specify the ports that will be blocked after detecting a password brute-force attempt.
# [[bruteForceProtection.groups.rate_limits]]
## How long will it take for the block to be triggered? In this case, if there were 5 detections within an hour, the block will be triggered.
## And it will block for 10 minutes.
# count = 5
# period = 3600
# blocking_time = 600
## Inside bruteForceProtection.groups.rate_limits you can override the block_type and ports settings.
## block_type = "ip_port" # Specify the blocking type: IP, IP_port. If you don't specify anything, the IP type will be used.
## ports = ["22/tcp", "22/udp", "80/tcp", "443/tcp"] # If the blocking type is ip_port, then you need to specify the ports that will be blocked after detecting a password brute-force attempt.
# [[bruteForceProtection.groups.rate_limits]]
## After the blocking is triggered, we move to the second level. Now, if there are three detections within an hour, the blocking will be triggered.
## And now it will block for an hour.
+341
View File
@@ -0,0 +1,341 @@
###############################################################################
# РАЗДЕЛ:Настройки для определения списков блокировки IP-адресов
# ***
# SECTION:Settings for defining IP address block lists
###############################################################################
###
# Обеспечивает поддержку получения списков заблокированных IP-адресов.
# !!! Для полученние данных нужно открыть исходящий порт 443. !!!
# !!! После включения не забудьте включить необходимые списки, так как по умолчанию они все отключены. !!!
# По умолчанию: false
# ***
# Enables support for retrieving IP block lists.
# !!! To receive data, you need to open outgoing port 443. !!!
# !!! After turning on, do not forget to turn on the necessary lists, as they are all turned off by default. !!!
# Default: false
###
enabled = false
###
# Spamhaus Don't Route Or Peer Lists (DROP IPv4)
# Details: https://www.spamhaus.org/blocklists/do-not-route-or-peer/
###
[[sources]]
enabled = false
name = "SPAMDROP"
url = "https://www.spamhaus.org/drop/drop_v4.json"
format = "json"
json_field = "cidr"
limit = 0
interval = 86400
###
# Spamhaus Don't Route Or Peer Lists (DROP IPv6)
# Details: https://www.spamhaus.org/blocklists/do-not-route-or-peer/
###
[[sources]]
enabled = false
name = "SPAMDROPV6"
url = "https://www.spamhaus.org/drop/drop_v6.json"
format = "json"
json_field = "cidr"
limit = 0
interval = 86400
###
# DShield.org Recommended Block List
# Details: https://dshield.org
###
[[sources]]
enabled = false
name = "DSHIELD"
url = "https://www.dshield.org/block.txt"
format = "txt"
txt_type = "cidr"
txt_field_ip = 0
txt_field_cidr = 2
txt_separator = "\t"
limit = 0
interval = 86400
###
# Тот же список, только мы получаем данные не через CIDR, а через диапазон IP-адресов.
# !!! Не рекомендуется включать DSHIELD и DSHIELD_INTERVAL одновременно. !!!
# ***
# The same list, only we receive data not via CIDR, but via a range of IP addresses.
# !!! It is not recommended to turn on DSHIELD and DSHIELD_INTERVAL together. !!!
###
[[sources]]
enabled = false
name = "DSHIELD_INTERVAL"
url = "https://www.dshield.org/block.txt"
format = "txt"
txt_type = "interval"
txt_field_ip = 0
txt_field_ip2 = 1
txt_separator = "\t"
limit = 0
interval = 600
###
# TOR Exit Nodes List
# Details: https://trac.torproject.org/projects/tor/wiki/doc/TorDNSExitList
###
[[sources]]
enabled = false
name = "TOR"
url = "https://check.torproject.org/torbulkexitlist"
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = " "
limit = 0
interval = 86400
###
# Project Honey Pot Directory of Dictionary Attacker IPs
# Details: https://www.projecthoneypot.org/
###
[[sources]]
enabled = false
name = "HONEYPOT"
url = "https://www.projecthoneypot.org/list_of_ips.php?t=d&rss=1"
format = "rss"
rss_tag = "item"
rss_field = "title"
rss_field_ip = 0
rss_field_separator = "|"
limit = 0
interval = 86400
###
# C.I. Army Malicious IP List
# Details: https://www.ciarmy.com/
###
[[sources]]
enabled = false
name = "CIARMY"
url = "https://www.ciarmy.com/list/ci-badguys.txt"
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = " "
limit = 0
interval = 86400
###
# BruteForceBlocker IP List
# Details: https://danger.rulez.sk/index.php/bruteforceblocker/
###
[[sources]]
enabled = false
name = "BFB"
url = "https://danger.rulez.sk/projects/bruteforceblocker/blist.php"
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = "\t"
limit = 0
interval = 86400
###
# Blocklist.de
# Details: https://www.blocklist.de
# В первом списке отображаются только IP-адреса, добавленные за последний час.
# ***
# This first list only retrieves the IP addresses added in the last hour.
###
[[sources]]
enabled = false
name = "BDE"
url = "https://api.blocklist.de/getlast.php?time=3600"
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = "\t"
limit = 0
interval = 86400
###
# Второй список содержит все IP-адреса, добавленные за последние 48 часов.
# Обычно это очень большой список (более 10000 записей), поэтому убедитесь, что у вас
# есть необходимые ресурсы для его использования.
# !!! Не рекомендуется включать BDE и BDEALL одновременно. !!!
# ***
# This second list retrieves all the IP addresses added in the last 48 hours
# and is usually a very large list (over 10000 entries), so be sure that you
# have the resources available to use it
# !!! It is not recommended to turn on BDE and BDEALL together. !!!
###
[[sources]]
enabled = false
name = "BDEALL"
url = "https://lists.blocklist.de/lists/all.txt"
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = "\t"
limit = 0
interval = 86400
# Stop Forum Spam (IPv4)
# Details: https://www.stopforumspam.com/downloads
# Многие из доступных списков содержат огромное количество IP-адресов,
# поэтому следует проявлять осторожность при выборе из этих списков.
# ***
# Many of the lists available contain a vast number of IP addresses so special
# care needs to be made when selecting from their lists.
[[sources]]
enabled = false
name = "STOPFORUMSPAM"
url = "https://www.stopforumspam.com/downloads/listed_ip_1.zip"
zip = true
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = " "
limit = 0
interval = 86400
# Stop Forum Spam (IPv6)
# Details: https://www.stopforumspam.com/downloads
# Многие из доступных списков содержат огромное количество IP-адресов,
# поэтому следует проявлять осторожность при выборе из этих списков.
# ***
# Many of the lists available contain a vast number of IP addresses so special
# care needs to be made when selecting from their lists.
[[sources]]
enabled = false
name = "STOPFORUMSPAMV6"
url = "https://www.stopforumspam.com/downloads/listed_ip_1_ipv6.zip"
zip = true
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = " "
limit = 0
interval = 86400
###
# GreenSnow Hack List
# Details: https://greensnow.co
###
[[sources]]
enabled = false
name = "GREENSNOW"
url = "https://blocklist.greensnow.co/greensnow.txt"
format = "txt"
txt_type = "default"
txt_field_ip = 0
txt_separator = " "
limit = 0
interval = 86400
###############################################################################
# Если вы хотите реализовать свой собственный список или хотите понять параметры,
# описание параметров вам может помочь:
# ***
# If you want to implement your own list or want to understand the parameters,
# the parameter description might help:
###############################################################################
#
# [[sources]]
#
# Включает или выключает получения данных с этого списка
# ***
# Enables or disables retrieving data from this list
# enabled = false
#
# Имя, которое будет использоваться в создание set в nftables.
# Имя должно быть уникальное и разрешены символы "a-z, A-Z, -, _"
# ***
# The name that will be used when creating a set in nftables.
# The name must be unique and the characters "a-z, A-Z, -, _" are allowed.
# name = "SPAMDROP"
#
# Адрес по которому будет программа обращаться, чтобы получить список IP адресов для блокировки.
# ***
# The address that the program will contact to obtain a list of IP addresses to block.
# url = "https://www.spamhaus.org/drop/drop_v4.json"
#
# Это максимальное количество IP-адресов из списка, которые можно использовать.
# Значение 0 означает использование всех IP-адресов.
# ***
# This is the maximum number of IP addresses from the list that can be used.
# A value of 0 means all IP addresses are used.
# limit = 0
#
# Интервал обновления для загрузки списка должен составлять минимум 60 секунд (не рекомендуется),
# но 86400 (в день) будет более чем достаточно.
# ***
# The refresh interval for loading the list should be at least 60 seconds (not recommended),
# but 86400 (per day) will be more than enough.
# interval = 86400
#
# Если получаемый файл в zip формате, то надо включить zip = true.
# ***
# If the file received is in zip format, then you must include zip = true.
# zip = false
#
# Есть несколько форматов: "json", "txt", "rss".
# ***
# There are several formats: "json", "txt", "rss".
# format = "json"
#
# Это поле используется, если `format = "json"`. Здесь мы указываем имя ключа, по которому получаем IP-адрес.
# ***
# This field is used if the `format = "json"`. Here we specify the name of the key by which we obtain the IP address.
# json_field = "cidr"
#
# Это поле используется, если `format = "txt"`. Допустимые значения: "default", "cidr", "interval"
# ***
# This field is used if `format = "txt"`. Valid values: "default", "cidr", "interval"
# txt_type = "cidr"
#
# Это поле используется, если `format = "txt"`. Это индекс поля для получения IP адреса.
# Допустимые значения от 0 и выше.
# ***
# This field is used if `format = "txt"`. This is the field index for obtaining the IP address.
# Valid values are 0 and higher.
# txt_field_ip = 0
#
# Это поле используется, если `format = "txt"`. Это разделитель. Парсер использует его для разделения строки на поля.
# ***
# This field is used if `format = "txt"`. It's a delimiter. The parser uses it to divide the string into fields.
# txt_separator = "\t"
#
# Это поле используется, если `format = "txt"` и `txt_type = "cidr"`. Указывает индекс поля для получения cidr.
# Допустимые значения от 0 и выше.
# ***
# This field is used if `format = "txt"` and `txt_type = "cidr"`. Specifies the field index to retrieve the cidr.
# Valid values are 0 and above.
# txt_field_cidr = 2
#
# Это поле используется, если `format = "txt"` и `txt_type = "interval"`.
# Указывает индекс поля для получения второго IP (по). Допустимые значения от 0 и выше.
# ***
# This field is used if `format = "txt"` and `txt_type = "interval"`.
# Specifies the field index to retrieve the second IP (to). Valid values are 0 and higher.
# txt_field_ip2 = 1
#
# Это поле используется, если `format = "rss"`. Указывает на родительский тег.
# ***
# This field is used if `format="rss"`. Points to the parent tag.
# rss_tag = "item"
#
# Это поле используется, если `format = "rss"`. Указывает на тег, который находится внутри родительского тега.
# ***
# This field is used if `format="rss"`. Points to a tag that is inside the parent tag.
# rss_field = "title"
#
# Это поле используется, если `format = "rss"`. Если нужно разделить получаемый текст.
# ***
# This field is used if `format = "rss"`. If you need to split the received text.
# rss_field_separator = "|"
#
# Это поле используется, если `format = "rss"` и указан `rss_field_separator`. Это индекс поля для получения IP адреса.
# Допустимые значения от 0 и выше.
# ***
# This field is used if `format = "rss"` and `rss_field_separator` is specified. This is the field index for obtaining the IP address.
# Valid values are 0 and above.
# rss_field_ip = 0
###
+61
View File
@@ -508,6 +508,67 @@ forward_drop = "drop"
###
forward_priority = -10
###############################################################################
# РАЗДЕЛ:Port knocking
# ***
# SECTION:Port knocking
###############################################################################
###
# Тут можно настрить Port knocking. Это когда надо открыть порт, только поcле определённых стуков по определённому портам.
#
# Пример:
# [[portKnocking]]
# name = "ssh" # Имя должно быть уникальное и разрешены символы: "a-z, A-Z, -, _"
# port = 22 # Номер порта, который нужно открыть после всех стуков
# protocol = "tcp" # Протокол: tcp, udp
# ip_version = "ip4" # Версия IP: ip4, ip6
# [[portKnocking.knock]] # Первый стук
# port = 2222 # Порт стука
# protocol = "tcp" # Протокол: tcp, udp
# timeout = 30 # Время в секундах на которое работает стук
# action = "drop" # Во время стука, какой ответ отдавать: accept, return, drop or reject
## Лучше установить для параметра "action" значение "drop", чтобы любой, кто попытается угадать такие порты для "стука",
## не смог отличить их от заблокированного порта.
# [[portKnocking.knock]] # Второй стук (можно добавлять сколько хотите)
# port = 2225
# protocol = "tcp"
# timeout = 30
# action = "drop"
#
# Рассмотрим пример. Мы назвали настройку для portKnocking "ssh". В nftables будут созданны наборы sets таким образом:
# knock_ssh_0, knock_ssh_1. Мы открываем 22/tcp порт для IP адреса, который прошёл в данном случае два стука.
# 1 стук надо сделать на 2222/tcp и в течении 30 секунд надо сделать второй стук на порт 2225/tcp.
# После второго стука будет открыт для данного IP адреса порт 22/tcp на 30 секунд.
#
# ***
#
# Here you can configure port knocking. This allows you to open a port only after certain knocks on a specific ports.
#
# Example:
# [[portKnocking]]
# name = "ssh" # The name must be unique and symbols are allowed: "a-z, A-Z, -, _"
# port = 22 # The port number that needs to be opened after all the knocking
# protocol = "tcp" # Protocol: tcp, udp
# ip_version = "ip4" # IP version: ip4, ip6
# [[portKnocking.knock]] # The first knock
# port = 2222 # Port of knocking
# protocol = "tcp" # Protocol: tcp, udp
# timeout = 30 # The time in seconds for which the knocking works
# action = "drop" # When knocking, what answer should I give: accept, return, drop or reject
## It's best to set the "action" parameter to "drop" so that anyone trying to guess such ports for "knocking"
## won't be able to distinguish them from a blocked port.
# [[portKnocking.knock]] # Second knock (you can add as many as you want)
# port = 2225
# protocol = "tcp"
# timeout = 30
# action = "drop"
#
# Let's look at an example. We named the portKnocking setting "ssh." Sets will be created in nftables as follows:
# knock_ssh_0, knock_ssh_1. We open port 22/tcp for the IP address that, in this case, has been knocked twice.
# The first knock should be made on 2222/tcp, and within 30 seconds, a second knock should be made on port 2225/tcp.
# After the second knock, port 22/tcp will be opened for this IP address for 30 seconds.
###
###############################################################################
# РАЗДЕЛ:Именование метаданных
# ***
+11
View File
@@ -273,3 +273,14 @@ analyzer = "/etc/kor-elf-shield/analyzer.toml"
# Default: /etc/kor-elf-shield/docker.toml
###
docker = "/etc/kor-elf-shield/docker.toml"
###
# Укажите путь к настройкам для получения списков блокировки IP-адресов.
# Файл должен иметь расширение .toml.
# По умолчанию: /etc/kor-elf-shield/blocklists.toml
# ***
# Specify the path to the settings for retrieving IP address block lists.
# The file must have the .toml extension.
# Default: /etc/kor-elf-shield/blocklists.toml
###
blocklists = "/etc/kor-elf-shield/blocklists.toml"
+1
View File
@@ -3,6 +3,7 @@ module git.kor-elf.net/kor-elf-shield/kor-elf-shield
go 1.25
require (
git.kor-elf.net/kor-elf-shield/blocklist v1.1.0
git.kor-elf.net/kor-elf-shield/go-nftables-client v0.1.1
github.com/nicksnyder/go-i18n/v2 v2.6.1
github.com/nxadm/tail v1.4.11
+4 -18
View File
@@ -1,8 +1,9 @@
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/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=
github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
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=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
@@ -10,8 +11,6 @@ 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/go-viper/mapstructure/v2 v2.4.0 h1:EBsztssimR/CONLSZZ04E8qAkxNYq4Qp9LvH92wZUgs=
github.com/go-viper/mapstructure/v2 v2.4.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM=
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=
@@ -20,8 +19,6 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/nicksnyder/go-i18n/v2 v2.6.0 h1:C/m2NNWNiTB6SK4Ao8df5EWm3JETSTIGNXBpMJTxzxQ=
github.com/nicksnyder/go-i18n/v2 v2.6.0/go.mod h1:88sRqr0C6OPyJn0/KRNaEz1uWorjxIKP7rUUcvycecE=
github.com/nicksnyder/go-i18n/v2 v2.6.1 h1:JDEJraFsQE17Dut9HFDHzCoAWGEQJom5s0TRd17NIEQ=
github.com/nicksnyder/go-i18n/v2 v2.6.1/go.mod h1:Vee0/9RD3Quc/NmwEjzzD7VTZ+Ir7QbXocrkhOzmUKA=
github.com/nxadm/tail v1.4.11 h1:8feyoE3OzPrcshW5/MJ4sGESc5cqmGkGCWlco4l0bqY=
@@ -46,8 +43,6 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
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.4.1 h1:1M9UOCy5bLmGnuu1yn3t3CB4rG79Rtoxuv1sPhnm6qM=
github.com/urfave/cli/v3 v3.4.1/go.mod h1:FJSKtM/9AiiTOJL4fJ6TbMUkxBXn7GO9guZqoZtpYpo=
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/wneessen/go-mail v0.7.2 h1:xxPnhZ6IZLSgxShebmZ6DPKh1b6OJcoHfzy7UjOkzS8=
@@ -56,26 +51,17 @@ go.etcd.io/bbolt v1.4.3 h1:dEadXpI6G79deX5prL3QRNP6JB8UxVkqo4UPnHaNXJo=
go.etcd.io/bbolt v1.4.3/go.mod h1:tKQlpPaYCVFctUIgFKFnAlvbmB3tpy1vkTnDWohtc0E=
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.10.0 h1:S0h4aNzvfcFsC3dRF1jLoaov7oRaKqRGC/pUEJ2yvPQ=
go.uber.org/multierr v1.10.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
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.0 h1:aJMhYGrd5QSmlpLMr2MftRKl7t8J8PTZPA732ud/XR8=
go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc=
go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc=
go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
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/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
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/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
golang.org/x/text v0.34.0 h1:oL/Qq0Kdaqxa1KbNeMKwQq0reLCCaFtqu2eNuSeNHbk=
golang.org/x/text v0.34.0/go.mod h1:homfLqTYRFyVYemLBFl5GgL/DWEiH5wcsQ5gSh1yziA=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
-53
View File
@@ -1,53 +0,0 @@
package daemon
import (
"context"
"errors"
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/socket"
"github.com/urfave/cli/v3"
)
func CmdBan() *cli.Command {
return &cli.Command{
Name: "ban",
Usage: i18n.Lang.T("cmd.daemon.ban.Usage"),
Commands: []*cli.Command{
{
Name: "clear",
Usage: i18n.Lang.T("cmd.daemon.ban.clear.Usage"),
Description: i18n.Lang.T("cmd.daemon.ban.clear.Description"),
Action: CmdBanClear,
},
},
}
}
func CmdBanClear(_ context.Context, _ *cli.Command) error {
if setting.Config.SocketFile == "" {
return errors.New(i18n.Lang.T("socket file is not specified"))
}
sock, err := socket.NewSocketClient(setting.Config.SocketFile)
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
defer func() {
_ = sock.Close()
}()
result, err := sock.Send("ban_clear")
if err != nil {
return err
}
if result != "ok" {
return errors.New(i18n.Lang.T("ban_clear_error"))
}
fmt.Println(i18n.Lang.T("ban_clear_success"))
return nil
}
+141
View File
@@ -0,0 +1,141 @@
package daemon
import (
"context"
"errors"
"fmt"
"net"
"strconv"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"github.com/urfave/cli/v3"
)
func CmdBlock() *cli.Command {
return &cli.Command{
Name: "block",
Usage: i18n.Lang.T("cmd.daemon.block.Usage"),
Commands: []*cli.Command{
{
Name: "add",
Usage: i18n.Lang.T("cmd.daemon.block.add.Usage"),
Description: i18n.Lang.T("cmd.daemon.block.add.Description"),
Action: cmdBlockAdd,
Flags: []cli.Flag{
&cli.StringFlag{
Name: "port",
Usage: i18n.Lang.T("cmd.daemon.block.add.FlagUsage.port"),
},
&cli.Uint32Flag{
Name: "seconds",
Usage: i18n.Lang.T("cmd.daemon.block.add.FlagUsage.seconds"),
},
&cli.StringFlag{
Name: "reason",
Usage: i18n.Lang.T("cmd.daemon.block.add.FlagUsage.reason"),
},
},
},
{
Name: "delete",
Usage: i18n.Lang.T("cmd.daemon.block.delete.Usage"),
Description: i18n.Lang.T("cmd.daemon.block.delete.Description"),
Action: cmdBlockDelete,
},
{
Name: "clear",
Usage: i18n.Lang.T("cmd.daemon.block.clear.Usage"),
Description: i18n.Lang.T("cmd.daemon.block.clear.Description"),
Action: cmdBlockClear,
},
},
}
}
func cmdBlockAdd(_ context.Context, cmd *cli.Command) error {
ip := net.ParseIP(cmd.Args().Get(0))
if ip == nil {
return errors.New("invalid ip address")
}
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
defer func() {
_ = sock.Close()
}()
result, err := sock.SendCommand("block_add_ip", map[string]string{
"ip": ip.String(),
"port": cmd.String("port"),
"seconds": strconv.Itoa(int(cmd.Uint32("seconds"))),
"reason": cmd.String("reason"),
})
if err != nil {
return err
}
if result != "ok" {
return errors.New(i18n.Lang.T("cmd.error", map[string]any{
"Error": result,
}))
}
fmt.Println(i18n.Lang.T("block_add_ip_success"))
return nil
}
func cmdBlockDelete(_ context.Context, cmd *cli.Command) error {
ip := net.ParseIP(cmd.Args().Get(0))
if ip == nil {
return errors.New("invalid ip address")
}
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
defer func() {
_ = sock.Close()
}()
result, err := sock.SendCommand("block_delete_ip", map[string]string{
"ip": ip.String(),
})
if err != nil {
return err
}
if result != "ok" {
return errors.New(i18n.Lang.T("cmd.error", map[string]any{
"Error": result,
}))
}
fmt.Println(i18n.Lang.T("block_delete_ip_success"))
return nil
}
func cmdBlockClear(_ context.Context, _ *cli.Command) error {
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
defer func() {
_ = sock.Close()
}()
result, err := sock.Send("block_clear")
if err != nil {
return err
}
if result != "ok" {
return errors.New(i18n.Lang.T("block_clear_error"))
}
fmt.Println(i18n.Lang.T("block_clear_success"))
return nil
}
+16
View File
@@ -0,0 +1,16 @@
package daemon
import (
"errors"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/socket"
)
func newSocket() (socket.Client, error) {
if setting.Config.SocketFile == "" {
return nil, errors.New(i18n.Lang.T("socket file is not specified"))
}
return socket.NewSocketClient(setting.Config.SocketFile)
}
+2 -10
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/socket"
"github.com/urfave/cli/v3"
)
@@ -39,10 +37,7 @@ func CmdNotifications() *cli.Command {
}
func cmdNotificationsQueueCount(_ context.Context, _ *cli.Command) error {
if setting.Config.SocketFile == "" {
return errors.New(i18n.Lang.T("socket file is not specified"))
}
sock, err := socket.NewSocketClient(setting.Config.SocketFile)
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
@@ -63,10 +58,7 @@ func cmdNotificationsQueueCount(_ context.Context, _ *cli.Command) error {
}
func cmdNotificationsQueueClear(_ context.Context, _ *cli.Command) error {
if setting.Config.SocketFile == "" {
return errors.New(i18n.Lang.T("socket file is not specified"))
}
sock, err := socket.NewSocketClient(setting.Config.SocketFile)
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
+1 -7
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/socket"
"github.com/urfave/cli/v3"
)
@@ -21,11 +19,7 @@ func CmdReopenLogger() *cli.Command {
}
func cmdReopenLogger(_ context.Context, _ *cli.Command) error {
if setting.Config.SocketFile == "" {
return errors.New(i18n.Lang.T("socket file is not specified"))
}
sock, err := socket.NewSocketClient(setting.Config.SocketFile)
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
+28 -1
View File
@@ -5,6 +5,7 @@ import (
"fmt"
"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"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/repository"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor"
@@ -75,7 +76,9 @@ func runDaemon(ctx context.Context, _ *cli.Command) error {
return err
}
d, err := daemon.NewDaemon(config, logger, notificationsService, dockerService)
blocklistService := newBlocklistService(ctx, repositories.Blocklist(), logger)
d, err := daemon.NewDaemon(config, logger, notificationsService, dockerService, blocklistService)
if err != nil {
logger.Fatal(err.Error())
@@ -123,3 +126,27 @@ func newDockerService(ctx context.Context, logger log.Logger) (dockerService doc
return dockerService, dockerSupport, nil
}
func newBlocklistService(ctx context.Context, blocklistRepository repository.BlocklistRepository, logger log.Logger) blocklist.Blocklist {
config, isEnabled, err := setting.Config.OtherSettingsPath.ToBlocklistConfig(logger)
if err != nil {
logger.Error(fmt.Sprintf("Failed to create blocklist service: %s", err))
return blocklist.NewFalseBlocklist()
}
if !isEnabled {
return blocklist.NewFalseBlocklist()
}
blocklistConfig := blocklist.Config{
BlocklistRepository: blocklistRepository,
Sources: config,
}
blocklistService, err := blocklist.New(blocklistConfig, ctx, logger)
if err != nil {
logger.Error(err.Error())
return blocklist.NewFalseBlocklist()
}
return blocklistService
}
+1 -7
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/socket"
"github.com/urfave/cli/v3"
)
@@ -21,11 +19,7 @@ func CmdStatus() *cli.Command {
}
func cmdStatus(_ context.Context, _ *cli.Command) error {
if setting.Config.SocketFile == "" {
return errors.New(i18n.Lang.T("socket file is not specified"))
}
sock, err := socket.NewSocketClient(setting.Config.SocketFile)
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
+1 -7
View File
@@ -6,8 +6,6 @@ import (
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/socket"
"github.com/urfave/cli/v3"
)
@@ -21,11 +19,7 @@ func CmdStop() *cli.Command {
}
func stopDaemon(_ context.Context, _ *cli.Command) error {
if setting.Config.SocketFile == "" {
return errors.New(i18n.Lang.T("socket file is not specified"))
}
sock, err := socket.NewSocketClient(setting.Config.SocketFile)
sock, err := newSocket()
if err != nil {
return errors.New(i18n.Lang.T("daemon is not running"))
}
+1 -1
View File
@@ -39,7 +39,7 @@ func NewMainApp(appVer AppVersion, defaultConfigPath string) *cli.Command {
daemon.CmdStatus(),
daemon.CmdReopenLogger(),
daemon.CmdNotifications(),
daemon.CmdBan(),
daemon.CmdBlock(),
}
return app
+3 -2
View File
@@ -7,6 +7,7 @@ import (
config2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
analyzerLog "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/log"
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/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/notifications"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
@@ -29,7 +30,7 @@ type analyzer struct {
logChan chan analysisServices.Entry
}
func New(config config2.Config, blockIPFunc analysisServices.BlockIPFunc, repositories db.Repositories, logger log.Logger, notify notifications.Notifications) Analyzer {
func New(config config2.Config, blockService brute_force_protection_group.BlockService, repositories db.Repositories, logger log.Logger, notify notifications.Notifications) Analyzer {
var journalMatches []string
journalMatchesUniq := map[string]struct{}{}
@@ -65,7 +66,7 @@ func New(config config2.Config, blockIPFunc analysisServices.BlockIPFunc, reposi
systemdService := analyzerLog.NewSystemd(config.BinPath.Journalctl, journalMatches, logger)
filesService := analyzerLog.NewFileMonitoring(files, logger)
analysisService := analyzerLog.NewAnalysis(rulesIndex, blockIPFunc, repositories, logger, notify)
analysisService := analyzerLog.NewAnalysis(rulesIndex, blockService, repositories, logger, notify)
return &analyzer{
config: config,
@@ -0,0 +1,34 @@
package brute_force_protection
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
type Block interface {
PortsBlocked() (bool, []types.L4Port)
}
type block struct {
shouldPortsBlocked bool
ports []types.L4Port
}
func NewBlockOnceIPConfig() Block {
return &block{
shouldPortsBlocked: false,
ports: nil,
}
}
func NewBlockIPAndPortsConfig(ports []types.L4Port) Block {
return &block{
shouldPortsBlocked: true,
ports: ports,
}
}
func (b *block) PortsBlocked() (bool, []types.L4Port) {
if !b.shouldPortsBlocked {
return false, nil
}
return true, b.ports
}
@@ -20,6 +20,7 @@ type RateLimit struct {
Count uint32
Period uint32
BlockingTimeSeconds uint32
BlockConfig Block
}
type PatternValue struct {
+2 -2
View File
@@ -22,13 +22,13 @@ type analysis struct {
bruteForceProtectionService analysisServices.BruteForceProtection
}
func NewAnalysis(rulesIndex *analysisServices.RulesIndex, blockIPFunc analysisServices.BlockIPFunc, repositories db.Repositories, logger log.Logger, notify notifications.Notifications) Analysis {
func NewAnalysis(rulesIndex *analysisServices.RulesIndex, blockService brute_force_protection_group.BlockService, repositories db.Repositories, logger log.Logger, notify notifications.Notifications) Analysis {
alertGroupService := alert_group.NewGroup(repositories.AlertGroup(), logger)
bruteForceProtectionGroupService := brute_force_protection_group.NewGroup(repositories.BruteForceProtectionGroup(), logger)
return &analysis{
alertService: analysisServices.NewAlert(rulesIndex, alertGroupService, logger, notify),
bruteForceProtectionService: analysisServices.NewBruteForceProtection(rulesIndex, bruteForceProtectionGroupService, blockIPFunc, logger, notify),
bruteForceProtectionService: analysisServices.NewBruteForceProtection(rulesIndex, bruteForceProtectionGroupService, blockService, logger, notify),
}
}
@@ -3,11 +3,13 @@ package analysis
import (
"fmt"
"net"
"strings"
"time"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/brute_force_protection"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/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"
"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"
@@ -18,12 +20,10 @@ type BruteForceProtection interface {
ClearDBData() error
}
type BlockIPFunc func(blockIP blocking.BlockIP) (bool, error)
type bruteForceProtection struct {
rulesIndex *RulesIndex
groupService brute_force_protection_group.Group
blockIP BlockIPFunc
blockService brute_force_protection_group.BlockService
logger log.Logger
notify notifications.Notifications
}
@@ -38,17 +38,18 @@ 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
}
func NewBruteForceProtection(rulesIndex *RulesIndex, groupService brute_force_protection_group.Group, blockIP BlockIPFunc, logger log.Logger, notify notifications.Notifications) BruteForceProtection {
func NewBruteForceProtection(rulesIndex *RulesIndex, groupService brute_force_protection_group.Group, blockService brute_force_protection_group.BlockService, logger log.Logger, notify notifications.Notifications) BruteForceProtection {
return &bruteForceProtection{
rulesIndex: rulesIndex,
groupService: groupService,
blockIP: blockIP,
blockService: blockService,
logger: logger,
notify: notify,
}
@@ -81,29 +82,35 @@ func (p *bruteForceProtection) Analyze(entry *Entry) {
continue
}
blockIP := blocking.BlockIP{
IP: result.ip,
TimeSeconds: groupResult.BlockSec,
Reason: rule.Message,
}
isBanned, err := p.blockIP(blockIP)
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,
})
ipWithPorts, l4Ports := groupResult.BlockConfig.PortsBlocked()
if !ipWithPorts {
p.handleBlockIP(entry, rule, &result, &groupResult)
continue
}
p.logger.Info(fmt.Sprintf("Block IP %s detected (%s) (group:%s): %s", result.ip, rule.Name, rule.Group.Name, entry.Message))
p.sendNotify(&bruteForceProtectionNotify{
p.handleBlockIPWithPorts(entry, rule, &result, &groupResult, l4Ports)
}
}
func (p *bruteForceProtection) ClearDBData() error {
return p.groupService.ClearDBData()
}
func (p *bruteForceProtection) handleBlockIP(
entry *Entry,
rule *brute_force_protection.Rule,
result *bruteForceProtectionAnalyzeRuleReturn,
groupResult *brute_force_protection_group.AnalysisResult,
) {
blockIP := blocking.BlockIP{
IP: result.ip,
TimeSeconds: groupResult.BlockSec,
Reason: rule.Message,
}
isBanned, err := p.blockService.BlockIP(blockIP)
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,
@@ -112,11 +119,61 @@ func (p *bruteForceProtection) Analyze(entry *Entry) {
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,
})
}
func (p *bruteForceProtection) ClearDBData() error {
return p.groupService.ClearDBData()
func (p *bruteForceProtection) handleBlockIPWithPorts(
entry *Entry,
rule *brute_force_protection.Rule,
result *bruteForceProtectionAnalyzeRuleReturn,
groupResult *brute_force_protection_group.AnalysisResult,
l4Ports []types.L4Port,
) {
blockIPWithPorts := blocking.BlockIPWithPorts{
IP: result.ip,
TimeSeconds: groupResult.BlockSec,
Reason: rule.Message,
Ports: l4Ports,
}
isBanned, err := p.blockService.BlockIPWithPorts(blockIPWithPorts)
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,
})
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,
})
}
func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, message string) bruteForceProtectionAnalyzeRuleReturn {
@@ -171,19 +228,45 @@ func (p *bruteForceProtection) analyzeRule(rule *brute_force_protection.Rule, me
return result
}
func (p *bruteForceProtection) sendNotify(notify *bruteForceProtectionNotify) {
func (p *bruteForceProtection) sendNotifySuccess(notify *bruteForceProtectionNotify) {
if !notify.rule.IsNotification {
return
}
groupName := notify.rule.Group.Name
groupMessage := notify.rule.Group.Message + "\n\n"
subject := i18n.Lang.T("alert.bruteForceProtection.subject", map[string]any{
"Name": notify.rule.Name,
"GroupName": groupName,
"IP": notify.ip,
})
p.sendNotify(subject, notify)
}
func (p *bruteForceProtection) sendNotifyError(notify *bruteForceProtectionNotify) {
if !notify.rule.IsNotification {
return
}
groupName := notify.rule.Group.Name
subject := i18n.Lang.T("alert.bruteForceProtection.subject-error", map[string]any{
"Name": notify.rule.Name,
"GroupName": groupName,
"IP": notify.ip,
})
p.sendNotify(subject, notify)
}
func (p *bruteForceProtection) sendNotify(subject string, notify *bruteForceProtectionNotify) {
if !notify.rule.IsNotification {
return
}
groupMessage := notify.rule.Group.Message + "\n\n"
text := subject + "\n\n" + groupMessage + notify.rule.Message + "\n\n"
if notify.err != nil {
text += i18n.Lang.T("alert.bruteForceProtection.error", map[string]any{
@@ -191,6 +274,15 @@ func (p *bruteForceProtection) sendNotify(notify *bruteForceProtectionNotify) {
}) + "\n"
}
text += "IP: " + notify.ip.String() + "\n"
if len(notify.ports) > 0 {
var ports []string
for _, port := range notify.ports {
ports = append(ports, port.ToString())
}
text += i18n.Lang.T("ports", map[string]any{
"Ports": strings.Join(ports, ", "),
}) + "\n"
}
text += i18n.Lang.T("blockSec", map[string]any{
"BlockSec": notify.blockSec,
}) + "\n"
@@ -206,36 +298,3 @@ func (p *bruteForceProtection) sendNotify(notify *bruteForceProtectionNotify) {
}
p.notify.SendAsync(notifications.Message{Subject: subject, Body: text})
}
func (p *bruteForceProtection) sendNotifyError(notify *bruteForceProtectionNotify) {
if !notify.rule.IsNotification {
return
}
groupName := notify.rule.Group.Name
groupMessage := notify.rule.Group.Message + "\n\n"
subject := i18n.Lang.T("alert.bruteForceProtection.subject-error", map[string]any{
"Name": notify.rule.Name,
"GroupName": groupName,
"IP": notify.ip,
})
text := subject + "\n\n" + groupMessage + notify.rule.Message + "\n\n"
if notify.err != nil {
text += i18n.Lang.T("alert.bruteForceProtection.error", map[string]any{
"Error": notify.err.Error(),
}) + "\n"
}
text += "IP: " + notify.ip.String() + "\n"
text += i18n.Lang.T("time", map[string]any{
"Time": notify.time,
}) + "\n"
for _, field := range notify.fields {
text += fmt.Sprintf("%s: %s\n", field.name, field.value)
}
text += "\n" + i18n.Lang.T("log") + "\n"
for _, message := range notify.messages {
text += message + "\n"
}
p.notify.SendAsync(notifications.Message{Subject: subject, Body: text})
}
@@ -0,0 +1,31 @@
package brute_force_protection_group
import "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/blocking"
type BlockService interface {
BlockIP(blockIP blocking.BlockIP) (bool, error)
BlockIPWithPorts(blockIP blocking.BlockIPWithPorts) (bool, error)
}
type BlockIPFunc func(blockIP blocking.BlockIP) (bool, error)
type BlockIPWithPortsFunc func(blockIP blocking.BlockIPWithPorts) (bool, error)
type blockService struct {
blockIPFunc BlockIPFunc
blockIPWithPortsFunc BlockIPWithPortsFunc
}
func NewBlockService(blockIPFunc BlockIPFunc, blockIPWithPortsFunc BlockIPWithPortsFunc) BlockService {
return &blockService{
blockIPFunc: blockIPFunc,
blockIPWithPortsFunc: blockIPWithPortsFunc,
}
}
func (b *blockService) BlockIP(blockIP blocking.BlockIP) (bool, error) {
return b.blockIPFunc(blockIP)
}
func (b *blockService) BlockIPWithPorts(blockIP blocking.BlockIPWithPorts) (bool, error) {
return b.blockIPWithPortsFunc(blockIP)
}
@@ -23,9 +23,10 @@ type group struct {
}
type AnalysisResult struct {
Block bool
BlockSec uint32
LastLogs []string
Block bool
BlockSec uint32
BlockConfig brute_force_protection.Block
LastLogs []string
}
func NewGroup(groupRepository repository.BruteForceProtectionGroupRepository, logger log.Logger) Group {
@@ -99,6 +100,7 @@ func (g *group) analysisResult(rateLimit brute_force_protection.RateLimit, event
analysisResult.LastLogs = entityGroup.LastLogs
analysisResult.Block = true
analysisResult.BlockSec = rateLimit.BlockingTimeSeconds
analysisResult.BlockConfig = rateLimit.BlockConfig
entityGroup.CurrentLevelTriggerCount++
entityGroup.TriggerCount = 0
+222
View File
@@ -0,0 +1,222 @@
package blocklist
import (
"context"
"fmt"
"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/log"
)
type newBlocklist func(name string) (block.Blocklist, error)
type Blocklist interface {
NftReload(newBlocklist newBlocklist) error
Run()
Close() error
}
type updateSource struct {
forcedly bool
source *SourceConfig
}
type blocklist struct {
Sources []*SourceConfig
blocklistRepository repository.BlocklistRepository
logger log.Logger
ctx context.Context
cancel context.CancelFunc
wg sync.WaitGroup
nftBlocklists map[string]block.Blocklist
mu sync.Mutex
launchChannel chan updateSource
}
func New(config Config, ctx context.Context, logger log.Logger) (Blocklist, error) {
return &blocklist{
Sources: config.Sources,
blocklistRepository: config.BlocklistRepository,
logger: logger,
ctx: ctx,
nftBlocklists: map[string]block.Blocklist{},
mu: sync.Mutex{},
launchChannel: make(chan updateSource, 50),
}, nil
}
func (b *blocklist) NftReload(newBlocklist newBlocklist) 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[source.Name] = nftBlocklist
b.mu.Unlock()
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))
}
}
}
return nil
}
func (b *blocklist) Run() {
b.logger.Debug("Starting blocklist")
if b.cancel != nil {
// already started
b.logger.Warn("Blocklist already started")
return
}
b.ctx, b.cancel = context.WithCancel(b.ctx)
go b.processUpdateData(b.ctx)
for _, src := range b.Sources {
if src == nil || src.Name == "" {
continue
}
interval := src.Interval
if interval <= 0 {
interval = 5 * time.Minute // дефолт
}
b.wg.Add(1)
go b.runSourceWorker(src, interval)
}
}
func (b *blocklist) runSourceWorker(sourceConfig *SourceConfig, interval time.Duration) {
defer b.wg.Done()
b.launchChannel <- updateSource{
forcedly: false,
source: sourceConfig,
}
ticker := time.NewTicker(interval)
defer ticker.Stop()
for {
select {
case <-b.ctx.Done():
b.logger.Debug(fmt.Sprintf("source %s stopped", sourceConfig.Name))
return
case <-ticker.C:
b.logger.Debug(fmt.Sprintf("source %s tick", sourceConfig.Name))
b.launchChannel <- updateSource{
forcedly: true,
source: sourceConfig,
}
}
}
}
func (b *blocklist) processUpdateData(ctx context.Context) {
for {
select {
case <-ctx.Done():
return
case updSource, ok := <-b.launchChannel:
if !ok {
// Channel closed
return
}
if updSource.forcedly {
b.refreshSource(updSource.source)
continue
}
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) {
b.logger.Debug(fmt.Sprintf("blocklist %s is fresh", updSource.source.Name))
continue
}
b.refreshSource(updSource.source)
}
}
}
func (b *blocklist) refreshSource(sourceConfig *SourceConfig) {
ipsV4, ipsV6, err := sourceConfig.Source.Get()
if err != nil {
b.logger.Error(fmt.Sprintf("Failed to get IPs from source %s: %s", sourceConfig.Name, err))
return
}
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 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 := b.blocklistRepository.Update(sourceConfig.Name, listEntity); err != nil {
b.logger.Error(fmt.Sprintf("Failed to update blocklist %s: %s", sourceConfig.Name, err))
}
} else {
b.logger.Error(fmt.Sprintf("NFTables sets blocklist %s not found", sourceConfig.Name))
return
}
b.logger.Debug(fmt.Sprintf("refresh blocklist from %s", sourceConfig.Name))
}
func (b *blocklist) Close() error {
b.logger.Debug("Stopping blocklist")
if b.cancel != nil {
b.cancel()
b.wg.Wait()
b.cancel = nil
}
close(b.launchChannel)
return nil
}
+19
View File
@@ -0,0 +1,19 @@
package blocklist
import (
"time"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist/sources"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/repository"
)
type Config struct {
BlocklistRepository repository.BlocklistRepository
Sources []*SourceConfig
}
type SourceConfig struct {
Name string
Interval time.Duration
Source sources.BlocklistSource
}
@@ -0,0 +1,18 @@
package blocklist
type FalseBlocklist struct {
}
func NewFalseBlocklist() Blocklist {
return &FalseBlocklist{}
}
func (b *FalseBlocklist) NftReload(_ newBlocklist) error {
return nil
}
func (b *FalseBlocklist) Run() {}
func (b *FalseBlocklist) Close() error {
return nil
}
@@ -0,0 +1,46 @@
package sources
import (
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
)
type BlocklistSource interface {
Get() (ipV4 parser.IPs, ipV6 parser.IPs, err error)
}
type blocklistSource struct {
url string
parser parser.Parser
config blocklist.Config
}
func NewBlocklistSource(url string, parser parser.Parser, config blocklist.Config) BlocklistSource {
return &blocklistSource{
url: url,
parser: parser,
config: config,
}
}
func (b *blocklistSource) Get() (ipV4 parser.IPs, ipV6 parser.IPs, err error) {
return blocklist.GetSeparatedIPs(b.url, b.parser, b.config)
}
type blocklistSourceZip struct {
url string
parser parser.Parser
config blocklist.ConfigZip
}
func NewBlocklistSourceZip(url string, parser parser.Parser, config blocklist.ConfigZip) BlocklistSource {
return &blocklistSourceZip{
url: url,
parser: parser,
config: config,
}
}
func (b *blocklistSourceZip) Get() (ipV4 parser.IPs, ipV6 parser.IPs, err error) {
return blocklist.GetZipSeparatedIPs(b.url, b.parser, b.config)
}
+142 -3
View File
@@ -4,16 +4,23 @@ import (
"context"
"errors"
"fmt"
"net"
"strconv"
"strings"
"time"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer"
"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"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/blocking"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/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"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
type Daemon interface {
@@ -29,6 +36,7 @@ type daemon struct {
notifications notifications.Notifications
analyzer analyzer.Analyzer
docker docker_monitor.Docker
blocklist blocklist.Blocklist
stopCh chan struct{}
}
@@ -77,6 +85,11 @@ func (d *daemon) Run(ctx context.Context, isTesting bool, testingInterval uint16
}()
}
d.blocklist.Run()
defer func() {
_ = d.blocklist.Close()
}()
go d.socket.Run(ctx, d.socketCommand)
d.runWorker(ctx, isTesting, testingInterval)
@@ -128,7 +141,7 @@ func (d *daemon) runWorker(ctx context.Context, isTesting bool, testingInterval
}
}
func (d *daemon) socketCommand(command string, socket socket.Connect) error {
func (d *daemon) socketCommand(command string, args map[string]string, socket socket.Connect) error {
switch command {
case "stop":
d.stopCh <- struct{}{}
@@ -150,9 +163,50 @@ func (d *daemon) socketCommand(command string, socket socket.Connect) error {
return err
}
return socket.Write("ok")
case "ban_clear":
case "block_add_ip":
if args["ip"] == "" {
return socket.Write("ip argument is required")
}
ipAddr := net.ParseIP(args["ip"])
if ipAddr == nil {
_ = socket.Write("invalid ip address")
return errors.New("invalid ip address")
}
port := args["port"]
if port != "" {
if err := d.cmdBlockAddIPWithPort(ipAddr, port, args); err != nil {
_ = socket.Write("block add failed: " + err.Error())
return err
}
} else {
if err := d.cmdBlockAddIP(ipAddr, args); err != nil {
_ = socket.Write("block add failed: " + err.Error())
return err
}
}
return socket.Write("ok")
case "block_delete_ip":
if args["ip"] == "" {
return socket.Write("ip argument is required")
}
ipAddr := net.ParseIP(args["ip"])
if ipAddr == nil {
_ = socket.Write("invalid ip address")
return errors.New("invalid ip address")
}
if err := d.firewall.UnblockIP(ipAddr); err != nil {
_ = socket.Write("block delete failed: " + err.Error())
return err
}
return socket.Write("ok")
case "block_clear":
if err := d.firewall.UnblockAllIPs(); err != nil {
_ = socket.Write("ban clear failed: " + err.Error())
_ = socket.Write("block clear failed: " + err.Error())
return err
}
return socket.Write("ok")
@@ -161,3 +215,88 @@ func (d *daemon) socketCommand(command string, socket socket.Connect) error {
return errors.New("unknown command")
}
}
func (d *daemon) cmdBlockAddIP(ip net.IP, args map[string]string) error {
blockIP := blocking.BlockIP{
IP: ip,
}
if args["seconds"] != "" {
seconds, err := strconv.Atoi(args["seconds"])
if err != nil {
return err
}
blockIP.TimeSeconds = uint32(seconds)
}
if args["reason"] != "" {
blockIP.Reason = args["reason"]
}
isBlock, err := d.firewall.BlockIP(blockIP)
if err != nil {
return err
}
if !isBlock {
return errors.New("the IP address is not blocked")
}
return nil
}
func (d *daemon) cmdBlockAddIPWithPort(ip net.IP, port string, args map[string]string) error {
l4Port, err := newL4PortFromString(port)
if err != nil {
return err
}
blockIP := blocking.BlockIPWithPorts{
IP: ip,
Ports: []types.L4Port{l4Port},
}
if args["seconds"] != "" {
seconds, err := strconv.Atoi(args["seconds"])
if err != nil {
return err
}
blockIP.TimeSeconds = uint32(seconds)
}
if args["reason"] != "" {
blockIP.Reason = args["reason"]
}
isBlock, err := d.firewall.BlockIPWithPorts(blockIP)
if err != nil {
return err
}
if !isBlock {
return errors.New("the IP address is not blocked")
}
return nil
}
func newL4PortFromString(s string) (types.L4Port, error) {
if s == "" {
return nil, errors.New("port is empty")
}
data := strings.Split(s, "/")
protocol := types.ProtocolTCP
port, err := strconv.Atoi(data[0])
if err != nil {
return nil, err
}
if err := validate.Port(port, "port"); err != nil {
return nil, err
}
if len(data) == 2 {
protocol, err = ip.ToProtocol(data[1])
if err != nil {
return nil, err
}
}
return types.NewL4Port(uint16(port), protocol)
}
+7
View File
@@ -19,6 +19,7 @@ type Repositories interface {
AlertGroup() repository.AlertGroupRepository
BruteForceProtectionGroup() repository.BruteForceProtectionGroupRepository
Blocking() repository.BlockingRepository
Blocklist() repository.BlocklistRepository
Close() error
}
@@ -28,6 +29,7 @@ type repositories struct {
alertGroup repository.AlertGroupRepository
bruteForceProtectionGroup repository.BruteForceProtectionGroupRepository
blocking repository.BlockingRepository
blocklist repository.BlocklistRepository
db []*bbolt.DB
}
@@ -57,6 +59,7 @@ func New(dataDir string) (Repositories, error) {
alertGroup: repository.NewAlertGroupRepository(appDB),
bruteForceProtectionGroup: repository.NewBruteForceProtectionGroupRepository(securityDB),
blocking: repository.NewBlockingRepository(securityDB),
blocklist: repository.NewBlocklistRepository(securityDB),
db: []*bbolt.DB{appDB, securityDB},
}, nil
@@ -78,6 +81,10 @@ func (r *repositories) Blocking() repository.BlockingRepository {
return r.blocking
}
func (r *repositories) Blocklist() repository.BlocklistRepository {
return r.blocklist
}
func (r *repositories) Close() error {
for _, db := range r.db {
_ = db.Close()
+48
View File
@@ -1,7 +1,55 @@
package entity
import (
"errors"
"fmt"
"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"
)
type Blocking struct {
IP string `json:"IP"`
Ports []BlockingPort
ExpireAtUnix int64 `json:"ExpireAtUnix"`
Reason string `json:"Reason"`
}
func (b *Blocking) IsPorts() bool {
return len(b.Ports) > 0
}
func (b *Blocking) ToL4Ports() ([]types.L4Port, error) {
if !b.IsPorts() {
return nil, fmt.Errorf("ports is empty")
}
l4Ports := make([]types.L4Port, 0, len(b.Ports))
for _, port := range b.Ports {
l4port, err := port.ToL4Port()
if err != nil {
return nil, err
}
l4Ports = append(l4Ports, l4port)
}
return l4Ports, nil
}
type BlockingPort struct {
Number uint16 `json:"Port"`
Protocol string `json:"Protocol"`
}
func (p *BlockingPort) ToL4Port() (types.L4Port, error) {
if p.Protocol == "" {
return nil, errors.New("protocol is empty")
}
protocol, err := ip.ToProtocol(p.Protocol)
if err != nil {
return nil, err
}
return types.NewL4Port(p.Number, protocol)
}
+17
View File
@@ -0,0 +1,17 @@
package entity
import (
"time"
)
type Blocklist struct {
UpdatedAtUnix int64 `json:"UpdateAtUnix"`
IPsV4 []string `json:"IPsV4"`
IPsV6 []string `json:"IPsV6"`
}
// IsFresh returns true if the blocklist is fresh.
func (b *Blocklist) IsFresh(interval time.Duration) bool {
lastUpdate := time.Unix(b.UpdatedAtUnix, 0)
return b.UpdatedAtUnix > 0 && time.Since(lastUpdate) <= interval
}
+40
View File
@@ -3,6 +3,7 @@ package repository
import (
"encoding/json"
"errors"
"net"
"time"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/db/entity"
@@ -13,6 +14,7 @@ import (
type BlockingRepository interface {
Add(blockedIP entity.Blocking) error
List(callback func(entity.Blocking) error) error
DeleteByIP(ip net.IP, callback func(entity.Blocking) error) error
DeleteExpired(limit int) (int, error)
Clear() error
}
@@ -73,6 +75,44 @@ func (r *blocking) List(callback func(entity.Blocking) error) error {
})
}
func (r *blocking) DeleteByIP(ip net.IP, callback func(entity.Blocking) error) error {
return r.db.Update(func(tx *bbolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists([]byte(r.bucket))
if err != nil {
return err
}
c := bucket.Cursor()
for k, v := c.First(); k != nil; {
blockedIP := entity.Blocking{}
err := json.Unmarshal(v, &blockedIP)
if err != nil {
return err
}
parsedBlockedIP := net.ParseIP(blockedIP.IP)
if parsedBlockedIP == nil || !parsedBlockedIP.Equal(ip) {
k, v = c.Next()
continue
}
if err := callback(blockedIP); err != nil {
return err
}
nextK, nextV := c.Next()
if err := bucket.Delete(k); err != nil {
return err
}
k = nextK
v = nextV
}
return nil
})
}
func (r *blocking) DeleteExpired(limit int) (int, error) {
if limit <= 0 {
return 0, nil
@@ -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 BlocklistRepository interface {
Get(name string) (*entity.Blocklist, error)
Update(name string, entity *entity.Blocklist) error
}
type blocklistRepository struct {
db *bbolt.DB
bucket string
}
func NewBlocklistRepository(appDB *bbolt.DB) BlocklistRepository {
return &blocklistRepository{
db: appDB,
bucket: blocklistBucket,
}
}
func (r *blocklistRepository) Get(name string) (*entity.Blocklist, error) {
blocklistEntity := &entity.Blocklist{}
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, blocklistEntity)
})
if err != nil {
return nil, err
}
return blocklistEntity, err
}
func (r *blocklistRepository) Update(name string, blocklistEntity *entity.Blocklist) 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(blocklistEntity)
if err != nil {
return err
}
return b.Put(key, data)
})
}
@@ -12,6 +12,7 @@ const (
alertGroupBucket = "alert_group"
bruteForceProtectionGroupBucket = "brute_force_protection_group"
blockingBucket = "blocking"
blocklistBucket = "blocklist"
)
func nextID(b *bbolt.Bucket) ([]byte, error) {
+135 -18
View File
@@ -10,20 +10,24 @@ 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"
"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) error
BlockIP(blockIP BlockIP) (bool, error)
NftReload(blockListIP block.ListIP, blockListIPWithPort block.ListIPWithPort) error
BlockIP(block BlockIP) (bool, error)
BlockIPWithPorts(block BlockIPWithPorts) (bool, error)
UnblockAllIPs() error
UnblockIP(ip net.IP) error
ClearDBData() error
}
type blocking struct {
blockingRepository repository.BlockingRepository
blockListIP block.ListIP
logger log.Logger
blockingRepository repository.BlockingRepository
blockListIP block.ListIP
blockListIPWithPort block.ListIPWithPort
logger log.Logger
mu sync.Mutex
}
@@ -34,6 +38,13 @@ type BlockIP struct {
Reason string
}
type BlockIPWithPorts struct {
IP net.IP
TimeSeconds uint32
Reason string
Ports []types.L4Port
}
func New(blockingRepository repository.BlockingRepository, logger log.Logger) API {
return &blocking{
blockingRepository: blockingRepository,
@@ -42,9 +53,10 @@ func New(blockingRepository repository.BlockingRepository, logger log.Logger) AP
}
}
func (b *blocking) NftReload(blockListIP block.ListIP) error {
func (b *blocking) NftReload(blockListIP block.ListIP, blockListIPWithPort block.ListIPWithPort) error {
b.mu.Lock()
b.blockListIP = blockListIP
b.blockListIPWithPort = blockListIPWithPort
b.mu.Unlock()
isExpiredEntries := false
@@ -56,16 +68,29 @@ func (b *blocking) NftReload(blockListIP block.ListIP) error {
return nil
}
banSeconds := uint32(0)
blockSeconds := uint32(0)
if e.ExpireAtUnix > 0 {
if e.ExpireAtUnix < nowUnix {
isExpiredEntries = true
return nil
}
banSeconds = uint32(e.ExpireAtUnix - nowUnix)
blockSeconds = uint32(e.ExpireAtUnix - nowUnix)
}
if err := b.blockListIP.AddIP(ip, banSeconds); err != nil {
if e.IsPorts() {
l4Ports, err := e.ToL4Ports()
if err != nil {
b.logger.Error(fmt.Sprintf("Failed to parse ports: %s", err))
return nil
}
if err := b.blockListIPWithPort.AddIP(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 {
b.logger.Error(fmt.Sprintf("Failed to add IP %s to block list: %s", ip.String(), err))
return nil
}
@@ -86,38 +111,117 @@ func (b *blocking) NftReload(blockListIP block.ListIP) error {
return err
}
func (b *blocking) BlockIP(blockIP BlockIP) (bool, error) {
if blockIP.IP.IsLoopback() {
return false, fmt.Errorf("loopback IP address %s cannot be blocked", blockIP.IP.String())
func (b *blocking) BlockIP(block BlockIP) (bool, error) {
if block.IP.IsLoopback() {
return false, fmt.Errorf("loopback IP address %s cannot be blocked", block.IP.String())
}
if err := b.blockListIP.AddIP(blockIP.IP, blockIP.TimeSeconds); err != nil {
if err := b.blockListIP.AddIP(block.IP, block.TimeSeconds); err != nil {
return false, err
}
expireAtUnix := int64(0)
if blockIP.TimeSeconds > 0 {
expire := time.Now().Add(time.Duration(int64(blockIP.TimeSeconds)) * time.Second)
if block.TimeSeconds > 0 {
expire := time.Now().Add(time.Duration(int64(block.TimeSeconds)) * time.Second)
expireAtUnix = expire.Unix()
}
data := entity.Blocking{
IP: blockIP.IP.String(),
IP: block.IP.String(),
ExpireAtUnix: expireAtUnix,
Reason: blockIP.Reason,
Reason: block.Reason,
}
if err := b.blockingRepository.Add(data); err != nil {
return true, fmt.Errorf("the IP is blocked, but not recorded in the database. Failed to add IP %s to database: %w", blockIP.IP.String(), err)
return true, fmt.Errorf("the IP is blocked, but not recorded in the database. Failed to add IP %s to database: %w", block.IP.String(), err)
}
return true, nil
}
func (b *blocking) BlockIPWithPorts(block BlockIPWithPorts) (bool, error) {
if block.IP.IsLoopback() {
return false, fmt.Errorf("loopback IP address %s cannot be blocked", block.IP.String())
}
if err := b.blockListIPWithPort.AddIP(block.IP, block.Ports, block.TimeSeconds); err != nil {
return false, err
}
var l4Ports []entity.BlockingPort
for _, port := range block.Ports {
l4Ports = append(l4Ports, entity.BlockingPort{
Number: port.Number(),
Protocol: port.ProtocolString(),
})
}
expireAtUnix := int64(0)
if block.TimeSeconds > 0 {
expire := time.Now().Add(time.Duration(int64(block.TimeSeconds)) * time.Second)
expireAtUnix = expire.Unix()
}
data := entity.Blocking{
IP: block.IP.String(),
ExpireAtUnix: expireAtUnix,
Reason: block.Reason,
Ports: l4Ports,
}
if err := b.blockingRepository.Add(data); err != nil {
return true, fmt.Errorf("the IP is blocked, but not recorded in the database. Failed to add IP %s to database: %w", block.IP.String(), err)
}
return true, nil
}
func (b *blocking) UnblockIP(ip net.IP) error {
err := b.blockingRepository.DeleteByIP(ip, func(e entity.Blocking) error {
if e.IsPorts() {
l4Ports, err := e.ToL4Ports()
if err != nil {
return err
}
return b.removeIPWithPorts(ip, l4Ports)
}
if err := b.blockListIP.DeleteIP(ip); err != nil {
if strings.Contains(err.Error(), "element does not exist") {
return nil
}
return err
}
return nil
})
if err != nil {
return err
}
return nil
}
func (b *blocking) UnblockAllIPs() error {
err := b.blockingRepository.List(func(e entity.Blocking) error {
ip := net.ParseIP(e.IP)
if ip == nil {
return fmt.Errorf("failed to parse IP address: %s", e.IP)
}
if e.IsPorts() {
l4Ports, err := e.ToL4Ports()
if err != nil {
return err
}
for _, port := range l4Ports {
if err := b.blockListIPWithPort.DeleteIP(ip, port); err != nil {
if strings.Contains(err.Error(), "element does not exist") ||
strings.Contains(err.Error(), "Error: Could not process rule: No such file or directory") {
continue
}
return err
}
}
}
if err := b.blockListIP.DeleteIP(ip); err != nil {
if strings.Contains(err.Error(), "element does not exist") {
return nil
@@ -138,3 +242,16 @@ func (b *blocking) UnblockAllIPs() error {
func (b *blocking) ClearDBData() error {
return b.blockingRepository.Clear()
}
func (b *blocking) removeIPWithPorts(ip net.IP, l4Ports []types.L4Port) error {
for _, port := range l4Ports {
if err := b.blockListIPWithPort.DeleteIP(ip, port); err != nil {
if strings.Contains(err.Error(), "element does not exist") ||
strings.Contains(err.Error(), "Error: Could not process rule: No such file or directory") {
continue
}
return err
}
}
return nil
}
@@ -0,0 +1,65 @@
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
}
@@ -2,6 +2,7 @@ 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"
@@ -11,6 +12,7 @@ type List interface {
Name() string
AddElement(element string) error
DeleteElement(element string) error
ReplaceElements(elements []string) error
}
type list struct {
@@ -57,3 +59,43 @@ func (l *list) DeleteElement(element string) error {
}
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
}
@@ -55,10 +55,10 @@ func (l *listIP) AddIP(addr net.IP, banSeconds uint32) error {
element := strings.Join(el, " ")
if addr.To4() != nil {
return l.listIPv4.AddElement(fmt.Sprintf("%s", element))
return l.listIPv4.AddElement(element)
}
return l.listIPv6.AddElement(fmt.Sprintf("%s", element))
return l.listIPv6.AddElement(element)
}
func (l *listIP) DeleteIP(addr net.IP) error {
@@ -0,0 +1,102 @@
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"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/types"
)
type ListIPWithPort interface {
// AddIP Add an IP address to the list.
AddIP(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
}
type listIPWithPort struct {
listIPv4 List
listIPv6 List
}
func NewListIPWithPort(nft nft.NFT, family family.Type, table string, name string) (ListIPWithPort, error) {
params := "type ipv4_addr . inet_proto . inet_service; flags interval, timeout;"
listName := name + "_ip4"
listIPv4, err := newList(nft, family, table, listName, 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)
if err != nil {
return nil, err
}
return &listIPWithPort{
listIPv4: listIPv4,
listIPv6: listIPv6,
}, nil
}
func (l *listIPWithPort) AddIP(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.AddElement(element)
}
return l.listIPv6.AddElement(element)
}
func (l *listIPWithPort) DeleteIP(addr net.IP, port types.L4Port) error {
if addr == nil {
return fmt.Errorf("IP address cannot be nil")
}
if port.ToString() == "" {
return fmt.Errorf("port cannot be empty")
}
element := fmt.Sprintf("%s . %s . %d", addr.String(), port.ProtocolString(), port.Number())
if addr.To4() != nil {
return l.listIPv4.DeleteElement(element)
}
return l.listIPv6.DeleteElement(element)
}
func (l *listIPWithPort) AddRuleToChain(chainAddRuleFunc func(expr ...string) error, action string) error {
rule := "ip saddr . meta l4proto . th dport @" + l.listIPv4.Name() + " " + action
if err := chainAddRuleFunc(rule); err != nil {
return err
}
rule = "ip6 saddr . meta l4proto . th dport @" + l.listIPv6.Name() + " " + action
if err := chainAddRuleFunc(rule); err != nil {
return err
}
return nil
}
@@ -0,0 +1,20 @@
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
}
+34
View File
@@ -42,6 +42,9 @@ type Chains interface {
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 {
@@ -231,6 +234,37 @@ func (c *chains) NewBlockListIP(name string) (block.ListIP, error) {
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) {
@@ -0,0 +1,91 @@
package chain
import (
"strconv"
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"
"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/types"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
)
type PortKnocking interface {
AddFirstStageRule(
name string,
ipVersion ip.Version,
l4Port types.L4Port,
timeout uint32,
action types.KnockAction,
) error
AddNextStageRule(
prevName, nextName string,
ipVersion ip.Version,
l4Port types.L4Port,
timeout uint32,
action types.KnockAction,
) error
AddRuleIn(AddRuleFunc func(expr ...string) error) error
}
type portKnocking struct {
nft nft.NFT
family family.Type
table string
chain string
}
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 {
return nil, err
}
return &portKnocking{
nft: nft,
family: family,
table: table,
chain: chain,
}, nil
}
func (k *portKnocking) AddRuleIn(AddRuleFunc func(expr ...string) error) error {
return AddRuleFunc("iifname != \"lo\" counter jump " + k.chain)
}
func (k *portKnocking) AddFirstStageRule(
name string,
ipVersion ip.Version,
l4Port types.L4Port,
timeout uint32,
action types.KnockAction,
) error {
if err := block.NewPortKnocking(k.nft, k.family, k.table, name, ipVersion, timeout); err != nil {
return err
}
expr := []string{
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...)
}
func (k *portKnocking) AddNextStageRule(
prevName, nextName string,
ipVersion ip.Version,
l4Port types.L4Port,
timeout uint32,
action types.KnockAction,
) error {
if err := block.NewPortKnocking(k.nft, k.family, k.table, nextName, ipVersion, timeout); err != nil {
return err
}
expr := []string{
ipVersion.ToNft(), "saddr", "@" + prevName,
l4Port.ProtocolString(), "dport", l4Port.NumberString(), "add", "@" + nextName,
"{", ipVersion.ToNft(), "saddr}", action.String(),
}
return k.nft.Rule().Add(k.family, k.table, k.chain, expr...)
}
+19 -81
View File
@@ -1,7 +1,8 @@
package firewall
import (
"fmt"
"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"
)
type Config struct {
@@ -12,6 +13,7 @@ type Config struct {
Options ConfigOptions
MetadataNaming ConfigMetadata
Policy ConfigPolicy
PortKnocking []ConfigPortKnocking
}
type ConfigOptions struct {
@@ -35,32 +37,14 @@ type ConfigPolicy struct {
DefaultAllowInput bool
DefaultAllowOutput bool
DefaultAllowForward bool
InputDrop PolicyDrop
InputDrop types.PolicyDrop
InputPriority int
OutputDrop PolicyDrop
OutputDrop types.PolicyDrop
OutputPriority int
ForwardDrop PolicyDrop
ForwardDrop types.PolicyDrop
ForwardPriority int
}
type PolicyDrop int8
const (
Drop PolicyDrop = iota + 1
Reject
)
func (p PolicyDrop) String() string {
switch p {
case Drop:
return "drop"
case Reject:
return "reject"
default:
return "drop"
}
}
type ConfigIP4 struct {
IcmpIn bool
IcmpInRate string
@@ -79,76 +63,30 @@ type ConfigIP6 struct {
}
type ConfigPort struct {
Number uint16
Protocol Protocol
Action Action
Port types.L4Port
Action types.Action
LimitRate string
}
type ConfigIP struct {
IP string
OnlyIP bool // Port is not taken into account
Port uint16
Action Action
Protocol Protocol
Port types.L4Port
Action types.Action
LimitRate string
}
type Action int8
const (
ActionAccept Action = iota + 1
ActionReject
ActionDrop
)
func (a Action) String() string {
switch a {
case ActionAccept:
return "accept"
case ActionReject:
return "reject"
case ActionDrop:
return "drop"
default:
return "drop"
}
type ConfigPortKnocking struct {
Name string
Port types.L4Port
IPVersion ip.Version
Knocks []*ConfigKnock
}
type Protocol int8
const (
ProtocolTCP Protocol = iota + 1
ProtocolUDP
)
func (p Protocol) String() string {
switch p {
case ProtocolTCP:
return "tcp"
case ProtocolUDP:
return "udp"
default:
return fmt.Sprintf("Protocol(%d)", p)
}
}
type Direction int8
const (
DirectionIn Direction = iota + 1
DirectionOut
)
func (d Direction) String() string {
switch d {
case DirectionIn:
return "in"
case DirectionOut:
return "out"
default:
return fmt.Sprintf("Direction(%d)", d)
}
type ConfigKnock struct {
Port types.L4Port
Action types.KnockAction
Timeout uint32
}
type ClearMode int8
+35 -1
View File
@@ -2,8 +2,10 @@ package firewall
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"
@@ -25,9 +27,15 @@ type API interface {
// BlockIP Block IP address.
BlockIP(blockIP blocking.BlockIP) (bool, error)
// BlockIPWithPorts Block IP address with ports.
BlockIPWithPorts(blockIP blocking.BlockIPWithPorts) (bool, error)
// UnblockAllIPs Unblock all IP addresses.
UnblockAllIPs() error
// UnblockIP Unblock IP address.
UnblockIP(ip net.IP) error
// ClearDBData Clear all data from DB
ClearDBData() error
@@ -42,9 +50,17 @@ type firewall struct {
blockingService blocking.API
chains chain.Chains
docker docker_monitor.Docker
blocklist blocklist.Blocklist
}
func New(pathNFT string, blockingService blocking.API, logger log.Logger, config Config, docker docker_monitor.Docker) (API, error) {
func New(
pathNFT string,
blockingService blocking.API,
logger log.Logger,
config Config,
docker docker_monitor.Docker,
blocklist blocklist.Blocklist,
) (API, error) {
nft, err := nftables.NewWithPath(pathNFT)
if err != nil {
return nil, fmt.Errorf("failed to create nft client: %w %s", err, pathNFT)
@@ -56,6 +72,7 @@ func New(pathNFT string, blockingService blocking.API, logger log.Logger, config
config: &config,
blockingService: blockingService,
docker: docker,
blocklist: blocklist,
}, nil
}
@@ -99,6 +116,10 @@ func (f *firewall) Reload() error {
return err
}
if err := f.blocklist.NftReload(f.chains.NewBlocklist); err != nil {
f.logger.Error(fmt.Sprintf("Failed to reload blocklist: %s", err))
}
f.logger.Debug("Reload nftables rules done")
return nil
}
@@ -126,6 +147,10 @@ func (f *firewall) UnblockAllIPs() error {
return f.blockingService.UnblockAllIPs()
}
func (f *firewall) UnblockIP(ip net.IP) error {
return f.blockingService.UnblockIP(ip)
}
func (f *firewall) ClearDBData() error {
return f.blockingService.ClearDBData()
}
@@ -167,6 +192,15 @@ func (f *firewall) BlockIP(blockIP blocking.BlockIP) (bool, error) {
return isBanned, err
}
func (f *firewall) BlockIPWithPorts(blockIP blocking.BlockIPWithPorts) (bool, error) {
isBanned, err := f.blockingService.BlockIPWithPorts(blockIP)
if err != nil {
f.logger.Warn(fmt.Sprintf("Failed to block ip %s: %s", blockIP.IP.String(), err))
}
return isBanned, err
}
func (f *firewall) DockerSupport() bool {
return f.config.Options.DockerSupport
}
+11 -3
View File
@@ -1,15 +1,23 @@
package firewall
func (f *firewall) reloadBlockList() error {
listBan, err := f.chains.NewBlockListIP("ban")
listBlockedIP, err := f.chains.NewBlockListIP("blocked_ip")
if err != nil {
return err
}
if err := listBan.AddRuleToChain(f.chains.BeforeLocalInput().AddRule, "drop"); err != nil {
if err := listBlockedIP.AddRuleToChain(f.chains.BeforeLocalInput().AddRule, "drop"); err != nil {
return err
}
if err := f.blockingService.NftReload(listBan); err != nil {
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
}
+4 -2
View File
@@ -1,5 +1,7 @@
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)
@@ -38,7 +40,7 @@ func (f *firewall) reloadForwardAddIPs() error {
}
for _, ipConfig := range f.config.IP4.InIPs {
if ipConfig.Action != ActionDrop && ipConfig.Action != ActionReject {
if ipConfig.Action != types.ActionDrop && ipConfig.Action != types.ActionReject {
continue
}
if err := forwardAddIP(chain.AddRule, ipConfig, "ip"); err != nil {
@@ -51,7 +53,7 @@ func (f *firewall) reloadForwardAddIPs() error {
}
for _, ipConfig := range f.config.IP6.InIPs {
if ipConfig.Action != ActionDrop && ipConfig.Action != ActionReject {
if ipConfig.Action != types.ActionDrop && ipConfig.Action != types.ActionReject {
continue
}
if err := forwardAddIP(chain.AddRule, ipConfig, "ip6"); err != nil {
+51 -4
View File
@@ -3,8 +3,8 @@ package firewall
import (
"fmt"
"net"
"strconv"
"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"
)
@@ -215,8 +215,8 @@ func (f *firewall) reloadInputICMP6Strict() error {
func (f *firewall) reloadInputPorts() error {
chain := f.chains.Input()
for _, port := range f.config.InPorts {
protocol := port.Protocol.String()
number := strconv.Itoa(int(port.Number))
protocol := port.Port.ProtocolString()
number := port.Port.NumberString()
baseRule := "iifname != \"lo\" meta l4proto " + protocol + " ct state new " + protocol + " dport " + number
@@ -248,6 +248,10 @@ func (f *firewall) reloadInputAddIPs() error {
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
@@ -266,11 +270,54 @@ func (f *firewall) reloadInputAddIPs() error {
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.Protocol.String() + " dport " + strconv.Itoa(int(config.Port))
rule += " " + config.Port.ProtocolString() + " dport " + config.Port.NumberString()
}
if config.LimitRate != "" {
rule += " limit rate " + config.LimitRate
+3 -4
View File
@@ -3,7 +3,6 @@ package firewall
import (
"fmt"
"net"
"strconv"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg"
)
@@ -176,8 +175,8 @@ func (f *firewall) reloadOutputICMPAfter() error {
func (f *firewall) reloadOutputPorts() error {
chain := f.chains.Output()
for _, port := range f.config.OutPorts {
protocol := port.Protocol.String()
number := strconv.Itoa(int(port.Number))
protocol := port.Port.ProtocolString()
number := port.Port.NumberString()
baseRule := "oifname != \"lo\" meta l4proto " + protocol + " ct state new " + protocol + " dport " + number
if port.LimitRate != "" {
@@ -231,7 +230,7 @@ func outputAddIP(addRuleFunc func(expr ...string) error, config ConfigIP, ipMatc
rule := ipMatch + " daddr " + config.IP + " oifname != \"lo\""
if !config.OnlyIP {
rule += " " + config.Protocol.String() + " dport " + strconv.Itoa(int(config.Port))
rule += " " + config.Port.ProtocolString() + " dport " + config.Port.NumberString()
}
if config.LimitRate != "" {
rule += " limit rate " + config.LimitRate
+43
View File
@@ -0,0 +1,43 @@
package types
import (
"errors"
"strconv"
)
type L4Port interface {
Number() uint16
NumberString() string
ProtocolString() string
ToString() string
}
type l4Port struct {
number uint16
protocol string
}
func NewL4Port(number uint16, protocol Protocol) (L4Port, error) {
if protocol != ProtocolTCP && protocol != ProtocolUDP {
return nil, errors.New("invalid protocol")
}
return &l4Port{number: number, protocol: protocol.String()}, nil
}
func (p *l4Port) Number() uint16 {
return p.number
}
func (p *l4Port) NumberString() string {
port := p.Number()
return strconv.Itoa(int(port))
}
func (p *l4Port) ProtocolString() string {
return p.protocol
}
func (p *l4Port) ToString() string {
return p.NumberString() + "/" + p.ProtocolString()
}
+102
View File
@@ -0,0 +1,102 @@
package types
import "fmt"
type PolicyDrop int8
const (
Drop PolicyDrop = iota + 1
Reject
)
func (p PolicyDrop) String() string {
switch p {
case Drop:
return "drop"
case Reject:
return "reject"
default:
return "drop"
}
}
type Action int8
const (
ActionAccept Action = iota + 1
ActionReject
ActionDrop
)
func (a Action) String() string {
switch a {
case ActionAccept:
return "accept"
case ActionReject:
return "reject"
case ActionDrop:
return "drop"
default:
return "drop"
}
}
type KnockAction int8
const (
KnockActionAccept KnockAction = iota + 1
KnockActionReject
KnockActionDrop
KnockActionReturn
)
func (a KnockAction) String() string {
switch a {
case KnockActionAccept:
return "accept"
case KnockActionReject:
return "reject"
case KnockActionDrop:
return "drop"
case KnockActionReturn:
return "return"
default:
return "drop"
}
}
type Protocol int8
const (
ProtocolTCP Protocol = iota + 1
ProtocolUDP
)
func (p Protocol) String() string {
switch p {
case ProtocolTCP:
return "tcp"
case ProtocolUDP:
return "udp"
default:
return fmt.Sprintf("Protocol(%d)", p)
}
}
type Direction int8
const (
DirectionIn Direction = iota + 1
DirectionOut
)
func (d Direction) String() string {
switch d {
case DirectionIn:
return "in"
case DirectionOut:
return "out"
default:
return fmt.Sprintf("Direction(%d)", d)
}
}
+20 -3
View File
@@ -4,6 +4,8 @@ import (
"errors"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer"
"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/blocklist"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/docker_monitor"
firewall2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall/blocking"
@@ -13,7 +15,13 @@ import (
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
)
func NewDaemon(opts DaemonOptions, logger log.Logger, notifications notifications.Notifications, docker docker_monitor.Docker) (Daemon, error) {
func NewDaemon(
opts DaemonOptions,
logger log.Logger,
notifications notifications.Notifications,
docker docker_monitor.Docker,
blocklist blocklist.Blocklist,
) (Daemon, error) {
if logger == nil {
return nil, errors.New("logger is nil")
}
@@ -29,9 +37,17 @@ func NewDaemon(opts DaemonOptions, logger log.Logger, notifications notification
}
blockingService := blocking.New(opts.Repositories.Blocking(), logger)
firewall, err := firewall2.New(opts.PathNftables, blockingService, logger, opts.ConfigFirewall, docker)
firewall, err := firewall2.New(
opts.PathNftables,
blockingService,
logger,
opts.ConfigFirewall,
docker,
blocklist,
)
analyzerService := analyzer.New(opts.ConfigAnalyzer, firewall.BlockIP, opts.Repositories, logger, notifications)
blockService := brute_force_protection_group.NewBlockService(firewall.BlockIP, firewall.BlockIPWithPorts)
analyzerService := analyzer.New(opts.ConfigAnalyzer, blockService, opts.Repositories, logger, notifications)
return &daemon{
pidFile: pidFile,
@@ -41,5 +57,6 @@ func NewDaemon(opts DaemonOptions, logger log.Logger, notifications notification
notifications: notifications,
analyzer: analyzerService,
docker: docker,
blocklist: blocklist,
}, nil
}
+33 -3
View File
@@ -2,6 +2,7 @@ package socket
import (
"context"
"encoding/json"
"errors"
"fmt"
"net"
@@ -11,7 +12,12 @@ import (
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
)
type HandleCommand func(command string, socket Connect) error
type Message struct {
Command string `json:"command"`
Args map[string]string `json:"args"`
}
type HandleCommand func(command string, args map[string]string, socket Connect) error
type Socket interface {
EnsureNoOtherProcess() error
@@ -121,13 +127,19 @@ func (s *socket) handleAction(conn net.Conn, handleCommand HandleCommand) {
_ = sock.Close()
}()
cmd, err := sock.Read()
raw, err := sock.Read()
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to read command: %s", err))
return
}
if err := handleCommand(cmd, sock); err != nil {
cmd, args, err := parseCommand(raw)
if err != nil {
s.logger.Error(fmt.Sprintf("Failed to parse command: %s", err))
return
}
if err := handleCommand(cmd, args, sock); err != nil {
s.logger.Error(fmt.Sprintf("Failed to handle command: %s", err))
}
}
@@ -147,3 +159,21 @@ func canConnect(path string) bool {
func isUseOfClosedNetworkError(err error) bool {
return err != nil && strings.Contains(err.Error(), "use of closed network connection")
}
func parseCommand(raw string) (string, map[string]string, error) {
var msg Message
if err := json.Unmarshal([]byte(raw), &msg); err != nil {
return "", nil, err
}
if msg.Command == "" {
return "", nil, errors.New("command is empty")
}
if msg.Args == nil {
msg.Args = map[string]string{}
}
return msg.Command, msg.Args, nil
}
+20 -6
View File
@@ -25,11 +25,22 @@
"notifications_queue_clear_error": "Failed to clear notification queue",
"notifications_queue_clear_success": "The notification queue has been cleared.",
"cmd.daemon.ban.Usage": "Ban",
"cmd.daemon.ban.clear.Usage": "Unban all banned IP addresses",
"cmd.daemon.ban.clear.Description": "Unban all banned IP addresses.",
"ban_clear_error": "Unable to unban all IP addresses",
"ban_clear_success": "The request was successfully completed",
"cmd.daemon.block.Usage": "Blocking",
"cmd.daemon.block.clear.Usage": "Unblock all banned IP addresses",
"cmd.daemon.block.clear.Description": "Unblock all banned IP addresses.",
"block_clear_error": "Unable to unblock all IP addresses",
"block_clear_success": "The request was successfully completed",
"cmd.daemon.block.add.Usage": "Add IP address to block list",
"cmd.daemon.block.add.Description": "Add an IP address to the block list. \nExamples: \nkor-elf-shield block add 192.168.1.1 \nkor-elf-shield block add 192.168.1.1 --seconds=600 \nkor-elf-shield block add 192.168.1.1 --port 80/tcp",
"cmd.daemon.block.add.FlagUsage.port": "The port to be blocked. If not specified, all ports will be blocked. \nExamples: \n--port=80/tcp \n--port=1000/udp",
"cmd.daemon.block.add.FlagUsage.seconds": "The blocking time in seconds. If not specified, the blocking will be permanent.",
"cmd.daemon.block.add.FlagUsage.reason": "Reason for blocking.",
"block_add_ip_success": "The IP address has been successfully added to the block list.",
"cmd.daemon.block.delete.Usage": "Remove IP address from block list",
"cmd.daemon.block.delete.Description": "Remove an IP address from the block list. \nExample: \nkor-elf-shield block delete 192.168.1.1",
"block_delete_ip_success": "The IP address has been successfully removed from the block list.",
"Command error": "Command error",
"invalid log level": "The log level specified in the settings is invalid. It is currently set to: {{.Level}}. Valid values: {{.Levels}}",
@@ -49,6 +60,7 @@
"access to user has been gained": "Access to user has been gained",
"unknown": "unknown",
"blockSec": "Blocked for {{.BlockSec}} seconds",
"ports": "Ports: {{.Ports}}",
"alert.subject": "Alert detected ({{.Name}}) (group:{{.GroupName}})",
"alert.login.ssh.message": "Logged into the OS via ssh.",
@@ -60,5 +72,7 @@
"alert.bruteForceProtection.subject-error": "A hacking attempt was detected, but the IP {{.IP}} is not blocked. Alert ({{.Name}}) (group:{{.GroupName}})",
"alert.bruteForceProtection.error": "Error: {{.Error}}",
"alert.bruteForceProtection.ssh.message": "An attempt to brute-force SSH was detected.",
"alert.bruteForceProtection.group._default.message": "Default group."
"alert.bruteForceProtection.group._default.message": "Default group.",
"cmd.error": "Command error: {{.Error}}"
}
+20 -6
View File
@@ -25,11 +25,22 @@
"notifications_queue_clear_error": "Хабарландыру кезегі тазаланбады",
"notifications_queue_clear_success": "Хабарландыру кезегі тазартылды",
"cmd.daemon.ban.Usage": "Тыйым салу",
"cmd.daemon.ban.clear.Usage": "Барлық тыйым салынған IP мекенжайларын алып тастау",
"cmd.daemon.ban.clear.Description": "Барлық тыйым салынған IP мекенжайларын алып тастау.",
"ban_clear_error": "Барлық IP мекенжайларын бұғаттаудан шығару мүмкін емес",
"ban_clear_success": "Сұраныс сәтті орындалды",
"cmd.daemon.block.Usage": "Бұғаттау",
"cmd.daemon.block.clear.Usage": "Барлық тыйым салынған IP мекенжайларын бұғаттан шығарыңыз",
"cmd.daemon.block.clear.Description": "Барлық тыйым салынған IP мекенжайларын бұғаттан шығарыңыз.",
"block_clear_error": "Барлық IP мекенжайларын бұғаттан шығару мүмкін емес",
"block_clear_success": "Сұраныс сәтті орындалды",
"cmd.daemon.block.add.Usage": "Блоктау тізіміне IP мекенжайын қосу",
"cmd.daemon.block.add.Description": "Блоктау тізіміне IP мекенжайын қосыңыз. \nМысалдар: \nkor-elf-shield block add 192.168.1.1 \nkor-elf-shield block add 192.168.1.1 --seconds=600 \nkor-elf-shield block add 192.168.1.1 --port 80/tcp",
"cmd.daemon.block.add.FlagUsage.port": "Блокталатын порт. Егер көрсетілмесе, барлық порттар бұғатталады. \nМысалдар: \n--port=80/tcp \n--port=1000/udp",
"cmd.daemon.block.add.FlagUsage.seconds": "Блоктау уақыты секундпен. Егер көрсетілмесе, блоктау тұрақты болады.",
"cmd.daemon.block.add.FlagUsage.reason": "Блоктау себебі.",
"block_add_ip_success": "IP мекенжайы блоктау тізіміне сәтті қосылды.",
"cmd.daemon.block.delete.Usage": "IP мекенжайын блоктау тізімінен алып тастаңыз",
"cmd.daemon.block.delete.Description": "IP мекенжайын блоктау тізімінен алып тастаңыз. \nМысал: \nkor-elf-shield block delete 192.168.1.1",
"block_delete_ip_success": "IP мекенжайы блоктау тізімінен сәтті жойылды.",
"Command error": "Командалық қате",
"invalid log level": "Параметрлерде көрсетілген журнал деңгейі жарамсыз. Ол қазір мына күйге орнатылған: {{.Level}}. Жарамды мәндер: {{.Levels}}",
@@ -49,6 +60,7 @@
"access to user has been gained": "Пайдаланушыға кіру мүмкіндігі алынды",
"unknown": "белгісіз",
"blockSec": "{{.BlockSec}} секундқа блокталды",
"ports": "Порттар: {{.Ports}}",
"alert.subject": "Ескерту анықталды ({{.Name}}) (топ:{{.GroupName}})",
"alert.login.ssh.message": "ОС-қа ssh арқылы кірді.",
@@ -60,5 +72,7 @@
"alert.bruteForceProtection.subject-error": "Хакерлік әрекет анықталды, бірақ IP мекенжайы {{.IP}} бұғатталмаған. Ескерту ({{.Name}}) (Топ:{{.GroupName}})",
"alert.bruteForceProtection.error": "Қате: {{.Error}}",
"alert.bruteForceProtection.ssh.message": "SSH-ті күштеп қолдану әрекеті анықталды.",
"alert.bruteForceProtection.group._default.message": "Әдепкі топ."
"alert.bruteForceProtection.group._default.message": "Әдепкі топ.",
"cmd.error": "Команда қатесі: {{.Error}}"
}
+20 -6
View File
@@ -25,11 +25,22 @@
"notifications_queue_clear_error": "Не удалось очистить очередь уведомлений",
"notifications_queue_clear_success": "Очередь уведомлений очищена",
"cmd.daemon.ban.Usage": "Бан",
"cmd.daemon.ban.clear.Usage": "Разбанить все забаненные IP адреса",
"cmd.daemon.ban.clear.Description": "Разбанить все забаненные IP адреса.",
"ban_clear_error": "Не смогли разбанить все IP адреса",
"ban_clear_success": "Запрос успешно выполнен",
"cmd.daemon.block.Usage": локировка",
"cmd.daemon.block.clear.Usage": "Разблокировать все забаненные IP адреса",
"cmd.daemon.block.clear.Description": "блокировка все забаненные IP адреса.",
"block_clear_error": "Не смогли разблокировать все IP адреса",
"block_clear_success": "Запрос успешно выполнен",
"cmd.daemon.block.add.Usage": "Добавить IP адрес в список заблокированных",
"cmd.daemon.block.add.Description": "Добавить IP адрес в список заблокированных. \nПримеры: \nkor-elf-shield block add 192.168.1.1 \nkor-elf-shield block add 192.168.1.1 --seconds=600 \nkor-elf-shield block add 192.168.1.1 --port 80/tcp",
"cmd.daemon.block.add.FlagUsage.port": "Порт, который будет заблокирован. Если не указать, то заблокируются все порты. \nПримеры: \n--port=80/tcp \n--port=1000/udp",
"cmd.daemon.block.add.FlagUsage.seconds": "Время блокировки в секундах. Если не указать, то блокировка будет вечной.",
"cmd.daemon.block.add.FlagUsage.reason": "Причина блокировки.",
"block_add_ip_success": "IP адрес успешно добавлен в список заблокированных.",
"cmd.daemon.block.delete.Usage": "Удалить IP адрес из списка заблокированных",
"cmd.daemon.block.delete.Description": "Удалить IP адрес из списка заблокированных. \nПример: \nkor-elf-shield block delete 192.168.1.1",
"block_delete_ip_success": "IP адрес успешно удален из списка заблокированных.",
"Command error": "Ошибка команды",
"invalid log level": "В настройках указан не верный уровень log. Сейчас указан: {{.Level}}. Допустимые значения: {{.Levels}}",
@@ -49,6 +60,7 @@
"access to user has been gained": "Получен доступ к пользователю",
"unknown": "неизвестный",
"blockSec": "Блокировка на {{.BlockSec}} секунд",
"ports": "Порты: {{.Ports}}",
"alert.subject": "Обнаружено оповещение ({{.Name}}) (группа:{{.GroupName}})",
"alert.login.ssh.message": "Вошли в ОС через ssh.",
@@ -60,5 +72,7 @@
"alert.bruteForceProtection.subject-error": "Обнаружена попытка взлома, но IP {{.IP}} не заблокирован. Оповещение ({{.Name}}) (группа:{{.GroupName}})",
"alert.bruteForceProtection.error": "Ошибка: {{.Error}}",
"alert.bruteForceProtection.ssh.message": "Обнаружена попытка атаки на SSH методом перебора паролей.",
"alert.bruteForceProtection.group._default.message": "Группа по умолчанию."
"alert.bruteForceProtection.group._default.message": "Группа по умолчанию.",
"cmd.error": "Ошибка команды: {{.Error}}"
}
+11
View File
@@ -23,6 +23,17 @@ func (v Version) ToNft() string {
}
}
func (v Version) ToNftForSet() string {
switch v {
case IPv4:
return "ipv4_addr"
case IPv6:
return "ipv6_addr"
default:
return "unknown"
}
}
func DetermineIPVersion(ip string) (ipNet string, version Version, err error) {
ipNet, version, err = parseCIDR(ip)
if err != nil {
+29 -14
View File
@@ -4,40 +4,55 @@ import (
"errors"
"strings"
"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/types"
)
func ToDirection(direction string) (firewall.Direction, error) {
func ToDirection(direction string) (types.Direction, error) {
switch strings.ToLower(direction) {
case "in":
return firewall.DirectionIn, nil
return types.DirectionIn, nil
case "out":
return firewall.DirectionOut, nil
return types.DirectionOut, nil
default:
return firewall.DirectionIn, errors.New("invalid direction. Must be in or out")
return types.DirectionIn, errors.New("invalid direction. Must be in or out")
}
}
func ToProtocol(protocol string) (firewall.Protocol, error) {
func ToProtocol(protocol string) (types.Protocol, error) {
switch strings.ToLower(protocol) {
case "tcp":
return firewall.ProtocolTCP, nil
return types.ProtocolTCP, nil
case "udp":
return firewall.ProtocolUDP, nil
return types.ProtocolUDP, nil
default:
return firewall.ProtocolTCP, errors.New("invalid protocol. Must be tcp or udp")
return types.ProtocolTCP, errors.New("invalid protocol. Must be tcp or udp")
}
}
func ToAction(action string) (firewall.Action, error) {
func ToAction(action string) (types.Action, error) {
switch strings.ToLower(action) {
case "accept":
return firewall.ActionAccept, nil
return types.ActionAccept, nil
case "drop":
return firewall.ActionDrop, nil
return types.ActionDrop, nil
case "reject":
return firewall.ActionReject, nil
return types.ActionReject, nil
default:
return firewall.ActionAccept, errors.New("invalid action. Must be accept, drop or reject")
return types.ActionAccept, errors.New("invalid action. Must be accept, drop or reject")
}
}
func ToKnockAction(action string) (types.KnockAction, error) {
switch strings.ToLower(action) {
case "accept":
return types.KnockActionAccept, nil
case "drop":
return types.KnockActionDrop, nil
case "reject":
return types.KnockActionReject, nil
case "return":
return types.KnockActionReturn, nil
default:
return types.KnockActionDrop, errors.New("invalid action. Must be accept, return, drop or reject")
}
}
@@ -3,10 +3,15 @@ package analyzer
import (
"errors"
"fmt"
"strconv"
"strings"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config/brute_force_protection"
"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/i18n"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
type BruteForceProtection struct {
@@ -123,6 +128,7 @@ func (p *BruteForceProtection) groups() (map[string]*brute_force_protection.Grou
Count: uint32(p.RateLimitCount),
Period: uint32(p.RateLimitPeriod),
BlockingTimeSeconds: uint32(p.BlockingTime),
BlockConfig: brute_force_protection.NewBlockOnceIPConfig(),
},
},
RateLimitResetPeriod: uint32(p.RateLimitResetPeriod),
@@ -138,3 +144,56 @@ func (p *BruteForceProtection) groups() (map[string]*brute_force_protection.Grou
return groups, nil
}
func toBlockConfigBySettings(blockType string, ports []string) (brute_force_protection.Block, error) {
if blockType == "" {
return nil, errors.New("block type is empty")
}
switch blockType {
case "ip":
return brute_force_protection.NewBlockOnceIPConfig(), nil
case "ip_port":
if len(ports) == 0 {
return nil, errors.New("ports is empty")
}
var blockPorts []types.L4Port
for _, port := range ports {
l4Port, err := toL4Port(port)
if err != nil {
return nil, err
}
blockPorts = append(blockPorts, l4Port)
}
return brute_force_protection.NewBlockIPAndPortsConfig(blockPorts), nil
}
return nil, errors.New("unknown block type")
}
func toL4Port(portString string) (types.L4Port, error) {
if portString == "" {
return nil, errors.New("port is empty")
}
data := strings.Split(portString, "/")
protocol := types.ProtocolTCP
port, err := strconv.Atoi(data[0])
if err != nil {
return nil, err
}
if err := validate.Port(port, "port"); err != nil {
return nil, err
}
if len(data) == 2 {
protocol, err = ip.ToProtocol(data[1])
if err != nil {
return nil, err
}
}
return types.NewL4Port(uint16(port), protocol)
}
@@ -10,6 +10,8 @@ type BruteForceProtectionGroup struct {
Name string `mapstructure:"name"`
Message string `mapstructure:"message"`
RateLimitResetPeriod int `mapstructure:"rate_limit_reset_period"`
BlockType string `mapstructure:"block_type"`
Ports []string `mapstructure:"ports"`
RateLimits []BruteForceProtectionGroupRateLimit `mapstructure:"rate_limits"`
}
@@ -20,8 +22,17 @@ func (g *BruteForceProtectionGroup) ToGroup() (*brute_force_protection.Group, er
var rateLimits []brute_force_protection.RateLimit
blockType := g.BlockType
if blockType == "" {
blockType = "ip"
}
blockConfig, err := toBlockConfigBySettings(blockType, g.Ports)
if err := err; err != nil {
return nil, err
}
for _, rateLimit := range g.RateLimits {
rLimit, err := rateLimit.ToRateLimit()
rLimit, err := rateLimit.ToRateLimit(blockConfig)
if err != nil {
return nil, err
}
@@ -7,20 +7,35 @@ import (
)
type BruteForceProtectionGroupRateLimit struct {
Count int `mapstructure:"count"`
Period int `mapstructure:"period"`
BlockingTime int `mapstructure:"blocking_time"`
Count int `mapstructure:"count"`
Period int `mapstructure:"period"`
BlockingTime int `mapstructure:"blocking_time"`
BlockType string `mapstructure:"block_type"`
Ports []string `mapstructure:"ports"`
}
func (l *BruteForceProtectionGroupRateLimit) ToRateLimit() (brute_force_protection.RateLimit, error) {
func (l *BruteForceProtectionGroupRateLimit) ToRateLimit(blockConfig brute_force_protection.Block) (brute_force_protection.RateLimit, error) {
if err := l.validate(); err != nil {
return brute_force_protection.RateLimit{}, err
}
var rateLimitBlockConfig brute_force_protection.Block
if l.BlockType != "" {
var err error
rateLimitBlockConfig, err = toBlockConfigBySettings(l.BlockType, l.Ports)
if err != nil {
return brute_force_protection.RateLimit{}, err
}
} else {
rateLimitBlockConfig = blockConfig
}
return brute_force_protection.RateLimit{
Count: uint32(l.Count),
Period: uint32(l.Period),
BlockingTimeSeconds: uint32(l.BlockingTime),
BlockConfig: rateLimitBlockConfig,
}, nil
}
+78
View File
@@ -0,0 +1,78 @@
package blocklists
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
"github.com/spf13/viper"
)
type Setting struct {
Enabled bool `mapstructure:"enabled"`
Sources []Sources
}
func InitSetting(path string) (Setting, error) {
if err := validate.IsTomlFile(path, "otherSettingsPath.blocklists"); err != nil {
return Setting{}, err
}
setting := settingDefault()
v := viper.New()
v.SetConfigType("toml")
v.SetConfigFile(path)
if err := v.ReadInConfig(); err != nil {
return Setting{}, err
}
if err := v.Unmarshal(&setting); err != nil {
return Setting{}, err
}
if !setting.Enabled {
return setting, nil
}
return setting, nil
}
func settingDefault() Setting {
return Setting{
Enabled: false,
Sources: []Sources{},
}
}
func (b *Setting) ToSources(logger log.Logger) []*blocklist.SourceConfig {
var sources []*blocklist.SourceConfig
if !b.Enabled {
return sources
}
sourceNames := make(map[string]string)
for _, source := range b.Sources {
if !source.Enabled {
continue
}
if _, ok := sourceNames[source.Name]; ok {
logger.Warn(fmt.Sprintf("duplicate source name: %s", source.Name))
continue
}
sourceNames[source.Name] = source.Name
sourceConfig, err := source.ToSourceConfig()
if err != nil {
logger.Warn(fmt.Sprintf("failed to convert source: %s", err))
continue
}
sources = append(sources, sourceConfig)
}
return sources
}
+246
View File
@@ -0,0 +1,246 @@
package blocklists
import (
"encoding/json"
"encoding/xml"
"errors"
"fmt"
"io"
"strings"
"time"
"git.kor-elf.net/kor-elf-shield/blocklist"
"git.kor-elf.net/kor-elf-shield/blocklist/parser"
daemonBlocklist "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/blocklist/sources"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
type Sources struct {
Enabled bool `mapstructure:"enabled"`
Name string `mapstructure:"name"`
URL string `mapstructure:"url"`
Limit int `mapstructure:"limit"`
Interval int64 `mapstructure:"interval"`
Zip bool `mapstructure:"zip"`
Format string `mapstructure:"format"`
JsonField string `mapstructure:"json_field"`
TxtType string `mapstructure:"txt_type"`
TxtFieldIP int `mapstructure:"txt_field_ip"`
TxtFieldIp2 int `mapstructure:"txt_field_ip2"`
TxtFieldCIDR int `mapstructure:"txt_field_cidr"`
TxtSeparator string `mapstructure:"txt_separator"`
RssTag string `mapstructure:"rss_tag"`
RssField string `mapstructure:"rss_field"`
RssFieldIP int `mapstructure:"rss_field_ip"`
RssFieldSeparator string `mapstructure:"rss_field_separator"`
}
func (s *Sources) ToSourceConfig() (*daemonBlocklist.SourceConfig, error) {
if err := s.Validate(); err != nil {
return &daemonBlocklist.SourceConfig{}, err
}
pars, err := s.parser()
if err != nil {
return &daemonBlocklist.SourceConfig{}, err
}
config := blocklist.NewConfig(uint(s.Limit))
if s.TxtType == "interval" {
config.Validator = &parser.IPRangeValidator{}
}
if s.Zip {
configZip := blocklist.NewConfigZip(config)
return &daemonBlocklist.SourceConfig{
Name: s.Name,
Interval: time.Duration(s.Interval) * time.Second,
Source: sources.NewBlocklistSourceZip(s.URL, pars, configZip),
}, nil
}
return &daemonBlocklist.SourceConfig{
Name: s.Name,
Interval: time.Duration(s.Interval) * time.Second,
Source: sources.NewBlocklistSource(s.URL, pars, config),
}, nil
}
func (s *Sources) Validate() error {
if err := validate.Name(s.Name, "sources.name"); err != nil {
return err
}
if s.Interval < 60 {
return errors.New("invalid limit. Must be greater than or equal to 60 seconds")
}
if s.Limit < 0 {
return errors.New("invalid limit. Must be greater than or equal to 0")
}
if s.URL == "" {
return errors.New("url is required")
} else if !strings.HasPrefix(s.URL, "http://") && !strings.HasPrefix(s.URL, "https://") {
return errors.New("the URL must be to an HTTP or HTTPS resource")
}
return nil
}
func (s *Sources) parser() (parser.Parser, error) {
switch s.Format {
case "json":
if s.JsonField == "" {
return nil, errors.New("json_field is required")
}
return parserJson(s.JsonField)
case "txt":
return s.parserText()
case "rss":
if s.RssTag == "" {
return nil, errors.New("rss_tag is required")
}
if s.RssField == "" {
return nil, errors.New("rss_field is required")
}
if s.RssFieldIP < 0 {
return nil, errors.New("rss_field_ip must be greater than or equal to 0")
}
return parserRss(s.RssTag, s.RssField, uint(s.RssFieldIP), s.RssFieldSeparator)
}
return nil, fmt.Errorf("format not support")
}
func (s *Sources) parserText() (parser.Parser, error) {
if s.TxtType == "" {
return nil, errors.New("txt_type is required")
}
if s.TxtFieldIP < 0 {
return nil, errors.New("txt_field_ip must be greater than or equal to 0")
}
if s.TxtSeparator == "" {
return nil, errors.New("txt_separator is required")
}
switch s.TxtType {
case "default":
return parserTextDefault(uint8(s.TxtFieldIP), s.TxtSeparator)
case "cidr":
if s.TxtFieldCIDR < 0 {
return nil, errors.New("txt_field_cidr must be greater than or equal to 0")
}
return parserTextCIDR(uint8(s.TxtFieldIP), uint8(s.TxtFieldCIDR), s.TxtSeparator)
case "interval":
if s.TxtFieldIp2 < 0 {
return nil, errors.New("txt_field_ip2 must be greater than or equal to 0")
}
return parserTextInterval(uint8(s.TxtFieldIP), uint8(s.TxtFieldIp2), s.TxtSeparator)
}
return nil, fmt.Errorf("txt_type not support")
}
func parserJson(fieldName string) (parser.Parser, error) {
return parser.NewJsonLines(func(item json.RawMessage) (string, error) {
var line map[string]any
if err := json.Unmarshal(item, &line); err != nil {
return "", fmt.Errorf("unmarshal json item: %w", err)
}
v, ok := line[fieldName]
if !ok {
return "", nil
}
ip, ok := v.(string)
if !ok {
return "", nil
}
return ip, nil
})
}
func parserTextDefault(ip uint8, separator string) (parser.Parser, error) {
return parser.NewText(parser.NewDefaultTextExtract(ip, separator))
}
func parserTextCIDR(ip uint8, cidr uint8, separator string) (parser.Parser, error) {
textExtract := parser.NewCIDRTextExtract(ip, cidr, separator)
return parser.NewText(textExtract)
}
func parserTextInterval(ip uint8, ip2 uint8, separator string) (parser.Parser, error) {
textExtract := parser.NewIntervalTextExtract(ip, ip2, separator)
return parser.NewText(textExtract)
}
func parserRss(itemTag string, fieldName string, fieldIP uint, separator string) (parser.Parser, error) {
return parser.NewRss(func(decoder *xml.Decoder, start xml.StartElement) (string, error) {
for {
tok, err := decoder.Token()
if err != nil {
if err == io.EOF {
return "", nil
}
return "", err
}
switch t := tok.(type) {
case xml.StartElement:
if t.Name.Local != itemTag {
continue
}
value, err := parserRssReadFieldFromItem(decoder, t, fieldName)
if err != nil {
return "", err
}
if value != "" {
if separator != "" {
fields := strings.Split(value, separator)
if len(fields) <= int(fieldIP) {
return "", nil
}
return strings.TrimSpace(fields[fieldIP]), nil
}
return strings.TrimSpace(value), nil
}
}
}
})
}
func parserRssReadFieldFromItem(decoder *xml.Decoder, start xml.StartElement, fieldTag string) (string, error) {
depth := 1
for {
tok, err := decoder.Token()
if err != nil {
return "", err
}
switch t := tok.(type) {
case xml.StartElement:
depth++
if t.Name.Local == fieldTag {
var value string
if err := decoder.DecodeElement(&value, &t); err != nil {
return "", err
}
return strings.TrimSpace(value), nil
}
case xml.EndElement:
depth--
if depth == 0 && t.Name.Local == start.Name.Local {
return "", nil
}
}
}
}
+26
View File
@@ -1,6 +1,8 @@
package firewall
import (
"fmt"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
"github.com/spf13/viper"
@@ -14,6 +16,7 @@ type Setting struct {
Options options
MetadataNaming metadataNaming
Policy policy
PortKnocking []portKnocking
}
func InitSetting(path string) (Setting, error) {
@@ -49,6 +52,7 @@ func settingDefault() Setting {
Options: defaultOptions(),
MetadataNaming: defaultMetadataNaming(),
Policy: defaultPolicy(),
PortKnocking: defaultPortKnocking(),
}
}
@@ -101,3 +105,25 @@ func (s Setting) ToIPs() (IPs IPs, error error) {
return
}
func (s Setting) ToConfigPortKnocking() ([]firewall.ConfigPortKnocking, error) {
fmt.Println(s.PortKnocking)
var configPortKnocking []firewall.ConfigPortKnocking
portKnockingNames := make(map[string]string)
for _, portKnocking := range s.PortKnocking {
if _, ok := portKnockingNames[portKnocking.Name]; ok {
return nil, fmt.Errorf("port knocking name %s is duplicated", portKnocking.Name)
}
portKnockingNames[portKnocking.Name] = portKnocking.Name
addPortKnocking, err := portKnocking.ToPortKnocking()
if err != nil {
return nil, err
}
configPortKnocking = append(configPortKnocking, addPortKnocking)
}
return configPortKnocking, nil
}
+19 -13
View File
@@ -4,6 +4,7 @@ import (
"errors"
"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/types"
port2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
@@ -99,7 +100,7 @@ func loopIP(baseConfigIP firewall.ConfigIP, directions []string, protocols []str
if len(ports) == 0 {
// If no port is specified, we only allow the IP address to be accepted.
addIP.OnlyIP = true
if addDirection == firewall.DirectionIn {
if addDirection == types.DirectionIn {
in = append(in, addIP)
} else {
out = append(out, addIP)
@@ -108,13 +109,12 @@ func loopIP(baseConfigIP firewall.ConfigIP, directions []string, protocols []str
}
if len(protocols) == 0 {
addIP.Protocol = firewall.ProtocolTCP
addIn, addOut, err := loopIPPort(addIP, ports, addDirection)
addIn, addOut, err := loopIPPort(addIP, ports, addDirection, types.ProtocolTCP)
if err != nil {
error = err
return
}
if addDirection == firewall.DirectionIn {
if addDirection == types.DirectionIn {
in = append(in, addIn...)
} else {
out = append(out, addOut...)
@@ -127,7 +127,7 @@ func loopIP(baseConfigIP firewall.ConfigIP, directions []string, protocols []str
error = err
return
}
if addDirection == firewall.DirectionIn {
if addDirection == types.DirectionIn {
in = append(in, addIn...)
} else {
out = append(out, addOut...)
@@ -136,7 +136,7 @@ func loopIP(baseConfigIP firewall.ConfigIP, directions []string, protocols []str
return
}
func loopIPProtocol(baseConfigIP firewall.ConfigIP, protocols []string, ports []int, direction firewall.Direction) (in []firewall.ConfigIP, out []firewall.ConfigIP, error error) {
func loopIPProtocol(baseConfigIP firewall.ConfigIP, protocols []string, ports []int, direction types.Direction) (in []firewall.ConfigIP, out []firewall.ConfigIP, error error) {
for _, protocol := range protocols {
addProtocol, err := port2.ToProtocol(protocol)
if err != nil {
@@ -144,10 +144,9 @@ func loopIPProtocol(baseConfigIP firewall.ConfigIP, protocols []string, ports []
return
}
addIP := baseConfigIP
addIP.Protocol = addProtocol
if len(ports) == 0 {
if direction == firewall.DirectionIn {
if direction == types.DirectionIn {
in = append(in, addIP)
} else {
out = append(out, addIP)
@@ -155,12 +154,12 @@ func loopIPProtocol(baseConfigIP firewall.ConfigIP, protocols []string, ports []
continue
}
addIn, addOut, err := loopIPPort(addIP, ports, direction)
addIn, addOut, err := loopIPPort(addIP, ports, direction, addProtocol)
if err != nil {
error = err
return
}
if direction == firewall.DirectionIn {
if direction == types.DirectionIn {
in = append(in, addIn...)
} else {
out = append(out, addOut...)
@@ -170,15 +169,22 @@ func loopIPProtocol(baseConfigIP firewall.ConfigIP, protocols []string, ports []
return
}
func loopIPPort(baseConfigIP firewall.ConfigIP, ports []int, direction firewall.Direction) (in []firewall.ConfigIP, out []firewall.ConfigIP, error error) {
func loopIPPort(baseConfigIP firewall.ConfigIP, ports []int, direction types.Direction, protocol types.Protocol) (in []firewall.ConfigIP, out []firewall.ConfigIP, error error) {
for _, port := range ports {
if err := validate.Port(port, "port"); err != nil {
error = err
return
}
l4Port, err := types.NewL4Port(uint16(port), protocol)
if err != nil {
error = err
return
}
addIP := baseConfigIP
addIP.Port = uint16(port)
if direction == firewall.DirectionIn {
addIP.Port = l4Port
if direction == types.DirectionIn {
in = append(in, addIP)
} else {
out = append(out, addIP)
+4 -3
View File
@@ -4,6 +4,7 @@ import (
"fmt"
"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/types"
)
type policy struct {
@@ -61,15 +62,15 @@ func (p policy) ToConfigPolicy() (firewall.ConfigPolicy, error) {
}, nil
}
func (p policy) dropToPolicyDrop(drop string, parametrName string) (firewall.PolicyDrop, error) {
func (p policy) dropToPolicyDrop(drop string, parametrName string) (types.PolicyDrop, error) {
if drop == "" {
return 0, fmt.Errorf("%s is empty", parametrName)
}
switch drop {
case "drop":
return firewall.Drop, nil
return types.Drop, nil
case "reject":
return firewall.Reject, nil
return types.Reject, nil
default:
return 0, fmt.Errorf("invalid %s . Must be drop or reject", parametrName)
}
+13 -7
View File
@@ -4,7 +4,8 @@ import (
"errors"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/firewall"
port2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"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"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
@@ -25,7 +26,7 @@ func (p *Port) ToPorts() (InPorts []firewall.ConfigPort, OutPorts []firewall.Con
error = err
return
}
action, err := port2.ToAction(p.Action)
action, err := ip.ToAction(p.Action)
if err != nil {
error = err
return
@@ -37,25 +38,30 @@ func (p *Port) ToPorts() (InPorts []firewall.ConfigPort, OutPorts []firewall.Con
return
}
for _, direction := range p.Directions {
addDirection, err := port2.ToDirection(direction)
addDirection, err := ip.ToDirection(direction)
if err != nil {
error = err
return
}
for _, protocol := range p.Protocols {
addProtocol, err := port2.ToProtocol(protocol)
addProtocol, err := ip.ToProtocol(protocol)
if err != nil {
error = err
return
}
l4Port, err := types.NewL4Port(uint16(port), addProtocol)
if err != nil {
error = err
return
}
addPort := firewall.ConfigPort{
Number: uint16(port),
Protocol: addProtocol,
Port: l4Port,
Action: action,
LimitRate: p.LimitRate,
}
if addDirection == firewall.DirectionIn {
if addDirection == types.DirectionIn {
InPorts = append(InPorts, addPort)
} else {
OutPorts = append(OutPorts, addPort)
@@ -0,0 +1,87 @@
package firewall
import (
"fmt"
"strings"
"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/types"
port2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
type portKnocking struct {
Name string `mapstructure:"name"`
IPVersion string `mapstructure:"ip_version"`
Port int `mapstructure:"port"`
Protocol string `mapstructure:"protocol"`
Knocks []portKnockingKnock `mapstructure:"knock"`
}
func defaultPortKnocking() []portKnocking {
return []portKnocking{}
}
func (p *portKnocking) ToPortKnocking() (firewall.ConfigPortKnocking, error) {
if len(p.Knocks) == 0 {
return firewall.ConfigPortKnocking{}, fmt.Errorf("port knocking must have at least one knock")
}
if err := p.validate(); err != nil {
return firewall.ConfigPortKnocking{}, err
}
protocol, err := port2.ToProtocol(p.Protocol)
if err != nil {
return firewall.ConfigPortKnocking{}, err
}
l4Port, err := types.NewL4Port(uint16(p.Port), protocol)
if err != nil {
return firewall.ConfigPortKnocking{}, err
}
ipVersion, err := toVersionIP(p.IPVersion)
if err != nil {
return firewall.ConfigPortKnocking{}, err
}
knocks := make([]*firewall.ConfigKnock, 0, len(p.Knocks))
for _, knock := range p.Knocks {
knock, err := knock.ToKnock()
if err != nil {
return firewall.ConfigPortKnocking{}, err
}
knocks = append(knocks, &knock)
}
return firewall.ConfigPortKnocking{
Name: p.Name,
Port: l4Port,
IPVersion: ipVersion,
Knocks: knocks,
}, nil
}
func (p *portKnocking) validate() error {
if err := validate.Name(p.Name, "portKnocking.name"); err != nil {
return err
}
if err := validate.Port(p.Port, "portKnocking.port"); err != nil {
return err
}
return nil
}
func toVersionIP(versionIP string) (port2.Version, error) {
switch strings.ToLower(versionIP) {
case "ip4":
return port2.IPv4, nil
case "ip6":
return port2.IPv6, nil
default:
return port2.IPv4, fmt.Errorf("invalid version_ip. Must be ip4 or ip6")
}
}
@@ -0,0 +1,55 @@
package firewall
import (
"fmt"
"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/types"
port2 "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/pkg/ip"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/validate"
)
type portKnockingKnock struct {
Port int `mapstructure:"port"`
Protocol string `mapstructure:"protocol"`
Timeout int32 `mapstructure:"timeout"`
Action string `mapstructure:"action"`
}
func (k *portKnockingKnock) ToKnock() (firewall.ConfigKnock, error) {
if err := k.validate(); err != nil {
return firewall.ConfigKnock{}, err
}
protocol, err := port2.ToProtocol(k.Protocol)
if err != nil {
return firewall.ConfigKnock{}, err
}
l4Port, err := types.NewL4Port(uint16(k.Port), protocol)
if err != nil {
return firewall.ConfigKnock{}, err
}
action, err := port2.ToKnockAction(k.Action)
if err != nil {
return firewall.ConfigKnock{}, err
}
return firewall.ConfigKnock{
Port: l4Port,
Action: action,
Timeout: uint32(k.Timeout),
}, nil
}
func (k *portKnockingKnock) validate() error {
if err := validate.Port(k.Port, "knock.port"); err != nil {
return err
}
if k.Timeout <= 0 {
return fmt.Errorf("knock.timeout must be positive")
}
return nil
}
+28 -1
View File
@@ -4,14 +4,18 @@ import (
"errors"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/analyzer/config"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/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"
"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"
logger "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/log"
analyzerSetting "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/analyzer"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/blocklists"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/docker"
firewallSetting "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/firewall"
notificationsSetting "git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/setting/notifications"
"github.com/wneessen/go-mail"
)
@@ -20,6 +24,7 @@ type otherSettingsPath struct {
Notifications string `mapstructure:"notifications"`
Analyzer string `mapstructure:"analyzer"`
Docker string `mapstructure:"docker"`
Blocklists string `mapstructure:"blocklists"`
}
func otherSettingsPathDefault() *otherSettingsPath {
@@ -28,6 +33,7 @@ func otherSettingsPathDefault() *otherSettingsPath {
Notifications: "/etc/kor-elf-shield/notifications.toml",
Analyzer: "/etc/kor-elf-shield/analyzer.toml",
Docker: "/etc/kor-elf-shield/docker.toml",
Blocklists: "/etc/kor-elf-shield/blocklists.toml",
}
}
@@ -57,6 +63,11 @@ func (o *otherSettingsPath) ToFirewallConfig(dockerSupport bool) (firewall.Confi
return firewall.Config{}, err
}
portKnocking, err := setting.ToConfigPortKnocking()
if err != nil {
return firewall.Config{}, err
}
return firewall.Config{
InPorts: inPorts,
OutPorts: outPorts,
@@ -90,7 +101,8 @@ func (o *otherSettingsPath) ToFirewallConfig(dockerSupport bool) (firewall.Confi
ChainOutputName: setting.MetadataNaming.ChainOutputName,
ChainForwardName: setting.MetadataNaming.ChainForwardName,
},
Policy: configPolicy,
Policy: configPolicy,
PortKnocking: portKnocking,
}, nil
}
@@ -189,3 +201,18 @@ func (o *otherSettingsPath) ToDockerConfig(binaryLocations *binaryLocations) (co
RuleStrategy: ruleStrategy,
}, setting.Enabled, nil
}
func (o *otherSettingsPath) ToBlocklistConfig(logger logger.Logger) (sources []*blocklist.SourceConfig, blocklistSupport bool, err error) {
setting, err := blocklists.InitSetting(o.Blocklists)
if err != nil {
return []*blocklist.SourceConfig{}, false, err
}
sources = setting.ToSources(logger)
if setting.Enabled && len(sources) == 0 {
return []*blocklist.SourceConfig{}, false, errors.New(i18n.Lang.T("blocklist sources are empty"))
}
return sources, setting.Enabled, nil
}
+36 -2
View File
@@ -1,9 +1,15 @@
package socket
import "net"
import (
"encoding/json"
"net"
"git.kor-elf.net/kor-elf-shield/kor-elf-shield/internal/daemon/socket"
)
type Client interface {
Send(command string) (result string, err error)
SendCommand(command string, args map[string]string) (result string, err error)
Read() (string, error)
Close() error
}
@@ -21,7 +27,35 @@ func NewSocketClient(path string) (Client, error) {
}
func (s *client) Send(command string) (result string, err error) {
_, err = s.conn.Write([]byte(command))
msg := socket.Message{
Command: command,
}
data, err := json.Marshal(msg)
if err != nil {
return "", err
}
_, err = s.conn.Write(data)
if err != nil {
return "", err
}
return s.Read()
}
func (s *client) SendCommand(command string, args map[string]string) (string, error) {
msg := socket.Message{
Command: command,
Args: args,
}
data, err := json.Marshal(msg)
if err != nil {
return "", err
}
_, err = s.conn.Write(data)
if err != nil {
return "", err
}