Merge branch 'master' into backend-error

This commit is contained in:
Pierre Fersing 2021-02-13 12:14:28 +01:00
commit 250485f2bd
3 changed files with 127 additions and 25 deletions

View File

@ -4,7 +4,10 @@ import (
"bufio"
"fmt"
"os"
"os/signal"
"strings"
"sync"
"syscall"
"github.com/iegomez/mosquitto-go-auth/hashing"
"github.com/pkg/errors"
@ -25,6 +28,7 @@ type AclRecord struct {
//FileBE holds paths to files, list of file users and general (no user or pattern) acl records.
type Files struct {
sync.Mutex
PasswordPath string
AclPath string
CheckAcls bool
@ -32,14 +36,15 @@ type Files struct {
AclRecords []AclRecord
filesOnly bool
hasher hashing.HashComparer
signals chan os.Signal
}
//NewFiles initializes a files backend.
func NewFiles(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (Files, error) {
func NewFiles(authOpts map[string]string, logLevel log.Level, hasher hashing.HashComparer) (*Files, error) {
log.SetLevel(logLevel)
var files = Files{
var files = &Files{
PasswordPath: "",
AclPath: "",
CheckAcls: false,
@ -47,6 +52,7 @@ func NewFiles(authOpts map[string]string, logLevel log.Level, hasher hashing.Has
AclRecords: make([]AclRecord, 0),
filesOnly: true,
hasher: hasher,
signals: make(chan os.Signal, 1),
}
if len(strings.Split(strings.Replace(authOpts["backends"], " ", "", -1), ",")) > 1 {
@ -56,7 +62,7 @@ func NewFiles(authOpts map[string]string, logLevel log.Level, hasher hashing.Has
if passwordPath, ok := authOpts["password_path"]; ok {
files.PasswordPath = passwordPath
} else {
return files, errors.New("Files backend error: no password path given")
return nil, errors.New("Files backend error: no password path given")
}
if aclPath, ok := authOpts["acl_path"]; ok {
@ -67,30 +73,58 @@ func NewFiles(authOpts map[string]string, logLevel log.Level, hasher hashing.Has
log.Info("Acls won't be checked")
}
//Now initialize FileUsers by reading from password and acl files.
uCount, err := files.readPasswords()
err := files.loadFiles()
if err != nil {
return files, errors.Errorf("read passwords: %s", err)
} else {
log.Debugf("got %d users from passwords file", uCount)
return nil, err
}
//Only read acls if path was given.
if files.CheckAcls {
aclCount, err := files.readAcls()
if err != nil {
return files, errors.Errorf("read acls: %s", err)
} else {
log.Infof("got %d lines from acl file", aclCount)
}
}
go files.watchSignals()
return files, nil
}
func (o *Files) watchSignals() {
signal.Notify(o.signals, syscall.SIGHUP)
for {
select {
case sig := <-o.signals:
if sig == syscall.SIGHUP {
log.Debugln("Got SIGHUP, reloading files.")
o.loadFiles()
}
default:
// NO-OP
}
}
}
func (o *Files) loadFiles() error {
o.Lock()
defer o.Unlock()
count, err := o.readPasswords()
if err != nil {
return errors.Errorf("read passwords: %s", err)
}
log.Debugf("got %d users from passwords file", count)
//Only read acls if path was given.
if o.CheckAcls {
count, err := o.readAcls()
if err != nil {
return errors.Errorf("read acls: %s", err)
}
log.Debugf("got %d lines from acl file", count)
}
return nil
}
//ReadPasswords read file and populates FileUsers. Return amount of users seen and possile error.
func (o Files) readPasswords() (int, error) {
func (o *Files) readPasswords() (int, error) {
usersCount := 0
@ -285,7 +319,7 @@ func checkCommentOrEmpty(line string) bool {
}
//GetUser checks that user exists and password is correct.
func (o Files) GetUser(username, password, clientid string) (bool, error) {
func (o *Files) GetUser(username, password, clientid string) (bool, error) {
fileUser, ok := o.Users[username]
if !ok {
@ -303,12 +337,12 @@ func (o Files) GetUser(username, password, clientid string) (bool, error) {
}
//GetSuperuser returns false for files backend.
func (o Files) GetSuperuser(username string) (bool, error) {
func (o *Files) GetSuperuser(username string) (bool, error) {
return false, nil
}
//CheckAcl checks that the topic may be read/written by the given user/clientid.
func (o Files) CheckAcl(username, topic, clientid string, acc int32) (bool, error) {
func (o *Files) CheckAcl(username, topic, clientid string, acc int32) (bool, error) {
//If there are no acls and Files is the only backend, all access is allowed.
//If there are other backends, then we can't blindly grant access.
if !o.CheckAcls {
@ -339,11 +373,11 @@ func (o Files) CheckAcl(username, topic, clientid string, acc int32) (bool, erro
}
//GetName returns the backend's name
func (o Files) GetName() string {
func (o *Files) GetName() string {
return "Files"
}
//Halt does nothing for files as there's no cleanup needed.
func (o Files) Halt() {
func (o *Files) Halt() {
//Do nothing
}

View File

@ -1,8 +1,12 @@
package backends
import (
"fmt"
"os"
"path/filepath"
"syscall"
"testing"
"time"
"github.com/iegomez/mosquitto-go-auth/hashing"
log "github.com/sirupsen/logrus"
@ -14,8 +18,10 @@ func TestFiles(t *testing.T) {
authOpts := make(map[string]string)
Convey("Given empty opts NewFiles should fail", t, func() {
_, err := NewFiles(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "files"))
files, err := NewFiles(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "files"))
So(err, ShouldBeError)
files.Halt()
})
pwPath, _ := filepath.Abs("../test-files/passwords")
@ -173,4 +179,66 @@ func TestFiles(t *testing.T) {
//Halt files
files.Halt()
})
Convey("On SIGHUP files should be reloaded", t, func() {
pwFile, err := os.Create("../test-files/test-passwords")
So(err, ShouldBeNil)
aclFile, err := os.Create("../test-files/test-acls")
So(err, ShouldBeNil)
defer os.Remove(pwFile.Name())
defer os.Remove(aclFile.Name())
hasher := hashing.NewHasher(authOpts, "files")
user1 := "test1"
user2 := "test2"
pw1, err := hasher.Hash(user1)
So(err, ShouldBeNil)
pw2, err := hasher.Hash(user2)
So(err, ShouldBeNil)
pwFile.WriteString(fmt.Sprintf("\n%s:%s\n", user1, pw1))
aclFile.WriteString("\nuser test1")
aclFile.WriteString("\ntopic read test/#")
pwFile.Sync()
aclFile.Sync()
authOpts["password_path"] = pwFile.Name()
authOpts["acl_path"] = aclFile.Name()
files, err := NewFiles(authOpts, log.DebugLevel, hasher)
So(err, ShouldBeNil)
user, ok := files.Users[user1]
So(ok, ShouldBeTrue)
record := user.AclRecords[0]
So(record.Acc, ShouldEqual, MOSQ_ACL_READ)
So(record.Topic, ShouldEqual, "test/#")
_, ok = files.Users[user2]
So(ok, ShouldBeFalse)
// Now add second user and reload.
pwFile.WriteString(fmt.Sprintf("\n%s:%s\n", user2, pw2))
aclFile.WriteString("\nuser test2")
aclFile.WriteString("\ntopic write test/#")
files.signals <- syscall.SIGHUP
time.Sleep(200 * time.Millisecond)
user, ok = files.Users[user2]
So(ok, ShouldBeTrue)
record = user.AclRecords[0]
So(record.Acc, ShouldEqual, MOSQ_ACL_WRITE)
So(record.Topic, ShouldEqual, "test/#")
})
}

View File

@ -301,7 +301,7 @@ func AuthPluginInit(keys []string, values []string, authOptsNum int) {
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)
cmBackends[filesBackend] = beIface.(*bes.Files)
}
case redisBackend:
beIface, err = bes.NewRedis(authOpts, authPlugin.logLevel, hasher)