Holding credentials is a posture¶
This module offers two ambient rungs that do the same work and differ only in what they keep:
awsclient.Ambient(...) // resolve once, share the result
awsclient.PerCall(...) // resolve afresh every time, retain nothing
PerCall is not a degraded Ambient, and it is not there for symmetry. It
exists because how long a component holds a credential is a security decision
that belongs to the component, not to this module.
The case that forced it¶
go/signing-aws-kms resolves AWS configuration inside every NewSigner call,
and says why in its own comment: there is exactly one Sign per mint, so
resolving lazily costs nothing measurable, and doing so avoids holding AWS
credentials longer than the operation requires.
Had this module offered only a memoising rung, that component would have faced a choice between keeping its own duplicate resolution logic — defeating the point of a shared module — or changing its security posture to adopt ours. Neither is acceptable, so the non-memoising rung is part of the contract.
Both return the same Source interface, so swapping posture is one word at the
construction site and nothing else changes.
Concurrent PerCall calls are deliberately not collapsed¶
Ambient collapses a concurrent wave into a single attempt. PerCall does not —
it resolves separately for each caller.
That looks wasteful until you state what collapsing would mean: sharing one resolution between callers who explicitly asked not to share one. The saving would come at the cost of the property the rung exists to provide.
Why isolation is the default¶
Two components that each take their ambient default resolve the chain twice. That is intended.
A hidden process-wide cache would save the duplication and cost more than it saves: it would silently share credentials between components that may deliberately differ — a different profile, an assumed role, a distinct endpoint — and it would make one component's transient failure everybody's, invisibly.
So sharing is something you do on purpose, by building one source and injecting it. That is what this module is for.
The footprint boundary¶
Resolving an ambient AWS credential chain is not free in dependency terms. On the
config adapters it measures at +7 to +10 modules — credentials, sso,
ssooidc, sts, imds and their transitive graph.
That cost is real, and it is why importing this module is an explicit act rather
than something an adapter does on your behalf. The AWS-backed config adapters put
their ambient rung behind an ambient subpackage precisely so that a consumer
who never wants ambient credentials never pays for them, and each subpackage
carries a test stating its own cost.
The rule the estate follows: an adapter's root package must not grow the
credential graph. If you never import the ambient subpackage, your dependency
footprint is exactly what it was before the ladder existed.
What refreshes, and what does not¶
An aws.Config from the default chain wraps its provider in
aws.NewCredentialsCache, so credentials renew underneath you. A long-lived
process holding one from Ambient does not go stale.
That is a property of AWS, not of this module, and it is not universal — a Vault
or Consul token does not renew itself. Where a value cannot renew,
clientlifecycle offers an
invalidatable strategy; AWS does not need it, and adopting it here would be
strictly worse.
Where this is specified¶
org spec 0003 — isolation as the default (P-5), the non-memoised rung (P-12), and the import boundary where an ambient rung would enlarge a graph (P-7).