Fleetdock

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 ports

The 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/0 requires an explicit choice (the UI warns).
  • Application credentials (scoped users with readonly, readwrite, or admin profiles) 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_events table (an UPDATE/DELETE trigger 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:

TokenPrefixPurposeLifecycle
Registration tokenfleetr_Enroll a new agentSingle-use, short-lived
Agent bearer tokenfleeta_Ongoing agent authenticationIssued on enrollment, stored on the node
API tokenfleetd_Automation / CI accessCreated, 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:

SettingClient IP taken fromUse when
false (default)the transport peerThe API is reachable directly
trueX-Forwarded-ForThe 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_OLD

Never 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 viewer or operator roles rather than owner by 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-protocol is 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).

On this page