Add CEL fieldSelector / labelSelector support to authorizer library
This commit is contained in:
		
				
					committed by
					
						
						Jordan Liggitt
					
				
			
			
				
	
			
			
			
						parent
						
							03d48b7683
						
					
				
				
					commit
					be2e32fa3e
				
			@@ -20,6 +20,12 @@ import (
 | 
				
			|||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"errors"
 | 
						"errors"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/fields"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/selection"
 | 
				
			||||||
 | 
						genericfeatures "k8s.io/apiserver/pkg/features"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
 | 
						featuregatetesting "k8s.io/component-base/featuregate/testing"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
	"testing"
 | 
						"testing"
 | 
				
			||||||
@@ -125,6 +131,11 @@ func TestCompile(t *testing.T) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func TestFilter(t *testing.T) {
 | 
					func TestFilter(t *testing.T) {
 | 
				
			||||||
 | 
						simpleLabelSelector, err := labels.NewRequirement("apple", selection.Equals, []string{"banana"})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							panic(err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	configMapParams := &corev1.ConfigMap{
 | 
						configMapParams := &corev1.ConfigMap{
 | 
				
			||||||
		ObjectMeta: metav1.ObjectMeta{
 | 
							ObjectMeta: metav1.ObjectMeta{
 | 
				
			||||||
			Name: "foo",
 | 
								Name: "foo",
 | 
				
			||||||
@@ -183,6 +194,7 @@ func TestFilter(t *testing.T) {
 | 
				
			|||||||
		testPerCallLimit uint64
 | 
							testPerCallLimit uint64
 | 
				
			||||||
		namespaceObject  *corev1.Namespace
 | 
							namespaceObject  *corev1.Namespace
 | 
				
			||||||
		strictCost       bool
 | 
							strictCost       bool
 | 
				
			||||||
 | 
							enableSelectors  bool
 | 
				
			||||||
	}{
 | 
						}{
 | 
				
			||||||
		{
 | 
							{
 | 
				
			||||||
			name: "valid syntax for object",
 | 
								name: "valid syntax for object",
 | 
				
			||||||
@@ -486,7 +498,65 @@ func TestFilter(t *testing.T) {
 | 
				
			|||||||
			name: "test authorizer allow resource check with all fields",
 | 
								name: "test authorizer allow resource check with all fields",
 | 
				
			||||||
			validations: []ExpressionAccessor{
 | 
								validations: []ExpressionAccessor{
 | 
				
			||||||
				&condition{
 | 
									&condition{
 | 
				
			||||||
					Expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()",
 | 
										Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								attributes: newValidAttribute(&podObject, false),
 | 
				
			||||||
 | 
								results: []EvaluationResult{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										EvalResult: celtypes.True,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
 | 
				
			||||||
 | 
									ResourceRequest: true,
 | 
				
			||||||
 | 
									APIGroup:        "apps",
 | 
				
			||||||
 | 
									Resource:        "deployments",
 | 
				
			||||||
 | 
									Subresource:     "status",
 | 
				
			||||||
 | 
									Namespace:       "test",
 | 
				
			||||||
 | 
									Name:            "backend",
 | 
				
			||||||
 | 
									Verb:            "create",
 | 
				
			||||||
 | 
									APIVersion:      "*",
 | 
				
			||||||
 | 
									FieldSelectorRequirements: fields.Requirements{
 | 
				
			||||||
 | 
										{Operator: "=", Field: "foo", Value: "bar"},
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									LabelSelectorRequirements: labels.Requirements{
 | 
				
			||||||
 | 
										*simpleLabelSelector,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								enableSelectors: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "test authorizer allow resource check with parse failures",
 | 
				
			||||||
 | 
								validations: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&condition{
 | 
				
			||||||
 | 
										Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo badoperator bar').labelSelector('apple badoperator banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								attributes: newValidAttribute(&podObject, false),
 | 
				
			||||||
 | 
								results: []EvaluationResult{
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										EvalResult: celtypes.True,
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
								},
 | 
				
			||||||
 | 
								authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{
 | 
				
			||||||
 | 
									ResourceRequest:         true,
 | 
				
			||||||
 | 
									APIGroup:                "apps",
 | 
				
			||||||
 | 
									Resource:                "deployments",
 | 
				
			||||||
 | 
									Subresource:             "status",
 | 
				
			||||||
 | 
									Namespace:               "test",
 | 
				
			||||||
 | 
									Name:                    "backend",
 | 
				
			||||||
 | 
									Verb:                    "create",
 | 
				
			||||||
 | 
									APIVersion:              "*",
 | 
				
			||||||
 | 
									FieldSelectorParsingErr: errors.New("invalid selector: 'foo badoperator bar'; can't understand 'foo badoperator bar'"),
 | 
				
			||||||
 | 
									LabelSelectorParsingErr: errors.New("unable to parse requirement: found 'badoperator', expected: in, notin, =, ==, !=, gt, lt"),
 | 
				
			||||||
 | 
								}),
 | 
				
			||||||
 | 
								enableSelectors: true,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							{
 | 
				
			||||||
 | 
								name: "test authorizer allow resource check with all fields, without gate",
 | 
				
			||||||
 | 
								validations: []ExpressionAccessor{
 | 
				
			||||||
 | 
									&condition{
 | 
				
			||||||
 | 
										Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()",
 | 
				
			||||||
				},
 | 
									},
 | 
				
			||||||
			},
 | 
								},
 | 
				
			||||||
			attributes: newValidAttribute(&podObject, false),
 | 
								attributes: newValidAttribute(&podObject, false),
 | 
				
			||||||
@@ -760,6 +830,10 @@ func TestFilter(t *testing.T) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	for _, tc := range cases {
 | 
						for _, tc := range cases {
 | 
				
			||||||
		t.Run(tc.name, func(t *testing.T) {
 | 
							t.Run(tc.name, func(t *testing.T) {
 | 
				
			||||||
 | 
								if tc.enableSelectors {
 | 
				
			||||||
 | 
									featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true)
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if tc.testPerCallLimit == 0 {
 | 
								if tc.testPerCallLimit == 0 {
 | 
				
			||||||
				tc.testPerCallLimit = celconfig.PerCallLimit
 | 
									tc.testPerCallLimit = celconfig.PerCallLimit
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
@@ -1400,6 +1474,7 @@ func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes)
 | 
				
			|||||||
		if !ok {
 | 
							if !ok {
 | 
				
			||||||
			panic(fmt.Sprintf("unsupported type: %T", a))
 | 
								panic(fmt.Sprintf("unsupported type: %T", a))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		if reflect.DeepEqual(f.match.match, *other) {
 | 
							if reflect.DeepEqual(f.match.match, *other) {
 | 
				
			||||||
			return f.match.decision, f.match.reason, f.match.err
 | 
								return f.match.decision, f.match.reason, f.match.err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,6 +19,10 @@ package library
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/fields"
 | 
				
			||||||
 | 
						"k8s.io/apimachinery/pkg/labels"
 | 
				
			||||||
 | 
						genericfeatures "k8s.io/apiserver/pkg/features"
 | 
				
			||||||
 | 
						utilfeature "k8s.io/apiserver/pkg/util/feature"
 | 
				
			||||||
	"reflect"
 | 
						"reflect"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -222,6 +226,12 @@ var authzLibraryDecls = map[string][]cel.FunctionOpt{
 | 
				
			|||||||
	"subresource": {
 | 
						"subresource": {
 | 
				
			||||||
		cel.MemberOverload("resourcecheck_subresource", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
 | 
							cel.MemberOverload("resourcecheck_subresource", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
 | 
				
			||||||
			cel.BinaryBinding(resourceCheckSubresource))},
 | 
								cel.BinaryBinding(resourceCheckSubresource))},
 | 
				
			||||||
 | 
						"fieldSelector": {
 | 
				
			||||||
 | 
							cel.MemberOverload("authorizer_fieldselector", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
 | 
				
			||||||
 | 
								cel.BinaryBinding(resourceCheckFieldSelector))},
 | 
				
			||||||
 | 
						"labelSelector": {
 | 
				
			||||||
 | 
							cel.MemberOverload("authorizer_labelselector", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
 | 
				
			||||||
 | 
								cel.BinaryBinding(resourceCheckLabelSelector))},
 | 
				
			||||||
	"namespace": {
 | 
						"namespace": {
 | 
				
			||||||
		cel.MemberOverload("resourcecheck_namespace", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
 | 
							cel.MemberOverload("resourcecheck_namespace", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
 | 
				
			||||||
			cel.BinaryBinding(resourceCheckNamespace))},
 | 
								cel.BinaryBinding(resourceCheckNamespace))},
 | 
				
			||||||
@@ -354,6 +364,38 @@ func resourceCheckSubresource(arg1, arg2 ref.Val) ref.Val {
 | 
				
			|||||||
	return result
 | 
						return result
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func resourceCheckFieldSelector(arg1, arg2 ref.Val) ref.Val {
 | 
				
			||||||
 | 
						resourceCheck, ok := arg1.(resourceCheckVal)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return types.MaybeNoSuchOverloadErr(arg1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fieldSelector, ok := arg2.Value().(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return types.MaybeNoSuchOverloadErr(arg1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := resourceCheck
 | 
				
			||||||
 | 
						result.fieldSelector = fieldSelector
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func resourceCheckLabelSelector(arg1, arg2 ref.Val) ref.Val {
 | 
				
			||||||
 | 
						resourceCheck, ok := arg1.(resourceCheckVal)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return types.MaybeNoSuchOverloadErr(arg1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						labelSelector, ok := arg2.Value().(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return types.MaybeNoSuchOverloadErr(arg1)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						result := resourceCheck
 | 
				
			||||||
 | 
						result.labelSelector = labelSelector
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func resourceCheckNamespace(arg1, arg2 ref.Val) ref.Val {
 | 
					func resourceCheckNamespace(arg1, arg2 ref.Val) ref.Val {
 | 
				
			||||||
	resourceCheck, ok := arg1.(resourceCheckVal)
 | 
						resourceCheck, ok := arg1.(resourceCheckVal)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
@@ -544,11 +586,13 @@ func (g groupCheckVal) resourceCheck(resource string) resourceCheckVal {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
type resourceCheckVal struct {
 | 
					type resourceCheckVal struct {
 | 
				
			||||||
	receiverOnlyObjectVal
 | 
						receiverOnlyObjectVal
 | 
				
			||||||
	groupCheck  groupCheckVal
 | 
						groupCheck    groupCheckVal
 | 
				
			||||||
	resource    string
 | 
						resource      string
 | 
				
			||||||
	subresource string
 | 
						subresource   string
 | 
				
			||||||
	namespace   string
 | 
						namespace     string
 | 
				
			||||||
	name        string
 | 
						name          string
 | 
				
			||||||
 | 
						fieldSelector string
 | 
				
			||||||
 | 
						labelSelector string
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
 | 
					func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
 | 
				
			||||||
@@ -563,6 +607,26 @@ func (a resourceCheckVal) Authorize(ctx context.Context, verb string) ref.Val {
 | 
				
			|||||||
		Verb:            verb,
 | 
							Verb:            verb,
 | 
				
			||||||
		User:            a.groupCheck.authorizer.userInfo,
 | 
							User:            a.groupCheck.authorizer.userInfo,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AuthorizeWithSelectors) {
 | 
				
			||||||
 | 
							if len(a.fieldSelector) > 0 {
 | 
				
			||||||
 | 
								selector, err := fields.ParseSelector(a.fieldSelector)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									attr.FieldSelectorRequirements, attr.FieldSelectorParsingErr = nil, err
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									attr.FieldSelectorRequirements, attr.FieldSelectorParsingErr = selector.Requirements(), nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if len(a.labelSelector) > 0 {
 | 
				
			||||||
 | 
								requirements, err := labels.ParseToRequirements(a.labelSelector)
 | 
				
			||||||
 | 
								if err != nil {
 | 
				
			||||||
 | 
									attr.LabelSelectorRequirements, attr.LabelSelectorParsingErr = nil, err
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									attr.LabelSelectorRequirements, attr.LabelSelectorParsingErr = requirements, nil
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	decision, reason, err := a.groupCheck.authorizer.authAuthorizer.Authorize(ctx, attr)
 | 
						decision, reason, err := a.groupCheck.authorizer.authAuthorizer.Authorize(ctx, attr)
 | 
				
			||||||
	return newDecision(decision, err, reason)
 | 
						return newDecision(decision, err, reason)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user