k8s.io/apiserver@v0.31.1/pkg/authorization/cel/compile_test.go (about) 1 /* 2 Copyright 2023 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package cel 18 19 import ( 20 "fmt" 21 "reflect" 22 "strings" 23 "testing" 24 25 v1 "k8s.io/api/authorization/v1" 26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 27 apiservercel "k8s.io/apiserver/pkg/cel" 28 "k8s.io/apiserver/pkg/cel/environment" 29 genericfeatures "k8s.io/apiserver/pkg/features" 30 utilfeature "k8s.io/apiserver/pkg/util/feature" 31 featuregatetesting "k8s.io/component-base/featuregate/testing" 32 ) 33 34 func TestCompileCELExpression(t *testing.T) { 35 cases := []struct { 36 name string 37 expression string 38 expectedError string 39 40 authorizeWithSelectorsEnabled bool 41 }{ 42 { 43 name: "SubjectAccessReviewSpec user comparison", 44 expression: "request.user == 'bob'", 45 }, 46 { 47 name: "undefined fields", 48 expression: "request.time == 'now'", 49 expectedError: "undefined field", 50 }, 51 { 52 name: "Syntax errors", 53 expression: "request++'", 54 expectedError: "Syntax error", 55 }, 56 { 57 name: "bad return type", 58 expression: "request.user", 59 expectedError: "must evaluate to bool", 60 }, 61 { 62 name: "undeclared reference", 63 expression: "x.user", 64 expectedError: "undeclared reference", 65 }, 66 { 67 name: "fieldSelector not enabled", 68 expression: "request.resourceAttributes.fieldSelector.rawSelector == 'foo'", 69 authorizeWithSelectorsEnabled: false, 70 expectedError: `undefined field 'fieldSelector'`, 71 }, 72 { 73 name: "fieldSelector rawSelector", 74 expression: "request.resourceAttributes.fieldSelector.rawSelector == 'foo'", 75 authorizeWithSelectorsEnabled: true, 76 }, 77 { 78 name: "fieldSelector requirement", 79 expression: "request.resourceAttributes.fieldSelector.requirements.exists(r, r.key == 'foo' && r.operator == 'In' && ('bar' in r.values))", 80 authorizeWithSelectorsEnabled: true, 81 }, 82 { 83 name: "labelSelector not enabled", 84 expression: "request.resourceAttributes.labelSelector.rawSelector == 'foo'", 85 authorizeWithSelectorsEnabled: false, 86 expectedError: `undefined field 'labelSelector'`, 87 }, 88 { 89 name: "labelSelector rawSelector", 90 expression: "request.resourceAttributes.labelSelector.rawSelector == 'foo'", 91 authorizeWithSelectorsEnabled: true, 92 }, 93 { 94 name: "labelSelector requirement", 95 expression: "request.resourceAttributes.labelSelector.requirements.exists(r, r.key == 'foo' && r.operator == 'In' && ('bar' in r.values))", 96 authorizeWithSelectorsEnabled: true, 97 }, 98 } 99 100 for _, tc := range cases { 101 t.Run(tc.name, func(t *testing.T) { 102 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, tc.authorizeWithSelectorsEnabled) 103 compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) 104 _, err := compiler.CompileCELExpression(&SubjectAccessReviewMatchCondition{ 105 Expression: tc.expression, 106 }) 107 if len(tc.expectedError) > 0 && (err == nil || !strings.Contains(err.Error(), tc.expectedError)) { 108 t.Fatalf("expected error: %s compiling expression %s, got: %v", tc.expectedError, tc.expression, err) 109 } 110 if len(tc.expectedError) == 0 && err != nil { 111 t.Fatalf("unexpected error %v compiling expression %s", err, tc.expression) 112 } 113 }) 114 } 115 } 116 117 func TestBuildRequestType(t *testing.T) { 118 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true) 119 f := func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField { 120 return apiservercel.NewDeclField(name, declType, required, nil, nil) 121 } 122 fs := func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField { 123 result := make(map[string]*apiservercel.DeclField, len(fields)) 124 for _, f := range fields { 125 result[f.Name] = f 126 } 127 return result 128 } 129 130 requestDeclType := buildRequestType(f, fs) 131 requestType := reflect.TypeOf(v1.SubjectAccessReviewSpec{}) 132 if len(requestDeclType.Fields) != requestType.NumField() { 133 t.Fatalf("expected %d fields for SubjectAccessReviewSpec, got %d", requestType.NumField(), len(requestDeclType.Fields)) 134 } 135 resourceAttributesDeclType := buildResourceAttributesType(f, fs) 136 resourceAttributeType := reflect.TypeOf(v1.ResourceAttributes{}) 137 if len(resourceAttributesDeclType.Fields) != resourceAttributeType.NumField() { 138 t.Fatalf("expected %d fields for ResourceAttributes, got %d", resourceAttributeType.NumField(), len(resourceAttributesDeclType.Fields)) 139 } 140 nonResourceAttributesDeclType := buildNonResourceAttributesType(f, fs) 141 nonResourceAttributeType := reflect.TypeOf(v1.NonResourceAttributes{}) 142 if len(nonResourceAttributesDeclType.Fields) != nonResourceAttributeType.NumField() { 143 t.Fatalf("expected %d fields for NonResourceAttributes, got %d", nonResourceAttributeType.NumField(), len(nonResourceAttributesDeclType.Fields)) 144 } 145 if err := compareFieldsForType(t, requestType, requestDeclType, f, fs); err != nil { 146 t.Error(err) 147 } 148 } 149 150 func compareFieldsForType(t *testing.T, nativeType reflect.Type, declType *apiservercel.DeclType, field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) error { 151 for i := 0; i < nativeType.NumField(); i++ { 152 nativeField := nativeType.Field(i) 153 jsonTagParts := strings.Split(nativeField.Tag.Get("json"), ",") 154 if len(jsonTagParts) < 1 { 155 t.Fatal("expected json tag to be present") 156 } 157 fieldName := jsonTagParts[0] 158 159 declField, ok := declType.Fields[fieldName] 160 if !ok { 161 t.Fatalf("expected field %q to be present", nativeField.Name) 162 } 163 declFieldType := nativeTypeToCELType(t, nativeField.Type, field, fields) 164 if declFieldType == nil { 165 return fmt.Errorf("no field type found for %s %v", nativeField.Name, nativeField.Type) 166 } 167 if declFieldType.CelType().Equal(declField.Type.CelType()).Value() != true { 168 return fmt.Errorf("expected native field %q to have type %v, got %v", nativeField.Name, nativeField.Type, declField.Type) 169 } 170 } 171 return nil 172 } 173 174 func nativeTypeToCELType(t *testing.T, nativeType reflect.Type, field func(name string, declType *apiservercel.DeclType, required bool) *apiservercel.DeclField, fields func(fields ...*apiservercel.DeclField) map[string]*apiservercel.DeclField) *apiservercel.DeclType { 175 switch nativeType { 176 case reflect.TypeOf(""), reflect.TypeOf(metav1.LabelSelectorOperator("")), reflect.TypeOf(metav1.FieldSelectorOperator("")): 177 return apiservercel.StringType 178 case reflect.TypeOf([]string{}): 179 return apiservercel.NewListType(apiservercel.StringType, -1) 180 case reflect.TypeOf(map[string]v1.ExtraValue{}): 181 return apiservercel.NewMapType(apiservercel.StringType, apiservercel.NewListType(apiservercel.StringType, -1), -1) 182 case reflect.TypeOf(&v1.ResourceAttributes{}): 183 resourceAttributesDeclType := buildResourceAttributesType(field, fields) 184 if err := compareFieldsForType(t, reflect.TypeOf(v1.ResourceAttributes{}), resourceAttributesDeclType, field, fields); err != nil { 185 t.Error(err) 186 return nil 187 } 188 return resourceAttributesDeclType 189 case reflect.TypeOf(&v1.NonResourceAttributes{}): 190 nonResourceAttributesDeclType := buildNonResourceAttributesType(field, fields) 191 if err := compareFieldsForType(t, reflect.TypeOf(v1.NonResourceAttributes{}), nonResourceAttributesDeclType, field, fields); err != nil { 192 t.Error(err) 193 return nil 194 } 195 return nonResourceAttributesDeclType 196 case reflect.TypeOf(&v1.FieldSelectorAttributes{}): 197 selectorAttributesDeclType := buildFieldSelectorType(field, fields) 198 if err := compareFieldsForType(t, reflect.TypeOf(v1.FieldSelectorAttributes{}), selectorAttributesDeclType, field, fields); err != nil { 199 t.Error(err) 200 return nil 201 } 202 return selectorAttributesDeclType 203 case reflect.TypeOf(&v1.LabelSelectorAttributes{}): 204 selectorAttributesDeclType := buildLabelSelectorType(field, fields) 205 if err := compareFieldsForType(t, reflect.TypeOf(v1.LabelSelectorAttributes{}), selectorAttributesDeclType, field, fields); err != nil { 206 t.Error(err) 207 return nil 208 } 209 return selectorAttributesDeclType 210 case reflect.TypeOf([]metav1.FieldSelectorRequirement{}): 211 requirementType := buildSelectorRequirementType(field, fields) 212 if err := compareFieldsForType(t, reflect.TypeOf(metav1.FieldSelectorRequirement{}), requirementType, field, fields); err != nil { 213 t.Error(err) 214 return nil 215 } 216 return apiservercel.NewListType(requirementType, -1) 217 case reflect.TypeOf([]metav1.LabelSelectorRequirement{}): 218 requirementType := buildSelectorRequirementType(field, fields) 219 if err := compareFieldsForType(t, reflect.TypeOf(metav1.LabelSelectorRequirement{}), requirementType, field, fields); err != nil { 220 t.Error(err) 221 return nil 222 } 223 return apiservercel.NewListType(requirementType, -1) 224 default: 225 t.Fatalf("unsupported type %v", nativeType) 226 } 227 return nil 228 }