mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-16 18:02:55 +02:00
182 lines
6.4 KiB
Nix
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";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
}
|