mirror of
https://github.com/HDT3213/godis.git
synced 2025-10-05 16:57:06 +08:00
support auth
This commit is contained in:
@@ -10,8 +10,6 @@
|
|||||||
`Godis` is a simple implementation of Redis Server, which intents to provide an example of writing a high concurrent
|
`Godis` is a simple implementation of Redis Server, which intents to provide an example of writing a high concurrent
|
||||||
middleware using golang.
|
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.
|
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:
|
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:
|
||||||
|
@@ -3,6 +3,8 @@ package cluster
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
|
"github.com/hdt3213/godis/config"
|
||||||
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
"github.com/hdt3213/godis/redis/client"
|
"github.com/hdt3213/godis/redis/client"
|
||||||
"github.com/jolestar/go-commons-pool/v2"
|
"github.com/jolestar/go-commons-pool/v2"
|
||||||
)
|
)
|
||||||
@@ -17,6 +19,10 @@ func (f *ConnectionFactory) MakeObject(ctx context.Context) (*pool.PooledObject,
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
c.Start()
|
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
|
return pool.NewPooledObject(c), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -79,6 +79,13 @@ func (cluster *Cluster) Close() {
|
|||||||
|
|
||||||
var router = MakeRouter()
|
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) {
|
func (cluster *Cluster) Exec(c redis.Connection, args [][]byte) (result redis.Reply) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
@@ -86,8 +93,13 @@ func (cluster *Cluster) Exec(c redis.Connection, args [][]byte) (result redis.Re
|
|||||||
result = &reply.UnknownErrReply{}
|
result = &reply.UnknownErrReply{}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
cmd := strings.ToLower(string(args[0]))
|
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]
|
cmdFunc, ok := router[cmd]
|
||||||
if !ok {
|
if !ok {
|
||||||
return reply.MakeErrReply("ERR unknown command '" + cmd + "', or not supported in cluster mode")
|
return reply.MakeErrReply("ERR unknown command '" + cmd + "', or not supported in cluster mode")
|
||||||
|
@@ -1,6 +1,9 @@
|
|||||||
package cluster
|
package cluster
|
||||||
|
|
||||||
import (
|
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"
|
"github.com/hdt3213/godis/redis/reply/asserts"
|
||||||
"testing"
|
"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) {
|
func TestRelay(t *testing.T) {
|
||||||
testCluster2 := MakeTestCluster([]string{"127.0.0.1:6379"})
|
testCluster2 := MakeTestCluster([]string{"127.0.0.1:6379"})
|
||||||
key := RandString(4)
|
key := RandString(4)
|
||||||
|
@@ -11,13 +11,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type PropertyHolder struct {
|
type PropertyHolder struct {
|
||||||
Bind string `cfg:"bind"`
|
Bind string `cfg:"bind"`
|
||||||
Port int `cfg:"port"`
|
Port int `cfg:"port"`
|
||||||
AppendOnly bool `cfg:"appendOnly"`
|
AppendOnly bool `cfg:"appendOnly"`
|
||||||
AppendFilename string `cfg:"appendFilename"`
|
AppendFilename string `cfg:"appendFilename"`
|
||||||
MaxClients int `cfg:"maxclients"`
|
MaxClients int `cfg:"maxclients"`
|
||||||
Peers []string `cfg:"peers"`
|
RequirePass string `cfg:"requirepass"`
|
||||||
Self string `cfg:"self"`
|
|
||||||
|
Peers []string `cfg:"peers"`
|
||||||
|
Self string `cfg:"self"`
|
||||||
}
|
}
|
||||||
|
|
||||||
var Properties *PropertyHolder
|
var Properties *PropertyHolder
|
||||||
|
7
db/db.go
7
db/db.go
@@ -112,7 +112,12 @@ func (db *DB) Exec(c redis.Connection, args [][]byte) (result redis.Reply) {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
cmd := strings.ToLower(string(args[0]))
|
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
|
// special commands
|
||||||
if cmd == "subscribe" {
|
if cmd == "subscribe" {
|
||||||
if len(args) < 2 {
|
if len(args) < 2 {
|
||||||
|
24
db/server.go
24
db/server.go
@@ -1,6 +1,7 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hdt3213/godis/config"
|
||||||
"github.com/hdt3213/godis/interface/redis"
|
"github.com/hdt3213/godis/interface/redis"
|
||||||
"github.com/hdt3213/godis/redis/reply"
|
"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")
|
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
|
||||||
|
}
|
@@ -1,7 +1,9 @@
|
|||||||
package db
|
package db
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/hdt3213/godis/config"
|
||||||
"github.com/hdt3213/godis/lib/utils"
|
"github.com/hdt3213/godis/lib/utils"
|
||||||
|
"github.com/hdt3213/godis/redis/connection"
|
||||||
"github.com/hdt3213/godis/redis/reply/asserts"
|
"github.com/hdt3213/godis/redis/reply/asserts"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
@@ -15,3 +17,22 @@ func TestPing(t *testing.T) {
|
|||||||
actual = Ping(testDB, utils.ToBytesList(val, val))
|
actual = Ping(testDB, utils.ToBytesList(val, val))
|
||||||
asserts.AssertErrReply(t, actual, "ERR wrong number of arguments for 'ping' command")
|
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")
|
||||||
|
|
||||||
|
}
|
@@ -2,7 +2,8 @@ package redis
|
|||||||
|
|
||||||
type Connection interface {
|
type Connection interface {
|
||||||
Write([]byte) error
|
Write([]byte) error
|
||||||
|
SetPassword(string)
|
||||||
|
GetPassword() string
|
||||||
// client should keep its subscribing channels
|
// client should keep its subscribing channels
|
||||||
Subscribe(channel string)
|
Subscribe(channel string)
|
||||||
UnSubscribe(channel string)
|
UnSubscribe(channel string)
|
||||||
|
@@ -20,6 +20,9 @@ type Connection struct {
|
|||||||
|
|
||||||
// subscribing channels
|
// subscribing channels
|
||||||
subs map[string]bool
|
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
|
// RemoteAddr returns the remote network address
|
||||||
@@ -97,6 +100,14 @@ func (c *Connection) GetChannels() []string {
|
|||||||
return channels
|
return channels
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Connection) SetPassword(password string) {
|
||||||
|
c.password = password
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Connection) GetPassword() string {
|
||||||
|
return c.password
|
||||||
|
}
|
||||||
|
|
||||||
type FakeConn struct {
|
type FakeConn struct {
|
||||||
Connection
|
Connection
|
||||||
buf bytes.Buffer
|
buf bytes.Buffer
|
||||||
@@ -113,4 +124,4 @@ func (c *FakeConn) Clean() {
|
|||||||
|
|
||||||
func (c *FakeConn) Bytes() []byte {
|
func (c *FakeConn) Bytes() []byte {
|
||||||
return c.buf.Bytes()
|
return c.buf.Bytes()
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user