External secret stores (SEC-011)
Reference a secret instead of pasting it: wt+secret://<store-name>/<path>#<key>. Path segments and the key use [A-Za-z0-9_.-]; empty, . and .. segments are rejected.
Manage stores at /v1/orgs/{org}/secret-stores (needs manage_security).
Kind | Config | Notes |
|---|---|---|
|
| KV v2. Credential: token string, or |
|
| SigV4. Sealed static keys, else the SDK default chain (env, IRSA, instance role). With |
|
| Sealed service-account key, else workload identity. |
env,fileandk8sstores no longer exist: they read the server's own environment and disk. They cannot be created, and rows from before this are refused at read time.A store without its own credential would use the server's identity (Kubernetes service-account token, cloud role or metadata token). That is refused unless the operator lists the host in
WT_SECRET_STORE_AMBIENT_HOSTS(comma-separated, empty by default). This covers Vaultkubernetesauth,aws_smandgcp_sm.Endpoints go through the SSRF guard. Private addresses need
"allow_private": truein the config.Values are cached 60 s in memory, zeroed on eviction, never stored, logged or audited.
POST .../{id}/testreturns{ok, error_code}only.Rust services resolve
vault(token auth); a reference into another kind is rejected at save time with 422unsupported_in_this_service.An unresolved reference marks the owning object unhealthy with error class
secret_unresolved; it never crashes the service.Deleting a store that receivers, export destinations or archives still reference returns 409 listing them.
Who may use a store
A store is usable by no project until
PUT /v1/orgs/{org}/secret-stores/{id}/grants(manage_security) allows one:{"grants": [{"project_id", "path_prefix"}]}. A prefix covers that path and everything under it; empty is the whole store.Saving a reference needs
manage_credentialsand a matching grant. It is audited assecret_ref.bound. Sending a receiver's stored reference back unchanged needs neither, but cannot move the receiver.Both are checked again on every read. A reference saved before this, or whose grant was removed, fails with
secret_ref_not_permitteduntil someone permitted saves it again.manage_credentialsholders can send a value they may bind, or a vault value, to a host of their own: that role is trusted with those secrets. Monitors and load tests record the names and target in the audit log (vault_names,target,allowed_hosts); values are never recorded.
Using a reference
Send credentials_ref instead of the secret value:
Object | Where the ref is kept | What the secret must hold | Stores |
|---|---|---|---|
Receiver |
| JSON like | all kinds (the notifier is Go) |
Export destination |
| JSON like |
|
Archive |
|
| all kinds (the archiver is Go) |
Uptime monitor | any | the value itself | all kinds |
Send either the value or the reference, never both (422). The reference is not secret and is returned as given. The reference replaces sealed secrets on update. Receivers and archives read the secret at delivery, so a rotated value takes effect within 60 s.