crowdsec
CrowdSec & OpenBSD PF Perimeter Security Setup
1. Overview & Architectural Concept
Distributed intrusion detection and perimeter enforcement across all public facing infrastucture (drheck.dev).
[ Public Internet (WAN) ]
│
▼
┌─────────────────────────────────┐
│ danknasty (OpenBSD Router) │
│ ─────────────────────────────── │
│ • OpenBSD PF Kernel Firewall │
│ • Kernel SSH Rate Limiter │
│ • CrowdSec PF Firewall Bouncer │
└────────────────┬────────────────┘
│ Drops banned IPs at WAN edge
│ Stream bans via LAN (10.0.0.3:8080)
▼
┌─────────────────────────────────┐
│ ul (Arch Linux Server) │
│ ─────────────────────────────── │
│ • Nginx Web Reverse Proxy │
│ • CrowdSec LAPI Decision Engine │
│ • 53-Hour Ban Policy Engine │
└─────────────────────────────────┘
Design Philosophy
- Offload Detection to Powerful Hardware (
ul):- Heavy log analysis, regex pattern matching, SQLite/LAPI database operations, and scenario evaluations run on the beefier server (
ul). - Parses Nginx access logs to detect scanner bots (e.g.,
.envharvesting, WordPress probes, webshell attempts, open proxy CONNECT attempts). - Issues a global 53-hour ban for any detected probe.
- Heavy log analysis, regex pattern matching, SQLite/LAPI database operations, and scenario evaluations run on the beefier server (
- Enforce Blocking at Perimeter Gateway (
danknasty):- The OpenBSD router (
danknasty) does not process web logs or run heavy detection agents. danknastyruns the lightweight CrowdSec PF Bouncer (crowdsec_firewall_bouncer), which connects over LAN (10.0.0.3:8080) to stream ban decisions.- Banned IPs are injected into OpenBSD PF's kernel
<crowdsec-blacklists>table. Banned packets are dropped at the network card before entering the local network.
- The OpenBSD router (
- Zero-Overhead Edge SSH Rate-Limiting (
danknasty):- OpenBSD PF tracks TCP connection rates directly in kernel state space (
max-src-conn-rate 3/30). - Any bot opening more than 3 SSH connections in 30 seconds is added to the OpenBSD PF
<bruteforce>table and dropped with 0% CPU and ~0 MB RAM usage.
- OpenBSD PF tracks TCP connection rates directly in kernel state space (
2. Implementation & Configuration Details
A. Central Decision Engine on ul (Arch Linux)
-
Listen URI (
/etc/crowdsec/config.yaml): Bound to10.0.0.3:8080to allow the router bouncer to connect over the LAN:api: server: listen_uri: 10.0.0.3:8080 -
Nginx Log Acquisition (
/etc/crowdsec/acquis.d/nginx.yaml):filenames: - /var/log/nginx/*.log - /var/log/nginx/access.log labels: type: nginx -
Custom Honeypot Scenario (
/etc/crowdsec/scenarios/honeypot-endpoints.yaml):type: leaky name: custom/honeypot-endpoints description: "Instant trigger on bot/scanner endpoints" filter: | evt.Parsed.request in ["/.env", "/cgi-bin/authLogin.cgi", "/IndoSec.php", "/alfa.php", "/luxe.php", "/BDKR28WP.php"] || evt.Parsed.request matches "(?i)^/(wp-admin|wp-login|wp-includes|\\.env|cgi-bin|alfa\\.php|1\\.php|222\\.php|8\\.php|shell\\.php)" capacity: 1 leakspeed: 1s groupby: evt.Meta.source_ip blackhole: 1m labels: service: nginx type: honeypot_scan remediation: ban -
53-Hour Ban Policy (
/etc/crowdsec/profiles.yaml):name: default_ip_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Ip" decisions: - type: ban duration: 53h on_success: break --- name: default_range_remediation filters: - Alert.Remediation == true && Alert.GetScope() == "Range" decisions: - type: ban duration: 53h on_success: break -
Host Firewall Rule (
/etc/nftables.conf):ip saddr 10.0.0.0/24 tcp dport 8080 accept comment "Allow CrowdSec LAPI from local LAN"
B. Perimeter Enforcement on danknasty (OpenBSD 7.9 Router)
-
Bouncer Configuration (
/etc/crowdsec-firewall-bouncer/crowdsec-firewall-bouncer.yaml):mode: pf retry_initial_connect: true update_frequency: 10s api_url: http://10.0.0.3:8080/ api_key: <GENERATED_API_KEY> deny_action: DROP blacklists_ipv4: crowdsec-blacklists blacklists_ipv6: crowdsec6-blacklists pf: anchor_name: "" -
Router Firewall Rules (
/etc/pf.conf):# CrowdSec Blacklist Tables table <crowdsec-blacklists> persist table <crowdsec6-blacklists> persist block drop quick from <crowdsec-blacklists> block drop quick from <crowdsec6-blacklists> # Table for SSH Brute-force Auto-Blocking table <bruteforce> persist block drop quick from <bruteforce> # SSH rules with stateful connection tracking pass in quick on $ext proto tcp to self port ssh \ flags S/SA keep state \ (max-src-conn 10, max-src-conn-rate 3/30, overload <bruteforce> flush global)
3. Interaction, Operations & Management
A. Viewing Active System State
1. View Active Bans on ul (CrowdSec LAPI)
sudo cscli decisions list
2. View Active Bouncers & Connection Status
sudo cscli bouncers list
3. View Detection Metrics & Statistics
sudo cscli metrics
4. View Active Kernel Ban Tables on OpenBSD Router (danknasty)
# View CrowdSec streamed bans:
SSH_AUTH_SOCK=/home/luke/.local/ssh_auth_sock/ssh_auth_sock ssh danknasty "doas pfctl -t crowdsec-blacklists -T show"
# View OpenBSD SSH kernel brute-force bans:
SSH_AUTH_SOCK=/home/luke/.local/ssh_auth_sock/ssh_auth_sock ssh danknasty "doas pfctl -t bruteforce -T show"
B. Manual Interactivity & Ban Management
1. Manually Ban an IP for 53 Hours
sudo cscli decisions add --ip <IP_ADDRESS> --duration 53h --reason "Manual administrative ban"
2. Remove a Ban (Unban an IP)
sudo cscli decisions delete --ip <IP_ADDRESS>
3. Inspect Recent Detection Alerts
sudo cscli alerts list
sudo cscli alerts inspect <ALERT_ID>
4. Flush Banned IP Tables on the Router (Emergency Reset)
SSH_AUTH_SOCK=/home/luke/.local/ssh_auth_sock/ssh_auth_sock ssh danknasty "doas pfctl -t crowdsec-blacklists -T flush"
C. Whitelisting Local Trusted Networks
To prevent accidental self-bans (e.g. during administrative testing or API debugging), create /etc/crowdsec/parsers/s02-override/00-whitelist.yaml on ul:
name: custom/whitelist
description: "Whitelist trusted local LAN IPs"
filter: "evt.Parsed.static_source_ip != ''"
whitelist:
reason: "Trusted internal IPs"
ip:
- "127.0.0.1"
- "10.0.0.3"
- "10.0.0.1"
cidr:
- "10.0.0.0/24"
- "fd77:7777:5010::/64"
After modifying whitelists or scenarios, reload CrowdSec:
sudo systemctl reload crowdsec