Refactor backends to take all the init and checking logic out of the main package, add options to enable registering specific checks per backend.

This commit is contained in:
Ignacio Gómez 2021-03-05 22:52:42 -03:00
parent 82ca3fc6a1
commit ca22c6f9fa
No known key found for this signature in database
GPG Key ID: 15A77C6BEC604B06
5 changed files with 633 additions and 535 deletions

View File

@ -443,6 +443,21 @@ You may run all tests (see Testing X for each backend's testing requirements) li
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.
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.
You may register which checks a backend will perform with the option `auth_opt_backend_register` followed by comma separated values of the registered checks, e.g.:
```
auth_opt_http_register user
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.
### Files
@ -1254,15 +1269,15 @@ func Init(authOpts map[string]string, logLevel log.Level) error {
return nil
}
func GetUser(username, password, clientid string) bool {
func GetUser(username, password, clientid string) (bool, error) {
return false
}
func GetSuperuser(username string) bool {
func GetSuperuser(username string) (bool, error) {
return false
}
func CheckAcl(username, topic, clientid string, acc int) bool {
func CheckAcl(username, topic, clientid string, acc int) (bool, error) {
return false
}

461
backends/backends.go Normal file
View File

@ -0,0 +1,461 @@
package backends
import (
"fmt"
"strings"
"github.com/iegomez/mosquitto-go-auth/hashing"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)
type Backend interface {
GetUser(username, password, clientid string) (bool, error)
GetSuperuser(username string) (bool, error)
CheckAcl(username, topic, clientId string, acc int32) (bool, error)
GetName() string
Halt()
}
type Backends struct {
backends map[string]Backend
aclCheckers []string
userCheckers []string
superuserCheckers []string
checkPrefix bool
prefixes map[string]string
disableSuperuser bool
}
const (
//backends
postgresBackend = "postgres"
jwtBackend = "jwt"
redisBackend = "redis"
httpBackend = "http"
filesBackend = "files"
mysqlBackend = "mysql"
sqliteBackend = "sqlite"
mongoBackend = "mongo"
pluginBackend = "plugin"
grpcBackend = "grpc"
jsBackend = "js"
//checks
aclCheck = "acl"
userCheck = "user"
superuserCheck = "superuser"
)
// AllowedBackendsOptsPrefix serves as a check for allowed backends and a map from backend to expected opts prefix.
var AllowedBackendsOptsPrefix = map[string]string{
postgresBackend: "pg",
jwtBackend: "jwt",
redisBackend: "redis",
httpBackend: "http",
filesBackend: "files",
mysqlBackend: "mysql",
sqliteBackend: "sqlite",
mongoBackend: "mongo",
pluginBackend: "plugin",
grpcBackend: "grpc",
jsBackend: "js",
}
// 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 {
b := &Backends{
backends: make(map[string]Backend),
aclCheckers: make([]string, 0),
userCheckers: make([]string, 0),
superuserCheckers: make([]string, 0),
checkPrefix: false,
prefixes: make(map[string]string),
}
//Disable superusers for all backends if option is set.
if authOpts["disable_superuser"] == "true" {
b.disableSuperuser = true
}
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:
beIface, err = NewPostgres(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("backend registered: %s", beIface.GetName())
b.backends[postgresBackend] = beIface.(Postgres)
}
case jwtBackend:
beIface, err = NewJWT(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[jwtBackend] = beIface.(*JWT)
}
case filesBackend:
beIface, err = NewFiles(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[filesBackend] = beIface.(*Files)
}
case redisBackend:
beIface, err = NewRedis(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[redisBackend] = beIface.(Redis)
}
case mysqlBackend:
beIface, err = NewMysql(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[mysqlBackend] = beIface.(Mysql)
}
case httpBackend:
beIface, err = NewHTTP(authOpts, logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[httpBackend] = beIface.(HTTP)
}
case sqliteBackend:
beIface, err = NewSqlite(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[sqliteBackend] = beIface.(Sqlite)
}
case mongoBackend:
beIface, err = NewMongo(authOpts, logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[mongoBackend] = beIface.(Mongo)
}
case grpcBackend:
beIface, err = NewGRPC(authOpts, logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[grpcBackend] = beIface.(GRPC)
}
case jsBackend:
beIface, err = NewJavascript(authOpts, logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[jsBackend] = beIface.(*Javascript)
}
case pluginBackend:
beIface, err = NewCustomPlugin(authOpts, logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
b.backends[pluginBackend] = beIface.(*CustomPlugin)
}
}
}
b.setCheckers(authOpts)
b.setPrefixes(authOpts, backends)
return b
}
func (b *Backends) setCheckers(authOpts map[string]string) error {
// We'll register which plugins will perform checks for user, superuser and acls.
// At least one backend must be registered for user and acl checks.
// When option auth_opt_backend_register is missing for the backend, we register all checks.
for name := range b.backends {
opt := fmt.Sprintf("%s_register", AllowedBackendsOptsPrefix[name])
options, ok := authOpts[opt]
if ok {
checkers := strings.Fields(options)
for _, check := range checkers {
switch check {
case aclCheck:
b.aclCheckers = append(b.aclCheckers, name)
case userCheck:
b.userCheckers = append(b.userCheckers, name)
case superuserCheck:
b.superuserCheckers = append(b.superuserCheckers, name)
default:
return fmt.Errorf("unsupported check %s found for backend %s, skipping registration", check, name)
}
}
} else {
b.aclCheckers = append(b.aclCheckers, name)
b.userCheckers = append(b.userCheckers, name)
b.superuserCheckers = append(b.superuserCheckers, name)
}
}
if len(b.userCheckers) == 0 {
return errors.New("no backend registered user checks")
}
if len(b.aclCheckers) == 0 {
return errors.New("no backend registered ACL checks")
}
return nil
}
// 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.
if prefixesStr, ok := authOpts["prefixes"]; ok {
prefixes := strings.Split(strings.Replace(prefixesStr, " ", "", -1), ",")
if len(prefixes) == len(backends) {
//Set prefixes
for i, backend := range backends {
b.prefixes[prefixes[i]] = backend
}
log.Infof("prefixes enabled for backends %s with prefixes %s.", authOpts["backends"], authOpts["prefixes"])
b.checkPrefix = true
} else {
log.Errorf("Error: got %d backends and %d prefixes, defaulting to prefixes disabled.", len(backends), len(prefixes))
b.checkPrefix = false
}
} else {
log.Warn("Error: prefixes enabled but no options given, defaulting to prefixes disabled.")
b.checkPrefix = false
}
} else {
b.checkPrefix = false
}
}
// checkPrefix checks if a username contains a valid prefix. If so, returns ok and the suitable backend name; else, !ok and empty string.
func (b *Backends) lookupPrefix(username string) (bool, string) {
if strings.Index(username, "_") > 0 {
userPrefix := username[0:strings.Index(username, "_")]
if prefix, ok := b.prefixes[userPrefix]; ok {
log.Debugf("Found prefix for user %s, using backend %s.", username, prefix)
return true, prefix
}
}
return false, ""
}
// getPrefixForBackend retrieves the user provided prefix for a given backend.
func (b *Backends) getPrefixForBackend(backend string) string {
for k, v := range b.prefixes {
if v == backend {
return k
}
}
return ""
}
func checkRegistered(bename string, checkers []string) bool {
for _, b := range checkers {
if b == bename {
return true
}
}
return false
}
// AuthUnpwdCheck checks user authentication.
func (b *Backends) AuthUnpwdCheck(username, password, clientid string) (bool, error) {
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 !checkRegistered(bename, b.userCheckers) {
return false, fmt.Errorf("backends %s not registered to check users", bename)
}
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]
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)
}
return authenticated, err
}
func (b *Backends) checkAuth(username, password, clientid string) (bool, error) {
var err error
authenticated := false
for _, bename := range b.userCheckers {
var backend = b.backends[bename]
log.Debugf("checking user %s with backend %s", username, backend.GetName())
if ok, getUserErr := backend.GetUser(username, password, clientid); ok && getUserErr == nil {
authenticated = true
log.Debugf("user %s authenticated with backend %s", username, backend.GetName())
break
} else if getUserErr != nil && err == nil {
err = getUserErr
}
}
// If authenticated is true, it means at least one backend didn't fail and
// accepted the user. If so, honor the backend and clear the error.
if authenticated {
err = nil
}
return authenticated, err
}
// AuthAclCheck checks user/topic/acc authentication.
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]
// 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)
}
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("backends %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 {
//If there's no valid prefix, check all backends.
aclCheck, err = b.checkAcl(username, topic, clientid, acc)
}
} else {
aclCheck, err = b.checkAcl(username, topic, clientid, acc)
}
log.Debugf("Acl is %t for user %s", aclCheck, username)
return aclCheck, err
}
func (b *Backends) checkAcl(username, topic, clientid string, acc int) (bool, error) {
//Check superusers first
var err error
aclCheck := false
if !b.disableSuperuser {
for _, bename := range b.superuserCheckers {
var backend = b.backends[bename]
log.Debugf("Superuser check with backend %s", backend.GetName())
if ok, getSuperuserErr := backend.GetSuperuser(username); ok && getSuperuserErr == nil {
log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName())
aclCheck = true
break
} else if getSuperuserErr != nil && err == nil {
err = getSuperuserErr
}
}
}
if !aclCheck {
for _, bename := range b.aclCheckers {
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 {
log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName())
aclCheck = true
break
} else if checkACLErr != nil && err == nil {
err = checkACLErr
}
}
}
// If aclCheck is true, it means at least one backend didn't fail and
// accepted the access. In this case trust this backend and clear the error.
if aclCheck {
err = nil
}
return aclCheck, err
}
func (b *Backends) Halt() {
//Halt every registered backend.
for _, v := range b.backends {
v.Halt()
}
}

131
backends/custom_plugin.go Normal file
View File

@ -0,0 +1,131 @@
package backends
import (
"fmt"
"plugin"
log "github.com/sirupsen/logrus"
)
type CustomPlugin struct {
plugin *plugin.Plugin
init func(map[string]string, log.Level) error
getName func() string
getUser func(username, password, clientid string) (bool, error)
getSuperuser func(username string) (bool, error)
checkAcl func(username, topic, clientid string, acc int32) (bool, error)
halt func()
}
func NewCustomPlugin(authOpts map[string]string, logLevel log.Level) (*CustomPlugin, error) {
plug, err := plugin.Open(authOpts["plugin_path"])
if err != nil {
return nil, fmt.Errorf("Could not init custom plugin: %s", err)
}
customPlugin := &CustomPlugin{
plugin: plug,
}
// Damn, this is gonna be tedious, freaking error handling!
plInit, err := plug.Lookup("Init")
if err != nil {
return nil, fmt.Errorf("Couldn't find func Init in plugin: %s", err)
}
initFunc := plInit.(func(authOpts map[string]string, logLevel log.Level) error)
err = initFunc(authOpts, logLevel)
if err != nil {
return nil, fmt.Errorf("Couldn't init plugin: %s", err)
}
customPlugin.init = initFunc
plName, err := plug.Lookup("GetName")
if err != nil {
return nil, fmt.Errorf("Couldn't find func GetName in plugin: %s", err)
}
nameFunc := plName.(func() string)
customPlugin.getName = nameFunc
plGetUser, err := plug.Lookup("GetUser")
if err != nil {
return nil, fmt.Errorf("couldn't find func GetUser in plugin: %s", err)
}
getUserFunc, ok := plGetUser.(func(username, password, clientid string) (bool, error))
if !ok {
// Here and in other places, we do this for backwards compatibility in case the custom plugin so was created before error was returned.
tmp := plGetUser.(func(username, password, clientid string) bool)
getUserFunc = func(username, password, clientid string) (bool, error) {
return tmp(username, password, clientid), nil
}
}
customPlugin.getUser = getUserFunc
plGetSuperuser, err := plug.Lookup("GetSuperuser")
if err != nil {
return nil, fmt.Errorf("couldn't find func GetSuperuser in plugin: %s", err)
}
getSuperuserFunc, ok := plGetSuperuser.(func(username string) (bool, error))
if !ok {
tmp := plGetSuperuser.(func(username string) bool)
getSuperuserFunc = func(username string) (bool, error) {
return tmp(username), nil
}
}
customPlugin.getSuperuser = getSuperuserFunc
plCheckAcl, err := plug.Lookup("CheckAcl")
if err != nil {
return nil, fmt.Errorf("couldn't find func CheckAcl in plugin: %s", err)
}
checkAclFunc, ok := plCheckAcl.(func(username, topic, clientid string, acc int32) (bool, error))
if !ok {
tmp := plCheckAcl.(func(username, topic, clientid string, acc int32) bool)
checkAclFunc = func(username, topic, clientid string, acc int32) (bool, error) {
return tmp(username, topic, clientid, acc), nil
}
}
customPlugin.checkAcl = checkAclFunc
plHalt, err := plug.Lookup("Halt")
if err != nil {
return nil, fmt.Errorf("Couldn't find func Halt in plugin: %s", err)
}
haltFunc := plHalt.(func())
customPlugin.halt = haltFunc
return customPlugin, nil
}
func (o *CustomPlugin) GetUser(username, password, clientid string) (bool, error) {
return o.getUser(username, password, clientid)
}
func (o *CustomPlugin) GetSuperuser(username string) (bool, error) {
return o.getSuperuser(username)
}
func (o *CustomPlugin) CheckAcl(username, topic, clientid string, acc int32) (bool, error) {
return o.checkAcl(username, topic, clientid, acc)
}
func (o *CustomPlugin) GetName() string {
return o.getName()
}
func (o *CustomPlugin) Halt() {
o.halt()
}

View File

@ -5,7 +5,6 @@ import "C"
import (
"context"
"os"
"plugin"
"strconv"
"strings"
"time"
@ -16,70 +15,25 @@ import (
log "github.com/sirupsen/logrus"
)
type Backend interface {
GetUser(username, password, clientid string) (bool, error)
GetSuperuser(username string) (bool, error)
CheckAcl(username, topic, clientId string, acc int32) (bool, error)
GetName() string
Halt()
}
type AuthPlugin struct {
backends map[string]Backend
customPlugin *plugin.Plugin
PInit func(map[string]string, log.Level) error
customPluginGetName func() string
customPluginGetUser func(username, password, clientid string) (bool, error)
customPluginGetSuperuser func(username string) (bool, error)
customPluginCheckAcl func(username, topic, clientid string, acc int) (bool, error)
customPluginHalt func()
useCache bool
checkPrefix bool
retryCount int
prefixes map[string]string
logLevel log.Level
logDest string
logFile string
disableSuperuser bool
ctx context.Context
cache cache.Store
hasher hashing.HashComparer
backends *bes.Backends
useCache bool
logLevel log.Level
logDest string
logFile string
ctx context.Context
cache cache.Store
hasher hashing.HashComparer
retryCount int
}
// errors to signal mosquitto
const (
//backends
postgresBackend = "postgres"
jwtBackend = "jwt"
redisBackend = "redis"
httpBackend = "http"
filesBackend = "files"
mysqlBackend = "mysql"
sqliteBackend = "sqlite"
mongoBackend = "mongo"
pluginBackend = "plugin"
grpcBackend = "grpc"
jsBackend = "js"
AuthRejected = 0
AuthGranted = 1
AuthError = 2
)
// Serves s a check for allowed backends and a map from backend to expected opts prefix.
var allowedBackendsOptsPrefix = map[string]string{
postgresBackend: "pg",
jwtBackend: "jwt",
redisBackend: "redis",
httpBackend: "http",
filesBackend: "files",
mysqlBackend: "mysql",
sqliteBackend: "sqlite",
mongoBackend: "mongo",
pluginBackend: "plugin",
grpcBackend: "grpc",
jsBackend: "js",
}
var backends []string //List of selected backends.
var authOpts map[string]string //Options passed by mosquitto.
var authPlugin AuthPlugin //General struct with options and conf.
@ -90,14 +44,10 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
FullTimestamp: true,
})
cmBackends := make(map[string]Backend)
//Initialize auth plugin struct with default and given values.
authPlugin = AuthPlugin{
checkPrefix: false,
prefixes: make(map[string]string),
logLevel: log.InfoLevel,
ctx: context.Background(),
logLevel: log.InfoLevel,
ctx: context.Background(),
}
//First, get backends
@ -109,7 +59,7 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
if len(backends) > 0 {
backendsCheck := true
for _, backend := range backends {
if _, ok := allowedBackendsOptsPrefix[backend]; !ok {
if _, ok := bes.AllowedBackendsOptsPrefix[backend]; !ok {
backendsCheck = false
log.Errorf("backend not allowed: %s", backend)
}
@ -135,11 +85,6 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
log.Fatal("backends error")
}
//Disable superusers for all backends if option is set.
if authOpts["disable_superuser"] == "true" {
authPlugin.disableSuperuser = true
}
//Check if log level is given. Set level if any valid option is given.
if logLevel, ok := authOpts["log_level"]; ok {
logLevel = strings.Replace(logLevel, " ", "", -1)
@ -179,199 +124,7 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
}
}
//Initialize backends
for _, bename := range backends {
var beIface Backend
var err error
if bename == pluginBackend {
plug, err := plugin.Open(authOpts["plugin_path"])
if err != nil {
log.Errorf("Could not init custom plugin: %s", err)
authPlugin.customPlugin = nil
} else {
authPlugin.customPlugin = plug
plInit, err := authPlugin.customPlugin.Lookup("Init")
if err != nil {
log.Errorf("Couldn't find func Init in plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
initFunc := plInit.(func(authOpts map[string]string, logLevel log.Level) error)
err = initFunc(authOpts, authPlugin.logLevel)
if err != nil {
log.Errorf("Couldn't init plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
authPlugin.PInit = initFunc
plName, err := authPlugin.customPlugin.Lookup("GetName")
if err != nil {
log.Errorf("Couldn't find func GetName in plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
nameFunc := plName.(func() string)
authPlugin.customPluginGetName = nameFunc
plGetUser, err := authPlugin.customPlugin.Lookup("GetUser")
if err != nil {
log.Errorf("couldn't find func GetUser in plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
getUserFunc, ok := plGetUser.(func(username, password, clientid string) (bool, error))
if !ok {
tmp := plGetUser.(func(username, password, clientid string) bool)
getUserFunc = func(username, password, clientid string) (bool, error) {
return tmp(username, password, clientid), nil
}
}
authPlugin.customPluginGetUser = getUserFunc
plGetSuperuser, err := authPlugin.customPlugin.Lookup("GetSuperuser")
if err != nil {
log.Errorf("couldn't find func GetSuperuser in plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
getSuperuserFunc, ok := plGetSuperuser.(func(username string) (bool, error))
if !ok {
tmp := plGetSuperuser.(func(username string) bool)
getSuperuserFunc = func(username string) (bool, error) {
return tmp(username), nil
}
}
authPlugin.customPluginGetSuperuser = getSuperuserFunc
plCheckAcl, err := authPlugin.customPlugin.Lookup("CheckAcl")
if err != nil {
log.Errorf("couldn't find func CheckAcl in plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
checkAclFunc, ok := plCheckAcl.(func(username, topic, clientid string, acc int) (bool, error))
if !ok {
tmp := plCheckAcl.(func(username, topic, clientid string, acc int) bool)
checkAclFunc = func(username, topic, clientid string, acc int) (bool, error) {
return tmp(username, topic, clientid, acc), nil
}
}
authPlugin.customPluginCheckAcl = checkAclFunc
plHalt, err := authPlugin.customPlugin.Lookup("Halt")
if err != nil {
log.Errorf("Couldn't find func Halt in plugin: %s", err)
authPlugin.customPlugin = nil
continue
}
haltFunc := plHalt.(func())
authPlugin.customPluginHalt = haltFunc
log.Infof("Backend registered: %s", authPlugin.customPluginGetName())
}
} else {
hasher := hashing.NewHasher(authOpts, allowedBackendsOptsPrefix[bename])
switch bename {
case postgresBackend:
beIface, err = bes.NewPostgres(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("backend registered: %s", beIface.GetName())
cmBackends[postgresBackend] = beIface.(bes.Postgres)
}
case jwtBackend:
beIface, err = bes.NewJWT(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[jwtBackend] = beIface.(*bes.JWT)
}
case filesBackend:
beIface, err = bes.NewFiles(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[filesBackend] = beIface.(*bes.Files)
}
case redisBackend:
beIface, err = bes.NewRedis(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[redisBackend] = beIface.(bes.Redis)
}
case mysqlBackend:
beIface, err = bes.NewMysql(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[mysqlBackend] = beIface.(bes.Mysql)
}
case httpBackend:
beIface, err = bes.NewHTTP(authOpts, authPlugin.logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[httpBackend] = beIface.(bes.HTTP)
}
case sqliteBackend:
beIface, err = bes.NewSqlite(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[sqliteBackend] = beIface.(bes.Sqlite)
}
case mongoBackend:
beIface, err = bes.NewMongo(authOpts, authPlugin.logLevel, hasher)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[mongoBackend] = beIface.(bes.Mongo)
}
case grpcBackend:
beIface, err = bes.NewGRPC(authOpts, authPlugin.logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[grpcBackend] = beIface.(bes.GRPC)
}
case jsBackend:
beIface, err = bes.NewJavascript(authOpts, authPlugin.logLevel)
if err != nil {
log.Fatalf("Backend register error: couldn't initialize %s backend with error %s.", bename, err)
} else {
log.Infof("Backend registered: %s", beIface.GetName())
cmBackends[jsBackend] = beIface.(*bes.Javascript)
}
}
}
}
authPlugin.backends = bes.Initialize(authOpts, authPlugin.logLevel, backends)
if cache, ok := authOpts["cache"]; ok && strings.Replace(cache, " ", "", -1) == "true" {
log.Info("redisCache activated")
@ -384,31 +137,6 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
if authPlugin.useCache {
setCache(authOpts)
}
if checkPrefix, ok := authOpts["check_prefix"]; ok && strings.Replace(checkPrefix, " ", "", -1) == "true" {
//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
for i, backend := range backends {
authPlugin.prefixes[prefixes[i]] = backend
}
log.Infof("prefixes enabled for backends %s with prefixes %s.", authOpts["backends"], authOpts["prefixes"])
authPlugin.checkPrefix = true
} else {
log.Errorf("Error: got %d backends and %d prefixes, defaulting to prefixes disabled.", len(backends), len(prefixes))
authPlugin.checkPrefix = false
}
} else {
log.Warn("Error: prefixes enabled but no options given, defaulting to prefixes disabled.")
authPlugin.checkPrefix = false
}
} else {
authPlugin.checkPrefix = false
}
authPlugin.backends = cmBackends
}
func setCache(authOpts map[string]string) {
@ -602,50 +330,7 @@ func authUnpwdCheck(username, password, clientid string) (bool, error) {
}
}
//If prefixes are enabled, check if username has a valid prefix and use the correct backend if so.
if authPlugin.checkPrefix {
validPrefix, bename := CheckPrefix(username)
if validPrefix {
if bename == pluginBackend {
authenticated, err = CheckPluginAuth(username, password, clientid)
} else {
// 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 := getPrefixForBackend(bename)
username = strings.TrimPrefix(username, prefix+"_")
}
var backend = authPlugin.backends[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 = CheckBackendsAuth(username, password, clientid)
//If not authenticated, check for a present plugin
if !authenticated {
if ok, checkAuthErr := CheckPluginAuth(username, password, clientid); ok && checkAuthErr == nil {
authenticated = true
err = nil
} else if checkAuthErr != nil && err == nil {
err = checkAuthErr
}
}
}
} else {
authenticated, err = CheckBackendsAuth(username, password, clientid)
//If not authenticated, check for a present plugin
if !authenticated {
if ok, checkAuthErr := CheckPluginAuth(username, password, clientid); ok && checkAuthErr == nil {
authenticated = true
err = nil
} else if checkAuthErr != nil && err == nil {
err = checkAuthErr
}
}
}
authenticated, err = authPlugin.backends.AuthUnpwdCheck(username, password, clientid)
if authPlugin.useCache && err == nil {
authGranted := "false"
@ -698,63 +383,8 @@ func authAclCheck(clientid, username, topic string, acc int) (bool, error) {
return granted, nil
}
}
//If prefixes are enabled, check if username has a valid prefix and use the correct backend if so.
//Else, check all backends.
if authPlugin.checkPrefix {
validPrefix, bename := CheckPrefix(username)
if validPrefix {
if bename == pluginBackend {
aclCheck, err = CheckPluginAcl(username, topic, clientid, acc)
} else {
// 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 := getPrefixForBackend(bename)
username = strings.TrimPrefix(username, prefix+"_")
}
var backend = authPlugin.backends[bename]
log.Debugf("Superuser check with backend %s", backend.GetName())
// Short circuit checks when superusers are disabled.
if !authPlugin.disableSuperuser {
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 {
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 = CheckBackendsAcl(username, topic, clientid, acc)
//If acl hasn't passed, check for plugin.
if !aclCheck {
if ok, checkACLErr := CheckPluginAcl(username, topic, clientid, acc); ok && checkACLErr == nil {
aclCheck = true
} else if checkACLErr != nil && err == nil {
err = checkACLErr
}
}
}
} else {
aclCheck, err = CheckBackendsAcl(username, topic, clientid, acc)
//If acl hasn't passed, check for plugin.
if !aclCheck {
if ok, checkACLErr := CheckPluginAcl(username, topic, clientid, acc); ok && checkACLErr == nil {
aclCheck = true
} else if checkACLErr != nil && err == nil {
err = checkACLErr
}
}
}
aclCheck, err = authPlugin.backends.AuthAclCheck(clientid, username, topic, acc)
if authPlugin.useCache && err == nil {
authGranted := "false"
@ -777,138 +407,6 @@ func AuthPskKeyGet() bool {
return true
}
//checkPrefix checks if a username contains a valid prefix. If so, returns ok and the suitable backend name; else, !ok and empty string.
func CheckPrefix(username string) (bool, string) {
if strings.Index(username, "_") > 0 {
userPrefix := username[0:strings.Index(username, "_")]
if prefix, ok := authPlugin.prefixes[userPrefix]; ok {
log.Debugf("Found prefix for user %s, using backend %s.", username, prefix)
return true, prefix
}
}
return false, ""
}
//getPrefixForBackend retrieves the user provided prefix for a given backend.
func getPrefixForBackend(backend string) string {
for k, v := range authPlugin.prefixes {
if v == backend {
return k
}
}
return ""
}
//CheckBackendsAuth checks for all backends if a username is authenticated and sets the authenticated param.
func CheckBackendsAuth(username, password, clientid string) (bool, error) {
var err error
authenticated := false
for _, bename := range backends {
if bename == pluginBackend {
continue
}
var backend = authPlugin.backends[bename]
log.Debugf("checking user %s with backend %s", username, backend.GetName())
if ok, getUserErr := backend.GetUser(username, password, clientid); ok && getUserErr == nil {
authenticated = true
log.Debugf("user %s authenticated with backend %s", username, backend.GetName())
break
} else if getUserErr != nil && err == nil {
err = getUserErr
}
}
// If authenticated is true, it means at least one backend didn't failed and
// accepted the user. In this case trust this backend and clear the error.
if authenticated {
err = nil
}
return authenticated, err
}
//CheckBackendsAcl checks for all backends if a username is superuser or has acl rights and sets the aclCheck param.
func CheckBackendsAcl(username, topic, clientid string, acc int) (bool, error) {
//Check superusers first
var err error
aclCheck := false
if !authPlugin.disableSuperuser {
for _, bename := range backends {
if bename == pluginBackend {
continue
}
var backend = authPlugin.backends[bename]
log.Debugf("Superuser check with backend %s", backend.GetName())
if ok, getSuperuserErr := backend.GetSuperuser(username); ok && getSuperuserErr == nil {
log.Debugf("superuser %s acl authenticated with backend %s", username, backend.GetName())
aclCheck = true
break
} else if getSuperuserErr != nil && err == nil {
err = getSuperuserErr
}
}
}
if !aclCheck {
for _, bename := range backends {
if bename == pluginBackend {
continue
}
var backend = authPlugin.backends[bename]
log.Debugf("Acl check with backend %s", backend.GetName())
if ok, checkACLErr := backend.CheckAcl(username, topic, clientid, int32(acc)); ok && checkACLErr == nil {
log.Debugf("user %s acl authenticated with backend %s", username, backend.GetName())
aclCheck = true
break
} else if checkACLErr != nil && err == nil {
err = checkACLErr
}
}
}
// If aclCheck is true, it means at least one backend didn't fail and
// accepted the access. In this case trust this backend and clear the error.
if aclCheck {
err = nil
}
return aclCheck, err
}
//CheckPluginAuth checks that the plugin is not nil and returns the plugins auth response.
func CheckPluginAuth(username, password, clientid string) (bool, error) {
if authPlugin.customPlugin == nil {
return false, nil
}
return authPlugin.customPluginGetUser(username, password, clientid)
}
//CheckPluginAcl checks that the plugin is not nil and returns the superuser/acl response.
func CheckPluginAcl(username, topic, clientid string, acc int) (bool, error) {
var aclCheck bool
var err error
if authPlugin.customPlugin == nil {
return false, nil
}
//If superuser, authorize it unless superusers are disabled.
if !authPlugin.disableSuperuser {
aclCheck, err = authPlugin.customPluginGetSuperuser(username)
}
if !aclCheck && err == nil {
//Check against the plugin's check acl function.
aclCheck, err = authPlugin.customPluginCheckAcl(username, topic, clientid, acc)
}
return aclCheck, err
}
//export AuthPluginCleanup
func AuthPluginCleanup() {
log.Info("Cleaning up plugin")
@ -917,14 +415,7 @@ func AuthPluginCleanup() {
authPlugin.cache.Close()
}
//Halt every registered backend.
for _, v := range authPlugin.backends {
v.Halt()
}
if authPlugin.customPlugin != nil {
authPlugin.customPluginHalt()
}
authPlugin.backends.Halt()
}
func main() {}

View File

@ -11,19 +11,19 @@ func Init(authOpts map[string]string, logLevel log.Level) error {
return nil
}
func GetUser(username, password, clientid string) bool {
func GetUser(username, password, clientid string) (bool, error) {
log.Debugf("Checking get user with custom plugin.")
return false
return false, nil
}
func GetSuperuser(username string) bool {
func GetSuperuser(username string) (bool, error) {
log.Debugf("Checking get superuser with custom plugin.")
return false
return false, nil
}
func CheckAcl(username, topic, clientid string, acc int) bool {
func CheckAcl(username, topic, clientid string, acc int) (bool, error) {
log.Debugf("Checking acl with custom plugin.")
return false
return false, nil
}
func GetName() string {