367 lines
11 KiB
Markdown
367 lines
11 KiB
Markdown
# CrowdSec Traffic Blocking - Final Validation Report
|
|
|
|
**Date:** December 15, 2025
|
|
**Agent:** QA_Security
|
|
**Environment:** Docker container `charon:local`
|
|
|
|
---
|
|
|
|
## ❌ VERDICT: FAIL
|
|
|
|
**Traffic blocking is NOT functional end-to-end.**
|
|
|
|
---
|
|
|
|
## Test Results Summary
|
|
|
|
| Component | Status | Details |
|
|
|-----------|--------|---------|
|
|
| CrowdSec Process | ✅ RUNNING | PID 324, started manually |
|
|
| LAPI Health | ✅ HEALTHY | Accessible at <http://127.0.0.1:8085> |
|
|
| Bouncer Registration | ✅ REGISTERED | `caddy-bouncer` active, last pull at 20:06:01Z |
|
|
| Bouncer API Connectivity | ✅ CONNECTED | Bouncer successfully querying LAPI |
|
|
| CrowdSec App Config | ✅ CONFIGURED | API key set, ticker_interval: 10s |
|
|
| Decision Creation | ✅ SUCCESS | Test IP 203.0.113.99 banned for 15m |
|
|
| **BLOCKING TEST** | ❌ **FAIL** | **Banned IP returned HTTP 200 instead of 403** |
|
|
| Normal Traffic | ✅ PASS | Non-banned traffic returns 200 OK |
|
|
| Pre-commit | ✅ PASS | All checks passed, 85.1% coverage |
|
|
|
|
---
|
|
|
|
## Critical Issue: HTTP Handler Middleware Not Applied
|
|
|
|
### Problem
|
|
|
|
While the CrowdSec bouncer is successfully:
|
|
|
|
- Running and connected to LAPI
|
|
- Fetching decisions from LAPI
|
|
- Registered with valid API key
|
|
|
|
The **Caddy HTTP handler middleware is not applied to routes**, so blocking decisions are not enforced on incoming traffic.
|
|
|
|
### Evidence
|
|
|
|
#### 1. CrowdSec LAPI Running and Healthy
|
|
|
|
```bash
|
|
$ docker exec charon ps aux | grep crowdsec
|
|
324 root 0:01 /usr/local/bin/crowdsec -c /app/data/crowdsec/config/config.yaml
|
|
|
|
$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli lapi status'
|
|
Trying to authenticate with username "844aa6ea34104e829b80a8b9f459b4d9QqsifNBhWtcwmq1s" on http://127.0.0.1:8085/
|
|
You can successfully interact with Local API (LAPI)
|
|
```
|
|
|
|
#### 2. Bouncer Registered and Active
|
|
|
|
```bash
|
|
$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli bouncers list'
|
|
---------------------------------------------------------------------------------------------
|
|
Name IP Address Valid Last API pull Type Version Auth Type
|
|
---------------------------------------------------------------------------------------------
|
|
caddy-bouncer 127.0.0.1 ✔️ 2025-12-15T20:06:01Z caddy-cs-bouncer v0.9.2 api-key
|
|
---------------------------------------------------------------------------------------------
|
|
```
|
|
|
|
#### 3. Decision Created Successfully
|
|
|
|
```bash
|
|
$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli decisions add --ip 203.0.113.99 --duration 15m --reason "FINAL QA VALIDATION TEST"'
|
|
level=info msg="Decision successfully added"
|
|
|
|
$ docker exec charon sh -c 'cd /app/data/crowdsec && /usr/local/bin/cscli decisions list' | grep 203.0.113.99
|
|
| 1 | cscli | Ip:203.0.113.99 | FINAL QA VALIDATION TEST | ban | | | 1 | 14m54s | 1 |
|
|
```
|
|
|
|
#### 4. ❌ BLOCKING TEST FAILED - Traffic NOT Blocked
|
|
|
|
```bash
|
|
$ curl -H "X-Forwarded-For: 203.0.113.99" http://localhost:8080/ -v
|
|
> GET / HTTP/1.1
|
|
> Host: localhost:8080
|
|
> User-Agent: curl/8.5.0
|
|
> Accept: */*
|
|
> X-Forwarded-For: 203.0.113.99
|
|
>
|
|
< HTTP/1.1 200 OK
|
|
< Accept-Ranges: bytes
|
|
< Content-Length: 687
|
|
< Content-Type: text/html; charset=utf-8
|
|
< Last-Modified: Mon, 15 Dec 2025 17:46:43 GMT
|
|
< Date: Mon, 15 Dec 2025 20:05:59 GMT
|
|
```
|
|
|
|
**Expected:** HTTP 403 Forbidden
|
|
**Actual:** HTTP 200 OK
|
|
**Result:** ❌ FAIL
|
|
|
|
#### 5. Caddy HTTP Routes Missing CrowdSec Handler
|
|
|
|
```bash
|
|
$ docker exec charon curl -s http://localhost:2019/config/apps/http/servers | jq '.[].routes[0].handle'
|
|
[
|
|
{
|
|
"handler": "rewrite",
|
|
"uri": "/unknown.html"
|
|
},
|
|
{
|
|
"handler": "file_server",
|
|
"root": "/app/frontend/dist"
|
|
}
|
|
]
|
|
```
|
|
|
|
**No `crowdsec` handler present in the middleware chain.**
|
|
|
|
#### 6. CrowdSec Headers
|
|
|
|
No `X-Crowdsec-*` headers were present in the response, confirming the middleware is not processing requests.
|
|
|
|
---
|
|
|
|
## Root Cause Analysis
|
|
|
|
### Configuration Gap
|
|
|
|
1. **CrowdSec App Level**: ✅ Configured with API key and URL
|
|
2. **HTTP Handler Level**: ❌ **NOT configured** - Missing from route middleware chain
|
|
|
|
The Caddy server has the CrowdSec bouncer module loaded:
|
|
|
|
```bash
|
|
$ docker exec charon caddy list-modules | grep crowdsec
|
|
admin.api.crowdsec
|
|
crowdsec
|
|
http.handlers.crowdsec
|
|
layer4.matchers.crowdsec
|
|
```
|
|
|
|
But the `http.handlers.crowdsec` is not applied to any routes in the current configuration.
|
|
|
|
### Why This Happened
|
|
|
|
Looking at the application logs:
|
|
|
|
```
|
|
{"bin_path":"/usr/local/bin/crowdsec","data_dir":"/app/data/crowdsec","level":"info","msg":"CrowdSec reconciliation: starting startup check","time":"2025-12-15T19:59:33Z"}
|
|
{"db_mode":"disabled","level":"info","msg":"CrowdSec reconciliation skipped: both SecurityConfig and Settings indicate disabled","setting_enabled":false,"time":"2025-12-15T19:59:33Z"}
|
|
```
|
|
|
|
And later:
|
|
|
|
```
|
|
Initializing CrowdSec configuration...
|
|
CrowdSec configuration initialized. Agent lifecycle is GUI-controlled.
|
|
```
|
|
|
|
**The system initialized CrowdSec configuration but did NOT auto-start it or configure Caddy routes because:**
|
|
|
|
- The reconciliation logic checked both `SecurityConfig` and `Settings` tables
|
|
- Even though I manually set `crowd_sec_mode='local'` and `enabled=1` in the database, the startup check at 19:59:33 found them disabled
|
|
- The system then initialized configs but left "Agent lifecycle GUI-controlled"
|
|
- Manual start of CrowdSec LAPI succeeded, but Caddy route configuration was never updated
|
|
|
|
---
|
|
|
|
## What Works
|
|
|
|
✅ **CrowdSec Core Components:**
|
|
|
|
- LAPI running and healthy
|
|
- Bouncer registered and polling decisions
|
|
- Decision management (add/delete/list) working
|
|
- `cscli` commands functional
|
|
- Database integration working
|
|
- Configuration files properly structured
|
|
|
|
✅ **Infrastructure:**
|
|
|
|
- Backend tests: 100% pass
|
|
- Code coverage: 85.1% (meets 85% requirement)
|
|
- Pre-commit hooks: All passed
|
|
- Container build: Successful
|
|
- Caddy admin API: Accessible and responsive
|
|
|
|
---
|
|
|
|
## What Doesn't Work
|
|
|
|
❌ **Traffic Enforcement:**
|
|
|
|
- HTTP requests from banned IPs are not blocked
|
|
- CrowdSec middleware not in Caddy route handler chain
|
|
- No automatic configuration of Caddy routes when CrowdSec is enabled
|
|
|
|
❌ **Auto-Start Logic:**
|
|
|
|
- CrowdSec does not auto-start when database is configured to `mode=local, enabled=true`
|
|
- Reconciliation logic may have race condition or query timing issue
|
|
- Manual intervention required to start LAPI process
|
|
|
|
---
|
|
|
|
## Production Readiness: NO
|
|
|
|
### Blockers
|
|
|
|
1. **Critical:** Traffic blocking does not work - primary security feature non-functional
|
|
2. **High:** Auto-start logic unreliable - requires manual intervention
|
|
3. **High:** Caddy route configuration not synchronized with CrowdSec state
|
|
|
|
### Required Fixes
|
|
|
|
#### 1. Fix Caddy Route Configuration (CRITICAL)
|
|
|
|
**File:** `backend/internal/caddy/manager.go` or similar Caddy config generator
|
|
|
|
**Action Required:**
|
|
When CrowdSec is enabled, the Caddy configuration builder must inject the `crowdsec` HTTP handler into the route middleware chain BEFORE other handlers.
|
|
|
|
**Expected Structure:**
|
|
|
|
```json
|
|
{
|
|
"handle": [
|
|
{
|
|
"handler": "crowdsec",
|
|
"trusted_proxies_raw": ["10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16", "127.0.0.1/32", "::1/128"]
|
|
},
|
|
{
|
|
"handler": "rewrite",
|
|
"uri": "/unknown.html"
|
|
},
|
|
{
|
|
"handler": "file_server",
|
|
"root": "/app/frontend/dist"
|
|
}
|
|
]
|
|
}
|
|
```
|
|
|
|
The `trusted_proxies_raw` field must be set at the HTTP handler level (not app level).
|
|
|
|
#### 2. Fix Auto-Start Logic (HIGH)
|
|
|
|
**File:** `backend/internal/services/crowdsec_startup.go`
|
|
|
|
**Issues:**
|
|
|
|
- Line 110-117: The check `if cfg.CrowdSecMode != "local" && !crowdSecEnabled` is skipping startup even when database shows enabled
|
|
- Possible issue: `db.First(&cfg)` not finding the manually-created record
|
|
- Consider: The `Name` field mismatch (code expects "Default Security Config", DB has "default")
|
|
|
|
**Recommended Fix:**
|
|
|
|
```go
|
|
// At line 43, ensure proper fallback:
|
|
if err := db.First(&cfg).Error; err != nil {
|
|
if err == gorm.ErrRecordNotFound {
|
|
// Try finding by uuid='default' as fallback
|
|
if err := db.Where("uuid = ?", "default").First(&cfg).Error; err != nil {
|
|
// Then proceed with auto-initialization logic
|
|
// ...
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 3. Add Integration Test for End-to-End Blocking
|
|
|
|
**File:** `scripts/crowdsec_blocking_integration.sh` (new)
|
|
|
|
**Test Steps:**
|
|
|
|
1. Enable CrowdSec in DB
|
|
2. Restart container
|
|
3. Verify LAPI running
|
|
4. Verify bouncer registered
|
|
5. Add ban decision
|
|
6. **Test traffic with banned IP → Assert 403**
|
|
7. Test normal traffic → Assert 200
|
|
8. Cleanup
|
|
|
|
This test must be added to CI/CD and must FAIL if traffic is not blocked.
|
|
|
|
---
|
|
|
|
## Recommendation
|
|
|
|
### **DO NOT DEPLOY**
|
|
|
|
The CrowdSec feature is **non-functional for its primary purpose: blocking traffic**. While all the supporting infrastructure works correctly (LAPI, bouncer registration, decision management), the absence of HTTP middleware enforcement makes this a **critical security feature gap**.
|
|
|
|
### Next Steps (Priority Order)
|
|
|
|
1. **IMMEDIATE (P0):** Fix Caddy route handler injection in `caddy/manager.go`
|
|
- Add `crowdsec` handler to route middleware chain
|
|
- Include `trusted_proxies_raw` configuration
|
|
- Reload Caddy config when CrowdSec is enabled/disabled
|
|
|
|
2. **HIGH (P1):** Fix CrowdSec auto-start reconciliation logic
|
|
- Debug why `db.First(&cfg)` returns 0 rows despite data existing
|
|
- Fix query or add fallback to uuid lookup
|
|
- Ensure consistent startup behavior
|
|
|
|
3. **HIGH (P1):** Add blocking integration test
|
|
- Create `crowdsec_blocking_integration.sh`
|
|
- Add to CI pipeline
|
|
- Must verify actual 403 responses
|
|
|
|
4. **MEDIUM (P2):** Add automatic bouncer registration
|
|
- When CrowdSec starts, auto-register bouncer if not exists
|
|
- Update Caddy config with generated API key
|
|
- Eliminate manual registration step
|
|
|
|
5. **LOW (P3):** Add admin UI controls
|
|
- Start/Stop CrowdSec buttons
|
|
- Bouncer status display
|
|
- Decision management interface
|
|
|
|
---
|
|
|
|
## Test Environment Details
|
|
|
|
**Container Image:** `charon:local`
|
|
**Build Date:** December 15, 2025
|
|
**Caddy Version:** (with crowdsec module v0.9.2)
|
|
**CrowdSec Version:** LAPI running, `cscli` available
|
|
**Database:** SQLite at `/app/data/charon.db`
|
|
**Host OS:** Linux
|
|
|
|
---
|
|
|
|
## Files Modified During Testing
|
|
|
|
- `data/charon.db` - Added `security_configs` and `settings` entries
|
|
- Caddy live config - Added `apps.crowdsec` configuration via admin API
|
|
|
|
**Note:** These changes are ephemeral in the container and not persisted in the repository.
|
|
|
|
---
|
|
|
|
## Conclusion
|
|
|
|
CrowdSec infrastructure is **80% complete** but missing the **critical 20%** - actual traffic enforcement. The foundation is solid:
|
|
|
|
- LAPI works
|
|
- Bouncer communicates
|
|
- Decisions are managed correctly
|
|
- Database integration works
|
|
- Code quality is high (85% coverage)
|
|
|
|
**However**, without the HTTP handler middleware properly configured, **zero traffic is being blocked**, making the feature unusable in production.
|
|
|
|
**Estimated effort to fix:** 4-8 hours
|
|
|
|
1. Add HTTP handler injection logic (2-4h)
|
|
2. Fix auto-start logic (1-2h)
|
|
3. Add integration test (1-2h)
|
|
4. Verify end-to-end (1h)
|
|
|
|
---
|
|
|
|
**Report Author:** QA_Security Agent
|
|
**Report Status:** FINAL
|
|
**Next Action:** Development team to implement fixes per recommendations above
|