Documentation
¶
Overview ¶
Package redis provides a shared Redis client connection layer used by toolhive components and stacklok-llm-gateway services.
The package wraps github.com/redis/go-redis/v9 with a single Config type and NewClient factory that supports three connection modes:
- Standalone — a single endpoint (Addr).
- Cluster — Redis Cluster protocol against a single seed Addr.
- Sentinel — high-availability failover via SentinelConfig.
The returned client is a goredis.UniversalClient so callers can write mode-agnostic code.
Connection Modes ¶
Standalone:
cli, err := redis.NewClient(ctx, &redis.Config{
Addr: "redis.example.com:6379",
Password: "...",
DB: 0,
})
Cluster:
cli, err := redis.NewClient(ctx, &redis.Config{
Addr: "cluster.example.com:6379",
ClusterMode: true,
Username: "app",
Password: "...",
})
Sentinel:
cli, err := redis.NewClient(ctx, &redis.Config{
SentinelConfig: &redis.SentinelConfig{
MasterName: "mymaster",
SentinelAddrs: []string{"sentinel-0:26379", "sentinel-1:26379"},
},
Password: "...",
})
TLS ¶
TLS is opt-in per connection target. When TLS is set, master/cluster connections use it. SentinelTLS, when set, applies to sentinel daemon connections independently — useful when the master and sentinels present different certificate chains. Both fields accept either system CAs (CACert nil) or a custom CA bundle. To use mTLS, set ClientCert and ClientKey to a PEM-encoded client certificate/key pair; the master and sentinel connections can use different pairs.
Defaults and Validation ¶
NewClient applies DefaultDialTimeout, DefaultReadTimeout, and DefaultWriteTimeout when the corresponding Config fields are zero, then validates connection-mode topology (Addr XOR SentinelConfig, ClusterMode requires Addr, Sentinel requires MasterName plus at least one address). It verifies the connection with a Ping before returning. Caller-specific validation (key-prefix requirements, ACL enforcement) remains the caller's responsibility.
Dynamic Authentication ¶
Config.DynamicAuth mints short-lived AUTH credentials from a cloud IAM backend instead of using a static Password:
- AWSElastiCacheIAM — ElastiCache/MemoryDB IAM authentication tokens, hand-signed with SigV4 (there is no RDS-style auth.BuildAuthToken helper for these services). Supports ElastiCache/MemoryCache Serverless via ResourceType.
- AzureAD — Entra ID (formerly Azure AD) access tokens for Azure Cache for Redis.
- GCPMemorystoreIAM — GCP OAuth2 access tokens for Memorystore for Redis Cluster IAM authentication. This backend authenticates token-only (AUTH <token>, no username) — Config.Username must be left empty.
Dynamic authentication requires a verified TLS connection (Config.TLS set, with InsecureSkipVerify false): these are bearer credentials, and sending them over an unverified or plaintext connection lets a network attacker capture and replay them. Set DynamicAuthConfig.AllowInsecureTransport to opt out for trusted local tunneling.
Unlike pgx's BeforeConnect hook, go-redis has no per-dial hook that runs before its own HELLO/AUTH handshake, and pooled connections are long-lived rather than reconnecting per operation. NewClient works around the first problem by wiring Options.CredentialsProviderContext, which go-redis resolves during that handshake — before RESP3 negotiation and DB selection — rather than OnConnect, which only fires afterward (breaking non-zero DB selection and silently downgrading otherwise RESP3-capable connections to RESP2). It works around the second by setting ConnMaxLifetime (defaulted per backend, inside the token's TTL, when Config.ConnMaxLifetime is zero) so go-redis periodically retires and redials pooled connections — re-running CredentialsProviderContext and picking up current credentials before the previous token would be rejected.
For Sentinel, CredentialsProviderContext is wired only onto the data-node (master/replica) connections: go-redis's FailoverOptions deliberately does not propagate it to the internal Sentinel-daemon connections it builds (those authenticate with SentinelUsername/SentinelPassword instead), so the cloud data-node identity never reaches the Sentinel daemons.
cli, err := redis.NewClient(ctx, &redis.Config{
Addr: "my-cluster.abcdef.ng.0001.use1.cache.amazonaws.com:6379",
Username: "app-iam-user",
TLS: &redis.TLSConfig{},
DynamicAuth: &redis.DynamicAuthConfig{
AWSElastiCacheIAM: &redis.DynamicAuthAWSElastiCacheIAM{
Region: "us-east-1",
ClusterName: "my-cluster",
},
},
})
Whether Config.Username is required depends on the backend: AWS ElastiCache/ MemoryDB IAM and Azure Entra ID require it (the IAM user / principal object ID that minted tokens authenticate as); GCP Memorystore IAM authentication rejects a username. Config.Password must always be empty when DynamicAuth is set.
Index ¶
- Constants
- func BuildTLSConfig(cfg *TLSConfig) (*tls.Config, error)
- func NewAuthToken(ctx context.Context, cfg *Config) (username, password string, err error)
- func NewClient(ctx context.Context, cfg *Config) (goredis.UniversalClient, error)
- type Config
- type CredentialsFunc
- type DynamicAuthAWSElastiCacheIAM
- type DynamicAuthAzureAD
- type DynamicAuthConfig
- type DynamicAuthGCPMemorystoreIAM
- type SentinelConfig
- type TLSConfig
Constants ¶
const ( DefaultDialTimeout = 5 * time.Second DefaultReadTimeout = 3 * time.Second DefaultWriteTimeout = 3 * time.Second )
Default timeouts applied by NewClient when the corresponding Config field is zero.
const DefaultAWSElastiCacheIAMTokenTTL = 12 * time.Minute
DefaultAWSElastiCacheIAMTokenTTL bounds go-redis's ConnMaxLifetime for ElastiCache/MemoryDB IAM auth so pooled connections are retired, and redialed with a fresh token, well before the previous token's 15-minute server-side ceiling.
const DefaultAzureADTokenTTL = 45 * time.Minute
DefaultAzureADTokenTTL bounds go-redis's ConnMaxLifetime for Azure Entra ID auth. Entra ID access tokens are typically valid ~60-90 minutes; pooled connections are retired well inside that window.
const DefaultGCPMemorystoreIAMTokenTTL = 45 * time.Minute
DefaultGCPMemorystoreIAMTokenTTL bounds go-redis's ConnMaxLifetime for GCP Memorystore IAM auth. GCP OAuth2 access tokens are typically valid ~60 minutes; pooled connections are retired well inside that window.
Variables ¶
This section is empty.
Functions ¶
func BuildTLSConfig ¶
BuildTLSConfig converts a TLSConfig into a *tls.Config suitable for dialing a Redis endpoint. Returns (nil, nil) when cfg is nil, signalling "no TLS". Returns an error when the CA certificate or client certificate and key cannot be parsed, or when only one of ClientCert and ClientKey is set.
The returned *tls.Config sets MinVersion to TLS 1.2 and uses the system root CAs unless CACert is supplied. When ClientCert and ClientKey are set, it presents them to the server for mutual TLS.
func NewAuthToken ¶ added in v0.0.43
NewAuthToken returns the username and short-lived password minted by the dynamic-auth backend configured in cfg.DynamicAuth. When DynamicAuth is nil, both return values are empty and no error is raised — this lets callers fall back to a static Username/Password.
This entry point is for callers that mint credentials outside NewClient (for example, a standalone health-check tool). NewClient does not call it; NewClient resolves a CredentialsFunc once and re-invokes it on every connection attempt so the credentials used stay current across reconnects.
func NewClient ¶
NewClient constructs a Redis client according to cfg. The returned client is a goredis.UniversalClient so callers can remain mode-agnostic. NewClient applies timeout defaults, validates connection-mode topology, builds the appropriate underlying client (standalone, cluster, or sentinel), and verifies connectivity with a Ping before returning. On Ping failure the underlying client is closed and the error is returned.
cfg is copied internally before defaults are applied; the caller's Config is not mutated.
Types ¶
type Config ¶
type Config struct {
// Addr is the Redis server address (host:port) for standalone or cluster
// modes. Mutually exclusive with SentinelConfig.
Addr string
// ClusterMode enables the Redis Cluster protocol. Requires Addr. Cluster
// mode ignores DB because Redis Cluster only supports database 0.
ClusterMode bool
// SentinelConfig activates Sentinel failover mode. Mutually exclusive
// with Addr.
SentinelConfig *SentinelConfig
// Username is the optional ACL username (Redis 6.0+). When empty, auth
// falls back to legacy AUTH using only Password.
//
// When DynamicAuth is set, whether Username is required depends on the
// backend: AWS ElastiCache/MemoryDB IAM and Azure Entra ID require it (the
// IAM user / principal object ID that minted tokens authenticate as); GCP
// Memorystore IAM authentication is token-only and rejects a username, so
// Username must be left empty for that backend.
Username string
// Password is the AUTH/ACL password. May be empty when the server does
// not require authentication. Mutually exclusive with DynamicAuth.
Password string //nolint:gosec // G101: field name, not a hardcoded credential
// DynamicAuth, when non-nil, mints short-lived AUTH credentials from a
// cloud IAM backend instead of using a static Password. NewClient
// installs an Options.CredentialsProviderContext hook that resolves
// fresh credentials for each connection attempt — during go-redis's
// handshake, before RESP3 negotiation and DB selection — and sets
// ConnMaxLifetime (when Config's own ConnMaxLifetime is zero) to a value
// inside the backend's token TTL so pooled connections are periodically
// retired and redialed with current credentials.
//
// Dynamic authentication requires a verified TLS connection (Config.TLS
// set, with InsecureSkipVerify false): cloud IAM tokens are bearer
// credentials, and sending them over an unverified or plaintext
// connection lets a network attacker capture and replay them. Set
// DynamicAuthConfig.AllowInsecureTransport to opt out for trusted local
// tunneling (for example, a sidecar-terminated mTLS tunnel where this
// package's own TLS handshake would be redundant).
DynamicAuth *DynamicAuthConfig
// DB is the Redis database index. Applies to standalone and sentinel
// modes; ignored in cluster mode.
DB int
// DialTimeout is the timeout for establishing a connection. When zero,
// DefaultDialTimeout is used.
DialTimeout time.Duration
// ReadTimeout is the timeout for socket reads. When zero,
// DefaultReadTimeout is used.
ReadTimeout time.Duration
// WriteTimeout is the timeout for socket writes. When zero,
// DefaultWriteTimeout is used.
WriteTimeout time.Duration
// TLS configures TLS for master/cluster connections. When nil, those
// connections are plaintext.
TLS *TLSConfig
// SentinelTLS configures TLS for sentinel daemon connections. Only
// applies when SentinelConfig is set. When nil, sentinel connections are
// plaintext (independent of TLS).
SentinelTLS *TLSConfig
// ConnMaxLifetime is the maximum amount of time a connection may be
// reused before go-redis retires and redials it. When zero and
// DynamicAuth is set, a backend-specific default inside the token's TTL
// is used instead of go-redis's own default. Ignored (left at go-redis's
// default) when DynamicAuth is nil and this is zero.
ConnMaxLifetime time.Duration
}
Config configures a Redis client. Exactly one of Addr or SentinelConfig must be set. ClusterMode upgrades an Addr-based config to the Redis Cluster protocol.
type CredentialsFunc ¶ added in v0.0.43
CredentialsFunc resolves the username/password to authenticate a dynamic-auth-enabled connection with. It is wired into go-redis's Options.CredentialsProviderContext, which go-redis calls during the HELLO/AUTH handshake inside initConn — before RESP3 negotiation and DB selection happen. An OnConnect hook is unsuitable for this: go-redis only invokes OnConnect after HELLO/AUTH and any SELECT have already completed, which would silently downgrade otherwise RESP3-capable servers to RESP2 and break selection of a non-zero database when Password is left empty for the hook to fill in later.
type DynamicAuthAWSElastiCacheIAM ¶ added in v0.0.43
type DynamicAuthAWSElastiCacheIAM struct {
// Region is the AWS region used to sign IAM tokens. Use "detect" to
// auto-discover the region from the EC2 instance metadata service (IMDS).
Region string
// ClusterName is the ElastiCache replication group ID / cache name, or
// the MemoryDB cluster name, that the presigned token is scoped to.
ClusterName string
// ServiceName is the SigV4 signing service name. Must be empty (the
// default, treated as "elasticache") or "memorydb".
ServiceName string
// ResourceType selects the AWS-required resource-type query parameter
// for serverless caches. Must be empty (the default, for provisioned
// ElastiCache/MemoryDB clusters) or "ServerlessCache" (for ElastiCache
// Serverless / MemoryDB Serverless).
ResourceType string
}
DynamicAuthAWSElastiCacheIAM configures AWS ElastiCache/MemoryDB IAM dynamic authentication.
type DynamicAuthAzureAD ¶ added in v0.0.43
type DynamicAuthAzureAD struct{}
DynamicAuthAzureAD configures Azure Entra ID (formerly Azure AD) authentication for Azure Cache for Redis. It has no fields: the token is minted from DefaultAzureCredential's normal resolution order (environment variables — including AZURE_CLIENT_ID to select a user-assigned managed identity — workload identity, system-assigned managed identity, Azure CLI, ...).
type DynamicAuthConfig ¶ added in v0.0.43
type DynamicAuthConfig struct {
// AWSElastiCacheIAM enables AWS ElastiCache/MemoryDB IAM authentication
// tokens.
AWSElastiCacheIAM *DynamicAuthAWSElastiCacheIAM
// AzureAD enables Azure Entra ID (formerly Azure AD) authentication
// tokens for Azure Cache for Redis.
AzureAD *DynamicAuthAzureAD
// GCPMemorystoreIAM enables GCP Memorystore for Redis Cluster IAM
// authentication tokens.
GCPMemorystoreIAM *DynamicAuthGCPMemorystoreIAM
// AllowInsecureTransport opts out of the requirement that Config.TLS be
// set (with verification enabled) when DynamicAuth is configured. Leave
// false unless a trusted local tunnel already provides transport
// security outside this package's own TLS handling.
AllowInsecureTransport bool
}
DynamicAuthConfig selects a dynamic-authentication backend. Exactly one backend field must be non-nil when DynamicAuthConfig itself is non-nil.
type DynamicAuthGCPMemorystoreIAM ¶ added in v0.0.43
type DynamicAuthGCPMemorystoreIAM struct{}
DynamicAuthGCPMemorystoreIAM configures GCP Memorystore for Redis Cluster IAM authentication. It has no fields: the token is minted from ambient Application Default Credentials, scoped for Memorystore IAM auth. Authentication is token-only (AUTH <token>) — Config.Username must be empty for this backend; Memorystore does not accept a username alongside the token.
type SentinelConfig ¶
type SentinelConfig struct {
// MasterName is the logical name of the monitored master, as configured
// on the sentinel daemons.
MasterName string
// SentinelAddrs is the list of sentinel daemon addresses (host:port).
SentinelAddrs []string
}
SentinelConfig describes a Redis Sentinel deployment used to discover the current master.
type TLSConfig ¶
type TLSConfig struct {
// InsecureSkipVerify disables certificate verification. Intended for
// self-signed development setups; never use in production.
InsecureSkipVerify bool
// CACert is the PEM-encoded CA bundle used to verify the server. When
// nil, system root CAs are used.
CACert []byte
// ClientCert and ClientKey are a PEM-encoded client certificate/key pair
// presented to the server for mutual TLS. Both fields must be set together.
ClientCert []byte
ClientKey []byte
}
TLSConfig describes how to verify a TLS-enabled Redis (or sentinel) endpoint. The mere presence of a TLSConfig enables TLS; the zero value means "verify against system CAs with hostname verification".