WvdS Vault
Vault is the one place for the operator secrets of the package family: stored encrypted, with an expiry date, hand-over rotation and an access log. Without it, credentials sit in as many places as there are packages. The package is meant for operators who have to enter secrets, change them and prove how they were used.
Getting started
Quickstart: store your first secret and reference it — the shortest path to a first result, in three steps.
Check the prerequisites → Choose the key source → Pick a scheme → Prove the accesses
Common tasks
| What you want | For whom | Where you do it | Instructions |
|---|---|---|---|
| enter or change a secret | operators | Administration > Vault | Five schemes |
| rotate a secret as planned | operators | Administration > Vault | Rotation |
| change the key source | operators | – | Changing the key source |
| prove who accessed what and when | operators | Administration > Vault | The access log |
| understand the second factor before access | operators | – | Second factor |
| know what the vault does not do | operators | – | What the vault does not do |
Purpose
One place for the operator secrets of the suite: stored encrypted, with an expiry date,
hand-over rotation and an access log. Without it, credentials sit in as many places as there are
plugins, in as many forms as their authors have invented — and at least one of those is plain text
in conf/local.php.
Registered service (see Core): vault.secrets. The entries live via Storage
in data/dwdo/.
What the vault explicitly does not do
The boundary comes first, because with a vault it is the most important thing to know.
With the default key source salt the master key is derived from the wiki's instance salt.
That salt lives under data/meta/ — the same branch as the vault database under data/dwdo/.
data/ contains the key and the ciphertext
together. Whoever holds that archive reaches the secrets. This plugin does not protect
against that.
What it does protect against is everything short of that: a page that says too much, a file permission set too wide, a plugin that logs its own configuration, a database handed to a developer.
This class of exposure can be left entirely by choosing another key source — see
Configuration. env and file put the key outside data/ and therefore outside the
wiki backup.
Five schemes, one decision by the operator
A plugin that needs a password does not get a value, it gets a reference. Which source an installation uses is therefore decided by the operator, not by the author of each individual plugin.
| Reference | Meaning |
|---|---|
vault:{id} | Entry from this vault |
env:{name} | Environment variable |
file:{path} | File below the directory configured in file_root |
enc:… | Value stored encrypted inline |
plain:… | Plain text, explicitly marked as such |
Resolution happens from within your own plugin through SecretRef; the result distinguishes two
cases that must not be conflated:
| Result | Meaning |
|---|---|
null | nothing was configured |
SecretRefException | something was configured and could not be redeemed |
Configuration
| Setting | Default | Meaning |
|---|---|---|
keysource | salt | Origin of the master key: salt, env or file |
keysource_ref | empty | Variable name for env, file path for file; no effect with salt |
file_root | empty | Root directory that file: references must stay inside. Empty disables the scheme entirely |
log_page_size | 100 | Rows per page in the access log |
log_keep_days | 365 | Age at which access records expire; 0 keeps them forever |
file_root is empty by default and therefore fail-closed. A reference is configuration
supplied by the operator; without a root that confines it, file: would be a tool for reading
arbitrary files dressed up as a secret.
salt is the default because it needs no setup — and it is the weakest of the three. env
and file can both be copied to another machine and another operating system. That very
requirement rules out machine-bound stores (DPAPI, TPM, keychain): a vault whose key cannot leave
the computer is irrecoverable after a hardware failure — backup present and unreadable.
Changing the key source
Changing it is supported and harmless in itself: every value carries a fingerprint of the key it was encrypted under, never the key itself. Existing entries therefore keep decrypting, and new writes use the new source.
The order is binding:
- Change the setting.
- Run the re-encryption on the administration screen.
- Only then remove the old key material.
The screen reports through keyUsage() how many entries still sit under the old fingerprint.
Those are exactly the ones that would become unreadable if it were removed too early.
Rotation: two slots, four steps
An entry has two slots. A new value is first staged and switches nothing; only promoting it makes it the valid value.
stage()— stage a new value, running operations keep using the old onepromote()— make the staged value the valid one
The access log
One line per read, write and promote, carrying actor, entry, operation and result — and never a value. That is the difference between a vault and an encrypted file.
The vault.secrets service hands out reading only (read(), has()) and binds the
actor. A plugin that needs a password can therefore neither rotate one nor attribute a read to
somebody else.
Cleanup runs through the indexer's task execution, that is, on ordinary page views. On a wiki without traffic the records therefore simply stay.
Second factor before every access to secret material
Secured through the Identity Plugin Contract v1 (contract description): a bridge
do=wkvault_stepup with a hard-wired list of categories.
| Category | Applies to |
|---|---|
create | Creating an entry |
rotate | Staging a value |
delete | Deleting an entry |
rekey | Changing the key source and re-encrypting everything |
The check runs twice — on read when the screen is drawn, and again on submit — and in both
cases in addition to the DokuWiki access list, never in its place. Without wkidentity the
package behaves unchanged.
The bridge never accepts a requirement supplied by the caller, only a name from its own list; an unknown name yields a refusal, never “no requirement”.
Prerequisites
- PHP 8.2 or newer.
- Administrator rights for the administration screen.
The schema creates two tables, “vault.secret” and “vault.zugriff”, each idempotently. The dot is part of the name and separates the module, now that all modules keep their tables in one shared database; it must always be quoted in SQL, because SQLite otherwise reads it as a database separator and does not find the table.
Limits
- The default key source shares its backup with the data — see above.
- An expired secret is still delivered. Expiry is a prompt, not a safeguard: refusing delivery on a due date would bring mail dispatch, databases and repository access to a halt simultaneously, and for an administrative reason at that.
- Staging switches nothing. Only promoting does. That is the design, not a shortcoming.
- The vault cannot revoke anything at the provider. Completing a rotation by invalidating the old credential remains the operator's job.
- Lost key material means lost values. By design there is no recovery path.
- Cleanup depends on the wiki's indexer.
Package details
Plugin: wkvault
Namespace: lib/plugins/wkvault/
Author: Wolfgang van der Stille Wolfgang.van.der.Stille@gmail.com (The White Knight Labs)
License: GPL 2