caddy-opnsense-blocker
caddy-opnsense-blocker is a local-first daemon that ingests Caddy access logs in their default JSON format, evaluates suspicious requests, keeps persistent local state in SQLite, provides a lightweight web UI for review, and blocks or unblocks IP addresses through an OPNsense alias.
Features
- Real-time ingestion of multiple Caddy JSON log files.
- One heuristic profile per log source.
- Persistent local state in SQLite.
- Local-only web UI for reviewing events and IPs.
- Manual block, unblock, and override reset actions.
- OPNsense alias backend with automatic alias creation.
- Concurrent polling across multiple log files.
Current scope
This first version is intentionally strong on ingestion, persistence, UI, and OPNsense integration. The decision engine is deliberately simple and deterministic for now:
- suspicious path prefixes
- unexpected
POSTrequests .phppath detection- explicit known-agent allow/deny rules
- excluded CIDR ranges
- manual overrides
This keeps the application usable immediately while leaving room for a more advanced network-intelligence engine later.
Architecture
internal/caddylog: parses default Caddy JSON access logsinternal/engine: evaluates requests against a profileinternal/store: persists events, IP state, manual decisions, backend actions, and source offsetsinternal/opnsense: manages the target OPNsense alias through its APIinternal/service: runs concurrent log followers and applies automatic decisionsinternal/web: serves the local review UI and JSON API
Quick start
- Generate or provision OPNsense API credentials.
- Copy
config.example.yamltoconfig.yamland adapt it. - Start the daemon:
CGO_ENABLED=0 go run ./cmd/caddy-opnsense-blocker -config ./config.yaml
- Open the local UI on the configured address, for example
http://127.0.0.1:9080.
Example configuration
See config.example.yaml.
Important points:
- Each source points to one Caddy log file.
- Each source references exactly one profile.
initial_position: endmeans “start following new lines only” on first boot.- The web UI should stay bound to a local address such as
127.0.0.1:9080.
Web UI and API
The web UI is intentionally small and server-rendered. It refreshes through lightweight JSON polling and exposes these endpoints:
GET /healthzGET /api/overviewGET /api/eventsGET /api/ipsGET /api/ips/{ip}POST /api/ips/{ip}/blockPOST /api/ips/{ip}/unblockPOST /api/ips/{ip}/reset
Development
Run the test suite:
CGO_ENABLED=0 go test ./...
Build the daemon:
CGO_ENABLED=0 go build ./cmd/caddy-opnsense-blocker
CGO_ENABLED=0 is useful on systems without a C toolchain. The application itself only relies on pure-Go dependencies.
Roadmap
- richer decision engine
- asynchronous DNS / RDAP / ASN enrichment
- richer review filters in the UI
- alternative blocking backends besides OPNsense
- direct streaming ingestion targets in addition to file polling