- Documented certificate management security features in security.md, including backup and recovery processes. - Implemented CertificateCleanupDialog component for confirming deletion of orphaned certificates when deleting proxy hosts. - Enhanced ProxyHosts page to check for orphaned certificates and prompt users accordingly during deletion. - Added tests for certificate cleanup prompts and behaviors in ProxyHosts, ensuring correct handling of unique, shared, and production certificates.
15 KiB
API Documentation
Charon REST API documentation. All endpoints return JSON and use standard HTTP status codes.
Base URL
http://localhost:8080/api/v1
Authentication
🚧 Authentication is not yet implemented. All endpoints are currently public.
Future authentication will use JWT tokens:
Authorization: Bearer <token>
Response Format
Success Response
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"name": "Example",
"created_at": "2025-01-18T10:00:00Z"
}
Error Response
{
"error": "Resource not found",
"code": 404
}
Status Codes
| Code | Description |
|---|---|
| 200 | Success |
| 201 | Created |
| 204 | No Content (successful deletion) |
| 400 | Bad Request (validation error) |
| 404 | Not Found |
| 500 | Internal Server Error |
Endpoints
Metrics (Prometheus)
Expose internal counters for scraping.
GET /metrics
No authentication required. Primary WAF metrics:
charon_waf_requests_total
charon_waf_blocked_total
charon_waf_monitored_total
Health Check
Check API health status.
GET /health
Response 200:
{
"status": "ok"
}
Security Suite (Cerberus)
Status
GET /security/status
Returns enabled flag plus modes for each module.
Get Global Security Config
GET /security/config
Response 200 (no config yet): { "config": null }
Upsert Global Security Config
POST /security/config
Content-Type: application/json
Request Body (example):
{
"name": "default",
"enabled": true,
"admin_whitelist": "198.51.100.10,203.0.113.0/24",
"crowdsec_mode": "local",
"waf_mode": "monitor",
"waf_rules_source": "owasp-crs-local"
}
Response 200: { "config": { ... } }
Enable Cerberus
POST /security/enable
Payload (optional break-glass token):
{ "break_glass_token": "abcd1234" }
Disable Cerberus
POST /security/disable
Payload (required if not localhost):
{ "break_glass_token": "abcd1234" }
Generate Break-Glass Token
POST /security/breakglass/generate
Response 200: { "token": "plaintext-token-once" }
List Security Decisions
GET /security/decisions?limit=50
Response 200: { "decisions": [ ... ] }
Create Manual Decision
POST /security/decisions
Content-Type: application/json
Payload:
{ "ip": "203.0.113.5", "action": "block", "details": "manual temporary block" }
List Rulesets
GET /security/rulesets
Response 200: { "rulesets": [ ... ] }
Upsert Ruleset
POST /security/rulesets
Content-Type: application/json
Payload:
{
"name": "owasp-crs-quick",
"source_url": "https://example.com/owasp-crs.txt",
"mode": "owasp",
"content": "# raw rules"
}
Response 200: { "ruleset": { ... } }
Delete Ruleset
DELETE /security/rulesets/:id
Response 200: { "deleted": true }
SSL Certificates
List All Certificates
GET /certificates
Response 200:
[
{
"id": 1,
"uuid": "cert-uuid-123",
"name": "My Custom Cert",
"provider": "custom",
"domains": "example.com, www.example.com",
"expires_at": "2026-01-01T00:00:00Z",
"created_at": "2025-01-01T10:00:00Z"
}
]
Upload Certificate
POST /certificates/upload
Content-Type: multipart/form-data
Request Body:
name(required) - Certificate namecertificate_file(required) - Certificate file (.crt or .pem)key_file(required) - Private key file (.key or .pem)
Response 201:
{
"id": 1,
"uuid": "cert-uuid-123",
"name": "My Custom Cert",
"provider": "custom",
"domains": "example.com"
}
Delete Certificate
Delete a certificate. Requires that the certificate is not currently in use by any proxy hosts.
DELETE /certificates/:id
Parameters:
id(path) - Certificate ID (numeric)
Response 200:
{
"message": "certificate deleted"
}
Response 400:
{
"error": "invalid id"
}
Response 409:
{
"error": "certificate is in use by one or more proxy hosts"
}
Response 500:
{
"error": "failed to delete certificate"
}
Note: A backup is automatically created before deletion. The certificate files are removed from disk along with the database record.
Proxy Hosts
List All Proxy Hosts
GET /proxy-hosts
Response 200:
[
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"domain": "example.com, www.example.com",
"forward_scheme": "http",
"forward_host": "localhost",
"forward_port": 8080,
"ssl_forced": false,
"http2_support": true,
"hsts_enabled": false,
"hsts_subdomains": false,
"block_exploits": true,
"websocket_support": false,
"enabled": true,
"remote_server_id": null,
"created_at": "2025-01-18T10:00:00Z",
"updated_at": "2025-01-18T10:00:00Z"
}
]
Get Proxy Host
GET /proxy-hosts/:uuid
Parameters:
uuid(path) - Proxy host UUID
Response 200:
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"domain": "example.com",
"forward_scheme": "https",
"forward_host": "backend.internal",
"forward_port": 9000,
"ssl_forced": true,
"websocket_support": false,
"enabled": true,
"created_at": "2025-01-18T10:00:00Z",
"updated_at": "2025-01-18T10:00:00Z"
}
Response 404:
{
"error": "Proxy host not found"
}
Create Proxy Host
POST /proxy-hosts
Content-Type: application/json
Request Body:
{
"domain": "new.example.com",
"forward_scheme": "http",
"forward_host": "localhost",
"forward_port": 3000,
"ssl_forced": false,
"http2_support": true,
"hsts_enabled": false,
"hsts_subdomains": false,
"block_exploits": true,
"websocket_support": false,
"enabled": true,
"remote_server_id": null
}
Required Fields:
domain- Domain name(s), comma-separatedforward_host- Target hostname or IPforward_port- Target port number
Optional Fields:
forward_scheme- Default:"http"ssl_forced- Default:falsehttp2_support- Default:truehsts_enabled- Default:falsehsts_subdomains- Default:falseblock_exploits- Default:truewebsocket_support- Default:falseenabled- Default:trueremote_server_id- Default:null
Response 201:
{
"uuid": "550e8400-e29b-41d4-a716-446655440001",
"domain": "new.example.com",
"forward_scheme": "http",
"forward_host": "localhost",
"forward_port": 3000,
"created_at": "2025-01-18T10:05:00Z",
"updated_at": "2025-01-18T10:05:00Z"
}
Response 400:
{
"error": "domain is required"
}
Update Proxy Host
PUT /proxy-hosts/:uuid
Content-Type: application/json
Parameters:
uuid(path) - Proxy host UUID
Request Body: (all fields optional)
{
"domain": "updated.example.com",
"forward_port": 8081,
"ssl_forced": true
}
Response 200:
{
"uuid": "550e8400-e29b-41d4-a716-446655440000",
"domain": "updated.example.com",
"forward_port": 8081,
"ssl_forced": true,
"updated_at": "2025-01-18T10:10:00Z"
}
Delete Proxy Host
DELETE /proxy-hosts/:uuid
Parameters:
uuid(path) - Proxy host UUID
Response 204: No content
Response 404:
{
"error": "Proxy host not found"
}
Remote Servers
List All Remote Servers
GET /remote-servers
Query Parameters:
enabled(optional) - Filter by enabled status (trueorfalse)
Response 200:
[
{
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"name": "Docker Registry",
"provider": "docker",
"host": "registry.local",
"port": 5000,
"reachable": true,
"last_checked": "2025-01-18T09:55:00Z",
"enabled": true,
"created_at": "2025-01-18T09:00:00Z",
"updated_at": "2025-01-18T09:55:00Z"
}
]
Get Remote Server
GET /remote-servers/:uuid
Parameters:
uuid(path) - Remote server UUID
Response 200:
{
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"name": "Docker Registry",
"provider": "docker",
"host": "registry.local",
"port": 5000,
"reachable": true,
"enabled": true
}
Create Remote Server
POST /remote-servers
Content-Type: application/json
Request Body:
{
"name": "Production API",
"provider": "generic",
"host": "api.prod.internal",
"port": 8080,
"enabled": true
}
Required Fields:
name- Server namehost- Hostname or IPport- Port number
Optional Fields:
provider- One of:generic,docker,kubernetes,aws,gcp,azure(default:generic)enabled- Default:true
Response 201:
{
"uuid": "660e8400-e29b-41d4-a716-446655440001",
"name": "Production API",
"provider": "generic",
"host": "api.prod.internal",
"port": 8080,
"reachable": false,
"enabled": true,
"created_at": "2025-01-18T10:15:00Z"
}
Update Remote Server
PUT /remote-servers/:uuid
Content-Type: application/json
Request Body: (all fields optional)
{
"name": "Updated Name",
"port": 8081,
"enabled": false
}
Response 200:
{
"uuid": "660e8400-e29b-41d4-a716-446655440000",
"name": "Updated Name",
"port": 8081,
"enabled": false,
"updated_at": "2025-01-18T10:20:00Z"
}
Delete Remote Server
DELETE /remote-servers/:uuid
Response 204: No content
Test Remote Server Connection
Test connectivity to a remote server.
POST /remote-servers/:uuid/test
Parameters:
uuid(path) - Remote server UUID
Response 200:
{
"reachable": true,
"address": "registry.local:5000",
"timestamp": "2025-01-18T10:25:00Z"
}
Response 200 (unreachable):
{
"reachable": false,
"address": "offline.server:8080",
"error": "connection timeout",
"timestamp": "2025-01-18T10:25:00Z"
}
Note: This endpoint updates the reachable and last_checked fields on the remote server.
Import Workflow
Check Import Status
Check if there's an active import session.
GET /import/status
Response 200 (no session):
{
"has_pending": false
}
Response 200 (active session):
{
"has_pending": true,
"session": {
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"filename": "Caddyfile",
"state": "reviewing",
"created_at": "2025-01-18T10:30:00Z",
"updated_at": "2025-01-18T10:30:00Z"
}
}
Get Import Preview
Get preview of hosts to be imported (only available when session state is reviewing).
GET /import/preview
Response 200:
{
"hosts": [
{
"domain": "example.com",
"forward_host": "localhost",
"forward_port": 8080,
"forward_scheme": "http"
},
{
"domain": "api.example.com",
"forward_host": "backend",
"forward_port": 9000,
"forward_scheme": "https"
}
],
"conflicts": [
"example.com already exists"
],
"errors": []
}
Response 404:
{
"error": "No active import session"
}
Upload Caddyfile
Upload a Caddyfile for import.
POST /import/upload
Content-Type: application/json
Request Body:
{
"content": "example.com {\n reverse_proxy localhost:8080\n}",
"filename": "Caddyfile"
}
Required Fields:
content- Caddyfile content
Optional Fields:
filename- Original filename (default:"Caddyfile")
Response 201:
{
"session": {
"uuid": "770e8400-e29b-41d4-a716-446655440000",
"filename": "Caddyfile",
"state": "parsing",
"created_at": "2025-01-18T10:35:00Z"
}
}
Response 400:
{
"error": "content is required"
}
Commit Import
Commit the import after resolving conflicts.
POST /import/commit
Content-Type: application/json
Request Body:
{
"session_uuid": "770e8400-e29b-41d4-a716-446655440000",
"resolutions": {
"example.com": "overwrite",
"api.example.com": "keep"
}
}
Required Fields:
session_uuid- Active import session UUIDresolutions- Map of domain to resolution strategy
Resolution Strategies:
"keep"- Keep existing configuration, skip import"overwrite"- Replace existing with imported configuration"skip"- Same as keep
Response 200:
{
"imported": 2,
"skipped": 1,
"failed": 0
}
Response 400:
{
"error": "Invalid session or unresolved conflicts"
}
Cancel Import
Cancel an active import session.
DELETE /import/cancel?session_uuid=770e8400-e29b-41d4-a716-446655440000
Query Parameters:
session_uuid- Active import session UUID
Response 204: No content
Rate Limiting
🚧 Rate limiting is not yet implemented.
Future rate limits:
- 100 requests per minute per IP
- 1000 requests per hour per IP
Pagination
🚧 Pagination is not yet implemented.
Future pagination:
GET /proxy-hosts?page=1&per_page=20
Filtering and Sorting
🚧 Advanced filtering is not yet implemented.
Future filtering:
GET /proxy-hosts?enabled=true&sort=created_at&order=desc
Webhooks
🚧 Webhooks are not yet implemented.
Future webhook events:
proxy_host.createdproxy_host.updatedproxy_host.deletedremote_server.unreachableimport.completed
SDKs
No official SDKs yet. The API follows REST conventions and can be used with any HTTP client.
JavaScript/TypeScript Example
const API_BASE = 'http://localhost:8080/api/v1';
// List proxy hosts
const hosts = await fetch(`${API_BASE}/proxy-hosts`).then(r => r.json());
// Create proxy host
const newHost = await fetch(`${API_BASE}/proxy-hosts`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
domain: 'example.com',
forward_host: 'localhost',
forward_port: 8080
})
}).then(r => r.json());
// Test remote server
const testResult = await fetch(`${API_BASE}/remote-servers/${uuid}/test`, {
method: 'POST'
}).then(r => r.json());
Python Example
import requests
API_BASE = 'http://localhost:8080/api/v1'
# List proxy hosts
hosts = requests.get(f'{API_BASE}/proxy-hosts').json()
# Create proxy host
new_host = requests.post(f'{API_BASE}/proxy-hosts', json={
'domain': 'example.com',
'forward_host': 'localhost',
'forward_port': 8080
}).json()
# Test remote server
test_result = requests.post(f'{API_BASE}/remote-servers/{uuid}/test').json()
Support
For API issues or questions:
- GitHub Issues: https://github.com/Wikid82/charon/issues
- Discussions: https://github.com/Wikid82/charon/discussions