Skip to main content

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

  1. 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., .env harvesting, WordPress probes, webshell attempts, open proxy CONNECT attempts).
    • Issues a global 53-hour ban for any detected probe.
  2. Enforce Blocking at Perimeter Gateway (danknasty):
    • The OpenBSD router (danknasty) does not process web logs or run heavy detection agents.
    • danknasty runs 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.
  3. 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.

2. Implementation & Configuration Details

A. Central Decision Engine on ul (Arch Linux)

  • Listen URI (/etc/crowdsec/config.yaml): Bound to 10.0.0.3:8080 to 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