Skip to content

Commit 44d89c8

Browse files
committed
Include empty string attributes for CEL authz evaluation
1 parent 2e2f51a commit 44d89c8

File tree

3 files changed

+54
-22
lines changed

3 files changed

+54
-22
lines changed

staging/src/k8s.io/apiserver/pkg/authorization/cel/compile.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import (
2222
"github.com/google/cel-go/cel"
2323
"github.com/google/cel-go/common/types/ref"
2424

25+
authorizationv1 "k8s.io/api/authorization/v1"
2526
"k8s.io/apimachinery/pkg/util/version"
2627
apiservercel "k8s.io/apiserver/pkg/cel"
2728
"k8s.io/apiserver/pkg/cel/environment"
@@ -143,6 +144,7 @@ func mustBuildEnv(baseEnv *environment.EnvSet) *environment.EnvSet {
143144
}
144145

145146
// buildRequestType generates a DeclType for SubjectAccessReviewSpec.
147+
// if attributes are added here, also add to convertObjectToUnstructured.
146148
func buildRequestType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
147149
resourceAttributesType := buildResourceAttributesType(field, fields)
148150
nonResourceAttributesType := buildNonResourceAttributesType(field, fields)
@@ -157,6 +159,7 @@ func buildRequestType(field func(name string, declType *apiservercel.DeclType, r
157159
}
158160

159161
// buildResourceAttributesType generates a DeclType for ResourceAttributes.
162+
// if attributes are added here, also add to convertObjectToUnstructured.
160163
func buildResourceAttributesType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
161164
return apiservercel.NewObjectType("kubernetes.ResourceAttributes", fields(
162165
field("namespace", apiservercel.StringType, false),
@@ -170,9 +173,42 @@ func buildResourceAttributesType(field func(name string, declType *apiservercel.
170173
}
171174

172175
// buildNonResourceAttributesType generates a DeclType for NonResourceAttributes.
176+
// if attributes are added here, also add to convertObjectToUnstructured.
173177
func buildNonResourceAttributesType(field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType {
174178
return apiservercel.NewObjectType("kubernetes.NonResourceAttributes", fields(
175179
field("path", apiservercel.StringType, false),
176180
field("verb", apiservercel.StringType, false),
177181
))
178182
}
183+
184+
func convertObjectToUnstructured(obj *authorizationv1.SubjectAccessReviewSpec) map[string]interface{} {
185+
// Construct version containing every SubjectAccessReview user and string attribute field, even omitempty ones, for evaluation by CEL
186+
extra := obj.Extra
187+
if extra == nil {
188+
extra = map[string]authorizationv1.ExtraValue{}
189+
}
190+
ret := map[string]interface{}{
191+
"user": obj.User,
192+
"groups": obj.Groups,
193+
"uid": string(obj.UID),
194+
"extra": extra,
195+
}
196+
if obj.ResourceAttributes != nil {
197+
ret["resourceAttributes"] = map[string]string{
198+
"namespace": obj.ResourceAttributes.Namespace,
199+
"verb": obj.ResourceAttributes.Verb,
200+
"group": obj.ResourceAttributes.Group,
201+
"version": obj.ResourceAttributes.Version,
202+
"resource": obj.ResourceAttributes.Resource,
203+
"subresource": obj.ResourceAttributes.Subresource,
204+
"name": obj.ResourceAttributes.Name,
205+
}
206+
}
207+
if obj.NonResourceAttributes != nil {
208+
ret["nonResourceAttributes"] = map[string]string{
209+
"verb": obj.NonResourceAttributes.Verb,
210+
"path": obj.NonResourceAttributes.Path,
211+
}
212+
}
213+
return ret
214+
}

staging/src/k8s.io/apiserver/pkg/authorization/cel/matcher.go

Lines changed: 2 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,8 @@ import (
2121
"fmt"
2222

2323
celgo "github.com/google/cel-go/cel"
24+
2425
authorizationv1 "k8s.io/api/authorization/v1"
25-
"k8s.io/apimachinery/pkg/runtime"
2626
utilerrors "k8s.io/apimachinery/pkg/util/errors"
2727
)
2828

@@ -33,12 +33,8 @@ type CELMatcher struct {
3333
// eval evaluates the given SubjectAccessReview against all cel matchCondition expression
3434
func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessReview) (bool, error) {
3535
var evalErrors []error
36-
specValObject, err := convertObjectToUnstructured(&r.Spec)
37-
if err != nil {
38-
return false, fmt.Errorf("authz celMatcher eval error: convert SubjectAccessReviewSpec object to unstructured failed: %w", err)
39-
}
4036
va := map[string]interface{}{
41-
"request": specValObject,
37+
"request": convertObjectToUnstructured(&r.Spec),
4238
}
4339
for _, compilationResult := range c.CompilationResults {
4440
evalResult, _, err := compilationResult.Program.ContextEval(ctx, va)
@@ -68,14 +64,3 @@ func (c *CELMatcher) Eval(ctx context.Context, r *authorizationv1.SubjectAccessR
6864
// return ALL matchConditions evaluate to TRUE successfully without error
6965
return true, nil
7066
}
71-
72-
func convertObjectToUnstructured(obj *authorizationv1.SubjectAccessReviewSpec) (map[string]interface{}, error) {
73-
if obj == nil {
74-
return nil, nil
75-
}
76-
ret, err := runtime.DefaultUnstructuredConverter.ToUnstructured(obj)
77-
if err != nil {
78-
return nil, err
79-
}
80-
return ret, nil
81-
}

staging/src/k8s.io/apiserver/plugin/pkg/authorizer/webhook/webhook_v1_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ func TestStructuredAuthzConfigFeatureEnablement(t *testing.T) {
704704
Name: "alice",
705705
UID: "1",
706706
Groups: []string{"group1", "group2"},
707+
Extra: map[string][]string{"key1": {"a", "b", "c"}},
707708
},
708709
ResourceRequest: true,
709710
Namespace: "kittensandponies",
@@ -798,6 +799,7 @@ func TestV1WebhookMatchConditions(t *testing.T) {
798799
Name: "alice",
799800
UID: "1",
800801
Groups: []string{"group1", "group2"},
802+
Extra: map[string][]string{"key1": {"a", "b", "c"}},
801803
},
802804
ResourceRequest: true,
803805
Namespace: "kittensandponies",
@@ -848,6 +850,18 @@ func TestV1WebhookMatchConditions(t *testing.T) {
848850
{
849851
Expression: "('group1' in request.groups)",
850852
},
853+
{
854+
Expression: "('key1' in request.extra)",
855+
},
856+
{
857+
Expression: "!('key2' in request.extra)",
858+
},
859+
{
860+
Expression: "('a' in request.extra['key1'])",
861+
},
862+
{
863+
Expression: "!('z' in request.extra['key1'])",
864+
},
851865
{
852866
Expression: "has(request.resourceAttributes) && request.resourceAttributes.namespace == 'kittensandponies'",
853867
},
@@ -1028,13 +1042,10 @@ func TestV1WebhookMatchConditions(t *testing.T) {
10281042
expectedCompileErr: "",
10291043
// default decisionOnError in newWithBackoff to skip
10301044
expectedDecision: authorizer.DecisionNoOpinion,
1031-
expectedEvalErr: "[cel evaluation error: expression 'request.user == 'bob'' resulted in error: no such key: user, cel evaluation error: expression 'has(request.nonResourceAttributes) && request.nonResourceAttributes.verb == 'get'' resulted in error: no such key: verb]",
1045+
expectedEvalErr: "cel evaluation error: expression 'request.resourceAttributes.verb == 'get'' resulted in error: no such key: resourceAttributes",
10321046
expressions: []apiserver.WebhookMatchCondition{
10331047
{
1034-
Expression: "request.user == 'bob'",
1035-
},
1036-
{
1037-
Expression: "has(request.nonResourceAttributes) && request.nonResourceAttributes.verb == 'get'",
1048+
Expression: "request.resourceAttributes.verb == 'get'",
10381049
},
10391050
},
10401051
},

0 commit comments

Comments
 (0)