Skip to content

Commit 754ae79

Browse files
committed
fix(redis): support sentinel credentials
1 parent 5e1deb6 commit 754ae79

11 files changed

Lines changed: 97 additions & 34 deletions

File tree

configs/mcp-gateway.yaml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,8 @@ notifier:
9090
master_name: "${NOTIFIER_REDIS_MASTER_NAME:}" # MasterName is the sentinel master name.
9191
username: "${NOTIFIER_REDIS_USERNAME:default}"
9292
password: "${NOTIFIER_REDIS_PASSWORD:}"
93+
sentinel_username: "${NOTIFIER_REDIS_SENTINEL_USERNAME:}"
94+
sentinel_password: "${NOTIFIER_REDIS_SENTINEL_PASSWORD:}"
9395
db: ${NOTIFIER_REDIS_DB:0}
9496
topic: "${NOTIFIER_REDIS_TOPIC:mcp-gateway:reload}"
9597

@@ -102,6 +104,8 @@ session:
102104
master_name: "${SESSION_REDIS_MASTER_NAME:}" # MasterName is the sentinel master name.
103105
username: "${SESSION_REDIS_USERNAME:default}"
104106
password: "${SESSION_REDIS_PASSWORD:}"
107+
sentinel_username: "${SESSION_REDIS_SENTINEL_USERNAME:}"
108+
sentinel_password: "${SESSION_REDIS_SENTINEL_PASSWORD:}"
105109
db: ${SESSION_REDIS_DB:0}
106110
topic: "${SESSION_REDIS_TOPIC:mcp-gateway:session}"
107111
prefix: "${SESSION_REDIS_PREFIX:session}"
@@ -119,6 +123,8 @@ auth:
119123
master_name: "${OAUTH2_REDIS_MASTER_NAME:}" # MasterName is the sentinel master name.
120124
username: "${OAUTH2_REDIS_USERNAME:default}"
121125
password: "${OAUTH2_REDIS_PASSWORD:}"
126+
sentinel_username: "${OAUTH2_REDIS_SENTINEL_USERNAME:}"
127+
sentinel_password: "${OAUTH2_REDIS_SENTINEL_PASSWORD:}"
122128
db: ${OAUTH2_REDIS_DB:0}
123129
cors:
124130
allowOrigins:

internal/auth/storage/factory.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ func NewStore(logger *zap.Logger, cfg *config.OAuth2StorageConfig) (Store, error
1515
case "memory":
1616
return NewMemoryStorage(), nil
1717
case "redis":
18-
return NewRedisStorage(cfg.Redis.ClusterType, cfg.Redis.Addr, cfg.Redis.MasterName, cfg.Redis.Username, cfg.Redis.Password, cfg.Redis.DB)
18+
return NewRedisStorage(cfg.Redis.ClusterType, cfg.Redis.Addr, cfg.Redis.MasterName, cfg.Redis.Username, cfg.Redis.Password, cfg.Redis.SentinelUsername, cfg.Redis.SentinelPassword, cfg.Redis.DB)
1919
default:
2020
return nil, fmt.Errorf("unsupported auth storage type: %s", cfg.Type)
2121
}

internal/auth/storage/redis.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ type RedisStorage struct {
1919
}
2020

2121
// NewRedisStorage creates a new Redis storage instance
22-
func NewRedisStorage(clusterType, addr, masterName string, username, password string, db int) (*RedisStorage, error) {
22+
func NewRedisStorage(clusterType, addr, masterName string, username, password, sentinelUsername, sentinelPassword string, db int) (*RedisStorage, error) {
2323
addrs := utils.SplitByMultipleDelimiters(addr, ";", ",")
2424
redisOptions := &redis.UniversalOptions{
2525
Addrs: addrs,
@@ -28,6 +28,8 @@ func NewRedisStorage(clusterType, addr, masterName string, username, password st
2828
}
2929
if clusterType == cnst.RedisClusterTypeSentinel {
3030
redisOptions.MasterName = masterName
31+
redisOptions.SentinelUsername = sentinelUsername
32+
redisOptions.SentinelPassword = sentinelPassword
3133
}
3234
if clusterType != cnst.RedisClusterTypeCluster {
3335
// can not set db in cluster mode

internal/auth/storage/redis_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ func newTestRedisStorage(t *testing.T) (*RedisStorage, *miniredis.Miniredis) {
1717
if err != nil {
1818
t.Fatalf("failed to start miniredis: %v", err)
1919
}
20-
s, err := NewRedisStorage(cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", 0)
20+
s, err := NewRedisStorage(cnst.RedisClusterTypeSingle, mr.Addr(), "", "", "", "", "", 0)
2121
if err != nil {
2222
mr.Close()
2323
t.Fatalf("failed to create RedisStorage: %v", err)
@@ -101,7 +101,7 @@ func TestRedisStorage_Token_Flow(t *testing.T) {
101101

102102
func TestNewRedisStorage_ConnectionError(t *testing.T) {
103103
// invalid address should fail to ping
104-
s, err := NewRedisStorage(cnst.RedisClusterTypeSingle, "127.0.0.1:0", "", "", "", 0)
104+
s, err := NewRedisStorage(cnst.RedisClusterTypeSingle, "127.0.0.1:0", "", "", "", "", "", 0)
105105
assert.Nil(t, s)
106106
assert.Error(t, err)
107107
}

internal/common/config/config.go

Lines changed: 19 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -80,15 +80,17 @@ type (
8080

8181
// SessionRedisConfig represents the Redis configuration for session storage
8282
SessionRedisConfig struct {
83-
ClusterType string `yaml:"cluster_type"` // "single", "cluster" or "sentinel"
84-
Addr string `yaml:"addr"` // multiple addresses separated by ;.
85-
MasterName string `yaml:"master_name"` // MasterName is the sentinel master name.
86-
Username string `yaml:"username"`
87-
Password string `yaml:"password"`
88-
DB int `yaml:"db"`
89-
Topic string `yaml:"topic"`
90-
Prefix string `yaml:"prefix"`
91-
TTL time.Duration `yaml:"ttl"` // TTL for session data in Redis
83+
ClusterType string `yaml:"cluster_type"` // "single", "cluster" or "sentinel"
84+
Addr string `yaml:"addr"` // multiple addresses separated by ;.
85+
MasterName string `yaml:"master_name"` // MasterName is the sentinel master name.
86+
Username string `yaml:"username"`
87+
Password string `yaml:"password"`
88+
SentinelUsername string `yaml:"sentinel_username"`
89+
SentinelPassword string `yaml:"sentinel_password"`
90+
DB int `yaml:"db"`
91+
Topic string `yaml:"topic"`
92+
Prefix string `yaml:"prefix"`
93+
TTL time.Duration `yaml:"ttl"` // TTL for session data in Redis
9294
}
9395

9496
// LoggerConfig represents the logger configuration
@@ -123,12 +125,14 @@ type (
123125
Redis OAuth2RedisConfig `yaml:"redis"`
124126
}
125127
OAuth2RedisConfig struct {
126-
ClusterType string `yaml:"cluster_type"` // "single", "cluster" or "sentinel"
127-
Addr string `yaml:"addr"`
128-
MasterName string `yaml:"master_name"` // MasterName is the sentinel master name.
129-
Username string `yaml:"username"`
130-
Password string `yaml:"password"`
131-
DB int `yaml:"db"`
128+
ClusterType string `yaml:"cluster_type"` // "single", "cluster" or "sentinel"
129+
Addr string `yaml:"addr"`
130+
MasterName string `yaml:"master_name"` // MasterName is the sentinel master name.
131+
Username string `yaml:"username"`
132+
Password string `yaml:"password"`
133+
SentinelUsername string `yaml:"sentinel_username"`
134+
SentinelPassword string `yaml:"sentinel_password"`
135+
DB int `yaml:"db"`
132136
}
133137

134138
// GoogleOAuthConfig defines Google OAuth configuration

internal/common/config/config_test.go

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,3 +76,48 @@ tool_access:
7676
assert.NoError(t, err)
7777
assert.Equal(t, []string{"127.0.0.1/32", "localhost", "::1/128"}, []string(cfg.ToolAccess.InternalNetwork.Allowlist))
7878
}
79+
80+
func TestLoadConfig_MCPGateway_RedisSentinelCredentials(t *testing.T) {
81+
tmp := t.TempDir()
82+
old, _ := os.Getwd()
83+
t.Cleanup(func() { _ = os.Chdir(old) })
84+
_ = os.Chdir(tmp)
85+
86+
t.Setenv("NOTIFIER_REDIS_SENTINEL_USERNAME", "sentinel-notifier")
87+
t.Setenv("NOTIFIER_REDIS_SENTINEL_PASSWORD", "notifier-pass")
88+
t.Setenv("SESSION_REDIS_SENTINEL_USERNAME", "sentinel-session")
89+
t.Setenv("SESSION_REDIS_SENTINEL_PASSWORD", "session-pass")
90+
t.Setenv("OAUTH2_REDIS_SENTINEL_USERNAME", "sentinel-oauth")
91+
t.Setenv("OAUTH2_REDIS_SENTINEL_PASSWORD", "oauth-pass")
92+
93+
yaml := `
94+
notifier:
95+
redis:
96+
sentinel_username: "${NOTIFIER_REDIS_SENTINEL_USERNAME:}"
97+
sentinel_password: "${NOTIFIER_REDIS_SENTINEL_PASSWORD:}"
98+
session:
99+
redis:
100+
sentinel_username: "${SESSION_REDIS_SENTINEL_USERNAME:}"
101+
sentinel_password: "${SESSION_REDIS_SENTINEL_PASSWORD:}"
102+
auth:
103+
oauth2:
104+
storage:
105+
redis:
106+
sentinel_username: "${OAUTH2_REDIS_SENTINEL_USERNAME:}"
107+
sentinel_password: "${OAUTH2_REDIS_SENTINEL_PASSWORD:}"
108+
`
109+
file := filepath.Join(tmp, "mcp-gateway.yaml")
110+
assert.NoError(t, os.WriteFile(file, []byte(yaml), 0o644))
111+
112+
cfg, _, err := LoadConfig[MCPGatewayConfig]("mcp-gateway.yaml")
113+
assert.NoError(t, err)
114+
if err != nil {
115+
return
116+
}
117+
assert.Equal(t, "sentinel-notifier", cfg.Notifier.Redis.SentinelUsername)
118+
assert.Equal(t, "notifier-pass", cfg.Notifier.Redis.SentinelPassword)
119+
assert.Equal(t, "sentinel-session", cfg.Session.Redis.SentinelUsername)
120+
assert.Equal(t, "session-pass", cfg.Session.Redis.SentinelPassword)
121+
assert.Equal(t, "sentinel-oauth", cfg.Auth.OAuth2.Storage.Redis.SentinelUsername)
122+
assert.Equal(t, "oauth-pass", cfg.Auth.OAuth2.Storage.Redis.SentinelPassword)
123+
}

internal/common/config/notifier.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,15 @@ type (
2424

2525
// RedisConfig represents the configuration for Redis-based notifier
2626
RedisConfig struct {
27-
ClusterType string `yaml:"cluster_type"` // "single", "cluster" or "sentinel"
28-
Addr string `yaml:"addr"`
29-
MasterName string `yaml:"master_name"` // MasterName is the sentinel master name.
30-
Username string `yaml:"username"`
31-
Password string `yaml:"password"`
32-
DB int `yaml:"db"`
33-
Topic string `yaml:"topic"`
27+
ClusterType string `yaml:"cluster_type"` // "single", "cluster" or "sentinel"
28+
Addr string `yaml:"addr"`
29+
MasterName string `yaml:"master_name"` // MasterName is the sentinel master name.
30+
Username string `yaml:"username"`
31+
Password string `yaml:"password"`
32+
SentinelUsername string `yaml:"sentinel_username"`
33+
SentinelPassword string `yaml:"sentinel_password"`
34+
DB int `yaml:"db"`
35+
Topic string `yaml:"topic"`
3436
}
3537
)
3638

internal/mcp/session/redis.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ func NewRedisStore(ctx context.Context, logger *zap.Logger, cfg config.SessionRe
4141
}
4242
if cfg.ClusterType == cnst.RedisClusterTypeSentinel {
4343
redisOptions.MasterName = cfg.MasterName
44+
redisOptions.SentinelUsername = cfg.SentinelUsername
45+
redisOptions.SentinelPassword = cfg.SentinelPassword
4446
}
4547
if cfg.ClusterType != cnst.RedisClusterTypeCluster {
4648
// can not set db in cluster mode

internal/mcp/storage/notifier/factory.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ func NewNotifier(ctx context.Context, logger *zap.Logger, cfg *config.NotifierCo
3636
case TypeAPI:
3737
return NewAPINotifier(logger, cfg.API.Port, role, cfg.API.TargetURL), nil
3838
case TypeRedis:
39-
return NewRedisNotifier(logger, cfg.Redis.ClusterType, cfg.Redis.Addr, cfg.Redis.MasterName, cfg.Redis.Username, cfg.Redis.Password, cfg.Redis.DB, cfg.Redis.Topic, role)
39+
return NewRedisNotifier(logger, cfg.Redis.ClusterType, cfg.Redis.Addr, cfg.Redis.MasterName, cfg.Redis.Username, cfg.Redis.Password, cfg.Redis.SentinelUsername, cfg.Redis.SentinelPassword, cfg.Redis.DB, cfg.Redis.Topic, role)
4040
case TypeComposite:
4141
notifiers := make([]Notifier, 0)
4242
// Add signal notifier
@@ -47,7 +47,7 @@ func NewNotifier(ctx context.Context, logger *zap.Logger, cfg *config.NotifierCo
4747
notifiers = append(notifiers, apiNotifier)
4848
// Add Redis notifier if configured
4949
if cfg.Redis.Addr != "" {
50-
redisNotifier, err := NewRedisNotifier(logger, cfg.Redis.ClusterType, cfg.Redis.Addr, cfg.Redis.MasterName, cfg.Redis.Username, cfg.Redis.Password, cfg.Redis.DB, cfg.Redis.Topic, role)
50+
redisNotifier, err := NewRedisNotifier(logger, cfg.Redis.ClusterType, cfg.Redis.Addr, cfg.Redis.MasterName, cfg.Redis.Username, cfg.Redis.Password, cfg.Redis.SentinelUsername, cfg.Redis.SentinelPassword, cfg.Redis.DB, cfg.Redis.Topic, role)
5151
if err != nil {
5252
return nil, err
5353
}

internal/mcp/storage/notifier/redis.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type RedisNotifier struct {
2525
}
2626

2727
// NewRedisNotifier creates a new Redis-based notifier
28-
func NewRedisNotifier(logger *zap.Logger, clusterType, addr, masterName, username, password string, db int, streamName string, role config.NotifierRole) (*RedisNotifier, error) {
28+
func NewRedisNotifier(logger *zap.Logger, clusterType, addr, masterName, username, password, sentinelUsername, sentinelPassword string, db int, streamName string, role config.NotifierRole) (*RedisNotifier, error) {
2929
addrs := utils.SplitByMultipleDelimiters(addr, ";", ",")
3030
redisOptions := &redis.UniversalOptions{
3131
Addrs: addrs,
@@ -34,6 +34,8 @@ func NewRedisNotifier(logger *zap.Logger, clusterType, addr, masterName, usernam
3434
}
3535
if clusterType == cnst.RedisClusterTypeSentinel {
3636
redisOptions.MasterName = masterName
37+
redisOptions.SentinelUsername = sentinelUsername
38+
redisOptions.SentinelPassword = sentinelPassword
3739
}
3840
if clusterType != cnst.RedisClusterTypeCluster {
3941
// can not set db in cluster mode

0 commit comments

Comments
 (0)