88 lines
4.2 KiB
Markdown
88 lines
4.2 KiB
Markdown
# Architecture
|
|
|
|
## Components and trust boundaries
|
|
|
|
The Rust agent is a stateless, pull-oriented HTTP API. It stores no metrics or
|
|
log history and does no background polling. `/proc`, `/sys`, journald, and fixed
|
|
read-only utilities are queried only when the control plane asks. The Go backend
|
|
owns users, sessions, servers, metric/log samples, retention, and audit. React
|
|
is served behind nginx and only calls the backend.
|
|
|
|
Grafana also talks only to the backend. The backend implements the constrained
|
|
read portions of the Prometheus and Loki HTTP APIs, backed by PostgreSQL. There
|
|
is intentionally no Grafana-to-agent or Loki-to-agent path and no agent token is
|
|
ever provisioned into Grafana.
|
|
|
|
Agent transport is expected to be WireGuard plus a 256-bit application token,
|
|
or TLS plus that token. The listen address is loopback by default. Tokens live in
|
|
files outside configuration and are never returned by an API.
|
|
|
|
## Agent API v1
|
|
|
|
All `/v1` routes require `Authorization: Bearer …`; `/healthz` only returns
|
|
liveness. JSON bodies are capped at 64 KiB. Read traffic is capped at 10 rps,
|
|
writes at 2 rps, burst 20, with 32 concurrent requests and five-second timeouts.
|
|
|
|
| Method | Route | Purpose |
|
|
|---|---|---|
|
|
| GET | `/v1/system/status` | CPU, load, memory, swap, filesystems, uptime |
|
|
| GET | `/v1/network/interfaces` | interfaces, addresses and counters |
|
|
| GET | `/v1/network/routes` | kernel routes |
|
|
| GET | `/v1/network/listeners` | listening TCP/UDP sockets |
|
|
| GET | `/v1/network/connections` | current TCP/UDP sockets |
|
|
| GET | `/v1/firewall/rules` | backend and read-only ruleset |
|
|
| GET | `/v1/fail2ban/status` | installation and jail summary |
|
|
| GET | `/v1/fail2ban/jails/:jail` | validated jail detail |
|
|
| POST | `/v1/fail2ban/ban` | validated IP and jail ban |
|
|
| POST | `/v1/fail2ban/unban` | validated IP and jail unban |
|
|
| GET | `/v1/wireguard/status` | interface and peer dump |
|
|
| GET | `/v1/services` | allow-listed services only |
|
|
| POST | `/v1/services/:name/restart` | allow-listed restart only |
|
|
| GET | `/v1/logs` | cursor-paginated journald rows from allow-listed units |
|
|
|
|
No generic command route exists. Child processes receive a fixed executable and
|
|
fixed subcommand plus individually validated argv values.
|
|
|
|
## Data model
|
|
|
|
- `users(id, username, password_hash, role, disabled, timestamps)`
|
|
- `sessions(id_hash, user_id, csrf_hash, expires_at, source_ip)`
|
|
- `servers(id, name, hostname, agent_endpoint, credential_ref, tags, status,
|
|
last_seen, timestamps)`
|
|
- `metric_samples(server_id, sampled_at, cpu, memory, disk, rx, tx,
|
|
connections, bans)` with 30-day retention
|
|
- `agent_log_entries(server_id, journal_cursor, event_at, unit, priority,
|
|
message, boot_id, pid)` with configurable seven-day retention
|
|
- `audit_events(actor, server, action, target, before, after, result, source_ip,
|
|
created_at)`; secrets are excluded
|
|
|
|
Credentials are indirect references to Docker secrets or environment variables,
|
|
not database values. Database migrations are append-only and run on startup.
|
|
|
|
## Observability compatibility APIs
|
|
|
|
`/integrations/prometheus` accepts direct selectors for the fixed
|
|
`vps_control_*` metric set. `/integrations/loki` accepts bounded stream selectors
|
|
over `server_id`, `server_name`, `unit`, and `priority`. Full PromQL, arbitrary
|
|
LogQL pipelines, writes, remote-write, and Loki push APIs are deliberately not
|
|
implemented. Queries are bearer-authenticated with an independent secret,
|
|
rate-limited, range-limited to 31 days, and result-limited.
|
|
|
|
The data path is:
|
|
|
|
```text
|
|
Grafana ──Prometheus/Loki read APIs──> backend ──semantic polling──> VPS agent
|
|
│
|
|
PostgreSQL
|
|
```
|
|
|
|
## Privilege design
|
|
|
|
The agent has no ambient capabilities and runs as `vps-agent`. Linux usually
|
|
permits the read-only endpoints without elevation. Fail2Ban mutations and
|
|
service restarts need administrator-provided sudoers or polkit rules restricted
|
|
to each approved verb/name. The shipped service does not install such policy
|
|
automatically. Managed firewall mutation is out of v1 because giving the main
|
|
daemon `CAP_NET_ADMIN` violates least privilege; its future helper protocol must
|
|
provide prepare/apply/health/confirm/commit and persistent timed rollback.
|