mirror of
https://github.com/rubenhensen/k8scd.git
synced 2026-09-18 18:42:56 +02:00
Add nix-infra-machine
This commit is contained in:
@@ -0,0 +1,318 @@
|
||||
# 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";
|
||||
};
|
||||
};
|
||||
}
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,181 @@
|
||||
# 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";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,313 @@
|
||||
# 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}";
|
||||
};
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,730 @@
|
||||
# CrowdSec - Collaborative Intrusion Prevention System
|
||||
#
|
||||
# This module provides simplified boolean feature toggles for common use cases
|
||||
# and can use either a custom implementation or the native NixOS module.
|
||||
#
|
||||
# Module structure:
|
||||
# - default.nix: Core CrowdSec engine and detection features
|
||||
# - bouncers/: Response modules (firewall, haproxy, python)
|
||||
# - integrations/: External system integrations (auditd, console)
|
||||
{ config, pkgs, lib, options, ... }:
|
||||
|
||||
let
|
||||
appName = "crowdsec";
|
||||
cfg = config.infrastructure.${appName};
|
||||
|
||||
# ==========================================================================
|
||||
# Version Detection (must not depend on cfg to avoid recursion)
|
||||
# ==========================================================================
|
||||
# Check if the native services.crowdsec module exists (NixOS 25.11+)
|
||||
hasNativeCrowdsecModule = options ? services && options.services ? crowdsec;
|
||||
|
||||
# The native module in NixOS 25.11 has multiple bugs that make it unusable:
|
||||
# - #445342: Missing sensible defaults, API server disabled by default
|
||||
# - #446764: Console enrollment broken
|
||||
# - #459224: Cannot enable local API
|
||||
# - Missing hub.postoverflows, hub.scenarios, hub.parsers options
|
||||
# - Null coercion errors in systemd service generation
|
||||
#
|
||||
# We mark the native module as unstable until these are fixed.
|
||||
# Users can override with implementation = "native" to test.
|
||||
nativeModuleIsStable = false;
|
||||
|
||||
# State directory for CrowdSec
|
||||
stateDir = "/var/lib/crowdsec";
|
||||
|
||||
# Helper to generate YAML format
|
||||
yamlFormat = pkgs.formats.yaml {};
|
||||
|
||||
in
|
||||
{
|
||||
# ==========================================================================
|
||||
# Import Sub-Modules
|
||||
# ==========================================================================
|
||||
imports = [
|
||||
# Bouncers - Response mechanisms
|
||||
./bouncers/firewall.nix
|
||||
./bouncers/haproxy.nix
|
||||
./bouncers/python.nix
|
||||
# Integrations - External system connections
|
||||
./integrations/auditd.nix
|
||||
./integrations/console.nix
|
||||
];
|
||||
|
||||
# ==========================================================================
|
||||
# Options
|
||||
# ==========================================================================
|
||||
options.infrastructure.${appName} = {
|
||||
enable = lib.mkEnableOption ''
|
||||
CrowdSec - Collaborative Intrusion Prevention System.
|
||||
|
||||
CrowdSec is an open-source security automation tool that detects and blocks
|
||||
malicious behavior by analyzing logs and sharing threat intelligence with
|
||||
the community.
|
||||
|
||||
This module provides simplified boolean feature toggles for common use cases
|
||||
and can use either a custom implementation or the native NixOS module.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(b) - Incident Handling: CrowdSec provides automated threat
|
||||
detection and response capabilities, helping organizations meet requirements
|
||||
for detecting, analyzing, and responding to cybersecurity incidents.
|
||||
|
||||
Article 21(2)(d) - Network Security: Acts as an Intrusion Detection/Prevention
|
||||
System (IDS/IPS), a core requirement for protecting network infrastructure.
|
||||
'';
|
||||
|
||||
implementation = lib.mkOption {
|
||||
type = lib.types.enum [ "auto" "native" "custom" ];
|
||||
description = ''
|
||||
Which implementation to use for CrowdSec.
|
||||
|
||||
- "auto": Automatically select based on NixOS version and module stability.
|
||||
Currently defaults to "custom" because the native module has bugs.
|
||||
- "native": Force use of NixOS's native services.crowdsec module.
|
||||
Requires NixOS 25.11+. May have bugs - use for testing only.
|
||||
- "custom": Use the custom implementation that manages its own systemd
|
||||
service. Works on all NixOS versions with the crowdsec package.
|
||||
|
||||
The native module in NixOS 25.11 has several known issues:
|
||||
- #445342: Missing sensible defaults
|
||||
- #446764: Console enrollment broken
|
||||
- #459224: Cannot enable local API
|
||||
|
||||
When these are fixed, "auto" will switch to using the native module.
|
||||
'';
|
||||
default = "auto";
|
||||
example = "custom";
|
||||
};
|
||||
|
||||
package = lib.mkOption {
|
||||
type = lib.types.package;
|
||||
description = "CrowdSec package to use.";
|
||||
default = pkgs.crowdsec;
|
||||
defaultText = lib.literalExpression "pkgs.crowdsec";
|
||||
};
|
||||
|
||||
logLevel = lib.mkOption {
|
||||
type = lib.types.enum [ "trace" "debug" "info" "warning" "error" "fatal" ];
|
||||
description = ''
|
||||
Log level for CrowdSec.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(g) - Security Monitoring: Appropriate logging level
|
||||
enables proper security event monitoring and incident investigation.
|
||||
'';
|
||||
default = "info";
|
||||
example = "debug";
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# API Configuration
|
||||
# ==========================================================================
|
||||
|
||||
api = {
|
||||
listenAddr = lib.mkOption {
|
||||
type = lib.types.str;
|
||||
description = ''
|
||||
Address for the CrowdSec Local API (LAPI) to listen on.
|
||||
Use "127.0.0.1" for local-only access or "0.0.0.0" for network access.
|
||||
'';
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
};
|
||||
|
||||
listenPort = lib.mkOption {
|
||||
type = lib.types.port;
|
||||
description = "Port for the CrowdSec Local API (LAPI) to listen on.";
|
||||
default = 8080;
|
||||
example = 8080;
|
||||
};
|
||||
|
||||
openFirewall = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Whether to open the firewall port for the CrowdSec API.
|
||||
Only needed if bouncers from other machines need to connect.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Detection Features (Simple Boolean Options)
|
||||
# ==========================================================================
|
||||
|
||||
features = {
|
||||
sshProtection = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enable SSH brute-force detection and prevention.
|
||||
|
||||
Monitors SSH authentication logs to detect and block IP addresses
|
||||
attempting password guessing or credential stuffing attacks.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(i) - Human Resources Security: Protects authentication
|
||||
systems and helps prevent unauthorized access attempts.
|
||||
|
||||
Article 21(2)(j) - Access Control: Provides automated protection
|
||||
against credential-based attacks on administrative interfaces.
|
||||
'';
|
||||
default = true;
|
||||
};
|
||||
|
||||
nginxProtection = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enable nginx/web server attack detection.
|
||||
|
||||
Monitors nginx access and error logs to detect web-based attacks
|
||||
including SQL injection, XSS, path traversal, and more.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(d) - Network Security: Provides web application
|
||||
firewall (WAF) capabilities to protect public-facing services.
|
||||
|
||||
Article 21(2)(e) - Supply Chain Security: Helps protect web
|
||||
services that may be part of the digital supply chain.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
|
||||
nginxLogPaths = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "Paths to nginx log files to monitor.";
|
||||
default = [ "/var/log/nginx/*.log" ];
|
||||
example = [ "/var/log/nginx/access.log" "/var/log/nginx/error.log" ];
|
||||
};
|
||||
|
||||
systemProtection = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enable system/kernel-level threat detection.
|
||||
|
||||
Monitors kernel and system logs for suspicious activity including
|
||||
privilege escalation attempts and system abuse.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(a) - Risk Analysis: Provides continuous monitoring
|
||||
to identify and respond to system-level threats.
|
||||
|
||||
Article 21(2)(g) - Security Monitoring: Implements comprehensive
|
||||
security monitoring across the system infrastructure.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
|
||||
communityBlocklists = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enable community-contributed IP blocklists.
|
||||
|
||||
When enrolled in the CrowdSec Console, your instance can receive
|
||||
curated blocklists of known malicious IPs from the community.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(d) - Network Security: Leverages collective threat
|
||||
intelligence to proactively block known attackers.
|
||||
|
||||
Article 14 - Information Sharing: Participates in cybersecurity
|
||||
information sharing to improve collective defense.
|
||||
'';
|
||||
default = true;
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Hub Configuration (Parsers, Scenarios, Collections)
|
||||
# ==========================================================================
|
||||
|
||||
hub = {
|
||||
collections = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = ''
|
||||
Additional CrowdSec Hub collections to install.
|
||||
|
||||
Collections bundle related parsers and scenarios together.
|
||||
Browse available collections at: https://hub.crowdsec.net/
|
||||
'';
|
||||
default = [];
|
||||
example = [ "crowdsecurity/apache2" "crowdsecurity/postfix" ];
|
||||
};
|
||||
|
||||
scenarios = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = ''
|
||||
Additional CrowdSec Hub scenarios to install.
|
||||
|
||||
Scenarios define detection rules for specific attack patterns.
|
||||
'';
|
||||
default = [];
|
||||
example = [ "crowdsecurity/http-bf-wordpress_bf" ];
|
||||
};
|
||||
|
||||
parsers = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = ''
|
||||
Additional CrowdSec Hub parsers to install.
|
||||
|
||||
Parsers extract structured data from log files.
|
||||
'';
|
||||
default = [];
|
||||
example = [ "crowdsecurity/docker-logs" ];
|
||||
};
|
||||
|
||||
postoverflows = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = "Additional post-overflow parsers to install.";
|
||||
default = [];
|
||||
example = [ "crowdsecurity/cdn-whitelist" ];
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Custom Acquisitions
|
||||
# ==========================================================================
|
||||
|
||||
acquisitions = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.attrs;
|
||||
description = ''
|
||||
Additional log sources for CrowdSec to monitor.
|
||||
|
||||
Each acquisition defines a log source (file, journalctl, etc.)
|
||||
and the parser type to use.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(g) - Security Monitoring: Enables comprehensive
|
||||
log collection and monitoring across all systems.
|
||||
'';
|
||||
default = [];
|
||||
example = lib.literalExpression ''
|
||||
[
|
||||
{
|
||||
source = "journalctl";
|
||||
journalctl_filter = [ "_SYSTEMD_UNIT=postgresql.service" ];
|
||||
labels.type = "syslog";
|
||||
}
|
||||
{
|
||||
filenames = [ "/var/log/myapp/*.log" ];
|
||||
labels.type = "syslog";
|
||||
}
|
||||
]
|
||||
'';
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Pass-through Configuration
|
||||
# ==========================================================================
|
||||
|
||||
extraSettings = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
description = ''
|
||||
Extra settings merged into the CrowdSec configuration.
|
||||
For native implementation: merged into services.crowdsec.settings.
|
||||
For custom implementation: merged into the generated config.yaml.
|
||||
'';
|
||||
default = {};
|
||||
};
|
||||
|
||||
extraLocalConfig = lib.mkOption {
|
||||
type = lib.types.attrsOf lib.types.anything;
|
||||
description = ''
|
||||
Extra settings merged into the local configuration.
|
||||
For native implementation: merged into services.crowdsec.localConfig.
|
||||
For custom implementation: not currently used.
|
||||
'';
|
||||
default = {};
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Configuration
|
||||
# ==========================================================================
|
||||
config = lib.mkIf cfg.enable (
|
||||
let
|
||||
# ========================================================================
|
||||
# All cfg-dependent values MUST be defined inside this let block
|
||||
# to avoid infinite recursion during module evaluation
|
||||
# ========================================================================
|
||||
|
||||
# Determine which implementation to use
|
||||
useNativeImplementation =
|
||||
if cfg.implementation == "native" then true
|
||||
else if cfg.implementation == "custom" then false
|
||||
else if cfg.implementation == "auto" then
|
||||
hasNativeCrowdsecModule && nativeModuleIsStable
|
||||
else false;
|
||||
|
||||
# Build acquisitions list based on enabled features
|
||||
acquisitions = lib.flatten [
|
||||
# SSH acquisition (journalctl-based)
|
||||
(lib.optional cfg.features.sshProtection {
|
||||
source = "journalctl";
|
||||
journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ];
|
||||
labels.type = "syslog";
|
||||
})
|
||||
# Nginx acquisition (log file-based)
|
||||
(lib.optional cfg.features.nginxProtection {
|
||||
filenames = cfg.features.nginxLogPaths;
|
||||
labels.type = "nginx";
|
||||
})
|
||||
# System/kernel logs acquisition
|
||||
(lib.optional cfg.features.systemProtection {
|
||||
source = "journalctl";
|
||||
journalctl_filter = [ "_TRANSPORT=kernel" ];
|
||||
labels.type = "syslog";
|
||||
})
|
||||
# Custom acquisitions from user
|
||||
cfg.acquisitions
|
||||
];
|
||||
|
||||
# Build hub collections list based on enabled features
|
||||
hubCollections = lib.flatten [
|
||||
(lib.optional cfg.features.sshProtection "crowdsecurity/sshd")
|
||||
(lib.optional cfg.features.nginxProtection "crowdsecurity/nginx")
|
||||
(lib.optional cfg.features.systemProtection "crowdsecurity/linux")
|
||||
cfg.hub.collections
|
||||
];
|
||||
|
||||
# ========================================================================
|
||||
# Custom Implementation: Configuration Files
|
||||
# ========================================================================
|
||||
|
||||
# Generate acquisitions file as multi-document YAML
|
||||
# CrowdSec expects each acquisition as a separate YAML document (separated by ---)
|
||||
# We use yamlFormat.generate for each acquisition and concatenate them
|
||||
acquisitionsFile = pkgs.writeText "acquisitions.yaml" (
|
||||
lib.concatMapStringsSep "\n---\n" (acq:
|
||||
builtins.readFile (yamlFormat.generate "acq.yaml" acq)
|
||||
) acquisitions
|
||||
);
|
||||
|
||||
# Generate simulation file (CrowdSec requires this)
|
||||
simulationFile = yamlFormat.generate "simulation.yaml" {
|
||||
simulation = false;
|
||||
exclusions = [];
|
||||
};
|
||||
|
||||
# Generate main config file (compatible with CrowdSec 1.7.x)
|
||||
configFile = yamlFormat.generate "config.yaml" {
|
||||
common = {
|
||||
daemonize = false;
|
||||
log_media = "stdout";
|
||||
log_level = cfg.logLevel;
|
||||
};
|
||||
config_paths = {
|
||||
config_dir = "${stateDir}/config";
|
||||
data_dir = "${stateDir}/data";
|
||||
hub_dir = "${stateDir}/hub";
|
||||
simulation_path = "${stateDir}/config/simulation.yaml";
|
||||
};
|
||||
crowdsec_service = {
|
||||
acquisition_path = "${stateDir}/config/acquisitions.yaml";
|
||||
parser_routines = 1;
|
||||
};
|
||||
cscli = {
|
||||
output = "human";
|
||||
};
|
||||
api = {
|
||||
client = {
|
||||
insecure_skip_verify = false;
|
||||
credentials_path = "${stateDir}/config/local_api_credentials.yaml";
|
||||
};
|
||||
server = {
|
||||
enable = true;
|
||||
listen_uri = "${cfg.api.listenAddr}:${toString cfg.api.listenPort}";
|
||||
profiles_path = "${stateDir}/config/profiles.yaml";
|
||||
online_client = {
|
||||
credentials_path = "${stateDir}/config/online_api_credentials.yaml";
|
||||
};
|
||||
};
|
||||
};
|
||||
db_config = {
|
||||
type = "sqlite";
|
||||
db_path = "${stateDir}/data/crowdsec.db";
|
||||
use_wal = true;
|
||||
};
|
||||
};
|
||||
|
||||
# Generate profiles file (CrowdSec expects multi-document YAML format)
|
||||
# Use bouncer.banDuration if available, otherwise default to 4h
|
||||
banDuration = cfg.bouncer.banDuration or "4h";
|
||||
profilesFile = pkgs.writeText "profiles.yaml" ''
|
||||
name: default_ip_remediation
|
||||
filters:
|
||||
- Alert.Remediation == true && Alert.GetScope() == "Ip"
|
||||
decisions:
|
||||
- type: ban
|
||||
duration: ${banDuration}
|
||||
on_success: break
|
||||
'';
|
||||
|
||||
# Initialization script - sets up CrowdSec on first run
|
||||
initScript = pkgs.writeShellScript "crowdsec-init" ''
|
||||
set -e
|
||||
export PATH="${lib.makeBinPath [ cfg.package pkgs.coreutils pkgs.gnugrep pkgs.nettools pkgs.findutils ]}:$PATH"
|
||||
|
||||
STATE_DIR="${stateDir}"
|
||||
CONFIG_DIR="$STATE_DIR/config"
|
||||
DATA_DIR="$STATE_DIR/data"
|
||||
HUB_DIR="$STATE_DIR/hub"
|
||||
PACKAGE="${cfg.package}"
|
||||
|
||||
# Create directories
|
||||
mkdir -p "$CONFIG_DIR" "$DATA_DIR" "$HUB_DIR"
|
||||
|
||||
# Copy configuration files
|
||||
cp -f ${configFile} "$CONFIG_DIR/config.yaml"
|
||||
cp -f ${profilesFile} "$CONFIG_DIR/profiles.yaml"
|
||||
cp -f ${acquisitionsFile} "$CONFIG_DIR/acquisitions.yaml"
|
||||
cp -f ${simulationFile} "$CONFIG_DIR/simulation.yaml"
|
||||
|
||||
# Debug: Show acquisitions file content
|
||||
echo "Generated acquisitions.yaml:"
|
||||
cat "$CONFIG_DIR/acquisitions.yaml"
|
||||
echo ""
|
||||
|
||||
# Copy patterns directory from package (required for parser grok patterns)
|
||||
echo "Looking for patterns directory..."
|
||||
|
||||
# Try common locations
|
||||
PATTERNS_FOUND=0
|
||||
for PATTERNS_PATH in \
|
||||
"$PACKAGE/share/crowdsec/config/patterns" \
|
||||
"$PACKAGE/share/crowdsec/patterns" \
|
||||
"$PACKAGE/etc/crowdsec/patterns" \
|
||||
; do
|
||||
if [ -d "$PATTERNS_PATH" ]; then
|
||||
echo "Found patterns at: $PATTERNS_PATH"
|
||||
rm -rf "$CONFIG_DIR/patterns"
|
||||
cp -r "$PATTERNS_PATH" "$CONFIG_DIR/patterns"
|
||||
PATTERNS_FOUND=1
|
||||
break
|
||||
fi
|
||||
done
|
||||
|
||||
# If not found in common locations, search the entire package
|
||||
if [ "$PATTERNS_FOUND" = "0" ]; then
|
||||
echo "Searching for patterns directory in package..."
|
||||
PATTERNS_PATH=$(find "$PACKAGE" -type d -name "patterns" 2>/dev/null | head -1)
|
||||
if [ -n "$PATTERNS_PATH" ]; then
|
||||
echo "Found patterns at: $PATTERNS_PATH"
|
||||
rm -rf "$CONFIG_DIR/patterns"
|
||||
cp -r "$PATTERNS_PATH" "$CONFIG_DIR/patterns"
|
||||
PATTERNS_FOUND=1
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ "$PATTERNS_FOUND" = "0" ]; then
|
||||
echo "WARNING: Could not find patterns directory!"
|
||||
echo "Package contents:"
|
||||
ls -la "$PACKAGE/" || true
|
||||
ls -la "$PACKAGE/share/" || true
|
||||
ls -la "$PACKAGE/share/crowdsec/" 2>/dev/null || true
|
||||
fi
|
||||
|
||||
# Initialize database if it doesn't exist
|
||||
if [ ! -f "$DATA_DIR/crowdsec.db" ]; then
|
||||
echo "Initializing CrowdSec database..."
|
||||
touch "$CONFIG_DIR/local_api_credentials.yaml"
|
||||
touch "$CONFIG_DIR/online_api_credentials.yaml"
|
||||
chmod 640 "$CONFIG_DIR/local_api_credentials.yaml"
|
||||
chmod 640 "$CONFIG_DIR/online_api_credentials.yaml"
|
||||
fi
|
||||
|
||||
# Generate machine ID if it doesn't exist
|
||||
if [ ! -f "$CONFIG_DIR/local_api_credentials.yaml" ] || [ ! -s "$CONFIG_DIR/local_api_credentials.yaml" ]; then
|
||||
echo "Registering local machine..."
|
||||
cscli -c "$CONFIG_DIR/config.yaml" machines add "$(hostname)" --auto --force || true
|
||||
fi
|
||||
|
||||
# Update hub index
|
||||
echo "Updating hub index..."
|
||||
cscli -c "$CONFIG_DIR/config.yaml" hub update || true
|
||||
|
||||
# Set correct ownership
|
||||
chown -R crowdsec:crowdsec "$STATE_DIR"
|
||||
'';
|
||||
|
||||
# Hub installation script (runs after service is started)
|
||||
hubInstallScript = pkgs.writeShellScript "crowdsec-hub-install" ''
|
||||
set -e
|
||||
export PATH="${lib.makeBinPath [ cfg.package pkgs.coreutils pkgs.gnugrep ]}:$PATH"
|
||||
|
||||
CONFIG_DIR="${stateDir}/config"
|
||||
|
||||
# Wait for API to be ready
|
||||
for i in $(seq 1 30); do
|
||||
if cscli -c "$CONFIG_DIR/config.yaml" hub list >/dev/null 2>&1; then
|
||||
break
|
||||
fi
|
||||
sleep 1
|
||||
done
|
||||
|
||||
# Install collections
|
||||
${lib.concatMapStringsSep "\n" (c: ''
|
||||
if ! cscli -c "$CONFIG_DIR/config.yaml" collections list 2>/dev/null | grep -q "${c}"; then
|
||||
cscli -c "$CONFIG_DIR/config.yaml" collections install ${c} || true
|
||||
fi
|
||||
'') hubCollections}
|
||||
|
||||
# Install additional scenarios
|
||||
${lib.concatMapStringsSep "\n" (s: ''
|
||||
if ! cscli -c "$CONFIG_DIR/config.yaml" scenarios list 2>/dev/null | grep -q "${s}"; then
|
||||
cscli -c "$CONFIG_DIR/config.yaml" scenarios install ${s} || true
|
||||
fi
|
||||
'') cfg.hub.scenarios}
|
||||
|
||||
# Install additional parsers
|
||||
${lib.concatMapStringsSep "\n" (p: ''
|
||||
if ! cscli -c "$CONFIG_DIR/config.yaml" parsers list 2>/dev/null | grep -q "${p}"; then
|
||||
cscli -c "$CONFIG_DIR/config.yaml" parsers install ${p} || true
|
||||
fi
|
||||
'') cfg.hub.parsers}
|
||||
'';
|
||||
|
||||
in lib.mkMerge [
|
||||
|
||||
# ==========================================================================
|
||||
# Common Configuration (both implementations)
|
||||
# ==========================================================================
|
||||
{
|
||||
# Assertions
|
||||
assertions = [
|
||||
{
|
||||
assertion = acquisitions != [];
|
||||
message = ''
|
||||
CrowdSec requires at least one acquisition source.
|
||||
|
||||
Enable at least one of:
|
||||
- infrastructure.crowdsec.features.sshProtection = true
|
||||
- infrastructure.crowdsec.features.nginxProtection = true
|
||||
- infrastructure.crowdsec.features.systemProtection = true
|
||||
|
||||
Or add custom acquisitions via infrastructure.crowdsec.acquisitions
|
||||
'';
|
||||
}
|
||||
{
|
||||
assertion = cfg.implementation != "native" || hasNativeCrowdsecModule;
|
||||
message = ''
|
||||
CrowdSec native implementation requires NixOS 25.11 or later.
|
||||
|
||||
Either:
|
||||
1. Upgrade to NixOS 25.11+
|
||||
2. Set infrastructure.crowdsec.implementation = "custom"
|
||||
3. Set infrastructure.crowdsec.implementation = "auto" (recommended)
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
# Open firewall for LAPI if configured
|
||||
networking.firewall.allowedTCPPorts =
|
||||
lib.mkIf cfg.api.openFirewall [ cfg.api.listenPort ];
|
||||
|
||||
# Install useful CLI tools
|
||||
environment.systemPackages = [
|
||||
cfg.package # Includes cscli
|
||||
];
|
||||
}
|
||||
|
||||
# ==========================================================================
|
||||
# Custom Implementation
|
||||
# ==========================================================================
|
||||
(lib.mkIf (!useNativeImplementation) {
|
||||
# Create crowdsec user and group
|
||||
users.users.crowdsec = {
|
||||
isSystemUser = true;
|
||||
group = "crowdsec";
|
||||
home = stateDir;
|
||||
description = "CrowdSec daemon user";
|
||||
};
|
||||
users.groups.crowdsec = {};
|
||||
|
||||
# Ensure data directories exist and create config symlink for cscli
|
||||
systemd.tmpfiles.rules = [
|
||||
"d ${stateDir} 0755 crowdsec crowdsec - -"
|
||||
"d ${stateDir}/config 0755 crowdsec crowdsec - -"
|
||||
"d ${stateDir}/data 0755 crowdsec crowdsec - -"
|
||||
"d ${stateDir}/hub 0755 crowdsec crowdsec - -"
|
||||
# Create /etc/crowdsec directory and symlink for cscli default config path
|
||||
"L+ /etc/crowdsec/config.yaml - - - - ${stateDir}/config/config.yaml"
|
||||
];
|
||||
|
||||
# Main CrowdSec service
|
||||
systemd.services.crowdsec = {
|
||||
description = "CrowdSec Security Engine";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "local-fs.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
Type = "simple";
|
||||
User = "crowdsec";
|
||||
Group = "crowdsec";
|
||||
ExecStartPre = [
|
||||
"+${initScript}" # Run as root for permissions
|
||||
];
|
||||
ExecStart = "${cfg.package}/bin/crowdsec -c ${stateDir}/config/config.yaml";
|
||||
ExecStartPost = "${hubInstallScript}";
|
||||
Restart = "always";
|
||||
RestartSec = "10s";
|
||||
|
||||
# Security hardening
|
||||
ProtectSystem = "strict";
|
||||
ProtectHome = true;
|
||||
PrivateTmp = true;
|
||||
NoNewPrivileges = true;
|
||||
ReadWritePaths = [ stateDir ];
|
||||
|
||||
# Allow journal access for systemd log sources
|
||||
SupplementaryGroups = lib.optional (cfg.features.sshProtection || cfg.features.systemProtection) "systemd-journal";
|
||||
};
|
||||
};
|
||||
})
|
||||
|
||||
# ==========================================================================
|
||||
# Native Implementation (NixOS 25.11+)
|
||||
# ==========================================================================
|
||||
(lib.mkIf (useNativeImplementation && hasNativeCrowdsecModule) {
|
||||
# Workarounds for native module bugs
|
||||
systemd.tmpfiles.rules = [
|
||||
# WORKAROUND #445342: Create state directory
|
||||
# WORKAROUND #446764: Create online_api_credentials.yaml
|
||||
"f /var/lib/crowdsec/online_api_credentials.yaml 0640 crowdsec crowdsec - -"
|
||||
];
|
||||
|
||||
services.crowdsec = {
|
||||
enable = true;
|
||||
package = cfg.package;
|
||||
|
||||
# Hub items to install (only collections - other options may not exist)
|
||||
hub = {
|
||||
collections = hubCollections;
|
||||
};
|
||||
|
||||
# Local configuration (acquisitions)
|
||||
localConfig = {
|
||||
inherit acquisitions;
|
||||
} // cfg.extraLocalConfig;
|
||||
|
||||
# Main settings
|
||||
settings = lib.mkMerge [
|
||||
{
|
||||
# WORKAROUND: BUG #445342 - Enable API server by default
|
||||
general.api.server.enable = true;
|
||||
}
|
||||
|
||||
# Console enrollment (if configured)
|
||||
# Note: console options are defined in integrations/console.nix
|
||||
(lib.mkIf (cfg.console.enrollKeyFile != null) {
|
||||
console.tokenFile = cfg.console.enrollKeyFile;
|
||||
})
|
||||
|
||||
# User's extra settings
|
||||
cfg.extraSettings
|
||||
];
|
||||
};
|
||||
})
|
||||
]
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
# CrowdSec Auditd Integration Module
|
||||
# Provides kernel-level security event monitoring via Linux Audit Framework
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
appName = "crowdsec";
|
||||
cfg = config.infrastructure.${appName};
|
||||
in
|
||||
{
|
||||
# ==========================================================================
|
||||
# Options
|
||||
# ==========================================================================
|
||||
options.infrastructure.${appName}.auditd = {
|
||||
enable = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Enable auditd integration with CrowdSec.
|
||||
|
||||
When enabled, configures auditd to send audit events to CrowdSec
|
||||
for analysis. This enables detection of:
|
||||
- Privilege escalation attempts
|
||||
- Unauthorized file access
|
||||
- System call anomalies
|
||||
- User authentication events
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(g) - Security Monitoring: Provides kernel-level
|
||||
visibility into security events and potential threats.
|
||||
'';
|
||||
default = false;
|
||||
};
|
||||
|
||||
rules = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = ''
|
||||
Additional auditd rules to configure for CrowdSec monitoring.
|
||||
|
||||
These rules are added to the system's auditd configuration.
|
||||
|
||||
Common rules for security monitoring:
|
||||
- File integrity: "-w /etc/passwd -p wa -k identity"
|
||||
- Privilege escalation: "-w /usr/bin/sudo -p x -k privilege"
|
||||
- Network configuration: "-w /etc/hosts -p wa -k network"
|
||||
'';
|
||||
default = [];
|
||||
example = [
|
||||
"-w /etc/passwd -p wa -k identity"
|
||||
"-w /etc/shadow -p wa -k identity"
|
||||
"-w /etc/sudoers -p wa -k privilege"
|
||||
];
|
||||
};
|
||||
|
||||
nixWrappersWhitelistProcess = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = ''
|
||||
List of process names to whitelist from auditd monitoring.
|
||||
|
||||
NOTE: This feature is currently disabled due to compatibility issues
|
||||
with the 'comm' field filter in some versions of auditd. The option
|
||||
is preserved for future use when auditd compatibility is resolved.
|
||||
|
||||
NixOS uses wrapper scripts in /run/wrappers/bin for setuid/setgid
|
||||
programs (like sudo, ping, etc.). These wrappers can generate a lot
|
||||
of noise in auditd logs.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(g) - Security Monitoring: Reduces audit log noise
|
||||
while maintaining security visibility on critical processes.
|
||||
'';
|
||||
default = [];
|
||||
example = [ "sshd" "systemd" "sudo" ];
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Configuration
|
||||
# ==========================================================================
|
||||
config = lib.mkIf (cfg.enable && cfg.auditd.enable) {
|
||||
# Enable the Linux Audit daemon
|
||||
security.auditd.enable = true;
|
||||
|
||||
# Add user-defined audit rules
|
||||
# Note: The nixWrappersWhitelistProcess feature is currently disabled
|
||||
# due to auditd compatibility issues with the 'comm' field filter
|
||||
security.audit.rules = cfg.auditd.rules;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,97 @@
|
||||
# CrowdSec Console Integration Module
|
||||
# Provides cloud enrollment and community threat intelligence sharing
|
||||
{ config, pkgs, lib, ... }:
|
||||
|
||||
let
|
||||
appName = "crowdsec";
|
||||
cfg = config.infrastructure.${appName};
|
||||
in
|
||||
{
|
||||
# ==========================================================================
|
||||
# Options
|
||||
# ==========================================================================
|
||||
options.infrastructure.${appName}.console = {
|
||||
enrollKeyFile = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Path to file containing the CrowdSec Console enrollment key.
|
||||
|
||||
Enrolling connects your instance to the CrowdSec Console for:
|
||||
- Centralized monitoring and management
|
||||
- Access to community and commercial blocklists
|
||||
- Threat intelligence dashboards
|
||||
- Alert visualization and analytics
|
||||
|
||||
Get your enrollment key from: https://app.crowdsec.net/
|
||||
|
||||
The enrollment key should be stored securely, for example using
|
||||
agenix or sops-nix for secrets management.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 21(2)(g) - Security Monitoring: Provides centralized
|
||||
visibility into security events across infrastructure.
|
||||
|
||||
Article 23 - Reporting: Facilitates incident documentation
|
||||
and reporting through centralized logging.
|
||||
'';
|
||||
default = null;
|
||||
example = "/run/secrets/crowdsec-enroll-key";
|
||||
};
|
||||
|
||||
shareDecisions = lib.mkOption {
|
||||
type = lib.types.bool;
|
||||
description = ''
|
||||
Share your detected threats with the CrowdSec community.
|
||||
|
||||
When enabled, anonymized attack signals are shared to improve
|
||||
collective threat intelligence for all CrowdSec users. This is
|
||||
a key part of CrowdSec's collaborative security model.
|
||||
|
||||
Shared data includes:
|
||||
- Source IP addresses of attacks
|
||||
- Attack type/scenario that triggered
|
||||
- Timestamp of the attack
|
||||
|
||||
Personal data and log contents are NOT shared.
|
||||
|
||||
[NIS2 COMPLIANCE]
|
||||
Article 14 - Information Sharing: Contributes to EU-wide
|
||||
cybersecurity by participating in threat intelligence sharing.
|
||||
'';
|
||||
default = true;
|
||||
};
|
||||
|
||||
name = lib.mkOption {
|
||||
type = lib.types.nullOr lib.types.str;
|
||||
description = ''
|
||||
Custom name for this instance in the CrowdSec Console.
|
||||
|
||||
If not set, the hostname will be used. Useful for identifying
|
||||
machines in multi-server deployments.
|
||||
'';
|
||||
default = null;
|
||||
example = "web-server-01";
|
||||
};
|
||||
|
||||
tags = lib.mkOption {
|
||||
type = lib.types.listOf lib.types.str;
|
||||
description = ''
|
||||
Tags to apply to this instance in the CrowdSec Console.
|
||||
|
||||
Tags help organize and filter machines in the console dashboard.
|
||||
'';
|
||||
default = [];
|
||||
example = [ "production" "web-tier" "eu-west" ];
|
||||
};
|
||||
};
|
||||
|
||||
# ==========================================================================
|
||||
# Configuration
|
||||
# ==========================================================================
|
||||
# Note: The actual console enrollment is handled by the main module
|
||||
# since it requires integration with both native and custom implementations.
|
||||
# This module only defines the options.
|
||||
#
|
||||
# For native implementation: settings are passed to services.crowdsec.settings
|
||||
# For custom implementation: enrollment is done via cscli in the init script
|
||||
}
|
||||
Reference in New Issue
Block a user