Agent index (llms.txt) — fetch this first, then the page it names.

Secrets and keys

Every secret and key in an Orama cluster, what it is for, where it lives, and who can read it. Use it to answer "if this leaks, what is exposed", and to plan rotation.

Cluster-wide secrets

Generated at cluster genesis in /opt/orama/.orama/secrets/ and distributed to joining nodes in the encrypted join response over HTTPS, pinned to the certificate fingerprint the invite carries.

SecretFileWhat it protectsRotation
Cluster secretcluster-secretThe IPFS Cluster private network key and mesh bearer; the root from which MAC keys are derived (below). Also the initial encryption rootNeeds a maintenance window. No tooling yet
RQLite credentialsrqlite-password, rqlite-auth.jsonHTTP and Raft access to every RQLite. rqlited is always started with -auth; a missing file refuses to startManual
API key HMAC secretapi-key-hmac-secretThe HMAC under which API keys are stored. Namespace and index gateways refuse to start without itManual
Olric keyolric-encryption-keyReserved for memberlist encryption. The Olric version in use has no encryptionKey field; WireGuard is the controln/a
Encryption root seedencryption-root (written at join)Seeds a gateway's empty encryption-root cache; the registry's encryption_roots table is the source of truthorama maint operator rotate-secrets --rotate
Swarm keyIPFS swarm keyAdmission to the private IPFS swarmManual

A node that joins gets all of these. A node that is compromised, or whose disk is imaged, exposes them all. This is the main reason Ubuntu node operation is by invitation.

Keys derived from the cluster secret

One secret, many independent purposes, each through HKDF with its own purpose string, so a key for one job cannot be used for another.

PurposeUsed for
internal-auth-hopThe MAC on the headers an index gateway sends a namespace gateway
internal-coordinationThe coordination MAC between nodes. v2 covers the method, the audience (the receiving node's peer id), path, query, body hash, a random nonce and the time. v3 adds the port of the process the request is for, read from the connection and never from the Host header, so a stamp for the index gateway is not good at a namespace gateway on the same node. Valid one minute either side; the receiver refuses a nonce seen in the last two minutes. A node accepts v2 alone, and v1 on four read-only routes, only while the registry shows a node that does not sign the nonced stamps (each node reports the stamp level it signs on registering and on every heartbeat, and a node rolled back to an older build counts as not signing them again from its next heartbeat); once every registered node signs the nonced stamps, a request with its newer stamp stripped is refused and the older stamps are no longer written, so a captured request cannot be replayed on the older form. The same holds for the unnonced ACME and node-registration stamps. A dead node that was never removed keeps the older forms accepted
acme-challengeThe MAC on /v1/internal/acme/*, so a local process cannot publish a TXT record. The stamp covers the body and carries a nonce the gateway remembers for two minutes, so a captured call cannot be replayed; the previous unnonced stamp is accepted for one release. Installed at /etc/caddy/orama-acme.key (root:orama 0640). Deliberately not the coordination key: Caddy faces the internet
caddy-tls-store, orama-tls-store-seal-v1, orama-tls-store-mac-v1Sealing and MACing the shared certificate store. Installed at /etc/caddy/orama-tls-store.key (root:orama 0640)
ipfs-cluster-rest-api, ipfs-kubo-apiThe IPFS Cluster REST password and the Kubo RPC bearer. Derived rather than generated, because every consumer already holds the cluster secret
turn-encryption, namespace-push-credentials, push-topic-tokens, push-topic-token-fp, orama-restore-v1:<namespace>Encryption of TURN secrets, push credentials and push topics; the namespace backup restore key
ipfs-wrap-v1Encrypting private blobs before they are added to IPFS
webrtc-sfu-control (from the namespace's TURN secret)The join ticket and control MAC between a namespace gateway and its SFUs
per-namespace capability keysWebSocket capabilities and fetch capabilities, under purposes of their own so one kind never verifies as the other

Every node holds the cluster secret, so any node can sign any of these MACs for any other node. A MAC proves cluster membership, not which node signed. That gap is closed in part by node identity, below.

The secret Caddy proves itself with

Every public request reaches a gateway from the node's own Caddy, so from 127.0.0.1, and a process on the node can write X-Forwarded-For as Caddy does. To tell the public callers apart without believing that process, Caddy sets X-Orama-Caddy-Proof on every request it forwards to the gateway, carrying 32 random bytes in hex from /etc/caddy/orama-caddy-proof.key (root:orama 0640, so a tenant's deployment cannot read it). It is generated per node, not derived from the cluster secret, and made again at every install and upgrade. The gateway compares the header in constant time, deletes it so no handler or proxied service sees it, and takes the client address from X-Forwarded-For only when it matched. Without it the caller is keyed by its connection address.

Encryption of stored secrets

TURN secrets, function secrets, push credentials, deployment environment variables and agent tokens are encrypted with AES-256-GCM. The key is HKDF(encryption root, purpose). The encryption root starts as a copy of the cluster secret; the registry's encryption_roots table is its source of truth, and each gateway caches it in its state directory. Ciphertext is versioned (enc:v1:<id>:). orama maint operator rotate-secrets rewrites everything onto the versioned envelope; --rotate generates a new root so a captured previous root cannot open new rows. A root carries a generation, and a gateway refuses a pushed root that is older than the one it holds, the same generation with a different key or a lower write level, or the same generation carrying a previous root other than the one it holds. A push with no previous root, which is how forgetting reaches a gateway, is accepted. So a captured re-encrypt call cannot roll the root back or restore a retired key, and a gateway that missed a rotation (a push more than one generation ahead) reads the root from the registry, the source of truth, and adopts it when it matches the push, without a restart.

A deployment's environment uses a second envelope, enc:v2:<id>:, sealed with the namespace and the deployment id as GCM additional data. A ciphertext copied onto another row fails to open there and the deployment's start is refused. Older forms (plaintext, enc:, enc:v1:) are told apart by prefix and still read. Writing enc:v2: is off until an operator runs rotate-secrets after every gateway is upgraded, because a gateway from before the change cannot open it; a cluster's first encryption root is created with it on (write_versioned = 2). It stops a ciphertext from moving between rows, not a writer who holds the root. The cluster secret itself (IPFS Cluster key, mesh bearer) is not touched by a rotation.

Namespace separation of secrets is by database, not by key: a namespace's secrets are in that namespace's own database, and the encryption key is the same across the cluster.

Signing keys

  • +Gateway token keys. Every gateway has its own Ed25519 key, generated at first boot. The index gateway's is a systemd credential (/var/lib/orama-gateway-keys/index, root 0400) loaded only into that unit. Public halves are published to the registry (GET /v1/auth/jwks). A key that every node could derive from the cluster secret used to sign tokens; it now only verifies, for one access-token lifetime after a gateway's first boot on the new release, and then is refused. Rotate with orama maint operator rotate-signing-key.
  • +Node keys. See below.
  • +Archive signer. A RootWallet EVM key. Its address list is /etc/orama/archive-signers.
  • +Release key. A dedicated ed25519 key at a SLIP-0010 path in RootWallet, for TUF release metadata. Never available to unattended signing.

Node identity

A node authenticates its own registration and heartbeats with an Ed25519 key it generated itself, kept at <orama>/secrets/node-key.pem (mode 0600) and never sent anywhere. The cluster records only the public half (node_credentials).

  • +A node with a live key on record is verified against that key; a node with no live key is verified against nothing. Nothing derived from the cluster secret is accepted on these calls, so a machine holding every shared secret still cannot speak as a node it is not.
  • +Enrolment is not trust on first use. The first call is stamped with the node's libp2p identity key, and for Ed25519 identities the public key is carried inside the peer id, so the cluster checks who is enrolling having been told nothing in advance. Only the machine holding node X's libp2p identity can enrol a key for X.
  • +Holding a key is not admission. Registration is refused unless the node is already registered and not retired, or has a WireGuard peer row written by a join under an operator-minted invite, or is an OramaOS node whose overlay address matches, or the registry is empty (genesis).
  • +A WireGuard peer is never registered by presenting the cluster secret. The old /v1/internal/wg/* routes that did so are deleted. A peer row comes only from a join under an operator-minted invite or an OramaOS enrolment.
  • +orama node remove revokes (revoked_at) rather than deleting, so a retired machine's disk stops working at once. Re-admitting it is a join with a fresh single-use invite.
  • +The claim is validated where it lands: addresses must be reachable by another node, the overlay address must match what was allocated, and ssh_user must be a POSIX login name.

Invites

An invite is single use and lives at most one hour. It is stored as sha256:<hex> of the token, never the token itself. It names one node, its public address, the domain to present and the SHA-256 fingerprint of the certificate it serves, which the joiner pins. A join validates every field and checks the invite is live before consuming it, then burns it and writes the peer row; a uniqueness conflict keeps the token spent, and only a cluster fault releases it, so nothing an attacker controls makes the token replayable. Invites travel on stdin (--secrets-stdin), never a command line.

Certificates

Certificates and private keys are in the shared store (tls_store), sealed with AES-256-GCM before they leave Caddy, with the storage key path as associated data. The registry's rows and snapshots never hold a private key in the clear. Locks are leases renewed every 20 seconds; clocks must agree within 40 seconds. Every node already held a key valid for *.<base>; sharing one key does not change that a compromised node can impersonate the cluster's hosts. See TLS and certificates.

What is where, at a glance

ItemLocation
Cluster secrets/opt/orama/.orama/secrets/
Archive trust anchor/etc/orama/archive-signers
Node key/opt/orama/.orama/secrets/node-key.pem
Index gateway signing key/var/lib/orama-gateway-keys/index (root 0400)
Tenant gateway signing keys, encryption-root cachedata/namespaces/<ns>/gateway/ (0700)
Caddy keys/etc/caddy/orama-acme.key, /etc/caddy/orama-tls-store.key, /etc/caddy/orama-caddy-proof.key
Tenant SQLitedata/sqlite/<ns>/ (0700; files 0600)
Deployment environmentA systemd-only file; encrypted in the registry
TURN tenant secrets/opt/orama/.orama/data/turn/turn.yaml (0600)
Validator key (global node)the chain home; leaves the host only sealed
Operator keysRootWallet, never on a node

Never in the clear

  • +Secrets never appear on a command line: invites and join secrets go on stdin; RQLite credentials go to curl on stdin (-K -); the Kubo bearer goes to the GC command (orama node ipfs-gc) in its environment file.
  • +Refresh tokens and invites are stored as hashes. API keys as HMACs.
  • +The audit trail records a fingerprint, never a credential.