From fa99ba6583754cc2f28e0d1334bf8b56907130e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ignacio=20G=C3=B3mez?= Date: Thu, 23 Sep 2021 20:40:32 -0300 Subject: [PATCH] Add option to strip prefixes upon checking user or acl. --- README.md | 10 +++++++- backends/backends.go | 21 +++++++++++++---- backends/backends_test.go | 48 +++++++++++++++++++++++++++++++++++++++ go.sum | 2 ++ 4 files changed, 75 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index a9764d8..e0f468c 100644 --- a/README.md +++ b/README.md @@ -405,10 +405,18 @@ The above example will do up to 2 retries (3 calls in total considering the orig #### Prefixes -Though the plugin may have multiple backends enabled, there's a way to specify which backend must be used for a given user: prefixes. When enabled, `prefixes` allow to check if the username contains a predefined prefix in the form prefix_username and use the configured backend for that prefix. Options to enable and set prefixes are the following: +Though the plugin may have multiple backends enabled, there's a way to specify which backend must be used for a given user: prefixes. +When enabled, `prefixes` allow to check if the username contains a predefined prefix in the form prefix_username and use the configured backend for that prefix. +There's also an option to strip the prefix upon checking user or acl, +so that if a record for `username` exists on a backend with prefix `prefix`, +then both `username` and `prefix_username` would be authenticated/authorized. Notice that the former would +need to loop through all the backends since it carries no prefix, while the latter will only be checked by the correct backend. + +Options to enable and set prefixes are the following: ``` auth_opt_check_prefix true +auth_opt_strip_prefix true auth_opt_prefixes filesprefix, pgprefix, jwtprefix ``` diff --git a/backends/backends.go b/backends/backends.go index 8196a22..a2fa9e3 100644 --- a/backends/backends.go +++ b/backends/backends.go @@ -25,6 +25,7 @@ type Backends struct { superuserCheckers []string checkPrefix bool + stripPrefix bool prefixes map[string]string disableSuperuser bool @@ -76,7 +77,6 @@ func Initialize(authOpts map[string]string, logLevel log.Level, version string) aclCheckers: make([]string, 0), userCheckers: make([]string, 0), superuserCheckers: make([]string, 0), - checkPrefix: false, prefixes: make(map[string]string), } @@ -273,6 +273,7 @@ func (b *Backends) setPrefixes(authOpts map[string]string, backends []string) { if !ok || strings.Replace(checkPrefix, " ", "", -1) != "true" { b.checkPrefix = false + b.stripPrefix = false return } @@ -282,6 +283,7 @@ func (b *Backends) setPrefixes(authOpts map[string]string, backends []string) { if !ok { log.Warn("Error: prefixes enabled but no options given, defaulting to prefixes disabled.") b.checkPrefix = false + b.stripPrefix = false return } @@ -291,10 +293,15 @@ func (b *Backends) setPrefixes(authOpts map[string]string, backends []string) { if len(prefixes) != len(backends) { log.Errorf("Error: got %d backends and %d prefixes, defaulting to prefixes disabled.", len(backends), len(prefixes)) b.checkPrefix = false + b.stripPrefix = false return } + if authOpts["strip_prefix"] == "true" { + b.stripPrefix = true + } + for i, backend := range backends { b.prefixes[prefixes[i]] = backend } @@ -355,8 +362,10 @@ func (b *Backends) AuthUnpwdCheck(username, password, clientid string) (bool, er return false, fmt.Errorf("backend %s not registered to check users", bename) } - // 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 { + // 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. + // Also strip the prefix if the strip_prefix option was set. + if bename == jwtBackend || b.stripPrefix { prefix := b.getPrefixForBackend(bename) username = strings.TrimPrefix(username, prefix+"_") } @@ -414,8 +423,10 @@ func (b *Backends) AuthAclCheck(clientid, username, topic string, acc int) (bool return b.checkAcl(username, topic, clientid, acc) } - // 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 { + // 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. + // Also strip the prefix if the strip_prefix option was set. + if bename == jwtBackend || b.stripPrefix { prefix := b.getPrefixForBackend(bename) username = strings.TrimPrefix(username, prefix+"_") } diff --git a/backends/backends_test.go b/backends/backends_test.go index f1117dd..4480602 100644 --- a/backends/backends_test.go +++ b/backends/backends_test.go @@ -444,5 +444,53 @@ func TestBackends(t *testing.T) { redis.Halt() }) + + Convey("When strip_prefix is true, the prefix will be stripped from the username prior to conducting checks", func() { + authOpts["backends"] = "redis" + authOpts["redis_register"] = "user, acl" + authOpts["check_prefix"] = "true" + authOpts["strip_prefix"] = "true" + authOpts["prefixes"] = "redis" + delete(authOpts, "disable_superuser") + + username := "redis_test1" + stripUsername := "test1" + password := username + passwordHash := "PBKDF2$sha512$100000$hgodnayqjfs0AOCxvsU+Zw==$dfc4LBGmZ/wB128NOD48qF5fCS+r/bsjU+oCXgT3UksAik73vIkXcPFydtbJKoIgnepNXP9t+zGIaR5wyRmXaA==" + + redis, err := NewRedis(authOpts, log.DebugLevel, hashing.NewHasher(authOpts, "redis")) + assert.Nil(t, err) + + ctx := context.Background() + + // Insert a user to test auth. + redis.conn.Set(ctx, stripUsername, passwordHash, 0) + redis.conn.Set(ctx, fmt.Sprintf("%s:su", stripUsername), "true", 0) + + b, err := Initialize(authOpts, log.DebugLevel, version) + So(err, ShouldBeNil) + + userCheck, err := b.AuthUnpwdCheck(username, password, clientid) + + So(err, ShouldBeNil) + So(userCheck, ShouldBeTrue) + + redis.conn.SAdd(ctx, stripUsername+":racls", "test/redis") + + aclCheck, err := b.AuthAclCheck(clientid, stripUsername, "test/redis", 1) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeTrue) + + userCheck, err = b.AuthUnpwdCheck(username, password, clientid) + + So(err, ShouldBeNil) + So(userCheck, ShouldBeTrue) + + aclCheck, err = b.AuthAclCheck(clientid, stripUsername, "test/redis", 1) + So(err, ShouldBeNil) + So(aclCheck, ShouldBeTrue) + + redis.Halt() + }) }) } diff --git a/go.sum b/go.sum index 4852bd6..2878a52 100644 --- a/go.sum +++ b/go.sum @@ -182,6 +182,7 @@ github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UV github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= @@ -318,6 +319,7 @@ gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=