Files
k8scd/nix-infra-machine/app_modules/_unstable/crowdsec/bouncers/haproxy.nix
T
2026-03-15 18:31:53 +01:00

182 lines
6.4 KiB
Nix

# CrowdSec HAProxy SPOA Bouncer Module
# Provides application-layer protection via HAProxy Stream Processing Offload API
{ config, pkgs, lib, ... }:
let
appName = "crowdsec";
cfg = config.infrastructure.${appName};
stateDir = "/var/lib/crowdsec";
yamlFormat = pkgs.formats.yaml {};
in
{
# ==========================================================================
# Options
# ==========================================================================
options.infrastructure.${appName} = {
features.haproxyProtection = lib.mkOption {
type = lib.types.bool;
description = ''
Enable HAProxy security integration via SPOA (Stream Processing Offload API).
The cs-haproxy-spoa-bouncer acts as a stream processing agent that checks
each connection in real-time against CrowdSec's decision database before
allowing traffic to reach your application servers.
This provides layer 7 application-level protection, complementing the
layer 3/4 protection from the firewall bouncer.
[NIS2 COMPLIANCE]
Article 21(2)(d) - Network Security: Provides application-layer protection
for HTTP/HTTPS traffic through HAProxy integration.
Article 21(2)(e) - Supply Chain Security: Protects web applications that
may be part of the digital supply chain.
'';
default = false;
};
haproxy = {
package = lib.mkOption {
type = lib.types.nullOr lib.types.package;
description = ''
CrowdSec HAProxy SPOA bouncer package to use.
The package should be available as pkgs.cs-haproxy-spoa-bouncer.
Set to null to disable the bouncer even when features.haproxyProtection
is enabled.
'';
default = pkgs.cs-haproxy-spoa-bouncer or null;
defaultText = lib.literalExpression "pkgs.cs-haproxy-spoa-bouncer";
example = lib.literalExpression "pkgs.cs-haproxy-spoa-bouncer";
};
listenAddr = lib.mkOption {
type = lib.types.str;
description = ''
Address for the SPOA bouncer to listen on.
HAProxy will connect to this address to check decisions.
'';
default = "127.0.0.1";
example = "0.0.0.0";
};
listenPort = lib.mkOption {
type = lib.types.port;
description = "Port for the SPOA bouncer to listen on.";
default = 3000;
example = 3000;
};
action = lib.mkOption {
type = lib.types.enum [ "deny" "tarpit" ];
description = ''
Action to take for blocked requests in HAProxy.
- "deny": Immediately reject the connection
- "tarpit": Slow down the connection (tar pit)
'';
default = "deny";
};
logLevel = lib.mkOption {
type = lib.types.enum [ "error" "warning" "info" "debug" ];
description = "Log level for the SPOA bouncer.";
default = "info";
};
};
};
# ==========================================================================
# Configuration
# ==========================================================================
config = lib.mkIf (cfg.enable && cfg.features.haproxyProtection && cfg.haproxy.package != null) (
let
# HAProxy SPOA bouncer config file
haproxyBouncerConfigFile = yamlFormat.generate "crowdsec-haproxy-spoa-bouncer.yaml" {
lapi_url = "http://${cfg.api.listenAddr}:${toString cfg.api.listenPort}";
lapi_key = "\${HAPROXY_SPOA_API_KEY}";
action = cfg.haproxy.action;
log_level = cfg.haproxy.logLevel;
listen_addr = cfg.haproxy.listenAddr;
listen_port = cfg.haproxy.listenPort;
update_frequency = "10s";
};
haproxyBouncerRegisterScript = pkgs.writeShellScript "crowdsec-haproxy-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-haproxy-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 "haproxy-spoa-bouncer"; then
# Register new bouncer and save key
KEY=$(cscli -c "$CONFIG_DIR/config.yaml" bouncers add haproxy-spoa-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 HAPROXY_SPOA_API_KEY=$(cat "$KEY_FILE")
fi
# Generate config with key substituted
# Use | as sed delimiter since API keys may contain /
if [ -n "$HAPROXY_SPOA_API_KEY" ]; then
sed "s|\''${HAPROXY_SPOA_API_KEY}|$HAPROXY_SPOA_API_KEY|g" ${haproxyBouncerConfigFile} > /var/lib/crowdsec-haproxy-bouncer/config.yaml
fi
'';
in {
# Assertions
assertions = [
{
assertion = cfg.haproxy.package != null;
message = ''
CrowdSec HAProxy SPOA bouncer is enabled but no package is configured.
The bouncer package should be available as pkgs.cs-haproxy-spoa-bouncer.
If the package is not available, you may need to:
1. Set infrastructure.crowdsec.features.haproxyProtection = false
2. Provide the package from an external source
'';
}
];
# Tmpfiles
systemd.tmpfiles.rules = [
"d /var/lib/crowdsec-haproxy-bouncer 0750 root root - -"
];
# HAProxy SPOA bouncer service
systemd.services.crowdsec-haproxy-bouncer = {
description = "CrowdSec HAProxy SPOA Bouncer";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" "crowdsec.service" ];
requires = [ "crowdsec.service" ];
serviceConfig = {
Type = "simple";
ExecStartPre = "${haproxyBouncerRegisterScript}";
ExecStart = "${cfg.haproxy.package}/bin/cs-haproxy-spoa-bouncer -c /var/lib/crowdsec-haproxy-bouncer/config.yaml";
Restart = "always";
RestartSec = "10s";
};
};
}
);
}