support auth

This commit is contained in:
hdt3213
2021-05-09 18:37:29 +08:00
parent bf90941b76
commit 65fc1c3e62
10 changed files with 111 additions and 13 deletions

View File

@@ -10,8 +10,6 @@
`Godis` is a simple implementation of Redis Server, which intents to provide an example of writing a high concurrent
middleware using golang.
Please be advised, NEVER think about using this in production environment.
Gods implemented most features of redis, including 5 data structures, ttl, publish/subscribe, geo and AOF persistence.
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:

View File

@@ -3,6 +3,8 @@ package cluster
import (
"context"
"errors"
"github.com/hdt3213/godis/config"
"github.com/hdt3213/godis/lib/utils"
"github.com/hdt3213/godis/redis/client"
"github.com/jolestar/go-commons-pool/v2"
)
@@ -17,6 +19,10 @@ func (f *ConnectionFactory) MakeObject(ctx context.Context) (*pool.PooledObject,
return nil, err
}
c.Start()
// all peers of cluster should use the same password
if config.Properties.RequirePass != "" {
c.Send(utils.ToBytesList("AUTH", config.Properties.RequirePass))
}
return pool.NewPooledObject(c), nil
}

View File

@@ -79,6 +79,13 @@ func (cluster *Cluster) Close() {
var router = MakeRouter()
func isAuthenticated(c redis.Connection) bool {
if config.Properties.RequirePass == "" {
return true
}
return c.GetPassword() == config.Properties.RequirePass
}
func (cluster *Cluster) Exec(c redis.Connection, args [][]byte) (result redis.Reply) {
defer func() {
if err := recover(); err != nil {
@@ -86,8 +93,13 @@ func (cluster *Cluster) Exec(c redis.Connection, args [][]byte) (result redis.Re
result = &reply.UnknownErrReply{}
}
}()
cmd := strings.ToLower(string(args[0]))
if cmd == "auth" {
return db.Auth(cluster.db, c, args[1:])
}
if !isAuthenticated(c) {
return reply.MakeErrReply("NOAUTH Authentication required")
}
cmdFunc, ok := router[cmd]
if !ok {
return reply.MakeErrReply("ERR unknown command '" + cmd + "', or not supported in cluster mode")

View File

@@ -1,6 +1,9 @@
package cluster
import (
"github.com/hdt3213/godis/config"
"github.com/hdt3213/godis/lib/utils"
"github.com/hdt3213/godis/redis/connection"
"github.com/hdt3213/godis/redis/reply/asserts"
"testing"
)
@@ -16,6 +19,21 @@ func TestExec(t *testing.T) {
}
}
func TestAuth(t *testing.T) {
passwd := utils.RandString(10)
config.Properties.RequirePass = passwd
defer func() {
config.Properties.RequirePass = ""
}()
conn := &connection.FakeConn{}
ret := testCluster.Exec(conn, toArgs("GET", "a"))
asserts.AssertErrReply(t, ret, "NOAUTH Authentication required")
ret = testCluster.Exec(conn, toArgs("AUTH", passwd))
asserts.AssertStatusReply(t, ret, "OK")
ret = testCluster.Exec(conn, toArgs("GET", "a"))
asserts.AssertNotError(t, ret)
}
func TestRelay(t *testing.T) {
testCluster2 := MakeTestCluster([]string{"127.0.0.1:6379"})
key := RandString(4)

View File

@@ -16,6 +16,8 @@ type PropertyHolder struct {
AppendOnly bool `cfg:"appendOnly"`
AppendFilename string `cfg:"appendFilename"`
MaxClients int `cfg:"maxclients"`
RequirePass string `cfg:"requirepass"`
Peers []string `cfg:"peers"`
Self string `cfg:"self"`
}

View File

@@ -112,7 +112,12 @@ func (db *DB) Exec(c redis.Connection, args [][]byte) (result redis.Reply) {
}()
cmd := strings.ToLower(string(args[0]))
if cmd == "auth" {
return Auth(db, c, args[1:])
}
if !isAuthenticated(c) {
return reply.MakeErrReply("NOAUTH Authentication required")
}
// special commands
if cmd == "subscribe" {
if len(args) < 2 {

View File

@@ -1,6 +1,7 @@
package db
import (
"github.com/hdt3213/godis/config"
"github.com/hdt3213/godis/interface/redis"
"github.com/hdt3213/godis/redis/reply"
)
@@ -15,3 +16,26 @@ func Ping(db *DB, args [][]byte) redis.Reply {
return reply.MakeErrReply("ERR wrong number of arguments for 'ping' command")
}
}
// Auth validate client's password
func Auth(db *DB, c redis.Connection, args [][]byte) redis.Reply {
if len(args) != 1 {
return reply.MakeErrReply("ERR wrong number of arguments for 'auth' command")
}
if config.Properties.RequirePass == "" {
return reply.MakeErrReply("ERR Client sent AUTH, but no password is set")
}
passwd := string(args[0])
c.SetPassword(passwd)
if config.Properties.RequirePass != passwd {
return reply.MakeErrReply("ERR invalid password")
}
return &reply.OkReply{}
}
func isAuthenticated(c redis.Connection) bool {
if config.Properties.RequirePass == "" {
return true
}
return c.GetPassword() == config.Properties.RequirePass
}

View File

@@ -1,7 +1,9 @@
package db
import (
"github.com/hdt3213/godis/config"
"github.com/hdt3213/godis/lib/utils"
"github.com/hdt3213/godis/redis/connection"
"github.com/hdt3213/godis/redis/reply/asserts"
"testing"
)
@@ -15,3 +17,22 @@ func TestPing(t *testing.T) {
actual = Ping(testDB, utils.ToBytesList(val, val))
asserts.AssertErrReply(t, actual, "ERR wrong number of arguments for 'ping' command")
}
func TestAuth(t *testing.T) {
passwd := utils.RandString(10)
c := &connection.FakeConn{}
ret := Auth(testDB, c, utils.ToBytesList())
asserts.AssertErrReply(t, ret, "ERR wrong number of arguments for 'auth' command")
ret = Auth(testDB, c, utils.ToBytesList(passwd))
asserts.AssertErrReply(t, ret, "ERR Client sent AUTH, but no password is set")
config.Properties.RequirePass = passwd
defer func() {
config.Properties.RequirePass = ""
}()
ret = Auth(testDB, c, utils.ToBytesList(passwd+passwd))
asserts.AssertErrReply(t, ret, "ERR invalid password")
ret = Auth(testDB, c, utils.ToBytesList(passwd))
asserts.AssertStatusReply(t, ret, "OK")
}

View File

@@ -2,7 +2,8 @@ package redis
type Connection interface {
Write([]byte) error
SetPassword(string)
GetPassword() string
// client should keep its subscribing channels
Subscribe(channel string)
UnSubscribe(channel string)

View File

@@ -20,6 +20,9 @@ type Connection struct {
// subscribing channels
subs map[string]bool
// password may be changed by CONFIG command during runtime, so store the password
password string
}
// RemoteAddr returns the remote network address
@@ -97,6 +100,14 @@ func (c *Connection) GetChannels() []string {
return channels
}
func (c *Connection) SetPassword(password string) {
c.password = password
}
func (c *Connection) GetPassword() string {
return c.password
}
type FakeConn struct {
Connection
buf bytes.Buffer