mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Add CEL fieldSelector / labelSelector support to authorizer library
This commit is contained in:
parent
03d48b7683
commit
be2e32fa3e
@ -20,6 +20,12 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
"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"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -125,6 +131,11 @@ func TestCompile(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{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "foo",
|
||||
@ -183,6 +194,7 @@ func TestFilter(t *testing.T) {
|
||||
testPerCallLimit uint64
|
||||
namespaceObject *corev1.Namespace
|
||||
strictCost bool
|
||||
enableSelectors bool
|
||||
}{
|
||||
{
|
||||
name: "valid syntax for object",
|
||||
@ -486,7 +498,65 @@ func TestFilter(t *testing.T) {
|
||||
name: "test authorizer allow resource check with all fields",
|
||||
validations: []ExpressionAccessor{
|
||||
&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),
|
||||
@ -760,6 +830,10 @@ func TestFilter(t *testing.T) {
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
if tc.enableSelectors {
|
||||
featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true)
|
||||
}
|
||||
|
||||
if tc.testPerCallLimit == 0 {
|
||||
tc.testPerCallLimit = celconfig.PerCallLimit
|
||||
}
|
||||
@ -1400,6 +1474,7 @@ func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("unsupported type: %T", a))
|
||||
}
|
||||
|
||||
if reflect.DeepEqual(f.match.match, *other) {
|
||||
return f.match.decision, f.match.reason, f.match.err
|
||||
}
|
||||
|
@ -19,6 +19,10 @@ package library
|
||||
import (
|
||||
"context"
|
||||
"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"
|
||||
"strings"
|
||||
|
||||
@ -222,6 +226,12 @@ var authzLibraryDecls = map[string][]cel.FunctionOpt{
|
||||
"subresource": {
|
||||
cel.MemberOverload("resourcecheck_subresource", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
|
||||
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": {
|
||||
cel.MemberOverload("resourcecheck_namespace", []*cel.Type{ResourceCheckType, cel.StringType}, ResourceCheckType,
|
||||
cel.BinaryBinding(resourceCheckNamespace))},
|
||||
@ -354,6 +364,38 @@ func resourceCheckSubresource(arg1, arg2 ref.Val) ref.Val {
|
||||
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 {
|
||||
resourceCheck, ok := arg1.(resourceCheckVal)
|
||||
if !ok {
|
||||
@ -544,11 +586,13 @@ func (g groupCheckVal) resourceCheck(resource string) resourceCheckVal {
|
||||
|
||||
type resourceCheckVal struct {
|
||||
receiverOnlyObjectVal
|
||||
groupCheck groupCheckVal
|
||||
resource string
|
||||
subresource string
|
||||
namespace string
|
||||
name string
|
||||
groupCheck groupCheckVal
|
||||
resource string
|
||||
subresource string
|
||||
namespace string
|
||||
name string
|
||||
fieldSelector string
|
||||
labelSelector string
|
||||
}
|
||||
|
||||
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,
|
||||
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)
|
||||
return newDecision(decision, err, reason)
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user