{ config, lib, masterDomain, ... }: let cfg = config.os.srv.security.crowdsec; in { options.os.srv.security.crowdsec = { enable = lib.mkEnableOption "enables CrowdSec collaborative intrusion prevention"; aggregator.enable = lib.mkEnableOption "this node acting as a central LAPI aggregator for the network"; agent.enable = lib.mkEnableOption "local log parsing and threat intelligence generation on this node"; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = config.networking.nftables.enable; message = "CrowdSec requires networking.nftables to be enabled for blocking."; } { assertion = cfg.agent.enable || cfg.aggregator.enable; message = "You must enable at least one CrowdSec role: 'agent.enable' or 'aggregator.enable'."; } ]; sops = { secrets."crowdsec/env" = { owner = "crowdsec"; group = "crowdsec"; restartUnits = [ "crowdsec.service" ]; }; templates."local_api_credentials.yaml" = { owner = "crowdsec"; group = "crowdsec"; restartUnits = [ "crowdsec.service" ]; content = '' url: http://${config.os.core.network.ips.gateway-vm}:8080 login: ${config.networking.hostName} password: ${config.sops.placeholder."crowdsec/client_password"} ''; }; }; systemd.services.crowdsec.serviceConfig.EnvironmentFile = config.sops.secrets."crowdsec/env".path; services.crowdsec = { enable = true; autoUpdateService = true; openFirewall = true; settings = { common = { compress_logs = true; log_format = "json"; }; prometheus = { enabled = true; level = "full"; listen_addr = "0.0.0.0"; listen_port = 6060; }; db_config = { type = "postgresql"; host = config.os.core.network.ips.database-vm; port = 5432; db_name = "crowdsec"; user = "crowdsec"; password = "$CROWDSEC_DB_PASSWORD"; sslmode = "require"; }; api = { server = { enable = cfg.aggregator.enable; listen_uri = "0.0.0.0:8080"; trusted_ips = [ "127.0.0.1" "10.0.0.0/24" ]; auto_registration = { enabled = cfg.aggregator.enable; token = "$CROWDSEC_REGISTER_TOKEN"; allowed_ranges = [ "10.0.0.0/24" ]; }; }; client.credentials_path = config.sops.templates."local_api_credentials.yaml".path; }; lapi.client.api_url = "http://${config.os.core.network.ips.gateway-vm}:8080"; }; hub = lib.mkIf cfg.agent.enable { collections = [ "crowdsecurity/linux" "crowdsecurity/nginx" "crowdsecurity/authelia" "crowdsecurity/sshd" ]; }; localConfig = { acquisitions = lib.mkIf cfg.agent.enable [ { source = "journalctl"; journalctl_filter = [ "_SYSTEMD_UNIT=sshd.service" ]; labels.type = "syslog"; } { source = "file"; filenames = [ "/var/log/nginx/*.log" ]; labels.type = "nginx"; } { source = "file"; filenames = [ "/var/log/authelia/authelia.log" ]; labels.type = "authelia"; } ]; parsers.s02Enrich = lib.mkIf cfg.agent.enable [ { name = "myips/whitelist"; description = "Prevent local address ranges from triggering bans"; whitelist = { reason = "Internal private subnets"; cidr = [ "10.0.0.0/24" "10.1.0.0/24" "10.3.0.0/24" "10.4.0.0/24" ]; }; } ]; notifications = lib.mkIf cfg.aggregator.enable [ { name = "ntfy_alerts"; type = "http"; method = "POST"; url = "https://ntfy.${masterDomain}/crowdsec-alerts"; headers = { Title = "CrowdSec Alert on Bibus-Lab"; Priority = "high"; Authorization = "$NTFY_AUTH_TOKEN"; }; format = '' {{range .}} {{.Alert.Message}} (Scenario: {{.Alert.Scenario}}) from IP {{.Alert.Source.IP}} {{end}} ''; log_level = "info"; } ]; }; }; services.crowdsec-firewall-bouncer = { enable = true; registerBouncer.enable = cfg.aggregator.enable; settings = { mode = "nftables"; update_frequency = "10s"; api_url = "http://${config.os.core.network.ips.gateway-vm}:8080"; api_key = lib.mkIf cfg.aggregator.enable "$CROWDSEC_LOCAL_BOUNCER_KEY"; }; }; users.users.crowdsec.extraGroups = lib.mkIf cfg.agent.enable [ "systemd-journal" "nginx" "authelia-main" ]; }; }