From 5cc687351e026771d846044a41a3b94b3658bf7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20G=C3=B3mez?= Date: Sat, 6 Mar 2021 00:36:18 -0300 Subject: [PATCH] Add backends tests and fix a couple of issues in Redis. --- README.md | 21 ++-- backends/backends.go | 181 +++++++++++++++++--------------- backends/backends_test.go | 182 +++++++++++++++++++++++++++++++++ backends/custom_plugin_test.go | 41 ++++++++ backends/files_test.go | 12 +++ backends/redis.go | 49 ++++++--- backends/redis_test.go | 11 +- go-auth.go | 7 +- plugin/main.go | 2 +- 9 files changed, 398 insertions(+), 108 deletions(-) create mode 100644 backends/backends_test.go create mode 100644 backends/custom_plugin_test.go diff --git a/README.md b/README.md index 48d98bb..ce00844 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,11 @@ # Mosquitto Go Auth Mosquitto Go Auth is an authentication and authorization plugin for the Mosquitto MQTT broker. +The name is terrible, I know, but it's too late to change it. And, you know: naming, cache invalidation, off-by-one errors and whatnot. # Current state -I don't use Mosquitto or any other MQTT broker and haven't in a very long time, nor do I have a need for them or this plugin. -I do maintain it still and will try to keep doing so. This is the list of status, current work and priorities: - - The plugin is up to date and is compatible with the recent [2.0 Mosquitto version](https://mosquitto.org/blog/2020/12/version-2-0-0-released/). -- Delayed work on disabling superusers is not yet ready. - Bug reports will be attended as they appear and will take priority over any work in progress. - Reviewing ongoing PRs is my next priority. - Feature requests are the lowest priority. Unless they are a super easy win in importance and implementation effort, I'll accept contributions and review @@ -16,7 +13,8 @@ I do maintain it still and will try to keep doing so. This is the list of status ### Intro -This is an authentication and authorization plugin for [mosquitto](https://mosquitto.org/), a well known open source MQTT broker. It's written (almost) entirely in Go: it uses `cgo` to expose mosquitto's auth plugin needed functions, but internally just calls Go to get everything done. +This is an authentication and authorization plugin for [mosquitto](https://mosquitto.org/), a well known open source MQTT broker. +It's written (almost) entirely in Go: it uses `cgo` to expose mosquitto's auth plugin needed functions, but internally just calls Go to get everything done. It is greatly inspired in [jpmens'](https://github.com/jpmens) [mosquitto-auth-plug](https://github.com/jpmens/mosquitto-auth-plug). @@ -52,6 +50,7 @@ Please open an issue with the `feature` or `enhancement` tag to request new back - [Log level](#log-level) - [Prefixes](#prefixes) - [Backend options](#backend-options) + - [Registering checks](#registering-checks) - [Files](#files) - [Passwords file](#passwords-file) - [ACL file](#acl-file) @@ -341,7 +340,7 @@ auth_opt_pg_hasher_parallelism # degree of parallelism (i.e. number o #### Logging -You can set the log level with the `log_level` option. Valid values are: debug, info, warn, error, fatal and panic. If not set, default value is `info`. +You can set the log level with the `log_level` option. Valid values are: `debug`, `info`, `warn`, `error`, `fatal` and `panic`. If not set, default value is `info`. ``` auth_opt_log_level debug @@ -356,6 +355,12 @@ auth_opt_log_file /var/log/mosquitto/mosquitto.log If `log_dest` or `log_file` are invalid, or if there's an error opening the file (e.g. no permissions), logging will default to `stderr`. +**Do not, I repeat, do not set `log_level` to `debug` in production, it may leak sensitive information.** +**Reason? When debugging it's quite useful to log actual passwords, hashes, etc. to check which backend or hasher is failing to do its job.** +**This should be used only when debugging locally, I can't stress enough how log level should never, ever be set to `debug` in production.** + +**You've been warned.** + #### Retry By default, if backend had an error (and no other backend granted access), an error is returned to Mosquitto. @@ -445,7 +450,7 @@ make test ### Registering checks -Backends may register which checks they'll run, enabling the option to e.g. only check user auth through an HTTP backend while delegating ACL to another backend, e.g. Files. +Backends may register which checks they'll run, enabling the option to only check user auth through some backends, for example an HTTP one, while delegating ACL checks to another backend, e.g. Files. By default, when the option is not present, all checks for that backend will be enabled (unless `superuser` is globally disabled in the case of `superuser` checks). For `user` and `acl` checks, at least one backend needs to be registered, either explicitly or by default. @@ -456,7 +461,7 @@ auth_opt_files_register user, acl auth_opt_redis_register superuser ``` -Possible values for checks are `user`, `superuser` and `acl`. Any other value will result in an error initializing the plugin. +Possible values for checks are `user`, `superuser` and `acl`. Any other value will result in an error on plugin initialization. ### Files diff --git a/backends/backends.go b/backends/backends.go index 91551b7..4cf82ee 100644 --- a/backends/backends.go +++ b/backends/backends.go @@ -31,7 +31,7 @@ type Backends struct { } const ( - //backends + // backends postgresBackend = "postgres" jwtBackend = "jwt" redisBackend = "redis" @@ -44,7 +44,7 @@ const ( grpcBackend = "grpc" jsBackend = "js" - //checks + // checks aclCheck = "acl" userCheck = "user" superuserCheck = "superuser" @@ -66,7 +66,7 @@ var AllowedBackendsOptsPrefix = map[string]string{ } // Initialize sets general options, tries to build the backends and register their checkers. -func Initialize(authOpts map[string]string, logLevel log.Level, backends []string) *Backends { +func Initialize(authOpts map[string]string, logLevel log.Level, backends []string) (*Backends, error) { b := &Backends{ backends: make(map[string]Backend), @@ -82,17 +82,26 @@ func Initialize(authOpts map[string]string, logLevel log.Level, backends []strin b.disableSuperuser = true } + err := b.addBackends(authOpts, logLevel, backends) + if err != nil { + return nil, err + } + + err = b.setCheckers(authOpts) + if err != nil { + return nil, err + } + + b.setPrefixes(authOpts, backends) + + return b, nil +} + +func (b *Backends) addBackends(authOpts map[string]string, logLevel log.Level, backends []string) error { for _, bename := range backends { var beIface Backend var err error - /* - TODO: this could be nicer if we had a initializer map, e.g.: - var initializers map[string] func(authOpts map[string]string, logLevel log.Level, hasher hashing.Hasher) (Backend, error) - - Sadly, not all backends require a hasher and I'm not sure about changing them to accept a dummy one just for the sake of this. - But I'll keep this comment for further thought and to remind me about changing those that don't return a pointer to do so. - */ hasher := hashing.NewHasher(authOpts, AllowedBackendsOptsPrefix[bename]) switch bename { case postgresBackend: @@ -183,13 +192,12 @@ func Initialize(authOpts map[string]string, logLevel log.Level, backends []strin log.Infof("Backend registered: %s", beIface.GetName()) b.backends[pluginBackend] = beIface.(*CustomPlugin) } + default: + return fmt.Errorf("unkown backend %s", bename) } } - b.setCheckers(authOpts) - b.setPrefixes(authOpts, backends) - - return b + return nil } func (b *Backends) setCheckers(authOpts map[string]string) error { @@ -206,18 +214,24 @@ func (b *Backends) setCheckers(authOpts map[string]string) error { switch check { case aclCheck: b.aclCheckers = append(b.aclCheckers, name) + log.Infof("registered acl checker: %s", name) case userCheck: b.userCheckers = append(b.userCheckers, name) + log.Infof("registered user checker: %s", name) case superuserCheck: b.superuserCheckers = append(b.superuserCheckers, name) + log.Infof("registered superuser checker: %s", name) default: - return fmt.Errorf("unsupported check %s found for backend %s, skipping registration", check, name) + return fmt.Errorf("unsupported check %s found for backend %s", check, name) } } } else { b.aclCheckers = append(b.aclCheckers, name) + log.Infof("registered acl checker: %s", name) b.userCheckers = append(b.userCheckers, name) + log.Infof("registered user checker: %s", name) b.superuserCheckers = append(b.superuserCheckers, name) + log.Infof("registered superuser checker: %s", name) } } @@ -235,11 +249,13 @@ func (b *Backends) setCheckers(authOpts map[string]string) error { // setPrefixes sets options for prefixes handling. func (b *Backends) setPrefixes(authOpts map[string]string, backends []string) { if checkPrefix, ok := authOpts["check_prefix"]; ok && strings.Replace(checkPrefix, " ", "", -1) == "true" { - //Check that backends match prefixes. + // Check that backends match prefixes. if prefixesStr, ok := authOpts["prefixes"]; ok { prefixes := strings.Split(strings.Replace(prefixesStr, " ", "", -1), ",") if len(prefixes) == len(backends) { - //Set prefixes + // Set prefixes + // (I know some people find this type of comments useless, even harmful, + // but I find them helpful for quick code navigation on a project I don't work on daily, so screw them). for i, backend := range backends { b.prefixes[prefixes[i]] = backend } @@ -296,32 +312,31 @@ func (b *Backends) AuthUnpwdCheck(username, password, clientid string) (bool, er var authenticated bool var err error - //If prefixes are enabled, check if username has a valid prefix and use the correct backend if so. - if b.checkPrefix { - validPrefix, bename := b.lookupPrefix(username) + // If prefixes are enabled, check if username has a valid prefix and use the correct backend if so. + if !b.checkPrefix { + return b.checkAuth(username, password, clientid) + } - if !checkRegistered(bename, b.userCheckers) { - return false, fmt.Errorf("backends %s not registered to check users", bename) - } + validPrefix, bename := b.lookupPrefix(username) - if validPrefix { - // If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix it will be handled in the common case. - if bename == jwtBackend { - prefix := b.getPrefixForBackend(bename) - username = strings.TrimPrefix(username, prefix+"_") - } - var backend = b.backends[bename] + if !checkRegistered(bename, b.userCheckers) { + return false, fmt.Errorf("backend %s not registered to check users", bename) + } - authenticated, err = backend.GetUser(username, password, clientid) - if authenticated && err == nil { - log.Debugf("user %s authenticated with backend %s", username, backend.GetName()) - } - } else { - //If there's no valid prefix, check all backends. - authenticated, err = b.checkAuth(username, password, clientid) - } - } else { - authenticated, err = b.checkAuth(username, password, clientid) + if !validPrefix { + return b.checkAuth(username, password, clientid) + } + + // If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix it will be handled in the common case. + if bename == jwtBackend { + prefix := b.getPrefixForBackend(bename) + username = strings.TrimPrefix(username, prefix+"_") + } + var backend = b.backends[bename] + + authenticated, err = backend.GetUser(username, password, clientid) + if authenticated && err == nil { + log.Debugf("user %s authenticated with backend %s", username, backend.GetName()) } return authenticated, err @@ -354,56 +369,56 @@ func (b *Backends) checkAuth(username, password, clientid string) (bool, error) return authenticated, err } -// AuthAclCheck checks user/topic/acc authentication. +// AuthAclCheck checks user/topic/acc authorization. func (b *Backends) AuthAclCheck(clientid, username, topic string, acc int) (bool, error) { var aclCheck bool var err error - //If prefixes are enabled, check if username has a valid prefix and use the correct backend if so. - //Else, check all backends. - if b.checkPrefix { - validPrefix, bename := b.lookupPrefix(username) - if validPrefix { - // If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix then it be handled in the common case. - if bename == jwtBackend { - prefix := b.getPrefixForBackend(bename) - username = strings.TrimPrefix(username, prefix+"_") - } - var backend = b.backends[bename] + // If prefixes are enabled, check if username has a valid prefix and use the correct backend if so. + // Else, check all backends. + if !b.checkPrefix { + return b.checkAcl(username, topic, clientid, acc) + } - // Short circuit checks when superusers are disabled. - if !b.disableSuperuser { - log.Debugf("Superuser check with backend %s", backend.GetName()) - if !checkRegistered(bename, b.superuserCheckers) { - return false, fmt.Errorf("backends %s not registered to check superusers", bename) - } + validPrefix, bename := b.lookupPrefix(username) - aclCheck, err = backend.GetSuperuser(username) + if !validPrefix { + return b.checkAcl(username, topic, clientid, acc) + } - if aclCheck && err == nil { - log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName()) - } - } - //If not superuser, check acl. - if !aclCheck { - if !checkRegistered(bename, b.aclCheckers) { - return false, fmt.Errorf("backends %s not registered to check superusers", bename) - } + // If the backend is JWT and the token was prefixed, then strip the token. If the token was passed without a prefix then let it be handled in the common case. + if bename == jwtBackend { + prefix := b.getPrefixForBackend(bename) + username = strings.TrimPrefix(username, prefix+"_") + } + var backend = b.backends[bename] - log.Debugf("Acl check with backend %s", backend.GetName()) - if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil { - aclCheck = true - log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName()) - } else if checkACLErr != nil && err == nil { - err = checkACLErr - } - } - } else { - //If there's no valid prefix, check all backends. - aclCheck, err = b.checkAcl(username, topic, clientid, acc) + // Short circuit checks when superusers are disabled. + if !b.disableSuperuser { + log.Debugf("Superuser check with backend %s", backend.GetName()) + if !checkRegistered(bename, b.superuserCheckers) { + return false, fmt.Errorf("backend %s not registered to check superusers", bename) + } + + aclCheck, err = backend.GetSuperuser(username) + + if aclCheck && err == nil { + log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName()) + } + } + // If not superuser, check acl. + if !aclCheck { + if !checkRegistered(bename, b.aclCheckers) { + return false, fmt.Errorf("backend %s not registered to check superusers", bename) + } + + log.Debugf("Acl check with backend %s", backend.GetName()) + if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil { + aclCheck = true + log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName()) + } else if checkACLErr != nil && err == nil { + err = checkACLErr } - } else { - aclCheck, err = b.checkAcl(username, topic, clientid, acc) } log.Debugf("Acl is %t for user %s", aclCheck, username) @@ -411,7 +426,7 @@ func (b *Backends) AuthAclCheck(clientid, username, topic string, acc int) (bool } func (b *Backends) checkAcl(username, topic, clientid string, acc int) (bool, error) { - //Check superusers first + // Check superusers first var err error aclCheck := false if !b.disableSuperuser { @@ -454,7 +469,7 @@ func (b *Backends) checkAcl(username, topic, clientid string, acc int) (bool, er } func (b *Backends) Halt() { - //Halt every registered backend. + // Halt every registered backend. for _, v := range b.backends { v.Halt() } diff --git a/backends/backends_test.go b/backends/backends_test.go new file mode 100644 index 0000000..8b28175 --- /dev/null +++ b/backends/backends_test.go @@ -0,0 +1,182 @@ +package backends + +import ( + "context" + "path/filepath" + "testing" + + "github.com/iegomez/mosquitto-go-auth/hashing" + log "github.com/sirupsen/logrus" + . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/assert" +) + +func TestBackends(t *testing.T) { + /* + No way we're gonna test every possibility given the amount of backends, + let's just make a sanity check for relevant functionality: + + - Test there must be at least one user and acl checker. + - Test backend is valid. + - Test non registered checks are skipped. + - Test checking user and acls from different backends works. + - Test initialization actually returns a useful initialized struct. + + */ + + authOpts := make(map[string]string) + + pwPath, _ := filepath.Abs("../test-files/passwords") + aclPath, _ := filepath.Abs("../test-files/acls") + + authOpts["password_path"] = pwPath + authOpts["acl_path"] = aclPath + + authOpts["redis_host"] = "localhost" + authOpts["redis_port"] = "6379" + authOpts["redis_db"] = "2" + authOpts["redis_password"] = "" + + backends := []string{"files", "redis"} + + username := "test1" + password := "test1" + passwordHash := "PBKDF2$sha512$100000$2WQHK5rjNN+oOT+TZAsWAw==$TDf4Y6J+9BdnjucFQ0ZUWlTwzncTjOOeE00W4Qm8lfPQyPCZACCjgfdK353jdGFwJjAf6vPAYaba9+z4GWK7Gg==" + clientid := "clientid" + + Convey("An unknown backend should result in an error", t, func() { + backends := []string{"unknown"} + + authOpts["backends"] = "unkown" + + _, err := Initialize(authOpts, log.DebugLevel, backends) + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "unkown backend unknown") + }) + + Convey("On initialization, lacking user/acl checkers should result in an error", t, func() { + authOpts["backends"] = "files, redis" + authOpts["files_register"] = "user" + authOpts["redis_register"] = "user" + + _, err := Initialize(authOpts, log.DebugLevel, backends) + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "no backend registered ACL checks") + + authOpts["backends"] = "files, redis" + authOpts["files_register"] = "acl" + authOpts["redis_register"] = "acl" + + _, err = Initialize(authOpts, log.DebugLevel, backends) + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "no backend registered user checks") + }) + + Convey("On initialization, unknown checkers should result in an error", t, func() { + authOpts["backends"] = "files, redis" + authOpts["files_register"] = "user" + authOpts["redis_register"] = "unknown" + + _, err := Initialize(authOpts, log.DebugLevel, backends) + So(err, ShouldNotBeNil) + So(err.Error(), ShouldEqual, "unsupported check unknown found for backend redis") + }) + + Convey("We should be able to auth users with one backend and acls with a different one", t, func() { + authOpts["backends"] = "files, redis" + authOpts["files_register"] = "acl" + authOpts["redis_register"] = "user" + + redis, err := NewRedis(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "redis")) + assert.Nil(t, err) + + ctx := context.Background() + + // Insert a user to test auth + username = "test1" + redis.conn.Set(ctx, username, passwordHash, 0) + + b, err := Initialize(authOpts, log.DebugLevel, backends) + So(err, ShouldBeNil) + + // Redis only contains test1, while files has a bunch of more users. + // Since Files only registers acl checks, those users should fail. + tt1, err1 := b.checkAuth(username, password, clientid) + tt2, err2 := b.checkAuth("test2", "test2", clientid) + + So(err1, ShouldBeNil) + So(tt1, ShouldBeTrue) + So(err2, ShouldBeNil) + So(tt2, ShouldBeFalse) + + /* + Files grants these to user test1: + + user test1 + topic write test/topic/1 + topic read test/topic/2 + topic readwrite readwrite/topic + + So if we add test/redis topic to Redis, the user should not have permission because acl chekcs are done by Files only. + + */ + + redis.conn.SAdd(ctx, username+":racls", "test/redis") + + aclCheck, err := b.checkAcl(username, "test/redis", clientid, 1) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeFalse) + + aclCheck, err = b.checkAcl(username, "test/topic/1", clientid, 2) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeTrue) + }) + + Convey("When not registering checks, all of them should be available", t, func() { + authOpts["backends"] = "files, redis" + delete(authOpts, "files_register") + delete(authOpts, "redis_register") + + redis, err := NewRedis(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "redis")) + assert.Nil(t, err) + + ctx := context.Background() + + // Insert a user to test auth + username = "test1" + redis.conn.Set(ctx, username, passwordHash, 0) + + b, err := Initialize(authOpts, log.DebugLevel, backends) + So(err, ShouldBeNil) + + tt1, err1 := b.checkAuth(username, password, clientid) + tt2, err2 := b.checkAuth("test2", "test2", clientid) + + So(err1, ShouldBeNil) + So(tt1, ShouldBeTrue) + So(err2, ShouldBeNil) + So(tt2, ShouldBeTrue) + + /* + Files grants these to user test1: + + user test1 + topic write test/topic/1 + topic read test/topic/2 + topic readwrite readwrite/topic + + Now the user should have permission for the redis topic since all backends do acl checks. + + */ + + redis.conn.SAdd(ctx, username+":racls", "test/redis") + + aclCheck, err := b.checkAcl(username, "test/redis", clientid, 1) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeTrue) + + aclCheck, err = b.checkAcl(username, "test/topic/1", clientid, 2) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeTrue) + }) +} diff --git a/backends/custom_plugin_test.go b/backends/custom_plugin_test.go new file mode 100644 index 0000000..c1f5647 --- /dev/null +++ b/backends/custom_plugin_test.go @@ -0,0 +1,41 @@ +package backends + +import ( + "testing" + + log "github.com/sirupsen/logrus" + . "github.com/smartystreets/goconvey/convey" +) + +func TestCustomPlugin(t *testing.T) { + // There's not much to test other than it loads and calls the functions as expected. + authOpts := map[string]string{ + "plugin_path": "../plugin/plugin.so", + } + + username := "user" + password := "password" + clientid := "clientid" + topic := "topic" + acc := int32(1) + + Convey("Loading dummy plugin should work", t, func() { + plugin, err := NewCustomPlugin(authOpts, log.DebugLevel) + So(err, ShouldBeNil) + + userCheck, err := plugin.GetUser(username, password, clientid) + So(err, ShouldBeNil) + So(userCheck, ShouldBeFalse) + + superuserCheck, err := plugin.getSuperuser(username) + So(err, ShouldBeNil) + So(superuserCheck, ShouldBeFalse) + + aclCheck, err := plugin.CheckAcl(username, topic, clientid, acc) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeFalse) + + name := plugin.GetName() + So(name, ShouldEqual, "Custom plugin") + }) +} diff --git a/backends/files_test.go b/backends/files_test.go index 34802ed..be2c2a2 100644 --- a/backends/files_test.go +++ b/backends/files_test.go @@ -131,6 +131,18 @@ func TestFiles(t *testing.T) { authenticated, err := files.GetUser(user1, user1, clientID) So(err, ShouldBeNil) So(authenticated, ShouldBeTrue) + + authenticated, err = files.GetUser(user2, user2, clientID) + So(err, ShouldBeNil) + So(authenticated, ShouldBeTrue) + + authenticated, err = files.GetUser(user3, user3, clientID) + So(err, ShouldBeNil) + So(authenticated, ShouldBeTrue) + + authenticated, err = files.GetUser(elton, elton, clientID) + So(err, ShouldBeNil) + So(authenticated, ShouldBeTrue) }) Convey("Given a username and an incorrect password, it should not authenticate it", func() { diff --git a/backends/redis.go b/backends/redis.go index 29a6c8d..0fa5c26 100644 --- a/backends/redis.go +++ b/backends/redis.go @@ -163,7 +163,9 @@ func (o Redis) GetUser(username, password, _ string) (bool, error) { func (o Redis) getUser(username, password string) (bool, error) { pwHash, err := o.conn.Get(o.ctx, username).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } @@ -200,12 +202,15 @@ func (o Redis) GetSuperuser(username string) (bool, error) { if err != nil { log.Debugf("redis get superuser error: %s", err) } + return ok, err } func (o Redis) getSuperuser(username string) (bool, error) { isSuper, err := o.conn.Get(o.ctx, fmt.Sprintf("%s:su", username)).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } @@ -252,34 +257,46 @@ func (o Redis) checkAcl(username, topic, clientid string, acc int32) (bool, erro //Get all user subscribe acls. var err error acls, err = o.conn.SMembers(o.ctx, fmt.Sprintf("%s:sacls", username)).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } //Get common subscribe acls. commonAcls, err = o.conn.SMembers(o.ctx, "common:sacls").Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } case MOSQ_ACL_READ: //Get all user read and readwrite acls. urAcls, err := o.conn.SMembers(o.ctx, fmt.Sprintf("%s:racls", username)).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } urwAcls, err := o.conn.SMembers(o.ctx, fmt.Sprintf("%s:rwacls", username)).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } //Get common read and readwrite acls rAcls, err := o.conn.SMembers(o.ctx, "common:racls").Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } rwAcls, err := o.conn.SMembers(o.ctx, "common:rwacls").Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } @@ -293,21 +310,29 @@ func (o Redis) checkAcl(username, topic, clientid string, acc int32) (bool, erro case MOSQ_ACL_WRITE: //Get all user write and readwrite acls. uwAcls, err := o.conn.SMembers(o.ctx, fmt.Sprintf("%s:wacls", username)).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } urwAcls, err := o.conn.SMembers(o.ctx, fmt.Sprintf("%s:rwacls", username)).Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } //Get common write and readwrite acls wAcls, err := o.conn.SMembers(o.ctx, "common:wacls").Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } rwAcls, err := o.conn.SMembers(o.ctx, "common:rwacls").Result() - if err != nil { + if err == goredis.Nil { + return false, nil + } else if err != nil { return false, err } diff --git a/backends/redis_test.go b/backends/redis_test.go index faad8e9..27e6e7c 100644 --- a/backends/redis_test.go +++ b/backends/redis_test.go @@ -57,13 +57,18 @@ func testRedis(ctx context.Context, t *testing.T, authOpts map[string]string) { assert.Nil(t, err) assert.False(t, authenticated) - redis.conn.Set(ctx, username+":su", "true", 0) - superuser, err := redis.GetSuperuser(username) + authenticated, err = redis.GetUser("wrong-user", userPass, "") + assert.Nil(t, err) + assert.False(t, authenticated) + + redis.conn.Set(ctx, "superuser", userPassHash, 0) + redis.conn.Set(ctx, "superuser:su", "true", 0) + superuser, err := redis.GetSuperuser("superuser") assert.Nil(t, err) assert.True(t, superuser) redis.disableSuperuser = true - superuser, err = redis.GetSuperuser(username) + superuser, err = redis.GetSuperuser("superuser") assert.Nil(t, err) assert.False(t, superuser) diff --git a/go-auth.go b/go-auth.go index 4cbe602..c1db5eb 100644 --- a/go-auth.go +++ b/go-auth.go @@ -124,7 +124,12 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) { } } - authPlugin.backends = bes.Initialize(authOpts, authPlugin.logLevel, backends) + var err error + + authPlugin.backends, err = bes.Initialize(authOpts, authPlugin.logLevel, backends) + if err != nil { + log.Fatalf("error initializing backends: %s", err) + } if cache, ok := authOpts["cache"]; ok && strings.Replace(cache, " ", "", -1) == "true" { log.Info("redisCache activated") diff --git a/plugin/main.go b/plugin/main.go index 005a637..d7b4836 100644 --- a/plugin/main.go +++ b/plugin/main.go @@ -21,7 +21,7 @@ func GetSuperuser(username string) (bool, error) { return false, nil } -func CheckAcl(username, topic, clientid string, acc int) (bool, error) { +func CheckAcl(username, topic, clientid string, acc int32) (bool, error) { log.Debugf("Checking acl with custom plugin.") return false, nil }