Skip to content

Commit 65fc1c3

Browse files
committed
support auth
1 parent bf90941 commit 65fc1c3

File tree

10 files changed

+111
-13
lines changed

10 files changed

+111
-13
lines changed

README.md

-2
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
`Godis` is a simple implementation of Redis Server, which intents to provide an example of writing a high concurrent
1111
middleware using golang.
1212

13-
Please be advised, NEVER think about using this in production environment.
14-
1513
Gods implemented most features of redis, including 5 data structures, ttl, publish/subscribe, geo and AOF persistence.
1614

1715
Godis can run as a server side cluster which is transparent to client. You can connect to any node in the cluster to access all data in the cluster:

cluster/client_pool.go

+6
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package cluster
33
import (
44
"context"
55
"errors"
6+
"github.com/hdt3213/godis/config"
7+
"github.com/hdt3213/godis/lib/utils"
68
"github.com/hdt3213/godis/redis/client"
79
"github.com/jolestar/go-commons-pool/v2"
810
)
@@ -17,6 +19,10 @@ func (f *ConnectionFactory) MakeObject(ctx context.Context) (*pool.PooledObject,
1719
return nil, err
1820
}
1921
c.Start()
22+
// all peers of cluster should use the same password
23+
if config.Properties.RequirePass != "" {
24+
c.Send(utils.ToBytesList("AUTH", config.Properties.RequirePass))
25+
}
2026
return pool.NewPooledObject(c), nil
2127
}
2228

cluster/cluster.go

+13-1
Original file line numberDiff line numberDiff line change
@@ -79,15 +79,27 @@ func (cluster *Cluster) Close() {
7979

8080
var router = MakeRouter()
8181

82+
func isAuthenticated(c redis.Connection) bool {
83+
if config.Properties.RequirePass == "" {
84+
return true
85+
}
86+
return c.GetPassword() == config.Properties.RequirePass
87+
}
88+
8289
func (cluster *Cluster) Exec(c redis.Connection, args [][]byte) (result redis.Reply) {
8390
defer func() {
8491
if err := recover(); err != nil {
8592
logger.Warn(fmt.Sprintf("error occurs: %v\n%s", err, string(debug.Stack())))
8693
result = &reply.UnknownErrReply{}
8794
}
8895
}()
89-
9096
cmd := strings.ToLower(string(args[0]))
97+
if cmd == "auth" {
98+
return db.Auth(cluster.db, c, args[1:])
99+
}
100+
if !isAuthenticated(c) {
101+
return reply.MakeErrReply("NOAUTH Authentication required")
102+
}
91103
cmdFunc, ok := router[cmd]
92104
if !ok {
93105
return reply.MakeErrReply("ERR unknown command '" + cmd + "', or not supported in cluster mode")

cluster/com_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package cluster
22

33
import (
4+
"github.com/hdt3213/godis/config"
5+
"github.com/hdt3213/godis/lib/utils"
6+
"github.com/hdt3213/godis/redis/connection"
47
"github.com/hdt3213/godis/redis/reply/asserts"
58
"testing"
69
)
@@ -16,6 +19,21 @@ func TestExec(t *testing.T) {
1619
}
1720
}
1821

22+
func TestAuth(t *testing.T) {
23+
passwd := utils.RandString(10)
24+
config.Properties.RequirePass = passwd
25+
defer func() {
26+
config.Properties.RequirePass = ""
27+
}()
28+
conn := &connection.FakeConn{}
29+
ret := testCluster.Exec(conn, toArgs("GET", "a"))
30+
asserts.AssertErrReply(t, ret, "NOAUTH Authentication required")
31+
ret = testCluster.Exec(conn, toArgs("AUTH", passwd))
32+
asserts.AssertStatusReply(t, ret, "OK")
33+
ret = testCluster.Exec(conn, toArgs("GET", "a"))
34+
asserts.AssertNotError(t, ret)
35+
}
36+
1937
func TestRelay(t *testing.T) {
2038
testCluster2 := MakeTestCluster([]string{"127.0.0.1:6379"})
2139
key := RandString(4)

config/config.go

+9-7
Original file line numberDiff line numberDiff line change
@@ -11,13 +11,15 @@ import (
1111
)
1212

1313
type PropertyHolder struct {
14-
Bind string `cfg:"bind"`
15-
Port int `cfg:"port"`
16-
AppendOnly bool `cfg:"appendOnly"`
17-
AppendFilename string `cfg:"appendFilename"`
18-
MaxClients int `cfg:"maxclients"`
19-
Peers []string `cfg:"peers"`
20-
Self string `cfg:"self"`
14+
Bind string `cfg:"bind"`
15+
Port int `cfg:"port"`
16+
AppendOnly bool `cfg:"appendOnly"`
17+
AppendFilename string `cfg:"appendFilename"`
18+
MaxClients int `cfg:"maxclients"`
19+
RequirePass string `cfg:"requirepass"`
20+
21+
Peers []string `cfg:"peers"`
22+
Self string `cfg:"self"`
2123
}
2224

2325
var Properties *PropertyHolder

db/db.go

+6-1
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,12 @@ func (db *DB) Exec(c redis.Connection, args [][]byte) (result redis.Reply) {
112112
}()
113113

114114
cmd := strings.ToLower(string(args[0]))
115-
115+
if cmd == "auth" {
116+
return Auth(db, c, args[1:])
117+
}
118+
if !isAuthenticated(c) {
119+
return reply.MakeErrReply("NOAUTH Authentication required")
120+
}
116121
// special commands
117122
if cmd == "subscribe" {
118123
if len(args) < 2 {

db/server.go

+24
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package db
22

33
import (
4+
"github.com/hdt3213/godis/config"
45
"github.com/hdt3213/godis/interface/redis"
56
"github.com/hdt3213/godis/redis/reply"
67
)
@@ -15,3 +16,26 @@ func Ping(db *DB, args [][]byte) redis.Reply {
1516
return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
1617
}
1718
}
19+
20+
// Auth validate client's password
21+
func Auth(db *DB, c redis.Connection, args [][]byte) redis.Reply {
22+
if len(args) != 1 {
23+
return reply.MakeErrReply("ERR wrong number of arguments for 'auth' command")
24+
}
25+
if config.Properties.RequirePass == "" {
26+
return reply.MakeErrReply("ERR Client sent AUTH, but no password is set")
27+
}
28+
passwd := string(args[0])
29+
c.SetPassword(passwd)
30+
if config.Properties.RequirePass != passwd {
31+
return reply.MakeErrReply("ERR invalid password")
32+
}
33+
return &reply.OkReply{}
34+
}
35+
36+
func isAuthenticated(c redis.Connection) bool {
37+
if config.Properties.RequirePass == "" {
38+
return true
39+
}
40+
return c.GetPassword() == config.Properties.RequirePass
41+
}

db/server_test.go

+21
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
package db
22

33
import (
4+
"github.com/hdt3213/godis/config"
45
"github.com/hdt3213/godis/lib/utils"
6+
"github.com/hdt3213/godis/redis/connection"
57
"github.com/hdt3213/godis/redis/reply/asserts"
68
"testing"
79
)
@@ -15,3 +17,22 @@ func TestPing(t *testing.T) {
1517
actual = Ping(testDB, utils.ToBytesList(val, val))
1618
asserts.AssertErrReply(t, actual, "ERR wrong number of arguments for 'ping' command")
1719
}
20+
21+
func TestAuth(t *testing.T) {
22+
passwd := utils.RandString(10)
23+
c := &connection.FakeConn{}
24+
ret := Auth(testDB, c, utils.ToBytesList())
25+
asserts.AssertErrReply(t, ret, "ERR wrong number of arguments for 'auth' command")
26+
ret = Auth(testDB, c, utils.ToBytesList(passwd))
27+
asserts.AssertErrReply(t, ret, "ERR Client sent AUTH, but no password is set")
28+
29+
config.Properties.RequirePass = passwd
30+
defer func() {
31+
config.Properties.RequirePass = ""
32+
}()
33+
ret = Auth(testDB, c, utils.ToBytesList(passwd+passwd))
34+
asserts.AssertErrReply(t, ret, "ERR invalid password")
35+
ret = Auth(testDB, c, utils.ToBytesList(passwd))
36+
asserts.AssertStatusReply(t, ret, "OK")
37+
38+
}

interface/redis/client.go

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ package redis
22

33
type Connection interface {
44
Write([]byte) error
5-
5+
SetPassword(string)
6+
GetPassword() string
67
// client should keep its subscribing channels
78
Subscribe(channel string)
89
UnSubscribe(channel string)

redis/connection/conn.go

+12-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,9 @@ type Connection struct {
2020

2121
// subscribing channels
2222
subs map[string]bool
23+
24+
// password may be changed by CONFIG command during runtime, so store the password
25+
password string
2326
}
2427

2528
// RemoteAddr returns the remote network address
@@ -97,6 +100,14 @@ func (c *Connection) GetChannels() []string {
97100
return channels
98101
}
99102

103+
func (c *Connection) SetPassword(password string) {
104+
c.password = password
105+
}
106+
107+
func (c *Connection) GetPassword() string {
108+
return c.password
109+
}
110+
100111
type FakeConn struct {
101112
Connection
102113
buf bytes.Buffer
@@ -113,4 +124,4 @@ func (c *FakeConn) Clean() {
113124

114125
func (c *FakeConn) Bytes() []byte {
115126
return c.buf.Bytes()
116-
}
127+
}

0 commit comments

Comments
 (0)