awssecretcache

awssecretcache caches AWS Secrets Manager lookups with single-flight deduplication and a TTL, so secret retrieval does not become a per-request API call.

Part of nurago, a collection of independent Go packages for backend services.

import "github.com/tecnickcom/nurago/pkg/awssecretcache"

Wraps an aws-sdk-go-v2 Secrets Manager client in an sfcache, so repeated lookups of the same secret hit memory rather than the API.

cache, err := awssecretcache.New(
    ctx,
    128,             // maximum cached secrets
    5*time.Minute,   // TTL per entry
)

Three reasons the cache is not optional

Secrets Manager GetSecretValue is metered, rate limited, and on the network.

Cost. It is billed per API call. A service fetching a database password on every connection, or a pepper on every login, generates a continuous billable call rate for a value that changes monthly at most.

Rate limits. The API has a quota. A fleet scaling up, or a burst of traffic after a deployment, can exhaust it, and the failure is a throttling error on a path that needs the secret to function at all.

Latency. It is an HTTPS round trip to a regional endpoint, placed on whatever request needed the secret.

Single-flight matters most at startup and after expiry. Without it, every goroutine that needs the secret at the moment of a cache miss issues its own GetSecretValue, which is exactly the burst most likely to hit the rate limit. Here, one call is made and the rest wait for it.

TTL against rotation

The TTL is how quickly a rotated secret is picked up, and that is a real trade rather than a tuning knob.

Secrets Manager supports automatic rotation. During a rotation window both the old and new values are typically valid, and that overlap is what makes a cache workable at all. The TTL must be comfortably shorter than that window, or instances keep using a value that has stopped working and authentication failures appear scattered across the fleet with no obvious cause.

A five-minute TTL against a rotation that keeps the previous value valid for an hour is safe. A one-hour TTL against the same window is not.

The inherited sfcache semantics apply: only successful lookups are cached, errors are shared with coalesced callers but never cached, and the next call retries.

Size bounds the secrets, not the requests

The size limit caps how many distinct secrets are held. For most services that is a small fixed number known at design time, so set it generously; the entries are small and eviction here means an extra API call.

Configuration

WithEndpointMutable points the client at a specific endpoint, which covers a non-default region, a VPC endpoint, or a local test double such as LocalStack.

The AWS configuration comes from awsopt, so credentials, region and retry behaviour match the other AWS clients in the service.

Where the value goes next

A secret retrieved here is usually a key for something else: the pepper for passwordhash, the key for encrypt, a signing secret for jwt, or a DSN for sqlconn.

Two things follow. The cached value is plaintext in process memory, which is the normal situation for any service that uses a secret, and means process memory access is equivalent to secret access. And a secret reaching a log destroys the benefit entirely, so redact belongs on the logging path.

Dependencies

Reaches github.com/aws/aws-sdk-go-v2/service/secretsmanager, plus sfcache and awsopt.