Infrastructure Documentation

1. Security Architecture (10 layers)

#LayerTechnology
1DNSDNSSEC ECDSA P256-SHA256
2Cloud CDN + WAFCloudflare WAF + DDoS + AI Crawl Control
3NetworkCrowdSec nftables Bouncer
4FirewallNetgear PR60X SPI
5Local WAFModSecurity + OWASP CRS 4.x
6IDS/IPSCrowdSec Agent + SSH/HTTP scenarios
7HTTPSTLS 1.3 + HSTS preload
8DNS-TLSDoH port 853
9ApplicationGrav CMS + CSP + Secure cookies
10MonitoringBetterStack + CrowdSec poller + Vector

2. CrowdSec → Cloudflare Sync (crowdsec-cf-sync.py)

Location: /usr/local/bin/crowdsec-cf-sync.py Service: crowdsec-cf-sync.service Logs: /var/log/crowdsec/cf-sync.log

Features

  • Syncs active CrowdSec bans → Cloudflare IP Access Rules
  • Reports banned IPs → AbuseIPDB
  • Repeat-offender escalation: 1st ban handled by CrowdSec | 2nd → 24h | 3rd+ → 7d
  • ModSecurity score ≥ 5 → immediate 2h CF ban + AbuseIPDB report
  • Automatic /24 ban: 2+ IPs from the same block within 7d → 24h

State files

FileRole
/var/log/crowdsec/abuseipdb-reported.jsonIPs already reported to AbuseIPDB
/var/log/crowdsec/recidivists.jsonRepeat-offender counter per IP
/var/log/crowdsec/modsec-banned.jsonActive ModSec bans
/var/log/crowdsec/cidr-banned.json/24 banned blocks

Cloudflare tags

TagSourceDuration
crowdsec-local-banCrowdSec/cscliPer escalation
modsec-banModSecurity score ≥ 52h
crowdsec-cidr-ban2+ IPs same /2424h

Bug fixes (April 2026)

  • cs_origin vs origin in get_recent_local_bans()
  • REST API pagination → replaced by cscli decisions list --origin
  • Itemsitems (lowercase) for CrowdSec allowlist

Useful commands

# Service status
systemctl status crowdsec-cf-sync

# Real-time logs
journalctl -fu crowdsec-cf-sync | grep -E "bans|Added|CIDR|ModSec|RECIDIVIST"

# Restart
systemctl restart crowdsec-cf-sync

# Show repeat offenders
cat /var/log/crowdsec/recidivists.json | python3 -c "
import json,sys
d=json.load(sys.stdin)
for ip,v in sorted(d.items(), key=lambda x: x[1]['count'], reverse=True):
    print(f'{ip:20} count={v[\"count\"]} last={v[\"last_seen\"]}')
"

3. CrowdSec Allowlist

Name: my_allowlist Sync: cloudflare-allowlist-update.py script (hourly cron) Sources: BetterStack IPs + Cloudflare IPv4/IPv6

# Inspect contents
cscli allowlists inspect my_allowlist

# Count entries
cscli allowlists list

4. Nginx — CSP (Content Security Policy)

File: /etc/nginx/nginx.conf ($csp_header map)

Public CSP (default)

V2 migration: dynamic nonces. See From 46 hashes to zero for details.

httpoxy fix (April 2026)

Added in /etc/nginx/fastcgi.conf:

fastcgi_param  HTTP_PROXY  "";

5. Hugo CMS

Root: /home/jm/hugo-site/ (KVM VM 192.168.122.69) Theme: LoveIt Build: hugo --minify via deploy.sh Deployment: rsync to /var/www/hugo/ (NUC host)

Important files

FileRole
hugo.tomlHugo config (baseURL, languages, theme)
content/<route>/index.{fr,en}.mdBilingual pages
themes/LoveIt/Theme (git submodule)
static/Static assets
deploy.shBuild + rsync + CF purge

Useful commands

# Local build for testing
cd ~/hugo-site && hugo --minify

# Full deployment
~/deploy.sh

# Run dev server
hugo server -D --bind 0.0.0.0

6. Cloudflare

Security rules (order)

#NameAction
1GOOD BITSSkip (allowlist)
2BLOCK ALL BADBlock
3AI Crawl ControlBlock (auto-managed)
4Filter NON EU/USChallenge

GOOD BITS — conditions

  • BetterStack Uptime Bot
  • Legitimate bot categories (Search Engine, Monitoring, Security…)
  • CloudflareBrowserRenderingCrawler
  • ip.src in $allowed_ip

AI Crawl Control — blocked bots

GPTBot, ClaudeBot, Bytespider, CCBot, ChatGPT-User, FacebookBot, Meta-ExternalAgent, Perplexity, MistralAI, OAI-SearchBot, AmazonBot…

💡 Note: BingBot passes via GOOD BITS (Search Engine Crawler). 💡 Note: DeepSeekBot blocked by Filter NON EU/US (China-hosted).

IP access rules (dynamic)

Managed automatically by crowdsec-cf-sync.py:

  • crowdsec-local-ban — local CrowdSec bans
  • modsec-ban — ModSecurity 2h bans
  • crowdsec-cidr-ban — /24 blocks

7. Monitoring & Alerts

BetterStack: status.arleo.eu Footer badge: https://status.arleo.eu/en/badge Cloudflare Analytics token: stored in /etc/secrets/ (not published)


8. SSL Certificates

Managed automatically by Cloudflare (auto-renewal). Qualys SSL Labs: A+


9. Points of attention

  • The Cloudflare challenge inline script (__CF$cv$params) changes on every request → unavoidable CSP error, non-blocking
  • The CrowdSec allowlist (my_allowlist) is the single source of truth — synced to Cloudflare
  • cscli bans (repeat-offender escalation) are NOT visible in the CrowdSec REST API with ?limit=1000 — use cscli decisions list --origin cscli
  • Cloudflare token and AbuseIPDB credentials are stored outside the repo in /etc/secrets/ with chmod 600