The token does not renew itself¶
Of the four provider modules in this estate, this is the only one whose value goes stale. It is worth being precise about why, because the difference is structural rather than incidental.
A credential, or a way to get one¶
The question that decides whether a value is safe to hold indefinitely:
Does the object know how to obtain a fresh credential, or is it already a credential?
| Provider | What you hold | Renews? |
|---|---|---|
| AWS | a provider chain behind aws.NewCredentialsCache |
yes, on expiry |
| Azure | a TokenCredential the pipeline re-calls |
yes, before expiry |
| GCP | a cached token provider | yes, once stale |
| Vault | a client carrying an already-minted token | no |
vaultapi.NewClient reads VAULT_TOKEN a single time and stores it on the
client. Nothing re-reads the environment afterwards, and nothing renews the
lease. The Vault SDK models a client as configured with a token rather than
configured with a way to get a token.
What that means in practice¶
A command that starts, reads configuration, and exits will never notice. Its token outlives it.
A long-lived process reloading configuration will fail once the TTL passes — and it fails permanently, not transiently. Every subsequent call is refused, and nothing recovers, because the client cannot learn that its credential lapsed. That is the worst shape a failure can take: it looks like a transient outage and behaves like a broken deployment.
Why this module does not fix it for you¶
It would need to detect that the token had expired, and then re-resolve.
The first half is the problem. A refusal from Vault might mean your token expired, or your token is fine and this path is not permitted, or you are being rate limited. Those want opposite responses, and re-resolving on the wrong one either loops forever or aims a growing number of concurrent authentications at a service already refusing them.
An estate primitive exists for exactly this shape —
clientlifecycle.Invalidatable
— and it is deliberately not used here, for that reason. Its own
documentation makes the ability to distinguish those three cases a prerequisite
for using it, not an improvement to it. This module cannot distinguish them, so
it does not pretend to.
What to do instead¶
Renew the token, which is a lifecycle you own. Keep a
vaultapi.LifetimeWatcher running against the client, and rebuild when renewal
finally fails. The how-to has
the shape.
Or accept it deliberately. A token with a long TTL, in a process that is redeployed more often than the token expires, is a legitimate choice — not a defect. What is not legitimate is being surprised by it.
The same is true one level up¶
config-vault's zero-conf rung has exactly this property, and says so on
Default itself. So does config-consul, for the same structural reason — both
HashiCorp SDKs model the client the same way, and Consul additionally reads its
token file at construction, though a token file is precisely the mechanism you
would rotate.
The ladder did not cause any of this: injecting a client always behaved this way. What a zero-conf rung changes is visibility — when you build the client, the token is in your hands; when something builds it for you, nobody confronts it. Which is why the modules where it is true say so loudly.