Rotation
Deployed and supported. The fields and commands described here are stable within version v1alpha1.
Rotation replaces the value of a secret with a new random value, stored as a new version. You can trigger it yourself (sng secret rotate) or entrust it to the platform, which renews the value every N days without intervention (--rotation N). Your applications receive the new value automatically.
Prerequisites
- An existing secret with a value: see Create and read a secret.
sng secret rotatereads then rewrites the secret: you needssm:readandssm:write(policiesreadwriteoradmin).- The application that consumes the secret must be able to reread its configuration on restart — this is the case for any App deployed with StackNGO, which is restarted for you (see below).
1. Renew now
sng secret rotate db-password
secret "db-password" rotated: new 16-character value (sng secret get db-password)
The new value is generated by the CLI with the same options as sng secret set: --length, --contains, --exclude-chars, --no-lowercase, --no-uppercase, --no-numbers, --no-symbols, --require-each-type.
sng secret rotate api-key --length 40 --contains letters,numbers
For a secret with several keys (set with --data), designate the key to renew; the others are kept:
sng secret rotate shop-api --key SMTP_PASSWORD
secret "shop-api" rotated: new 16-character value for key "SMTP_PASSWORD"
Without --key on a multi-key secret, the command refuses: the secret holds several keys (DATABASE_URL, SMTP_HOST, SMTP_PASSWORD): choose one with --key.
2. Arm an automatic rotation
sng secret set db-password --rotation 30 --length 24 --contains letters,numbers
secret "db-password" saved with a generated 24-character value (sng secret get db-password)
From now on, the platform generates a new 24-character value (letters and digits) every 30 days. The generation options passed with --rotation describe the future value: at the deadline, no client is there to choose them, so the platform must know them. That is why they remain accepted even when you provide the initial value:
# initial value provided, renewed by the platform every 90 days
sng secret set db-password --from-file ./initial --rotation 90 --length 32
--rotation also applies to sng secret rotate, to renew right away and arm what follows:
sng secret rotate db-password --rotation 30
For a multi-key secret, --rotation-key says which key the platform will have to renew (required if the secret has several keys, otherwise the scheduled rotation fails):
sng secret set shop-api --data DATABASE_URL=… --data SMTP_PASSWORD=… \
--rotation 60 --rotation-key SMTP_PASSWORD
Check the schedule:
sng secret describe db-password
{
"name": "db-password",
"keys": ["value"],
"version_id": "2",
"rotation_enabled": true,
"rotation_days": 30,
"next_rotation_date": "2026-11-06T10:02:17Z",
"version_stages": { "current": "2" },
"bindings": { "...": "..." }
}
3. Modify or disarm
| You want to | Command |
|---|---|
| Change the interval | sng secret set NAME --rotation 15 (the deadline restarts from today) |
| Change the shape of the future value | sng secret set NAME --rotation 30 --length 40 (same interval: the current deadline is kept) |
| Disarm | sng secret set NAME --rotation 0, or the API POST /v1/secrets/NAME/rotate/cancel (without touching the value) |
| Change nothing about the rotation | do not specify --rotation: an ordinary set keeps the schedule in place |
A sng secret set NAME --rotation 0 alone, without a value, also generates a new value (it is a set). To disarm without touching the value, pass the current value again: sng secret get NAME --key value --out-file ./cur && sng secret set NAME --from-file ./cur --rotation 0.
Cancel the scheduled rotation without touching the value
The route POST /v1/secrets/{name}/rotate/cancel disarms the scheduled rotation: the value and the versions do not change, the next due date disappears. It requires the ssm:rotate action on this secret. There is no dedicated sng command.
curl -s -X POST -H "Authorization: Bearer $SESSION" \
https://api.dev.stackngo.fr/v1/secrets/db-password/rotate/cancel
{
"name": "db-password",
"keys": ["value"],
"version_id": "4"
}
The response no longer contains rotation_enabled nor next_rotation_date: the rotation is disarmed. The call also succeeds on a secret whose rotation is already disarmed; it responds 404 if the secret does not exist or was deleted. sng secret describe db-password no longer shows a due date either.
If the secret is declared by an SSMSecret manifest that carries rotateDays, the manifest prevails: the rotation is rearmed on its own, at most 6 hours after the cancellation. To stop it for good, remove rotateDays from the manifest (or set 0) and apply again.
On-demand rotation, scheduled rotation
- On-demand rotation (
sng secret rotate) is done by your CLI: it reads the current value, generates a new one and stores it. It therefore requiresssm:readandssm:write. - Scheduled rotation is done by the platform, without any connected client. At the deadline (checked every minute), a new value is generated according to the stored options, stored as a new version, and the next deadline is set to
now + N days. The platform can only have a value generated: it does not read it and cannot choose one. - A managed policy reserved for rotation is present in each tenant (visible with
sng tenant policy list). Do not remove it. If your own policies denyssm:rotateon this secret, the rotation fails; after 5 consecutive failures, it is disarmed (rotation_enableddisappears fromdescribe) so as not to retry indefinitely. Rearm it after fixing the policy. - Rotation replaces the value whatever its origin, including a value set by hand with
--from-file.
How an App sees the new value
An App references a secret by its name (env: { DB_PASSWORD: { secret: db-password, key: value } }). After each write, manual or scheduled:
- within 5 minutes, the platform detects the new version;
- the
deploymentservices that read this secret are restarted gracefully: the new containers start with the new value before the old ones stop; jobandcronjobservices are not restarted: their next run reads the new value.
Consequence: a rotation causes a restart of your services within 5 minutes. Size replicas and strategy so that it happens without interruption, and avoid rotating a secret whose new value must also be set elsewhere (external database, SMTP provider) without having planned the corresponding step: rotation only updates the secret, not the service that checks it.
Verification
sng secret versions db-password # one more version after each rotation
sng secret describe db-password # next_rotation_date moves forward by N days
sng kind get App shop # the App went through a deployment again
If it doesn't work
| What you see | Cause | Fix |
|---|---|---|
the secret holds several keys (…): choose one with --key | Multi-key secret without --key | Add --key (or --rotation-key for the scheduled rotation) |
rotation_enabled missing from describe although you had armed it | 5 scheduled rotations failed in a row, often because of a policy that denies ssm:rotate; or someone canceled the rotation | Fix the policy, then rearm with --rotation N |
| The rotation comes back after a cancellation | The secret is declared by an SSMSecret with rotateDays | Remove rotateDays from the manifest |
| The service that checks the password rejects it after a rotation | The rotation only changed the secret, not the external service | Disarm the rotation for this secret, or update the external service at each rotation |
forbidden (…) on sng secret rotate | ssm:read or ssm:write is missing | Ask an administrator for readwrite |
Going further
- Bounds:
--rotationaccepts 1 to 3650 days;--length1 to 4096. - Deadline: rearming the same interval does not push back the deadline (otherwise frequent
setcalls would postpone it endlessly). A different interval restarts from today. - Secret declared by manifest:
spec.rotateDaysof an SSMSecret arms the same scheduled rotation, withspec.lengthandspec.containsas the shape of the future value.status.nextRotationAtreflects its deadline. - API: the policy travels in the
rotation_policyfield of the body ofPUT /v1/secrets/{name}:{"enabled": true, "interval_days": 30, "key": "SMTP_PASSWORD", "generation": {"length": 24, "contains": ["lower","upper","number"], "exclude_chars": "", "require_each_type": true}}. The classes on the API side arelower,upper,number,symbol.{"enabled": false}disarms; field absent = unchanged. POST /v1/secrets/{name}/rotate(actionssm:rotate) exists in the API but does not do whatsng secret rotatedoes: it renews the encryption of the current value without changing it. It creates a version but does not renew the password.