k8s.io/apiserver@v0.31.1/pkg/admission/plugin/policy/validating/admission_test.go (about) 1 /* 2 Copyright 2022 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 validating_test 18 19 import ( 20 "context" 21 "fmt" 22 "strings" 23 "sync" 24 "sync/atomic" 25 "testing" 26 "time" 27 28 "github.com/stretchr/testify/require" 29 30 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 31 v1 "k8s.io/api/core/v1" 32 "k8s.io/apimachinery/pkg/api/meta" 33 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 34 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" 35 "k8s.io/apimachinery/pkg/labels" 36 "k8s.io/apimachinery/pkg/runtime" 37 "k8s.io/apimachinery/pkg/runtime/schema" 38 "k8s.io/apimachinery/pkg/types" 39 utiljson "k8s.io/apimachinery/pkg/util/json" 40 "k8s.io/apimachinery/pkg/util/sets" 41 "k8s.io/apiserver/pkg/admission" 42 "k8s.io/apiserver/pkg/admission/plugin/policy/generic" 43 "k8s.io/apiserver/pkg/admission/plugin/policy/matching" 44 "k8s.io/apiserver/pkg/admission/plugin/policy/validating" 45 auditinternal "k8s.io/apiserver/pkg/apis/audit" 46 "k8s.io/apiserver/pkg/authorization/authorizer" 47 "k8s.io/apiserver/pkg/warning" 48 ) 49 50 var ( 51 clusterScopedParamsGVK schema.GroupVersionKind = schema.GroupVersionKind{ 52 Group: "example.com", 53 Version: "v1", 54 Kind: "ClusterScopedParamsConfig", 55 } 56 57 paramsGVK schema.GroupVersionKind = schema.GroupVersionKind{ 58 Group: "example.com", 59 Version: "v1", 60 Kind: "ParamsConfig", 61 } 62 63 // Common objects 64 denyPolicy *admissionregistrationv1.ValidatingAdmissionPolicy = &admissionregistrationv1.ValidatingAdmissionPolicy{ 65 ObjectMeta: metav1.ObjectMeta{ 66 Name: "denypolicy.example.com", 67 ResourceVersion: "1", 68 }, 69 Spec: admissionregistrationv1.ValidatingAdmissionPolicySpec{ 70 ParamKind: &admissionregistrationv1.ParamKind{ 71 APIVersion: paramsGVK.GroupVersion().String(), 72 Kind: paramsGVK.Kind, 73 }, 74 FailurePolicy: ptrTo(admissionregistrationv1.Fail), 75 Validations: []admissionregistrationv1.Validation{ 76 { 77 Expression: "messageId for deny policy", 78 }, 79 }, 80 }, 81 } 82 83 fakeParams *unstructured.Unstructured = &unstructured.Unstructured{ 84 Object: map[string]interface{}{ 85 "apiVersion": paramsGVK.GroupVersion().String(), 86 "kind": paramsGVK.Kind, 87 "metadata": map[string]interface{}{ 88 "name": "replicas-test.example.com", 89 "namespace": "default", 90 "resourceVersion": "1", 91 }, 92 "maxReplicas": int64(3), 93 }, 94 } 95 96 denyBinding *admissionregistrationv1.ValidatingAdmissionPolicyBinding = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ 97 ObjectMeta: metav1.ObjectMeta{ 98 Name: "denybinding.example.com", 99 ResourceVersion: "1", 100 }, 101 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{ 102 PolicyName: denyPolicy.Name, 103 ParamRef: &admissionregistrationv1.ParamRef{ 104 Name: fakeParams.GetName(), 105 Namespace: fakeParams.GetNamespace(), 106 // fake object tracker does not populate defaults 107 ParameterNotFoundAction: ptrTo(admissionregistrationv1.DenyAction), 108 }, 109 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny}, 110 }, 111 } 112 denyBindingWithNoParamRef *admissionregistrationv1.ValidatingAdmissionPolicyBinding = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ 113 ObjectMeta: metav1.ObjectMeta{ 114 Name: "denybinding.example.com", 115 ResourceVersion: "1", 116 }, 117 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{ 118 PolicyName: denyPolicy.Name, 119 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny}, 120 }, 121 } 122 123 denyBindingWithAudit = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ 124 ObjectMeta: metav1.ObjectMeta{ 125 Name: "denybinding.example.com", 126 ResourceVersion: "1", 127 }, 128 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{ 129 PolicyName: denyPolicy.Name, 130 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Audit}, 131 }, 132 } 133 denyBindingWithWarn = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ 134 ObjectMeta: metav1.ObjectMeta{ 135 Name: "denybinding.example.com", 136 ResourceVersion: "1", 137 }, 138 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{ 139 PolicyName: denyPolicy.Name, 140 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Warn}, 141 }, 142 } 143 denyBindingWithAll = &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ 144 ObjectMeta: metav1.ObjectMeta{ 145 Name: "denybinding.example.com", 146 ResourceVersion: "1", 147 }, 148 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{ 149 PolicyName: denyPolicy.Name, 150 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny, admissionregistrationv1.Warn, admissionregistrationv1.Audit}, 151 }, 152 } 153 ) 154 155 func newParam(name, namespace string, labels map[string]string) *unstructured.Unstructured { 156 if len(namespace) == 0 { 157 namespace = metav1.NamespaceDefault 158 } 159 res := &unstructured.Unstructured{ 160 Object: map[string]interface{}{ 161 "apiVersion": paramsGVK.GroupVersion().String(), 162 "kind": paramsGVK.Kind, 163 "metadata": map[string]interface{}{ 164 "name": name, 165 "namespace": namespace, 166 "resourceVersion": "1", 167 }, 168 }, 169 } 170 res.SetLabels(labels) 171 return res 172 } 173 174 func newClusterScopedParam(name string, labels map[string]string) *unstructured.Unstructured { 175 res := &unstructured.Unstructured{ 176 Object: map[string]interface{}{ 177 "apiVersion": clusterScopedParamsGVK.GroupVersion().String(), 178 "kind": clusterScopedParamsGVK.Kind, 179 "metadata": map[string]interface{}{ 180 "name": name, 181 "resourceVersion": "1", 182 }, 183 }, 184 } 185 res.SetLabels(labels) 186 return res 187 } 188 189 var _ validating.Validator = validateFunc(nil) 190 191 type validateFunc func( 192 ctx context.Context, 193 matchResource schema.GroupVersionResource, 194 versionedAttr *admission.VersionedAttributes, 195 versionedParams runtime.Object, 196 namespace *v1.Namespace, 197 runtimeCELCostBudget int64, 198 authz authorizer.Authorizer) validating.ValidateResult 199 200 type fakeCompiler struct { 201 ValidateFuncs map[types.NamespacedName]validating.Validator 202 203 lock sync.Mutex 204 NumCompiles map[types.NamespacedName]int 205 } 206 207 func (f *fakeCompiler) getNumCompiles(p *validating.Policy) int { 208 f.lock.Lock() 209 defer f.lock.Unlock() 210 return f.NumCompiles[types.NamespacedName{ 211 Name: p.Name, 212 Namespace: p.Namespace, 213 }] 214 } 215 216 func (f *fakeCompiler) RegisterDefinition(definition *validating.Policy, vf validateFunc) { 217 if f.ValidateFuncs == nil { 218 f.ValidateFuncs = make(map[types.NamespacedName]validating.Validator) 219 } 220 221 f.ValidateFuncs[types.NamespacedName{ 222 Name: definition.Name, 223 Namespace: definition.Namespace, 224 }] = vf 225 } 226 227 func (f *fakeCompiler) CompilePolicy(policy *validating.Policy) validating.Validator { 228 nn := types.NamespacedName{ 229 Name: policy.Name, 230 Namespace: policy.Namespace, 231 } 232 233 defer func() { 234 f.lock.Lock() 235 defer f.lock.Unlock() 236 if f.NumCompiles == nil { 237 f.NumCompiles = make(map[types.NamespacedName]int) 238 } 239 f.NumCompiles[nn]++ 240 }() 241 return f.ValidateFuncs[nn] 242 } 243 244 func (f validateFunc) Validate( 245 ctx context.Context, 246 matchResource schema.GroupVersionResource, 247 versionedAttr *admission.VersionedAttributes, 248 versionedParams runtime.Object, 249 namespace *v1.Namespace, 250 runtimeCELCostBudget int64, 251 authz authorizer.Authorizer, 252 ) validating.ValidateResult { 253 return f( 254 ctx, 255 matchResource, 256 versionedAttr, 257 versionedParams, 258 namespace, 259 runtimeCELCostBudget, 260 authz, 261 ) 262 } 263 264 var _ generic.PolicyMatcher = &fakeMatcher{} 265 266 func (f *fakeMatcher) ValidateInitialization() error { 267 return nil 268 } 269 270 func (f *fakeMatcher) GetNamespace(name string) (*v1.Namespace, error) { 271 return nil, nil 272 } 273 274 type fakeMatcher struct { 275 DefaultMatch bool 276 DefinitionMatchFuncs map[types.NamespacedName]func(generic.PolicyAccessor, admission.Attributes) bool 277 BindingMatchFuncs map[types.NamespacedName]func(generic.BindingAccessor, admission.Attributes) bool 278 } 279 280 func (f *fakeMatcher) RegisterDefinition(definition *admissionregistrationv1.ValidatingAdmissionPolicy, matchFunc func(generic.PolicyAccessor, admission.Attributes) bool) { 281 namespace, name := definition.Namespace, definition.Name 282 key := types.NamespacedName{ 283 Name: name, 284 Namespace: namespace, 285 } 286 287 if matchFunc != nil { 288 if f.DefinitionMatchFuncs == nil { 289 f.DefinitionMatchFuncs = make(map[types.NamespacedName]func(generic.PolicyAccessor, admission.Attributes) bool) 290 } 291 f.DefinitionMatchFuncs[key] = matchFunc 292 } 293 } 294 295 func (f *fakeMatcher) RegisterBinding(binding *admissionregistrationv1.ValidatingAdmissionPolicyBinding, matchFunc func(generic.BindingAccessor, admission.Attributes) bool) { 296 namespace, name := binding.Namespace, binding.Name 297 key := types.NamespacedName{ 298 Name: name, 299 Namespace: namespace, 300 } 301 302 if matchFunc != nil { 303 if f.BindingMatchFuncs == nil { 304 f.BindingMatchFuncs = make(map[types.NamespacedName]func(generic.BindingAccessor, admission.Attributes) bool) 305 } 306 f.BindingMatchFuncs[key] = matchFunc 307 } 308 } 309 310 // Matches says whether this policy definition matches the provided admission 311 // resource request 312 func (f *fakeMatcher) DefinitionMatches(a admission.Attributes, o admission.ObjectInterfaces, definition generic.PolicyAccessor) (bool, schema.GroupVersionResource, schema.GroupVersionKind, error) { 313 namespace, name := definition.GetNamespace(), definition.GetName() 314 key := types.NamespacedName{ 315 Name: name, 316 Namespace: namespace, 317 } 318 if fun, ok := f.DefinitionMatchFuncs[key]; ok { 319 return fun(definition, a), a.GetResource(), a.GetKind(), nil 320 } 321 322 // Default is match everything 323 return f.DefaultMatch, a.GetResource(), a.GetKind(), nil 324 } 325 326 // Matches says whether this policy definition matches the provided admission 327 // resource request 328 func (f *fakeMatcher) BindingMatches(a admission.Attributes, o admission.ObjectInterfaces, binding generic.BindingAccessor) (bool, error) { 329 namespace, name := binding.GetNamespace(), binding.GetName() 330 key := types.NamespacedName{ 331 Name: name, 332 Namespace: namespace, 333 } 334 if fun, ok := f.BindingMatchFuncs[key]; ok { 335 return fun(binding, a), nil 336 } 337 338 // Default is match everything 339 return f.DefaultMatch, nil 340 } 341 342 func setupFakeTest(t *testing.T, comp *fakeCompiler, match *fakeMatcher) *generic.PolicyTestContext[*validating.Policy, *validating.PolicyBinding, validating.Validator] { 343 return setupTestCommon(t, comp, match, true) 344 } 345 346 // Starts CEL admission controller and sets up a plugin configured with it as well 347 // as object trackers for manipulating the objects available to the system 348 // 349 // ParamTracker only knows the gvk `paramGVK`. If in the future we need to 350 // support multiple types of params this function needs to be augmented 351 // 352 // PolicyTracker expects FakePolicyDefinition and FakePolicyBinding types 353 // !TODO: refactor this test/framework to remove startInformers argument and 354 // clean up the return args, and in general make it more accessible. 355 func setupTestCommon( 356 t *testing.T, 357 compiler *fakeCompiler, 358 matcher generic.PolicyMatcher, 359 shouldStartInformers bool, 360 ) *generic.PolicyTestContext[*validating.Policy, *validating.PolicyBinding, validating.Validator] { 361 testContext, testContextCancel, err := generic.NewPolicyTestContext( 362 validating.NewValidatingAdmissionPolicyAccessor, 363 validating.NewValidatingAdmissionPolicyBindingAccessor, 364 func(p *validating.Policy) validating.Validator { 365 return compiler.CompilePolicy(p) 366 }, 367 func(a authorizer.Authorizer, m *matching.Matcher) generic.Dispatcher[validating.PolicyHook] { 368 coolMatcher := matcher 369 if coolMatcher == nil { 370 coolMatcher = generic.NewPolicyMatcher(m) 371 } 372 return validating.NewDispatcher(a, coolMatcher) 373 }, 374 nil, 375 []meta.RESTMapping{ 376 { 377 Resource: paramsGVK.GroupVersion().WithResource("paramsconfigs"), 378 GroupVersionKind: paramsGVK, 379 Scope: meta.RESTScopeNamespace, 380 }, 381 { 382 Resource: clusterScopedParamsGVK.GroupVersion().WithResource("clusterscopedparamsconfigs"), 383 GroupVersionKind: clusterScopedParamsGVK, 384 Scope: meta.RESTScopeRoot, 385 }, 386 { 387 Resource: schema.GroupVersionResource{Group: "admissionregistration.k8s.io", Version: "v1beta1", Resource: "validatingadmissionpolicies"}, 388 GroupVersionKind: schema.GroupVersionKind{Group: "admissionregistration.k8s.io", Version: "v1beta1", Kind: "ValidatingAdmissionPolicy"}, 389 Scope: meta.RESTScopeRoot, 390 }, 391 }, 392 ) 393 require.NoError(t, err) 394 t.Cleanup(testContextCancel) 395 396 if shouldStartInformers { 397 require.NoError(t, testContext.Start()) 398 } 399 400 return testContext 401 } 402 403 func attributeRecord( 404 old, new runtime.Object, 405 operation admission.Operation, 406 ) *FakeAttributes { 407 if old == nil && new == nil { 408 panic("both `old` and `new` may not be nil") 409 } 410 411 // one of old/new may be nil, but not both 412 example := new 413 if example == nil { 414 example = old 415 } 416 417 accessor, err := meta.Accessor(example) 418 if err != nil { 419 panic(err) 420 } 421 422 return &FakeAttributes{ 423 Attributes: admission.NewAttributesRecord( 424 new, 425 old, 426 example.GetObjectKind().GroupVersionKind(), 427 accessor.GetNamespace(), 428 accessor.GetName(), 429 schema.GroupVersionResource{}, 430 "", 431 operation, 432 nil, 433 false, 434 nil, 435 ), 436 } 437 } 438 439 func ptrTo[T any](obj T) *T { 440 return &obj 441 } 442 443 // ////////////////////////////////////////////////////////////////////////////// 444 // Functionality Tests 445 // ////////////////////////////////////////////////////////////////////////////// 446 447 func TestPluginNotReady(t *testing.T) { 448 compiler := &fakeCompiler{} 449 matcher := &fakeMatcher{ 450 DefaultMatch: true, 451 } 452 453 // Show that an unstarted informer (or one that has failed its listwatch) 454 // will show proper error from plugin 455 ctx := setupTestCommon(t, compiler, matcher, false) 456 err := ctx.Plugin.Dispatch( 457 context.Background(), 458 // Object is irrelevant/unchecked for this test. Just test that 459 // the evaluator is executed, and returns a denial 460 attributeRecord(nil, fakeParams, admission.Create), 461 &admission.RuntimeObjectInterfaces{}, 462 ) 463 464 require.ErrorContains(t, err, "not yet ready to handle request") 465 466 // Show that by now starting the informer, the error is dissipated 467 ctx = setupTestCommon(t, compiler, matcher, true) 468 err = ctx.Plugin.Dispatch( 469 context.Background(), 470 // Object is irrelevant/unchecked for this test. Just test that 471 // the evaluator is executed, and returns a denial 472 attributeRecord(nil, fakeParams, admission.Create), 473 &admission.RuntimeObjectInterfaces{}, 474 ) 475 476 require.NoError(t, err) 477 } 478 479 func TestBasicPolicyDefinitionFailure(t *testing.T) { 480 datalock := sync.Mutex{} 481 numCompiles := 0 482 483 compiler := &fakeCompiler{} 484 matcher := &fakeMatcher{ 485 DefaultMatch: true, 486 } 487 488 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 489 datalock.Lock() 490 numCompiles += 1 491 datalock.Unlock() 492 return validating.ValidateResult{ 493 Decisions: []validating.PolicyDecision{ 494 { 495 Action: validating.ActionDeny, 496 Message: "Denied", 497 }, 498 }, 499 } 500 }) 501 502 testContext := setupFakeTest(t, compiler, matcher) 503 require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding)) 504 505 warningRecorder := newWarningRecorder() 506 warnCtx := warning.WithWarningRecorder(testContext, warningRecorder) 507 attr := attributeRecord(nil, fakeParams, admission.Create) 508 err := testContext.Plugin.Dispatch( 509 warnCtx, 510 // Object is irrelevant/unchecked for this test. Just test that 511 // the evaluator is executed, and returns a denial 512 attr, 513 &admission.RuntimeObjectInterfaces{}, 514 ) 515 516 require.Equal(t, 0, warningRecorder.len()) 517 518 annotations := attr.GetAnnotations(auditinternal.LevelMetadata) 519 require.Empty(t, annotations) 520 521 require.ErrorContains(t, err, `Denied`) 522 } 523 524 // Shows that if a definition does not match the input, it will not be used. 525 // But with a different input it will be used. 526 func TestDefinitionDoesntMatch(t *testing.T) { 527 compiler := &fakeCompiler{} 528 matcher := &fakeMatcher{ 529 DefaultMatch: true, 530 } 531 532 testContext := setupFakeTest(t, compiler, matcher) 533 534 datalock := sync.Mutex{} 535 passedParams := []*unstructured.Unstructured{} 536 numCompiles := 0 537 538 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 539 datalock.Lock() 540 numCompiles += 1 541 datalock.Unlock() 542 return validating.ValidateResult{ 543 Decisions: []validating.PolicyDecision{ 544 { 545 Action: validating.ActionDeny, 546 Message: "Denied", 547 }, 548 }, 549 } 550 }) 551 552 matcher.RegisterDefinition(denyPolicy, func(vap generic.PolicyAccessor, a admission.Attributes) bool { 553 // Match names with even-numbered length 554 obj := a.GetObject() 555 556 accessor, err := meta.Accessor(obj) 557 if err != nil { 558 t.Fatal(err) 559 return false 560 } 561 562 return len(accessor.GetName())%2 == 0 563 }) 564 565 require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding)) 566 567 // Validate a non-matching input. 568 // Should pass validation with no error. 569 570 nonMatchingParams := &unstructured.Unstructured{ 571 Object: map[string]interface{}{ 572 "apiVersion": paramsGVK.GroupVersion().String(), 573 "kind": paramsGVK.Kind, 574 "metadata": map[string]interface{}{ 575 "name": "oddlength", 576 "resourceVersion": "1", 577 }, 578 }, 579 } 580 require.NoError(t, 581 testContext.Plugin.Dispatch(testContext, 582 attributeRecord( 583 nil, nonMatchingParams, 584 admission.Create), &admission.RuntimeObjectInterfaces{})) 585 require.Empty(t, passedParams) 586 587 // Validate a matching input. 588 // Should match and be denied. 589 matchingParams := &unstructured.Unstructured{ 590 Object: map[string]interface{}{ 591 "apiVersion": paramsGVK.GroupVersion().String(), 592 "kind": paramsGVK.Kind, 593 "metadata": map[string]interface{}{ 594 "name": "evenlength", 595 "resourceVersion": "1", 596 }, 597 }, 598 } 599 require.ErrorContains(t, 600 testContext.Plugin.Dispatch(testContext, 601 attributeRecord( 602 nil, matchingParams, 603 admission.Create), &admission.RuntimeObjectInterfaces{}), 604 `Denied`) 605 require.Equal(t, numCompiles, 1) 606 } 607 608 func TestReconfigureBinding(t *testing.T) { 609 compiler := &fakeCompiler{} 610 matcher := &fakeMatcher{ 611 DefaultMatch: true, 612 } 613 614 testContext := setupFakeTest(t, compiler, matcher) 615 616 datalock := sync.Mutex{} 617 numCompiles := 0 618 619 fakeParams2 := &unstructured.Unstructured{ 620 Object: map[string]interface{}{ 621 "apiVersion": paramsGVK.GroupVersion().String(), 622 "kind": paramsGVK.Kind, 623 "metadata": map[string]interface{}{ 624 "name": "replicas-test2.example.com", 625 // fake object tracker does not populate missing namespace 626 "namespace": "default", 627 "resourceVersion": "2", 628 }, 629 "maxReplicas": int64(35), 630 }, 631 } 632 633 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 634 datalock.Lock() 635 numCompiles += 1 636 datalock.Unlock() 637 return validating.ValidateResult{ 638 Decisions: []validating.PolicyDecision{ 639 { 640 Action: validating.ActionDeny, 641 Message: "Denied", 642 }, 643 }, 644 } 645 }) 646 647 denyBinding2 := &admissionregistrationv1.ValidatingAdmissionPolicyBinding{ 648 ObjectMeta: metav1.ObjectMeta{ 649 Name: "denybinding.example.com", 650 ResourceVersion: "2", 651 }, 652 Spec: admissionregistrationv1.ValidatingAdmissionPolicyBindingSpec{ 653 PolicyName: denyPolicy.Name, 654 ParamRef: &admissionregistrationv1.ParamRef{ 655 Name: fakeParams2.GetName(), 656 Namespace: fakeParams2.GetNamespace(), 657 ParameterNotFoundAction: ptrTo(admissionregistrationv1.DenyAction), 658 }, 659 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny}, 660 }, 661 } 662 663 require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding)) 664 665 err := testContext.Plugin.Dispatch( 666 testContext, 667 attributeRecord(nil, fakeParams, admission.Create), 668 &admission.RuntimeObjectInterfaces{}, 669 ) 670 671 // Expect validation to fail for first time due to binding unconditionally 672 // failing 673 require.ErrorContains(t, err, `Denied`, "expect policy validation error") 674 675 // Expect `Compile` only called once 676 require.Equal(t, 1, numCompiles, "expect `Compile` to be called only once") 677 678 // Update the tracker to point at different params 679 require.NoError(t, testContext.UpdateAndWait(denyBinding2)) 680 681 err = testContext.Plugin.Dispatch( 682 testContext, 683 attributeRecord(nil, fakeParams, admission.Create), 684 &admission.RuntimeObjectInterfaces{}, 685 ) 686 687 require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction") 688 689 // Add the missing params 690 require.NoError(t, testContext.UpdateAndWait(fakeParams2)) 691 692 // Expect validation to now fail again. 693 err = testContext.Plugin.Dispatch( 694 testContext, 695 attributeRecord(nil, fakeParams, admission.Create), 696 &admission.RuntimeObjectInterfaces{}, 697 ) 698 699 // Expect validation to fail the third time due to validation failure 700 require.ErrorContains(t, err, `Denied`, "expected a true policy failure, not a configuration error") 701 // require.Equal(t, []*unstructured.Unstructured{fakeParams, fakeParams2}, passedParams, "expected call to `Validate` to cause call to evaluator") 702 require.Equal(t, 2, numCompiles, "expect changing binding causes a recompile") 703 } 704 705 // Shows that a policy which is in effect will stop being in effect when removed 706 func TestRemoveDefinition(t *testing.T) { 707 compiler := &fakeCompiler{} 708 matcher := &fakeMatcher{ 709 DefaultMatch: true, 710 } 711 712 testContext := setupFakeTest(t, compiler, matcher) 713 714 datalock := sync.Mutex{} 715 numCompiles := 0 716 717 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 718 datalock.Lock() 719 numCompiles += 1 720 datalock.Unlock() 721 722 return validating.ValidateResult{ 723 Decisions: []validating.PolicyDecision{ 724 { 725 Action: validating.ActionDeny, 726 Message: "Denied", 727 }, 728 }, 729 } 730 }) 731 732 require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding)) 733 734 record := attributeRecord(nil, fakeParams, admission.Create) 735 require.ErrorContains(t, 736 testContext.Plugin.Dispatch( 737 testContext, 738 record, 739 &admission.RuntimeObjectInterfaces{}, 740 ), 741 `Denied`) 742 743 require.NoError(t, testContext.DeleteAndWait(denyPolicy)) 744 745 require.NoError(t, testContext.Plugin.Dispatch( 746 testContext, 747 // Object is irrelevant/unchecked for this test. Just test that 748 // the evaluator is executed, and returns a denial 749 record, 750 &admission.RuntimeObjectInterfaces{}, 751 )) 752 } 753 754 // Shows that a binding which is in effect will stop being in effect when removed 755 func TestRemoveBinding(t *testing.T) { 756 compiler := &fakeCompiler{} 757 matcher := &fakeMatcher{ 758 DefaultMatch: true, 759 } 760 761 testContext := setupFakeTest(t, compiler, matcher) 762 763 datalock := sync.Mutex{} 764 numCompiles := 0 765 766 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 767 datalock.Lock() 768 numCompiles += 1 769 datalock.Unlock() 770 771 return validating.ValidateResult{ 772 Decisions: []validating.PolicyDecision{ 773 { 774 Action: validating.ActionDeny, 775 Message: "Denied", 776 }, 777 }, 778 } 779 }) 780 781 require.NoError(t, testContext.UpdateAndWait(fakeParams, denyPolicy, denyBinding)) 782 783 record := attributeRecord(nil, fakeParams, admission.Create) 784 785 require.ErrorContains(t, 786 testContext.Plugin.Dispatch( 787 testContext, 788 record, 789 &admission.RuntimeObjectInterfaces{}, 790 ), 791 `Denied`) 792 793 require.NoError(t, testContext.DeleteAndWait(denyBinding)) 794 } 795 796 // Shows that an error is surfaced if a paramSource specified in a binding does 797 // not actually exist 798 func TestInvalidParamSourceGVK(t *testing.T) { 799 compiler := &fakeCompiler{} 800 matcher := &fakeMatcher{ 801 DefaultMatch: true, 802 } 803 804 testContext := setupFakeTest(t, compiler, matcher) 805 passedParams := make(chan *unstructured.Unstructured) 806 807 badPolicy := *denyPolicy 808 badPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{ 809 APIVersion: paramsGVK.GroupVersion().String(), 810 Kind: "BadParamKind", 811 } 812 813 require.NoError(t, testContext.UpdateAndWait(&badPolicy, denyBinding)) 814 815 err := testContext.Plugin.Dispatch( 816 testContext, 817 attributeRecord(nil, fakeParams, admission.Create), 818 &admission.RuntimeObjectInterfaces{}, 819 ) 820 821 // expect the specific error to be that the param was not found, not that CRD 822 // is not existing 823 require.ErrorContains(t, err, 824 `failed to configure policy: failed to find resource referenced by paramKind: 'example.com/v1, Kind=BadParamKind'`) 825 826 close(passedParams) 827 require.Empty(t, passedParams) 828 } 829 830 // Shows that an error is surfaced if a param specified in a binding does not 831 // actually exist 832 func TestInvalidParamSourceInstanceName(t *testing.T) { 833 compiler := &fakeCompiler{} 834 matcher := &fakeMatcher{ 835 DefaultMatch: true, 836 } 837 838 testContext := setupFakeTest(t, compiler, matcher) 839 840 datalock := sync.Mutex{} 841 passedParams := []*unstructured.Unstructured{} 842 numCompiles := 0 843 844 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 845 datalock.Lock() 846 numCompiles += 1 847 datalock.Unlock() 848 849 return validating.ValidateResult{ 850 Decisions: []validating.PolicyDecision{ 851 { 852 Action: validating.ActionDeny, 853 Message: "Denied", 854 }, 855 }, 856 } 857 }) 858 859 require.NoError(t, testContext.UpdateAndWait(denyPolicy, denyBinding)) 860 861 err := testContext.Plugin.Dispatch( 862 testContext, 863 attributeRecord(nil, fakeParams, admission.Create), 864 &admission.RuntimeObjectInterfaces{}, 865 ) 866 867 // expect the specific error to be that the param was not found, not that CRD 868 // is not existing 869 require.ErrorContains(t, err, 870 "no params found for policy binding with `Deny` parameterNotFoundAction") 871 require.Empty(t, passedParams) 872 } 873 874 // Show that policy still gets evaluated with `nil` param if paramRef & namespaceParamRef 875 // are both unset 876 func TestEmptyParamRef(t *testing.T) { 877 compiler := &fakeCompiler{} 878 matcher := &fakeMatcher{ 879 DefaultMatch: true, 880 } 881 882 testContext := setupFakeTest(t, compiler, matcher) 883 884 datalock := sync.Mutex{} 885 numCompiles := 0 886 887 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 888 datalock.Lock() 889 numCompiles += 1 890 datalock.Unlock() 891 892 // Versioned params must be nil to pass the test 893 if versionedParams != nil { 894 return validating.ValidateResult{ 895 Decisions: []validating.PolicyDecision{ 896 { 897 Action: validating.ActionAdmit, 898 }, 899 }, 900 } 901 } 902 return validating.ValidateResult{ 903 Decisions: []validating.PolicyDecision{ 904 { 905 Action: validating.ActionDeny, 906 Message: "Denied", 907 }, 908 }, 909 } 910 }) 911 912 require.NoError(t, testContext.UpdateAndWait(denyPolicy, denyBindingWithNoParamRef)) 913 914 err := testContext.Plugin.Dispatch( 915 testContext, 916 // Object is irrelevant/unchecked for this test. Just test that 917 // the evaluator is executed, and returns a denial 918 attributeRecord(nil, fakeParams, admission.Create), 919 &admission.RuntimeObjectInterfaces{}, 920 ) 921 922 require.ErrorContains(t, err, `Denied`) 923 require.Equal(t, 1, numCompiles) 924 } 925 926 // Shows that a definition with no param source works just fine, and has 927 // nil params passed to its evaluator. 928 // 929 // Also shows that if binding has specified params in this instance then they 930 // are silently ignored. 931 func TestEmptyParamSource(t *testing.T) { 932 compiler := &fakeCompiler{} 933 matcher := &fakeMatcher{ 934 DefaultMatch: true, 935 } 936 937 testContext := setupFakeTest(t, compiler, matcher) 938 939 datalock := sync.Mutex{} 940 numCompiles := 0 941 942 // Push some fake 943 noParamSourcePolicy := *denyPolicy 944 noParamSourcePolicy.Spec.ParamKind = nil 945 946 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 947 datalock.Lock() 948 numCompiles += 1 949 datalock.Unlock() 950 return validating.ValidateResult{ 951 Decisions: []validating.PolicyDecision{ 952 { 953 Action: validating.ActionDeny, 954 Message: "Denied", 955 }, 956 }, 957 } 958 }) 959 960 require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithNoParamRef)) 961 962 err := testContext.Plugin.Dispatch( 963 testContext, 964 // Object is irrelevant/unchecked for this test. Just test that 965 // the evaluator is executed, and returns a denial 966 attributeRecord(nil, fakeParams, admission.Create), 967 &admission.RuntimeObjectInterfaces{}, 968 ) 969 970 require.ErrorContains(t, err, `Denied`) 971 require.Equal(t, 1, numCompiles) 972 } 973 974 // Shows what happens when multiple policies share one param type, then 975 // one policy stops using the param. The expectation is the second policy 976 // keeps behaving normally 977 func TestMultiplePoliciesSharedParamType(t *testing.T) { 978 compiler := &fakeCompiler{} 979 matcher := &fakeMatcher{ 980 DefaultMatch: true, 981 } 982 983 testContext := setupFakeTest(t, compiler, matcher) 984 985 // Use ConfigMap native-typed param 986 policy1 := *denyPolicy 987 policy1.Name = "denypolicy1.example.com" 988 policy1.Spec = admissionregistrationv1.ValidatingAdmissionPolicySpec{ 989 ParamKind: &admissionregistrationv1.ParamKind{ 990 APIVersion: paramsGVK.GroupVersion().String(), 991 Kind: paramsGVK.Kind, 992 }, 993 FailurePolicy: ptrTo(admissionregistrationv1.Fail), 994 Validations: []admissionregistrationv1.Validation{ 995 { 996 Expression: "policy1", 997 }, 998 }, 999 } 1000 1001 policy2 := *denyPolicy 1002 policy2.Name = "denypolicy2.example.com" 1003 policy2.Spec = admissionregistrationv1.ValidatingAdmissionPolicySpec{ 1004 ParamKind: &admissionregistrationv1.ParamKind{ 1005 APIVersion: paramsGVK.GroupVersion().String(), 1006 Kind: paramsGVK.Kind, 1007 }, 1008 FailurePolicy: ptrTo(admissionregistrationv1.Fail), 1009 Validations: []admissionregistrationv1.Validation{ 1010 { 1011 Expression: "policy2", 1012 }, 1013 }, 1014 } 1015 1016 binding1 := *denyBinding 1017 binding2 := *denyBinding 1018 1019 binding1.Name = "denybinding1.example.com" 1020 binding1.Spec.PolicyName = policy1.Name 1021 binding2.Name = "denybinding2.example.com" 1022 binding2.Spec.PolicyName = policy2.Name 1023 1024 evaluations1 := atomic.Int64{} 1025 evaluations2 := atomic.Int64{} 1026 1027 compiler.RegisterDefinition(&policy1, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1028 evaluations1.Add(1) 1029 1030 return validating.ValidateResult{ 1031 Decisions: []validating.PolicyDecision{ 1032 { 1033 Action: validating.ActionAdmit, 1034 }, 1035 }, 1036 } 1037 }) 1038 1039 compiler.RegisterDefinition(&policy2, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1040 evaluations2.Add(1) 1041 return validating.ValidateResult{ 1042 Decisions: []validating.PolicyDecision{ 1043 { 1044 Action: validating.ActionDeny, 1045 Message: "Policy2Denied", 1046 }, 1047 }, 1048 } 1049 }) 1050 1051 require.NoError(t, testContext.UpdateAndWait(fakeParams, &policy1, &binding1)) 1052 1053 // Make sure policy 1 is created and bound to the params type first 1054 require.NoError(t, testContext.UpdateAndWait(&policy2, &binding2)) 1055 1056 err := testContext.Plugin.Dispatch( 1057 testContext, 1058 // Object is irrelevant/unchecked for this test. Just test that 1059 // the evaluator is executed, and returns admit meaning the params 1060 // passed was a configmap 1061 attributeRecord(nil, fakeParams, admission.Create), 1062 &admission.RuntimeObjectInterfaces{}, 1063 ) 1064 1065 require.ErrorContains(t, err, `Denied`) 1066 require.EqualValues(t, 1, compiler.getNumCompiles(&policy1)) 1067 require.EqualValues(t, 1, evaluations1.Load()) 1068 require.EqualValues(t, 1, compiler.getNumCompiles(&policy2)) 1069 require.EqualValues(t, 1, evaluations2.Load()) 1070 1071 // Remove param type from policy1 1072 // Show that policy2 evaluator is still being passed the configmaps 1073 policy1.Spec.ParamKind = nil 1074 policy1.ResourceVersion = "2" 1075 1076 binding1.Spec.ParamRef = nil 1077 binding1.ResourceVersion = "2" 1078 1079 require.NoError(t, testContext.UpdateAndWait(&policy1, &binding1)) 1080 1081 err = testContext.Plugin.Dispatch( 1082 testContext, 1083 // Object is irrelevant/unchecked for this test. Just test that 1084 // the evaluator is executed, and returns admit meaning the params 1085 // passed was a configmap 1086 attributeRecord(nil, fakeParams, admission.Create), 1087 &admission.RuntimeObjectInterfaces{}, 1088 ) 1089 1090 require.ErrorContains(t, err, `Policy2Denied`) 1091 require.EqualValues(t, 2, compiler.getNumCompiles(&policy1)) 1092 require.EqualValues(t, 2, evaluations1.Load()) 1093 require.EqualValues(t, 1, compiler.getNumCompiles(&policy2)) 1094 require.EqualValues(t, 2, evaluations2.Load()) 1095 } 1096 1097 // Shows that we can refer to native-typed params just fine 1098 // (as opposed to CRD params) 1099 func TestNativeTypeParam(t *testing.T) { 1100 compiler := &fakeCompiler{} 1101 matcher := &fakeMatcher{ 1102 DefaultMatch: true, 1103 } 1104 testContext := setupFakeTest(t, compiler, matcher) 1105 evaluations := atomic.Int64{} 1106 1107 // Use ConfigMap native-typed param 1108 nativeTypeParamPolicy := *denyPolicy 1109 nativeTypeParamPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{ 1110 APIVersion: "v1", 1111 Kind: "ConfigMap", 1112 } 1113 1114 compiler.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1115 evaluations.Add(1) 1116 if _, ok := versionedParams.(*v1.ConfigMap); ok { 1117 return validating.ValidateResult{ 1118 Decisions: []validating.PolicyDecision{ 1119 { 1120 Action: validating.ActionDeny, 1121 Message: "correct type", 1122 }, 1123 }, 1124 } 1125 } 1126 return validating.ValidateResult{ 1127 Decisions: []validating.PolicyDecision{ 1128 { 1129 Action: validating.ActionDeny, 1130 Message: "Incorrect param type", 1131 }, 1132 }, 1133 } 1134 }) 1135 1136 configMapParam := &v1.ConfigMap{ 1137 TypeMeta: metav1.TypeMeta{ 1138 APIVersion: "v1", 1139 Kind: "ConfigMap", 1140 }, 1141 ObjectMeta: metav1.ObjectMeta{ 1142 Name: "replicas-test.example.com", 1143 Namespace: "default", 1144 ResourceVersion: "1", 1145 }, 1146 Data: map[string]string{ 1147 "coolkey": "coolvalue", 1148 }, 1149 } 1150 require.NoError(t, testContext.UpdateAndWait(&nativeTypeParamPolicy, denyBinding, configMapParam)) 1151 1152 err := testContext.Plugin.Dispatch( 1153 testContext, 1154 // Object is irrelevant/unchecked for this test. Just test that 1155 // the evaluator is executed, and returns admit meaning the params 1156 // passed was a configmap 1157 attributeRecord(nil, fakeParams, admission.Create), 1158 &admission.RuntimeObjectInterfaces{}, 1159 ) 1160 1161 require.ErrorContains(t, err, "correct type") 1162 require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy)) 1163 require.EqualValues(t, 1, evaluations.Load()) 1164 } 1165 1166 func TestAuditValidationAction(t *testing.T) { 1167 compiler := &fakeCompiler{} 1168 matcher := &fakeMatcher{ 1169 DefaultMatch: true, 1170 } 1171 testContext := setupFakeTest(t, compiler, matcher) 1172 1173 // Push some fake 1174 noParamSourcePolicy := *denyPolicy 1175 noParamSourcePolicy.Spec.ParamKind = nil 1176 1177 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1178 return validating.ValidateResult{ 1179 Decisions: []validating.PolicyDecision{ 1180 { 1181 Action: validating.ActionDeny, 1182 Message: "I'm sorry Dave", 1183 }, 1184 }, 1185 } 1186 }) 1187 1188 require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithAudit)) 1189 1190 attr := attributeRecord(nil, fakeParams, admission.Create) 1191 warningRecorder := newWarningRecorder() 1192 warnCtx := warning.WithWarningRecorder(testContext, warningRecorder) 1193 err := testContext.Plugin.Dispatch( 1194 warnCtx, 1195 attr, 1196 &admission.RuntimeObjectInterfaces{}, 1197 ) 1198 1199 require.Equal(t, 0, warningRecorder.len()) 1200 1201 annotations := attr.GetAnnotations(auditinternal.LevelMetadata) 1202 require.Len(t, annotations, 1) 1203 valueJson, ok := annotations["validation.policy.admission.k8s.io/validation_failure"] 1204 require.True(t, ok) 1205 var value []validating.ValidationFailureValue 1206 jsonErr := utiljson.Unmarshal([]byte(valueJson), &value) 1207 require.NoError(t, jsonErr) 1208 expected := []validating.ValidationFailureValue{{ 1209 ExpressionIndex: 0, 1210 Message: "I'm sorry Dave", 1211 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Audit}, 1212 Binding: "denybinding.example.com", 1213 Policy: noParamSourcePolicy.Name, 1214 }} 1215 require.Equal(t, expected, value) 1216 1217 require.NoError(t, err) 1218 } 1219 1220 func TestWarnValidationAction(t *testing.T) { 1221 compiler := &fakeCompiler{} 1222 matcher := &fakeMatcher{ 1223 DefaultMatch: true, 1224 } 1225 testContext := setupFakeTest(t, compiler, matcher) 1226 1227 // Push some fake 1228 noParamSourcePolicy := *denyPolicy 1229 noParamSourcePolicy.Spec.ParamKind = nil 1230 1231 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1232 return validating.ValidateResult{ 1233 Decisions: []validating.PolicyDecision{ 1234 { 1235 Action: validating.ActionDeny, 1236 Message: "I'm sorry Dave", 1237 }, 1238 }, 1239 } 1240 }) 1241 1242 require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithWarn)) 1243 1244 attr := attributeRecord(nil, fakeParams, admission.Create) 1245 warningRecorder := newWarningRecorder() 1246 warnCtx := warning.WithWarningRecorder(testContext, warningRecorder) 1247 err := testContext.Plugin.Dispatch( 1248 warnCtx, 1249 attr, 1250 &admission.RuntimeObjectInterfaces{}, 1251 ) 1252 1253 require.Equal(t, 1, warningRecorder.len()) 1254 require.True(t, warningRecorder.hasWarning("Validation failed for ValidatingAdmissionPolicy 'denypolicy.example.com' with binding 'denybinding.example.com': I'm sorry Dave")) 1255 1256 annotations := attr.GetAnnotations(auditinternal.LevelMetadata) 1257 require.Empty(t, annotations) 1258 1259 require.NoError(t, err) 1260 } 1261 1262 func TestAllValidationActions(t *testing.T) { 1263 compiler := &fakeCompiler{} 1264 matcher := &fakeMatcher{ 1265 DefaultMatch: true, 1266 } 1267 testContext := setupFakeTest(t, compiler, matcher) 1268 1269 // Push some fake 1270 noParamSourcePolicy := *denyPolicy 1271 noParamSourcePolicy.Spec.ParamKind = nil 1272 1273 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1274 return validating.ValidateResult{ 1275 Decisions: []validating.PolicyDecision{ 1276 { 1277 Action: validating.ActionDeny, 1278 Message: "I'm sorry Dave", 1279 }, 1280 }, 1281 } 1282 }) 1283 1284 require.NoError(t, testContext.UpdateAndWait(&noParamSourcePolicy, denyBindingWithAll)) 1285 1286 attr := attributeRecord(nil, fakeParams, admission.Create) 1287 warningRecorder := newWarningRecorder() 1288 warnCtx := warning.WithWarningRecorder(testContext, warningRecorder) 1289 err := testContext.Plugin.Dispatch( 1290 warnCtx, 1291 attr, 1292 &admission.RuntimeObjectInterfaces{}, 1293 ) 1294 1295 require.Equal(t, 1, warningRecorder.len()) 1296 require.True(t, warningRecorder.hasWarning("Validation failed for ValidatingAdmissionPolicy 'denypolicy.example.com' with binding 'denybinding.example.com': I'm sorry Dave")) 1297 1298 annotations := attr.GetAnnotations(auditinternal.LevelMetadata) 1299 require.Len(t, annotations, 1) 1300 valueJson, ok := annotations["validation.policy.admission.k8s.io/validation_failure"] 1301 require.True(t, ok) 1302 var value []validating.ValidationFailureValue 1303 jsonErr := utiljson.Unmarshal([]byte(valueJson), &value) 1304 require.NoError(t, jsonErr) 1305 expected := []validating.ValidationFailureValue{{ 1306 ExpressionIndex: 0, 1307 Message: "I'm sorry Dave", 1308 ValidationActions: []admissionregistrationv1.ValidationAction{admissionregistrationv1.Deny, admissionregistrationv1.Warn, admissionregistrationv1.Audit}, 1309 Binding: "denybinding.example.com", 1310 Policy: noParamSourcePolicy.Name, 1311 }} 1312 require.Equal(t, expected, value) 1313 1314 require.ErrorContains(t, err, "I'm sorry Dave") 1315 } 1316 1317 func TestNamespaceParamRefName(t *testing.T) { 1318 compiler := &fakeCompiler{} 1319 matcher := &fakeMatcher{ 1320 DefaultMatch: true, 1321 } 1322 testContext := setupFakeTest(t, compiler, matcher) 1323 1324 evaluations := atomic.Int64{} 1325 1326 // Use ConfigMap native-typed param 1327 nativeTypeParamPolicy := *denyPolicy 1328 nativeTypeParamPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{ 1329 APIVersion: "v1", 1330 Kind: "ConfigMap", 1331 } 1332 1333 namespaceParamBinding := *denyBinding 1334 namespaceParamBinding.Spec.ParamRef = &admissionregistrationv1.ParamRef{ 1335 Name: "replicas-test.example.com", 1336 } 1337 lock := sync.Mutex{} 1338 observedParamNamespaces := []string{} 1339 compiler.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1340 lock.Lock() 1341 defer lock.Unlock() 1342 1343 evaluations.Add(1) 1344 if p, ok := versionedParams.(*v1.ConfigMap); ok { 1345 observedParamNamespaces = append(observedParamNamespaces, p.Namespace) 1346 return validating.ValidateResult{ 1347 Decisions: []validating.PolicyDecision{ 1348 { 1349 Action: validating.ActionDeny, 1350 Message: "correct type", 1351 }, 1352 }, 1353 } 1354 } 1355 return validating.ValidateResult{ 1356 Decisions: []validating.PolicyDecision{ 1357 { 1358 Action: validating.ActionDeny, 1359 Message: "Incorrect param type", 1360 }, 1361 }, 1362 } 1363 }) 1364 1365 configMapParam := &v1.ConfigMap{ 1366 TypeMeta: metav1.TypeMeta{ 1367 APIVersion: "v1", 1368 Kind: "ConfigMap", 1369 }, 1370 ObjectMeta: metav1.ObjectMeta{ 1371 Name: "replicas-test.example.com", 1372 Namespace: "default", 1373 ResourceVersion: "1", 1374 }, 1375 Data: map[string]string{ 1376 "coolkey": "default", 1377 }, 1378 } 1379 configMapParam2 := &v1.ConfigMap{ 1380 TypeMeta: metav1.TypeMeta{ 1381 APIVersion: "v1", 1382 Kind: "ConfigMap", 1383 }, 1384 ObjectMeta: metav1.ObjectMeta{ 1385 Name: "replicas-test.example.com", 1386 Namespace: "mynamespace", 1387 ResourceVersion: "1", 1388 }, 1389 Data: map[string]string{ 1390 "coolkey": "mynamespace", 1391 }, 1392 } 1393 configMapParam3 := &v1.ConfigMap{ 1394 TypeMeta: metav1.TypeMeta{ 1395 APIVersion: "v1", 1396 Kind: "ConfigMap", 1397 }, 1398 ObjectMeta: metav1.ObjectMeta{ 1399 Name: "replicas-test.example.com", 1400 Namespace: "othernamespace", 1401 ResourceVersion: "1", 1402 }, 1403 Data: map[string]string{ 1404 "coolkey": "othernamespace", 1405 }, 1406 } 1407 require.NoError(t, testContext.UpdateAndWait(&nativeTypeParamPolicy, &namespaceParamBinding, configMapParam, configMapParam2, configMapParam3)) 1408 1409 // Object is irrelevant/unchecked for this test. Just test that 1410 // the evaluator is executed with correct namespace, and returns admit 1411 // meaning the params passed was a configmap 1412 err := testContext.Plugin.Dispatch( 1413 testContext, 1414 attributeRecord(nil, configMapParam, admission.Create), 1415 &admission.RuntimeObjectInterfaces{}, 1416 ) 1417 1418 func() { 1419 lock.Lock() 1420 defer lock.Unlock() 1421 require.ErrorContains(t, err, "correct type") 1422 require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy)) 1423 require.EqualValues(t, 1, evaluations.Load()) 1424 }() 1425 1426 err = testContext.Plugin.Dispatch( 1427 testContext, 1428 attributeRecord(nil, configMapParam2, admission.Create), 1429 &admission.RuntimeObjectInterfaces{}, 1430 ) 1431 1432 func() { 1433 lock.Lock() 1434 defer lock.Unlock() 1435 require.ErrorContains(t, err, "correct type") 1436 require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy)) 1437 require.EqualValues(t, 2, evaluations.Load()) 1438 }() 1439 1440 err = testContext.Plugin.Dispatch( 1441 testContext, 1442 attributeRecord(nil, configMapParam3, admission.Create), 1443 &admission.RuntimeObjectInterfaces{}, 1444 ) 1445 1446 func() { 1447 lock.Lock() 1448 defer lock.Unlock() 1449 require.ErrorContains(t, err, "correct type") 1450 require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy)) 1451 require.EqualValues(t, 3, evaluations.Load()) 1452 }() 1453 1454 err = testContext.Plugin.Dispatch( 1455 testContext, 1456 attributeRecord(nil, configMapParam, admission.Create), 1457 &admission.RuntimeObjectInterfaces{}, 1458 ) 1459 1460 func() { 1461 lock.Lock() 1462 defer lock.Unlock() 1463 require.ErrorContains(t, err, "correct type") 1464 require.EqualValues(t, []string{"default", "mynamespace", "othernamespace", "default"}, observedParamNamespaces) 1465 require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy)) 1466 require.EqualValues(t, 4, evaluations.Load()) 1467 }() 1468 } 1469 1470 func TestParamRef(t *testing.T) { 1471 for _, paramIsClusterScoped := range []bool{false, true} { 1472 for _, nameIsSet := range []bool{false, true} { 1473 for _, namespaceIsSet := range []bool{false, true} { 1474 if paramIsClusterScoped && namespaceIsSet { 1475 // Skip invalid configuration 1476 continue 1477 } 1478 1479 for _, selectorIsSet := range []bool{false, true} { 1480 if selectorIsSet && nameIsSet { 1481 // SKip invalid configuration 1482 continue 1483 } 1484 1485 for _, denyNotFound := range []bool{false, true} { 1486 1487 name := "ParamRef" 1488 1489 if paramIsClusterScoped { 1490 name = "ClusterScoped" + name 1491 } 1492 1493 if nameIsSet { 1494 name = name + "WithName" 1495 } else if selectorIsSet { 1496 name = name + "WithLabelSelector" 1497 } else { 1498 name = name + "WithEverythingSelector" 1499 } 1500 1501 if namespaceIsSet { 1502 name = name + "WithNamespace" 1503 } 1504 1505 if denyNotFound { 1506 name = name + "DenyNotFound" 1507 } else { 1508 name = name + "AllowNotFound" 1509 } 1510 1511 t.Run(name, func(t *testing.T) { 1512 t.Parallel() 1513 // Test creating a policy with a cluster or namesapce-scoped param 1514 // and binding with the provided configuration. Test will ensure 1515 // that the provided configuration is capable of matching 1516 // params as expected, and not matching params when not expected. 1517 // Also ensures the NotFound setting works as expected with this particular 1518 // configuration of ParamRef when all the previously 1519 // matched params are deleted. 1520 testParamRefCase(t, paramIsClusterScoped, nameIsSet, namespaceIsSet, selectorIsSet, denyNotFound) 1521 }) 1522 } 1523 } 1524 } 1525 } 1526 } 1527 } 1528 1529 // testParamRefCase constructs a ParamRef and policy with appropriate ParamKind 1530 // for the given parameters, then constructs a scenario with several matching/non-matching params 1531 // of varying names, namespaces, labels. 1532 // 1533 // Test then selects subset of params that should match provided configuration 1534 // and ensuers those params are the only ones used. 1535 // 1536 // Also ensures NotFound action is enforced correctly by deleting all found 1537 // params and ensuring the Action is used. 1538 // 1539 // This test is not meant to test every possible scenario of matching/not matching: 1540 // only that each ParamRef CAN be evaluated correctly for both cluster scoped 1541 // and namespace-scoped request kinds, and that the failure action is correctly 1542 // applied. 1543 func testParamRefCase(t *testing.T, paramIsClusterScoped, nameIsSet, namespaceIsSet, selectorIsSet, denyNotFound bool) { 1544 // Create a cluster scoped and a namespace scoped CRD 1545 policy := *denyPolicy 1546 binding := *denyBinding 1547 binding.Spec.ParamRef = &admissionregistrationv1.ParamRef{} 1548 paramRef := binding.Spec.ParamRef 1549 1550 shouldErrorOnClusterScopedRequests := !namespaceIsSet && !paramIsClusterScoped 1551 1552 matchingParamName := "replicas-test.example.com" 1553 matchingNamespace := "mynamespace" 1554 nonMatchingNamespace := "othernamespace" 1555 1556 matchingLabels := labels.Set{"doesitmatch": "yes"} 1557 nonmatchingLabels := labels.Set{"doesitmatch": "no"} 1558 otherNonmatchingLabels := labels.Set{"notaffiliated": "no"} 1559 1560 if paramIsClusterScoped { 1561 policy.Spec.ParamKind = &admissionregistrationv1.ParamKind{ 1562 APIVersion: clusterScopedParamsGVK.GroupVersion().String(), 1563 Kind: clusterScopedParamsGVK.Kind, 1564 } 1565 } else { 1566 policy.Spec.ParamKind = &admissionregistrationv1.ParamKind{ 1567 APIVersion: paramsGVK.GroupVersion().String(), 1568 Kind: paramsGVK.Kind, 1569 } 1570 } 1571 1572 if nameIsSet { 1573 paramRef.Name = matchingParamName 1574 } else if selectorIsSet { 1575 paramRef.Selector = metav1.SetAsLabelSelector(matchingLabels) 1576 } else { 1577 paramRef.Selector = &metav1.LabelSelector{} 1578 } 1579 1580 if namespaceIsSet { 1581 paramRef.Namespace = matchingNamespace 1582 } 1583 1584 if denyNotFound { 1585 paramRef.ParameterNotFoundAction = ptrTo(admissionregistrationv1.DenyAction) 1586 } else { 1587 paramRef.ParameterNotFoundAction = ptrTo(admissionregistrationv1.AllowAction) 1588 } 1589 1590 compiler := &fakeCompiler{} 1591 matcher := &fakeMatcher{ 1592 DefaultMatch: true, 1593 } 1594 1595 var matchedParams []runtime.Object 1596 paramLock := sync.Mutex{} 1597 observeParam := func(p runtime.Object) { 1598 paramLock.Lock() 1599 defer paramLock.Unlock() 1600 matchedParams = append(matchedParams, p) 1601 } 1602 getAndResetObservedParams := func() []runtime.Object { 1603 paramLock.Lock() 1604 defer paramLock.Unlock() 1605 oldParams := matchedParams 1606 matchedParams = nil 1607 return oldParams 1608 } 1609 1610 compiler.RegisterDefinition(&policy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1611 observeParam(versionedParams) 1612 return validating.ValidateResult{ 1613 Decisions: []validating.PolicyDecision{ 1614 { 1615 Action: validating.ActionDeny, 1616 Message: "Denied by policy", 1617 }, 1618 }, 1619 } 1620 }) 1621 1622 testContext := setupFakeTest(t, compiler, matcher) 1623 1624 // Create library of params to try to fool the controller 1625 params := []*unstructured.Unstructured{ 1626 newParam(matchingParamName, v1.NamespaceDefault, nonmatchingLabels), 1627 newParam(matchingParamName, matchingNamespace, nonmatchingLabels), 1628 newParam(matchingParamName, nonMatchingNamespace, nonmatchingLabels), 1629 1630 newParam(matchingParamName+"1", v1.NamespaceDefault, matchingLabels), 1631 newParam(matchingParamName+"1", matchingNamespace, matchingLabels), 1632 newParam(matchingParamName+"1", nonMatchingNamespace, matchingLabels), 1633 1634 newParam(matchingParamName+"2", v1.NamespaceDefault, otherNonmatchingLabels), 1635 newParam(matchingParamName+"2", matchingNamespace, otherNonmatchingLabels), 1636 newParam(matchingParamName+"2", nonMatchingNamespace, otherNonmatchingLabels), 1637 1638 newParam(matchingParamName+"3", v1.NamespaceDefault, otherNonmatchingLabels), 1639 newParam(matchingParamName+"3", matchingNamespace, matchingLabels), 1640 newParam(matchingParamName+"3", nonMatchingNamespace, matchingLabels), 1641 1642 newClusterScopedParam(matchingParamName, matchingLabels), 1643 newClusterScopedParam(matchingParamName+"1", nonmatchingLabels), 1644 newClusterScopedParam(matchingParamName+"2", otherNonmatchingLabels), 1645 newClusterScopedParam(matchingParamName+"3", matchingLabels), 1646 newClusterScopedParam(matchingParamName+"4", nonmatchingLabels), 1647 newClusterScopedParam(matchingParamName+"5", otherNonmatchingLabels), 1648 } 1649 1650 for _, p := range params { 1651 // Don't wait for these sync the informers would not have been 1652 // created unless bound to a policy 1653 require.NoError(t, testContext.Update(p)) 1654 } 1655 1656 require.NoError(t, testContext.UpdateAndWait(&policy, &binding)) 1657 1658 namespacedRequestObject := newParam("some param", nonMatchingNamespace, nil) 1659 clusterScopedRequestObject := newClusterScopedParam("other param", nil) 1660 1661 // Validate a namespaced object, and verify that the params being validated 1662 // are the ones we would expect 1663 timeoutCtx, timeoutCancel := context.WithTimeout(testContext, 5*time.Second) 1664 defer timeoutCancel() 1665 var expectedParamsForNamespacedRequest []*unstructured.Unstructured 1666 for _, p := range params { 1667 if p.GetAPIVersion() != policy.Spec.ParamKind.APIVersion || p.GetKind() != policy.Spec.ParamKind.Kind { 1668 continue 1669 } else if len(paramRef.Name) > 0 && p.GetName() != paramRef.Name { 1670 continue 1671 } else if len(paramRef.Namespace) > 0 && p.GetNamespace() != paramRef.Namespace { 1672 continue 1673 } 1674 1675 if !paramIsClusterScoped { 1676 // If the paramRef has empty namespace and the kind is 1677 // namespaced-scoped, then it only matches params of the same 1678 // namespace 1679 if len(paramRef.Namespace) == 0 && p.GetNamespace() != namespacedRequestObject.GetNamespace() { 1680 continue 1681 } 1682 } 1683 1684 if paramRef.Selector != nil { 1685 ls := p.GetLabels() 1686 matched := true 1687 1688 for k, v := range paramRef.Selector.MatchLabels { 1689 if l, hasLabel := ls[k]; !hasLabel { 1690 matched = false 1691 break 1692 } else if l != v { 1693 matched = false 1694 break 1695 } 1696 } 1697 1698 // Empty selector matches everything 1699 if len(paramRef.Selector.MatchExpressions) == 0 && len(paramRef.Selector.MatchLabels) == 0 { 1700 matched = true 1701 } 1702 1703 if !matched { 1704 continue 1705 } 1706 } 1707 1708 expectedParamsForNamespacedRequest = append(expectedParamsForNamespacedRequest, p) 1709 require.NoError(t, testContext.WaitForReconcile(timeoutCtx, p)) 1710 } 1711 require.NotEmpty(t, expectedParamsForNamespacedRequest, "all test cases should match at least one param") 1712 require.ErrorContains(t, testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, namespacedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{}), "Denied by policy") 1713 require.ElementsMatch(t, expectedParamsForNamespacedRequest, getAndResetObservedParams(), "should exactly match expected params") 1714 1715 // Validate a cluster-scoped object, and verify that the params being validated 1716 // are the ones we would expect 1717 var expectedParamsForClusterScopedRequest []*unstructured.Unstructured 1718 timeoutCtx, timeoutCancel = context.WithTimeout(testContext, 5*time.Second) 1719 defer timeoutCancel() 1720 for _, p := range params { 1721 if shouldErrorOnClusterScopedRequests { 1722 continue 1723 } else if p.GetAPIVersion() != policy.Spec.ParamKind.APIVersion || p.GetKind() != policy.Spec.ParamKind.Kind { 1724 continue 1725 } else if len(paramRef.Name) > 0 && p.GetName() != paramRef.Name { 1726 continue 1727 } else if len(paramRef.Namespace) > 0 && p.GetNamespace() != paramRef.Namespace { 1728 continue 1729 } else if !paramIsClusterScoped && len(paramRef.Namespace) == 0 && p.GetNamespace() != v1.NamespaceDefault { 1730 continue 1731 } 1732 1733 if paramRef.Selector != nil { 1734 ls := p.GetLabels() 1735 matched := true 1736 for k, v := range paramRef.Selector.MatchLabels { 1737 if l, hasLabel := ls[k]; !hasLabel { 1738 matched = false 1739 break 1740 } else if l != v { 1741 matched = false 1742 break 1743 } 1744 } 1745 1746 // Empty selector matches everything 1747 if len(paramRef.Selector.MatchExpressions) == 0 && len(paramRef.Selector.MatchLabels) == 0 { 1748 matched = true 1749 } 1750 1751 if !matched { 1752 continue 1753 } 1754 } 1755 1756 expectedParamsForClusterScopedRequest = append(expectedParamsForClusterScopedRequest, p) 1757 require.NoError(t, testContext.WaitForReconcile(timeoutCtx, p)) 1758 1759 } 1760 1761 err := testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, clusterScopedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{}) 1762 if shouldErrorOnClusterScopedRequests { 1763 // Cannot validate cliuster-scoped resources against a paramRef that sets namespace 1764 require.ErrorContains(t, err, "failed to configure binding: cannot use namespaced paramRef in policy binding that matches cluster-scoped resources") 1765 } else { 1766 require.NotEmpty(t, expectedParamsForClusterScopedRequest, "all test cases should match at least one param") 1767 require.ErrorContains(t, err, "Denied by policy") 1768 } 1769 require.ElementsMatch(t, expectedParamsForClusterScopedRequest, getAndResetObservedParams(), "should exactly match expected params") 1770 1771 // Remove all params matched by namespaced, and cluster-scoped validation. 1772 // Validate again to make sure NotFoundAction is respected 1773 var deleted []runtime.Object 1774 for _, p := range expectedParamsForNamespacedRequest { 1775 deleted = append(deleted, p) 1776 } 1777 1778 for _, p := range expectedParamsForClusterScopedRequest { 1779 deleted = append(deleted, p) 1780 } 1781 1782 require.NoError(t, testContext.DeleteAndWait(deleted...)) 1783 1784 // Check that NotFound is working correctly for both namespaeed & non-namespaced 1785 // request object 1786 err = testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, namespacedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{}) 1787 if denyNotFound { 1788 require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction") 1789 } else { 1790 require.NoError(t, err, "Allow not found expects no error when no params found. Policy should have been skipped") 1791 } 1792 require.Empty(t, getAndResetObservedParams(), "policy should not have been evaluated") 1793 1794 err = testContext.Plugin.Dispatch(context.TODO(), attributeRecord(nil, clusterScopedRequestObject, admission.Create), &admission.RuntimeObjectInterfaces{}) 1795 if shouldErrorOnClusterScopedRequests { 1796 require.ErrorContains(t, err, "failed to configure binding: cannot use namespaced paramRef in policy binding that matches cluster-scoped resources") 1797 1798 } else if denyNotFound { 1799 require.ErrorContains(t, err, "no params found for policy binding with `Deny` parameterNotFoundAction") 1800 } else { 1801 require.NoError(t, err, "Allow not found expects no error when no params found. Policy should have been skipped") 1802 } 1803 require.Empty(t, getAndResetObservedParams(), "policy should not have been evaluated") 1804 } 1805 1806 // If the ParamKind is ClusterScoped, and namespace param is used. 1807 // This is a Configuration Error of the policy 1808 func TestNamespaceParamRefClusterScopedParamError(t *testing.T) { 1809 compiler := &fakeCompiler{} 1810 matcher := &fakeMatcher{ 1811 DefaultMatch: true, 1812 } 1813 testContext := setupFakeTest(t, compiler, matcher) 1814 1815 evaluations := atomic.Int64{} 1816 1817 // Use ValidatingAdmissionPolicy for param type since it is cluster-scoped 1818 nativeTypeParamPolicy := *denyPolicy 1819 nativeTypeParamPolicy.Spec.ParamKind = &admissionregistrationv1.ParamKind{ 1820 APIVersion: "admissionregistration.k8s.io/v1beta1", 1821 Kind: "ValidatingAdmissionPolicy", 1822 } 1823 1824 namespaceParamBinding := *denyBinding 1825 namespaceParamBinding.Spec.ParamRef = &admissionregistrationv1.ParamRef{ 1826 Name: "other-param-to-use-with-no-label.example.com", 1827 Namespace: "mynamespace", 1828 } 1829 1830 compiler.RegisterDefinition(&nativeTypeParamPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1831 evaluations.Add(1) 1832 if _, ok := versionedParams.(*admissionregistrationv1.ValidatingAdmissionPolicy); ok { 1833 return validating.ValidateResult{ 1834 Decisions: []validating.PolicyDecision{ 1835 { 1836 Action: validating.ActionAdmit, 1837 Message: "correct type", 1838 }, 1839 }, 1840 } 1841 } 1842 return validating.ValidateResult{ 1843 Decisions: []validating.PolicyDecision{ 1844 { 1845 Action: validating.ActionDeny, 1846 Message: fmt.Sprintf("Incorrect param type %T", versionedParams), 1847 }, 1848 }, 1849 } 1850 }) 1851 1852 require.NoError(t, testContext.UpdateAndWait(&nativeTypeParamPolicy, &namespaceParamBinding)) 1853 1854 // Object is irrelevant/unchecked for this test. Just test that 1855 // the evaluator is executed with correct namespace, and returns admit 1856 // meaning the params passed was a configmap 1857 err := testContext.Plugin.Dispatch( 1858 testContext, 1859 attributeRecord(nil, fakeParams, admission.Create), 1860 &admission.RuntimeObjectInterfaces{}, 1861 ) 1862 1863 require.ErrorContains(t, err, "paramRef.namespace must not be provided for a cluster-scoped `paramKind`") 1864 require.EqualValues(t, 1, compiler.getNumCompiles(&nativeTypeParamPolicy)) 1865 require.EqualValues(t, 0, evaluations.Load()) 1866 } 1867 1868 func TestAuditAnnotations(t *testing.T) { 1869 compiler := &fakeCompiler{} 1870 matcher := &fakeMatcher{ 1871 DefaultMatch: true, 1872 } 1873 testContext := setupFakeTest(t, compiler, matcher) 1874 1875 // Push some fake 1876 policy := *denyPolicy 1877 compiler.RegisterDefinition(denyPolicy, func(ctx context.Context, matchedResource schema.GroupVersionResource, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object, namespace *v1.Namespace, runtimeCELCostBudget int64, authz authorizer.Authorizer) validating.ValidateResult { 1878 o, err := meta.Accessor(versionedParams) 1879 if err != nil { 1880 t.Fatal(err) 1881 } 1882 exampleValue := "normal-value" 1883 if o.GetName() == "replicas-test2.example.com" { 1884 exampleValue = "special-value" 1885 } 1886 return validating.ValidateResult{ 1887 AuditAnnotations: []validating.PolicyAuditAnnotation{ 1888 { 1889 Key: "example-key", 1890 Value: exampleValue, 1891 Action: validating.AuditAnnotationActionPublish, 1892 }, 1893 { 1894 Key: "excluded-key", 1895 Value: "excluded-value", 1896 Action: validating.AuditAnnotationActionExclude, 1897 }, 1898 { 1899 Key: "error-key", 1900 Action: validating.AuditAnnotationActionError, 1901 Error: "example error", 1902 }, 1903 }, 1904 } 1905 }) 1906 1907 fakeParams2 := fakeParams.DeepCopy() 1908 fakeParams2.SetName("replicas-test2.example.com") 1909 denyBinding2 := denyBinding.DeepCopy() 1910 denyBinding2.SetName("denybinding2.example.com") 1911 denyBinding2.Spec.ParamRef.Name = fakeParams2.GetName() 1912 1913 fakeParams3 := fakeParams.DeepCopy() 1914 fakeParams3.SetName("replicas-test3.example.com") 1915 denyBinding3 := denyBinding.DeepCopy() 1916 denyBinding3.SetName("denybinding3.example.com") 1917 denyBinding3.Spec.ParamRef.Name = fakeParams3.GetName() 1918 1919 require.NoError(t, testContext.UpdateAndWait(fakeParams, fakeParams2, fakeParams3, &policy, denyBinding, denyBinding2, denyBinding3)) 1920 1921 attr := attributeRecord(nil, fakeParams, admission.Create) 1922 err := testContext.Plugin.Dispatch( 1923 testContext, 1924 attr, 1925 &admission.RuntimeObjectInterfaces{}, 1926 ) 1927 1928 annotations := attr.GetAnnotations(auditinternal.LevelMetadata) 1929 require.Len(t, annotations, 1) 1930 value := annotations[policy.Name+"/example-key"] 1931 parts := strings.Split(value, ", ") 1932 require.Len(t, parts, 2) 1933 require.Contains(t, parts, "normal-value", "special-value") 1934 1935 require.ErrorContains(t, err, "example error") 1936 } 1937 1938 // FakeAttributes decorates admission.Attributes. It's used to trace the added annotations. 1939 type FakeAttributes struct { 1940 admission.Attributes 1941 annotations map[string]string 1942 mutex sync.Mutex 1943 } 1944 1945 // AddAnnotation adds an annotation key value pair to FakeAttributes 1946 func (f *FakeAttributes) AddAnnotation(k, v string) error { 1947 return f.AddAnnotationWithLevel(k, v, auditinternal.LevelMetadata) 1948 } 1949 1950 // AddAnnotationWithLevel adds an annotation key value pair to FakeAttributes 1951 func (f *FakeAttributes) AddAnnotationWithLevel(k, v string, _ auditinternal.Level) error { 1952 f.mutex.Lock() 1953 defer f.mutex.Unlock() 1954 if err := f.Attributes.AddAnnotation(k, v); err != nil { 1955 return err 1956 } 1957 if f.annotations == nil { 1958 f.annotations = make(map[string]string) 1959 } 1960 f.annotations[k] = v 1961 return nil 1962 } 1963 1964 // GetAnnotations reads annotations from FakeAttributes 1965 func (f *FakeAttributes) GetAnnotations(_ auditinternal.Level) map[string]string { 1966 f.mutex.Lock() 1967 defer f.mutex.Unlock() 1968 annotations := make(map[string]string, len(f.annotations)) 1969 for k, v := range f.annotations { 1970 annotations[k] = v 1971 } 1972 return annotations 1973 } 1974 1975 type warningRecorder struct { 1976 sync.Mutex 1977 warnings sets.Set[string] 1978 } 1979 1980 func newWarningRecorder() *warningRecorder { 1981 return &warningRecorder{warnings: sets.New[string]()} 1982 } 1983 1984 func (r *warningRecorder) AddWarning(_, text string) { 1985 r.Lock() 1986 defer r.Unlock() 1987 r.warnings.Insert(text) 1988 return 1989 } 1990 1991 func (r *warningRecorder) hasWarning(text string) bool { 1992 r.Lock() 1993 defer r.Unlock() 1994 return r.warnings.Has(text) 1995 } 1996 1997 func (r *warningRecorder) len() int { 1998 r.Lock() 1999 defer r.Unlock() 2000 return len(r.warnings) 2001 }