knative.dev/pkg@v0.0.0-20260602142205-ac97e43f6622/webhook/resourcesemantics/validation/reconcile_config_test.go (about) 1 /* 2 Copyright 2019 The Knative 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 validation 18 19 import ( 20 "context" 21 "testing" 22 "time" 23 24 kubeclient "knative.dev/pkg/client/injection/kube/client/fake" 25 _ "knative.dev/pkg/injection/clients/namespacedkube/informers/core/v1/secret/fake" 26 pkgreconciler "knative.dev/pkg/reconciler" 27 28 admissionregistrationv1 "k8s.io/api/admissionregistration/v1" 29 corev1 "k8s.io/api/core/v1" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 "k8s.io/apimachinery/pkg/runtime" 32 "k8s.io/apimachinery/pkg/runtime/schema" 33 "k8s.io/apimachinery/pkg/types" 34 "k8s.io/apimachinery/pkg/util/wait" 35 clientgotesting "k8s.io/client-go/testing" 36 37 "knative.dev/pkg/configmap" 38 "knative.dev/pkg/controller" 39 "knative.dev/pkg/ptr" 40 "knative.dev/pkg/system" 41 "knative.dev/pkg/webhook" 42 certresources "knative.dev/pkg/webhook/certificates/resources" 43 "knative.dev/pkg/webhook/resourcesemantics" 44 45 . "knative.dev/pkg/reconciler/testing" 46 . "knative.dev/pkg/webhook/testing" 47 ) 48 49 func TestReconcile(t *testing.T) { 50 const name, path = "foo.bar.baz", "/blah" 51 const secretName = "webhook-secret" 52 53 secret := &corev1.Secret{ 54 ObjectMeta: metav1.ObjectMeta{ 55 Name: secretName, 56 Namespace: system.Namespace(), 57 }, 58 Data: map[string][]byte{ 59 certresources.ServerKey: []byte("present"), 60 certresources.ServerCert: []byte("present"), 61 certresources.CACert: []byte("present"), 62 }, 63 } 64 65 ns := &corev1.Namespace{ 66 ObjectMeta: metav1.ObjectMeta{ 67 Name: system.Namespace(), 68 }, 69 } 70 nsRef := *metav1.NewControllerRef(ns, corev1.SchemeGroupVersion.WithKind("Namespace")) 71 nsRef.Controller = ptr.Bool(false) 72 expectedOwnerReferences := []metav1.OwnerReference{nsRef} 73 74 // This is the namespace selector setup 75 namespaceSelector := &metav1.LabelSelector{ 76 MatchExpressions: []metav1.LabelSelectorRequirement{{ 77 Key: "webhooks.knative.dev/exclude", 78 Operator: metav1.LabelSelectorOpDoesNotExist, 79 }}, 80 } 81 82 // These are the rules we expect given the context of "handlers". 83 expectedRules := []admissionregistrationv1.RuleWithOperations{{ 84 Operations: []admissionregistrationv1.OperationType{"CREATE", "DELETE", "UPDATE"}, 85 Rule: admissionregistrationv1.Rule{ 86 APIGroups: []string{"pkg.knative.dev"}, 87 APIVersions: []string{"v1alpha1"}, 88 Resources: []string{"callbackresources", "callbackresources/status"}, 89 }, 90 }, { 91 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE", "DELETE"}, 92 Rule: admissionregistrationv1.Rule{ 93 APIGroups: []string{"pkg.knative.dev"}, 94 APIVersions: []string{"v1alpha1"}, 95 Resources: []string{"innerdefaultresources", "innerdefaultresources/status"}, 96 }, 97 }, { 98 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE", "DELETE"}, 99 Rule: admissionregistrationv1.Rule{ 100 APIGroups: []string{"pkg.knative.dev"}, 101 APIVersions: []string{"v1alpha1"}, 102 Resources: []string{"resources", "resources/status"}, 103 }, 104 }, { 105 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE", "DELETE"}, 106 Rule: admissionregistrationv1.Rule{ 107 APIGroups: []string{"pkg.knative.dev"}, 108 APIVersions: []string{"v1beta1"}, 109 Resources: []string{"resources", "resources/status"}, 110 }, 111 }, { 112 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE", "DELETE"}, 113 Rule: admissionregistrationv1.Rule{ 114 APIGroups: []string{"pkg.knative.io"}, 115 APIVersions: []string{"v1alpha1"}, 116 Resources: []string{"innerdefaultresources", "innerdefaultresources/status"}, 117 }, 118 }} 119 120 // The key to use, which for this singleton reconciler doesn't matter (although the 121 // namespace matters for namespace validation). 122 key := system.Namespace() + "/does not matter" 123 124 table := TableTest{{ 125 Name: "no secret", 126 Key: key, 127 WantErr: true, 128 }, { 129 Name: "secret missing CA Cert", 130 Key: key, 131 Objects: []runtime.Object{&corev1.Secret{ 132 ObjectMeta: metav1.ObjectMeta{ 133 Name: secretName, 134 Namespace: system.Namespace(), 135 }, 136 Data: map[string][]byte{ 137 certresources.ServerKey: []byte("present"), 138 certresources.ServerCert: []byte("present"), 139 // certresources.CACert: []byte("missing"), 140 }, 141 }}, 142 WantErr: true, 143 }, { 144 Name: "secret exists, but VWH does not", 145 Key: key, 146 Objects: []runtime.Object{secret}, 147 WantErr: true, 148 }, { 149 Name: "secret and VWH exist, missing service reference", 150 Key: key, 151 Objects: []runtime.Object{ 152 secret, ns, 153 &admissionregistrationv1.ValidatingWebhookConfiguration{ 154 ObjectMeta: metav1.ObjectMeta{ 155 Name: name, 156 }, 157 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 158 Name: name, 159 }}, 160 }, 161 }, 162 WantErr: true, 163 }, { 164 Name: "secret and VWH exist, missing other stuff", 165 Key: key, 166 Objects: []runtime.Object{ 167 secret, ns, 168 &admissionregistrationv1.ValidatingWebhookConfiguration{ 169 ObjectMeta: metav1.ObjectMeta{ 170 Name: name, 171 }, 172 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 173 Name: name, 174 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 175 Service: &admissionregistrationv1.ServiceReference{ 176 Namespace: system.Namespace(), 177 Name: "webhook", 178 }, 179 }, 180 }}, 181 }, 182 }, 183 WantUpdates: []clientgotesting.UpdateActionImpl{{ 184 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 185 ObjectMeta: metav1.ObjectMeta{ 186 Name: name, 187 OwnerReferences: expectedOwnerReferences, 188 }, 189 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 190 Name: name, 191 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 192 Service: &admissionregistrationv1.ServiceReference{ 193 Namespace: system.Namespace(), 194 Name: "webhook", 195 // Path is added. 196 Path: ptr.String(path), 197 }, 198 // CABundle is added. 199 CABundle: []byte("present"), 200 }, 201 // Rules are added. 202 Rules: expectedRules, 203 NamespaceSelector: namespaceSelector, 204 }}, 205 }, 206 }}, 207 }, { 208 Name: "secret and VWH exist, added fields are incorrect", 209 Key: key, 210 Objects: []runtime.Object{ 211 secret, ns, 212 &admissionregistrationv1.ValidatingWebhookConfiguration{ 213 ObjectMeta: metav1.ObjectMeta{ 214 Name: name, 215 }, 216 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 217 Name: name, 218 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 219 Service: &admissionregistrationv1.ServiceReference{ 220 Namespace: system.Namespace(), 221 Name: "webhook", 222 // Incorrect 223 Path: ptr.String("incorrect"), 224 }, 225 // Incorrect 226 CABundle: []byte("incorrect"), 227 }, 228 // Incorrect (really just incomplete) 229 Rules: []admissionregistrationv1.RuleWithOperations{{ 230 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 231 Rule: admissionregistrationv1.Rule{ 232 APIGroups: []string{"pkg.knative.dev"}, 233 APIVersions: []string{"v1alpha1"}, 234 Resources: []string{"innerdefaultresources/*"}, 235 }, 236 }}, 237 }}, 238 }, 239 }, 240 WantUpdates: []clientgotesting.UpdateActionImpl{{ 241 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 242 ObjectMeta: metav1.ObjectMeta{ 243 Name: name, 244 OwnerReferences: expectedOwnerReferences, 245 }, 246 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 247 Name: name, 248 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 249 Service: &admissionregistrationv1.ServiceReference{ 250 Namespace: system.Namespace(), 251 Name: "webhook", 252 // Path is fixed. 253 Path: ptr.String(path), 254 }, 255 // CABundle is fixed. 256 CABundle: []byte("present"), 257 }, 258 // Rules are fixed. 259 Rules: expectedRules, 260 NamespaceSelector: namespaceSelector, 261 }}, 262 }, 263 }}, 264 }, { 265 Name: "failure updating VWH", 266 Key: key, 267 WantErr: true, 268 WithReactors: []clientgotesting.ReactionFunc{ 269 InduceFailure("update", "validatingwebhookconfigurations"), 270 }, 271 Objects: []runtime.Object{ 272 secret, ns, 273 &admissionregistrationv1.ValidatingWebhookConfiguration{ 274 ObjectMeta: metav1.ObjectMeta{ 275 Name: name, 276 }, 277 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 278 Name: name, 279 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 280 Service: &admissionregistrationv1.ServiceReference{ 281 Namespace: system.Namespace(), 282 Name: "webhook", 283 // Incorrect 284 Path: ptr.String("incorrect"), 285 }, 286 // Incorrect 287 CABundle: []byte("incorrect"), 288 }, 289 // Incorrect (really just incomplete) 290 Rules: []admissionregistrationv1.RuleWithOperations{{ 291 Operations: []admissionregistrationv1.OperationType{"CREATE", "UPDATE"}, 292 Rule: admissionregistrationv1.Rule{ 293 APIGroups: []string{"pkg.knative.dev"}, 294 APIVersions: []string{"v1alpha1"}, 295 Resources: []string{"innerdefaultresources/*"}, 296 }, 297 }}, 298 }}, 299 }, 300 }, 301 WantUpdates: []clientgotesting.UpdateActionImpl{{ 302 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 303 ObjectMeta: metav1.ObjectMeta{ 304 Name: name, 305 OwnerReferences: expectedOwnerReferences, 306 }, 307 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 308 Name: name, 309 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 310 Service: &admissionregistrationv1.ServiceReference{ 311 Namespace: system.Namespace(), 312 Name: "webhook", 313 // Path is fixed. 314 Path: ptr.String(path), 315 }, 316 // CABundle is fixed. 317 CABundle: []byte("present"), 318 }, 319 // Rules are fixed. 320 Rules: expectedRules, 321 NamespaceSelector: namespaceSelector, 322 }}, 323 }, 324 }}, 325 }, { 326 Name: ":fire: everything is fine :fire:", 327 Key: key, 328 Objects: []runtime.Object{ 329 secret, ns, 330 &admissionregistrationv1.ValidatingWebhookConfiguration{ 331 ObjectMeta: metav1.ObjectMeta{ 332 Name: name, 333 OwnerReferences: expectedOwnerReferences, 334 }, 335 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 336 Name: name, 337 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 338 Service: &admissionregistrationv1.ServiceReference{ 339 Namespace: system.Namespace(), 340 Name: "webhook", 341 // Path is fine. 342 Path: ptr.String(path), 343 }, 344 // CABundle is fine. 345 CABundle: []byte("present"), 346 }, 347 // Rules are fine. 348 Rules: expectedRules, 349 // A non-knative key in the namespace selector is fine. 350 NamespaceSelector: &metav1.LabelSelector{ 351 MatchExpressions: []metav1.LabelSelectorRequirement{{ 352 Key: "webhooks.knative.dev/exclude", 353 Operator: metav1.LabelSelectorOpDoesNotExist, 354 }, { 355 Key: "foo.bar/baz", 356 Operator: metav1.LabelSelectorOpDoesNotExist, 357 }}, 358 }, 359 }}, 360 }, 361 }, 362 }, { 363 Name: "secret and VWH exist, correcting namespaceSelector", 364 Key: key, 365 Objects: []runtime.Object{ 366 secret, ns, 367 &admissionregistrationv1.ValidatingWebhookConfiguration{ 368 ObjectMeta: metav1.ObjectMeta{ 369 Name: name, 370 }, 371 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 372 Name: name, 373 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 374 Service: &admissionregistrationv1.ServiceReference{ 375 Namespace: system.Namespace(), 376 Name: "webhook", 377 // Path is fine. 378 Path: ptr.String(path), 379 }, 380 // CABundle is fine. 381 CABundle: []byte("present"), 382 }, 383 // Rules are fine. 384 Rules: expectedRules, 385 // NamespaceSelector contains non-knative things. 386 NamespaceSelector: &metav1.LabelSelector{ 387 MatchExpressions: []metav1.LabelSelectorRequirement{{ 388 Key: "foo.knative.dev/exclude", 389 Operator: metav1.LabelSelectorOpDoesNotExist, 390 }, { 391 Key: "foo.bar/baz", 392 Operator: metav1.LabelSelectorOpDoesNotExist, 393 }}, 394 }, 395 }}, 396 }, 397 }, 398 WantUpdates: []clientgotesting.UpdateActionImpl{{ 399 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 400 ObjectMeta: metav1.ObjectMeta{ 401 Name: name, 402 OwnerReferences: expectedOwnerReferences, 403 }, 404 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 405 Name: name, 406 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 407 Service: &admissionregistrationv1.ServiceReference{ 408 Namespace: system.Namespace(), 409 Name: "webhook", 410 Path: ptr.String(path), 411 }, 412 CABundle: []byte("present"), 413 }, 414 Rules: expectedRules, 415 NamespaceSelector: &metav1.LabelSelector{ 416 // The knative key is added while the non-knative key is kept. 417 // Old knative key is removed. 418 MatchExpressions: []metav1.LabelSelectorRequirement{{ 419 Key: "webhooks.knative.dev/exclude", 420 Operator: metav1.LabelSelectorOpDoesNotExist, 421 }, { 422 Key: "foo.bar/baz", 423 Operator: metav1.LabelSelectorOpDoesNotExist, 424 }}, 425 }, 426 }}, 427 }, 428 }}, 429 }, { 430 Name: "secret and VWH exist, correcting namespaceSelector, preserving matchLabels", 431 Key: key, 432 Objects: []runtime.Object{ 433 secret, ns, 434 &admissionregistrationv1.ValidatingWebhookConfiguration{ 435 ObjectMeta: metav1.ObjectMeta{ 436 Name: name, 437 }, 438 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 439 Name: name, 440 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 441 Service: &admissionregistrationv1.ServiceReference{ 442 Namespace: system.Namespace(), 443 Name: "webhook", 444 // Path is fine. 445 Path: ptr.String(path), 446 }, 447 // CABundle is fine. 448 CABundle: []byte("present"), 449 }, 450 // Rules are fine. 451 Rules: expectedRules, 452 // NamespaceSelector contains non-knative things. 453 NamespaceSelector: &metav1.LabelSelector{ 454 MatchLabels: map[string]string{ 455 "app.kubernetes.io/name": "knative-eventing", 456 }, 457 MatchExpressions: []metav1.LabelSelectorRequirement{{ 458 Key: "foo.knative.dev/exclude", 459 Operator: metav1.LabelSelectorOpDoesNotExist, 460 }, { 461 Key: "foo.bar/baz", 462 Operator: metav1.LabelSelectorOpDoesNotExist, 463 }}, 464 }, 465 }}, 466 }, 467 }, 468 WantUpdates: []clientgotesting.UpdateActionImpl{{ 469 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 470 ObjectMeta: metav1.ObjectMeta{ 471 Name: name, 472 OwnerReferences: expectedOwnerReferences, 473 }, 474 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 475 Name: name, 476 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 477 Service: &admissionregistrationv1.ServiceReference{ 478 Namespace: system.Namespace(), 479 Name: "webhook", 480 Path: ptr.String(path), 481 }, 482 CABundle: []byte("present"), 483 }, 484 Rules: expectedRules, 485 NamespaceSelector: &metav1.LabelSelector{ 486 MatchLabels: map[string]string{ 487 "app.kubernetes.io/name": "knative-eventing", 488 }, 489 // The knative key is added while the non-knative key is kept. 490 // Old knative key is removed. 491 MatchExpressions: []metav1.LabelSelectorRequirement{{ 492 Key: "webhooks.knative.dev/exclude", 493 Operator: metav1.LabelSelectorOpDoesNotExist, 494 }, { 495 Key: "foo.bar/baz", 496 Operator: metav1.LabelSelectorOpDoesNotExist, 497 }}, 498 }, 499 }}, 500 }, 501 }}, 502 }, { 503 Name: "secret and VWH exist, correcting namespaceSelector without existing matchExpressions, preserving matchLabels", 504 Key: key, 505 Objects: []runtime.Object{ 506 secret, ns, 507 &admissionregistrationv1.ValidatingWebhookConfiguration{ 508 ObjectMeta: metav1.ObjectMeta{ 509 Name: name, 510 }, 511 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 512 Name: name, 513 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 514 Service: &admissionregistrationv1.ServiceReference{ 515 Namespace: system.Namespace(), 516 Name: "webhook", 517 // Path is fine. 518 Path: ptr.String(path), 519 }, 520 // CABundle is fine. 521 CABundle: []byte("present"), 522 }, 523 // Rules are fine. 524 Rules: expectedRules, 525 // NamespaceSelector contains non-knative things. 526 NamespaceSelector: &metav1.LabelSelector{ 527 MatchLabels: map[string]string{ 528 "app.kubernetes.io/name": "knative-eventing", 529 }, 530 }, 531 }}, 532 }, 533 }, 534 WantUpdates: []clientgotesting.UpdateActionImpl{{ 535 Object: &admissionregistrationv1.ValidatingWebhookConfiguration{ 536 ObjectMeta: metav1.ObjectMeta{ 537 Name: name, 538 OwnerReferences: expectedOwnerReferences, 539 }, 540 Webhooks: []admissionregistrationv1.ValidatingWebhook{{ 541 Name: name, 542 ClientConfig: admissionregistrationv1.WebhookClientConfig{ 543 Service: &admissionregistrationv1.ServiceReference{ 544 Namespace: system.Namespace(), 545 Name: "webhook", 546 Path: ptr.String(path), 547 }, 548 CABundle: []byte("present"), 549 }, 550 Rules: expectedRules, 551 NamespaceSelector: &metav1.LabelSelector{ 552 MatchLabels: map[string]string{ 553 "app.kubernetes.io/name": "knative-eventing", 554 }, 555 // The knative key is added 556 MatchExpressions: []metav1.LabelSelectorRequirement{{ 557 Key: "webhooks.knative.dev/exclude", 558 Operator: metav1.LabelSelectorOpDoesNotExist, 559 }}, 560 }, 561 }}, 562 }, 563 }}, 564 }} 565 566 table.Test(t, MakeFactory(func(ctx context.Context, listers *Listers, cmw configmap.Watcher) controller.Reconciler { 567 return &reconciler{ 568 key: types.NamespacedName{ 569 Name: name, 570 }, 571 path: path, 572 573 handlers: handlers, 574 callbacks: callbacks, 575 576 client: kubeclient.Get(ctx), 577 vwhlister: listers.GetValidatingWebhookConfigurationLister(), 578 secretlister: listers.GetSecretLister(), 579 580 secretName: secretName, 581 } 582 })) 583 } 584 585 func TestNew(t *testing.T) { 586 ctx, cancel, _ := SetupFakeContextWithCancel(t) 587 defer cancel() 588 ctx = webhook.WithOptions(ctx, webhook.Options{}) 589 590 c := NewAdmissionController(ctx, "foo", "/bar", 591 map[schema.GroupVersionKind]resourcesemantics.GenericCRD{}, 592 func(ctx context.Context) context.Context { 593 return ctx 594 }, true /* disallow unknown field */) 595 if c == nil { 596 t.Fatal("Expected NewController to return a non-nil value") 597 } 598 599 if want, got := 0, c.WorkQueue().Len(); want != got { 600 t.Errorf("WorkQueue.Len() = %d, wanted %d", got, want) 601 } 602 603 la, ok := c.Reconciler.(pkgreconciler.LeaderAware) 604 if !ok { 605 t.Fatalf("%T is not leader aware", c.Reconciler) 606 } 607 608 if err := la.Promote(pkgreconciler.UniversalBucket(), c.MaybeEnqueueBucketKey); err != nil { 609 t.Error("Promote() =", err) 610 } 611 612 // Queue has async moving parts so if we check at the wrong moment, this might still be 0. 613 if wait.PollUntilContextTimeout(ctx, 10*time.Millisecond, 250*time.Millisecond, true, func(ctx context.Context) (bool, error) { 614 return c.WorkQueue().Len() == 1, nil 615 }) != nil { 616 t.Error("Queue length was never 1") 617 } 618 }