Skip to content
Fuse
Esc
navigateopen⌘Jpreview
On this page

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.

Was this page helpful?