secubox-openwrt/package/secubox/crowdsec-firewall-bouncer/files/crowdsec-firewall-bouncer.initd
CyberMind-FR 2053cfb614 fix(crowdsec): Patch dispatcher for auth logging and fix firewall interfaces
- secubox-auth-logger v1.2.0: Patch LuCI ucode dispatcher.uc to log
  authentication failures server-side instead of relying on JS hooks
- crowdsec-firewall-bouncer: Add helper function for UCI list reading
  and default to eth1, br-lan, br-wan interfaces to ensure WAN traffic
  is checked against the blocklist
- Update postrm to properly restore dispatcher backup on uninstall

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-13 15:50:55 +01:00

268 lines
7.5 KiB
Bash

#!/bin/sh /etc/rc.common
# SPDX-License-Identifier: MIT
# CrowdSec Firewall Bouncer - nftables integration for SecuBox OpenWrt
# Copyright (C) 2021-2022 Gerald Kerma
# Copyright (C) 2024-2025 CyberMind.fr (SecuBox adaptation)
USE_PROCD=1
START=99
NAME=crowdsec-firewall-bouncer
PROG=/usr/bin/cs-firewall-bouncer
VARCONFIGDIR=/var/etc/crowdsec/bouncers
VARCONFIG=/var/etc/crowdsec/bouncers/crowdsec-firewall-bouncer.yaml
CONFIGURATION=crowdsec
TABLE="crowdsec"
TABLE6="crowdsec6"
service_triggers() {
procd_add_reload_trigger crowdsec-firewall-bouncer
procd_add_config_trigger "config.change" "crowdsec" /etc/init.d/crowdsec-firewall-bouncer reload
}
init_yaml() {
local section="$1"
local set_only
local hook_priority
local update_frequency
local log_level
local api_url
local api_key
local ipv6
local deny_action
local deny_log
local log_prefix
local log_max_size
local log_max_backups
local log_max_age
local ipv4
local chain_name
local chain6_name
local retry_initial_connect
config_get hook_priority $section priority "4"
config_get update_frequency $section update_frequency '10s'
config_get log_level $section log_level 'info'
config_get api_url $section api_url "http://127.0.0.1:8080"
config_get api_key $section api_key "API_KEY"
config_get_bool ipv6 $section ipv6 '1'
config_get deny_action $section deny_action "drop"
config_get_bool deny_log $section deny_log '0'
config_get log_prefix $section log_prefix "crowdsec: "
config_get log_max_size $section log_max_size '100'
config_get log_max_backups $section log_max_backups '3'
config_get log_max_age $section log_max_age '30'
config_get_bool ipv4 $section ipv4 '1'
config_get chain_name $section chain_name "crowdsec-chain"
config_get chain6_name $section chain6_name "crowdsec6-chain"
config_get_bool retry_initial_connect $section retry_initial_connect '1'
# Create tmp dir & permissions if needed
if [ ! -d "${VARCONFIGDIR}" ]; then
mkdir -m 0755 -p "${VARCONFIGDIR}"
fi
cat > $VARCONFIG <<-EOM
mode: nftables
pid_dir: /var/run/
update_frequency: $update_frequency
daemonize: true
log_mode: file
log_dir: /var/log/
log_level: $log_level
log_compression: true
log_max_size: $log_max_size
log_max_backups: $log_max_backups
log_max_age: $log_max_age
api_url: $api_url
api_key: $api_key
retry_initial_connect: bool($retry_initial_connect)
insecure_skip_verify: true
disable_ipv6: boolnot($ipv6)
deny_action: $deny_action
deny_log: bool($deny_log)
supported_decisions_type:
- ban
deny_log_prefix: "$log_prefix"
blacklists_ipv4: crowdsec-blacklists
blacklists_ipv6: crowdsec6-blacklists
ipset_type: nethash
iptables_chains:
- INPUT
nftables:
ipv4:
enabled: bool($ipv4)
set-only: false
table: $TABLE
chain: $chain_name
priority: $hook_priority
ipv6:
enabled: bool($ipv6)
set-only: false
table: $TABLE6
chain: $chain6_name
priority: $hook_priority
nftables_hooks:
- input
- forward
pf:
anchor_name: ""
prometheus:
enabled: false
listen_addr: 127.0.0.1
listen_port: 60601
EOM
# Replace bool placeholders with actual values
sed -i "s/bool(1)/true/g" $VARCONFIG
sed -i "s/bool(0)/false/g" $VARCONFIG
sed -i "s/boolnot(1)/false/g" $VARCONFIG
sed -i "s/boolnot(0)/true/g" $VARCONFIG
sed -i "s,^\(\s*api_url\s*:\s*\).*\$,\1$api_url," $VARCONFIG
sed -i "s,^\(\s*api_key\s*:\s*\).*\$,\1$api_key," $VARCONFIG
}
_add_interface_to_list() {
if [ -z "$interface_list" ]; then
interface_list="$1"
else
interface_list="$interface_list $1"
fi
}
init_nftables() {
local section="$1"
local hook_priority
local deny_action
local deny_log
local log_prefix
local ipv4
local ipv6
local filter_input
local filter_forward
local chain_name
local chain6_name
local interface
local log_term=""
config_get hook_priority $section priority "4"
config_get deny_action $section deny_action "drop"
config_get_bool deny_log $section deny_log '0'
config_get log_prefix $section log_prefix "crowdsec: "
config_get_bool ipv4 $section ipv4 '1'
config_get_bool ipv6 $section ipv6 '1'
config_get_bool filter_input $section filter_input '1'
config_get_bool filter_forward $section filter_forward '1'
config_get chain_name $section chain_name "crowdsec-chain"
config_get chain6_name $section chain6_name "crowdsec6-chain"
# Read interface list properly (UCI list or single value)
local interface_list=""
config_list_foreach "$section" interface _add_interface_to_list
if [ -z "$interface_list" ]; then
# Fallback: try single value
config_get interface_list $section interface ''
fi
# Default interfaces for SecuBox (eth1=WAN on x86, br-wan=WAN bridge, br-lan=LAN)
interface="${interface_list:-eth1, br-lan, br-wan}"
if [ "$deny_log" -eq "1" ]; then
log_term="log prefix \"${log_prefix}\""
fi
# Handle multiple interfaces (space-separated to comma-separated)
interface="${interface// /, }"
# Clean up existing tables (kernel 3.18+ supports delete without flush)
nft delete table ip crowdsec 2>/dev/null
nft delete table ip6 crowdsec6 2>/dev/null
# Setup IPv4 nftables
if [ "$ipv4" -eq "1" ]; then
nft add table ip crowdsec
nft add set ip crowdsec crowdsec-blacklists '{ type ipv4_addr; flags timeout; }'
if [ "$filter_input" -eq "1" ]; then
nft add chain ip "$TABLE" $chain_name-input "{ type filter hook input priority $hook_priority; policy accept; }"
nft add rule ip "$TABLE" $chain_name-input ct state established,related accept
nft add rule ip "$TABLE" $chain_name-input iifname != \{ $interface \} accept
fi
if [ "$filter_forward" -eq "1" ]; then
nft add chain ip "$TABLE" $chain_name-forward "{ type filter hook forward priority $hook_priority; policy accept; }"
nft add rule ip "$TABLE" $chain_name-forward ct state established,related accept
nft add rule ip "$TABLE" $chain_name-forward iifname != \{ $interface \} accept
fi
fi
# Setup IPv6 nftables
if [ "$ipv6" -eq "1" ]; then
nft add table ip6 crowdsec6
nft add set ip6 crowdsec6 crowdsec6-blacklists '{ type ipv6_addr; flags timeout; }'
if [ "$filter_input" -eq "1" ]; then
nft add chain ip6 "$TABLE6" $chain6_name-input "{ type filter hook input priority $hook_priority; policy accept; }"
nft add rule ip6 "$TABLE6" $chain6_name-input ct state established,related accept
nft add rule ip6 "$TABLE6" $chain6_name-input iifname != \{ $interface \} accept
fi
if [ "$filter_forward" -eq "1" ]; then
nft add chain ip6 "$TABLE6" $chain6_name-forward "{ type filter hook forward priority $hook_priority; policy accept; }"
nft add rule ip6 "$TABLE6" $chain6_name-forward ct state established,related accept
nft add rule ip6 "$TABLE6" $chain6_name-forward iifname != \{ $interface \} accept
fi
fi
}
run_bouncer() {
local section="$1"
local enabled
config_get_bool enabled $section enabled 0
if [ "$enabled" -eq "1" ]; then
init_yaml "$section"
init_nftables "$section"
procd_open_instance
procd_set_param command "$PROG" -c "$VARCONFIG"
procd_set_param stdout 1
procd_set_param stderr 1
procd_set_param nice 10
# Use ujail if available for security isolation
if [ -x "/sbin/ujail" ]; then
procd_add_jail cs-bouncer log
procd_add_jail_mount $VARCONFIG
procd_add_jail_mount_rw /var/log/
procd_set_param no_new_privs 1
fi
procd_close_instance
fi
}
start_service() {
config_load "${CONFIGURATION}"
config_foreach run_bouncer bouncer
}
service_stopped() {
# Clean up config file
rm -f $VARCONFIG
# Remove nftables tables
nft delete table ip crowdsec 2>/dev/null
nft delete table ip6 crowdsec6 2>/dev/null
}
reload_service() {
stop
start
}