Merge pull request #183 from ukd1/auth-v1

Attempt at Auth :)
This commit is contained in:
siddontang 2015-09-13 10:53:23 +08:00
commit f0dab4218f
8 changed files with 128 additions and 9 deletions

View File

@ -22,6 +22,7 @@ LedisDB now supports multiple different databases as backends.
+ Replication to guarantee data safety. + Replication to guarantee data safety.
+ Supplies tools to load, dump, and repair database. + Supplies tools to load, dump, and repair database.
+ Supports cluster, use [xcodis](https://github.com/siddontang/xcodis) + Supports cluster, use [xcodis](https://github.com/siddontang/xcodis)
+ Authentication (though, not via http)
## Build and Install ## Build and Install

View File

@ -91,6 +91,8 @@ type SnapshotConfig struct {
type Config struct { type Config struct {
m sync.RWMutex `toml:"-"` m sync.RWMutex `toml:"-"`
AuthPassword string `toml:"auth_password"`
FileName string `toml:"-"` FileName string `toml:"-"`
Addr string `toml:"addr"` Addr string `toml:"addr"`
@ -168,6 +170,9 @@ func NewConfigDefault() *Config {
cfg.SlaveOf = "" cfg.SlaveOf = ""
cfg.Readonly = false cfg.Readonly = false
// Disable Auth by default, by setting password to blank
cfg.AuthPassword = ""
// default databases number // default databases number
cfg.Databases = 16 cfg.Databases = 16

View File

@ -25,6 +25,9 @@ slaveof = ""
# for readonly mode, only replication and flushall can write # for readonly mode, only replication and flushall can write
readonly = false readonly = false
# Authentication (for non-http connections). Connect, then use the AUTH command to authenticate.
# auth_password = "russellwashere"
# Choose which backend storage to use, now support: # Choose which backend storage to use, now support:
# #
# leveldb # leveldb

View File

@ -10,9 +10,49 @@ import (
) )
var testAppOnce sync.Once var testAppOnce sync.Once
var testAppAuthOnce sync.Once
var testApp *App var testApp *App
var testLedisClient *goredis.Client var testLedisClient *goredis.Client
var testLedisClientAuth *goredis.Client
func getTestConnAuth(password string) *goredis.PoolConn {
startTestAppAuth(password)
conn, _ := testLedisClientAuth.Get()
return conn
}
func newTestLedisClientAuth() {
testLedisClientAuth = goredis.NewClient("127.0.0.1:20000", "")
testLedisClientAuth.SetMaxIdleConns(4)
}
func startTestAppAuth(password string) {
f := func() {
newTestLedisClientAuth()
cfg := config.NewConfigDefault()
cfg.DataDir = "/tmp/testdb_auth"
os.RemoveAll(cfg.DataDir)
cfg.Addr = "127.0.0.1:20000"
cfg.HttpAddr = "127.0.0.1:20001"
cfg.AuthPassword = password
os.RemoveAll(cfg.DataDir)
var err error
testApp, err = NewApp(cfg)
if err != nil {
println(err.Error())
panic(err)
}
go testApp.Run()
}
testAppAuthOnce.Do(f)
}
func newTestLedisClient() { func newTestLedisClient() {
testLedisClient = goredis.NewClient("127.0.0.1:16380", "") testLedisClient = goredis.NewClient("127.0.0.1:16380", "")
@ -36,7 +76,7 @@ func startTestApp() {
cfg.Addr = "127.0.0.1:16380" cfg.Addr = "127.0.0.1:16380"
cfg.HttpAddr = "127.0.0.1:21181" cfg.HttpAddr = "127.0.0.1:21181"
os.RemoveAll("/tmp/testdb") os.RemoveAll(cfg.DataDir)
var err error var err error
testApp, err = NewApp(cfg) testApp, err = NewApp(cfg)

View File

@ -64,6 +64,8 @@ type client struct {
cmd string cmd string
args [][]byte args [][]byte
isAuthed bool
resp responseWriter resp responseWriter
syncBuf bytes.Buffer syncBuf bytes.Buffer
@ -86,6 +88,7 @@ func newClient(app *App) *client {
c.app = app c.app = app
c.ldb = app.ldb c.ldb = app.ldb
c.isAuthed = false || c.authEnabled()
c.db, _ = app.ldb.Select(0) //use default db c.db, _ = app.ldb.Select(0) //use default db
return c return c
@ -95,6 +98,10 @@ func (c *client) close() {
} }
func (c *client) authEnabled() bool {
return len(c.app.cfg.AuthPassword) > 0
}
func (c *client) perform() { func (c *client) perform() {
var err error var err error
@ -104,6 +111,8 @@ func (c *client) perform() {
err = ErrEmptyCommand err = ErrEmptyCommand
} else if exeCmd, ok := regCmds[c.cmd]; !ok { } else if exeCmd, ok := regCmds[c.cmd]; !ok {
err = ErrNotFound err = ErrNotFound
} else if c.authEnabled() && !c.isAuthed && c.cmd != "auth" {
err = ErrNotAuthenticated
} else { } else {
// if c.db.IsTransaction() { // if c.db.IsTransaction() {
// if _, ok := txUnsupportedCmds[c.cmd]; ok { // if _, ok := txUnsupportedCmds[c.cmd]; ok {
@ -120,7 +129,6 @@ func (c *client) perform() {
// } // }
err = exeCmd(c) err = exeCmd(c)
} }
if c.app.access != nil { if c.app.access != nil {

View File

@ -14,6 +14,21 @@ func pingCommand(c *client) error {
return nil return nil
} }
func authCommand(c *client) error {
if len(c.args) != 1 {
return ErrCmdParams
}
if c.app.cfg.AuthPassword == string(c.args[0]) {
c.isAuthed = true
c.resp.writeStatus(OK)
return nil
} else {
c.isAuthed = false
return ErrAuthenticationFailure
}
}
func echoCommand(c *client) error { func echoCommand(c *client) error {
if len(c.args) != 1 { if len(c.args) != 1 {
return ErrCmdParams return ErrCmdParams
@ -156,6 +171,7 @@ func configCommand(c *client) error {
} }
func init() { func init() {
register("auth", authCommand)
register("ping", pingCommand) register("ping", pingCommand)
register("echo", echoCommand) register("echo", echoCommand)
register("select", selectCommand) register("select", selectCommand)

View File

@ -6,6 +6,50 @@ import (
"github.com/siddontang/goredis" "github.com/siddontang/goredis"
) )
func TestAuth(t *testing.T) {
c1 := getTestConn()
defer c1.Close()
// Should error, no params
_, err := c1.Do("AUTH")
if err == nil {
t.Fatal(err)
}
// Should error, invalid pass
_, err = c1.Do("AUTH", "password")
if err.Error() != " authentication failure" {
t.Fatal("Expected authentication error:", err)
}
c2 := getTestConnAuth("password")
defer c2.Close()
// Login
_, err = c2.Do("AUTH", "password")
if err != nil {
t.Fatal(err)
}
// Should be ok doing a command
_, err = c2.Do("GET", "tmp_select_key")
if err != nil {
t.Fatal(err)
}
// Log out by sending wrong pass
_, err = c2.Do("AUTH", "wrong password")
if err.Error() != " authentication failure" {
t.Fatal("Expected authentication error:", err)
}
// Should fail doing a command as we're logged out
_, err = c2.Do("GET", "tmp_select_key")
if err.Error() != " not authenticated" {
t.Fatal("Expected authentication error:", err)
}
}
func TestXSelect(t *testing.T) { func TestXSelect(t *testing.T) {
c1 := getTestConn() c1 := getTestConn()
defer c1.Close() defer c1.Close()

View File

@ -7,13 +7,15 @@ import (
) )
var ( var (
ErrEmptyCommand = errors.New("empty command") ErrEmptyCommand = errors.New("empty command")
ErrNotFound = errors.New("command not found") ErrNotFound = errors.New("command not found")
ErrCmdParams = errors.New("invalid command param") ErrNotAuthenticated = errors.New("not authenticated")
ErrValue = errors.New("value is not an integer or out of range") ErrAuthenticationFailure = errors.New("authentication failure")
ErrSyntax = errors.New("syntax error") ErrCmdParams = errors.New("invalid command param")
ErrOffset = errors.New("offset bit is not an natural number") ErrValue = errors.New("value is not an integer or out of range")
ErrBool = errors.New("value is not 0 or 1") ErrSyntax = errors.New("syntax error")
ErrOffset = errors.New("offset bit is not an natural number")
ErrBool = errors.New("value is not 0 or 1")
) )
var ( var (