Whoobe Docs

Secrets and key management#

Inventory of every secret a Whoobe installation holds, what each one protects, what happens if it is lost and if it is leaked, and how to rotate it. Background on encryption levels and backups: encryption; variable details: configuration reference. There is no built-in key-management service, key versioning or automated rotation: everything below is a manual procedure.

1. Where secrets live#

  • Environment file .env.<slug> (mode 600, created by provision-workspace.sh, never in git, excluded from the images): all keys of sections 2 and 3. Anyone who can read it, or run a command inside a container, has them. For stricter setups, inject the variables into the containers from a secret manager and keep the file empty of them.
  • Database (users, websites.settings, gateway_endpoints, mail_settings ...): hashes and ciphertext - and, until F-055 is fixed, AI/Figma/push keys in clear text.
  • Operator's machine: the backup private key (age identity or GPG secret key), TLS account data, SSH keys.
  • Let's Encrypt certificates: on the host (/etc/letsencrypt), renewed by scripts/renew-certs.sh.

2. Infrastructure secrets#

Secret Generated Used by If lost If leaked Rotate
POSTGRES_PASSWORD / DB_PASSWORD provision-workspace.sh (random) postgres, core-api, asset-processor, gateway restore from the env backup; or ALTER USER as the container's superuser full database access (needs network reach: loopback only by default) ALTER USER … PASSWORD, update both variables, recreate the three backends
S3_ACCESS_KEY_ID, S3_SECRET_ACCESS_KEY random (GK+24 hex / 64 hex for Garage) garage, asset-processor, core-api create a new key in the store read/write/delete of every asset and log create a second key, switch the variables, recreate asset-processor and core-api, delete the old key; Garage: garage key commands inside the container
DAM_SERVICE_TOKEN random core-api, asset-processor (and the logger of every backend) none: set a new one read of private files and logs through asset-processor, and the attach endpoint of form uploads set a new value in the env file, recreate all backends at once; no data impact

3. Application keys#

Secret Protects If lost If leaked Rotate
GATEWAY_AUTH_ENCRYPTION_KEY upstream credentials of gateway endpoints the credentials are unreadable: the endpoints stop authenticating upstream; re-enter them on each endpoint with a database copy, all stored upstream credentials no re-key tool: change the key, then re-enter the credential of every endpoint
MIGRATION_SOURCE_ENCRYPTION_KEY credentials of saved Pimcore sources re-enter the sources with a database copy, those credentials same as above
MAIL_SETTINGS_ENCRYPTION_KEY SMTP password, mail-provider API keys re-enter them in Settings > Mail with a database copy, the mail credentials (sending as your domain) change the key, re-enter the secrets
TOTP_ENCRYPTION_KEY users' TOTP secrets every user with MFA must enrol again (an admin can clear MFA with the reset script) with a database copy, the ability to generate valid codes for every user change the key, users re-enrol
SITE_API_KEY_PEPPER hashes of per-website site keys all site keys stop working with a database copy, offline guessing of site keys change the value, regenerate the key of each website (dashboard) and redeploy the sites that use it
INTEGRATION_SECRETS_ENCRYPTION_KEY AI provider keys (text, image, translation), the Figma token, the push provider (OneSignal) key the credentials are unreadable: enter them again (AI keys per user, Figma, push per website) with a database copy, those third-party credentials change the key, enter the credentials again; until the key is set they are stored in clear text
UPLOAD_TOKEN_SECRET the one-file upload tickets none: set a new one forging upload tickets within the website's own limits change at any time; tickets in flight (10 minutes) fail; no stored data depends on it
SEED_ADMIN_PASSWORD the first administrator reset with the reset script until changed change it at first login; the variable is only read when no user exists

Same rule for all of them: one key per area, never shared between areas or workspaces, 32 random bytes (openssl rand -hex 32). provision-workspace.sh does that. The change_me… values in .env.example are placeholders, not secrets: Compose refuses to start when the database and S3 credentials are unset, but does not recognise a placeholder, so replace them (only UPLOAD_TOKEN_SECRET treats its placeholder as "not set").

4. Credentials stored through the dashboard#

Secret Stored Note
Gateway upstream credentials encrypted (GATEWAY_AUTH_ENCRYPTION_KEY) never returned by the API
SMTP password, mail-provider keys encrypted (MAIL_SETTINGS_ENCRYPTION_KEY) write-only
AI provider keys, Figma token, push-provider key encrypted (INTEGRATION_SECRETS_ENCRYPTION_KEY, F-055); in clear text on an installation that has not set the key never returned by the API (the push key used to be, F-059)
Per-website site keys hash with pepper; the clear key is shown once at creation
User passwords argon2id hash
Preview-link tokens stored as issued in data_object_preview_tokens, with an expiry

5. Other secrets of the operator#

Secret Handling
Backup decryption key (age identity / GPG secret key) not on the server; password manager or KMS plus a second custodian. Without it the backups are unreadable by design
TLS private keys certbot on the host; renew-certs.sh (cron)
GitHub token / SSH keys used to deploy never in git remotes (F-002); use SSH keys or a credential helper
CMS_SITE_API_KEY of a published site on the site's server side only, never in the browser bundle

6. Procedures#

Rotate a secret that was not leaked#

Pick the row above; change the value in .env.<slug>; recreate the services that use it (docker compose -p ws-<slug> --env-file .env.<slug> up -d --force-recreate <services>); do the data step in "Rotate" if there is one; keep the old env file until the data step is done and verified. Record the date.

Suspected leak of the env file or a backup#

  1. Treat every key in it as compromised. Generate new values for all of section 2 and 3.
  2. Revoke all sessions: UPDATE sessions SET revoked_at = now() WHERE revoked_at IS NULL; (or change the password of the affected users, which revokes theirs).
  3. Re-enter gateway, mail and migration credentials; ask users to re-enrol MFA; regenerate site keys.
  4. Rotate the upstream credentials themselves (AI, Figma, push, SMTP, ERP/CRM keys): an installation without INTEGRATION_SECRETS_ENCRYPTION_KEY stored the AI, Figma and push keys in clear text.
  5. Rotate the S3 key and the database password; check the S3 and database access logs of the provider for the period.
  6. Re-encrypt backups made with a leaked backup key (generate a new recipient) and destroy the old ones.

Lost the env file with no backup#

The database can be restored but encrypted values cannot be read. Generate a new env file, restore the database, then re-enter all credentials (gateway, mail, migration sources), have users re-enrol MFA, regenerate site keys. This is why the backup script stores .env.<slug> (encrypted) next to the database dump.

Before a new customer or environment#

Never copy an env file from another workspace. Findings F-001 and F-009 are the record of what happens when one is committed or left on a host.

7. Gaps (for the security office)#

No automated rotation, no key versioning (enc:v1: values carry a format version but one key), no KMS/Vault integration, no secret scanning of the database content, AI/Figma/push keys unencrypted on installations without INTEGRATION_SECRETS_ENCRYPTION_KEY, sessions unhashed (F-056).

Last updated