Rotate the Connector Gateway encryption key
The Connector Gateway seals the upstream OAuth tokens and dynamic client registrations it stores in Redis with a key-encryption key (KEK). To rotate the KEK, you add a new key version to the KEK Secret, roll the gateway so every replica can read it, and then switch new writes to it. Old versions stay in the Secret, so credentials sealed under them remain readable and users don't have to reconnect their connectors.
This page uses the stacklok-enterprise release name, the stacklok-system
namespace, and the connector-gateway-kek Secret from
Configure the Connector Gateway.
Adjust the names if yours differ.
Prerequisites
- The KEK in a Secret you manage, referenced by
connector-gateway.kek.existingSecret. If you setkek.valueinstead, put the same version maps shown below, base64-encoded, intokek.value. If you setkek.generate: true, the chart preserves whatever thestacklok-enterprise-connector-gateway-kekSecret holds across upgrades, so edit that Secret in place. kubectl,helm,openssl, andjq, with access to the release namespace.- A secure place to keep key material. The commands below write keys to local files. Store them according to your secrets-handling policy and delete the local copies when you're done.
How key versions work
The KEK Secret's kek entry holds either a single 32-byte key, which the
gateway treats as version 1, or a JSON version map that maps positive integer
versions to base64-encoded 32-byte keys:
{ "1": "<BASE64_KEY_1>", "2": "<BASE64_KEY_2>" }
The gateway reads every version in the map and can open credentials sealed under
any of them. It seals new credentials under the active version, which you pin
with connector-gateway.kek.activeVersion. When the pin is unset, the active
version is the highest version in the map.
Each replica reads the Secret once at startup, so a change to the Secret or to
activeVersion reaches a replica only when it restarts. Rotation therefore
takes two rolling restarts: the first makes the new version readable on every
replica, and the second makes it active. If you made the new version active in a
single rollout, an updated replica could seal a credential that a replica still
running the old keyring can't open.
Rotate the key
Add the new version
-
Export the current key material:
kubectl get secret connector-gateway-kek \--namespace stacklok-system \--output jsonpath='{.data.kek}' | base64 -d > kek-current -
Build the current version map. If
wc -c < kek-currentprints32, the Secret holds a single key; wrap it as version 1 without changing its bytes:jq -n --arg k "$(base64 < kek-current | tr -d '\n')" '{"1": $k}' > keyring.jsonOtherwise, the Secret already holds a version map:
cp kek-current keyring.json -
Add a new key one version higher than the current highest. For example, to add version 2:
jq --arg k "$(openssl rand -base64 32)" '. + {"2": $k}' keyring.json > keyring-new.json -
Replace the Secret's contents with the new map:
kubectl create secret generic connector-gateway-kek \--namespace stacklok-system \--from-file=kek=./keyring-new.json \--dry-run=client --output yaml | kubectl apply -f -If External Secrets Operator or another tool syncs this Secret from a secrets manager, write the new map to the source instead, and confirm the sync has updated the Secret in the cluster before you continue.
-
In your values file, pin the active version to the current version, and set
keyringGenerationto a new value:values.yamlconnector-gateway:kek:existingSecret: 'connector-gateway-kek'activeVersion: '1'keyringGeneration: '2'The pin keeps new writes on version 1 while replicas restart. Without it, each restarted replica would seal under version 2 immediately. The chart restarts the gateway only when a KEK-related value changes, and editing the Secret changes none of them, so change
keyringGenerationon every rotation to force the rollout. Any new string works; using the new version number keeps it easy to track. -
Upgrade the release and wait for the rollout to finish:
helm upgrade stacklok-enterprise \oci://oci.stacklok.com/stacklok-enterprise/<CHANNEL>/stacklok-enterprise-platform \--version <VERSION> \--namespace stacklok-system \--values values.yamlkubectl rollout status deployment/stacklok-enterprise-connector-gateway \--namespace stacklok-systemContinue only when the rollout completes and no pod from the previous ReplicaSet is still running. On this rollout, the first replica to start logs a
no KEK canary foundwarning for version 2, which is expected. See Startup checks.
Activate the new version
Set activeVersion to the new version, then upgrade the release again and wait
for the rollout:
connector-gateway:
kek:
existingSecret: 'connector-gateway-kek'
activeVersion: '2'
keyringGeneration: '2'
Changing activeVersion restarts the gateway on its own. Once the rollout
completes, new credentials are sealed under version 2, and credentials sealed
under version 1 stay readable. Keep activeVersion pinned explicitly from here
on, rather than unsetting it.
Keep version 1 in the Secret until you retire it. Credentials move to version 2 gradually, as users sign in and the gateway refreshes their tokens.
Roll back a rotation
To make an earlier version active again, set activeVersion back to it and
upgrade the release. Because every version is still in the Secret, credentials
sealed under either version stay readable and no one has to reconnect. This
works only while the version you return to is still in the Secret, so don't
retire a version you might need to roll back to.
Retire an old version
Removing a version from the Secret makes every credential still sealed under it unreadable. The gateway treats those credentials as missing and recovers them: users reconnect the affected connectors, and the gateway re-registers clients for providers that use dynamic client registration. Waiting before you remove the version keeps that wave small.
-
Confirm the version is inactive.
activeVersionnames a newer version, and that rollout has completed on every replica. -
Wait for stored tokens to turn over. Every token refresh reseals the credential under the active version, and a stored upstream token expires 30 days after its access token does. Waiting at least 30 days after the activation rollout leaves only credentials with no expiry, such as clients registered with a non-expiring client secret, under the old version.
-
Remove the version. Export the current version map as in Add the new version, delete the old entry, and apply the result. For example, to remove version 1:
kubectl get secret connector-gateway-kek \--namespace stacklok-system \--output jsonpath='{.data.kek}' | base64 -d > keyring.jsonjq 'del(."1")' keyring.json > keyring-retired.jsonkubectl create secret generic connector-gateway-kek \--namespace stacklok-system \--from-file=kek=./keyring-retired.json \--dry-run=client --output yaml | kubectl apply -f - -
Roll the gateway. Change
keyringGenerationto a new value, upgrade the release, and wait for the rollout to complete. Replicas that haven't restarted yet keep reading the removed version until they do. -
Watch the recovery. The
kek_unsealable_reads_totalcounter increments each time the gateway reads a credential sealed under a version that's no longer in the Secret, labeled with thatversion. Its rate shows the remaining reconnect wave draining. The counter tracks read events, so it stays at zero while the version is still present and can't tell you in advance how many credentials still use it. Export it with the gateway's metrics.
When a removed version still seals a dynamic client registration, the gateway registers a new client with that provider on startup. This has two effects to plan for:
- Every user of that connector reconnects, not only those whose tokens were sealed under the removed version, because refresh tokens are bound to the client they were issued to. Schedule the removal for a window where that's acceptable.
- The old client stays registered at the provider. The gateway logs a
warning with the
replaced_client_idfield so you can find and delete it.
Replicas coordinate through Redis, so the gateway registers one new client per provider regardless of the replica count.
Keep the KEK Secret append-only
Treat the KEK Secret as append-only: add higher versions, never change the bytes of an existing version, and remove a version only through the retirement steps above. Several routine operations can break this rule without warning:
helm rollbackto a revision whose values carried an olderkek.valueoractiveVersion.- An Argo CD sync to an earlier Git revision of the Secret or of the
ExternalSecretthat populates it. - A Terraform apply of an earlier configuration, or any change that destroys and
recreates the secret resource, such as
terraform taint.
If a rollback drops a version that still seals credentials, the gateway starts
normally and those users reconnect, as in a retirement without the waiting
period. If it drops the active version, every replica refuses to start. If you
manage the key with Terraform, set lifecycle.prevent_destroy on the secret
resource, and prevent in-place edits to the key value with
lifecycle.ignore_changes or an equivalent write-once rule.
Startup checks
The gateway verifies its keyring against a canary in Redis before it serves traffic. For each key version, the first replica to start with that version seals a known value under it and stores the result. Every later startup opens those values to confirm each version still holds the same key.
A replica logs no KEK canary found; establishing a new baseline at warning
level when it seeds a canary. That's expected on a first install and on the
first rollout after you add a version. On an install that has already stored
credentials, an unexpected occurrence means the canary was deleted or evicted
from Redis, so the gateway can no longer detect a wrong key for that version.
Check your Redis eviction policy.
Next steps
- Collect Connector Gateway telemetry to export
kek_unsealable_reads_totalbefore you retire a version. - Forward Connector Gateway audit logs to keep a record of every tool call.
Related information
- Configure the Connector Gateway - creating the KEK Secret and the other gateway secrets
- Connector authentication - how users connect to connectors that use OAuth
Troubleshooting
Replicas refuse to start with a kek canary error
The error wraps
kek canary: cannot unwrap under the KEK version it is sealed under. A version
in the mounted Secret holds different bytes than the version that sealed its
canary: the Secret was regenerated, overwritten, or restored from the wrong
backup. Restore the original bytes for that version. Don't delete the canary to
get past the check, because the gateway would then accept the wrong key and fail
to open every credential sealed under that version.
If the error instead reads
kek canary: verified but could not advance to the current version, the key is
correct and the gateway couldn't write to Redis. Check Redis connectivity and
permissions.
Replicas refuse to start with a kek retirement guard error
kek.activeVersion names a version the mounted Secret doesn't hold, usually
because the active version was removed or a rollback reverted the Secret. Either
restore that version to the Secret, or set activeVersion to a version the
Secret holds, then upgrade the release.
Reads fail with a credential integrity error
The credential's version is present in the Secret, but its key doesn't open the credential, which means that version's bytes changed. The gateway keeps the credential and doesn't prompt the user to reconnect. Restore the original bytes for that version to make every affected credential readable again. Removing the version would discard those credentials instead.