git init
This commit is contained in:
67
docs/security.md
Normal file
67
docs/security.md
Normal file
@@ -0,0 +1,67 @@
|
||||
# Security model
|
||||
|
||||
## Assumptions
|
||||
|
||||
The agent host, control-plane host, private transport, and browser are distinct
|
||||
trust zones. Root compromise is out of scope; reducing the blast radius of an
|
||||
application compromise is in scope. WireGuard/TLS protects transport while the
|
||||
application token authenticates the control plane. Control-plane cookies are
|
||||
HttpOnly, Secure in production, SameSite=Strict, short-lived, and paired with a
|
||||
CSRF header on mutations. Passwords use Argon2id.
|
||||
|
||||
## Threats and mitigations
|
||||
|
||||
| Threat | Mitigation |
|
||||
|---|---|
|
||||
| Public scanning | loopback default, documented WireGuard-only binding, no unauthenticated metadata beyond liveness |
|
||||
| Stolen agent token | file permissions, independent per-server credential refs, rotation without config secrets, TLS/WireGuard |
|
||||
| Malicious API input | typed JSON, size limits, IP/jail/service validation, fixed argv, no shell |
|
||||
| Request flooding | request timeouts, concurrency semaphore, token buckets, reverse-proxy limits |
|
||||
| Command injection | no command strings; executable and subcommands are constants; service names are allow-listed |
|
||||
| Privilege escalation | unprivileged user, systemd sandbox, no capabilities, write privileges opt-in and narrow |
|
||||
| Accidental firewall lockout | v1 is read-only; future mutations require local, crash-safe confirm-or-rollback |
|
||||
| Disk/log exhaustion | journald only, no metric spool, PostgreSQL retention, bounded body/audit fields |
|
||||
| Observability token theft | independent file secret, constant-time verification, per-source limiting, no agent credentials in Grafana |
|
||||
| Expensive dashboard queries | fixed metric set, restricted selectors, 31-day ranges, point/row/result caps |
|
||||
| Compromised browser | RBAC, CSRF, Secure cookies, no agent access, audit; administrators still must use a trusted endpoint |
|
||||
| Compromised control plane | semantic agent API and allow-lists limit actions; unique rotatable tokens reduce lateral movement |
|
||||
| Authentication brute force | generic errors, Argon2id, per-IP login limiter, login audit |
|
||||
| Suspicious traffic misclassification | display factual events and counts; no ungrounded “security score” |
|
||||
|
||||
## Credential rotation
|
||||
|
||||
Generate at least 32 random bytes (`openssl rand -hex 32`). Write the new value
|
||||
to `/etc/vps-agent/credentials/control-plane.token`, owned by `vps-agent`, mode
|
||||
0600, atomically replace
|
||||
the file, restart the agent, then update its control-plane Docker secret and
|
||||
restart the backend. Keep the overlap window short; the agent intentionally
|
||||
accepts one token. Never pass the token on a command line.
|
||||
|
||||
## Firewall safety design (future helper)
|
||||
|
||||
The future root helper owns only `table inet vps_control`. It writes a bounded
|
||||
snapshot (maximum five), fsyncs a pending transaction with a monotonic deadline,
|
||||
applies a complete validated table, and starts an independent rollback timer.
|
||||
Only an authenticated confirmation commits. On restart it rolls back any pending
|
||||
transaction before accepting a new one. Unknown tables/chains remain read-only.
|
||||
|
||||
## Operational logging
|
||||
|
||||
The agent logs startup/shutdown, invalid configuration, auth failures (without
|
||||
tokens), mutations, and unexpected errors. Successful metric polls are silent.
|
||||
Bound journald globally only after reviewing other services, for example with
|
||||
`SystemMaxUse=500M`, `RuntimeMaxUse=100M`, and `MaxRetentionSec=1month`.
|
||||
|
||||
Agent log export is off by default. When enabled, unit names are configuration
|
||||
allow-listed, journal cursors and timestamps are validated, message/page sizes
|
||||
are capped, subprocess time and output are bounded, and backlog ingestion is
|
||||
limited per poll. Common `Authorization`, password, token, secret, and API-key
|
||||
shapes are redacted on the agent and again before database insertion. This is a
|
||||
defense in depth measure, not a substitute for applications avoiding secrets in
|
||||
logs. PostgreSQL is the durable log store. The Loki-compatible API is read-only
|
||||
and cannot be used to inject logs.
|
||||
|
||||
Treat the integration bearer token like a password. Keep it distinct from all
|
||||
agent tokens, mount it from a secret file, rotate it by replacing the file and
|
||||
restarting backend/Grafana, and restrict `/integrations/` at the outer reverse
|
||||
proxy to Grafana source addresses where possible.
|
||||
Reference in New Issue
Block a user