mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-28 05:57:25 +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"
|
"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 {
|
||||||
@ -549,6 +591,8 @@ type resourceCheckVal struct {
|
|||||||
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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user