Skip to content

Commit

Permalink
feat: add acl auth support for sentinels
Browse files Browse the repository at this point in the history
  • Loading branch information
jsievenpiper committed Sep 18, 2021
1 parent 7c92957 commit f66582f
Show file tree
Hide file tree
Showing 3 changed files with 114 additions and 2 deletions.
48 changes: 47 additions & 1 deletion main_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,15 +40,27 @@ const (
sentinelPort3 = "9128"
)

const (
aclSentinelUsername = "sentinel-user"
aclSentinelPassword = "sentinel-pass"
aclSentinelName = "my_server"
aclServerPort = "10001"
aclSentinelPort1 = "10002"
aclSentinelPort2 = "10003"
aclSentinelPort3 = "10004"
)

var (
sentinelAddrs = []string{":" + sentinelPort1, ":" + sentinelPort2, ":" + sentinelPort3}
aclSentinelAddrs = []string {":" + aclSentinelPort1, ":" + aclSentinelPort2, ":" + aclSentinelPort3}

processes map[string]*redisProcess

redisMain *redisProcess
redisMain, aclServer *redisProcess
ringShard1, ringShard2, ringShard3 *redisProcess
sentinelMaster, sentinelSlave1, sentinelSlave2 *redisProcess
sentinel1, sentinel2, sentinel3 *redisProcess
aclSentinel1, aclSentinel2, aclSentinel3 *redisProcess
)

var cluster = &clusterScenario{
Expand Down Expand Up @@ -101,6 +113,18 @@ var _ = BeforeSuite(func() {
Expect(err).NotTo(HaveOccurred())

Expect(startCluster(ctx, cluster)).NotTo(HaveOccurred())

aclServer, err = startRedis(aclServerPort)
Expect(err).NotTo(HaveOccurred())

aclSentinel1, err = startSentinelWithAcl(aclSentinelPort1, aclSentinelName, aclServerPort)
Expect(err).NotTo(HaveOccurred())

aclSentinel2, err = startSentinelWithAcl(aclSentinelPort2, aclSentinelName, aclServerPort)
Expect(err).NotTo(HaveOccurred())

aclSentinel3, err = startSentinelWithAcl(aclSentinelPort3, aclSentinelName, aclServerPort)
Expect(err).NotTo(HaveOccurred())
})

var _ = AfterSuite(func() {
Expand Down Expand Up @@ -364,6 +388,28 @@ func startSentinel(port, masterName, masterPort string) (*redisProcess, error) {
return p, nil
}

func startSentinelWithAcl(port, masterName, masterPort string) (*redisProcess, error) {
process, err := startSentinel(port, masterName, masterPort)
if err != nil {
return nil, err
}

for _, cmd := range []*redis.StatusCmd{
redis.NewStatusCmd(ctx, "ACL", "SETUSER", aclSentinelUsername, "ON", ">" + aclSentinelPassword, "-@all",
"+auth", "+client|getname", "+client|id", "+client|setname", "+command", "+hello", "+ping", "+role",
"+sentinel|get-master-addr-by-name", "+sentinel|master", "+sentinel|myid", "+sentinel|replicas",
"+sentinel|sentinels"),
} {
process.Client.Process(ctx, cmd)
if err := cmd.Err(); err != nil {
process.Kill()
return nil, err
}
}

return process, nil
}

//------------------------------------------------------------------------------

type badConnError string
Expand Down
9 changes: 8 additions & 1 deletion sentinel.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,13 @@ type FailoverOptions struct {
MasterName string
// A seed list of host:port addresses of sentinel nodes.
SentinelAddrs []string
// Sentinel password from "requirepass <password>" (if enabled) in Sentinel configuration

// If specified with SentinelPassword, enables ACL-based authentication (via
// AUTH <user> <pass>).
SentinelUsername string
// Sentinel password from "requirepass <password>" (if enabled) in Sentinel
// configuration, or, if SentinelUsername is also supplied, used for ACL-based
// authentication.
SentinelPassword string

// Allows routing read-only commands to the closest master or slave node.
Expand Down Expand Up @@ -109,6 +115,7 @@ func (opt *FailoverOptions) sentinelOptions(addr string) *Options {
OnConnect: opt.OnConnect,

DB: 0,
Username: opt.SentinelUsername,
Password: opt.SentinelPassword,

MaxRetries: opt.MaxRetries,
Expand Down
59 changes: 59 additions & 0 deletions sentinel_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,3 +212,62 @@ var _ = Describe("NewFailoverClusterClient", func() {
Expect(err).NotTo(HaveOccurred())
})
})

var _ = Describe("SentinelAclAuth", func() {
var client *redis.Client
var server *redis.Client
var sentinel *redis.SentinelClient

BeforeEach(func() {
client = redis.NewFailoverClient(&redis.FailoverOptions{
MasterName: aclSentinelName,
SentinelAddrs: aclSentinelAddrs,
MaxRetries: -1,
SentinelUsername: aclSentinelUsername,
SentinelPassword: aclSentinelPassword,
})

Expect(client.FlushDB(ctx).Err()).NotTo(HaveOccurred())

sentinel = redis.NewSentinelClient(&redis.Options{
Addr: aclSentinelAddrs[0],
MaxRetries: -1,
Username: aclSentinelUsername,
Password: aclSentinelPassword,
})

addr, err := sentinel.GetMasterAddrByName(ctx, aclSentinelName).Result()
Expect(err).NotTo(HaveOccurred())

server = redis.NewClient(&redis.Options{
Addr: net.JoinHostPort(addr[0], addr[1]),
MaxRetries: -1,
})

// Wait until sentinels are picked up by each other.
Eventually(func() string {
return aclSentinel1.Info(ctx).Val()
}, "15s", "100ms").Should(ContainSubstring("sentinels=3"))
Eventually(func() string {
return aclSentinel2.Info(ctx).Val()
}, "15s", "100ms").Should(ContainSubstring("sentinels=3"))
Eventually(func() string {
return aclSentinel3.Info(ctx).Val()
}, "15s", "100ms").Should(ContainSubstring("sentinels=3"))
})

AfterEach(func() {
_ = client.Close()
_ = server.Close()
_ = sentinel.Close()
})

It("should still facilitate operations", func() {
err := client.Set(ctx, "wow", "acl-auth", 0).Err()
Expect(err).NotTo(HaveOccurred())

val, err := client.Get(ctx, "wow").Result()
Expect(err).NotTo(HaveOccurred())
Expect(val).To(Equal("acl-auth"))
})
})

0 comments on commit f66582f

Please sign in to comment.