Secret Rotation and Credential Expiry#

Symptom#

Something that worked yesterday now fails with an authentication or authorization error, and no deployment changed. Typical shapes:

  • Storage backend calls fail with an access-denied error from S3 or Azure Blob.

  • A service cannot authenticate to its database.

  • Service-to-service calls fail with an invalid or expired token.

  • A queue consumer stops receiving, with an authorization error in its logs.

Blast radius#

Scoped to whatever used the rotated credential — but that can be wide. A rotated backend credential takes out all access to that backend while leaving every other endpoint working, which presents as “some content is unavailable” rather than an outage.

Credentials in this stack come from several places, and they fail differently:

Credential

Where it comes from

Failure shape

Object-store keys

Deployment values or a Kubernetes secret

Backend access denied

Workload identity (AKS / EKS IRSA)

Projected service-account token

Token expiry, or trust misconfigured

Service identity token

Access-token file provided by a sidecar

Calls rejected as unauthenticated

OAuth2 client secret

Deployment values or secret

Token endpoint rejects the client

Database credentials

ExternalSecret from a vault or key vault

Authentication failure at connect

Queue credentials

Access-token file or connection secret

Consumer stops receiving

Triage#

1. Confirm it is credentials, not permissions.

An expired credential and a revoked grant look similar from the client. The service logs distinguish them: authentication failures name the credential, authorization failures name the action or resource.

2. Is the secret present and current in the cluster?

kubectl -n <namespace> get externalsecret
kubectl -n <namespace> describe externalsecret <name> | tail -20
kubectl -n <namespace> get secret <name> -o jsonpath='{.metadata.creationTimestamp}{"\n"}'

An ExternalSecret whose status is not SecretSynced is the whole answer — the vault side failed, and the cluster is still serving the last value it synced, which is now stale.

3. Has the pod picked it up?

This is the step most often missed. A secret can be correct in the cluster and stale in the process:

  • A secret mounted as a file is updated in place, but only if the consuming code re-reads it.

  • A secret injected as an environment variable is fixed for the life of the pod. Rotating it requires a restart.

# When did the pod start, relative to when the secret changed?
kubectl -n <namespace> get pods -o custom-columns='NAME:.metadata.name,START:.status.startTime'

If the pod started before the secret changed and the value is injected as an environment variable, the pod is running the old credential regardless of what the secret now contains.

4. Is the token simply expired rather than rotated?

Short-lived tokens — workload identity, access-token files — expire on a schedule. If the failure recurs at a regular interval, the refresh mechanism is broken rather than the credential.

kubectl -n <namespace> exec deployment/<service> -- \
    sh -c 'ls -l --time-style=full-iso <token-path>'

A token file whose modification time has stopped advancing means the provisioning sidecar has stopped refreshing it.

Fix#

  • ExternalSecret not syncing — fix the vault-side cause (missing key, expired vault credential, changed path), then let it resync. Deleting the Kubernetes secret to force a resync is safe only once the source is fixed; deleting it while the source is broken removes the last working value.

  • Pod holding a stale value — roll the deployment. Prefer a rollout restart over deleting pods, so surge and readiness are respected:

    kubectl -n <namespace> rollout restart deployment/<service>
    kubectl -n <namespace> rollout status deployment/<service>
    
  • Rotated backend key — update it through your deployment values so the change survives the next deploy, not by editing the live secret.

  • Expired workload identity — check the federated credential and the service-account annotation still match the identity; an identity replaced on the cloud side leaves both looking present but no longer trusted.

Warning

Do not patch credentials directly into a running deployment as a permanent fix. The next deploy reconciles from source and the failure returns, typically at the least convenient moment and with no record of what changed.

Verify#

  • The failing operation succeeds again.

  • ExternalSecrets report SecretSynced.

  • The failure does not recur at the next rotation interval — which is the real test for expiry-driven cases, and worth scheduling a check for rather than assuming.