feat: add AWS IAM authentication (IRSA) for Redis/ElastiCache sessions

Signed-off-by: Mathias Gebbe <mgebbe@hellmann.com>
This commit is contained in:
Mathias Gebbe 2026-03-27 00:19:43 +01:00
parent 96c9ec6986
commit 0d258e6a09
No known key found for this signature in database
GPG Key ID: 2A35E2EC75E5438F
9 changed files with 382 additions and 5 deletions

16
go.mod
View File

@ -7,12 +7,15 @@ require (
github.com/Bose/minisentinel v0.0.0-20200130220412-917c5a9223bb
github.com/a8m/envsubst v1.4.3
github.com/alicebob/miniredis/v2 v2.35.0
github.com/aws/aws-sdk-go-v2 v1.41.5
github.com/aws/aws-sdk-go-v2/config v1.32.13
github.com/bitly/go-simplejson v0.5.1
github.com/bsm/redislock v0.9.4
github.com/coreos/go-oidc/v3 v3.17.0
github.com/coreos/go-systemd v0.0.0-20191104093116-d3cd4ed1dbcf
github.com/fsnotify/fsnotify v1.9.0
github.com/go-jose/go-jose/v3 v3.0.4
github.com/go-jose/go-jose/v4 v4.1.3
github.com/go-viper/mapstructure/v2 v2.4.0
github.com/golang-jwt/jwt/v5 v5.3.0
github.com/google/go-cmp v0.7.0
@ -44,12 +47,23 @@ require (
cloud.google.com/go/auth v0.18.0 // indirect
cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect
github.com/Masterminds/semver/v3 v3.4.0 // indirect
github.com/aws/aws-sdk-go-v2/credentials v1.19.13 // indirect
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 // indirect
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 // indirect
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 // indirect
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 // indirect
github.com/aws/aws-sdk-go-v2/service/sso v1.30.14 // indirect
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18 // indirect
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 // indirect
github.com/aws/smithy-go v1.24.2 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.3.0 // indirect
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/felixge/httpsnoop v1.0.4 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect
github.com/go-logr/logr v1.4.3 // indirect
github.com/go-logr/stdr v1.2.2 // indirect
github.com/go-task/slim-sprig/v3 v3.0.0 // indirect

28
go.sum
View File

@ -16,6 +16,34 @@ github.com/alicebob/gopher-json v0.0.0-20180125190556-5a6b3ba71ee6/go.mod h1:SGn
github.com/alicebob/miniredis/v2 v2.11.1/go.mod h1:UA48pmi7aSazcGAvcdKcBB49z521IC9VjTTRz2nIaJE=
github.com/alicebob/miniredis/v2 v2.35.0 h1:QwLphYqCEAo1eu1TqPRN2jgVMPBweeQcR21jeqDCONI=
github.com/alicebob/miniredis/v2 v2.35.0/go.mod h1:TcL7YfarKPGDAthEtl5NBeHZfeUQj6OXMm/+iu5cLMM=
github.com/aws/aws-sdk-go-v2 v1.41.5 h1:dj5kopbwUsVUVFgO4Fi5BIT3t4WyqIDjGKCangnV/yY=
github.com/aws/aws-sdk-go-v2 v1.41.5/go.mod h1:mwsPRE8ceUUpiTgF7QmQIJ7lgsKUPQOUl3o72QBrE1o=
github.com/aws/aws-sdk-go-v2/config v1.32.13 h1:5KgbxMaS2coSWRrx9TX/QtWbqzgQkOdEa3sZPhBhCSg=
github.com/aws/aws-sdk-go-v2/config v1.32.13/go.mod h1:8zz7wedqtCbw5e9Mi2doEwDyEgHcEE9YOJp6a8jdSMY=
github.com/aws/aws-sdk-go-v2/credentials v1.19.13 h1:mA59E3fokBvyEGHKFdnpNNrvaR351cqiHgRg+JzOSRI=
github.com/aws/aws-sdk-go-v2/credentials v1.19.13/go.mod h1:yoTXOQKea18nrM69wGF9jBdG4WocSZA1h38A+t/MAsk=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21 h1:NUS3K4BTDArQqNu2ih7yeDLaS3bmHD0YndtA6UP884g=
github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.21/go.mod h1:YWNWJQNjKigKY1RHVJCuupeWDrrHjRqHm0N9rdrWzYI=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21 h1:Rgg6wvjjtX8bNHcvi9OnXWwcE0a2vGpbwmtICOsvcf4=
github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.21/go.mod h1:A/kJFst/nm//cyqonihbdpQZwiUhhzpqTsdbhDdRF9c=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21 h1:PEgGVtPoB6NTpPrBgqSE5hE/o47Ij9qk/SEZFbUOe9A=
github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.21/go.mod h1:p+hz+PRAYlY3zcpJhPwXlLC4C+kqn70WIHwnzAfs6ps=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6 h1:qYQ4pzQ2Oz6WpQ8T3HvGHnZydA72MnLuFK9tJwmrbHw=
github.com/aws/aws-sdk-go-v2/internal/ini v1.8.6/go.mod h1:O3h0IK87yXci+kg6flUKzJnWeziQUKciKrLjcatSNcY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7 h1:5EniKhLZe4xzL7a+fU3C2tfUN4nWIqlLesfrjkuPFTY=
github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.7/go.mod h1:x0nZssQ3qZSnIcePWLvcoFisRXJzcTVvYpAAdYX8+GI=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21 h1:c31//R3xgIJMSC8S6hEVq+38DcvUlgFY0FM6mSI5oto=
github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.21/go.mod h1:r6+pf23ouCB718FUxaqzZdbpYFyDtehyZcmP5KL9FkA=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9 h1:QKZH0S178gCmFEgst8hN0mCX1KxLgHBKKY/CLqwP8lg=
github.com/aws/aws-sdk-go-v2/service/signin v1.0.9/go.mod h1:7yuQJoT+OoH8aqIxw9vwF+8KpvLZ8AWmvmUWHsGQZvI=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.14 h1:GcLE9ba5ehAQma6wlopUesYg/hbcOhFNWTjELkiWkh4=
github.com/aws/aws-sdk-go-v2/service/sso v1.30.14/go.mod h1:WSvS1NLr7JaPunCXqpJnWk1Bjo7IxzZXrZi1QQCkuqM=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18 h1:mP49nTpfKtpXLt5SLn8Uv8z6W+03jYVoOSAl/c02nog=
github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.18/go.mod h1:YO8TrYtFdl5w/4vmjL8zaBSsiNp3w0L1FfKVKenZT7w=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10 h1:p8ogvvLugcR/zLBXTXrTkj0RYBUdErbMnAFFp12Lm/U=
github.com/aws/aws-sdk-go-v2/service/sts v1.41.10/go.mod h1:60dv0eZJfeVXfbT1tFJinbHrDfSJ2GZl4Q//OSSNAVw=
github.com/aws/smithy-go v1.24.2 h1:FzA3bu/nt/vDvmnkg+R8Xl46gmzEDam6mZ1hzmwXFng=
github.com/aws/smithy-go v1.24.2/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bitly/go-simplejson v0.5.1 h1:xgwPbetQScXt1gh9BmoJ6j9JMr3TElvuIyjR8pgdoow=

View File

@ -159,6 +159,11 @@ func NewFlagSet() *pflag.FlagSet {
flagSet.Bool("redis-use-cluster", false, "Connect to redis cluster. Must set --redis-cluster-connection-urls to use this feature")
flagSet.StringSlice("redis-cluster-connection-urls", []string{}, "List of Redis cluster connection URLs (eg redis://[USER[:PASSWORD]@]HOST[:PORT]). Used in conjunction with --redis-use-cluster")
flagSet.Int("redis-connection-idle-timeout", 0, "Redis connection idle timeout seconds, if Redis timeout option is non-zero, the --redis-connection-idle-timeout must be less then Redis timeout option")
flagSet.Bool("redis-use-iam-auth", false, "Use AWS IAM authentication for Redis/ElastiCache. Requires TLS and IRSA or other AWS credential source")
flagSet.String("redis-iam-user-id", "", "ElastiCache IAM-enabled user ID for AWS IAM authentication")
flagSet.String("redis-iam-replication-group-id", "", "ElastiCache replication group ID (cluster name) for AWS IAM authentication")
flagSet.String("redis-iam-region", "", "AWS region for ElastiCache IAM authentication. If empty, uses AWS_REGION or AWS_DEFAULT_REGION")
flagSet.Bool("redis-iam-serverless", false, "Set when using ElastiCache Serverless (adds ResourceType=ServerlessCache to the IAM auth token)")
flagSet.String("signature-key", "", "GAP-Signature request signature key (algorithm:secretkey)")
flagSet.Bool("gcp-healthchecks", false, "Enable GCP/GKE healthcheck endpoints")

View File

@ -34,6 +34,11 @@ type RedisStoreOptions struct {
CAPath string `flag:"redis-ca-path" cfg:"redis_ca_path"`
InsecureSkipTLSVerify bool `flag:"redis-insecure-skip-tls-verify" cfg:"redis_insecure_skip_tls_verify"`
IdleTimeout int `flag:"redis-connection-idle-timeout" cfg:"redis_connection_idle_timeout"`
UseIAMAuth bool `flag:"redis-use-iam-auth" cfg:"redis_use_iam_auth"`
IAMUserID string `flag:"redis-iam-user-id" cfg:"redis_iam_user_id"`
IAMReplicationGroupID string `flag:"redis-iam-replication-group-id" cfg:"redis_iam_replication_group_id"`
IAMRegion string `flag:"redis-iam-region" cfg:"redis_iam_region"`
IAMServerless bool `flag:"redis-iam-serverless" cfg:"redis_iam_serverless"`
}
func sessionOptionsDefaults() SessionOptions {

View File

@ -0,0 +1,69 @@
package redis
import (
"context"
"fmt"
"net/http"
"net/url"
"strconv"
"strings"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
v4 "github.com/aws/aws-sdk-go-v2/aws/signer/v4"
)
const (
elasticacheServiceName = "elasticache"
connectAction = "connect"
tokenExpirySeconds = 900 // 15 minutes
// SHA-256 hash of an empty string, required by SigV4 PresignHTTP.
emptyBodySHA256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
)
// iamTokenGenerator generates short-lived IAM auth tokens for ElastiCache
// by SigV4-presigning a request. Each call to Generate produces a fresh token.
type iamTokenGenerator struct {
userID string
replicationGroupID string
region string
serverless bool
credentials aws.CredentialsProvider
}
// Generate creates a new IAM auth token by SigV4 presigning a synthetic
// HTTP request. The returned string is used as the Redis AUTH password.
func (g *iamTokenGenerator) Generate(ctx context.Context) (string, error) {
u, err := url.Parse(fmt.Sprintf("http://%s/", g.replicationGroupID))
if err != nil {
return "", fmt.Errorf("failed to build IAM token URL: %w", err)
}
query := u.Query()
query.Set("Action", connectAction)
query.Set("User", g.userID)
if g.serverless {
query.Set("ResourceType", "ServerlessCache")
}
query.Set("X-Amz-Expires", strconv.Itoa(tokenExpirySeconds))
u.RawQuery = query.Encode()
req, err := http.NewRequestWithContext(ctx, http.MethodGet, u.String(), nil)
if err != nil {
return "", fmt.Errorf("failed to create IAM token request: %w", err)
}
creds, err := g.credentials.Retrieve(ctx)
if err != nil {
return "", fmt.Errorf("failed to retrieve AWS credentials: %w", err)
}
signer := v4.NewSigner()
uri, _, err := signer.PresignHTTP(ctx, creds, req, emptyBodySHA256, elasticacheServiceName, g.region, time.Now())
if err != nil {
return "", fmt.Errorf("failed to presign IAM token: %w", err)
}
// The auth token is the presigned URL without the "http://" prefix.
return strings.TrimPrefix(uri, "http://"), nil
}

View File

@ -0,0 +1,79 @@
package redis
import (
"context"
"strings"
"testing"
"time"
"github.com/aws/aws-sdk-go-v2/aws"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// staticCredentials implements aws.CredentialsProvider with fixed test values.
type staticCredentials struct{}
func (s staticCredentials) Retrieve(_ context.Context) (aws.Credentials, error) {
return aws.Credentials{
AccessKeyID: "AKIAIOSFODNN7EXAMPLE",
SecretAccessKey: "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
SessionToken: "test-session-token",
Source: "test",
CanExpire: true,
Expires: time.Now().Add(1 * time.Hour),
}, nil
}
func TestIAMTokenGenerator_Generate(t *testing.T) {
gen := &iamTokenGenerator{
userID: "my-user",
replicationGroupID: "my-cluster",
region: "us-east-1",
credentials: staticCredentials{},
}
token, err := gen.Generate(context.Background())
require.NoError(t, err)
// The token should not have the http:// prefix
assert.False(t, strings.HasPrefix(token, "http://"))
// The token must start with the replication group ID
assert.True(t, strings.HasPrefix(token, "my-cluster/"))
// The token must contain the required query parameters
assert.Contains(t, token, "Action=connect")
assert.Contains(t, token, "User=my-user")
assert.Contains(t, token, "X-Amz-Expires=900")
// Non-serverless tokens must NOT contain ResourceType
assert.NotContains(t, token, "ResourceType")
// SigV4 signature fields
assert.Contains(t, token, "X-Amz-Credential=AKIAIOSFODNN7EXAMPLE")
assert.Contains(t, token, "X-Amz-Signature=")
assert.Contains(t, token, "X-Amz-Security-Token=test-session-token")
assert.Contains(t, token, "X-Amz-SignedHeaders=host")
}
func TestIAMTokenGenerator_Generate_Serverless(t *testing.T) {
gen := &iamTokenGenerator{
userID: "my-user",
replicationGroupID: "my-cache",
region: "us-east-1",
serverless: true,
credentials: staticCredentials{},
}
token, err := gen.Generate(context.Background())
require.NoError(t, err)
assert.False(t, strings.HasPrefix(token, "http://"))
assert.True(t, strings.HasPrefix(token, "my-cache/"))
assert.Contains(t, token, "Action=connect")
assert.Contains(t, token, "User=my-user")
assert.Contains(t, token, "ResourceType=ServerlessCache")
assert.Contains(t, token, "X-Amz-Expires=900")
assert.Contains(t, token, "X-Amz-Signature=")
}

View File

@ -8,6 +8,7 @@ import (
"os"
"time"
awsconfig "github.com/aws/aws-sdk-go-v2/config"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/options"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/sessions"
"github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
@ -117,7 +118,7 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) {
return nil, err
}
client := redis.NewFailoverClient(&redis.FailoverOptions{
failoverOpts := &redis.FailoverOptions{
MasterName: opts.SentinelMasterName,
SentinelAddrs: addrs,
SentinelPassword: opts.SentinelPassword,
@ -125,7 +126,19 @@ func buildSentinelClient(opts options.RedisStoreOptions) (Client, error) {
Password: opt.Password,
TLSConfig: opt.TLSConfig,
ConnMaxIdleTime: opt.ConnMaxIdleTime,
})
}
if opts.UseIAMAuth {
credsFn, err := newIAMCredentialsProvider(opts)
if err != nil {
return nil, err
}
failoverOpts.Username = ""
failoverOpts.Password = ""
failoverOpts.CredentialsProviderContext = credsFn
}
client := redis.NewFailoverClient(failoverOpts)
return newClient(client), nil
}
@ -150,13 +163,25 @@ func buildClusterClient(opts options.RedisStoreOptions) (Client, error) {
return nil, err
}
client := redis.NewClusterClient(&redis.ClusterOptions{
clusterOpts := &redis.ClusterOptions{
Addrs: addrs,
Username: opt.Username,
Password: opt.Password,
TLSConfig: opt.TLSConfig,
ConnMaxIdleTime: opt.ConnMaxIdleTime,
})
}
if opts.UseIAMAuth {
credsFn, err := newIAMCredentialsProvider(opts)
if err != nil {
return nil, err
}
clusterOpts.Username = ""
clusterOpts.Password = ""
clusterOpts.CredentialsProviderContext = credsFn
}
client := redis.NewClusterClient(clusterOpts)
return newClusterClient(client), nil
}
@ -182,6 +207,16 @@ func buildStandaloneClient(opts options.RedisStoreOptions) (Client, error) {
return nil, err
}
if opts.UseIAMAuth {
credsFn, err := newIAMCredentialsProvider(opts)
if err != nil {
return nil, err
}
opt.Username = ""
opt.Password = ""
opt.CredentialsProviderContext = credsFn
}
client := redis.NewClient(opt)
return newClient(client), nil
}
@ -245,4 +280,34 @@ func parseRedisURLs(urls []string) ([]string, *redis.Options, error) {
return addrs, redisOptions, nil
}
// newIAMCredentialsProvider creates a go-redis CredentialsProviderContext
// that generates fresh IAM auth tokens on each new Redis connection.
func newIAMCredentialsProvider(opts options.RedisStoreOptions) (func(ctx context.Context) (string, string, error), error) {
var cfgOpts []func(*awsconfig.LoadOptions) error
if opts.IAMRegion != "" {
cfgOpts = append(cfgOpts, awsconfig.WithRegion(opts.IAMRegion))
}
cfg, err := awsconfig.LoadDefaultConfig(context.Background(), cfgOpts...)
if err != nil {
return nil, fmt.Errorf("failed to load AWS config for IAM Redis auth: %w", err)
}
gen := &iamTokenGenerator{
userID: opts.IAMUserID,
replicationGroupID: opts.IAMReplicationGroupID,
region: cfg.Region,
serverless: opts.IAMServerless,
credentials: cfg.Credentials,
}
return func(ctx context.Context) (string, string, error) {
token, err := gen.Generate(ctx)
if err != nil {
return "", "", err
}
return opts.IAMUserID, token, nil
}, nil
}
var _ persistence.Store = (*SessionStore)(nil)

View File

@ -39,6 +39,29 @@ func validateSessionCookieMinimal(o *options.Options) []string {
return msgs
}
// validateRedisIAMAuth checks that IAM auth options are consistent
func validateRedisIAMAuth(o *options.Options) []string {
r := o.Session.Redis
if !r.UseIAMAuth {
return []string{}
}
var msgs []string
if r.IAMUserID == "" {
msgs = append(msgs, "--redis-iam-user-id must be set when using --redis-use-iam-auth")
}
if r.IAMReplicationGroupID == "" {
msgs = append(msgs, "--redis-iam-replication-group-id must be set when using --redis-use-iam-auth")
}
if r.Password != "" {
msgs = append(msgs, "--redis-password and --redis-use-iam-auth are mutually exclusive")
}
if r.Username != "" {
msgs = append(msgs, "--redis-username and --redis-use-iam-auth are mutually exclusive; IAM auth provides its own username")
}
return msgs
}
// validateRedisSessionStore builds a Redis Client from the options and
// attempts to connect, Set, Get and Del a random health check key
func validateRedisSessionStore(o *options.Options) []string {
@ -46,6 +69,10 @@ func validateRedisSessionStore(o *options.Options) []string {
return []string{}
}
if msgs := validateRedisIAMAuth(o); len(msgs) > 0 {
return msgs
}
client, err := redis.NewRedisClient(o.Session.Redis)
if err != nil {
return []string{fmt.Sprintf("unable to initialize a redis client: %v", err)}

View File

@ -209,6 +209,91 @@ var _ = Describe("Sessions", func() {
errStrings []string
}
type iamAuthTableInput struct {
opts *options.Options
errStrings []string
}
DescribeTable("validateRedisIAMAuth",
func(o *iamAuthTableInput) {
Expect(validateRedisIAMAuth(o.opts)).To(ConsistOf(o.errStrings))
},
Entry("IAM auth disabled is valid", &iamAuthTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
},
},
errStrings: []string{},
}),
Entry("IAM auth missing user ID", &iamAuthTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseIAMAuth: true,
IAMReplicationGroupID: "my-cluster",
},
},
},
errStrings: []string{"--redis-iam-user-id must be set when using --redis-use-iam-auth"},
}),
Entry("IAM auth missing replication group ID", &iamAuthTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseIAMAuth: true,
IAMUserID: "my-user",
},
},
},
errStrings: []string{"--redis-iam-replication-group-id must be set when using --redis-use-iam-auth"},
}),
Entry("IAM auth conflicts with username", &iamAuthTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseIAMAuth: true,
IAMUserID: "my-user",
IAMReplicationGroupID: "my-cluster",
Username: "some-user",
},
},
},
errStrings: []string{"--redis-username and --redis-use-iam-auth are mutually exclusive; IAM auth provides its own username"},
}),
Entry("IAM auth conflicts with password", &iamAuthTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseIAMAuth: true,
IAMUserID: "my-user",
IAMReplicationGroupID: "my-cluster",
Password: "secret",
},
},
},
errStrings: []string{"--redis-password and --redis-use-iam-auth are mutually exclusive"},
}),
Entry("IAM auth valid config", &iamAuthTableInput{
opts: &options.Options{
Session: options.SessionOptions{
Type: options.RedisSessionStoreType,
Redis: options.RedisStoreOptions{
UseIAMAuth: true,
IAMUserID: "my-user",
IAMReplicationGroupID: "my-cluster",
IAMRegion: "us-east-1",
},
},
},
errStrings: []string{},
}),
)
DescribeTable("validateRedisSessionStore",
func(o *redisStoreTableInput) {
mr, err := miniredis.Run()