feat: Make Casbin matcher configurable on runtime(globMatch(default) or RegexMatch) (#7165)

* feat: Make Casbin matcher configurable on runtime(globMatch(default) or RegexMatch)

Signed-off-by: cezhang <c1zhang.dev@gmail.com>
This commit is contained in:
c1_zh
2021-10-10 03:01:09 +08:00
committed by GitHub
parent b2c70df619
commit 363e1d2d49
4 changed files with 112 additions and 19 deletions

View File

@@ -11,4 +11,4 @@ g = _, _
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
[matchers]
m = g(r.sub, p.sub) && globMatch(r.res, p.res) && globMatch(r.act, p.act) && globMatch(r.obj, p.obj)
m = g(r.sub, p.sub) && globOrRegexMatch(r.res, p.res) && globOrRegexMatch(r.act, p.act) && globOrRegexMatch(r.obj, p.obj)

View File

@@ -28,3 +28,8 @@ data:
# If omitted, defaults to: '[groups]'. The scope value can be a string, or a list of strings.
scopes: '[cognito:groups, email]'
# matchMode configures the matchers function for casbin.
# There are two options for this, 'glob' for glob matcher or 'regex' for regex matcher. If omitted or mis-configured,
# will be set to 'glob' as default.
policy.matchMode: 'glob'

View File

@@ -14,6 +14,7 @@ import (
"github.com/casbin/casbin"
"github.com/casbin/casbin/model"
"github.com/casbin/casbin/util"
jwt "github.com/dgrijalva/jwt-go/v4"
log "github.com/sirupsen/logrus"
"google.golang.org/grpc/codes"
@@ -31,8 +32,10 @@ const (
ConfigMapPolicyCSVKey = "policy.csv"
ConfigMapPolicyDefaultKey = "policy.default"
ConfigMapScopesKey = "scopes"
defaultRBACSyncPeriod = 10 * time.Minute
ConfigMapMatchModeKey = "policy.matchMode"
GlobMatchMode = "glob"
RegexMatchMode = "regex"
defaultRBACSyncPeriod = 10 * time.Minute
)
// Enforcer is a wrapper around an Casbin enforcer that:
@@ -50,6 +53,7 @@ type Enforcer struct {
claimsEnforcerFunc ClaimsEnforcerFunc
model model.Model
defaultRole string
matchMode string
}
// ClaimsEnforcerFunc is func template to enforce a JWT claims. The subject is replaced
@@ -60,22 +64,8 @@ func newEnforcerSafe(params ...interface{}) (e *casbin.Enforcer, err error) {
if err != nil {
return nil, err
}
enfs.AddFunction("globMatch", func(args ...interface{}) (interface{}, error) {
if len(args) < 2 {
return false, nil
}
val, ok := args[0].(string)
if !ok {
return false, nil
}
pattern, ok := args[1].(string)
if !ok {
return false, nil
}
return glob.Match(pattern, val), nil
})
// Default glob match mode
enfs.AddFunction("globOrRegexMatch", globMatchFunc)
return enfs, nil
}
@@ -98,6 +88,40 @@ func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, cl
}
}
// Glob match func
func globMatchFunc(args ...interface{}) (interface{}, error) {
if len(args) < 2 {
return false, nil
}
val, ok := args[0].(string)
if !ok {
return false, nil
}
pattern, ok := args[1].(string)
if !ok {
return false, nil
}
return glob.Match(pattern, val), nil
}
// SetMatchMode set match mode on runtime, glob match or regex match
func (e *Enforcer) SetMatchMode(mode string) {
if mode == RegexMatchMode {
e.matchMode = RegexMatchMode
} else {
e.matchMode = GlobMatchMode
}
e.Enforcer.AddFunction("globOrRegexMatch", func(args ...interface{}) (interface{}, error) {
if mode == RegexMatchMode {
return util.RegexMatchFunc(args...)
} else {
return globMatchFunc(args...)
}
})
}
// SetDefaultRole sets a default role to use during enforcement. Will fall back to this role if
// normal enforcement fails
func (e *Enforcer) SetDefaultRole(roleName string) {
@@ -281,6 +305,7 @@ func (e *Enforcer) runInformer(ctx context.Context, onUpdated func(cm *apiv1.Con
// syncUpdate updates the enforcer
func (e *Enforcer) syncUpdate(cm *apiv1.ConfigMap, onUpdated func(cm *apiv1.ConfigMap) error) error {
e.SetDefaultRole(cm.Data[ConfigMapPolicyDefaultKey])
e.SetMatchMode(cm.Data[ConfigMapMatchModeKey])
policyCSV, ok := cm.Data[ConfigMapPolicyCSVKey]
if !ok {
policyCSV = ""

View File

@@ -336,3 +336,66 @@ func TestEnforceErrorMessage(t *testing.T) {
assert.Equal(t, "rpc error: code = PermissionDenied desc = permission denied: project, sub: proj:default:admin", err.Error())
}
func TestDefaultGlobMatchMode(t *testing.T) {
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
err := enf.syncUpdate(fakeConfigMap(), noOpUpdate)
assert.Nil(t, err)
policy := `
p, alice, clusters, get, "https://github.com/*/*.git", allow
`
_ = enf.SetUserPolicy(policy)
assert.True(t, enf.Enforce("alice", "clusters", "get", "https://github.com/argoproj/argo-cd.git"))
assert.False(t, enf.Enforce("alice", "repositories", "get", "https://github.com/argoproj/argo-cd.git"))
}
func TestGlobMatchMode(t *testing.T) {
cm := fakeConfigMap()
cm.Data[ConfigMapMatchModeKey] = GlobMatchMode
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
err := enf.syncUpdate(cm, noOpUpdate)
assert.Nil(t, err)
policy := `
p, alice, clusters, get, "https://github.com/*/*.git", allow
`
_ = enf.SetUserPolicy(policy)
assert.True(t, enf.Enforce("alice", "clusters", "get", "https://github.com/argoproj/argo-cd.git"))
assert.False(t, enf.Enforce("alice", "clusters", "get", "https://github.com/argo-cd.git"))
}
func TestRegexMatchMode(t *testing.T) {
cm := fakeConfigMap()
cm.Data[ConfigMapMatchModeKey] = RegexMatchMode
kubeclientset := fake.NewSimpleClientset()
enf := NewEnforcer(kubeclientset, fakeNamespace, fakeConfigMapName, nil)
err := enf.syncUpdate(cm, noOpUpdate)
assert.Nil(t, err)
policy := `
p, alice, clusters, get, "https://github.com/argo[a-z]{4}/argo-[a-z]+.git", allow
`
_ = enf.SetUserPolicy(policy)
assert.True(t, enf.Enforce("alice", "clusters", "get", "https://github.com/argoproj/argo-cd.git"))
assert.False(t, enf.Enforce("alice", "clusters", "get", "https://github.com/argoproj/1argo-cd.git"))
}
func TestGlobMatchFunc(t *testing.T) {
ok, _ := globMatchFunc("arg1")
assert.False(t, ok.(bool))
ok, _ = globMatchFunc(time.Now(), "arg2")
assert.False(t, ok.(bool))
ok, _ = globMatchFunc("arg1", time.Now())
assert.False(t, ok.(bool))
ok, _ = globMatchFunc("arg/123", "arg/*")
assert.True(t, ok.(bool))
}