Secrets and Key Ceremony
Self-hosted secret provisioning for OGuardAI, the full Tier 1 and Tier 2 secret set, generation, validation, and rotation
OGuardAI is self-hosted. You run the entire runtime in your own infrastructure. There is no hosted or managed OGuardAI service, and no on-premises-as-a-service offering, so no third party (including Oronts) ever holds your secrets. You generate the secrets below in your own environment, store them in your own secrets backend, and inject them into the runtime at start. Key custody is entirely the operator's responsibility.
This page is the site mirror of the canonical operator runbook in the repository at
docs/key-ceremony.md. When the two differ, docs/key-ceremony.md wins.
Never commit real secret values. Never paste them into chat, a ticket, or a log. The runtime never logs secret material, only key ids and fingerprints.
The secret set
Tier 1: cryptographic root secrets
A leak of any Tier 1 secret breaks the trust boundary (sealed sessions, the audit chain, or corpus
token identity). Generate each as 32 random bytes, base64-encoded (openssl rand -base64 32).
| Env var | Config key | Role | If unset |
|---|---|---|---|
GUARDAI_SESSION_SECRET | session.secret / session.keyring | Master session-sealing secret. The AES session key, and (unless set separately) the audit-HMAC, corpus-identity, revocation, and prompt-preamble-marker keys derive from it. | Required. An ephemeral random secret is used and sessions do not survive a restart. |
GUARDAI_AUDIT_SECRET | audit.secret | Dedicated audit-chain HMAC root, independent of the session secret so the tamper-evident audit log verifies across a session-key rotation. | Falls back to session-derived. |
GUARDAI_CORPUS_SECRET | rag.corpus_secret | RAG deterministic corpus-token identity. | Falls back to session-derived. |
GUARDAI_REVOCATION_SECRET | (revocation) | Revocation digest HMAC. | Falls back to session-derived. |
GUARDAI_POLICY_SIGNING_KEY | (policy) | Policy integrity signing. | Policy signing disabled. |
| per-tenant BYOK | session.tenants.<id>.session_keys | A tenant seals under its own key domain. Raw inline material is accepted only in dev; production references a keyring. | Tenant seals under the root-derived key. |
Tier 2: access credentials
| Env var | Role |
|---|---|
GUARDAI_API_KEY_<NAME> | Server client keys. Each carries per-key entitlement companions: _SCOPES, _ALLOWED_POLICIES, _ALLOWED_OUTPUT_CHANNELS, _ALLOWED_DESTINATIONS. |
GUARDAI_PROXY_API_KEY | Transparent proxy client auth. |
GUARDAI_DETECTOR_API_KEY | Auth to the Python NER detector sidecar (must match the sidecar's configured key). |
GUARDAI_BEARER_TOKEN / GUARDAI_BEARER_TOKEN_FILE | Bearer credential the MCP server presents to the OGuardAI API (alternative to GUARDAI_API_KEY; mutually exclusive). |
GUARDAI_TLS_KEY | TLS private key, if terminating TLS in-process rather than at a load balancer. |
session.redis_url | Redis connection string; may embed a password when the shared store backends are Redis. |
Ceremony
-
Generate the set in a controlled environment (an operator workstation or a CI secrets step), not on a shared host:
deploy/scripts/gen-secrets.sh --all--tier1prints only the root secrets;--api-key NAMEprints one extra client key line. The script writes nothing itself: it prints an.envtemplate with fresh high-entropy values to stdout for you to pipe into your secrets backend. -
Store the values in your secrets backend. Options: AWS Secrets Manager, HashiCorp Vault, a Kubernetes
Secret, or an encrypted.envyour orchestrator injects. Do not write them to a file the repo tracks. -
Inject them into the runtime as environment variables (Helm: a referenced
Secret; docker-compose: anenv_fileorsecrets; bare process: your process manager). The container image never bakes a secret in. -
Validate before serving traffic:
oguardai config validate --config /path/to/config.yamlA placeholder or example session secret, or a malformed value, aborts startup.
Keep every value above a placeholder in your notes and examples.
GENERATE_WITH_openssl_rand_base64_32is the intended shape, never a real-looking secret.
Rotation
Session-key rotation and audit-root rotation are separate ceremonies.
For session sealing, use session.keyring: add a new key id, deploy it everywhere while the old id
remains current, switch current_kid, wait one full session TTL, then retire the old id. This avoids
client-visible session expiry. If the audit root falls back to session.secret, changing that base
secret also rotates the audit root and requires the audit procedure below before the change.
For an audit-root change, stop every writer to the live file, move that file to a private archive,
and preserve its owner and mode 0400 or 0600. Put the previous root in
audit.retired_secrets, configure the new audit.secret, then restart the runtime so it creates a
new exact-0600 live file in the trusted parent. Verify the archive with the same configuration:
oguardai-server audit verify --config /etc/guardai/oguardai.yaml \
--path /var/lib/guardai/audit/audit-previous.jsonlThe verifier selects the archived file's writing key from its header key id, or verifies configured candidates for a legacy header, and uses one opened descriptor throughout. See Verifying audit-log integrity for source custody and export rules.
Keep GUARDAI_CORPUS_SECRET stable, or already-ingested RAG corpora lose token identity.
Tier 2 access credentials rotate on a fixed cadence (90 days is a reasonable default) and immediately on suspected exposure. Client keys can be rotated one at a time without downtime.
The step-by-step keyring procedure, with its ordering constraints, is in the Key Rotation Runbook. The Deployment guide covers the same rotation from the config angle.
Deployment model
Self-hosted only. The operator runs the runtime and holds all secrets. A managed or hosted SaaS is not offered today, and there is no on-premises-as-a-service offering. Until that changes, managed key custody is not part of the product, and this ceremony is the operator's responsibility end to end.