Stellar Control protects its data in transit with TLS on every hop, at rest with JetStream encryption and encrypted disks, and keeps its secrets out of any repository. Between tenants of a shared platform, NATS accounts do the isolation (see Cells); encryption protects disks and backups.
Layers#
| Layer | Protection |
|---|---|
| Clients ↔ NATS | TLS required (tls://), CA given by the deployment; client certificate optional (mTLS) |
| Leaf nodes, cluster routes | TLS; leaf nodes also over WebSocket on 443 |
| API, web console, editor | HTTPS and WSS, terminated by a reverse proxy (one subdomain per cell, ACME certificates); the CLI speaks HTTPS and WSS |
| Data at rest | JetStream encryption (streams, KV buckets, object stores), one key per server; encrypted disks for the rest |
| Secrets | Operator key offline; per cell, the account signing key, OIDC secrets and the JetStream key in encrypted Nomad variables or Vault; never in a repository |
TLS to NATS#
Services of the core#
nats:
url: tls://nats.mcs.example.org:4222
tls:
ca: /etc/stellar/tls/ca.pem # TLS required, server checked against this CA
cert: /etc/stellar/tls/client.pem # with key: mTLS
key: /etc/stellar/tls/client-key.pem- With
tls.ca, the connection requires TLS and checks the server against that CA. - With
tls.certandtls.key(always together), the component presents a client certificate. - A
tls://URL withouttls.cachecks the server against the system roots. - A
wss://URL goes through the NATS WebSocket port, for networks that only let HTTPS out.
A certificate or credentials the server refuses are logged at the first connection attempt, with the reason, then retried in the background.
Components of the SDKs and the CLI#
Drivers, transports, gateways and connectors (Rust and Python SDKs) read the same settings from
their environment; the CLI takes them as options, for its commands that talk to NATS
(stellar compile --publish, stellar sim):
| Environment | CLI option | Meaning |
|---|---|---|
STELLAR_NATS_CA | --nats-ca | CA certificates (PEM): TLS only |
STELLAR_NATS_CERT | --nats-cert | Client certificate (PEM), for mTLS; goes with the key |
STELLAR_NATS_KEY | --nats-key | Its private key (PEM) |
STELLAR_NATS_CREDENTIALS | --nats-credentials | Credentials file (JWT and seed) |
Server side#
The development server requires TLS with NATS_CONF=nats-server-tls.conf docker compose up -d nats, with a CA and certificates generated by dev/nats/setup.sh in dev/nats/secrets/tls/:
tls {
cert_file: /etc/nats/secrets/tls/server.pem
key_file: /etc/nats/secrets/tls/server-key.pem
ca_file: /etc/nats/secrets/tls/ca.pem
verify: false # true: client certificates required (mTLS)
}The Nomad deployment requires TLS on every port with
NATS_TLS=1 (or mtls), and its nats job takes tls_dir, tls_verify, leafnode_port and
websocket_port for a real CA.
Leaf nodes of the benches#
A bench joins the cell of its customer through a NATS leaf node bound to the account of the cell. Leaf node connections use TLS, and can also run over the NATS WebSocket on 443 when the bench network only lets HTTPS out. Cluster routes between NATS servers use TLS too.
HTTPS for the API#
The API itself serves plain HTTP on api.listen: HTTPS and WSS are terminated by a reverse proxy
in front of it. The Nomad deployment ships Traefik (jobs/proxy.nomad.hcl), with ACME
certificates in production and one subdomain per cell.
The CLI speaks HTTPS and WSS to an https:// API, and checks its certificate against the system
roots, or against the CA given with --api-ca (STELLAR_API_CA):
stellar send sim-1 'tcu[TCU1].ping' --api https://demo.mcs.example.org
STELLAR_API_CA=/etc/stellar/tls/ca.pem stellar alarms --api https://localhost:8443Encryption at rest#
JetStream#
NATS encrypts its streams, KV buckets and object stores with a key per server
(ChaCha20-Poly1305 in the Nomad deployment, encrypt=true and NATS_ENCRYPT=1). The key comes
from a secret store, never from a file of the repository.
Rotating the key loses nothing: give the new key together with the old one as prev_key.
The server restarts, reads the existing data with the old key and writes with the new one. Once
everything is rewritten, drop prev_key. In the Nomad deployment:
- set
jetstream_prev_keyto the current key andjetstream_keyto the new one in the Nomad variablenomad/jobs/nats; - let the
natsjob restart; - remove
jetstream_prev_keylater.
Everything else#
What JetStream does not hold belongs on encrypted disks (LUKS, or the encrypted volumes of the provider): the configuration repositories and the working copies of the editor, the database of an output connector (TimescaleDB), the data directory of Nomad.
Secrets#
| Secret | Where | Who reads it |
|---|---|---|
| Operator key of NATS | Offline, in the directory of stellar-admin between provisionings | stellar-admin |
| Account signing key of a cell | Nomad variable nomad/jobs/cell-<name>, readable by the job of the cell only, or Vault | The reconciler (reconciler.signing_key_file) and the API |
| Credentials of the services and bootstrap credentials | Same variable | The services; the benches get the bootstrap credentials from stellar-admin cell credentials |
| OIDC key set, issuer, audience | Deployment configuration | API, executor, editor |
| Forge token of the editor | forge_token of the cell variable, in the variable named by editor.forge.token_env | The editor |
| JetStream key | Nomad variable nomad/jobs/nats | NATS |
| TLS keys | Files on the hosts, or ACME | NATS, the reverse proxy |
The space link#
Encryption and authentication of the space link itself (SDLS, CCSDS 355.0) are not a NATS matter: they belong to a transport of the link, which frames the units of the driver for the medium.