The credential vault
What the vault stores, the four credential types, how each secret is encrypted, and why losing the encryption key means the vault is gone for good.
The vault is where privileged secrets live instead of in a spreadsheet. This article covers what a vault entry holds, the four kinds of secret it accepts, how each one is encrypted in terms you can act on, what the platform will never hand back to a caller, and the one operational fact about this module that has no workaround: if you lose the encryption key, the vault is gone.
It is written for whoever runs IT. There is a small amount of cryptography in it, described in terms of what you have to do rather than in terms of the mathematics. If you take one thing away, take the section on the encryption key.
What a vault entry holds
A credential is a name, a type, an encrypted secret, and a set of links that say what the secret is for.
- `name` and `description`
- For humans. Write the description as if for the colleague who inherits this after you leave — 'the account the nightly backup job runs as' is worth ten minutes of somebody's confusion later.
- `credential_type`
- One of exactly four values, listed in the next section. It constrains which platforms the credential can be attached to.
- `target_id` and `account`
- The machine the secret opens, and the account name on it. Both are needed for rotation: without a target Qorionix has no address to connect to, and without an account it does not know whose password to change. A credential missing either is still stored, and reports the gap in plain words rather than pretending to be scheduled.
- `rotation_interval_days` and `last_rotated_at`
- How often the secret should change, and when it last did. Covered in Credential rotation overview.
- `envelope`
- The encryption metadata for this specific secret — the cipher version, the wrapped data key, the wrapping key id and region, and when it was last rewrapped. This is returned by the API, and the section on encryption explains why that is safe.
The four credential types
There are four, they are validated on every write, and anything else is refused. The verbatim values are:
password
ssh_key
api_token
certificateThe console shows them as Password, SSH key, API token and Certificate. A value outside the four returns HTTP 400 with the reason spelled out rather than a schema complaint:
invalid credential type: must be password, ssh_key, api_token, or certificate
The type is not decoration. It is half of how the rotation engine is chosen — the other half being the target's platform. A linux target can hold both a password and an ssh_key credential, but only the password one can be rotated, because rotation on Linux works by changing a password over SSH. Storing an SSH key against a Linux target is entirely correct; expecting it to rotate is not, and the credential will tell you so.
What is returned to a caller, and under what controls
Outside of that one controlled path, the encrypted bytes are still excluded from serialisation at the type level, so ordinary reads never carry the ciphertext, let alone the plaintext. What an ordinary read returns is the metadata: id, name, description, type, envelope metadata, rotation state, target, account, and the last test result.
So the honest framing now is narrower than it used to be, not absent: Qorionix is still not a place you browse passwords casually. A reveal is an audited, justified, usually-approved event — not a list you scroll. If your intention was to store the domain administrator password here and read it back on your phone at will with no friction, that is still not what this is. Three of the other places plaintext is produced at all remain entirely internal to the platform and hand it to nobody:
- Opening a brokered session. The secret is decrypted immediately before the connection is made, handed to the session gateway, injected into the connection to the target, and never persisted. The person doing the work receives a listener endpoint, not a password. See Proxied SSH sessions.
- Running a connection test. The stored secret is opened to attempt a non-destructive sign-in, then discarded. It is not logged, not written to the audit row, and not in the response.
- Performing a rotation. The engine needs the current password to authenticate before it can set a new one.
The fourth is new, and it is different in kind because it does hand a secret out — to a machine, never to a person. The application-to-application fetch — the route that lets a script or an automated workload collect a secret at run time instead of having it hard-coded — used to return the literal placeholder redacted-see-docs instead of a secret, and we said here that it was not usable and that you should leave your scripts alone. That is fixed: it now returns the real secret. The advice is therefore reversed, and the next section is what you need before you act on it.
Letting a machine fetch a secret
This is the answer to the hard-coded-password problem in your own automation: a nightly job, a deployment script or a service that needs a database password does not need it written into a file. It can ask the vault for it at the moment it runs, and prove who it is with its own machine identity — a certificate or a signed token belonging to the workload — rather than with a human being's sign-in.
It is governed by the same policies as human access, not by a separate and weaker set of rules. What that means in practice:
- No policy, no fetch. A credential with no active access policy at all cannot be fetched this way. The absence of a rule is treated as a refusal rather than as permission.
- A policy a machine cannot satisfy is a policy that refuses it. If the covering policy requires human approval or MFA, the fetch is refused — a workload cannot click Approve and cannot type a code from an authenticator. This is not a gap to work around; it is the policy doing its job. If a credential genuinely should be available to automation, give it a policy that says so.
- Time windows apply. Access is granted only during whatever window the policy allows. A job that runs outside it is refused, which is worth knowing before you schedule one at 03:00 against a business-hours policy.
- The secret is short-lived. What a machine receives is time-limited to a few minutes, shortened further if any covering policy asks for less. It is meant to be used immediately and discarded, not cached.
- There is a rate limit per machine identity. This is the one that exists specifically for the bad day: without it, a compromised workload credential would turn the vault into an unlimited secret-dispensing oracle, and an attacker would simply ask it for everything.
Every fetch is written to the tamper-evident audit chain, naming the workload as the actor — including the refusals. A machine identity that starts being told no is exactly the signal you want to see, and a trail that recorded only successes would discard it.
So the previous advice here is reversed. If you have hard-coded credentials in your own scripts, this is now the mechanism for removing them, once you have API access set up for it. The remaining honest caveat is the one above: there is no interface, so treat this as an integration project rather than a setting you switch on.
How a secret is encrypted
Each secret is encrypted twice over, in a pattern called envelope encryption. Described without jargon:
A fresh key is generated for this one secret
A 32-byte random data key, produced per credential. No two credentials in your vault share one. That means a compromise of one credential's key is a compromise of one credential.
The secret is sealed with that key
AES-256-GCM, with a random 12-byte nonce stored in front of the ciphertext. GCM is authenticated encryption: tampering with the stored bytes makes decryption fail rather than produce a different plaintext.
The per-secret key is then itself encrypted
Under a wrapping key derived from your deployment's master key. The wrapped key is what gets stored next to the ciphertext.
The result is labelled with a cipher version
v2-envelope. The label exists so that a future change of scheme can be rolled out credential by credential instead of in one migration you have to hold your breath through.
The wrapping key is not stored anywhere. It is derived on demand as HMAC-SHA256(master key, region || 0x00 || key id), where the key id defaults to pam-root and the region to global. The practical consequence of deriving rather than storing is that the only thing you have to protect and back up is the master key itself.
The envelope metadata is returned by the API, and that is deliberate rather than an oversight. It contains the wrapped data key, not the data key. Without the master key, the wrapped key is a random-looking blob. Exposing it lets the console show you which wrapping key a credential is currently under and when it was last rewrapped, which is exactly the information you need to run a key rotation with confidence.
Credentials created before envelope encryption existed carry an empty cipher version and are encrypted directly under the master key. They are readable, they work, and they are migrated to v2-envelope the first time they are rewrapped.
The encryption key, and the sentence about losing it
One environment variable protects every stored privileged secret in your deployment:
PAM_ENCRYPTION_KEY
64 hexadecimal characters — 32 bytes, an AES-256 key.It is marked required. The platform will not start without it, and it will not start with a malformed one: the value is hex-decoded at boot, and each of the following is refused with a message naming exactly which check failed and which component was constructing at the time — PAM_ENCRYPTION_KEY is not set; PAM_ENCRYPTION_KEY still holds the example placeholder; PAM_ENCRYPTION_KEY is not hex; or PAM_ENCRYPTION_KEY must be 32 bytes (64 hex chars), got <n> if it decodes to the wrong length. A process that failed to start is far better than a process that started with a key it could not use, because the second one accepts writes it can never read back.
The check now goes further than length and format, because a key can be perfectly well-formed and still be worthless. The storage layer also refuses to start if the key, while correctly 64 hexadecimal characters, is obviously not random — a run of identical bytes, a known placeholder value that some example configuration files ship with, or a short pattern repeated to fill the length.
The same key is also used as the master key for the stored credentials of your integrations and identity connectors. So it is not 'the PAM key' in practice — it is the key for every secret the platform holds on your behalf.
This cuts the other way too, and it is a fair question to ask us: if the key is only an environment variable on a host we operate, the security of your vault rests on the security of that host. That is true, it is the honest limit of the current design, and it is why the master key can be rotated without re-encrypting anything.
Rotating the master key without touching the secrets
Because the per-secret key is wrapped rather than the secret being encrypted directly, changing which key wraps it requires unwrapping and rewrapping a very small amount of data. The ciphertext of the secret itself is not touched, not re-read and not rewritten. The rewrap operation is append-only and auditable: each rewrap adds a record rather than overwriting the history.
In the console this appears as Envelope encryption, with a CMK key id, a wrapping region, a last-rewrapped timestamp, and a Rewrap under new CMK action. The refusals, all HTTP 400 unless noted:
wrapping_key_id and wrapping_region are required
invalid wrapping target
credential envelope metadata is invalid
credential not found (404)
failed to rewrap credential (500)credential envelope metadata is invalid usually means the row is a legacy one whose envelope has not been written yet, or that the metadata was edited outside the product. invalid wrapping target means the key id or region you supplied is not one the deployment recognises.
One tenant cannot read another
Every credential read is scoped to your organisation in the database query itself, not only by the permission check on the route. That distinction is the whole point. A route guard answers 'is this caller allowed to read credentials?'. It does not answer 'is this caller allowed to read this credential?'. Without the organisation clause in the query, a caller supplying a valid token for their own organisation and the identifier of another tenant's credential would read that tenant's vault row, and every permission check in the request would have passed.
Rotation schedules re-verify credential ownership at creation and again at every update, for the same reason twice over: a schedule pointing at another tenant's credential would be simultaneously a cross-tenant secret decryption and a cross-tenant vault overwrite. Checking once at creation would leave the update path open.
The Needs attention panel, and enabling rotation in bulk
The vault screen opens with a panel titled Needs attention, and on a well-run vault it says exactly that: Nothing needs your attention: every credential is rotating, governed by a policy, and no emergency access is open. When it has something to say, it lists up to five findings per category, each with a severity and a named list of the credentials or sessions involved — not just a count.
| Finding | What it means |
|---|---|
| Credentials not rotated in over N days | Rotation is either off or has never run for these. Enable rotation fixes this in one action, described below. |
| Rotation failing | A schedule exists but the last run did not succeed, so the stored secret may be older than the schedule claims. |
| Credentials with no access policy | No approval, no MFA, no time window applies — only one session at a time is enforced. Create a policy jumps straight to fixing it. |
| Break-glass sessions open right now | Emergency access is currently in use. View sessions takes you to it. |
| Access requests waiting for a decision | Somebody is blocked on you. Review requests takes you to the queue. |
Deleting a credential asks you to type its name
Deleting a vault entry is one of the few actions in the console that requires typing the credential's name into the confirmation dialog before the delete button will do anything, rather than a single click-to-confirm. That is deliberate, not extra friction for its own sake: the vault is where the only copy of a password lives, and a mis-click here is not a row that comes back from a backup — it is a system nobody can sign in to any more until the credential is recreated from scratch.
What to do next
- Confirm where
PAM_ENCRYPTION_KEYis backed up, and that the backup is not on the same machine. Do this before you store anything you cannot reset by hand. - Register the machines your secrets open and run the connection test on each.
- Read how access to a credential actually works — it is not a checkout in the sense you are expecting.
- Turn on rotation once the target tests clean, or clear the Needs attention panel's rotation finding in bulk.
- Organise credentials into Safes if you want different people to see different subsets of the vault.