Skip to content
ABRQ DATADocs Abrq DIP · latest
Product page Request a trial
On this page

Secrets and Encryption at Rest

The master key#

ABRQ_MASTER_KEY is one of the two settings the backend refuses to start without. It is a Fernet key (AES-128-CBC with HMAC-SHA256 authentication; 32 url-safe base64 bytes) that encrypts every secret the platform stores. Generate one with:

python -c "from cryptography.fernet import Fernet; print(Fernet.generate_key().decode())"

Ciphertexts live in a dedicated secrets table; each row is stamped with a key_version so rotation progress is always observable.

What is encrypted#

Every stored credential goes through the same envelope — there is no plaintext fallback path:

  • Data connector credentials (database passwords, cloud storage keys, SASL credentials)
  • IMAP inbox passwords
  • SMTP password and the license-alert notification target
  • SSO (OIDC) provider configuration blobs, client secret included
  • LDAP provider bind configuration blobs
  • AI model provider API keys
  • Notification channel targets (webhook URLs, routing keys) and alert-rule channel targets
  • Stream outbound auth headers and the audit-forwarder target

Write-only API semantics#

Secrets flow one way through the API:

  • Set, never get. A secret value can be written (create or update) but no endpoint returns it. Update payloads use explicit set-flag semantics so "leave the stored secret unchanged" and "overwrite it" are unambiguous.
  • Masked previews. Where the UI needs to show that a target is configured, it renders a masked preview (enough to recognize, never enough to use).
  • Decryption happens only at the dispatch boundary — the moment a connection is opened or a notification is sent — never to serve an API read.

What is never logged or returned#

  • Plaintext secrets never appear in API responses, structured logs, or audit rows; log output passes through a redaction layer.
  • The license file's signature is never returned by the license endpoint.
  • The only file the application ever writes a credential to is the one-time initial admin password file (0600 permissions — see Authentication).

Key rotation — the four-step ritual#

ABRQ_MASTER_KEY accepts a comma-separated key list (new key first). With more than one key configured, the platform writes with the first key and reads with whichever key matches (MultiFernet), so rotation has no downtime and no window where rows are unreadable.

  1. Generate a new key (command above). Handle it like any production secret.

  2. Bring up NEW alongside OLD. Set the deployment environment to the comma list and bump the version stamp, then restart the API and workers:

    ABRQ_MASTER_KEY=<NEW_KEY>,<OLD_KEY>
    ABRQ_MASTER_KEY_VERSION=2
    

    Existing rows still decrypt under OLD; new writes are encrypted under NEW and stamped key_version=2.

  3. Run the rewrap script to re-encrypt every existing row under NEW:

    cd backend
    uv run python scripts/rotate_master_key.py
    

    The script decrypts each secrets row with whichever key works and re-encrypts under the active key. A mid-run abort is safe (both keys are still configured) — just re-run. Rows it cannot decrypt are logged and skipped, and the script exits non-zero so automation notices. Check progress at any time:

    SELECT key_version, count(*) FROM secrets GROUP BY 1;
    
  4. Drop OLD. Once every row is on the new version, restart with only the new key:

    ABRQ_MASTER_KEY=<NEW_KEY>
    ABRQ_MASTER_KEY_VERSION=2
    

Each step is independently reversible. Keep the old key retrievable (in your vault, not in the environment) for one backup-retention window — metadata backups taken before the rotation decrypt under the key that was active when they were made.

Tip. Take a metadata backup before rotating. See backup and restore.

The JWT secret is rotated independently#

ABRQ_JWT_SECRET signs session tokens and is not part of the master-key envelope. Rotating it is a one-step change with a different blast radius: every outstanding access and refresh token becomes invalid immediately and all users must sign in again. Use it as a session kill switch; it does not touch stored secrets. See Authentication.

Pluggable secret backends#

ABRQ_SECRET_BACKEND selects the encryption backend and defaults to fernet (the local master-key scheme described above). The seam is designed for managed KMS backends: the values vault and aws are reserved, and selecting either today fails fast at startup rather than silently falling back to local encryption. The key_version column carries across a future migration, so the audit trail of which generation encrypted each row survives a KMS move.

Note. For the full environment-variable inventory, including ABRQ_MASTER_KEY_VERSION semantics, see environment variables.