289 lines
10 KiB
Go
289 lines
10 KiB
Go
package backends
|
|
|
|
import (
|
|
"testing"
|
|
|
|
. "github.com/iegomez/mosquitto-go-auth/backends/constants"
|
|
"github.com/iegomez/mosquitto-go-auth/hashing"
|
|
log "github.com/sirupsen/logrus"
|
|
. "github.com/smartystreets/goconvey/convey"
|
|
)
|
|
|
|
func TestPostgres(t *testing.T) {
|
|
|
|
//Initialize Postgres without mandatory values (fail).
|
|
authOpts := make(map[string]string)
|
|
authOpts["pg_host"] = "localhost"
|
|
authOpts["pg_port"] = "5432"
|
|
|
|
Convey("If mandatory params are not set initialization should fail", t, func() {
|
|
_, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeError)
|
|
})
|
|
|
|
//Initialize Postgres with some test values (omit tls).
|
|
authOpts["pg_dbname"] = "go_auth_test"
|
|
authOpts["pg_user"] = "go_auth_test"
|
|
authOpts["pg_sslmode"] = "disable"
|
|
authOpts["pg_password"] = "go_auth_test"
|
|
authOpts["pg_userquery"] = "SELECT password_hash FROM test_user WHERE username = $1 limit 1"
|
|
authOpts["pg_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
|
|
authOpts["pg_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND (rw = $2 or rw = 3)"
|
|
|
|
Convey("Given valid params NewPostgres should return a Postgres backend instance", t, func() {
|
|
postgres, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeNil)
|
|
|
|
//Empty db
|
|
postgres.DB.MustExec("delete from test_user where 1 = 1")
|
|
postgres.DB.MustExec("delete from test_acl where 1 = 1")
|
|
|
|
//Insert a user to test auth
|
|
username := "test"
|
|
userPass := "testpw"
|
|
//Hash generated by the pw utility
|
|
userPassHash := "PBKDF2$sha512$100000$os24lcPr9cJt2QDVWssblQ==$BK1BQ2wbwU1zNxv3Ml3wLuu5//hPop3/LvaPYjjCwdBvnpwusnukJPpcXQzyyjOlZdieXTx6sXAcX4WnZRZZnw=="
|
|
wrongUsername := "not_present"
|
|
|
|
insertQuery := "INSERT INTO test_user(username, password_hash, is_admin) values($1, $2, $3) returning id"
|
|
|
|
userID := 0
|
|
|
|
err = postgres.DB.Get(&userID, insertQuery, username, userPassHash, true)
|
|
|
|
So(err, ShouldBeNil)
|
|
So(userID, ShouldBeGreaterThan, 0)
|
|
|
|
Convey("Given a username and a correct password, it should correctly authenticate it", func() {
|
|
|
|
authenticated, err := postgres.GetUser(username, userPass, "")
|
|
So(err, ShouldBeNil)
|
|
So(authenticated, ShouldBeTrue)
|
|
|
|
})
|
|
|
|
Convey("Given a username and an incorrect password, it should not authenticate it", func() {
|
|
|
|
authenticated, err := postgres.GetUser(username, "wrong_password", "")
|
|
So(err, ShouldBeNil)
|
|
So(authenticated, ShouldBeFalse)
|
|
|
|
})
|
|
|
|
Convey("Given a wrong username, it should not authenticate it and not return error", func() {
|
|
|
|
authenticated, err := postgres.GetUser(wrongUsername, "whatever_password", "")
|
|
So(err, ShouldBeNil)
|
|
So(authenticated, ShouldBeFalse)
|
|
|
|
})
|
|
|
|
Convey("Given a username that is admin, super user should pass", func() {
|
|
superuser, err := postgres.GetSuperuser(username)
|
|
So(err, ShouldBeNil)
|
|
So(superuser, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Given a wrong username, super user should not return error", func() {
|
|
superuser, err := postgres.GetSuperuser(wrongUsername)
|
|
So(err, ShouldBeNil)
|
|
So(superuser, ShouldBeFalse)
|
|
})
|
|
|
|
//Now create some acls and test topics
|
|
|
|
strictAcl := "test/topic/1"
|
|
singleLevelAcl := "test/topic/+"
|
|
hierarchyAcl := "test/#"
|
|
|
|
userPattern := "test/%u"
|
|
clientPattern := "test/%c"
|
|
|
|
clientID := "test_client"
|
|
|
|
aclID := 0
|
|
aclQuery := "INSERT INTO test_acl(test_user_id, topic, rw) values($1, $2, $3) returning id"
|
|
err = postgres.DB.Get(&aclID, aclQuery, userID, strictAcl, MOSQ_ACL_READ)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Given only strict acl in db, an exact match should work and and inexact one not", func() {
|
|
|
|
testTopic1 := `test/topic/1`
|
|
testTopic2 := `test/topic/2`
|
|
|
|
tt1, err1 := postgres.CheckAcl(username, testTopic1, clientID, MOSQ_ACL_READ)
|
|
tt2, err2 := postgres.CheckAcl(username, testTopic2, clientID, MOSQ_ACL_READ)
|
|
|
|
So(err1, ShouldBeNil)
|
|
So(err2, ShouldBeNil)
|
|
So(tt1, ShouldBeTrue)
|
|
So(tt2, ShouldBeFalse)
|
|
|
|
})
|
|
|
|
Convey("Given read only privileges, a pub check should fail", func() {
|
|
|
|
testTopic1 := "test/topic/1"
|
|
tt1, err1 := postgres.CheckAcl(username, testTopic1, clientID, MOSQ_ACL_WRITE)
|
|
So(err1, ShouldBeNil)
|
|
So(tt1, ShouldBeFalse)
|
|
|
|
})
|
|
|
|
Convey("Given wildcard subscriptions against strict db acl, acl checks should fail", func() {
|
|
|
|
tt1, err1 := postgres.CheckAcl(username, singleLevelAcl, clientID, MOSQ_ACL_READ)
|
|
tt2, err2 := postgres.CheckAcl(username, hierarchyAcl, clientID, MOSQ_ACL_READ)
|
|
|
|
So(err1, ShouldBeNil)
|
|
So(err2, ShouldBeNil)
|
|
So(tt1, ShouldBeFalse)
|
|
So(tt2, ShouldBeFalse)
|
|
|
|
})
|
|
|
|
//Now check against patterns.
|
|
|
|
err = postgres.DB.Get(&aclID, aclQuery, userID, userPattern, MOSQ_ACL_READ)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Given a topic that mentions username, acl check should pass", func() {
|
|
tt1, err1 := postgres.CheckAcl(username, "test/test", clientID, MOSQ_ACL_READ)
|
|
So(err1, ShouldBeNil)
|
|
So(tt1, ShouldBeTrue)
|
|
})
|
|
|
|
err = postgres.DB.Get(&aclID, aclQuery, userID, clientPattern, MOSQ_ACL_READ)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Given a topic that mentions clientid, acl check should pass", func() {
|
|
tt1, err1 := postgres.CheckAcl(username, "test/test_client", clientID, MOSQ_ACL_READ)
|
|
So(err1, ShouldBeNil)
|
|
So(tt1, ShouldBeTrue)
|
|
})
|
|
|
|
//Now insert single level topic to check against.
|
|
|
|
err = postgres.DB.Get(&aclID, aclQuery, userID, singleLevelAcl, MOSQ_ACL_READ)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Given a topic not strictly present that matches a db single level wildcard, acl check should pass", func() {
|
|
tt1, err1 := postgres.CheckAcl(username, "test/topic/whatever", clientID, MOSQ_ACL_READ)
|
|
So(err1, ShouldBeNil)
|
|
So(tt1, ShouldBeTrue)
|
|
})
|
|
|
|
//Now insert hierarchy wildcard to check against.
|
|
|
|
err = postgres.DB.Get(&aclID, aclQuery, userID, hierarchyAcl, MOSQ_ACL_READ)
|
|
So(err, ShouldBeNil)
|
|
|
|
Convey("Given a topic not strictly present that matches a hierarchy wildcard, acl check should pass", func() {
|
|
tt1, err1 := postgres.CheckAcl(username, "test/what/ever", clientID, MOSQ_ACL_READ)
|
|
So(err1, ShouldBeNil)
|
|
So(tt1, ShouldBeTrue)
|
|
})
|
|
|
|
Convey("Given a bad username, acl check should not return error", func() {
|
|
testTopic1 := `test/topic/1`
|
|
tt1, err1 := postgres.CheckAcl(wrongUsername, testTopic1, clientID, MOSQ_ACL_READ)
|
|
So(err1, ShouldBeNil)
|
|
So(tt1, ShouldBeFalse)
|
|
})
|
|
|
|
//Empty db
|
|
postgres.DB.MustExec("delete from test_user where 1 = 1")
|
|
postgres.DB.MustExec("delete from test_acl where 1 = 1")
|
|
|
|
postgres.Halt()
|
|
|
|
})
|
|
|
|
}
|
|
|
|
func TestPostgresTls(t *testing.T) {
|
|
authOpts := make(map[string]string)
|
|
authOpts["pg_host"] = "localhost"
|
|
authOpts["pg_port"] = "5432"
|
|
authOpts["pg_sslmode"] = "disable"
|
|
authOpts["pg_dbname"] = "go_auth_test"
|
|
authOpts["pg_user"] = "go_auth_test_tls"
|
|
authOpts["pg_password"] = "go_auth_test_tls"
|
|
authOpts["pg_userquery"] = "SELECT password_hash FROM test_user WHERE username = $1 limit 1"
|
|
authOpts["pg_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
|
|
authOpts["pg_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND (rw = $2 or rw = 3)"
|
|
|
|
Convey("Given custom ssl disabled, it should fail", t, func() {
|
|
postgres, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeError)
|
|
So(err.Error(), ShouldContainSubstring, "pg_hba.conf rejects connection")
|
|
So(postgres.DB, ShouldBeNil)
|
|
})
|
|
|
|
authOpts["pg_sslmode"] = "verify-full"
|
|
authOpts["pg_sslrootcert"] = "/test-files/certificates/ca.pem"
|
|
|
|
Convey("Given custom ssl enabled, it should work without a client certificate", t, func() {
|
|
postgres, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeNil)
|
|
|
|
rows, err := postgres.DB.Query("SELECT cipher FROM pg_stat_activity JOIN pg_stat_ssl USING(pid);")
|
|
So(err, ShouldBeNil)
|
|
So(rows.Next(), ShouldBeTrue)
|
|
|
|
var sslCipher string
|
|
err = rows.Scan(&sslCipher)
|
|
So(err, ShouldBeNil)
|
|
So(sslCipher, ShouldNotBeBlank)
|
|
})
|
|
}
|
|
|
|
func TestPostgresMutualTls(t *testing.T) {
|
|
authOpts := make(map[string]string)
|
|
authOpts["pg_host"] = "localhost"
|
|
authOpts["pg_port"] = "5432"
|
|
authOpts["pg_dbname"] = "go_auth_test"
|
|
authOpts["pg_user"] = "go_auth_test_mutual_tls"
|
|
authOpts["pg_password"] = "go_auth_test_mutual_tls"
|
|
authOpts["pg_userquery"] = "SELECT password_hash FROM test_user WHERE username = $1 limit 1"
|
|
authOpts["pg_superquery"] = "select count(*) from test_user where username = $1 and is_admin = true"
|
|
authOpts["pg_aclquery"] = "SELECT test_acl.topic FROM test_acl, test_user WHERE test_user.username = $1 AND test_acl.test_user_id = test_user.id AND (rw = $2 or rw = 3)"
|
|
|
|
authOpts["pg_sslmode"] = "verify-full"
|
|
authOpts["pg_sslrootcert"] = "/test-files/certificates/ca.pem"
|
|
|
|
Convey("Given custom ssl enabled and no client certificate is given, it should fail", t, func() {
|
|
postgres, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeError)
|
|
So(err.Error(), ShouldEqual, "PG backend error: couldn't open db: couldn't ping database postgres: pq: connection requires a valid client certificate")
|
|
So(postgres.DB, ShouldBeNil)
|
|
})
|
|
|
|
authOpts["pg_sslcert"] = "/test-files/certificates/grpc/client.pem"
|
|
authOpts["pg_sslkey"] = "/test-files/certificates/grpc/client-key.pem"
|
|
|
|
Convey("Given custom ssl enabled and invalid client certificate is given, it should fail", t, func() {
|
|
postgres, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeError)
|
|
So(err.Error(), ShouldEqual, "PG backend error: couldn't open db: couldn't ping database postgres: pq: connection requires a valid client certificate")
|
|
So(postgres.DB, ShouldBeNil)
|
|
})
|
|
|
|
authOpts["pg_sslcert"] = "/test-files/certificates/db/client.pem"
|
|
authOpts["pg_sslkey"] = "/test-files/certificates/db/client-key.pem"
|
|
|
|
Convey("Given custom ssl enabled and client certificate is given, it should work", t, func() {
|
|
postgres, err := NewPostgres(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "postgres"))
|
|
So(err, ShouldBeNil)
|
|
|
|
rows, err := postgres.DB.Query("SELECT cipher FROM pg_stat_activity JOIN pg_stat_ssl USING(pid);")
|
|
So(err, ShouldBeNil)
|
|
So(rows.Next(), ShouldBeTrue)
|
|
|
|
var sslCipher string
|
|
err = rows.Scan(&sslCipher)
|
|
So(err, ShouldBeNil)
|
|
So(sslCipher, ShouldNotBeBlank)
|
|
})
|
|
}
|