fix: use glob matcher in casbin built-in model (#3966)

This commit is contained in:
Alexander Matyushentsev 2020-07-20 13:55:19 -07:00 committed by GitHub
parent 52926b7cb0
commit dfd7457c21
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 66 additions and 45 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) && keyMatch(r.res, p.res) && keyMatch(r.act, p.act) && keyMatch(r.obj, p.obj)
m = g(r.sub, p.sub) && globMatch(r.res, p.res) && globMatch(r.act, p.act) && globMatch(r.obj, p.obj)

View file

@ -47,9 +47,8 @@ import (
"github.com/argoproj/argo-cd/util/argo"
appstatecache "github.com/argoproj/argo-cd/util/cache/appstate"
"github.com/argoproj/argo-cd/util/db"
"github.com/argoproj/argo-cd/util/glob"
settings_util "github.com/argoproj/argo-cd/util/settings"
"github.com/gobwas/glob"
)
const (
@ -265,9 +264,9 @@ func isKnownOrphanedResourceExclusion(key kube.ResourceKey, proj *appv1.AppProje
}
list := proj.Spec.OrphanedResources.Ignore
for _, item := range list {
if item.Kind == "" || match(item.Kind, key.Kind) {
if match(item.Group, key.Group) {
if item.Name == "" || match(item.Name, key.Name) {
if item.Kind == "" || glob.Match(item.Kind, key.Kind) {
if glob.Match(item.Group, key.Group) {
if item.Name == "" || glob.Match(item.Name, key.Name) {
return true
}
}
@ -276,15 +275,6 @@ func isKnownOrphanedResourceExclusion(key kube.ResourceKey, proj *appv1.AppProje
return false
}
func match(pattern, text string) bool {
compiledGlob, err := glob.Compile(pattern)
if err != nil {
log.Warnf("failed to compile pattern %s due to error %v", pattern, err)
return false
}
return compiledGlob.Match(text)
}
func (ctrl *ApplicationController) getResourceTree(a *appv1.Application, managedResources []*appv1.ResourceDiff) (*appv1.ApplicationTree, error) {
nodes := make([]appv1.ResourceNode, 0)

View file

@ -5,12 +5,12 @@ import (
"github.com/argoproj/gitops-engine/pkg/diff"
jsonpatch "github.com/evanphx/json-patch"
"github.com/gobwas/glob"
log "github.com/sirupsen/logrus"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/argoproj/argo-cd/pkg/apis/application/v1alpha1"
"github.com/argoproj/argo-cd/util/glob"
)
type normalizerPatch struct {
@ -62,23 +62,14 @@ func NewIgnoreNormalizer(ignore []v1alpha1.ResourceIgnoreDifferences, overrides
return &ignoreNormalizer{patches: patches}, nil
}
func match(pattern, text string) bool {
compiledGlob, err := glob.Compile(pattern)
if err != nil {
log.Warnf("failed to compile pattern %s due to error %v", pattern, err)
return false
}
return compiledGlob.Match(text)
}
// Normalize removes fields from supplied resource using json paths from matching items of specified resources ignored differences list
func (n *ignoreNormalizer) Normalize(un *unstructured.Unstructured) error {
matched := make([]normalizerPatch, 0)
for _, patch := range n.patches {
groupKind := un.GroupVersionKind().GroupKind()
if match(patch.groupKind.Group, groupKind.Group) &&
match(patch.groupKind.Kind, groupKind.Kind) &&
if glob.Match(patch.groupKind.Group, groupKind.Group) &&
glob.Match(patch.groupKind.Kind, groupKind.Kind) &&
(patch.name == "" || patch.name == un.GetName()) &&
(patch.namespace == "" || patch.namespace == un.GetNamespace()) {

15
util/glob/glob.go Normal file
View file

@ -0,0 +1,15 @@
package glob
import (
"github.com/gobwas/glob"
log "github.com/sirupsen/logrus"
)
func Match(pattern, text string) bool {
compiledGlob, err := glob.Compile(pattern)
if err != nil {
log.Warnf("failed to compile pattern %s due to error %v", pattern, err)
return false
}
return compiledGlob.Match(text)
}

View file

@ -9,6 +9,7 @@ import (
"time"
"github.com/argoproj/argo-cd/util/assets"
"github.com/argoproj/argo-cd/util/glob"
jwtutil "github.com/argoproj/argo-cd/util/jwt"
"github.com/casbin/casbin"
@ -54,10 +55,37 @@ type Enforcer struct {
// ClaimsEnforcerFunc is func template to enforce a JWT claims. The subject is replaced
type ClaimsEnforcerFunc func(claims jwt.Claims, rvals ...interface{}) bool
func newEnforcerSafe(params ...interface{}) (e *casbin.Enforcer, err error) {
enfs, err := casbin.NewEnforcerSafe(params...)
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
})
return enfs, nil
}
func NewEnforcer(clientset kubernetes.Interface, namespace, configmap string, claimsEnforcer ClaimsEnforcerFunc) *Enforcer {
adapter := newAdapter("", "", "")
builtInModel := newBuiltInModel()
enf := casbin.NewEnforcer(builtInModel, adapter)
enf, err := newEnforcerSafe(builtInModel, adapter)
if err != nil {
panic(err)
}
enf.EnableLog(false)
return &Enforcer{
Enforcer: enf,
@ -134,7 +162,7 @@ func (e *Enforcer) EnforceRuntimePolicy(policy string, rvals ...interface{}) boo
if policy == "" {
enf = e.Enforcer
} else {
enf, err = casbin.NewEnforcerSafe(newBuiltInModel(), newAdapter(e.adapter.builtinPolicy, e.adapter.userDefinedPolicy, policy))
enf, err = newEnforcerSafe(newBuiltInModel(), newAdapter(e.adapter.builtinPolicy, e.adapter.userDefinedPolicy, policy))
if err != nil {
log.Warnf("invalid runtime policy: %s", policy)
enf = e.Enforcer
@ -259,7 +287,7 @@ func (e *Enforcer) syncUpdate(cm *apiv1.ConfigMap, onUpdated func(cm *apiv1.Conf
// ValidatePolicy verifies a policy string is acceptable to casbin
func ValidatePolicy(policy string) error {
_, err := casbin.NewEnforcerSafe(newBuiltInModel(), newAdapter("", "", policy))
_, err := newEnforcerSafe(newBuiltInModel(), newAdapter("", "", policy))
if err != nil {
return fmt.Errorf("policy syntax error: %s", policy)
}

View file

@ -128,6 +128,8 @@ p, mike, *, *, foo/obj, deny
p, trudy, applications, get, foo/obj, allow
p, trudy, applications/*, get, foo/obj, allow
p, trudy, applications/secrets, get, foo/obj, deny
p, danny, applications, get, */obj, allow
p, danny, applications, get, proj1/a*p1, allow
`
_ = enf.SetUserPolicy(policy)
@ -171,6 +173,13 @@ p, trudy, applications/secrets, get, foo/obj, deny
assert.True(t, enf.Enforce("trudy", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("trudy", "applications/logs", "get", "foo/obj"))
assert.False(t, enf.Enforce("trudy", "applications/secrets", "get", "foo/obj"))
// Verify trailing wildcards don't grant full access
assert.True(t, enf.Enforce("danny", "applications", "get", "foo/obj"))
assert.True(t, enf.Enforce("danny", "applications", "get", "bar/obj"))
assert.False(t, enf.Enforce("danny", "applications", "get", "foo/bar"))
assert.True(t, enf.Enforce("danny", "applications", "get", "proj1/app1"))
assert.False(t, enf.Enforce("danny", "applications", "get", "proj1/app2"))
}
// TestProjectIsolationEnforcement verifies the ability to create Project specific policies

View file

@ -1,9 +1,6 @@
package settings
import (
"github.com/gobwas/glob"
log "github.com/sirupsen/logrus"
)
import "github.com/argoproj/argo-cd/util/glob"
type FilteredResource struct {
APIGroups []string `json:"apiGroups,omitempty"`
@ -13,22 +10,13 @@ type FilteredResource struct {
func (r FilteredResource) matchGroup(apiGroup string) bool {
for _, excludedApiGroup := range r.APIGroups {
if match(excludedApiGroup, apiGroup) {
if glob.Match(excludedApiGroup, apiGroup) {
return true
}
}
return len(r.APIGroups) == 0
}
func match(pattern, text string) bool {
compiledGlob, err := glob.Compile(pattern)
if err != nil {
log.Warnf("failed to compile pattern %s due to error %v", pattern, err)
return false
}
return compiledGlob.Match(text)
}
func (r FilteredResource) matchKind(kind string) bool {
for _, excludedKind := range r.Kinds {
if excludedKind == "*" || excludedKind == kind {
@ -40,7 +28,7 @@ func (r FilteredResource) matchKind(kind string) bool {
func (r FilteredResource) matchCluster(cluster string) bool {
for _, excludedCluster := range r.Clusters {
if match(excludedCluster, cluster) {
if glob.Match(excludedCluster, cluster) {
return true
}
}