- Implemented new CSS animations for UI elements including bobbing, pulsing, rotating, and spinning effects. - Integrated loading overlays in CrowdSecConfig, Login, ProxyHosts, Security, and WafConfig pages to enhance user experience during asynchronous operations. - Added contextual messages for loading states to inform users about ongoing processes. - Created tests for Login and Security pages to ensure overlays function correctly during login attempts and security operations.
11 KiB
Cerberus Technical Documentation
This document is for developers and advanced users who want to understand how Cerberus works under the hood.
Looking for the user guide? See Security Features instead.
What Is Cerberus?
Cerberus is the optional security suite built into Charon. It includes:
- WAF (Web Application Firewall) — Inspects requests for malicious payloads
- CrowdSec — Blocks IPs based on behavior and reputation
- Access Lists — Static allow/deny rules (IP, CIDR, geo)
- Rate Limiting — Volume-based abuse prevention (placeholder)
All components are disabled by default and can be enabled independently.
Architecture
Request Flow
When a request hits Charon:
- Check if Cerberus is enabled (global setting + dynamic database flag)
- WAF evaluation (if
waf_mode != disabled)- Increment
charon_waf_requests_totalmetric - Check payload against loaded rulesets
- If suspicious:
blockmode: Return 403 + incrementcharon_waf_blocked_totalmonitormode: Log + incrementcharon_waf_monitored_total
- Increment
- ACL evaluation (if enabled)
- Test client IP against active access lists
- First denial = 403 response
- CrowdSec check (placeholder for future)
- Rate limit check (placeholder for future)
- Pass to downstream handler (if not blocked)
Middleware Integration
Cerberus runs as Gin middleware on all /api/v1 routes:
r.Use(cerberusMiddleware.RequestLogger())
This means it protects the management API but does not directly inspect traffic to proxied websites (that happens in Caddy).
Configuration Model
Database Schema
SecurityConfig table:
type SecurityConfig struct {
ID uint `gorm:"primaryKey"`
Name string `json:"name"`
Enabled bool `json:"enabled"`
AdminWhitelist string `json:"admin_whitelist"` // CSV of IPs/CIDRs
CrowdsecMode string `json:"crowdsec_mode"` // disabled, local, external
CrowdsecAPIURL string `json:"crowdsec_api_url"`
CrowdsecAPIKey string `json:"crowdsec_api_key"`
WafMode string `json:"waf_mode"` // disabled, monitor, block
WafRulesSource string `json:"waf_rules_source"` // Ruleset identifier
WafLearning bool `json:"waf_learning"`
RateLimitEnable bool `json:"rate_limit_enable"`
RateLimitBurst int `json:"rate_limit_burst"`
RateLimitRequests int `json:"rate_limit_requests"`
RateLimitWindowSec int `json:"rate_limit_window_sec"`
}
Environment Variables (Fallbacks)
If no database config exists, Charon reads from environment:
CERBERUS_SECURITY_WAF_MODE—disabled|monitor|blockCERBERUS_SECURITY_CROWDSEC_MODE—disabled|local|externalCERBERUS_SECURITY_CROWDSEC_API_URL— URL for external CrowdSec bouncerCERBERUS_SECURITY_CROWDSEC_API_KEY— API key for external bouncerCERBERUS_SECURITY_ACL_ENABLED—true|falseCERBERUS_SECURITY_RATELIMIT_ENABLED—true|false
WAF (Web Application Firewall)
Current Implementation
Status: Prototype with placeholder detection
The current WAF checks for <script> tags as a proof-of-concept. Full OWASP CRS integration is planned.
func (w *WAF) EvaluateRequest(r *http.Request) (Decision, error) {
if strings.Contains(r.URL.Query().Get("q"), "<script>") {
return Decision{Action: "block", Reason: "XSS detected"}, nil
}
return Decision{Action: "allow"}, nil
}
Future: Coraza Integration
Planned integration with Coraza WAF and OWASP Core Rule Set:
waf, err := coraza.NewWAF(coraza.NewWAFConfig().
WithDirectives(loadedRuleContent))
This will provide production-grade detection of:
- SQL injection
- Cross-site scripting (XSS)
- Remote code execution
- File inclusion attacks
- And more
Rulesets
SecurityRuleSet table stores rule definitions:
type SecurityRuleSet struct {
ID uint `gorm:"primaryKey"`
Name string `json:"name"`
SourceURL string `json:"source_url"` // Optional URL for rule updates
Mode string `json:"mode"` // owasp, custom
Content string `json:"content"` // Raw rule text
}
Manage via /api/v1/security/rulesets.
Prometheus Metrics
charon_waf_requests_total{mode="block|monitor"} — Total requests evaluated
charon_waf_blocked_total{mode="block"} — Requests blocked
charon_waf_monitored_total{mode="monitor"} — Requests logged but not blocked
Scrape from /metrics endpoint (no auth required).
Structured Logging
WAF decisions emit JSON-like structured logs:
{
"source": "waf",
"decision": "block",
"mode": "block",
"path": "/api/v1/proxy-hosts",
"query": "name=<script>alert(1)</script>",
"ip": "203.0.113.50"
}
Use these for dashboard creation and alerting.
Access Control Lists (ACLs)
How They Work
Each AccessList defines:
- Type:
whitelist|blacklist|geo_whitelist|geo_blacklist|local_only - IPs: Comma-separated IPs or CIDR blocks
- Countries: Comma-separated ISO country codes (US, GB, FR, etc.)
Evaluation logic:
- Whitelist: If IP matches list → allow; else → deny
- Blacklist: If IP matches list → deny; else → allow
- Geo Whitelist: If country matches → allow; else → deny
- Geo Blacklist: If country matches → deny; else → allow
- Local Only: If RFC1918 private IP → allow; else → deny
Multiple ACLs can be assigned to a proxy host. The first denial wins.
GeoIP Database
Uses MaxMind GeoLite2-Country database:
- Path configured via
CHARON_GEOIP_DB_PATH - Default:
/app/data/GeoLite2-Country.mmdb(Docker) - Update monthly from MaxMind for accuracy
CrowdSec Integration
Current Status
Placeholder. Configuration models exist but bouncer integration is not yet implemented.
Planned Implementation
Local mode:
- Run CrowdSec agent inside Charon container
- Parse logs from Caddy
- Make decisions locally
External mode:
- Connect to existing CrowdSec bouncer via API
- Query IP reputation before allowing requests
Security Decisions
The SecurityDecision table logs all security actions:
type SecurityDecision struct {
ID uint `gorm:"primaryKey"`
Source string `json:"source"` // waf, crowdsec, acl, ratelimit, manual
IPAddress string `json:"ip_address"`
Action string `json:"action"` // allow, block, challenge
Reason string `json:"reason"`
Timestamp time.Time `json:"timestamp"`
}
Use cases:
- Audit trail for compliance
- UI visibility into recent blocks
- Manual override tracking
Self-Lockout Prevention
Admin Whitelist
Purpose: Prevent admins from blocking themselves
Implementation:
- Stored in
SecurityConfig.admin_whitelistas CSV - Checked before applying any block decision
- If requesting IP matches whitelist → always allow
Recommendation: Add your VPN IP, Tailscale IP, or home network before enabling Cerberus.
Break-Glass Token
Purpose: Emergency disable when locked out
How it works:
- Generate via
POST /api/v1/security/breakglass/generate - Returns one-time token (plaintext, never stored hashed)
- Token can be used in
POST /api/v1/security/disableto turn off Cerberus - Token expires after first use
Storage: Tokens are hashed in database using bcrypt.
Localhost Bypass
Requests from 127.0.0.1 or ::1 may bypass security checks (configurable). Allows local management access even when locked out.
API Reference
Status
GET /api/v1/security/status
Returns:
{
"enabled": true,
"waf_mode": "monitor",
"crowdsec_mode": "local",
"acl_enabled": true,
"ratelimit_enabled": false
}
Enable Cerberus
POST /api/v1/security/enable
Content-Type: application/json
{
"admin_whitelist": "198.51.100.10,203.0.113.0/24"
}
Requires either:
admin_whitelistwith at least one IP/CIDR- OR valid break-glass token in header
Disable Cerberus
POST /api/v1/security/disable
Requires either:
- Request from localhost
- OR valid break-glass token in header
Get/Update Config
GET /api/v1/security/config
POST /api/v1/security/config
See SecurityConfig schema above.
Rulesets
GET /api/v1/security/rulesets
POST /api/v1/security/rulesets
DELETE /api/v1/security/rulesets/:id
Decisions (Audit Log)
GET /api/v1/security/decisions?limit=50
POST /api/v1/security/decisions # Manual override
Testing
Integration Test
Run the Coraza integration test:
bash scripts/coraza_integration.sh
Or via Go:
cd backend
go test -tags=integration ./integration -run TestCorazaIntegration -v
Manual Testing
- Enable WAF in
monitormode - Send request with
<script>in query string - Check
/api/v1/security/decisionsfor logged attempt - Switch to
blockmode - Repeat — should receive 403
Observability
Recommended Dashboards
Block Rate:
rate(charon_waf_blocked_total[5m]) / rate(charon_waf_requests_total[5m])
Monitor vs Block Comparison:
rate(charon_waf_monitored_total[5m])
rate(charon_waf_blocked_total[5m])
Alerting Rules
High block rate (potential attack):
alert: HighWAFBlockRate
expr: rate(charon_waf_blocked_total[5m]) > 0.3
for: 10m
annotations:
summary: "WAF blocking >30% of requests"
No WAF evaluation (misconfiguration):
alert: WAFNotEvaluating
expr: rate(charon_waf_requests_total[10m]) == 0
for: 15m
annotations:
summary: "WAF received zero requests, check middleware config"
Development Roadmap
| Phase | Feature | Status |
|---|---|---|
| 1 | WAF placeholder + metrics | ✅ Complete |
| 2 | ACL implementation | ✅ Complete |
| 3 | Break-glass token | ✅ Complete |
| 4 | Coraza CRS integration | 📋 Planned |
| 5 | CrowdSec local agent | 📋 Planned |
| 6 | Rate limiting enforcement | 📋 Planned |
| 7 | Adaptive learning/tuning | 🔮 Future |
FAQ
Why is the WAF just a placeholder?
We wanted to ship the architecture and observability first. This lets you enable monitoring, see the metrics, and prepare dashboards before the full rule engine is integrated.
Can I use my own WAF rules?
Yes, via /api/v1/security/rulesets. Upload custom Coraza-compatible rules.
Does Cerberus protect Caddy's proxy traffic?
Not yet. Currently it only protects the management API (/api/v1). Future versions will integrate directly with Caddy's request pipeline to protect proxied traffic.
Why is monitor mode still blocking?
Known issue with the placeholder implementation. This will be fixed when Coraza integration is complete.