Security
Fleetdock's trust model — Manager and Agent privileges, tokens and session invalidation, encryption, SSRF protection, compromise scenarios, and current limitations.
Fleetdock holds credentials for your entire database fleet, so its security model is explicit — including the boundaries it does not yet cover. Read this before exposing the control plane. To report a vulnerability, see the security policy.
Trust boundaries
Internet / corp network
│
Caddy :80/:443 (the only public HTTP surface)
│
Fleetdock :8080 ── Metadata DB (Postgres, private)
one process: API + dashboard
│
┌───────────┴────────────┐
Managed servers External DBs
(agent → API, outbound) (API → host:port, direct)
│
Fleetdock Gateway (optional, off by default)
CIDR-gated TCP proxy → instance portsThe control plane is a single process serving both the API and the dashboard on one port, behind a bundled Caddy that terminates TLS. Nothing else needs to be published: the dashboard has no port of its own, and the API is not reachable except through the proxy.
Manager privileges
The Manager is the highest-value component. It stores encrypted credentials for the whole fleet and can enqueue operations on every node. Protect it accordingly: terminate TLS in front of it, restrict who can reach it, and monitor the Operations log.
Agent privileges
Each agent runs as root via systemd today and executes approved operations on its own node. Its authority is scoped to that single node and the databases on it. Treat any host running an agent as one that can act on its local databases and Docker.
Docker socket access
Provisioning uses Docker on the node, so the agent can create and remove database containers and their data volumes on that host. Docker access is powerful — a compromised agent has Docker-level control of its node.
Database credentials
Instance admin credentials and destination storage keys are envelope-encrypted at rest
with FLEETDOCK_ENCRYPTION_KEY. Use least-privilege database users for stored admin
credentials so a leak cannot exceed its grants.
Gateway and public access
The Fleetdock Gateway (HAProxy) exposes databases to external applications through dedicated TCP ports with CIDR enforcement at the proxy layer.
- Public access is disabled by default — you must explicitly enable it per database.
- CIDR allowlists are enforced in HAProxy;
0.0.0.0/0requires an explicit choice (the UI warns). - Application credentials (scoped users with
readonly,readwrite, oradminprofiles) are used for external apps — instance root credentials are never exposed for public use. - Connectivity changes, credential creation, rotation, and revocation are written to an
append-only
audit_eventstable (anUPDATE/DELETEtrigger forbids mutation).
Those events are recorded but not yet readable: there is no API endpoint
and no dashboard page for them in the current release. To inspect them today,
query audit_events on the metadata database directly (fleetdock psql).
Surfacing them in the API and UI is planned.
See External database access for setup and TLS behavior (TCP passthrough in the current MVP).
Tokens and their lifecycle
Fleetdock uses three token types, each with a display prefix:
| Token | Prefix | Purpose | Lifecycle |
|---|---|---|---|
| Registration token | fleetr_ | Enroll a new agent | Single-use, short-lived |
| Agent bearer token | fleeta_ | Ongoing agent authentication | Issued on enrollment, stored on the node |
| API token | fleetd_ | Automation / CI access | Created, listed, and revoked in the dashboard |
Never share a real registration token or paste one into a public channel.
Because the agent's fleeta_ token is stored on disk in the agent state
directory, protect that directory's permissions and restrict backup access to
it.
Creating, expiring, and revoking tokens
Create API tokens scoped like sessions and set an expiry. Revoke API tokens from the
dashboard. Rotating FLEETDOCK_JWT_SECRET invalidates all sessions and login-derived API
tokens (but not agent tokens). Suspending a user account stops its sessions and tokens from
working immediately.
Session invalidation on credential change
Every issued JWT carries a token epoch — a counter stored on the user record and compared on every authenticated request. Changing or resetting a password increments it, which immediately invalidates every session and login-derived token that user already had, everywhere.
This is what makes "reset the password" a real containment step: a stolen JWT that has not
yet expired stops working the moment the password changes, without waiting out its TTL or
rotating the server-wide FLEETDOCK_JWT_SECRET and logging out everyone else.
Explicit revocation of an issued agent registration/bearer token is limited in the current release — plan to re-enroll a node if you need to fully rotate its agent credentials. This gap is tracked in the project's product notes.
Authentication and encrypted transport
Login uses email and password (bcrypt-hashed) and issues a JWT. Deploy only over HTTPS — the bundled Caddy does this for you and obtains a certificate automatically. If you replace it with your own proxy, terminate TLS there and publish nothing but the proxy — leaving port 8080 reachable also defeats the login rate limiter (see below).
The dashboard stores its session JWT in localStorage, which is exposed to
XSS. Keep frontend dependencies patched, avoid untrusted browser extensions on
admin machines, and consider a VPN, SSO gateway, or IP allowlist in front of
the dashboard. httpOnly cookie sessions and SSO/OIDC are on the roadmap, not
in the current release.
Outbound requests (SSRF protection)
Notification channels let anyone with notification:write supply a URL that the control
plane will then fetch. Unguarded, that is a request-forgery primitive: a webhook pointed at
http://169.254.169.254/ reads cloud instance-metadata credentials, and one pointed at
loopback reaches services that were never meant to be public.
Webhook and Slack delivery therefore goes through an SSRF-guarded HTTP client that refuses to dial:
- loopback (
127.0.0.0/8,::1) - link-local unicast and multicast (
169.254.0.0/16,fe80::/10) - the unspecified address (
0.0.0.0,::) - cloud instance-metadata endpoints, including the IPv6 form
The check runs at dial time against the resolved IP, not on the URL string, so DNS rebinding and redirect-based bypasses are blocked too — a hostname that resolves to loopback is rejected at connect, however it got there.
RFC1918 private ranges (10/8, 172.16/12, 192.168/16) are deliberately
allowed: a self-hosted Mattermost or alerting endpoint on your LAN is a
legitimate webhook target. If your threat model needs those blocked too, keep
notification:write restricted to trusted roles.
Login rate limiting and proxy headers
Login is rate-limited per client IP. Which address counts as "the client" is controlled by
FLEETDOCK_TRUST_PROXY_HEADERS, and it is wrong in both directions if set carelessly:
| Setting | Client IP taken from | Use when |
|---|---|---|
false (default) | the transport peer | The API is reachable directly |
true | X-Forwarded-For | The API is reachable only through a trusted proxy |
Leaving it false behind a reverse proxy makes every login attempt on the internet share a
single bucket — the proxy's address — so one attacker locks out all users. Setting it true
while the API port is also directly reachable is worse: a client can spoof X-Forwarded-For
and get an unlimited number of attempts.
The bundled Caddy deployment sets it to true and never publishes port 8080, which is the
combination that is safe. If you put the API behind your own proxy, publish nothing but the
proxy.
Command authorization (RBAC)
Access is permission-based. System roles — owner, admin, operator, viewer — are
seeded from a permission catalog, and you can define custom roles from any subset of it.
Guards prevent lockout: the last active owner cannot be demoted, suspended, or deleted, and
you cannot suspend or delete yourself.
Backup access
Backups stream to object storage through time-limited presigned URLs. Agents move the bytes but never receive your storage credentials — the Manager holds and encrypts them. Keep backup buckets private and enable provider-side encryption.
Secret storage and rotation
Secrets are envelope-encrypted, so rotating the master key only re-wraps each secret's data key — payload ciphertext is untouched. Rotate with a new key id:
export FLEETDOCK_ENCRYPTION_KEYS_OLD="master-1=<old-secret>" # keep old key readable
export FLEETDOCK_ENCRYPTION_KEY="<new-secret>" # new primary secret
export FLEETDOCK_ENCRYPTION_KEY_ID="master-2" # new primary id
make rotate-keys
# once it reports 0 remaining, drop FLEETDOCK_ENCRYPTION_KEYS_OLDNever reuse a key id with a different secret — existing secrets would become unreadable. Always promote a new id when rotating.
Production hardening
Set FLEETDOCK_ENV=production. In production mode the API refuses to start if
FLEETDOCK_JWT_SECRET, FLEETDOCK_ENCRYPTION_KEY, or FLEETDOCK_ADMIN_PASSWORD still use
insecure defaults. The installer generates all three, so a default install is already in
this state.
API responses carry X-Content-Type-Options: nosniff, X-Frame-Options: DENY,
Referrer-Policy: no-referrer and Cache-Control: no-store — the last one matters because
API payloads include tokens, credentials and connection strings that must never be held by
an intermediary. Dashboard assets are served outside that chain so they keep their own
immutable caching. /healthz and /readyz are unauthenticated probes.
Node isolation and least privilege
- Keep database ports on private networks; use the Fleetdock Gateway for external access instead of publishing instance ports directly to the internet.
- Use least-privilege database users for stored admin credentials.
- Give people
vieweroroperatorroles rather thanownerby default. - Isolate nodes from one another so an agent compromise does not spread.
Compromise scenarios
- Manager compromise — the largest blast radius: an attacker could reach every stored credential and command every agent. Restrict access and monitor operations.
- Agent compromise — limited to that node's databases and Docker host, but that node's data is exposed. Isolate nodes and use least-privilege users.
- Database credential exposure — bounded by the user's grants; rotate the credential and re-test the connection.
- Backup credential exposure — storage keys are encrypted on the Manager and never given to agents; presigned URLs are time-limited. Rotate keys and keep buckets private.
Current limitations
- Session JWT is stored in
localStorage(XSS exposure). - No SSO/OIDC or 2FA yet.
- The agent runs as root via systemd.
- Agent registration and bearer tokens cannot be revoked individually — re-enroll the node.
- Audit events are recorded but not exposed through the API or dashboard yet.
- Gateway TLS is passthrough only — no termination at the proxy in the current MVP.
- Gateway CIDR allowlists match the address HAProxy observes, which is not the real client
address behind an L4 load balancer unless
FLEETDOCK_GATEWAY_SOURCE_IP_MODE=proxy-protocolis set. See External database access. - No Content-Security-Policy header is set.
- A single worker instance should run per metadata database (no external job locking yet).
External database access
Expose managed databases to applications outside your private network through the Fleetdock Gateway — HAProxy TCP proxy, CIDR allowlists, and application credentials.
Troubleshooting
Diagnose common Fleetdock problems — agent connectivity, tokens, TLS and DNS, Docker permissions, port conflicts, disk space, and stuck operations.