Remove handler-level `trusted_proxies` configuration from ReverseProxyHandler that was
using an invalid object structure. Caddy's reverse_proxy handler expects trusted_proxies
to be an array of CIDR strings, not an object with {source, ranges}.
The server-level trusted_proxies configuration in config.go already provides equivalent
IP spoofing protection globally for all routes, making the handler-level setting redundant.
Changes:
- backend: Remove lines 184-189 from internal/caddy/types.go
- backend: Update 3 unit tests to remove handler-level trusted_proxies assertions
- docs: Document fix in CHANGELOG.md
Fixes: #[issue-number] (500 error when saving proxy hosts)
Tests: All 84 backend tests pass (84.6% coverage)
Security: Trivy + govulncheck clean, no vulnerabilities
339 lines
13 KiB
Markdown
339 lines
13 KiB
Markdown
# Investigation: Caddy Configuration File Analysis
|
|
|
|
**Date:** December 20, 2024
|
|
**Issue:** After Charon restart, `/app/data/caddy/config.json` does not exist
|
|
**Status:** ✅ **NOT A BUG - SYSTEM WORKING AS DESIGNED**
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
The `/app/data/caddy/config.json` file **does not exist and is not supposed to exist**. This is the correct and expected behavior.
|
|
|
|
**Key Finding:** Charon uses Caddy's Admin API to dynamically manage configuration, not file-based configuration. The config is stored in Caddy's memory and updated via HTTP POST requests to the Admin API.
|
|
|
|
---
|
|
|
|
## 1. Why the Config File Doesn't Exist
|
|
|
|
### Architecture Overview
|
|
|
|
Charon uses **API-based configuration management** for Caddy v2.x:
|
|
|
|
```
|
|
Database (ProxyHost models)
|
|
↓
|
|
GenerateConfig() → In-Memory Config (Go struct)
|
|
↓
|
|
Admin API (HTTP POST to localhost:2019/config/)
|
|
↓
|
|
Caddy's In-Memory State
|
|
```
|
|
|
|
### Code Evidence
|
|
|
|
From `backend/internal/caddy/manager.go` (ApplyConfig workflow):
|
|
|
|
```go
|
|
// 1. Generate config from database models
|
|
generatedConfig, err := GenerateConfig(...)
|
|
|
|
// 2. Push config to Caddy via Admin API (NOT file write)
|
|
if err := m.client.Load(ctx, generatedConfig); err != nil {
|
|
// Rollback on failure
|
|
return fmt.Errorf("apply failed: %w", err)
|
|
}
|
|
|
|
// 3. Save snapshot for rollback capability
|
|
if err := m.saveSnapshot(generatedConfig); err != nil {
|
|
// Warning only, not a failure
|
|
}
|
|
```
|
|
|
|
**The `client.Load()` method sends the config via HTTP POST to Caddy's Admin API, NOT by writing a file.**
|
|
|
|
---
|
|
|
|
## 2. Where Charon Actually Stores/Applies Config
|
|
|
|
### Active Configuration Location
|
|
|
|
- **Primary Storage:** Caddy's in-memory state (managed by Caddy Admin API)
|
|
- **Access Method:** Caddy Admin API at `http://localhost:2019/config/`
|
|
- **Persistence:** Caddy maintains its own state across restarts using its internal storage
|
|
|
|
### Snapshot Files for Rollback
|
|
|
|
Charon DOES save configuration snapshots in `/app/data/caddy/`, but these are:
|
|
|
|
- **For rollback purposes only** (disaster recovery)
|
|
- **Named with timestamps:** `config-<unix-timestamp>.json`
|
|
- **NOT used as the active config source**
|
|
|
|
**Current snapshots on disk:**
|
|
|
|
```bash
|
|
-rw-r--r-- 1 root root 40.4K Dec 18 12:38 config-1766079503.json
|
|
-rw-r--r-- 1 root root 40.4K Dec 18 18:52 config-1766101930.json
|
|
-rw-r--r-- 1 root root 40.2K Dec 18 18:59 config-1766102384.json
|
|
-rw-r--r-- 1 root root 39.8K Dec 18 19:00 config-1766102447.json
|
|
-rw-r--r-- 1 root root 40.4K Dec 18 19:01 config-1766102504.json
|
|
-rw-r--r-- 1 root root 40.2K Dec 18 19:02 config-1766102535.json
|
|
-rw-r--r-- 1 root root 39.5K Dec 18 19:02 config-1766102562.json
|
|
-rw-r--r-- 1 root root 39.5K Dec 18 20:04 config-1766106283.json
|
|
-rw-r--r-- 1 root root 39.5K Dec 19 01:02 config-1766124161.json
|
|
-rw-r--r-- 1 root root 44.7K Dec 19 13:57 config-1766170642.json (LATEST)
|
|
```
|
|
|
|
**Latest snapshot:** December 19, 2024 at 13:57 (44.7 KB)
|
|
|
|
---
|
|
|
|
## 3. How to Verify Current Caddy Configuration
|
|
|
|
### Method 1: Query Caddy Admin API
|
|
|
|
**Retrieve full configuration:**
|
|
|
|
```bash
|
|
curl -s http://localhost:2019/config/ | jq '.'
|
|
```
|
|
|
|
**Check specific routes:**
|
|
|
|
```bash
|
|
curl -s http://localhost:2019/config/apps/http/servers/srv0/routes | jq '.'
|
|
```
|
|
|
|
**Verify Caddy is responding:**
|
|
|
|
```bash
|
|
curl -s http://localhost:2019/config/ -w "\nHTTP Status: %{http_code}\n"
|
|
```
|
|
|
|
### Method 2: Check Container Logs
|
|
|
|
**View recent Caddy activity:**
|
|
|
|
```bash
|
|
docker logs charon --tail 100 2>&1 | grep -i caddy
|
|
```
|
|
|
|
**Monitor real-time logs:**
|
|
|
|
```bash
|
|
docker logs -f charon
|
|
```
|
|
|
|
### Method 3: Inspect Latest Snapshot
|
|
|
|
**View most recent config snapshot:**
|
|
|
|
```bash
|
|
docker exec charon cat /app/data/caddy/config-1766170642.json | jq '.'
|
|
```
|
|
|
|
**List all snapshots:**
|
|
|
|
```bash
|
|
docker exec charon ls -lh /app/data/caddy/config-*.json
|
|
```
|
|
|
|
---
|
|
|
|
## 4. What Logs to Check for Errors
|
|
|
|
### Container Logs Analysis (Last 100 Lines)
|
|
|
|
**Command:**
|
|
|
|
```bash
|
|
docker logs charon --tail 100 2>&1
|
|
```
|
|
|
|
**Current Status:**
|
|
✅ **Caddy is operational and proxying traffic successfully**
|
|
|
|
**Log Evidence:**
|
|
|
|
- **Proxy Traffic:** Successfully handling requests to nzbget, sonarr, radarr, seerr
|
|
- **Health Check:** `GET /api/v1/health` returning 200 OK
|
|
- **HTTP/2 & HTTP/3:** Properly negotiating protocols
|
|
- **Security Headers:** All security headers (HSTS, CSP, X-Frame-Options, etc.) are applied correctly
|
|
- **No Config Errors:** Zero errors related to configuration application or Caddy startup
|
|
|
|
**Secondary Issue Detected (Non-Blocking):**
|
|
|
|
```
|
|
{"level":"error","msg":"failed to connect to LAPI, retrying in 10s: API error: access forbidden"}
|
|
```
|
|
|
|
- **Component:** CrowdSec bouncer integration
|
|
- **Impact:** Does NOT affect Caddy functionality or proxy operations
|
|
- **Action:** Check CrowdSec API key configuration if CrowdSec integration is required
|
|
|
|
---
|
|
|
|
## 5. Recommended Fix
|
|
|
|
**⚠️ NO FIX NEEDED** - System is working as designed.
|
|
|
|
### Why No Action Is Required
|
|
|
|
1. **Caddy is running correctly:** All proxy routes are operational
|
|
2. **Config is being applied:** Admin API is managing configuration dynamically
|
|
3. **Snapshots exist:** Rollback capability is functioning (10 snapshots on disk)
|
|
4. **No errors:** Logs show successful request handling with proper security headers
|
|
|
|
### If You Need Static Config File for Reference
|
|
|
|
If you need a static reference file for debugging or documentation:
|
|
|
|
**Option 1: Export current config from Admin API**
|
|
|
|
```bash
|
|
curl -s http://localhost:2019/config/ | jq '.' > /tmp/caddy-current-config.json
|
|
```
|
|
|
|
**Option 2: Copy latest snapshot**
|
|
|
|
```bash
|
|
docker exec charon cat /app/data/caddy/config-1766170642.json > /tmp/caddy-snapshot.json
|
|
```
|
|
|
|
---
|
|
|
|
## 6. Architecture Benefits
|
|
|
|
### Why Caddy Admin API is Superior to File-Based Config
|
|
|
|
1. **Dynamic Updates:** Apply config changes without restarting Caddy
|
|
2. **Atomic Operations:** Config updates are all-or-nothing (prevents partial failures)
|
|
3. **Rollback Capability:** Built-in rollback mechanism via snapshots
|
|
4. **Validation:** API validates config before applying
|
|
5. **Zero Downtime:** No service interruption during config updates
|
|
6. **Programmatic Management:** Easy to automate and integrate with applications
|
|
|
|
---
|
|
|
|
## 7. Configuration Flow Diagram
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ User Creates Proxy Host │
|
|
│ (via Charon Web UI/API) │
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Database (SQLite/PostgreSQL) │
|
|
│ Stores: ProxyHost, SSLCert, Security │
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ Charon: manager.ApplyConfig(ctx) Triggered │
|
|
│ (via API call or scheduled job) │
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ caddy.GenerateConfig(...) [In-Memory] │
|
|
│ • Fetch ProxyHost models from DB │
|
|
│ • Build Caddy JSON config struct │
|
|
│ • Apply: SSL, CrowdSec, WAF, Rate Limiting, ACL │
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ client.Load(ctx, generatedConfig) [Admin API] │
|
|
│ HTTP POST → localhost:2019/config/ │
|
|
│ (Pushes config to Caddy) │
|
|
└───────────────────────────────┬─────────────────────────────────┘
|
|
│
|
|
┌───────┴────────┐
|
|
│ │
|
|
▼ ▼
|
|
┌─────────────────┐ ┌──────────────────────┐
|
|
│ Caddy Accepts │ │ Caddy Rejects & │
|
|
│ Configuration │ │ Returns Error │
|
|
└────────┬────────┘ └──────────┬───────────┘
|
|
│ │
|
|
│ ▼
|
|
│ ┌─────────────────────────┐
|
|
│ │ manager.rollback(ctx) │
|
|
│ │ • Load latest snapshot │
|
|
│ │ • Apply to Admin API │
|
|
│ └─────────────────────────┘
|
|
│
|
|
▼
|
|
┌─────────────────────────────────────────────────────────────────┐
|
|
│ manager.saveSnapshot(generatedConfig) │
|
|
│ Writes: /app/data/caddy/config-<timestamp>.json │
|
|
│ (For rollback only, not active config) │
|
|
└─────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## 8. Key Takeaways
|
|
|
|
1. **`/app/data/caddy/config.json` NEVER existed** - This is not a regression or bug
|
|
2. **Charon uses Caddy Admin API** - This is the modern, recommended approach for Caddy v2
|
|
3. **Snapshots are for rollback** - They are not the active config source
|
|
4. **Caddy is working correctly** - Logs show successful proxy operations
|
|
5. **CrowdSec warning is cosmetic** - Does not impact Caddy functionality
|
|
|
|
---
|
|
|
|
## 9. References
|
|
|
|
### Code Files Analyzed
|
|
|
|
- [backend/internal/caddy/manager.go](../../backend/internal/caddy/manager.go) - Configuration lifecycle management
|
|
- [backend/internal/caddy/config.go](../../backend/internal/caddy/config.go) - Configuration generation (850+ lines)
|
|
- [backend/internal/caddy/client.go](../../backend/internal/caddy/client.go) - Admin API HTTP client
|
|
- [backend/internal/config/config.go](../../backend/internal/config/config.go) - Application settings
|
|
- [backend/cmd/api/main.go](../../backend/cmd/api/main.go) - Application startup
|
|
|
|
### Caddy Documentation
|
|
|
|
- [Caddy Admin API](https://caddyserver.com/docs/api)
|
|
- [Caddy Config Structure](https://caddyserver.com/docs/json/)
|
|
- [Caddy Autosave](https://caddyserver.com/docs/json/admin/config/persist/)
|
|
|
|
---
|
|
|
|
## Appendix: Verification Commands Summary
|
|
|
|
```bash
|
|
# 1. Check Caddy is running
|
|
docker exec charon caddy version
|
|
|
|
# 2. Query active configuration
|
|
curl -s http://localhost:2019/config/ | jq '.'
|
|
|
|
# 3. List config snapshots
|
|
docker exec charon ls -lh /app/data/caddy/config-*.json
|
|
|
|
# 4. View latest snapshot
|
|
docker exec charon cat /app/data/caddy/config-1766170642.json | jq '.'
|
|
|
|
# 5. Check container logs
|
|
docker logs charon --tail 100 2>&1
|
|
|
|
# 6. Monitor real-time logs
|
|
docker logs -f charon
|
|
|
|
# 7. Test proxy is working (from host)
|
|
curl -I https://yourdomain.com
|
|
|
|
# 8. Check Caddy health via Admin API
|
|
curl -s http://localhost:2019/metrics
|
|
```
|
|
|
|
---
|
|
|
|
**Investigation Complete** ✅
|
|
**Status:** System working as designed, no action required.
|