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.
| Secret | File | What it protects | Rotation |
|---|---|---|---|
| Cluster secret | cluster-secret | The IPFS Cluster private network key and mesh bearer; the root from which MAC keys are derived (below). Also the initial encryption root | Needs a maintenance window. No tooling yet |
| RQLite credentials | rqlite-password, rqlite-auth.json | HTTP and Raft access to every RQLite. rqlited is always started with -auth; a missing file refuses to start | Manual |
| API key HMAC secret | api-key-hmac-secret | The HMAC under which API keys are stored. Namespace and index gateways refuse to start without it | Manual |
| Olric key | olric-encryption-key | Reserved for memberlist encryption. The Olric version in use has no encryptionKey field; WireGuard is the control | n/a |
| Encryption root seed | encryption-root (written at join) | Seeds a gateway's empty encryption-root cache; the registry's encryption_roots table is the source of truth | orama maint operator rotate-secrets --rotate |
| Swarm key | IPFS swarm key | Admission to the private IPFS swarm | Manual |
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.
| Purpose | Used for |
|---|---|
internal-auth-hop | The MAC on the headers an index gateway sends a namespace gateway |
internal-coordination | The 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-challenge | The 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-v1 | Sealing and MACing the shared certificate store. Installed at /etc/caddy/orama-tls-store.key (root:orama 0640) |
ipfs-cluster-rest-api, ipfs-kubo-api | The 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-v1 | Encrypting 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 keys | WebSocket 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, root0400) 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 withorama 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 removerevokes (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_usermust 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
| Item | Location |
|---|---|
| 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 cache | data/namespaces/<ns>/gateway/ (0700) |
| Caddy keys | /etc/caddy/orama-acme.key, /etc/caddy/orama-tls-store.key, /etc/caddy/orama-caddy-proof.key |
| Tenant SQLite | data/sqlite/<ns>/ (0700; files 0600) |
| Deployment environment | A 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 keys | RootWallet, 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
curlon 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.