Add `miroir-ctl key rotate-node-master` command implementing plan §9 4-step zero-downtime rotation: create new admin-scoped key on all Meilisearch nodes, print K8s Secret update instructions, wait for rolling restart confirmation, delete old key. Supports --dry-run, node auto-discovery via topology API, and rollback on step 1 failure. Add `address` field to topology API NodeInfo for CLI node discovery. Add runbooks for both nodeMasterKey (zero-downtime) and startup master key (maintenance window required) rotation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
4.9 KiB
nodeMasterKey Zero-Downtime Rotation
Rotates the admin-scoped child key (
nodeMasterKey) that Miroir uses to authenticate with Meilisearch nodes. No maintenance window required.This is NOT the startup master key (
MEILI_MASTER_KEY). For that, see startup-master-key-rotation.md.
Background (plan §9)
Meilisearch allows multiple admin-scoped keys (created via POST /keys with
actions: ["*"], indexes: ["*"]) to coexist. The nodeMasterKey in Miroir
config is one such key. Because old and new keys are both valid until the old
one is explicitly deleted, rotation is zero-downtime.
Prerequisites
miroir-ctlbinary built from this repo- Admin API key (
MIROIR_ADMIN_API_KEYenv var, credentials file, or--admin-key) - Current
nodeMasterKeyvalue (--current-keyorMIROIR_NODE_MASTER_KEYenv var) - Miroir admin API reachable (default
http://localhost:8080, override with--api-url) kubectlaccess to update the K8s secret and restart Miroir pods
Quick Start
# Dry-run — prints the plan without executing
miroir-ctl key rotate-node-master --dry-run \
--current-key "$MIROIR_NODE_MASTER_KEY"
# Live rotation with auto-discovered nodes
miroir-ctl key rotate-node-master \
--current-key "$MIROIR_NODE_MASTER_KEY"
# Live rotation with explicit nodes
miroir-ctl key rotate-node-master \
--current-key "$MIROIR_NODE_MASTER_KEY" \
--node http://meili-0.search.svc:7700 \
--node http://meili-1.search.svc:7700 \
--node http://meili-2.search.svc:7700
What the CLI Does (4 steps)
Step 1 — Create new admin-scoped key on every Meilisearch node
POST /keys with actions: ["*"], indexes: ["*"]. If any node fails, the
CLI rolls back by deleting the new key from all nodes where creation succeeded.
Step 2 — Print K8s Secret update instructions
The CLI prints a kubectl patch secret command. Apply it:
kubectl -n search patch secret miroir-keys \
-p '{"stringData":{"nodeMasterKey":"<new-key>"}}'
Or update your ExternalSecret / OpenBao source and wait for ESO to sync.
Step 3 — Rolling restart Miroir pods
kubectl -n search rollout restart deployment/miroir
kubectl -n search rollout status deployment/miroir
During rollout, pods with the old key and pods with the new key both authenticate against Meilisearch — no downtime.
The CLI pauses and waits for you to confirm all pods are running.
Step 4 — Delete old admin-scoped key
The CLI finds the old key UID via GET /keys (matching by prefix) and deletes
it from all Meilisearch nodes with DELETE /keys/{uid}.
CLI Flags
| Flag | Default | Description |
|---|---|---|
--dry-run |
false | Print plan without executing |
--current-key |
env MIROIR_NODE_MASTER_KEY |
Current key (required) |
--node |
auto-discovered | Meilisearch node URLs (repeatable) |
--key-name |
miroir-node-master |
Name for the new key |
--expires-at |
none | Optional ISO 8601 expiration |
--namespace |
search |
K8s namespace |
--secret-name |
miroir-keys |
K8s Secret name |
--yes |
false | Skip confirmation prompts |
Manual Steps (if CLI is unavailable)
-
Create new key on each Meilisearch node:
for i in 0 1 2 3; do curl -s -X POST "http://meili-${i}.search.svc:7700/keys" \ -H "Authorization: Bearer $CURRENT_KEY" \ -H "Content-Type: application/json" \ -d '{"name":"miroir-node-master","description":"rotated key","actions":["*"],"indexes":["*"]}' \ | jq '{uid,key}' done -
Update secret with the new key value from step 1.
-
Rolling restart Miroir deployment.
-
Delete old key — list keys, find the old one by prefix match, delete by UID:
curl -s http://meili-0.search.svc:7700/keys \ -H "Authorization: Bearer $NEW_KEY" | jq '.results[] | {uid,key,name}' # Then DELETE /keys/{old-uid} on each node
Verification
# Confirm Miroir is healthy
curl -s http://miroir.search.svc:7700/health
# Check topology
miroir-ctl status
# Test search
curl -s http://miroir.search.svc:7700/indexes/test-index/search \
-H "Authorization: Bearer $MIROIR_MASTER_KEY" \
-d '{"q": ""}'
Cadence
- Rotate on suspected compromise (immediately)
- Rotate proactively every 90 days
- Chain after startup-master rotation (see below)
Relationship to Startup Master Rotation
If you have just rotated MEILI_MASTER_KEY (see
startup-master-key-rotation.md), the new
Meilisearch nodes have no admin-scoped child keys yet. Create one using the
new master key, then run this zero-downtime flow to rotate it.
See Also
- startup-master-key-rotation.md — startup master (requires maintenance window)
- Plan §9 — full secrets handling documentation
miroir-ctl key rotate-node-master --dry-run— preview the rotation plan