mTLS

mTLS

Client certificate authentication for rstream agents and published Tunnel access.


mTLS gives rstream a certificate-backed authentication path for machines, services, devices, and published clients. The private key stays with the caller. rstream stores and authorizes the public certificate fingerprint through credentials, then applies the credential's permissions and Tunnel access rules at runtime.

mTLS is enforced by hosted rstream and private deployments that enable certificate-backed runtime authentication. Self-hosted CE uses token-based agent authentication and does not enforce published tunnel mTLS. Project plan gates are separate in hosted rstream: agent mTLS authentication is available on every project plan, while mTLS for public traffic entering published tunnel endpoints is available on Pro and Enterprise projects.

Surfaces

The Control plane API is the hosted API used to manage accounts, workspaces, projects, credentials, billing, and token minting. It is authenticated with tokens.

The engine control channel is the runtime connection used by agents and SDK runtimes to create tunnels, open private or direct connections, and attach streams. It can authenticate a client with either a token or an mTLS client certificate.

The Engine HTTP API exposes live resource listing, SSE, WebSocket watch streams, WebTTY session operations, and TURN credential issuance. It accepts tokens on the ordinary Engine host and client certificates on a separately discovered mTLS host.

Published Tunnel access is traffic entering a public tunnel URL or public socket. A tunnel can require mTLS for that public traffic when it enables mtls_auth.

Client certificate credentials

Create an mTLS Client Certificate (MTLS) credential from the Dashboard and paste the PEM-encoded client certificate. rstream stores the certificate SHA-256 fingerprint, not the certificate body or private key. The private key stays with the client that will authenticate.

A client certificate credential can carry Engine API permissions, TURN permissions, and resources.tunnels boundaries. In the Dashboard, select the Engine operations the device needs and grant turn.credentials.create for TURN issuance. Keep Control plane API permissions disabled: certificates do not authenticate the hosted Control plane.

Certificate matching is exact, and a certificate fingerprint can be registered only once. If another user tries to register the same certificate, creation is rejected. Use a separate certificate and private key for each user, device, service, or principal so one private key cannot map to multiple identities.

Rotating the client certificate means creating a new credential for the replacement fingerprint, then moving the agent or client to the replacement certificate. rstream checks the certificate validity window when the credential is created and when the certificate is presented at runtime, so expired certificates and certificates that are not valid yet are rejected.

Generate a client certificate

For a local test or a first production rollout, generate one client key pair per agent, service, user, or device:

openssl genpkey \
  -algorithm EC \
  -pkeyopt ec_paramgen_curve:P-384 \
  -out rstream-client.key
chmod 600 rstream-client.key
openssl req \
  -new \
  -x509 \
  -key rstream-client.key \
  -sha384 \
  -days 397 \
  -subj "/CN=rstream-agent-prod-01" \
  -addext "keyUsage=digitalSignature" \
  -addext "extendedKeyUsage=clientAuth" \
  -out rstream-client.crt

-days 397 is a conservative rotation-friendly example, not a protocol limit. Use the lifetime that matches your operational policy, and rotate before the certificate expires. Longer lifetimes reduce operational churn but increase the blast radius of a leaked private key.

Paste the full contents of rstream-client.crt into the Dashboard when creating the mTLS Client Certificate (MTLS) credential:

-----BEGIN CERTIFICATE-----
...
-----END CERTIFICATE-----

Do not paste the private key into the Dashboard. Store rstream-client.key on the agent or client host and protect it as authentication material. The certificate may be self-signed because rstream authorizes the exact certificate fingerprint registered on the credential.

Agent authentication

Agents and SDK runtimes connecting to the engine control channel can authenticate with either a token or an mTLS client certificate. Supplying both on the same engine connection is rejected as ambiguous.

Use token authentication for the default CLI login flow, CI tokens, and application credentials. Use mTLS authentication when the agent identity should be bound to a private key managed outside the token store.

CLI contexts configure this under auth.mtls with either certificateFile / keyFile or inline certificate / key values. Stateless agents can set RSTREAM_MTLS_CERT_FILE and RSTREAM_MTLS_KEY_FILE. See Configuration File and Environment Variables for the exact shapes and precedence rules.

Advanced agents can keep the private key outside the YAML file by using macOS Keychain, a PKCS#11 module, or a local external signer. See Credential Storage for the supported SDKs, config fields, and setup requirements.

Direct and private tunnel dialing uses the authenticated engine session. It does not use published Tunnel access authentication because no public tunnel URL is involved.

HSMs with a vendor API

The standard Go SDK and CLI support auth.mtls.storage.kind: exec. Use this backend when a device's HSM exposes a vendor SDK or OpenSSL provider without a PKCS#11 interface. A small local executable implements rstream's versioned identity/signature protocol. It reads the public certificate and signs through the HSM; rstream keeps control of TLS and tunnel traffic.

Provision and enroll the public certificate first, then pin its SHA-256 fingerprint in the local configuration. The private key stays behind the helper. A different certificate or an invalid signature fails the connection; there is no automatic fallback to a software key.

contexts:
  - name: device
    engine: "<project-engine-host:port>"
    auth:
      mtls:
        storage:
          kind: exec
          certificateSHA256: "<enrolled-leaf-certificate-SHA256>"
          exec:
            command: /usr/libexec/device-mtls-helper
            args: ["--identity", "device"]
            timeout: 5s

Use that context with the existing commands:

rstream --context device doctor
rstream --context device webtty server --rstream --name maintenance --no-publish
rstream --context device run --apply tunnels.yaml

doctor explicitly checks the identity, signs a local challenge, and tests engine admission. Reading or selecting a context does not run the executable. A private WebTTY deployment still needs its own terminal admission and optional E2E policy; this setting only authenticates the agent's engine connection. For token-free private attachment, use an explicit engine context and --no-discovery as described in WebTTY.

See external signer configuration for all settings and the helper protocol and example for implementation details, including the distinction between message signing and digest signing. The standard Go CLI and SDK implement this backend; the other SDKs reject it explicitly. The restricted FIPS profile does not support it.

Published Tunnel access

mtls_auth protects traffic that enters a published tunnel through its public endpoint. If it is enabled, the client reaching the published endpoint must present an allowed client certificate during the TLS handshake.

Published tunnel authentication methods are tunnel policy choices. A tunnel may enable more than one method, for example token authentication, rstream Auth, challenge mode, and mTLS, so different clients can use different authentication mechanisms.

The strict conflict rule applies to a single request or connection: mTLS must not be combined with HTTP-layer authentication proofs. A request with both a valid client certificate and a bearer token is rejected as ambiguous. A request with both a valid client certificate and an rstream Auth session cookie is rejected for the same reason. Token authentication and rstream Auth can still be enabled together on the tunnel; bearer token requests use the token path, and browser requests can use the rstream Auth path.

For HTTP tunnels, mTLS is checked before the request is forwarded upstream. For non-HTTP published tunnels where the edge terminates TLS, the certificate is checked during the client handshake. TLS passthrough is different: the edge does not terminate TLS, so client certificate validation belongs to the upstream service.

Engine HTTP API

The SDK discovers the dedicated mTLS authority with an anonymous HTTPS GET /.well-known/rstream/engine on the ordinary project Engine host. Discovery returns the project endpoint, mtlsApiUrl, and capabilities. Go, Node.js, and Python cache discovery in memory per client, normally for one hour. Reuse the client for repeated operations and TURN renewal; discovery is not fetched for every request. No new YAML field, login step, or DNS TXT lookup is required.

The ordinary host never requests a client certificate, preserving browser behavior. Only the dedicated host requests one. On that host, use either a certificate or a token; combining proofs is rejected. Server TLS verification and project binding apply to discovery, and redirects are rejected.

The Engine API supports HTTP/1.1 and HTTP/2 over TLS/TCP, and HTTP/3 over QUIC. Go supports HTTP/3 API requests and SSE; its WebSocket client uses TCP. Node.js and Python Engine API clients use HTTPS/TCP. Node.js certificate watches use SSE; Python supports SSE and WebSocket certificate watches. Browser clients keep the ordinary token-authenticated endpoint.

Certificate-only Go CLI contexts can use client list, tunnel list, events, ui, Engine-backed WebTTY session operations, and doctor when their permissions allow them. Workspace administration, enrollment, and managed trust provisioning still need the Control plane. For a fixed regional device, configure its explicit regional Engine address without a region selector; resolving a selector still requires Control plane metadata.

Doctor reports a permissions warning when a restricted certificate cannot read Engine health or inventory. If health access is denied, readiness remains unverified; this does not mean the Engine is down. Authentication and connectivity failures remain errors.

TURN issuance uses POST /api/turn-server/credentials on the discovered API. It requires the same turn.credentials.create permission and project resource boundaries as the Control plane equivalent. See STUN and TURN.

Published Tunnel access plan limits do not apply to agent authentication. They apply only to public traffic entering published tunnel URLs or public sockets.