mirror of
https://github.com/gomods/athens
synced 2026-02-03 08:40:31 +00:00
Redis Sentinel SingeFlight: support of Redis master node username and password (#2039)
* Add support for Redis Username and Password configuration Introduced Redis master authentication parameters (username and password) to the Redis Sentinel setup. This enhances compatibility with Redis environments that require authentication for both sentinel and master nodes. * Add support for protected Redis Sentinel configuration and related unit tests
This commit is contained in:
committed by
GitHub
parent
ebb5ac698b
commit
ab1775afee
@@ -34,6 +34,9 @@ jobs:
|
|||||||
REDIS_SENTINEL_TEST_MASTER_NAME: redis-1
|
REDIS_SENTINEL_TEST_MASTER_NAME: redis-1
|
||||||
REDIS_SENTINEL_TEST_PASSWORD: sekret
|
REDIS_SENTINEL_TEST_PASSWORD: sekret
|
||||||
PROTECTED_REDIS_TEST_ENDPOINT: localhost:6380
|
PROTECTED_REDIS_TEST_ENDPOINT: localhost:6380
|
||||||
|
PROTECTED_REDIS_TEST_USERNAME: default
|
||||||
|
REDIS_SENTINEL_TEST_PROTECTED_ENDPOINT: localhost:26380
|
||||||
|
REDIS_SENTINEL_TEST_PROTECTED_MASTER_NAME: protectedredis-1
|
||||||
ATHENS_PROTECTED_REDIS_PASSWORD: AthensPass1
|
ATHENS_PROTECTED_REDIS_PASSWORD: AthensPass1
|
||||||
GA_PULL_REQUEST: ${{github.event.number}}
|
GA_PULL_REQUEST: ${{github.event.number}}
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
@@ -69,6 +72,17 @@ jobs:
|
|||||||
env:
|
env:
|
||||||
REDIS_PORT_NUMBER: 6380
|
REDIS_PORT_NUMBER: 6380
|
||||||
REDIS_PASSWORD: AthensPass1
|
REDIS_PASSWORD: AthensPass1
|
||||||
|
redis-sentinel-protected-redis:
|
||||||
|
image: bitnami/redis-sentinel
|
||||||
|
env:
|
||||||
|
REDIS_MASTER_HOST: protectedredis
|
||||||
|
REDIS_MASTER_PORT_NUMBER: 6380
|
||||||
|
REDIS_MASTER_SET: protectedredis-1
|
||||||
|
REDIS_SENTINEL_PASSWORD: sekret
|
||||||
|
REDIS_SENTINEL_QUORUM: "1"
|
||||||
|
REDIS_SENTINEL_PORT_NUMBER: 26380
|
||||||
|
ports:
|
||||||
|
- 26380:26380
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
|
|||||||
@@ -166,6 +166,8 @@ func getSingleFlight(l *log.Logger, c *config.Config, s storage.Backend, checker
|
|||||||
c.SingleFlight.RedisSentinel.Endpoints,
|
c.SingleFlight.RedisSentinel.Endpoints,
|
||||||
c.SingleFlight.RedisSentinel.MasterName,
|
c.SingleFlight.RedisSentinel.MasterName,
|
||||||
c.SingleFlight.RedisSentinel.SentinelPassword,
|
c.SingleFlight.RedisSentinel.SentinelPassword,
|
||||||
|
c.SingleFlight.RedisSentinel.RedisUsername,
|
||||||
|
c.SingleFlight.RedisSentinel.RedisPassword,
|
||||||
checker,
|
checker,
|
||||||
c.SingleFlight.RedisSentinel.LockConfig,
|
c.SingleFlight.RedisSentinel.LockConfig,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -369,6 +369,12 @@ ShutdownTimeout = 60
|
|||||||
# Env override: ATHENS_REDIS_SENTINEL_PASSWORD
|
# Env override: ATHENS_REDIS_SENTINEL_PASSWORD
|
||||||
SentinelPassword = "sekret"
|
SentinelPassword = "sekret"
|
||||||
|
|
||||||
|
# The Redis master authorization parameters
|
||||||
|
# Env override: ATHENS_REDIS_USERNAME
|
||||||
|
RedisUsername = ""
|
||||||
|
# Env override: ATHENS_REDIS_PASSWORD
|
||||||
|
RedisPassword = ""
|
||||||
|
|
||||||
[SingleFlight.RedisSentinel.LockConfig]
|
[SingleFlight.RedisSentinel.LockConfig]
|
||||||
# TTL for the lock in seconds. Defaults to 900 seconds (15 minutes).
|
# TTL for the lock in seconds. Defaults to 900 seconds (15 minutes).
|
||||||
# Env override: ATHENS_REDIS_LOCK_TTL
|
# Env override: ATHENS_REDIS_LOCK_TTL
|
||||||
|
|||||||
@@ -179,6 +179,8 @@ func defaultConfig() *Config {
|
|||||||
Endpoints: []string{"127.0.0.1:26379"},
|
Endpoints: []string{"127.0.0.1:26379"},
|
||||||
MasterName: "redis-1",
|
MasterName: "redis-1",
|
||||||
SentinelPassword: "sekret",
|
SentinelPassword: "sekret",
|
||||||
|
RedisUsername: "",
|
||||||
|
RedisPassword: "",
|
||||||
LockConfig: DefaultRedisLockConfig(),
|
LockConfig: DefaultRedisLockConfig(),
|
||||||
},
|
},
|
||||||
GCP: DefaultGCPConfig(),
|
GCP: DefaultGCPConfig(),
|
||||||
|
|||||||
@@ -375,7 +375,7 @@ func getEnvMap(config *Config) map[string]string {
|
|||||||
if singleFlight.Redis != nil {
|
if singleFlight.Redis != nil {
|
||||||
envVars["ATHENS_SINGLE_FLIGHT_TYPE"] = "redis"
|
envVars["ATHENS_SINGLE_FLIGHT_TYPE"] = "redis"
|
||||||
envVars["ATHENS_REDIS_ENDPOINT"] = singleFlight.Redis.Endpoint
|
envVars["ATHENS_REDIS_ENDPOINT"] = singleFlight.Redis.Endpoint
|
||||||
envVars["ATHENS_REDIS_PASSWORD"] = singleFlight.Redis.Endpoint
|
envVars["ATHENS_REDIS_PASSWORD"] = singleFlight.Redis.Password
|
||||||
if singleFlight.Redis.LockConfig != nil {
|
if singleFlight.Redis.LockConfig != nil {
|
||||||
envVars["ATHENS_REDIS_LOCK_TTL"] = strconv.Itoa(singleFlight.Redis.LockConfig.TTL)
|
envVars["ATHENS_REDIS_LOCK_TTL"] = strconv.Itoa(singleFlight.Redis.LockConfig.TTL)
|
||||||
envVars["ATHENS_REDIS_LOCK_TIMEOUT"] = strconv.Itoa(singleFlight.Redis.LockConfig.Timeout)
|
envVars["ATHENS_REDIS_LOCK_TIMEOUT"] = strconv.Itoa(singleFlight.Redis.LockConfig.Timeout)
|
||||||
@@ -386,6 +386,8 @@ func getEnvMap(config *Config) map[string]string {
|
|||||||
envVars["ATHENS_REDIS_SENTINEL_ENDPOINTS"] = strings.Join(singleFlight.RedisSentinel.Endpoints, ",")
|
envVars["ATHENS_REDIS_SENTINEL_ENDPOINTS"] = strings.Join(singleFlight.RedisSentinel.Endpoints, ",")
|
||||||
envVars["ATHENS_REDIS_SENTINEL_MASTER_NAME"] = singleFlight.RedisSentinel.MasterName
|
envVars["ATHENS_REDIS_SENTINEL_MASTER_NAME"] = singleFlight.RedisSentinel.MasterName
|
||||||
envVars["ATHENS_REDIS_SENTINEL_PASSWORD"] = singleFlight.RedisSentinel.SentinelPassword
|
envVars["ATHENS_REDIS_SENTINEL_PASSWORD"] = singleFlight.RedisSentinel.SentinelPassword
|
||||||
|
envVars["ATHENS_REDIS_USERNAME"] = singleFlight.RedisSentinel.RedisUsername
|
||||||
|
envVars["ATHENS_REDIS_PASSWORD"] = singleFlight.RedisSentinel.RedisPassword
|
||||||
if singleFlight.RedisSentinel.LockConfig != nil {
|
if singleFlight.RedisSentinel.LockConfig != nil {
|
||||||
envVars["ATHENS_REDIS_LOCK_TTL"] = strconv.Itoa(singleFlight.RedisSentinel.LockConfig.TTL)
|
envVars["ATHENS_REDIS_LOCK_TTL"] = strconv.Itoa(singleFlight.RedisSentinel.LockConfig.TTL)
|
||||||
envVars["ATHENS_REDIS_LOCK_TIMEOUT"] = strconv.Itoa(singleFlight.RedisSentinel.LockConfig.Timeout)
|
envVars["ATHENS_REDIS_LOCK_TIMEOUT"] = strconv.Itoa(singleFlight.RedisSentinel.LockConfig.Timeout)
|
||||||
|
|||||||
@@ -31,6 +31,8 @@ type RedisSentinel struct {
|
|||||||
Endpoints []string `envconfig:"ATHENS_REDIS_SENTINEL_ENDPOINTS"`
|
Endpoints []string `envconfig:"ATHENS_REDIS_SENTINEL_ENDPOINTS"`
|
||||||
MasterName string `envconfig:"ATHENS_REDIS_SENTINEL_MASTER_NAME"`
|
MasterName string `envconfig:"ATHENS_REDIS_SENTINEL_MASTER_NAME"`
|
||||||
SentinelPassword string `envconfig:"ATHENS_REDIS_SENTINEL_PASSWORD"`
|
SentinelPassword string `envconfig:"ATHENS_REDIS_SENTINEL_PASSWORD"`
|
||||||
|
RedisUsername string `envconfig:"ATHENS_REDIS_USERNAME"`
|
||||||
|
RedisPassword string `envconfig:"ATHENS_REDIS_PASSWORD"`
|
||||||
LockConfig *RedisLockConfig
|
LockConfig *RedisLockConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import (
|
|||||||
|
|
||||||
// WithRedisSentinelLock returns a distributed singleflight
|
// WithRedisSentinelLock returns a distributed singleflight
|
||||||
// with a redis cluster that utilizes sentinel for quorum and failover.
|
// with a redis cluster that utilizes sentinel for quorum and failover.
|
||||||
func WithRedisSentinelLock(l RedisLogger, endpoints []string, master, password string, checker storage.Checker, lockConfig *config.RedisLockConfig) (Wrapper, error) {
|
func WithRedisSentinelLock(l RedisLogger, endpoints []string, master, sentinelPassword, redisUsername, redisPassword string, checker storage.Checker, lockConfig *config.RedisLockConfig) (Wrapper, error) {
|
||||||
redis.SetLogger(l)
|
redis.SetLogger(l)
|
||||||
|
|
||||||
const op errors.Op = "stash.WithRedisSentinelLock"
|
const op errors.Op = "stash.WithRedisSentinelLock"
|
||||||
@@ -23,7 +23,9 @@ func WithRedisSentinelLock(l RedisLogger, endpoints []string, master, password s
|
|||||||
client := redis.NewFailoverClient(&redis.FailoverOptions{
|
client := redis.NewFailoverClient(&redis.FailoverOptions{
|
||||||
MasterName: master,
|
MasterName: master,
|
||||||
SentinelAddrs: endpoints,
|
SentinelAddrs: endpoints,
|
||||||
SentinelPassword: password,
|
SentinelPassword: sentinelPassword,
|
||||||
|
Username: redisUsername,
|
||||||
|
Password: redisPassword,
|
||||||
})
|
})
|
||||||
_, err := client.Ping(context.Background()).Result()
|
_, err := client.Ping(context.Background()).Result()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -18,8 +18,8 @@ import (
|
|||||||
func TestWithRedisSentinelLock(t *testing.T) {
|
func TestWithRedisSentinelLock(t *testing.T) {
|
||||||
endpoint := os.Getenv("REDIS_SENTINEL_TEST_ENDPOINT")
|
endpoint := os.Getenv("REDIS_SENTINEL_TEST_ENDPOINT")
|
||||||
masterName := os.Getenv("REDIS_SENTINEL_TEST_MASTER_NAME")
|
masterName := os.Getenv("REDIS_SENTINEL_TEST_MASTER_NAME")
|
||||||
password := os.Getenv("REDIS_SENTINEL_TEST_PASSWORD")
|
sentinelPassword := os.Getenv("REDIS_SENTINEL_TEST_PASSWORD")
|
||||||
if len(endpoint) == 0 || len(masterName) == 0 || len(password) == 0 {
|
if len(endpoint) == 0 || len(masterName) == 0 || len(sentinelPassword) == 0 {
|
||||||
t.SkipNow()
|
t.SkipNow()
|
||||||
}
|
}
|
||||||
strg, err := mem.NewStorage()
|
strg, err := mem.NewStorage()
|
||||||
@@ -29,7 +29,7 @@ func TestWithRedisSentinelLock(t *testing.T) {
|
|||||||
ms := &mockRedisStasher{strg: strg}
|
ms := &mockRedisStasher{strg: strg}
|
||||||
l := &testingRedisLogger{t: t}
|
l := &testingRedisLogger{t: t}
|
||||||
|
|
||||||
wrapper, err := WithRedisSentinelLock(l, []string{endpoint}, masterName, password, storage.WithChecker(strg), config.DefaultRedisLockConfig())
|
wrapper, err := WithRedisSentinelLock(l, []string{endpoint}, masterName, sentinelPassword, "", "", storage.WithChecker(strg), config.DefaultRedisLockConfig())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
@@ -50,3 +50,109 @@ func TestWithRedisSentinelLock(t *testing.T) {
|
|||||||
t.Fatal(err)
|
t.Fatal(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestWithRedisSentinelLockWithRedisPassword verifies WithRedisSentinelLock working with
|
||||||
|
// password protected redis sentinel and redis master nodes
|
||||||
|
func TestWithRedisSentinelLockWithRedisPassword(t *testing.T) {
|
||||||
|
endpoint := os.Getenv("REDIS_SENTINEL_TEST_PROTECTED_ENDPOINT")
|
||||||
|
masterName := os.Getenv("REDIS_SENTINEL_TEST_PROTECTED_MASTER_NAME")
|
||||||
|
sentinelPassword := os.Getenv("REDIS_SENTINEL_TEST_PASSWORD")
|
||||||
|
redisPassword := os.Getenv("ATHENS_PROTECTED_REDIS_PASSWORD")
|
||||||
|
if len(endpoint) == 0 || len(masterName) == 0 {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
strg, err := mem.NewStorage()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ms := &mockRedisStasher{strg: strg}
|
||||||
|
l := &testingRedisLogger{t: t}
|
||||||
|
wrapper, err := WithRedisSentinelLock(l, []string{endpoint}, masterName, sentinelPassword, "", redisPassword, storage.WithChecker(strg), config.DefaultRedisLockConfig())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := wrapper(ms)
|
||||||
|
|
||||||
|
var eg errgroup.Group
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
eg.Go(func() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
_, err := s.Stash(ctx, "mod", "ver")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWithRedisSentinelLockWithPassword verifies WithRedisSentinelLock working with
|
||||||
|
// username & password protected master node under the redis sentinel
|
||||||
|
func TestWithRedisSentinelLockWithUsernameAndPassword(t *testing.T) {
|
||||||
|
endpoint := os.Getenv("REDIS_SENTINEL_TEST_PROTECTED_ENDPOINT")
|
||||||
|
masterName := os.Getenv("REDIS_SENTINEL_TEST_PROTECTED_MASTER_NAME")
|
||||||
|
sentinelPassword := os.Getenv("REDIS_SENTINEL_TEST_PASSWORD")
|
||||||
|
redisPassword := os.Getenv("ATHENS_PROTECTED_REDIS_PASSWORD")
|
||||||
|
redisUsername := os.Getenv("PROTECTED_REDIS_TEST_USERNAME")
|
||||||
|
if len(endpoint) == 0 || len(masterName) == 0 {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
strg, err := mem.NewStorage()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
ms := &mockRedisStasher{strg: strg}
|
||||||
|
l := &testingRedisLogger{t: t}
|
||||||
|
wrapper, err := WithRedisSentinelLock(l, []string{endpoint}, masterName, sentinelPassword, redisUsername, redisPassword, storage.WithChecker(strg), config.DefaultRedisLockConfig())
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
s := wrapper(ms)
|
||||||
|
|
||||||
|
var eg errgroup.Group
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
eg.Go(func() error {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
|
||||||
|
defer cancel()
|
||||||
|
_, err := s.Stash(ctx, "mod", "ver")
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
err = eg.Wait()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestWithRedisSentinelLockWithWrongPassword verifies the WithRedisSentinelLock fails
|
||||||
|
// with the correct error when trying to connect with wrong passwords
|
||||||
|
func TestWithRedisSentinelLockWithWrongRedisPassword(t *testing.T) {
|
||||||
|
endpoint := os.Getenv("REDIS_SENTINEL_TEST_PROTECTED_ENDPOINT")
|
||||||
|
masterName := os.Getenv("REDIS_SENTINEL_TEST_PROTECTED_MASTER_NAME")
|
||||||
|
sentinelPassword := os.Getenv("REDIS_SENTINEL_TEST_PASSWORD")
|
||||||
|
redisPassword := os.Getenv("ATHENS_PROTECTED_REDIS_PASSWORD")
|
||||||
|
if len(endpoint) == 0 || len(masterName) == 0 {
|
||||||
|
t.SkipNow()
|
||||||
|
}
|
||||||
|
strg, err := mem.NewStorage()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
l := &testingRedisLogger{t: t}
|
||||||
|
|
||||||
|
// Test with wrong sentinel password
|
||||||
|
_, err = WithRedisSentinelLock(l, []string{endpoint}, masterName, "wrong-sentinel-password", "", redisPassword, storage.WithChecker(strg), config.DefaultRedisLockConfig())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected Connection Error for wrong sentinel password")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test with wrong redis password
|
||||||
|
_, err = WithRedisSentinelLock(l, []string{endpoint}, masterName, sentinelPassword, "", "wrong-redis-password", storage.WithChecker(strg), config.DefaultRedisLockConfig())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("Expected Connection Error for wrong redis password")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user