#!/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 }