Some of what the platform stores must be readable back — a mailbox password it uses to fetch mail, the shared secret behind an authenticator code, a softphone's SIP credential. These are sealed at rest under a single master key, and everything below follows from that being one key.
Six kinds of sealed value#
A module registers a kind of sealed value; the platform seals and opens them all the same way. Measured, there are six:
| kind | what it protects |
|---|---|
secrets file |
the application's own secrets file |
email bodies |
stored .eml message bodies in the VFS |
mailbox password |
a mailbox's IMAP/SMTP password |
mailbox oauth |
a mailbox's OAuth grant |
user factor |
the second factor's shared secret (Elevation) |
sip credential |
a per-user WebRTC-SIP softphone password |
Each value is sealed with libsodium, a fresh nonce per value, and carries an
enc:v1: marker so the platform can tell a sealed value from a legacy plaintext
one and knows how to open it. A module adds a kind by implementing one interface
and tagging the service — nothing central changes — which is also what lets
rotation walk every kind without knowing what any
of them is.
One key, and what that means#
The key lives in the environment, COOLMS_SECRET_MASTER_KEY, a base64-encoded
32-byte value. One key seals all six kinds, and that has two consequences an
operator has to take on board:
- Back it up, separately from the database. Everything sealed above is
undecryptable without it. A database restored without the key is a database of
values nobody can open — the mail won't fetch, the second factors won't
verify, the softphones won't register. The key is provisioned into your
environment file by
coolms:installon a fresh install (see Install); that file is the thing to back up and to keep out of the database's own backup. - An unreadable value fails loudly, by name, not silently. If the key is missing, malformed, or does not open a value, the platform answers 409 and says which of four things is wrong: no key set, a key that is not a valid base64 32-byte value, a value that does not open under the current key, or a value stored unsealed from before encryption at rest. A secret that cannot be read is a stated conflict — never a quiet fall-through to "no second factor" or "no password".
What the operator sets, and what a shell generates#
Setting the key is an environment act — the operator puts
COOLMS_SECRET_MASTER_KEY in the deployment's environment and backs it up.
There is no screen for it, deliberately: a master key an admin session could
read or change from a browser would defeat the point of sealing anything.
Generating a well-formed key is the one part that needs a shell:
docker exec coolms_app php bin/console coolms:secret:generate-key
It prints a base64 32-byte key for the operator to place in the environment. On
a fresh install coolms:install provisions one so the system can seal from the
first write; generate-key is for the operator who is setting up a real
deployment and wants to choose and store the key themselves.
Replacing the key once values are sealed under it is not a matter of editing the variable — that would strand everything already sealed. It is a rotation, which is its own mechanism and its own page.