Web Application Firewall built with Kemal framework
HTTP endpoints for health checks, metrics, and the admin API.
/healthHealth check endpoint to verify WAF is running.
Request:
curl http://localhost:3030/health
Response:
{
"status": "healthy",
"rules_loaded": 42,
"observe_mode": false
}
Response Fields:
status: Service status (healthy or unhealthy)rules_loaded: Number of active WAF rulesobserve_mode: Whether observe mode is enabledStatus Codes:
200 OK - Service is healthy503 Service Unavailable - Service is unhealthy/metricsPrometheus format metrics endpoint.
Request:
curl http://localhost:9090/metrics
Response:
# HELP waf_requests_total Total number of requests processed
# TYPE waf_requests_total counter
waf_requests_total 12345
# HELP waf_blocked_total Number of blocked requests
# TYPE waf_blocked_total counter
waf_blocked_total 123
# HELP waf_observed_total Number of requests matched in observe mode
# TYPE waf_observed_total counter
waf_observed_total 45
# HELP waf_rules_loaded Number of loaded rules
# TYPE waf_rules_loaded gauge
waf_rules_loaded 42
Metrics:
waf_requests_total - Total number of requests processedwaf_blocked_total - Number of blocked requestswaf_observed_total - Number of requests matched in observe modewaf_rules_loaded - Number of loaded rulesThe Admin Panel provides a REST API for managing domains, rules, and configuration.
http://localhost:8888/api/https://yourdomain.com/admin/api/All API endpoints require JWT authentication. Get a token by logging in through the Admin Panel UI.
Request:
curl -X POST http://localhost:8888/api/auth/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "password"}'
Response:
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"expires_in": 3600
}
Using Token:
curl -H "Authorization: Bearer <token>" \
http://localhost:8888/api/domains
/api/domainsList all configured domains.
Response:
[
{
"domain": "example.com",
"default_upstream": "http://localhost:8080",
"letsencrypt_enabled": false
}
]
/api/domainsCreate a new domain configuration.
Request:
{
"domain": "example.com",
"default_upstream": "http://localhost:8080",
"letsencrypt_enabled": true,
"letsencrypt_email": "admin@example.com"
}
/api/domains/:domainUpdate domain configuration.
/api/domains/:domainDelete domain configuration.
/api/rulesList all active rules.
Response:
[
{
"id": 942100,
"name": "SQL Injection Detection",
"msg": "SQL Injection Attack Detected",
"category": "sqli",
"severity": "CRITICAL",
"action": "deny"
}
]
/api/rulesCreate a new rule.
Request:
{
"id": 942100,
"name": "SQL Injection Detection",
"msg": "SQL Injection Attack Detected",
"operator": "libinjection_sqli",
"variables": ["ARGS", "BODY"],
"action": "deny"
}
/api/rules/:idUpdate a rule.
/api/rules/:idDelete a rule.
curl -i http://localhost:3030/api/users
Expected: 200 OK (response from upstream)
curl -i "http://localhost:3030/api/users?id=1' OR '1'='1"
Expected: 403 Forbidden
{
"error": "Request blocked by WAF",
"rule_id": 942100,
"message": "SQL Injection Attack Detected via libinjection"
}
curl -i "http://localhost:3030/search?q=<script>alert('xss')</script>"
Expected: 403 Forbidden
{
"error": "Request blocked by WAF",
"rule_id": 941100,
"message": "XSS Attack Detected"
}
curl -i -X POST http://localhost:3030/api/login \
-H "Content-Type: application/json" \
-d '{"username": "admin", "password": "pass OR 1=1"}'
Expected: 403 Forbidden
# Enable observe mode in config
# config/waf.yml: mode: observe
# Try SQL injection
curl -i "http://localhost:3030/api/users?id=1' OR '1'='1"
Expected: 200 OK (proxied to upstream, but logged)
Check logs:
docker logs kemal-waf | grep "OBSERVE MODE"
Request blocked by WAF rule.
{
"error": "Request blocked by WAF",
"rule_id": 942100,
"message": "SQL Injection Attack Detected"
}
Rate limit exceeded.
{
"error": "Rate limit exceeded",
"retry_after": 60
}
Upstream server error or domain not configured.
{
"error": "Bad Gateway",
"message": "Upstream server unavailable"
}
When rate limiting is enabled, responses include rate limit headers:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 95
X-RateLimit-Reset: 1640995200
Scrape metrics from /metrics endpoint:
# prometheus.yml
scrape_configs:
- job_name: 'kemal-waf'
static_configs:
- targets: ['localhost:9090']
Use Prometheus as data source and create dashboards for:
The Admin Panel uses WebSocket for real-time updates. Connect to:
ws://localhost:8888/ws
Authentication is required via JWT token in the connection header.