2026-04-25 15:00:31 +01:00
|
|
|
# services/crowdsec.nix — Vendors the crowdsec module rewrite from
|
|
|
|
|
# https://github.com/NixOS/nixpkgs/pull/446307 (TornaxO7's branch) until
|
|
|
|
|
# it lands upstream. The upstream module in nixpkgs at the pinned revision
|
|
|
|
|
# is broken for first-time bootstrap (no auto cscli machines add, DynamicUser
|
|
|
|
|
# state ownership wedges).
|
2026-04-24 22:30:16 +01:00
|
|
|
#
|
2026-04-25 15:00:31 +01:00
|
|
|
# When PR #446307 merges to nixpkgs unstable:
|
|
|
|
|
# 1. Bump flake.lock past the merge commit
|
|
|
|
|
# 2. Delete ../modules/crowdsec/ and the disabledModules + imports lines below
|
|
|
|
|
# 3. The settings/option API is the same as the PR's, so config below is forward-compatible
|
2026-04-24 22:30:16 +01:00
|
|
|
#
|
2026-04-25 15:00:31 +01:00
|
|
|
# Before first deploy, create /var/secrets/ntfy-url with your topic URL:
|
2026-04-24 22:30:16 +01:00
|
|
|
# echo 'https://ntfy.sh/nordhammer-<random>' | sudo tee /var/secrets/ntfy-url
|
|
|
|
|
# sudo chmod 640 /var/secrets/ntfy-url
|
|
|
|
|
{ config, lib, ... }:
|
|
|
|
|
let
|
|
|
|
|
ntfyUrlFile = "/var/secrets/ntfy-url";
|
|
|
|
|
ntfyUrl =
|
|
|
|
|
if builtins.pathExists ntfyUrlFile
|
|
|
|
|
then lib.removeSuffix "\n" (builtins.readFile ntfyUrlFile)
|
|
|
|
|
else "https://ntfy.sh/CHANGE-ME-CREATE-VAR-SECRETS-NTFY-URL";
|
|
|
|
|
in
|
|
|
|
|
{
|
2026-04-25 15:00:31 +01:00
|
|
|
disabledModules = [
|
|
|
|
|
"services/security/crowdsec.nix"
|
|
|
|
|
"services/security/crowdsec-firewall-bouncer.nix"
|
|
|
|
|
];
|
|
|
|
|
|
|
|
|
|
imports = [
|
|
|
|
|
../modules/crowdsec/crowdsec.nix
|
|
|
|
|
../modules/crowdsec/crowdsec-firewall-bouncer.nix
|
|
|
|
|
];
|
|
|
|
|
|
2026-04-24 22:30:16 +01:00
|
|
|
config = lib.mkIf (config.networking.hostName == "FredOS-Mediaserver") {
|
|
|
|
|
|
|
|
|
|
services.crowdsec = {
|
|
|
|
|
enable = true;
|
2026-04-25 15:00:31 +01:00
|
|
|
name = "fredos-mediaserver";
|
2026-04-24 22:30:16 +01:00
|
|
|
|
|
|
|
|
hub.collections = [
|
2026-04-25 15:00:31 +01:00
|
|
|
"crowdsecurity/linux" # sshd + linux LPE
|
|
|
|
|
"crowdsecurity/nginx" # nginx parser
|
|
|
|
|
"crowdsecurity/base-http-scenarios" # generic HTTP attacks
|
2026-04-24 22:30:16 +01:00
|
|
|
"crowdsecurity/http-cve" # known-CVE fingerprints
|
|
|
|
|
"crowdsecurity/whitelist-good-actors" # don't ban legit crawlers
|
|
|
|
|
];
|
|
|
|
|
|
2026-04-25 15:00:31 +01:00
|
|
|
# Allow the agent to read nginx logs (it runs as DynamicUser).
|
|
|
|
|
readOnlyPaths = [ "/var/log/nginx" ];
|
|
|
|
|
|
|
|
|
|
settings = {
|
|
|
|
|
# config.yaml — main agent + LAPI configuration
|
|
|
|
|
config.api.server.listen_uri = "127.0.0.1:8081"; # 8080 is qBit
|
|
|
|
|
|
|
|
|
|
# Log sources to ingest
|
2026-04-24 22:30:16 +01:00
|
|
|
acquisitions = [
|
|
|
|
|
{
|
|
|
|
|
source = "file";
|
|
|
|
|
filenames = [ "/var/log/nginx/access.log" ];
|
|
|
|
|
labels.type = "nginx";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
source = "journalctl";
|
|
|
|
|
journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ];
|
|
|
|
|
labels.type = "syslog";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
source = "journalctl";
|
|
|
|
|
journalctl_filter = [ "_SYSTEMD_UNIT=authelia-main.service" ];
|
|
|
|
|
labels.type = "syslog";
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
2026-04-25 15:00:31 +01:00
|
|
|
# Push notifications via ntfy.sh
|
2026-04-24 22:30:16 +01:00
|
|
|
notifications = [
|
|
|
|
|
{
|
|
|
|
|
name = "ntfy_http";
|
|
|
|
|
type = "http";
|
|
|
|
|
log_level = "info";
|
|
|
|
|
url = ntfyUrl;
|
|
|
|
|
method = "POST";
|
|
|
|
|
headers = {
|
|
|
|
|
Title = "CrowdSec alert";
|
|
|
|
|
Priority = "high";
|
|
|
|
|
Tags = "rotating_light";
|
|
|
|
|
};
|
|
|
|
|
format = ''
|
|
|
|
|
{{range . -}}
|
|
|
|
|
{{.Scenario}} from {{.Source.IP}} ({{.Source.Cn}}) — {{len .Decisions}} decision(s) taken
|
|
|
|
|
{{end -}}
|
|
|
|
|
'';
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
|
2026-04-25 15:00:31 +01:00
|
|
|
# Override default profiles to attach the ntfy notifier
|
2026-04-24 22:30:16 +01:00
|
|
|
profiles = [
|
|
|
|
|
{
|
|
|
|
|
name = "default_ip_remediation";
|
|
|
|
|
filters = [ "Alert.Remediation == true && Alert.GetScope() == 'Ip'" ];
|
|
|
|
|
decisions = [{ type = "ban"; duration = "4h"; }];
|
|
|
|
|
notifications = [ "ntfy_http" ];
|
|
|
|
|
on_success = "break";
|
|
|
|
|
}
|
|
|
|
|
{
|
|
|
|
|
name = "default_range_remediation";
|
|
|
|
|
filters = [ "Alert.Remediation == true && Alert.GetScope() == 'Range'" ];
|
|
|
|
|
decisions = [{ type = "ban"; duration = "4h"; }];
|
|
|
|
|
notifications = [ "ntfy_http" ];
|
|
|
|
|
on_success = "break";
|
|
|
|
|
}
|
|
|
|
|
];
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
|
2026-04-25 15:00:31 +01:00
|
|
|
# Firewall bouncer enforces decisions via nftables; auto-registers with LAPI
|
2026-04-24 22:30:16 +01:00
|
|
|
services.crowdsec-firewall-bouncer = {
|
|
|
|
|
enable = true;
|
|
|
|
|
registerBouncer.enable = true;
|
|
|
|
|
};
|
|
|
|
|
};
|
|
|
|
|
}
|