k8s.io/apiserver@v0.31.1/pkg/admission/plugin/cel/filter_test.go (about) 1 /* 2 Copyright 2019 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 "context" 21 "errors" 22 "fmt" 23 "reflect" 24 "strings" 25 "testing" 26 27 celgo "github.com/google/cel-go/cel" 28 celtypes "github.com/google/cel-go/common/types" 29 "github.com/stretchr/testify/require" 30 31 pointer "k8s.io/utils/ptr" 32 33 corev1 "k8s.io/api/core/v1" 34 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 35 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 36 "k8s.io/apimachinery/pkg/fields" 37 "k8s.io/apimachinery/pkg/labels" 38 "k8s.io/apimachinery/pkg/runtime" 39 "k8s.io/apimachinery/pkg/runtime/schema" 40 "k8s.io/apimachinery/pkg/selection" 41 "k8s.io/apimachinery/pkg/util/version" 42 "k8s.io/apiserver/pkg/admission" 43 celconfig "k8s.io/apiserver/pkg/apis/cel" 44 "k8s.io/apiserver/pkg/authentication/user" 45 "k8s.io/apiserver/pkg/authorization/authorizer" 46 apiservercel "k8s.io/apiserver/pkg/cel" 47 "k8s.io/apiserver/pkg/cel/environment" 48 genericfeatures "k8s.io/apiserver/pkg/features" 49 utilfeature "k8s.io/apiserver/pkg/util/feature" 50 featuregatetesting "k8s.io/component-base/featuregate/testing" 51 ) 52 53 type condition struct { 54 Expression string 55 } 56 57 func (c *condition) GetExpression() string { 58 return c.Expression 59 } 60 61 func (v *condition) ReturnTypes() []*celgo.Type { 62 return []*celgo.Type{celgo.BoolType} 63 } 64 65 func TestCompile(t *testing.T) { 66 cases := []struct { 67 name string 68 validation []ExpressionAccessor 69 errorExpressions map[string]string 70 }{ 71 { 72 name: "invalid syntax", 73 validation: []ExpressionAccessor{ 74 &condition{ 75 Expression: "1 < 'asdf'", 76 }, 77 &condition{ 78 Expression: "1 < 2", 79 }, 80 }, 81 errorExpressions: map[string]string{ 82 "1 < 'asdf'": "found no matching overload for '_<_' applied to '(int, string)", 83 }, 84 }, 85 { 86 name: "valid syntax", 87 validation: []ExpressionAccessor{ 88 &condition{ 89 Expression: "1 < 2", 90 }, 91 &condition{ 92 Expression: "object.spec.string.matches('[0-9]+')", 93 }, 94 &condition{ 95 Expression: "request.kind.group == 'example.com' && request.kind.version == 'v1' && request.kind.kind == 'Fake'", 96 }, 97 }, 98 }, 99 } 100 101 for _, tc := range cases { 102 t.Run(tc.name, func(t *testing.T) { 103 c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), true))} 104 e := c.Compile(tc.validation, OptionalVariableDeclarations{HasParams: false, HasAuthorizer: false, StrictCost: true}, environment.NewExpressions) 105 if e == nil { 106 t.Fatalf("unexpected nil validator") 107 } 108 validations := tc.validation 109 CompilationResults := e.(*filter).compilationResults 110 require.Equal(t, len(validations), len(CompilationResults)) 111 112 meets := make([]bool, len(validations)) 113 for expr, expectErr := range tc.errorExpressions { 114 for i, result := range CompilationResults { 115 if validations[i].GetExpression() == expr { 116 if result.Error == nil { 117 t.Errorf("Expect expression '%s' to contain error '%v' but got no error", expr, expectErr) 118 } else if !strings.Contains(result.Error.Error(), expectErr) { 119 t.Errorf("Expected validations '%s' error to contain '%v' but got: %v", expr, expectErr, result.Error) 120 } 121 meets[i] = true 122 } 123 } 124 } 125 for i, meet := range meets { 126 if !meet && CompilationResults[i].Error != nil { 127 t.Errorf("Unexpected err '%v' for expression '%s'", CompilationResults[i].Error, validations[i].GetExpression()) 128 } 129 } 130 }) 131 } 132 } 133 134 func TestFilter(t *testing.T) { 135 simpleLabelSelector, err := labels.NewRequirement("apple", selection.Equals, []string{"banana"}) 136 if err != nil { 137 panic(err) 138 } 139 140 configMapParams := &corev1.ConfigMap{ 141 ObjectMeta: metav1.ObjectMeta{ 142 Name: "foo", 143 }, 144 Data: map[string]string{ 145 "fakeString": "fake", 146 }, 147 } 148 crdParams := &unstructured.Unstructured{ 149 Object: map[string]interface{}{ 150 "spec": map[string]interface{}{ 151 "testSize": 10, 152 }, 153 }, 154 } 155 podObject := corev1.Pod{ 156 ObjectMeta: metav1.ObjectMeta{ 157 Name: "foo", 158 }, 159 Spec: corev1.PodSpec{ 160 NodeName: "testnode", 161 }, 162 } 163 164 nsObject := &corev1.Namespace{ 165 ObjectMeta: metav1.ObjectMeta{ 166 Name: "test", 167 Labels: map[string]string{ 168 "env": "test", 169 "foo": "demo", 170 }, 171 Annotations: map[string]string{ 172 "annotation1": "testAnnotation1", 173 }, 174 Finalizers: []string{"f1"}, 175 }, 176 Spec: corev1.NamespaceSpec{ 177 Finalizers: []corev1.FinalizerName{ 178 corev1.FinalizerKubernetes, 179 }, 180 }, 181 Status: corev1.NamespaceStatus{ 182 Phase: corev1.NamespaceActive, 183 }, 184 } 185 186 v130 := version.MajorMinor(1, 30) 187 v131 := version.MajorMinor(1, 31) 188 189 var nilUnstructured *unstructured.Unstructured 190 cases := []struct { 191 name string 192 attributes admission.Attributes 193 params runtime.Object 194 validations []ExpressionAccessor 195 results []EvaluationResult 196 hasParamKind bool 197 authorizer authorizer.Authorizer 198 testPerCallLimit uint64 199 namespaceObject *corev1.Namespace 200 strictCost bool 201 enableSelectors bool 202 203 compatibilityVersion *version.Version 204 }{ 205 { 206 name: "valid syntax for object", 207 validations: []ExpressionAccessor{ 208 &condition{ 209 Expression: "has(object.subsets) && object.subsets.size() < 2", 210 }, 211 }, 212 attributes: newValidAttribute(nil, false), 213 results: []EvaluationResult{ 214 { 215 EvalResult: celtypes.True, 216 }, 217 }, 218 hasParamKind: false, 219 }, 220 { 221 name: "valid syntax for metadata", 222 validations: []ExpressionAccessor{ 223 &condition{ 224 Expression: "object.metadata.name == 'endpoints1'", 225 }, 226 }, 227 attributes: newValidAttribute(nil, false), 228 results: []EvaluationResult{ 229 { 230 EvalResult: celtypes.True, 231 }, 232 }, 233 hasParamKind: false, 234 }, 235 { 236 name: "valid syntax for oldObject", 237 validations: []ExpressionAccessor{ 238 &condition{ 239 Expression: "oldObject == null", 240 }, 241 &condition{ 242 Expression: "object != null", 243 }, 244 }, 245 attributes: newValidAttribute(nil, false), 246 results: []EvaluationResult{ 247 { 248 EvalResult: celtypes.True, 249 }, 250 { 251 EvalResult: celtypes.True, 252 }, 253 }, 254 hasParamKind: false, 255 }, 256 { 257 name: "valid syntax for request", 258 validations: []ExpressionAccessor{ 259 &condition{ 260 Expression: "request.operation == 'CREATE'", 261 }, 262 }, 263 attributes: newValidAttribute(nil, false), 264 results: []EvaluationResult{ 265 { 266 EvalResult: celtypes.True, 267 }, 268 }, 269 hasParamKind: false, 270 }, 271 { 272 name: "valid syntax for configMap", 273 validations: []ExpressionAccessor{ 274 &condition{ 275 Expression: "request.namespace != params.data.fakeString", 276 }, 277 }, 278 attributes: newValidAttribute(nil, false), 279 results: []EvaluationResult{ 280 { 281 EvalResult: celtypes.True, 282 }, 283 }, 284 hasParamKind: true, 285 params: configMapParams, 286 }, 287 { 288 name: "test failure", 289 validations: []ExpressionAccessor{ 290 &condition{ 291 Expression: "object.subsets.size() > 2", 292 }, 293 }, 294 attributes: newValidAttribute(nil, false), 295 results: []EvaluationResult{ 296 { 297 EvalResult: celtypes.False, 298 }, 299 }, 300 hasParamKind: true, 301 params: &corev1.ConfigMap{ 302 ObjectMeta: metav1.ObjectMeta{ 303 Name: "foo", 304 }, 305 Data: map[string]string{ 306 "fakeString": "fake", 307 }, 308 }, 309 }, 310 { 311 name: "test failure with multiple validations", 312 validations: []ExpressionAccessor{ 313 &condition{ 314 Expression: "has(object.subsets)", 315 }, 316 &condition{ 317 Expression: "object.subsets.size() > 2", 318 }, 319 }, 320 attributes: newValidAttribute(nil, false), 321 results: []EvaluationResult{ 322 { 323 EvalResult: celtypes.True, 324 }, 325 { 326 EvalResult: celtypes.False, 327 }, 328 }, 329 hasParamKind: true, 330 params: configMapParams, 331 }, 332 { 333 name: "test failure policy with multiple failed validations", 334 validations: []ExpressionAccessor{ 335 &condition{ 336 Expression: "oldObject != null", 337 }, 338 &condition{ 339 Expression: "object.subsets.size() > 2", 340 }, 341 }, 342 attributes: newValidAttribute(nil, false), 343 results: []EvaluationResult{ 344 { 345 EvalResult: celtypes.False, 346 }, 347 { 348 EvalResult: celtypes.False, 349 }, 350 }, 351 hasParamKind: true, 352 params: configMapParams, 353 }, 354 { 355 name: "test Object null in delete", 356 validations: []ExpressionAccessor{ 357 &condition{ 358 Expression: "oldObject != null", 359 }, 360 &condition{ 361 Expression: "object == null", 362 }, 363 }, 364 attributes: newValidAttribute(nil, true), 365 results: []EvaluationResult{ 366 { 367 EvalResult: celtypes.True, 368 }, 369 { 370 EvalResult: celtypes.True, 371 }, 372 }, 373 hasParamKind: true, 374 params: configMapParams, 375 }, 376 { 377 name: "test runtime error", 378 validations: []ExpressionAccessor{ 379 &condition{ 380 Expression: "oldObject.x == 100", 381 }, 382 }, 383 attributes: newValidAttribute(nil, true), 384 results: []EvaluationResult{ 385 { 386 Error: errors.New("expression 'oldObject.x == 100' resulted in error"), 387 }, 388 }, 389 hasParamKind: true, 390 params: configMapParams, 391 }, 392 { 393 name: "test against crd param", 394 validations: []ExpressionAccessor{ 395 &condition{ 396 Expression: "object.subsets.size() < params.spec.testSize", 397 }, 398 }, 399 attributes: newValidAttribute(nil, false), 400 results: []EvaluationResult{ 401 { 402 EvalResult: celtypes.True, 403 }, 404 }, 405 hasParamKind: true, 406 params: crdParams, 407 }, 408 { 409 name: "test compile failure", 410 validations: []ExpressionAccessor{ 411 &condition{ 412 Expression: "fail to compile test", 413 }, 414 &condition{ 415 Expression: "object.subsets.size() > params.spec.testSize", 416 }, 417 }, 418 attributes: newValidAttribute(nil, false), 419 results: []EvaluationResult{ 420 { 421 Error: errors.New("compilation error"), 422 }, 423 { 424 EvalResult: celtypes.False, 425 }, 426 }, 427 hasParamKind: true, 428 params: crdParams, 429 }, 430 { 431 name: "test pod", 432 validations: []ExpressionAccessor{ 433 &condition{ 434 Expression: "object.spec.nodeName == 'testnode'", 435 }, 436 }, 437 attributes: newValidAttribute(&podObject, false), 438 results: []EvaluationResult{ 439 { 440 EvalResult: celtypes.True, 441 }, 442 }, 443 hasParamKind: true, 444 params: crdParams, 445 }, 446 { 447 name: "test deny paramKind without paramRef", 448 validations: []ExpressionAccessor{ 449 &condition{ 450 Expression: "params != null", 451 }, 452 }, 453 attributes: newValidAttribute(&podObject, false), 454 results: []EvaluationResult{ 455 { 456 EvalResult: celtypes.False, 457 }, 458 }, 459 hasParamKind: true, 460 }, 461 { 462 name: "test allow paramKind without paramRef", 463 validations: []ExpressionAccessor{ 464 &condition{ 465 Expression: "params == null", 466 }, 467 }, 468 attributes: newValidAttribute(&podObject, false), 469 results: []EvaluationResult{ 470 { 471 EvalResult: celtypes.True, 472 }, 473 }, 474 hasParamKind: true, 475 params: runtime.Object(nilUnstructured), 476 }, 477 { 478 name: "test authorizer allow resource check", 479 validations: []ExpressionAccessor{ 480 &condition{ 481 Expression: "authorizer.group('').resource('endpoints').check('create').allowed()", 482 }, 483 &condition{ 484 Expression: "authorizer.group('').resource('endpoints').check('create').errored()", 485 }, 486 }, 487 attributes: newValidAttribute(&podObject, false), 488 results: []EvaluationResult{ 489 { 490 EvalResult: celtypes.True, 491 }, 492 { 493 EvalResult: celtypes.False, 494 }, 495 }, 496 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 497 ResourceRequest: true, 498 Resource: "endpoints", 499 Verb: "create", 500 APIVersion: "*", 501 }), 502 }, 503 { 504 name: "test authorizer error using fieldSelector with 1.30 compatibility", 505 validations: []ExpressionAccessor{ 506 &condition{ 507 Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()", 508 }, 509 }, 510 attributes: newValidAttribute(&podObject, false), 511 results: []EvaluationResult{ 512 { 513 Error: fmt.Errorf("fieldSelector"), 514 }, 515 }, 516 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 517 ResourceRequest: true, 518 APIGroup: "apps", 519 Resource: "deployments", 520 Subresource: "status", 521 Namespace: "test", 522 Name: "backend", 523 Verb: "create", 524 APIVersion: "*", 525 FieldSelectorRequirements: fields.Requirements{ 526 {Operator: "=", Field: "foo", Value: "bar"}, 527 }, 528 LabelSelectorRequirements: labels.Requirements{ 529 *simpleLabelSelector, 530 }, 531 }), 532 enableSelectors: true, 533 compatibilityVersion: v130, 534 }, 535 { 536 name: "test authorizer allow resource check with all fields", 537 validations: []ExpressionAccessor{ 538 &condition{ 539 Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()", 540 }, 541 }, 542 attributes: newValidAttribute(&podObject, false), 543 results: []EvaluationResult{ 544 { 545 EvalResult: celtypes.True, 546 }, 547 }, 548 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 549 ResourceRequest: true, 550 APIGroup: "apps", 551 Resource: "deployments", 552 Subresource: "status", 553 Namespace: "test", 554 Name: "backend", 555 Verb: "create", 556 APIVersion: "*", 557 FieldSelectorRequirements: fields.Requirements{ 558 {Operator: "=", Field: "foo", Value: "bar"}, 559 }, 560 LabelSelectorRequirements: labels.Requirements{ 561 *simpleLabelSelector, 562 }, 563 }), 564 enableSelectors: true, 565 compatibilityVersion: v131, 566 }, 567 { 568 name: "test authorizer allow resource check with parse failures", 569 validations: []ExpressionAccessor{ 570 &condition{ 571 Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo badoperator bar').labelSelector('apple badoperator banana').subresource('status').namespace('test').name('backend').check('create').allowed()", 572 }, 573 }, 574 attributes: newValidAttribute(&podObject, false), 575 results: []EvaluationResult{ 576 { 577 EvalResult: celtypes.True, 578 }, 579 }, 580 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 581 ResourceRequest: true, 582 APIGroup: "apps", 583 Resource: "deployments", 584 Subresource: "status", 585 Namespace: "test", 586 Name: "backend", 587 Verb: "create", 588 APIVersion: "*", 589 FieldSelectorParsingErr: errors.New("invalid selector: 'foo badoperator bar'; can't understand 'foo badoperator bar'"), 590 LabelSelectorParsingErr: errors.New("unable to parse requirement: found 'badoperator', expected: in, notin, =, ==, !=, gt, lt"), 591 }), 592 enableSelectors: true, 593 compatibilityVersion: v131, 594 }, 595 { 596 name: "test authorizer allow resource check with all fields, without gate", 597 validations: []ExpressionAccessor{ 598 &condition{ 599 Expression: "authorizer.group('apps').resource('deployments').fieldSelector('foo=bar').labelSelector('apple=banana').subresource('status').namespace('test').name('backend').check('create').allowed()", 600 }, 601 }, 602 attributes: newValidAttribute(&podObject, false), 603 results: []EvaluationResult{ 604 { 605 EvalResult: celtypes.True, 606 }, 607 }, 608 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 609 ResourceRequest: true, 610 APIGroup: "apps", 611 Resource: "deployments", 612 Subresource: "status", 613 Namespace: "test", 614 Name: "backend", 615 Verb: "create", 616 APIVersion: "*", 617 }), 618 compatibilityVersion: v131, 619 }, 620 { 621 name: "test authorizer not allowed resource check one incorrect field", 622 validations: []ExpressionAccessor{ 623 &condition{ 624 625 Expression: "authorizer.group('apps').resource('deployments').subresource('status').namespace('test').name('backend').check('create').allowed()", 626 }, 627 }, 628 attributes: newValidAttribute(&podObject, false), 629 results: []EvaluationResult{ 630 { 631 EvalResult: celtypes.False, 632 }, 633 }, 634 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 635 ResourceRequest: true, 636 APIGroup: "apps", 637 Resource: "deployments-xxxx", 638 Subresource: "status", 639 Namespace: "test", 640 Name: "backend", 641 Verb: "create", 642 APIVersion: "*", 643 }), 644 }, 645 { 646 name: "test authorizer reason", 647 validations: []ExpressionAccessor{ 648 &condition{ 649 Expression: "authorizer.group('').resource('endpoints').check('create').reason() == 'fake reason'", 650 }, 651 }, 652 attributes: newValidAttribute(&podObject, false), 653 results: []EvaluationResult{ 654 { 655 EvalResult: celtypes.True, 656 }, 657 }, 658 authorizer: denyAll, 659 }, 660 { 661 name: "test authorizer error", 662 validations: []ExpressionAccessor{ 663 &condition{ 664 Expression: "authorizer.group('').resource('endpoints').check('create').errored()", 665 }, 666 &condition{ 667 Expression: "authorizer.group('').resource('endpoints').check('create').error() == 'fake authz error'", 668 }, 669 &condition{ 670 Expression: "authorizer.group('').resource('endpoints').check('create').allowed()", 671 }, 672 }, 673 attributes: newValidAttribute(&podObject, false), 674 results: []EvaluationResult{ 675 { 676 EvalResult: celtypes.True, 677 }, 678 { 679 EvalResult: celtypes.True, 680 }, 681 { 682 EvalResult: celtypes.False, 683 }, 684 }, 685 authorizer: errorAll, 686 }, 687 { 688 name: "test authorizer allow path check", 689 validations: []ExpressionAccessor{ 690 &condition{ 691 Expression: "authorizer.path('/healthz').check('get').allowed()", 692 }, 693 }, 694 attributes: newValidAttribute(&podObject, false), 695 results: []EvaluationResult{ 696 { 697 EvalResult: celtypes.True, 698 }, 699 }, 700 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 701 Path: "/healthz", 702 Verb: "get", 703 }), 704 }, 705 { 706 name: "test authorizer decision is denied path check", 707 validations: []ExpressionAccessor{ 708 &condition{ 709 Expression: "authorizer.path('/healthz').check('get').allowed() == false", 710 }, 711 }, 712 attributes: newValidAttribute(&podObject, false), 713 results: []EvaluationResult{ 714 { 715 EvalResult: celtypes.True, 716 }, 717 }, 718 authorizer: denyAll, 719 }, 720 { 721 name: "test request resource authorizer allow check", 722 validations: []ExpressionAccessor{ 723 &condition{ 724 Expression: "authorizer.requestResource.check('custom-verb').allowed()", 725 }, 726 }, 727 attributes: endpointCreateAttributes(), 728 results: []EvaluationResult{ 729 { 730 EvalResult: celtypes.True, 731 }, 732 }, 733 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 734 ResourceRequest: true, 735 APIGroup: "", 736 Resource: "endpoints", 737 Subresource: "", 738 Namespace: "default", 739 Name: "endpoints1", 740 Verb: "custom-verb", 741 APIVersion: "*", 742 }), 743 }, 744 { 745 name: "test subresource request resource authorizer allow check", 746 validations: []ExpressionAccessor{ 747 &condition{ 748 Expression: "authorizer.requestResource.check('custom-verb').allowed()", 749 }, 750 }, 751 attributes: endpointStatusUpdateAttributes(), 752 results: []EvaluationResult{ 753 { 754 EvalResult: celtypes.True, 755 }, 756 }, 757 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 758 ResourceRequest: true, 759 APIGroup: "", 760 Resource: "endpoints", 761 Subresource: "status", 762 Namespace: "default", 763 Name: "endpoints1", 764 Verb: "custom-verb", 765 APIVersion: "*", 766 }), 767 }, 768 { 769 name: "test serviceAccount authorizer allow check", 770 validations: []ExpressionAccessor{ 771 &condition{ 772 Expression: "authorizer.serviceAccount('default', 'test-serviceaccount').group('').resource('endpoints').namespace('default').name('endpoints1').check('custom-verb').allowed()", 773 }, 774 }, 775 attributes: endpointCreateAttributes(), 776 results: []EvaluationResult{ 777 { 778 EvalResult: celtypes.True, 779 }, 780 }, 781 authorizer: newAuthzAllowMatch(authorizer.AttributesRecord{ 782 User: &user.DefaultInfo{ 783 Name: "system:serviceaccount:default:test-serviceaccount", 784 Groups: []string{"system:serviceaccounts", "system:serviceaccounts:default"}, 785 }, 786 ResourceRequest: true, 787 APIGroup: "", 788 Resource: "endpoints", 789 Namespace: "default", 790 Name: "endpoints1", 791 Verb: "custom-verb", 792 APIVersion: "*", 793 }), 794 }, 795 { 796 name: "test perCallLimit exceed", 797 validations: []ExpressionAccessor{ 798 &condition{ 799 Expression: "object.subsets.size() < params.spec.testSize", 800 }, 801 }, 802 attributes: newValidAttribute(nil, false), 803 results: []EvaluationResult{ 804 { 805 Error: errors.New(fmt.Sprintf("operation cancelled: actual cost limit exceeded")), 806 }, 807 }, 808 hasParamKind: true, 809 params: crdParams, 810 testPerCallLimit: 1, 811 }, 812 { 813 name: "test namespaceObject", 814 validations: []ExpressionAccessor{ 815 &condition{ 816 Expression: "namespaceObject.metadata.name == 'test'", 817 }, 818 &condition{ 819 Expression: "'env' in namespaceObject.metadata.labels && namespaceObject.metadata.labels.env == 'test'", 820 }, 821 &condition{ 822 Expression: "('fake' in namespaceObject.metadata.labels) && namespaceObject.metadata.labels.fake == 'test'", 823 }, 824 &condition{ 825 Expression: "namespaceObject.spec.finalizers[0] == 'kubernetes'", 826 }, 827 &condition{ 828 Expression: "namespaceObject.status.phase == 'Active'", 829 }, 830 &condition{ 831 Expression: "size(namespaceObject.metadata.managedFields) == 1", 832 }, 833 &condition{ 834 Expression: "size(namespaceObject.metadata.ownerReferences) == 1", 835 }, 836 &condition{ 837 Expression: "'env' in namespaceObject.metadata.annotations", 838 }, 839 }, 840 attributes: newValidAttribute(&podObject, false), 841 results: []EvaluationResult{ 842 { 843 EvalResult: celtypes.True, 844 }, 845 { 846 EvalResult: celtypes.True, 847 }, 848 { 849 EvalResult: celtypes.False, 850 }, 851 { 852 EvalResult: celtypes.True, 853 }, 854 { 855 EvalResult: celtypes.True, 856 }, 857 { 858 Error: errors.New("undefined field 'managedFields'"), 859 }, 860 { 861 Error: errors.New("undefined field 'ownerReferences'"), 862 }, 863 { 864 EvalResult: celtypes.False, 865 }, 866 }, 867 hasParamKind: false, 868 namespaceObject: nsObject, 869 }, 870 } 871 872 for _, tc := range cases { 873 t.Run(tc.name, func(t *testing.T) { 874 if tc.enableSelectors { 875 featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AuthorizeWithSelectors, true) 876 } 877 878 if tc.testPerCallLimit == 0 { 879 tc.testPerCallLimit = celconfig.PerCallLimit 880 } 881 compatibilityVersion := tc.compatibilityVersion 882 if compatibilityVersion == nil { 883 compatibilityVersion = environment.DefaultCompatibilityVersion() 884 } 885 env, err := environment.MustBaseEnvSet(compatibilityVersion, tc.strictCost).Extend( 886 environment.VersionedOptions{ 887 IntroducedVersion: compatibilityVersion, 888 ProgramOptions: []celgo.ProgramOption{celgo.CostLimit(tc.testPerCallLimit)}, 889 }, 890 ) 891 if err != nil { 892 t.Fatal(err) 893 } 894 c := NewFilterCompiler(env) 895 f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: tc.authorizer != nil, StrictCost: tc.strictCost}, environment.NewExpressions) 896 if f == nil { 897 t.Fatalf("unexpected nil validator") 898 } 899 validations := tc.validations 900 CompilationResults := f.(*filter).compilationResults 901 require.Equal(t, len(validations), len(CompilationResults)) 902 903 versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest()) 904 if err != nil { 905 t.Fatalf("unexpected error on conversion: %v", err) 906 } 907 908 optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer} 909 ctx := context.TODO() 910 evalResults, _, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, CreateNamespaceObject(tc.namespaceObject), celconfig.RuntimeCELCostBudget) 911 if err != nil { 912 t.Fatalf("unexpected error: %v", err) 913 } 914 require.Equal(t, len(evalResults), len(tc.results)) 915 for i, result := range tc.results { 916 if result.Error != nil && !strings.Contains(evalResults[i].Error.Error(), result.Error.Error()) { 917 t.Errorf("Expected result '%v' but got '%v'", result.Error, evalResults[i].Error) 918 } 919 if result.Error == nil && evalResults[i].Error != nil { 920 t.Errorf("Expected result '%v' but got error '%v'", result.EvalResult, evalResults[i].Error) 921 continue 922 } 923 if result.EvalResult != evalResults[i].EvalResult { 924 t.Errorf("Expected result '%v' but got '%v'", result.EvalResult, evalResults[i].EvalResult) 925 } 926 } 927 }) 928 } 929 } 930 931 func TestRuntimeCELCostBudget(t *testing.T) { 932 configMapParams := &corev1.ConfigMap{ 933 ObjectMeta: metav1.ObjectMeta{ 934 Name: "foo", 935 }, 936 Data: map[string]string{ 937 "fakeString": "fake", 938 }, 939 } 940 941 cases := []struct { 942 name string 943 attributes admission.Attributes 944 params runtime.Object 945 validations []ExpressionAccessor 946 hasParamKind bool 947 authorizer authorizer.Authorizer 948 testRuntimeCELCostBudget int64 949 exceedBudget bool 950 expectRemainingBudget *int64 951 enableStrictCostEnforcement bool 952 exceedPerCallLimit bool 953 }{ 954 { 955 name: "expression exceed RuntimeCELCostBudget at fist expression", 956 validations: []ExpressionAccessor{ 957 &condition{ 958 Expression: "has(object.subsets) && object.subsets.size() < 2", 959 }, 960 &condition{ 961 Expression: "has(object.subsets)", 962 }, 963 }, 964 attributes: newValidAttribute(nil, false), 965 hasParamKind: false, 966 testRuntimeCELCostBudget: 1, 967 exceedBudget: true, 968 }, 969 { 970 name: "expression exceed RuntimeCELCostBudget at last expression", 971 validations: []ExpressionAccessor{ 972 &condition{ 973 Expression: "has(object.subsets) && object.subsets.size() < 2", 974 }, 975 &condition{ 976 Expression: "object.subsets.size() > 2", 977 }, 978 }, 979 attributes: newValidAttribute(nil, false), 980 hasParamKind: true, 981 params: configMapParams, 982 testRuntimeCELCostBudget: 5, 983 exceedBudget: true, 984 }, 985 { 986 name: "test RuntimeCELCostBudge is not exceed", 987 validations: []ExpressionAccessor{ 988 &condition{ 989 Expression: "oldObject != null", 990 }, 991 &condition{ 992 Expression: "object.subsets.size() > 2", 993 }, 994 }, 995 attributes: newValidAttribute(nil, false), 996 hasParamKind: true, 997 params: configMapParams, 998 exceedBudget: false, 999 testRuntimeCELCostBudget: 10, 1000 expectRemainingBudget: pointer.To(int64(4)), // 10 - 6 1001 }, 1002 { 1003 name: "test RuntimeCELCostBudge exactly covers", 1004 validations: []ExpressionAccessor{ 1005 &condition{ 1006 Expression: "oldObject != null", 1007 }, 1008 &condition{ 1009 Expression: "object.subsets.size() > 2", 1010 }, 1011 }, 1012 attributes: newValidAttribute(nil, false), 1013 hasParamKind: true, 1014 params: configMapParams, 1015 exceedBudget: false, 1016 testRuntimeCELCostBudget: 6, 1017 expectRemainingBudget: pointer.To(int64(0)), 1018 }, 1019 { 1020 name: "test RuntimeCELCostBudge exactly covers then constant", 1021 validations: []ExpressionAccessor{ 1022 &condition{ 1023 Expression: "oldObject != null", 1024 }, 1025 &condition{ 1026 Expression: "object.subsets.size() > 2", 1027 }, 1028 &condition{ 1029 Expression: "true", // zero cost 1030 }, 1031 }, 1032 attributes: newValidAttribute(nil, false), 1033 hasParamKind: true, 1034 params: configMapParams, 1035 exceedBudget: false, 1036 testRuntimeCELCostBudget: 6, 1037 expectRemainingBudget: pointer.To(int64(0)), 1038 }, 1039 { 1040 name: "Extended library cost: authz check", 1041 validations: []ExpressionAccessor{ 1042 &condition{ 1043 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1044 }, 1045 }, 1046 attributes: newValidAttribute(nil, false), 1047 hasParamKind: false, 1048 exceedBudget: false, 1049 testRuntimeCELCostBudget: 6, 1050 expectRemainingBudget: pointer.To(int64(0)), 1051 authorizer: denyAll, 1052 }, 1053 { 1054 name: "Extended library cost: isSorted()", 1055 validations: []ExpressionAccessor{ 1056 &condition{ 1057 Expression: "[1,2,3,4].isSorted()", 1058 }, 1059 }, 1060 attributes: newValidAttribute(nil, false), 1061 hasParamKind: false, 1062 exceedBudget: false, 1063 testRuntimeCELCostBudget: 1, 1064 expectRemainingBudget: pointer.To(int64(0)), 1065 }, 1066 { 1067 name: "Extended library cost: url", 1068 validations: []ExpressionAccessor{ 1069 &condition{ 1070 Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'", 1071 }, 1072 }, 1073 attributes: newValidAttribute(nil, false), 1074 hasParamKind: false, 1075 exceedBudget: false, 1076 testRuntimeCELCostBudget: 2, 1077 expectRemainingBudget: pointer.To(int64(0)), 1078 }, 1079 { 1080 name: "Extended library cost: split", 1081 validations: []ExpressionAccessor{ 1082 &condition{ 1083 Expression: "size('abc 123 def 123'.split(' ')) > 0", 1084 }, 1085 }, 1086 attributes: newValidAttribute(nil, false), 1087 hasParamKind: false, 1088 exceedBudget: false, 1089 testRuntimeCELCostBudget: 3, 1090 expectRemainingBudget: pointer.To(int64(0)), 1091 }, 1092 { 1093 name: "Extended library cost: join", 1094 validations: []ExpressionAccessor{ 1095 &condition{ 1096 Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0", 1097 }, 1098 }, 1099 attributes: newValidAttribute(nil, false), 1100 hasParamKind: false, 1101 exceedBudget: false, 1102 testRuntimeCELCostBudget: 3, 1103 expectRemainingBudget: pointer.To(int64(0)), 1104 }, 1105 { 1106 name: "Extended library cost: find", 1107 validations: []ExpressionAccessor{ 1108 &condition{ 1109 Expression: "size('abc 123 def 123'.find('123')) > 0", 1110 }, 1111 }, 1112 attributes: newValidAttribute(nil, false), 1113 hasParamKind: false, 1114 exceedBudget: false, 1115 testRuntimeCELCostBudget: 3, 1116 expectRemainingBudget: pointer.To(int64(0)), 1117 }, 1118 { 1119 name: "Extended library cost: quantity", 1120 validations: []ExpressionAccessor{ 1121 &condition{ 1122 Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")", 1123 }, 1124 }, 1125 attributes: newValidAttribute(nil, false), 1126 hasParamKind: false, 1127 exceedBudget: false, 1128 testRuntimeCELCostBudget: 6, 1129 expectRemainingBudget: pointer.To(int64(0)), 1130 }, 1131 { 1132 name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at fist expression", 1133 validations: []ExpressionAccessor{ 1134 &condition{ 1135 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1136 }, 1137 &condition{ 1138 Expression: "has(object.subsets)", 1139 }, 1140 }, 1141 attributes: newValidAttribute(nil, false), 1142 hasParamKind: false, 1143 testRuntimeCELCostBudget: 35000, 1144 exceedBudget: true, 1145 authorizer: denyAll, 1146 enableStrictCostEnforcement: true, 1147 }, 1148 { 1149 name: "With StrictCostEnforcementForVAP enabled: expression exceed RuntimeCELCostBudget at last expression", 1150 validations: []ExpressionAccessor{ 1151 &condition{ 1152 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1153 }, 1154 &condition{ 1155 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1156 }, 1157 }, 1158 attributes: newValidAttribute(nil, false), 1159 hasParamKind: false, 1160 testRuntimeCELCostBudget: 700000, 1161 exceedBudget: true, 1162 authorizer: denyAll, 1163 enableStrictCostEnforcement: true, 1164 }, 1165 { 1166 name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge is not exceed", 1167 validations: []ExpressionAccessor{ 1168 &condition{ 1169 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1170 }, 1171 &condition{ 1172 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1173 }, 1174 }, 1175 attributes: newValidAttribute(nil, false), 1176 hasParamKind: true, 1177 params: configMapParams, 1178 exceedBudget: false, 1179 testRuntimeCELCostBudget: 700011, 1180 expectRemainingBudget: pointer.To(int64(1)), // 700011 - 700010 1181 authorizer: denyAll, 1182 enableStrictCostEnforcement: true, 1183 }, 1184 { 1185 name: "With StrictCostEnforcementForVAP enabled: test RuntimeCELCostBudge exactly covers", 1186 validations: []ExpressionAccessor{ 1187 &condition{ 1188 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1189 }, 1190 &condition{ 1191 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed()", 1192 }, 1193 }, 1194 attributes: newValidAttribute(nil, false), 1195 hasParamKind: true, 1196 params: configMapParams, 1197 exceedBudget: false, 1198 testRuntimeCELCostBudget: 700010, 1199 expectRemainingBudget: pointer.To(int64(0)), 1200 authorizer: denyAll, 1201 enableStrictCostEnforcement: true, 1202 }, 1203 { 1204 name: "With StrictCostEnforcementForVAP enabled: per call limit exceeds", 1205 validations: []ExpressionAccessor{ 1206 &condition{ 1207 Expression: "!authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed() && !authorizer.group('').resource('endpoints').check('create').allowed()", 1208 }, 1209 }, 1210 attributes: newValidAttribute(nil, false), 1211 hasParamKind: true, 1212 params: configMapParams, 1213 authorizer: denyAll, 1214 exceedPerCallLimit: true, 1215 testRuntimeCELCostBudget: -1, 1216 enableStrictCostEnforcement: true, 1217 }, 1218 { 1219 name: "With StrictCostEnforcementForVAP enabled: Extended library cost: isSorted()", 1220 validations: []ExpressionAccessor{ 1221 &condition{ 1222 Expression: "[1,2,3,4].isSorted()", 1223 }, 1224 }, 1225 attributes: newValidAttribute(nil, false), 1226 hasParamKind: false, 1227 exceedBudget: false, 1228 testRuntimeCELCostBudget: 4, 1229 expectRemainingBudget: pointer.To(int64(0)), 1230 enableStrictCostEnforcement: true, 1231 }, 1232 { 1233 name: "With StrictCostEnforcementForVAP enabled: Extended library cost: url", 1234 validations: []ExpressionAccessor{ 1235 &condition{ 1236 Expression: "url('https:://kubernetes.io/').getHostname() == 'kubernetes.io'", 1237 }, 1238 }, 1239 attributes: newValidAttribute(nil, false), 1240 hasParamKind: false, 1241 exceedBudget: false, 1242 testRuntimeCELCostBudget: 4, 1243 expectRemainingBudget: pointer.To(int64(0)), 1244 enableStrictCostEnforcement: true, 1245 }, 1246 { 1247 name: "With StrictCostEnforcementForVAP enabled: Extended library cost: split", 1248 validations: []ExpressionAccessor{ 1249 &condition{ 1250 Expression: "size('abc 123 def 123'.split(' ')) > 0", 1251 }, 1252 }, 1253 attributes: newValidAttribute(nil, false), 1254 hasParamKind: false, 1255 exceedBudget: false, 1256 testRuntimeCELCostBudget: 5, 1257 expectRemainingBudget: pointer.To(int64(0)), 1258 enableStrictCostEnforcement: true, 1259 }, 1260 { 1261 name: "With StrictCostEnforcementForVAP enabled: Extended library cost: join", 1262 validations: []ExpressionAccessor{ 1263 &condition{ 1264 Expression: "size(['aa', 'bb', 'cc', 'd', 'e', 'f', 'g', 'h', 'i', 'j'].join(' ')) > 0", 1265 }, 1266 }, 1267 attributes: newValidAttribute(nil, false), 1268 hasParamKind: false, 1269 exceedBudget: false, 1270 testRuntimeCELCostBudget: 7, 1271 expectRemainingBudget: pointer.To(int64(0)), 1272 enableStrictCostEnforcement: true, 1273 }, 1274 { 1275 name: "With StrictCostEnforcementForVAP enabled: Extended library cost: find", 1276 validations: []ExpressionAccessor{ 1277 &condition{ 1278 Expression: "size('abc 123 def 123'.find('123')) > 0", 1279 }, 1280 }, 1281 attributes: newValidAttribute(nil, false), 1282 hasParamKind: false, 1283 exceedBudget: false, 1284 testRuntimeCELCostBudget: 4, 1285 expectRemainingBudget: pointer.To(int64(0)), 1286 enableStrictCostEnforcement: true, 1287 }, 1288 { 1289 name: "With StrictCostEnforcementForVAP enabled: Extended library cost: quantity", 1290 validations: []ExpressionAccessor{ 1291 &condition{ 1292 Expression: "quantity(\"200M\") == quantity(\"0.2G\") && quantity(\"0.2G\") == quantity(\"200M\")", 1293 }, 1294 }, 1295 attributes: newValidAttribute(nil, false), 1296 hasParamKind: false, 1297 exceedBudget: false, 1298 testRuntimeCELCostBudget: 6, 1299 expectRemainingBudget: pointer.To(int64(0)), 1300 enableStrictCostEnforcement: true, 1301 }, 1302 } 1303 1304 for _, tc := range cases { 1305 t.Run(tc.name, func(t *testing.T) { 1306 c := filterCompiler{compiler: NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), tc.enableStrictCostEnforcement))} 1307 f := c.Compile(tc.validations, OptionalVariableDeclarations{HasParams: tc.hasParamKind, HasAuthorizer: true, StrictCost: tc.enableStrictCostEnforcement}, environment.NewExpressions) 1308 if f == nil { 1309 t.Fatalf("unexpected nil validator") 1310 } 1311 validations := tc.validations 1312 CompilationResults := f.(*filter).compilationResults 1313 require.Equal(t, len(validations), len(CompilationResults)) 1314 1315 versionedAttr, err := admission.NewVersionedAttributes(tc.attributes, tc.attributes.GetKind(), newObjectInterfacesForTest()) 1316 if err != nil { 1317 t.Fatalf("unexpected error on conversion: %v", err) 1318 } 1319 1320 if tc.testRuntimeCELCostBudget < 0 { 1321 tc.testRuntimeCELCostBudget = celconfig.RuntimeCELCostBudget 1322 } 1323 optionalVars := OptionalVariableBindings{VersionedParams: tc.params, Authorizer: tc.authorizer} 1324 ctx := context.TODO() 1325 evalResults, remaining, err := f.ForInput(ctx, versionedAttr, CreateAdmissionRequest(versionedAttr.Attributes, metav1.GroupVersionResource(versionedAttr.GetResource()), metav1.GroupVersionKind(versionedAttr.VersionedKind)), optionalVars, nil, tc.testRuntimeCELCostBudget) 1326 if tc.exceedPerCallLimit { 1327 hasCostErr := false 1328 for _, evalResult := range evalResults { 1329 if evalResult.Error != nil && strings.Contains(evalResult.Error.Error(), "operation cancelled: actual cost limit exceeded") { 1330 hasCostErr = true 1331 break 1332 } 1333 } 1334 if !hasCostErr { 1335 t.Errorf("Expected per call limit exceeded error but didn't get one") 1336 } 1337 } 1338 if tc.exceedBudget && err == nil { 1339 t.Errorf("Expected RuntimeCELCostBudge to be exceeded but got nil") 1340 } 1341 if tc.exceedBudget && err != nil && !strings.Contains(err.Error(), "validation failed due to running out of cost budget, no further validation rules will be run") { 1342 t.Errorf("Expected RuntimeCELCostBudge exceeded error but got: %v", err) 1343 } 1344 if err != nil && remaining != -1 { 1345 t.Errorf("expected -1 remaining when error, but got %d", remaining) 1346 } 1347 if err != nil && !tc.exceedBudget { 1348 t.Fatalf("unexpected error: %v", err) 1349 } 1350 if tc.exceedBudget && len(evalResults) != 0 { 1351 t.Fatalf("unexpected result returned: %v", evalResults) 1352 } 1353 if tc.expectRemainingBudget != nil && *tc.expectRemainingBudget != remaining { 1354 t.Errorf("wrong remaining budget, expect %d, but got %d", *tc.expectRemainingBudget, remaining) 1355 } 1356 }) 1357 } 1358 } 1359 1360 // newObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file. 1361 func newObjectInterfacesForTest() admission.ObjectInterfaces { 1362 scheme := runtime.NewScheme() 1363 corev1.AddToScheme(scheme) 1364 return admission.NewObjectInterfacesFromScheme(scheme) 1365 } 1366 1367 func newValidAttribute(object runtime.Object, isDelete bool) admission.Attributes { 1368 var oldObject runtime.Object 1369 if !isDelete { 1370 if object == nil { 1371 object = &corev1.Endpoints{ 1372 ObjectMeta: metav1.ObjectMeta{ 1373 Name: "endpoints1", 1374 }, 1375 Subsets: []corev1.EndpointSubset{ 1376 { 1377 Addresses: []corev1.EndpointAddress{{IP: "127.0.0.0"}}, 1378 }, 1379 }, 1380 } 1381 } 1382 } else { 1383 object = nil 1384 oldObject = &corev1.Endpoints{ 1385 Subsets: []corev1.EndpointSubset{ 1386 { 1387 Addresses: []corev1.EndpointAddress{{IP: "127.0.0.0"}}, 1388 }, 1389 }, 1390 } 1391 } 1392 return admission.NewAttributesRecord(object, oldObject, schema.GroupVersionKind{}, "default", "foo", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil) 1393 1394 } 1395 1396 func TestCompilationErrors(t *testing.T) { 1397 cases := []struct { 1398 name string 1399 results []CompilationResult 1400 expected []error 1401 }{ 1402 { 1403 name: "no errors, empty list", 1404 results: []CompilationResult{}, 1405 expected: []error{}, 1406 }, 1407 { 1408 name: "no errors, several results", 1409 results: []CompilationResult{ 1410 {}, {}, {}, 1411 }, 1412 expected: []error{}, 1413 }, 1414 { 1415 name: "all errors", 1416 results: []CompilationResult{ 1417 { 1418 Error: &apiservercel.Error{ 1419 Detail: "error1", 1420 }, 1421 }, 1422 { 1423 Error: &apiservercel.Error{ 1424 Detail: "error2", 1425 }, 1426 }, 1427 { 1428 Error: &apiservercel.Error{ 1429 Detail: "error3", 1430 }, 1431 }, 1432 }, 1433 expected: []error{ 1434 errors.New("error1"), 1435 errors.New("error2"), 1436 errors.New("error3"), 1437 }, 1438 }, 1439 { 1440 name: "mixed errors and non errors", 1441 results: []CompilationResult{ 1442 {}, 1443 { 1444 Error: &apiservercel.Error{ 1445 Detail: "error1", 1446 }, 1447 }, 1448 {}, 1449 { 1450 Error: &apiservercel.Error{ 1451 Detail: "error2", 1452 }, 1453 }, 1454 {}, 1455 {}, 1456 { 1457 Error: &apiservercel.Error{ 1458 Detail: "error3", 1459 }, 1460 }, 1461 {}, 1462 }, 1463 expected: []error{ 1464 errors.New("error1"), 1465 errors.New("error2"), 1466 errors.New("error3"), 1467 }, 1468 }, 1469 } 1470 1471 for _, tc := range cases { 1472 t.Run(tc.name, func(t *testing.T) { 1473 e := filter{ 1474 compilationResults: tc.results, 1475 } 1476 compilationErrors := e.CompilationErrors() 1477 if compilationErrors == nil { 1478 t.Fatalf("unexpected nil value returned") 1479 } 1480 require.Equal(t, len(compilationErrors), len(tc.expected)) 1481 1482 for i, expectedError := range tc.expected { 1483 if expectedError.Error() != compilationErrors[i].Error() { 1484 t.Errorf("Expected error '%v' but got '%v'", expectedError.Error(), compilationErrors[i].Error()) 1485 } 1486 } 1487 }) 1488 } 1489 } 1490 1491 var denyAll = fakeAuthorizer{defaultResult: authorizerResult{decision: authorizer.DecisionDeny, reason: "fake reason", err: nil}} 1492 var errorAll = fakeAuthorizer{defaultResult: authorizerResult{decision: authorizer.DecisionNoOpinion, reason: "", err: fmt.Errorf("fake authz error")}} 1493 1494 func newAuthzAllowMatch(match authorizer.AttributesRecord) fakeAuthorizer { 1495 return fakeAuthorizer{ 1496 match: &authorizerMatch{ 1497 match: match, 1498 authorizerResult: authorizerResult{decision: authorizer.DecisionAllow, reason: "", err: nil}, 1499 }, 1500 defaultResult: authorizerResult{decision: authorizer.DecisionDeny, reason: "", err: nil}, 1501 } 1502 } 1503 1504 type fakeAuthorizer struct { 1505 match *authorizerMatch 1506 defaultResult authorizerResult 1507 } 1508 1509 type authorizerResult struct { 1510 decision authorizer.Decision 1511 reason string 1512 err error 1513 } 1514 1515 type authorizerMatch struct { 1516 authorizerResult 1517 match authorizer.AttributesRecord 1518 } 1519 1520 func (f fakeAuthorizer) Authorize(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { 1521 if f.match != nil { 1522 other, ok := a.(*authorizer.AttributesRecord) 1523 if !ok { 1524 panic(fmt.Sprintf("unsupported type: %T", a)) 1525 } 1526 1527 if reflect.DeepEqual(f.match.match, *other) { 1528 return f.match.decision, f.match.reason, f.match.err 1529 } 1530 } 1531 return f.defaultResult.decision, f.defaultResult.reason, f.defaultResult.err 1532 } 1533 1534 func endpointCreateAttributes() admission.Attributes { 1535 name := "endpoints1" 1536 namespace := "default" 1537 var object, oldObject runtime.Object 1538 object = &corev1.Endpoints{ 1539 TypeMeta: metav1.TypeMeta{ 1540 Kind: "Endpoints", 1541 APIVersion: "v1", 1542 }, 1543 ObjectMeta: metav1.ObjectMeta{ 1544 Name: name, 1545 }, 1546 Subsets: []corev1.EndpointSubset{ 1547 { 1548 Addresses: []corev1.EndpointAddress{{IP: "127.0.0.0"}}, 1549 }, 1550 }, 1551 } 1552 gvk := schema.GroupVersionKind{Group: "", Version: "v1", Kind: "Endpoints"} 1553 gvr := schema.GroupVersionResource{Group: "", Version: "v1", Resource: "endpoints"} 1554 return admission.NewAttributesRecord(object, oldObject, gvk, namespace, name, gvr, "", admission.Create, &metav1.CreateOptions{}, false, nil) 1555 } 1556 1557 func endpointStatusUpdateAttributes() admission.Attributes { 1558 attrs := endpointCreateAttributes() 1559 return admission.NewAttributesRecord( 1560 attrs.GetObject(), attrs.GetObject(), attrs.GetKind(), attrs.GetNamespace(), attrs.GetName(), 1561 attrs.GetResource(), "status", admission.Update, &metav1.UpdateOptions{}, false, nil) 1562 }