Key rotation¶
This page shows how to change each secret that Sluice uses.
Example¶
Goal: give a client a new API key while the old key still works.
Store the hash of the new key in a file that only the owner can write:
sluice apikey hash --pepper key-rotation-demo-pepper --id report-bot-2026b --material new-secret \
> /tmp/sluice-key-rotation/report-bot-2026b.hash
chmod 600 /tmp/sluice-key-rotation/report-bot-2026b.hash
Add a second entry to the subject binding. A secret file needs an absolute path. The example uses /tmp/sluice-key-rotation:
apiVersion: sluice.bino.bi/v1alpha1
kind: SubjectBinding
metadata:
name: report-bot
spec:
apiKeys:
- id: "report-bot-2026a"
hashRef: "secret://file//tmp/sluice-key-rotation/report-bot-2026a.hash"
groups: ["agents"]
# The new key. It needs its own id.
- id: "report-bot-2026b"
hashRef: "secret://file//tmp/sluice-key-rotation/report-bot-2026b.hash"
groups: ["agents"]
Reload:
curl -s -X POST http://localhost:9091/admin/reload \
-H "Authorization: Bearer demo-admin-token-change-me"
Both keys work now. The new key:
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: report-bot-2026b.new-secret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT id, name FROM shop.main.customers ORDER BY id"}'
{
"query_id": "<query-id>",
"columns": ["id", "name"],
"rows": [
[1, "Alice"],
[2, "Bob"]
],
"row_count": 2,
"truncated": false
}
When the client uses the new key, remove the old entry and reload. Then the old key gets this answer:
curl -s http://localhost:8080/v1/query \
-H "X-Api-Key: report-bot-2026a.old-secret" \
-H "Content-Type: application/json" \
-d '{"sql": "SELECT id, name FROM shop.main.customers ORDER BY id"}'
What happened: Sluice reads every hashRef again on a reload. The new key still works.
Secrets¶
| Secret | Key | New value works | What to know |
|---|---|---|---|
| API key | apiKeys[].hashRef | After a reload (secret://file/) | Use two entries with two ids, as in the example |
| Pepper | identity.apiKeyPepper | After a restart | Every key hash changes. First compute all hashes again with sluice apikey hash |
| JWT key (RS, ES) | JWKS of the issuer | On first use | Publish the new key next to the old one. A removed key works until jwksCacheTtl ends |
| JWT secret (HS) | hmacSecretRef | After a reload (secret://file/) | One secret per issuer. Old tokens fail at once |
| Audit genesis | audit.file.genesis | Only in an empty audit directory | Set a new audit.file.path too, then restart. Verify each directory with its own --anchor |
| Admin token | admin.token | After a restart | /metrics needs the same token |
| Data source credentials | credentialsRef, tokenRef, headersRef | After a restart | Keep the old credential valid at the source until then |
Column mask salt and fpe key | saltRef, keyRef | After a reload (secret://file/) | The same input gives a new masked value |
Sluice reads a secret://env/ value from its own process environment. A new or changed variable needs a restart.
Limits¶
- Sluice cuts spaces and line ends at the end of the pepper and the genesis.
sluice apikey hash --pepper secret://...does the same. - Without a reload, Sluice can use an old mask salt for up to 10 minutes.
- With
claims.subjectId, the audit log does not show which key a client used.
See also¶
- Subjects: API keys and JWT.
- Reload
- Server config:
secret://references.