mirror of
https://github.com/argoproj/argo-cd.git
synced 2026-02-20 01:28:45 +01:00
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:
@@ -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)
|
||||
|
||||
@@ -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'
|
||||
|
||||
|
||||
@@ -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 = ""
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user