Security hardening
TLS, CIDR allowlisting, fail-closed mode, and secrets encryption
Fuse’s auth model is deliberately simple: a single shared bearer token, no users, no roles. See Authentication for the full model. This page covers the operational hardening controls available on top of that model.
TLS
export ORCH_TLS_CERT=/path/to/cert.pem
export ORCH_TLS_KEY=/path/to/key.pem
Both unset (the default) serves plain HTTP. Setting both switches to HTTPS and
flips the session cookie’s Secure attribute on, an HttpOnly cookie without
Secure would otherwise be sendable over an accidental plaintext connection.
Setting only one of the two fails at startup with a message naming the
missing variable, so a half-configured deployment can never quietly serve
plaintext while you believe TLS is on. Confirm the scheme=https field on the
listening line the orchestrator logs at boot rather than assuming TLS came up.
There is no built-in certificate rotation, mutual TLS, or cipher-suite
configuration, terminate TLS at a reverse proxy in front of the orchestrator
if you need any of that.
Session cookies behind a TLS proxy
export ORCH_SECURE_COOKIES=true
When a reverse proxy terminates TLS (the topology recommended above), the
orchestrator itself speaks plain HTTP and cannot tell that the browser’s
connection was encrypted. Set ORCH_SECURE_COOKIES=true so the session cookie,
which carries the master token for seven days, still gets its Secure
attribute. Without it that cookie is sendable over any plaintext hop.
The orchestrator deliberately ignores X-Forwarded-Proto and friends: any
client can send those headers, so trusting them would let a caller decide
whether their own session cookie is protected.
ORCH_SECURE_COOKIES |
Result |
|---|---|
| unset (default) | Secure when the orchestrator terminates TLS itself, else off |
true |
always Secure, for TLS-terminating proxy deployments |
false |
never Secure, for plaintext local development only |
Any other value fails at startup. Running plaintext without an explicit setting logs a warning at boot naming this variable.
CIDR allowlisting
export ORCH_ALLOWED_CIDRS=10.0.0.0/8,192.168.1.0/24
Comma-separated CIDR blocks. Requests from outside every listed range are
rejected with 403, before bearer auth even runs. Empty (the default) means
no IP restriction. Invalid CIDR syntax fails the orchestrator at startup
rather than silently ignoring the bad entry, so a typo here is caught
immediately, not discovered later as an unexpected open door.
/metrics, /health, /ready, and /v1/version are mounted outside the
allowlist (on the outer mux, alongside but not behind the router that installs
the CIDR middleware), so they stay reachable from any source address. /metrics
in particular exposes fleet size and route names, restrict these four at the
reverse proxy or the host firewall if that matters to you.
Fail-closed production mode
export ORCH_REQUIRE_AUTH=true
Without this, the orchestrator boots happily with no auth configured at all,
convenient for local dev, dangerous to leave on by accident in production.
Setting ORCH_REQUIRE_AUTH=true makes the orchestrator refuse to start unless
ORCH_AUTH_TOKEN, TOKEN_ENCRYPTION_KEY, and DATABASE_URL are all
set, a missing one fails fast at boot with the specific missing variable
named, rather than silently running in a weaker mode than intended. Set this
in every production deployment.
Secrets encryption at rest
export TOKEN_ENCRYPTION_KEY=$(openssl rand -hex 32)
Must be exactly 64 hex characters (32 bytes); anything else fails at boot with
a specific error rather than silently truncating or padding. Used to encrypt
three things at rest with AES-256-GCM before they’re written to the state store:
per-VM guest-agent tokens, per-host agent bearer tokens, and per-VM secrets.
Losing this key doesn’t lock you out of the orchestrator itself, but nothing it
encrypted can be decrypted afterward. If you ever have to regenerate it, rotate
every environment’s token via
fuse environment rotate-token,
re-register every host (its stored agent token is unreadable, which surfaces
later as a firecracker create vm: http 401 at environment-create time rather
than as an error at boot), and re-supply any per-VM secrets.
Master token vs. API keys
Prefer revocable API keys over the master token for anything automated (CI runners, other services). The master token can mint and revoke keys; a compromised API key can only do what its holder was scoped to do at the API level (everything an authenticated caller can do, except manage other keys), and it can be individually revoked without rotating the master secret and re-distributing it everywhere.
Same-origin deployment only
The session-cookie flow (SameSite=Lax, no CORS middleware) only supports a
browser SPA served same-origin through a reverse proxy in front of both the
static assets and the API. A genuinely cross-origin deployment (the SPA on a
different domain than the API) isn’t supported today, the browser blocks the
credentialed /login request at the CORS preflight before auth even runs.
Front any browser-based dashboard through a same-origin reverse proxy rather
than attempting a cross-origin setup. See
Authentication for the full
cookie model.
Request size limits
Every JSON endpoint decodes through a bounded reader, so no single request can
make the orchestrator allocate without limit. Exceeding a limit returns 413
with the error code payload_too_large. The limits count bytes actually read,
so they apply to chunked requests as well as ones that declare a
Content-Length.
| Endpoint | Limit |
|---|---|
POST /login |
4 KiB |
POST /v1/environments, exec |
1 MiB |
| all other JSON endpoints | 64 KiB |
An inline manifest plus secrets plus a startup script must fit inside the 1 MiB ceiling. Reference a manifest instead of inlining it if you are near that.
The host agents apply their own ceilings, configurable per host: 1 MiB for
control-plane calls (FC_AGENT_MAX_BODY_BYTES / QEMU_AGENT_MAX_BODY_BYTES)
and 64 MiB for guest file uploads (FC_AGENT_MAX_UPLOAD_BYTES /
QEMU_AGENT_MAX_UPLOAD_BYTES). They reject chunked request bodies outright,
which nothing in Fuse sends. FC_AGENT_REQUEST_TIMEOUT /
QEMU_AGENT_REQUEST_TIMEOUT (60s) bound a client that opens a connection and
then stalls; interactive attach sessions are exempt, since idling is what they
are for.
Host agent tokens are a separate concern
FIRECRACKER_TOKEN (and QEMU_AGENT_TOKEN for GPU hosts) authenticate the
orchestrator to each host agent, entirely independent of the operator-facing
API token above. Rotating one doesn’t affect the other. See
Firecracker host setup and
GPU host setup.
What this doesn’t cover
Broader infrastructure hardening, network segmentation between hosts, host OS patching, Firecracker’s own jailer configuration, isn’t implemented by Fuse’s Go code and is genuinely general infrastructure-security practice rather than something specific to this project. Apply your organization’s standard hardening baseline to the machines running the orchestrator and host agents, the controls on this page are what Fuse itself provides on top of that.