mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-17 10:22:54 +02:00
314 lines
12 KiB
Nix
314 lines
12 KiB
Nix
# CrowdSec Python Bouncer Module
|
|
# Provides API key registration for pycrowdsec and python-capi-sdk integration
|
|
{ config, pkgs, lib, ... }:
|
|
|
|
let
|
|
appName = "crowdsec";
|
|
cfg = config.infrastructure.${appName};
|
|
stateDir = "/var/lib/crowdsec";
|
|
in
|
|
{
|
|
# ==========================================================================
|
|
# Options
|
|
# ==========================================================================
|
|
options.infrastructure.${appName}.python = {
|
|
enable = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
Enable Python bouncer registration for use with pycrowdsec.
|
|
|
|
This registers a bouncer with CrowdSec and stores the API key in a
|
|
configurable location so Python applications can use the pycrowdsec
|
|
library to check IPs against CrowdSec decisions.
|
|
|
|
Python applications should use the StreamClient or QueryClient from
|
|
pycrowdsec to query decisions:
|
|
|
|
```python
|
|
from pycrowdsec.client import StreamClient
|
|
client = StreamClient(
|
|
api_key=open("/run/crowdsec-python-bouncer/api_key").read().strip(),
|
|
lapi_url="http://127.0.0.1:8080/"
|
|
)
|
|
client.run()
|
|
action = client.get_action_for("1.2.3.4") # Returns "ban", "captcha", etc.
|
|
```
|
|
|
|
For Flask/Django integration, configure the middleware to read the API key
|
|
from the configured apiKeyFile path.
|
|
|
|
[NIS2 COMPLIANCE]
|
|
Article 21(2)(d) - Network Security: Provides application-layer protection
|
|
for Python web applications through CrowdSec integration.
|
|
'';
|
|
default = false;
|
|
};
|
|
|
|
bouncerName = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = "Name to register the Python bouncer with in CrowdSec.";
|
|
default = "python-bouncer";
|
|
example = "my-flask-app-bouncer";
|
|
};
|
|
|
|
apiKeyFile = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
Path where the bouncer API key will be stored.
|
|
|
|
This file will be readable by the configured group (default: root).
|
|
Python applications need read access to this file to authenticate
|
|
with the CrowdSec Local API.
|
|
'';
|
|
default = "/run/crowdsec-python-bouncer/api_key";
|
|
example = "/run/secrets/crowdsec-python-api-key";
|
|
};
|
|
|
|
apiKeyFileGroup = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
Group that should have read access to the API key file.
|
|
|
|
Set this to match the group your Python application runs as.
|
|
For example, if your Flask app runs as user "flask" in group "flask",
|
|
set this to "flask".
|
|
'';
|
|
default = "root";
|
|
example = "www-data";
|
|
};
|
|
|
|
enableCapi = lib.mkOption {
|
|
type = lib.types.bool;
|
|
description = ''
|
|
Enable Central API (CAPI) credentials for signal sharing with python-capi-sdk.
|
|
|
|
When enabled, this generates machine credentials that can be used with
|
|
the python-capi-sdk to send attack signals to CrowdSec's central infrastructure
|
|
and receive community blocklists.
|
|
|
|
The credentials are stored in the capiCredentialsFile location.
|
|
|
|
Example usage with python-capi-sdk:
|
|
|
|
```python
|
|
from cscapi.client import CAPIClient, CAPIClientConfig
|
|
from cscapi.sql_storage import SQLStorage
|
|
import yaml
|
|
|
|
# Load credentials generated by this module
|
|
with open("/run/crowdsec-python-bouncer/capi_credentials.yaml") as f:
|
|
creds = yaml.safe_load(f)
|
|
|
|
client = CAPIClient(
|
|
storage=SQLStorage(connection_string="sqlite:///signals.db"),
|
|
config=CAPIClientConfig(scenarios=["crowdsecurity/ssh-bf"])
|
|
)
|
|
```
|
|
|
|
[NIS2 COMPLIANCE]
|
|
Article 14 - Information Sharing: Enables participation in threat
|
|
intelligence sharing through the CrowdSec community network.
|
|
'';
|
|
default = false;
|
|
};
|
|
|
|
capiCredentialsFile = lib.mkOption {
|
|
type = lib.types.str;
|
|
description = ''
|
|
Path where CAPI credentials will be stored for python-capi-sdk.
|
|
|
|
This file contains machine_id and password for authenticating
|
|
with CrowdSec's Central API.
|
|
'';
|
|
default = "/run/crowdsec-python-bouncer/capi_credentials.yaml";
|
|
example = "/run/secrets/crowdsec-capi-credentials.yaml";
|
|
};
|
|
|
|
capiScenarios = lib.mkOption {
|
|
type = lib.types.listOf lib.types.str;
|
|
description = ''
|
|
Scenarios that your Python application will report signals for.
|
|
|
|
These should match the attack patterns your application detects.
|
|
Common scenarios include:
|
|
- crowdsecurity/ssh-bf (SSH brute force)
|
|
- crowdsecurity/http-bf (HTTP brute force)
|
|
- crowdsecurity/http-crawl-non_statics (Web crawling)
|
|
'';
|
|
default = [];
|
|
example = [ "crowdsecurity/http-bf" "crowdsecurity/http-crawl-non_statics" ];
|
|
};
|
|
};
|
|
|
|
# ==========================================================================
|
|
# Configuration
|
|
# ==========================================================================
|
|
config = lib.mkIf (cfg.enable && cfg.python.enable) (
|
|
let
|
|
# Get the directory from the apiKeyFile path
|
|
pythonBouncerDir = builtins.dirOf cfg.python.apiKeyFile;
|
|
pythonCapiDir = builtins.dirOf cfg.python.capiCredentialsFile;
|
|
|
|
# Python bouncer registration script
|
|
pythonBouncerRegisterScript = pkgs.writeShellScript "crowdsec-python-bouncer-register" ''
|
|
set -e
|
|
export PATH="${lib.makeBinPath [ cfg.package pkgs.coreutils pkgs.gnugrep pkgs.gnused ]}:$PATH"
|
|
|
|
CONFIG_DIR="${stateDir}/config"
|
|
KEY_FILE="${cfg.python.apiKeyFile}"
|
|
KEY_DIR="${pythonBouncerDir}"
|
|
BOUNCER_NAME="${cfg.python.bouncerName}"
|
|
KEY_GROUP="${cfg.python.apiKeyFileGroup}"
|
|
|
|
# Create directory if it doesn't exist
|
|
mkdir -p "$KEY_DIR"
|
|
|
|
# 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 "$BOUNCER_NAME"; then
|
|
# Register new bouncer and save key
|
|
KEY=$(cscli -c "$CONFIG_DIR/config.yaml" bouncers add "$BOUNCER_NAME" -o raw 2>/dev/null || echo "")
|
|
if [ -n "$KEY" ]; then
|
|
echo "$KEY" > "$KEY_FILE"
|
|
# Set permissions: owner read/write, group read
|
|
chmod 640 "$KEY_FILE"
|
|
chown root:"$KEY_GROUP" "$KEY_FILE"
|
|
echo "Python bouncer '$BOUNCER_NAME' registered successfully"
|
|
echo "API key stored at: $KEY_FILE"
|
|
fi
|
|
else
|
|
echo "Python bouncer '$BOUNCER_NAME' already registered"
|
|
fi
|
|
|
|
# Create a helper config file for Python applications
|
|
LAPI_URL="http://${cfg.api.listenAddr}:${toString cfg.api.listenPort}/"
|
|
cat > "$KEY_DIR/config.yaml" << EOF
|
|
# CrowdSec Python Bouncer Configuration
|
|
# Generated by NixOS infrastructure.crowdsec module
|
|
#
|
|
# Usage with pycrowdsec:
|
|
# from pycrowdsec.client import StreamClient
|
|
# import yaml
|
|
#
|
|
# with open('${pythonBouncerDir}/config.yaml') as f:
|
|
# config = yaml.safe_load(f)
|
|
#
|
|
# client = StreamClient(
|
|
# api_key=open(config['api_key_file']).read().strip(),
|
|
# lapi_url=config['lapi_url']
|
|
# )
|
|
# client.run()
|
|
|
|
lapi_url: "$LAPI_URL"
|
|
api_key_file: "$KEY_FILE"
|
|
bouncer_name: "$BOUNCER_NAME"
|
|
EOF
|
|
chmod 644 "$KEY_DIR/config.yaml"
|
|
chown root:"$KEY_GROUP" "$KEY_DIR/config.yaml"
|
|
'';
|
|
|
|
# Python CAPI credentials script (for python-capi-sdk signal sharing)
|
|
pythonCapiRegisterScript = pkgs.writeShellScript "crowdsec-python-capi-register" ''
|
|
set -e
|
|
export PATH="${lib.makeBinPath [ cfg.package pkgs.coreutils pkgs.gnugrep pkgs.gnused pkgs.openssl ]}:$PATH"
|
|
|
|
CONFIG_DIR="${stateDir}/config"
|
|
CAPI_FILE="${cfg.python.capiCredentialsFile}"
|
|
CAPI_DIR="${pythonCapiDir}"
|
|
KEY_GROUP="${cfg.python.apiKeyFileGroup}"
|
|
|
|
# Create directory if it doesn't exist
|
|
mkdir -p "$CAPI_DIR"
|
|
|
|
# Generate unique machine ID based on hostname and a random component
|
|
MACHINE_ID="python-$(hostname)-$(openssl rand -hex 4)"
|
|
|
|
# Generate a secure password
|
|
MACHINE_PASSWORD=$(openssl rand -base64 32)
|
|
|
|
# Check if credentials already exist
|
|
if [ -f "$CAPI_FILE" ]; then
|
|
echo "CAPI credentials already exist at $CAPI_FILE"
|
|
else
|
|
# Create credentials file for python-capi-sdk
|
|
cat > "$CAPI_FILE" << EOF
|
|
# CrowdSec Central API Credentials for python-capi-sdk
|
|
# Generated by NixOS infrastructure.crowdsec module
|
|
#
|
|
# Usage with python-capi-sdk:
|
|
# from cscapi.client import CAPIClient, CAPIClientConfig
|
|
# from cscapi.sql_storage import SQLStorage
|
|
# from cscapi.utils import generate_machine_id_from_key
|
|
# import yaml
|
|
#
|
|
# with open('${cfg.python.capiCredentialsFile}') as f:
|
|
# creds = yaml.safe_load(f)
|
|
#
|
|
# client = CAPIClient(
|
|
# storage=SQLStorage(connection_string="sqlite:///signals.db"),
|
|
# config=CAPIClientConfig(
|
|
# scenarios=${builtins.toJSON cfg.python.capiScenarios}
|
|
# )
|
|
# )
|
|
#
|
|
# # Note: Machine enrollment with CrowdSec CAPI requires manual approval
|
|
# # Contact CrowdSec for signal sharing partnership details
|
|
|
|
machine_id: "$MACHINE_ID"
|
|
password: "$MACHINE_PASSWORD"
|
|
scenarios: ${builtins.toJSON cfg.python.capiScenarios}
|
|
capi_url: "https://api.crowdsec.net/"
|
|
|
|
# Local API connection (for reading decisions)
|
|
lapi_url: "http://${cfg.api.listenAddr}:${toString cfg.api.listenPort}/"
|
|
EOF
|
|
chmod 640 "$CAPI_FILE"
|
|
chown root:"$KEY_GROUP" "$CAPI_FILE"
|
|
echo "CAPI credentials generated at $CAPI_FILE"
|
|
echo ""
|
|
echo "NOTE: To share signals with CrowdSec CAPI, you need to:"
|
|
echo "1. Contact CrowdSec for signal sharing partnership enrollment"
|
|
echo "2. Use the machine_id from this file when enrolling"
|
|
echo "3. Update your application to use the python-capi-sdk"
|
|
fi
|
|
'';
|
|
|
|
in {
|
|
# Python bouncer registration service (oneshot - registers bouncer and stores API key)
|
|
systemd.services.crowdsec-python-bouncer = {
|
|
description = "CrowdSec Python Bouncer Registration";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" "crowdsec.service" ];
|
|
requires = [ "crowdsec.service" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
ExecStart = "${pythonBouncerRegisterScript}";
|
|
};
|
|
};
|
|
|
|
# Python CAPI credentials service (oneshot - generates CAPI credentials for signal sharing)
|
|
systemd.services.crowdsec-python-capi = lib.mkIf cfg.python.enableCapi {
|
|
description = "CrowdSec Python CAPI Credentials Generation";
|
|
wantedBy = [ "multi-user.target" ];
|
|
after = [ "network.target" "crowdsec.service" "crowdsec-python-bouncer.service" ];
|
|
requires = [ "crowdsec.service" ];
|
|
|
|
serviceConfig = {
|
|
Type = "oneshot";
|
|
RemainAfterExit = true;
|
|
ExecStart = "${pythonCapiRegisterScript}";
|
|
};
|
|
};
|
|
}
|
|
);
|
|
}
|