k8s.io/apiserver@v0.31.1/pkg/authentication/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 "reflect" 21 "strings" 22 "testing" 23 24 authenticationv1 "k8s.io/api/authentication/v1" 25 apiservercel "k8s.io/apiserver/pkg/cel" 26 "k8s.io/apiserver/pkg/cel/environment" 27 ) 28 29 func TestCompileClaimsExpression(t *testing.T) { 30 testCases := []struct { 31 name string 32 expressionAccessors []ExpressionAccessor 33 }{ 34 { 35 name: "valid ClaimMappingCondition", 36 expressionAccessors: []ExpressionAccessor{ 37 &ClaimMappingExpression{ 38 Expression: "claims.foo", 39 }, 40 }, 41 }, 42 { 43 name: "valid ClaimValidationCondition", 44 expressionAccessors: []ExpressionAccessor{ 45 &ClaimValidationCondition{ 46 Expression: "claims.foo == 'bar'", 47 }, 48 }, 49 }, 50 { 51 name: "valid ExtraMapppingCondition", 52 expressionAccessors: []ExpressionAccessor{ 53 &ExtraMappingExpression{ 54 Expression: "claims.foo", 55 }, 56 }, 57 }, 58 } 59 60 compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) 61 62 for _, tc := range testCases { 63 t.Run(tc.name, func(t *testing.T) { 64 for _, expressionAccessor := range tc.expressionAccessors { 65 _, err := compiler.CompileClaimsExpression(expressionAccessor) 66 if err != nil { 67 t.Errorf("unexpected error: %v", err) 68 } 69 } 70 }) 71 } 72 } 73 74 func TestCompileUserExpression(t *testing.T) { 75 testCases := []struct { 76 name string 77 expressionAccessors []ExpressionAccessor 78 }{ 79 { 80 name: "valid UserValidationCondition", 81 expressionAccessors: []ExpressionAccessor{ 82 &ExtraMappingExpression{ 83 Expression: "user.username == 'bar'", 84 }, 85 }, 86 }, 87 } 88 89 compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) 90 91 for _, tc := range testCases { 92 t.Run(tc.name, func(t *testing.T) { 93 for _, expressionAccessor := range tc.expressionAccessors { 94 _, err := compiler.CompileUserExpression(expressionAccessor) 95 if err != nil { 96 t.Errorf("unexpected error: %v", err) 97 } 98 } 99 }) 100 } 101 } 102 103 func TestCompileClaimsExpressionError(t *testing.T) { 104 testCases := []struct { 105 name string 106 expressionAccessors []ExpressionAccessor 107 wantErr string 108 }{ 109 { 110 name: "invalid ClaimValidationCondition", 111 expressionAccessors: []ExpressionAccessor{ 112 &ClaimValidationCondition{ 113 Expression: "claims.foo", 114 }, 115 }, 116 wantErr: "must evaluate to bool", 117 }, 118 { 119 name: "UserValidationCondition with wrong env", 120 expressionAccessors: []ExpressionAccessor{ 121 &UserValidationCondition{ 122 Expression: "user.username == 'foo'", 123 }, 124 }, 125 wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'user' (in container '')`, 126 }, 127 { 128 name: "invalid ClaimMappingCondition", 129 expressionAccessors: []ExpressionAccessor{ 130 &ClaimMappingExpression{ 131 Expression: "claims + 1", 132 }, 133 }, 134 wantErr: `compilation failed: ERROR: <input>:1:8: found no matching overload for '_+_' applied to '(map(string, any), int)'`, 135 }, 136 } 137 138 compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) 139 140 for _, tc := range testCases { 141 t.Run(tc.name, func(t *testing.T) { 142 for _, expressionAccessor := range tc.expressionAccessors { 143 _, err := compiler.CompileClaimsExpression(expressionAccessor) 144 if err == nil { 145 t.Errorf("expected error but got nil") 146 } 147 if !strings.Contains(err.Error(), tc.wantErr) { 148 t.Errorf("expected error to contain %q but got %q", tc.wantErr, err.Error()) 149 } 150 } 151 }) 152 } 153 } 154 155 func TestCompileUserExpressionError(t *testing.T) { 156 testCases := []struct { 157 name string 158 expressionAccessors []ExpressionAccessor 159 wantErr string 160 }{ 161 { 162 name: "invalid UserValidationCondition", 163 expressionAccessors: []ExpressionAccessor{ 164 &UserValidationCondition{ 165 Expression: "user.username", 166 }, 167 }, 168 wantErr: "must evaluate to bool", 169 }, 170 { 171 name: "ClamMappingCondition with wrong env", 172 expressionAccessors: []ExpressionAccessor{ 173 &ClaimMappingExpression{ 174 Expression: "claims.foo", 175 }, 176 }, 177 wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`, 178 }, 179 { 180 name: "ExtraMappingCondition with wrong env", 181 expressionAccessors: []ExpressionAccessor{ 182 &ExtraMappingExpression{ 183 Expression: "claims.foo", 184 }, 185 }, 186 wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`, 187 }, 188 { 189 name: "ClaimValidationCondition with wrong env", 190 expressionAccessors: []ExpressionAccessor{ 191 &ClaimValidationCondition{ 192 Expression: "claims.foo == 'bar'", 193 }, 194 }, 195 wantErr: `compilation failed: ERROR: <input>:1:1: undeclared reference to 'claims' (in container '')`, 196 }, 197 { 198 name: "UserValidationCondition expression with unknown field", 199 expressionAccessors: []ExpressionAccessor{ 200 &UserValidationCondition{ 201 Expression: "user.unknown == 'foo'", 202 }, 203 }, 204 wantErr: `compilation failed: ERROR: <input>:1:5: undefined field 'unknown'`, 205 }, 206 } 207 208 compiler := NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true)) 209 210 for _, tc := range testCases { 211 t.Run(tc.name, func(t *testing.T) { 212 for _, expressionAccessor := range tc.expressionAccessors { 213 _, err := compiler.CompileUserExpression(expressionAccessor) 214 if err == nil { 215 t.Errorf("expected error but got nil") 216 } 217 if !strings.Contains(err.Error(), tc.wantErr) { 218 t.Errorf("expected error to contain %q but got %q", tc.wantErr, err.Error()) 219 } 220 } 221 }) 222 } 223 } 224 225 func TestBuildUserType(t *testing.T) { 226 userDeclType := buildUserType() 227 userType := reflect.TypeOf(authenticationv1.UserInfo{}) 228 229 if len(userDeclType.Fields) != userType.NumField() { 230 t.Errorf("expected %d fields, got %d", userType.NumField(), len(userDeclType.Fields)) 231 } 232 233 for i := 0; i < userType.NumField(); i++ { 234 field := userType.Field(i) 235 jsonTagParts := strings.Split(field.Tag.Get("json"), ",") 236 if len(jsonTagParts) < 1 { 237 t.Fatal("expected json tag to be present") 238 } 239 fieldName := jsonTagParts[0] 240 241 declField, ok := userDeclType.Fields[fieldName] 242 if !ok { 243 t.Errorf("expected field %q to be present", field.Name) 244 } 245 if nativeTypeToCELType(t, field.Type).CelType().Equal(declField.Type.CelType()).Value() != true { 246 t.Errorf("expected field %q to have type %v, got %v", field.Name, field.Type, declField.Type) 247 } 248 } 249 } 250 251 func nativeTypeToCELType(t *testing.T, nativeType reflect.Type) *apiservercel.DeclType { 252 switch nativeType { 253 case reflect.TypeOf(""): 254 return apiservercel.StringType 255 case reflect.TypeOf([]string{}): 256 return apiservercel.NewListType(apiservercel.StringType, -1) 257 case reflect.TypeOf(map[string]authenticationv1.ExtraValue{}): 258 return apiservercel.NewMapType(apiservercel.StringType, apiservercel.AnyType, -1) 259 default: 260 t.Fatalf("unsupported type %v", nativeType) 261 } 262 return nil 263 }