Add read-only web dashboard and phone push notifications
Both zero-dependency (stdlib http.server + urllib), consistent with the project's no-dependency-tree stance for a security daemon. Web dashboard (enodia_sentinel/web.py + static/dashboard.html): - read-only JSON API over the log dir: /api/status, /api/alerts, /api/alerts/<id>, /api/events - bearer-token auth (constant-time; header or ?token=), required on non- loopback binds, auto-generated + persisted (0600) when unset - binds the host's Tailscale IP by default (auto-detected), reachable from the tailnet but not the LAN/internet - self-contained dark SPA: severity cards, live alert list, full snapshot viewer; 10s auto-refresh - path-traversal-safe alert lookup; `enodia-sentinel web` subcommand; daemon now writes a pidfile so the dashboard can show live status - hardened enodia-sentinel-web.service (read-only, no caps) Phone push (enodia_sentinel/notify/): - pluggable backends — ntfy, Pushover, generic webhook — each separating a pure build() (unit-tested, no network) from send() - a backend turns on when its config keys are set; pushes gated by notify_min_severity; severity → per-service priority/tags - fired from snapshot.capture on worker threads, errors swallowed - desktop notify-send retained Tests: +16 (9 web incl. a real-server 401/200 auth test, 7 notify request-build cases). 55/55 pass. Live end-to-end verified: daemon → alert → API → page. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
0eb5077551
commit
c00fff224c
17 changed files with 960 additions and 5 deletions
48
README.md
48
README.md
|
|
@ -72,8 +72,11 @@ enodia_sentinel/
|
|||
├── config.py dataclass config (TOML + env overrides)
|
||||
├── netutil.py public-IP / CIDR logic (stdlib ipaddress)
|
||||
├── alert.py Alert / Severity (with Snort-style sid + classtype)
|
||||
├── web.py read-only dashboard: stdlib http server + JSON API + auth
|
||||
├── static/ the self-contained dashboard SPA
|
||||
├── detectors/ poll detectors — one module per signature, each a pure
|
||||
│ function: detect(state, cfg) -> Iterable[Alert]
|
||||
├── notify/ outbound push — ntfy / Pushover / webhook backends
|
||||
└── events/ event-driven layer (eBPF)
|
||||
├── bcc_source.py real eBPF execve probe loaded via bcc
|
||||
├── exec_event.py the ExecEvent type
|
||||
|
|
@ -191,6 +194,48 @@ enodia-sentinel.service`. Every key is optional. Highlights:
|
|||
| `capture_execve_bpftrace` | false | add a bpftrace execve trace to snapshots |
|
||||
| `notify_users` | [] | desktop notify-send targets |
|
||||
|
||||
## Web dashboard
|
||||
|
||||
A read-only console, served by the stdlib `http.server` (no Flask, no JS
|
||||
framework, no CDN — one self-contained page):
|
||||
|
||||
```bash
|
||||
enodia-sentinel web # serves on the Tailscale IP by default
|
||||
# or as a service:
|
||||
sudo systemctl enable --now enodia-sentinel-web
|
||||
```
|
||||
|
||||
- **Bound to your Tailscale interface** by default (auto-detected), so it's
|
||||
reachable from your phone/laptop on the tailnet but not the LAN or internet.
|
||||
- **Bearer-token auth** (constant-time check); the token is auto-generated and
|
||||
saved on first run and printed in the startup line. Open
|
||||
`http://<tailscale-ip>:8787/?token=…`.
|
||||
- **Read-only**: severity cards, a live alert list, and the full forensic
|
||||
snapshot per alert. No actions, no writes — minimal attack surface for
|
||||
sensitive data. JSON API at `/api/status`, `/api/alerts`, `/api/alerts/<id>`,
|
||||
`/api/events`.
|
||||
|
||||
## Phone push notifications
|
||||
|
||||
When an alert at/above `notify_min_severity` fires, Sentinel pushes to whichever
|
||||
backends you've configured (all via stdlib `urllib`, no SDKs):
|
||||
|
||||
| Backend | Enable by setting | Notes |
|
||||
|---|---|---|
|
||||
| **ntfy** | `notify_ntfy_url` + `notify_ntfy_topic` | open-source, self-hostable, free apps |
|
||||
| **Pushover** | `notify_pushover_token` + `_user` | polished, reliable |
|
||||
| **Webhook** | `notify_webhook_url` | generic JSON POST (Discord/Slack/your own) |
|
||||
|
||||
Severity maps to each service's priority (a CRITICAL is an urgent ntfy push / a
|
||||
high-priority Pushover). Sends happen on worker threads and swallow their own
|
||||
errors — a flaky notifier never stalls detection.
|
||||
|
||||
```toml
|
||||
notify_min_severity = "HIGH"
|
||||
notify_ntfy_url = "https://ntfy.sh"
|
||||
notify_ntfy_topic = "enodia-7Hq2x" # keep this secret — it's the access control
|
||||
```
|
||||
|
||||
## Security model
|
||||
|
||||
Sentinel runs as root because it must read every process's `/proc`, the full
|
||||
|
|
@ -237,6 +282,9 @@ regression suite for both.
|
|||
|
||||
## Project status
|
||||
|
||||
v0.4 — adds a read-only **web dashboard** (stdlib server, Tailscale-bound,
|
||||
token-auth) and **phone push** (ntfy / Pushover / webhook), both zero-dependency.
|
||||
|
||||
v0.3 — adds the event-driven **eBPF layer**: a real `bcc` execve probe feeding a
|
||||
Snort-style declarative rule engine (4 default rules), stable signature IDs +
|
||||
classtypes on every detection, fail-safe degradation to poll-only, and an
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue