Skip to main content

Rotation

Available

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 rotate reads then rewrites the secret: you need ssm:read and ssm:write (policies readwrite or admin).
  • 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 toCommand
Change the intervalsng secret set NAME --rotation 15 (the deadline restarts from today)
Change the shape of the future valuesng secret set NAME --rotation 30 --length 40 (same interval: the current deadline is kept)
Disarmsng secret set NAME --rotation 0, or the API POST /v1/secrets/NAME/rotate/cancel (without touching the value)
Change nothing about the rotationdo 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.

Secret declared by manifest

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 requires ssm:read and ssm: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 deny ssm:rotate on this secret, the rotation fails; after 5 consecutive failures, it is disarmed (rotation_enabled disappears from describe) 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:

  1. within 5 minutes, the platform detects the new version;
  2. the deployment services that read this secret are restarted gracefully: the new containers start with the new value before the old ones stop;
  3. job and cronjob services 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 seeCauseFix
the secret holds several keys (…): choose one with --keyMulti-key secret without --keyAdd --key (or --rotation-key for the scheduled rotation)
rotation_enabled missing from describe although you had armed it5 scheduled rotations failed in a row, often because of a policy that denies ssm:rotate; or someone canceled the rotationFix the policy, then rearm with --rotation N
The rotation comes back after a cancellationThe secret is declared by an SSMSecret with rotateDaysRemove rotateDays from the manifest
The service that checks the password rejects it after a rotationThe rotation only changed the secret, not the external serviceDisarm the rotation for this secret, or update the external service at each rotation
forbidden (…) on sng secret rotatessm:read or ssm:write is missingAsk an administrator for readwrite

Going further​

  • Bounds: --rotation accepts 1 to 3650 days; --length 1 to 4096.
  • Deadline: rearming the same interval does not push back the deadline (otherwise frequent set calls would postpone it endlessly). A different interval restarts from today.
  • Secret declared by manifest: spec.rotateDays of an SSMSecret arms the same scheduled rotation, with spec.length and spec.contains as the shape of the future value. status.nextRotationAt reflects its deadline.
  • API: the policy travels in the rotation_policy field of the body of PUT /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 are lower, upper, number, symbol. {"enabled": false} disarms; field absent = unchanged.
  • POST /v1/secrets/{name}/rotate (action ssm:rotate) exists in the API but does not do what sng secret rotate does: it renews the encryption of the current value without changing it. It creates a version but does not renew the password.

See also​