Skip to content

Add support for authenticating against Sentinel #11

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 15, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,28 +20,34 @@ func main() {
sentinelAddr = ":26379"
masterName = "mymaster"
masterResolveRetries = 3
password = ""
)

flag.StringVar(&localAddr, "listen", localAddr, "local address")
flag.StringVar(&sentinelAddr, "sentinel", sentinelAddr, "remote address")
flag.StringVar(&masterName, "master", masterName, "name of the master redis node")
flag.StringVar(&password, "password", password, "redis password")
flag.IntVar(&masterResolveRetries, "resolve-retries", masterResolveRetries, "number of consecutive retries of the redis master node resolve")
flag.Parse()

if err := runProxying(localAddr, sentinelAddr, masterName, masterResolveRetries); err != nil {
if envPassword := os.Getenv("SENTINEL_PASSWORD"); envPassword != "" {
password = envPassword
}

if err := runProxying(localAddr, sentinelAddr, password, masterName, masterResolveRetries); err != nil {
log.Fatalf("Fatal: %s", err)
}
log.Println("Exiting...")
}

func runProxying(localAddr, sentinelAddr, masterName string, masterResolveRetries int) error {
func runProxying(localAddr, sentinelAddr, password string, masterName string, masterResolveRetries int) error {
ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer cancel()

laddr := resolveTCPAddr(localAddr)
saddr := resolveTCPAddr(sentinelAddr)

masterAddrResolver := masterresolver.NewRedisMasterResolver(masterName, saddr, masterResolveRetries)
masterAddrResolver := masterresolver.NewRedisMasterResolver(masterName, saddr, password, masterResolveRetries)
rsp := proxy.NewRedisSentinelProxy(laddr, masterAddrResolver)

eg, ctx := errgroup.WithContext(ctx)
Expand Down
54 changes: 40 additions & 14 deletions pkg/master_resolver/master_resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (
type RedisMasterResolver struct {
masterName string
sentinelAddr *net.TCPAddr
sentinelPassword string
retryOnMasterResolveFail int

masterAddrLock *sync.RWMutex
Expand All @@ -24,10 +25,11 @@ type RedisMasterResolver struct {
masterAddr string
}

func NewRedisMasterResolver(masterName string, sentinelAddr *net.TCPAddr, retryOnMasterResolveFail int) *RedisMasterResolver {
func NewRedisMasterResolver(masterName string, sentinelAddr *net.TCPAddr, sentinelPassword string, retryOnMasterResolveFail int) *RedisMasterResolver {
return &RedisMasterResolver{
masterName: masterName,
sentinelAddr: sentinelAddr,
sentinelPassword: sentinelPassword,
retryOnMasterResolveFail: retryOnMasterResolveFail,
masterAddrLock: &sync.RWMutex{},
initialMasterResolveLock: make(chan struct{}),
Expand All @@ -49,7 +51,7 @@ func (r *RedisMasterResolver) setMasterAddress(masterAddr *net.TCPAddr) {
}

func (r *RedisMasterResolver) updateMasterAddress() error {
masterAddr, err := redisMasterFromSentinelAddr(r.sentinelAddr, r.masterName)
masterAddr, err := redisMasterFromSentinelAddr(r.sentinelAddr, r.sentinelPassword, r.masterName)
if err != nil {
log.Println(err)
return err
Expand All @@ -59,7 +61,7 @@ func (r *RedisMasterResolver) updateMasterAddress() error {
}

func (r *RedisMasterResolver) UpdateMasterAddressLoop(ctx context.Context) error {
if err := r.initialMasterAdressResolve(); err != nil {
if err := r.initialMasterAddressResolve(); err != nil {
return err
}

Expand All @@ -84,46 +86,70 @@ func (r *RedisMasterResolver) UpdateMasterAddressLoop(ctx context.Context) error
return err
}

func (r *RedisMasterResolver) initialMasterAdressResolve() error {
func (r *RedisMasterResolver) initialMasterAddressResolve() error {
defer close(r.initialMasterResolveLock)
return r.updateMasterAddress()
}

func redisMasterFromSentinelAddr(sentinelAddress *net.TCPAddr, masterName string) (*net.TCPAddr, error) {
func redisMasterFromSentinelAddr(sentinelAddress *net.TCPAddr, sentinelPassword string, masterName string) (*net.TCPAddr, error) {
conn, err := utils.TCPConnectWithTimeout(sentinelAddress.String())
conn.SetDeadline(time.Now().Add(time.Second))
if err != nil {
return nil, fmt.Errorf("error connecting to sentinel: %w", err)
}
defer conn.Close()

getMasterCommand := fmt.Sprintf("sentinel get-master-addr-by-name %s\n", masterName)
conn.SetDeadline(time.Now().Add(time.Second))

// Authenticate with sentinel if password is provided
if sentinelPassword != "" {
authCommand := fmt.Sprintf("AUTH %s\r\n", sentinelPassword)
if _, err := conn.Write([]byte(authCommand)); err != nil {
return nil, fmt.Errorf("error sending AUTH to sentinel: %w", err)
}

// Read response from AUTH
b := make([]byte, 256)
n, err := conn.Read(b)
if err != nil {
return nil, fmt.Errorf("error reading AUTH response: %w", err)
}
response := string(b[:n])
if !strings.HasPrefix(response, "+OK") {
return nil, fmt.Errorf("sentinel AUTH failed: %s", response)
}
}

// Request master address
getMasterCommand := fmt.Sprintf("SENTINEL get-master-addr-by-name %s\r\n", masterName)
if _, err := conn.Write([]byte(getMasterCommand)); err != nil {
return nil, fmt.Errorf("error writing to sentinel: %w", err)
}

// Read response
b := make([]byte, 256)
if _, err := conn.Read(b); err != nil {
n, err := conn.Read(b)
if err != nil {
return nil, fmt.Errorf("error getting info from sentinel: %w", err)
}

parts := strings.Split(string(b), "\r\n")

// Extract master address parts
parts := strings.Split(string(b[:n]), "\r\n")
if len(parts) < 5 {
return nil, errors.New("couldn't get master address from sentinel")
}

// getting the string address for the master node
stringaddr := fmt.Sprintf("%s:%s", parts[2], parts[4])
addr, err := net.ResolveTCPAddr("tcp", stringaddr)
// Assemble master address
formattedMasterAddress := fmt.Sprintf("%s:%s", parts[2], parts[4])
addr, err := net.ResolveTCPAddr("tcp", formattedMasterAddress)
if err != nil {
return nil, fmt.Errorf("error resolving redis master: %w", err)
}

// check that there's actually someone listening on that address
// Check if there is a Redis instance listening on the master address
if err := checkTCPConnect(addr); err != nil {
return nil, fmt.Errorf("error checking redis master: %w", err)
}

return addr, nil
}

Expand Down