mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-16 18:02:55 +02:00
319 lines
11 KiB
Nix
319 lines
11 KiB
Nix
# CrowdSec Firewall Bouncer Module
|
|
# Provides firewall-level IP blocking using iptables/nftables/ipset
|
|
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
appName = "crowdsec";
|
|
cfg = config.infrastructure.${appName};
|
|
stateDir = "/var/lib/crowdsec";
|
|
yamlFormat = pkgs.formats.yaml {};
|
|
in
|
|
{
|
|
# ==========================================================================
|
|
# Options
|
|
# ==========================================================================
|
|
options.infrastructure.${appName} = {
|
|
features.firewallBouncer = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
Enable the firewall bouncer to automatically block malicious IPs.
|
|
|
|
The bouncer fetches decisions from the CrowdSec API and applies
|
|
them to the system firewall (iptables/nftables). Available in
|
|
nixpkgs as pkgs.crowdsec-firewall-bouncer starting from NixOS 25.11.
|
|
|
|
When using nftables mode (default), the module creates declarative
|
|
nftables tables that integrate properly with NixOS's firewall and
|
|
survive system rebuilds.
|
|
|
|
[NIS2 COMPLIANCE]
|
|
Article 21(2)(b) - Incident Handling: Provides automated incident
|
|
response by blocking identified threats in real-time.
|
|
|
|
Article 21(2)(d) - Network Security: Implements active network
|
|
protection through automated firewall rule management.
|
|
'';
|
|
default = false;
|
|
};
|
|
|
|
bouncer = {
|
|
package = lib.mkOption {
|
|
type = lib.types.nullOr lib.types.package;
|
|
description = ''
|
|
CrowdSec firewall bouncer package to use.
|
|
|
|
The package is available in nixpkgs as pkgs.crowdsec-firewall-bouncer
|
|
starting from NixOS 25.11.
|
|
|
|
Set to null to disable the bouncer even when features.firewallBouncer
|
|
is enabled (useful for testing detection without blocking).
|
|
'';
|
|
default = pkgs.crowdsec-firewall-bouncer or null;
|
|
defaultText = lib.literalExpression "pkgs.crowdsec-firewall-bouncer";
|
|
example = lib.literalExpression "pkgs.crowdsec-firewall-bouncer";
|
|
};
|
|
|
|
mode = lib.mkOption {
|
|
type = lib.types.enum [ "iptables" "nftables" "ipset" ];
|
|
description = ''
|
|
Firewall mode for the bouncer.
|
|
|
|
- "nftables": Recommended for NixOS. Uses nftables sets which integrate
|
|
well with NixOS declarative firewall. The module creates the necessary
|
|
tables/chains declaratively, and the bouncer only manages set membership.
|
|
|
|
- "iptables": Traditional iptables rules. May conflict with NixOS firewall
|
|
on system rebuilds.
|
|
|
|
- "ipset": Uses ipset for IP blocking. More compatible with iptables-based
|
|
firewalls and survives rule flushes better.
|
|
|
|
[NIS2 COMPLIANCE]
|
|
All modes provide equivalent security protection. Choose based on your
|
|
existing firewall infrastructure.
|
|
'';
|
|
default = "nftables";
|
|
};
|
|
|
|
nftablesIntegration = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
When using nftables mode, declaratively create the CrowdSec table
|
|
structure in NixOS configuration. This ensures the tables/chains
|
|
survive NixOS rebuilds and prevents conflicts with the declarative
|
|
firewall.
|
|
|
|
When enabled:
|
|
- Creates "crowdsec" and "crowdsec6" tables declaratively
|
|
- Configures bouncer in "set-only" mode
|
|
- Bouncer only manages IP set membership, not table structure
|
|
|
|
When disabled:
|
|
- Bouncer creates and manages its own tables
|
|
- May conflict with NixOS firewall rebuilds
|
|
'';
|
|
default = true;
|
|
};
|
|
|
|
denyAction = lib.mkOption {
|
|
type = lib.types.enum [ "DROP" "REJECT" ];
|
|
description = ''
|
|
Action to take for blocked IPs.
|
|
|
|
- "DROP": Silently drop packets (recommended for security)
|
|
- "REJECT": Send rejection response to client
|
|
|
|
DROP is generally preferred as it doesn't reveal firewall presence.
|
|
'';
|
|
default = "DROP";
|
|
};
|
|
|
|
denyLog = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
Log blocked connections before dropping/rejecting.
|
|
|
|
[NIS2 COMPLIANCE]
|
|
Article 21(2)(g) - Security Monitoring: Maintains audit trail
|
|
of blocked threats for incident analysis and reporting.
|
|
'';
|
|
default = true;
|
|
};
|
|
|
|
denyLogPrefix = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Prefix for firewall log entries.";
|
|
default = "crowdsec: ";
|
|
};
|
|
|
|
banDuration = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
Default ban duration for blocked IPs.
|
|
|
|
Format: Go duration string (e.g., "4h", "24h", "7d")
|
|
'';
|
|
default = "4h";
|
|
example = "24h";
|
|
};
|
|
};
|
|
};
|
|
|
|
# ==========================================================================
|
|
# Configuration
|
|
# ==========================================================================
|
|
config = lib.mkIf (cfg.enable && cfg.features.firewallBouncer && cfg.bouncer.package != null) (
|
|
let
|
|
useNftablesIntegration = cfg.bouncer.mode == "nftables" && cfg.bouncer.nftablesIntegration;
|
|
|
|
# Bouncer config - uses set-only mode when nftablesIntegration is enabled
|
|
bouncerConfigFile = yamlFormat.generate "crowdsec-firewall-bouncer.yaml" ({
|
|
mode = cfg.bouncer.mode;
|
|
update_frequency = "10s";
|
|
api_url = "http://${cfg.api.listenAddr}:${toString cfg.api.listenPort}/";
|
|
api_key = "\${BOUNCER_API_KEY}";
|
|
disable_ipv6 = false;
|
|
deny_action = cfg.bouncer.denyAction;
|
|
deny_log = cfg.bouncer.denyLog;
|
|
deny_log_prefix = cfg.bouncer.denyLogPrefix;
|
|
} // lib.optionalAttrs (cfg.bouncer.mode == "nftables") {
|
|
nftables = {
|
|
ipv4 = {
|
|
enabled = true;
|
|
set-only = useNftablesIntegration;
|
|
table = "crowdsec";
|
|
chain = "crowdsec-chain";
|
|
set = "crowdsec-blocklist";
|
|
};
|
|
ipv6 = {
|
|
enabled = true;
|
|
set-only = useNftablesIntegration;
|
|
table = "crowdsec6";
|
|
chain = "crowdsec6-chain";
|
|
set = "crowdsec6-blocklist";
|
|
};
|
|
};
|
|
} // lib.optionalAttrs (cfg.bouncer.mode == "iptables") {
|
|
iptables_chains = [ "INPUT" "FORWARD" ];
|
|
} // lib.optionalAttrs (cfg.bouncer.mode == "ipset") {
|
|
ipset_type = "nethash";
|
|
ipset = "crowdsec-blocklist";
|
|
ipset6 = "crowdsec6-blocklist";
|
|
});
|
|
|
|
bouncerRegisterScript = pkgs.writeShellScript "crowdsec-bouncer-register" ''
|
|
set -e
|
|
export PATH="${lib.makeBinPath [ cfg.package pkgs.coreutils pkgs.gnugrep pkgs.gnused ]}:$PATH"
|
|
|
|
CONFIG_DIR="${stateDir}/config"
|
|
KEY_FILE="/var/lib/crowdsec-firewall-bouncer/api_key"
|
|
|
|
# Wait for CrowdSec API to be ready
|
|
for i in $(seq 1 60); do
|
|
if cscli -c "$CONFIG_DIR/config.yaml" bouncers list >/dev/null 2>&1; then
|
|
break
|
|
fi
|
|
sleep 1
|
|
done
|
|
|
|
# Check if bouncer already registered
|
|
if ! cscli -c "$CONFIG_DIR/config.yaml" bouncers list 2>/dev/null | grep -q "firewall-bouncer"; then
|
|
# Register new bouncer and save key
|
|
KEY=$(cscli -c "$CONFIG_DIR/config.yaml" bouncers add firewall-bouncer -o raw 2>/dev/null || echo "")
|
|
if [ -n "$KEY" ]; then
|
|
echo "$KEY" > "$KEY_FILE"
|
|
chmod 600 "$KEY_FILE"
|
|
fi
|
|
fi
|
|
|
|
# Read existing key
|
|
if [ -f "$KEY_FILE" ]; then
|
|
export BOUNCER_API_KEY=$(cat "$KEY_FILE")
|
|
fi
|
|
|
|
# Generate config with key substituted
|
|
# Use | as sed delimiter since API keys may contain /
|
|
if [ -n "$BOUNCER_API_KEY" ]; then
|
|
sed "s|\''${BOUNCER_API_KEY}|$BOUNCER_API_KEY|g" ${bouncerConfigFile} > /var/lib/crowdsec-firewall-bouncer/config.yaml
|
|
fi
|
|
'';
|
|
|
|
in lib.mkMerge [
|
|
# Assertions
|
|
{
|
|
assertions = [
|
|
{
|
|
assertion = cfg.bouncer.package != null;
|
|
message = ''
|
|
CrowdSec firewall bouncer is enabled but no package is configured.
|
|
|
|
The bouncer package should be available as pkgs.crowdsec-firewall-bouncer
|
|
on NixOS 25.11+. If using an older NixOS version, you may need to:
|
|
|
|
1. Upgrade to NixOS 25.11+
|
|
2. Set infrastructure.crowdsec.features.firewallBouncer = false
|
|
3. Provide the package from an external source
|
|
'';
|
|
}
|
|
];
|
|
|
|
# Install CLI tools based on mode
|
|
environment.systemPackages =
|
|
lib.optionals (cfg.bouncer.mode == "nftables") [ pkgs.nftables ]
|
|
++ lib.optionals (cfg.bouncer.mode == "iptables") [ pkgs.iptables ]
|
|
++ lib.optionals (cfg.bouncer.mode == "ipset") [ pkgs.ipset ];
|
|
}
|
|
|
|
# Declarative nftables Integration
|
|
(lib.mkIf useNftablesIntegration {
|
|
networking.nftables.enable = true;
|
|
|
|
networking.nftables.tables = {
|
|
# IPv4 CrowdSec table
|
|
crowdsec = {
|
|
family = "ip";
|
|
content = ''
|
|
set crowdsec-blocklist {
|
|
type ipv4_addr
|
|
flags timeout
|
|
}
|
|
|
|
chain crowdsec-chain {
|
|
type filter hook input priority -1; policy accept;
|
|
${lib.optionalString cfg.bouncer.denyLog ''
|
|
ip saddr @crowdsec-blocklist log prefix "${cfg.bouncer.denyLogPrefix}"
|
|
''}
|
|
ip saddr @crowdsec-blocklist ${lib.toLower cfg.bouncer.denyAction}
|
|
}
|
|
'';
|
|
};
|
|
|
|
# IPv6 CrowdSec table
|
|
crowdsec6 = {
|
|
family = "ip6";
|
|
content = ''
|
|
set crowdsec6-blocklist {
|
|
type ipv6_addr
|
|
flags timeout
|
|
}
|
|
|
|
chain crowdsec6-chain {
|
|
type filter hook input priority -1; policy accept;
|
|
${lib.optionalString cfg.bouncer.denyLog ''
|
|
ip6 saddr @crowdsec6-blocklist log prefix "${cfg.bouncer.denyLogPrefix}"
|
|
''}
|
|
ip6 saddr @crowdsec6-blocklist ${lib.toLower cfg.bouncer.denyAction}
|
|
}
|
|
'';
|
|
};
|
|
};
|
|
})
|
|
|
|
# Tmpfiles and service
|
|
{
|
|
systemd.tmpfiles.rules = [
|
|
"d /var/lib/crowdsec-firewall-bouncer 0750 root root - -"
|
|
];
|
|
|
|
# Firewall bouncer service
|
|
systemd.services.crowdsec-firewall-bouncer = {
|
|
description = "CrowdSec Firewall Bouncer";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" "crowdsec.service" ];
|
|
requires = [ "crowdsec.service" ];
|
|
|
|
path = lib.optionals (cfg.bouncer.mode == "iptables") [ pkgs.iptables pkgs.ipset ];
|
|
|
|
serviceConfig = {
|
|
Type = "simple";
|
|
ExecStartPre = "${bouncerRegisterScript}";
|
|
ExecStart = "${cfg.bouncer.package}/bin/cs-firewall-bouncer -c /var/lib/crowdsec-firewall-bouncer/config.yaml";
|
|
Restart = "always";
|
|
RestartSec = "10s";
|
|
};
|
|
};
|
|
}
|
|
]
|
|
);
|
|
}
|