You've already forked caddy-opnsense-blocker
160 lines
4.8 KiB
Markdown
160 lines
4.8 KiB
Markdown
# 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, IPs, and the full request history of a selected address.
|
|
- On-demand IP investigation with persistent caching for bot verification, reverse DNS, RDAP, and Spamhaus lookups.
|
|
- Manual block, unblock, and clear-override actions with OPNsense-aware UI state.
|
|
- 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 `POST` requests
|
|
- `.php` path 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 policy engine later.
|
|
|
|
## Architecture
|
|
|
|
- `internal/caddylog`: parses default Caddy JSON access logs
|
|
- `internal/engine`: evaluates requests against a profile
|
|
- `internal/investigation`: performs on-demand bot verification and IP enrichment
|
|
- `internal/store`: persists events, IP state, manual decisions, backend actions, and source offsets
|
|
- `internal/opnsense`: manages the target OPNsense alias through its API
|
|
- `internal/service`: runs concurrent log followers and applies automatic decisions
|
|
- `internal/web`: serves the local review UI and JSON API
|
|
|
|
## License
|
|
|
|
This project is licensed under the MIT License. See `LICENSE`.
|
|
|
|
## Quick start
|
|
|
|
1. Generate or provision OPNsense API credentials.
|
|
2. Copy `config.example.yaml` to `config.yaml` and adapt it.
|
|
3. Start the daemon:
|
|
|
|
```bash
|
|
CGO_ENABLED=0 go run ./cmd/caddy-opnsense-blocker -config ./config.yaml
|
|
```
|
|
|
|
4. 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: end` means “start following new lines only” on first boot.
|
|
- The `investigation` section controls how long IP enrichment is cached and whether on-demand Spamhaus lookups are enabled.
|
|
- 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 /healthz`
|
|
- `GET /api/overview`
|
|
- `GET /api/events`
|
|
- `GET /api/ips`
|
|
- `GET /api/ips/{ip}`
|
|
- `POST /api/ips/{ip}/investigate`
|
|
- `POST /api/ips/{ip}/block`
|
|
- `POST /api/ips/{ip}/unblock`
|
|
- `POST /api/ips/{ip}/clear-override`
|
|
|
|
The legacy `POST /api/ips/{ip}/reset` endpoint is still accepted as a backwards-compatible alias for `clear-override`.
|
|
|
|
## Development
|
|
|
|
Run the test suite:
|
|
|
|
```bash
|
|
CGO_ENABLED=0 go test ./...
|
|
```
|
|
|
|
Build the daemon:
|
|
|
|
```bash
|
|
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.
|
|
|
|
## Nix packaging
|
|
|
|
The repository ships with first-class Nix files:
|
|
|
|
- `package.nix`: reusable package definition
|
|
- `default.nix`: convenience entry point for `nix-build`
|
|
- `module.nix`: reusable NixOS module
|
|
|
|
Build the package directly from the repository root:
|
|
|
|
```bash
|
|
nix-build
|
|
```
|
|
|
|
Use the NixOS module from another configuration:
|
|
|
|
```nix
|
|
{
|
|
imports = [ /path/to/caddy-opnsense-blocker/module.nix ];
|
|
|
|
services.caddy-opnsense-blocker = {
|
|
enable = true;
|
|
credentials.opnsenseApiKeyFile = "/run/secrets/opnsense-api-key";
|
|
credentials.opnsenseApiSecretFile = "/run/secrets/opnsense-api-secret";
|
|
|
|
settings = {
|
|
opnsense = {
|
|
enabled = true;
|
|
base_url = "https://router.example.test";
|
|
ensure_alias = true;
|
|
alias.name = "blocked-ips";
|
|
};
|
|
|
|
profiles.public-web = {
|
|
auto_block = true;
|
|
block_unexpected_posts = true;
|
|
block_php_paths = true;
|
|
suspicious_path_prefixes = [ "/wp-admin" "/wp-login.php" "/.env" ];
|
|
};
|
|
|
|
sources = [
|
|
{
|
|
name = "public-web";
|
|
path = "/var/log/caddy/public-web.json";
|
|
profile = "public-web";
|
|
}
|
|
];
|
|
};
|
|
};
|
|
}
|
|
```
|
|
|
|
## Roadmap
|
|
|
|
- richer decision engine
|
|
- optional GeoIP and ASN providers beyond RDAP
|
|
- richer review filters in the UI
|
|
- alternative blocking backends besides OPNsense
|
|
- direct streaming ingestion targets in addition to file polling
|