k8s.io/apiserver@v0.31.1/pkg/admission/plugin/webhook/testing/testcase.go (about) 1 /* 2 Copyright 2018 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 testing 18 19 import ( 20 "fmt" 21 "net/http" 22 "net/url" 23 "reflect" 24 "strings" 25 "sync" 26 "time" 27 28 registrationv1 "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/apis/meta/v1/unstructured" 32 "k8s.io/apimachinery/pkg/runtime" 33 "k8s.io/apimachinery/pkg/runtime/schema" 34 "k8s.io/apiserver/pkg/admission" 35 "k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts" 36 auditinternal "k8s.io/apiserver/pkg/apis/audit" 37 "k8s.io/apiserver/pkg/authentication/user" 38 "k8s.io/client-go/informers" 39 "k8s.io/client-go/kubernetes" 40 fakeclientset "k8s.io/client-go/kubernetes/fake" 41 ) 42 43 var matchEverythingRules = []registrationv1.RuleWithOperations{{ 44 Operations: []registrationv1.OperationType{registrationv1.OperationAll}, 45 Rule: registrationv1.Rule{ 46 APIGroups: []string{"*"}, 47 APIVersions: []string{"*"}, 48 Resources: []string{"*/*"}, 49 }, 50 }} 51 52 var sideEffectsUnknown = registrationv1.SideEffectClassUnknown 53 var sideEffectsNone = registrationv1.SideEffectClassNone 54 var sideEffectsSome = registrationv1.SideEffectClassSome 55 var sideEffectsNoneOnDryRun = registrationv1.SideEffectClassNoneOnDryRun 56 57 var reinvokeNever = registrationv1.NeverReinvocationPolicy 58 var reinvokeIfNeeded = registrationv1.IfNeededReinvocationPolicy 59 60 // NewFakeValidatingDataSource returns a mock client and informer returning the given webhooks. 61 func NewFakeValidatingDataSource(name string, webhooks []registrationv1.ValidatingWebhook, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) { 62 var objs = []runtime.Object{ 63 &corev1.Namespace{ 64 ObjectMeta: metav1.ObjectMeta{ 65 Name: name, 66 Labels: map[string]string{ 67 "runlevel": "0", 68 }, 69 }, 70 }, 71 } 72 objs = append(objs, ®istrationv1.ValidatingWebhookConfiguration{ 73 ObjectMeta: metav1.ObjectMeta{ 74 Name: "test-webhooks", 75 }, 76 Webhooks: webhooks, 77 }) 78 79 client := fakeclientset.NewSimpleClientset(objs...) 80 informerFactory := informers.NewSharedInformerFactory(client, 0) 81 82 return client, informerFactory 83 } 84 85 // NewFakeMutatingDataSource returns a mock client and informer returning the given webhooks. 86 func NewFakeMutatingDataSource(name string, webhooks []registrationv1.MutatingWebhook, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) { 87 var objs = []runtime.Object{ 88 &corev1.Namespace{ 89 ObjectMeta: metav1.ObjectMeta{ 90 Name: name, 91 Labels: map[string]string{ 92 "runlevel": "0", 93 }, 94 }, 95 }, 96 } 97 objs = append(objs, ®istrationv1.MutatingWebhookConfiguration{ 98 ObjectMeta: metav1.ObjectMeta{ 99 Name: "test-webhooks", 100 }, 101 Webhooks: webhooks, 102 }) 103 104 client := fakeclientset.NewSimpleClientset(objs...) 105 informerFactory := informers.NewSharedInformerFactory(client, 0) 106 107 return client, informerFactory 108 } 109 110 func newAttributesRecord(object metav1.Object, oldObject metav1.Object, kind schema.GroupVersionKind, namespace string, name string, resource string, labels map[string]string, dryRun bool) admission.Attributes { 111 object.SetName(name) 112 object.SetNamespace(namespace) 113 objectLabels := map[string]string{resource + ".name": name} 114 for k, v := range labels { 115 objectLabels[k] = v 116 } 117 object.SetLabels(objectLabels) 118 119 oldObject.SetName(name) 120 oldObject.SetNamespace(namespace) 121 122 gvr := kind.GroupVersion().WithResource(resource) 123 subResource := "" 124 userInfo := user.DefaultInfo{ 125 Name: "webhook-test", 126 UID: "webhook-test", 127 } 128 options := &metav1.UpdateOptions{} 129 130 return &FakeAttributes{ 131 Attributes: admission.NewAttributesRecord(object.(runtime.Object), oldObject.(runtime.Object), kind, namespace, name, gvr, subResource, admission.Update, options, dryRun, &userInfo), 132 } 133 } 134 135 // FakeAttributes decorate admission.Attributes. It's used to trace the added annotations. 136 type FakeAttributes struct { 137 admission.Attributes 138 annotations map[string]string 139 mutex sync.Mutex 140 } 141 142 // AddAnnotation adds an annotation key value pair to FakeAttributes 143 func (f *FakeAttributes) AddAnnotation(k, v string) error { 144 return f.AddAnnotationWithLevel(k, v, auditinternal.LevelMetadata) 145 } 146 147 // AddAnnotationWithLevel adds an annotation key value pair to FakeAttributes 148 func (f *FakeAttributes) AddAnnotationWithLevel(k, v string, _ auditinternal.Level) error { 149 f.mutex.Lock() 150 defer f.mutex.Unlock() 151 if err := f.Attributes.AddAnnotation(k, v); err != nil { 152 return err 153 } 154 if f.annotations == nil { 155 f.annotations = make(map[string]string) 156 } 157 f.annotations[k] = v 158 return nil 159 } 160 161 // GetAnnotations reads annotations from FakeAttributes 162 func (f *FakeAttributes) GetAnnotations(level auditinternal.Level) map[string]string { 163 f.mutex.Lock() 164 defer f.mutex.Unlock() 165 return f.annotations 166 } 167 168 // NewAttribute returns static admission Attributes for testing. 169 func NewAttribute(namespace string, labels map[string]string, dryRun bool) admission.Attributes { 170 // Set up a test object for the call 171 object := corev1.Pod{ 172 TypeMeta: metav1.TypeMeta{ 173 APIVersion: "v1", 174 Kind: "Pod", 175 }, 176 } 177 oldObject := corev1.Pod{} 178 kind := corev1.SchemeGroupVersion.WithKind("Pod") 179 name := "my-pod" 180 181 return newAttributesRecord(&object, &oldObject, kind, namespace, name, "pod", labels, dryRun) 182 } 183 184 // NewAttributeUnstructured returns static admission Attributes for testing with custom resources. 185 func NewAttributeUnstructured(namespace string, labels map[string]string, dryRun bool) admission.Attributes { 186 // Set up a test object for the call 187 object := unstructured.Unstructured{} 188 object.SetKind("TestCRD") 189 object.SetAPIVersion("custom.resource/v1") 190 oldObject := unstructured.Unstructured{} 191 oldObject.SetKind("TestCRD") 192 oldObject.SetAPIVersion("custom.resource/v1") 193 kind := object.GroupVersionKind() 194 name := "my-test-crd" 195 196 return newAttributesRecord(&object, &oldObject, kind, namespace, name, "crd", labels, dryRun) 197 } 198 199 type urlConfigGenerator struct { 200 baseURL *url.URL 201 } 202 203 func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1.WebhookClientConfig { 204 u2 := *c.baseURL 205 u2.Path = urlPath 206 urlString := u2.String() 207 return registrationv1.WebhookClientConfig{ 208 URL: &urlString, 209 CABundle: testcerts.CACert, 210 } 211 } 212 213 // ValidatingTest is a validating webhook test case. 214 type ValidatingTest struct { 215 Name string 216 Webhooks []registrationv1.ValidatingWebhook 217 Path string 218 IsCRD bool 219 IsDryRun bool 220 AdditionalLabels map[string]string 221 SkipBenchmark bool 222 ExpectLabels map[string]string 223 ExpectAllow bool 224 ErrorContains string 225 ExpectAnnotations map[string]string 226 ExpectStatusCode int32 227 ExpectReinvokeWebhooks map[string]bool 228 } 229 230 // MutatingTest is a mutating webhook test case. 231 type MutatingTest struct { 232 Name string 233 Webhooks []registrationv1.MutatingWebhook 234 Path string 235 IsCRD bool 236 IsDryRun bool 237 AdditionalLabels map[string]string 238 SkipBenchmark bool 239 ExpectLabels map[string]string 240 ExpectAllow bool 241 ErrorContains string 242 ExpectAnnotations map[string]string 243 ExpectStatusCode int32 244 ExpectReinvokeWebhooks map[string]bool 245 } 246 247 // DurationTest is webhook duration test case, used both in mutating and 248 // validating plugin test cases. 249 type DurationTest struct { 250 Name string 251 Webhooks []registrationv1.ValidatingWebhook 252 InitContext bool 253 IsDryRun bool 254 ExpectedDurationSum time.Duration 255 ExpectedDurationMax time.Duration 256 } 257 258 // ConvertToMutatingTestCases converts a validating test case to a mutating one for test purposes. 259 func ConvertToMutatingTestCases(tests []ValidatingTest, configurationName string) []MutatingTest { 260 r := make([]MutatingTest, len(tests)) 261 for i, t := range tests { 262 for idx, hook := range t.Webhooks { 263 if t.ExpectAnnotations == nil { 264 t.ExpectAnnotations = map[string]string{} 265 } 266 // Add expected annotation if the converted webhook is intended to match 267 if reflect.DeepEqual(hook.NamespaceSelector, &metav1.LabelSelector{}) && 268 reflect.DeepEqual(hook.ObjectSelector, &metav1.LabelSelector{}) && 269 reflect.DeepEqual(hook.Rules, matchEverythingRules) { 270 key := fmt.Sprintf("mutation.webhook.admission.k8s.io/round_0_index_%d", idx) 271 value := mutationAnnotationValue(configurationName, hook.Name, false) 272 t.ExpectAnnotations[key] = value 273 } 274 // Break if the converted webhook is intended to fail close 275 if strings.Contains(hook.Name, "internalErr") && (hook.FailurePolicy == nil || *hook.FailurePolicy == registrationv1.Fail) { 276 break 277 } 278 } 279 // Change annotation keys for Validating's fail open to Mutating's fail open. 280 failOpenAnnotations := map[string]string{} 281 for key, value := range t.ExpectAnnotations { 282 if strings.HasPrefix(key, "failed-open.validating.webhook.admission.k8s.io/") { 283 failOpenAnnotations[key] = value 284 } 285 } 286 for key, value := range failOpenAnnotations { 287 newKey := strings.Replace(key, "failed-open.validating.webhook.admission.k8s.io/", "failed-open.mutation.webhook.admission.k8s.io/", 1) 288 t.ExpectAnnotations[newKey] = value 289 delete(t.ExpectAnnotations, key) 290 } 291 r[i] = MutatingTest{t.Name, ConvertToMutatingWebhooks(t.Webhooks), t.Path, t.IsCRD, t.IsDryRun, t.AdditionalLabels, t.SkipBenchmark, t.ExpectLabels, t.ExpectAllow, t.ErrorContains, t.ExpectAnnotations, t.ExpectStatusCode, t.ExpectReinvokeWebhooks} 292 } 293 return r 294 } 295 296 // ConvertToMutatingWebhooks converts a validating webhook to a mutating one for test purposes. 297 func ConvertToMutatingWebhooks(webhooks []registrationv1.ValidatingWebhook) []registrationv1.MutatingWebhook { 298 mutating := make([]registrationv1.MutatingWebhook, len(webhooks)) 299 for i, h := range webhooks { 300 mutating[i] = registrationv1.MutatingWebhook{ 301 Name: h.Name, 302 ClientConfig: h.ClientConfig, 303 Rules: h.Rules, 304 FailurePolicy: h.FailurePolicy, 305 MatchPolicy: h.MatchPolicy, 306 NamespaceSelector: h.NamespaceSelector, 307 ObjectSelector: h.ObjectSelector, 308 SideEffects: h.SideEffects, 309 TimeoutSeconds: h.TimeoutSeconds, 310 AdmissionReviewVersions: h.AdmissionReviewVersions, 311 } 312 } 313 return mutating 314 } 315 316 // NewNonMutatingTestCases returns test cases with a given base url. 317 // All test cases in NewNonMutatingTestCases have no Patch set in 318 // AdmissionResponse. The test cases are used by both MutatingAdmissionWebhook 319 // and ValidatingAdmissionWebhook. 320 func NewNonMutatingTestCases(url *url.URL) []ValidatingTest { 321 policyFail := registrationv1.Fail 322 policyIgnore := registrationv1.Ignore 323 ccfgURL := urlConfigGenerator{url}.ccfgURL 324 325 return []ValidatingTest{ 326 { 327 Name: "no match", 328 Webhooks: []registrationv1.ValidatingWebhook{{ 329 Name: "nomatch", 330 ClientConfig: ccfgSVC("disallow"), 331 Rules: []registrationv1.RuleWithOperations{{ 332 Operations: []registrationv1.OperationType{registrationv1.Create}, 333 }}, 334 NamespaceSelector: &metav1.LabelSelector{}, 335 ObjectSelector: &metav1.LabelSelector{}, 336 AdmissionReviewVersions: []string{"v1beta1"}, 337 }}, 338 ExpectAllow: true, 339 }, 340 { 341 Name: "match & allow", 342 Webhooks: []registrationv1.ValidatingWebhook{{ 343 Name: "allow.example.com", 344 ClientConfig: ccfgSVC("allow"), 345 Rules: matchEverythingRules, 346 NamespaceSelector: &metav1.LabelSelector{}, 347 ObjectSelector: &metav1.LabelSelector{}, 348 AdmissionReviewVersions: []string{"v1beta1"}, 349 }}, 350 ExpectAllow: true, 351 ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, 352 }, 353 { 354 Name: "match & disallow", 355 Webhooks: []registrationv1.ValidatingWebhook{{ 356 Name: "disallow", 357 ClientConfig: ccfgSVC("disallow"), 358 Rules: matchEverythingRules, 359 NamespaceSelector: &metav1.LabelSelector{}, 360 ObjectSelector: &metav1.LabelSelector{}, 361 AdmissionReviewVersions: []string{"v1beta1"}, 362 }}, 363 ExpectStatusCode: http.StatusForbidden, 364 ErrorContains: "without explanation", 365 }, 366 { 367 Name: "match & disallow ii", 368 Webhooks: []registrationv1.ValidatingWebhook{{ 369 Name: "disallowReason", 370 ClientConfig: ccfgSVC("disallowReason"), 371 Rules: matchEverythingRules, 372 NamespaceSelector: &metav1.LabelSelector{}, 373 ObjectSelector: &metav1.LabelSelector{}, 374 AdmissionReviewVersions: []string{"v1beta1"}, 375 }}, 376 ExpectStatusCode: http.StatusForbidden, 377 ErrorContains: "you shall not pass", 378 }, 379 { 380 Name: "match & disallow & but allowed because namespaceSelector exempt the ns", 381 Webhooks: []registrationv1.ValidatingWebhook{{ 382 Name: "disallow", 383 ClientConfig: ccfgSVC("disallow"), 384 Rules: newMatchEverythingRules(), 385 NamespaceSelector: &metav1.LabelSelector{ 386 MatchExpressions: []metav1.LabelSelectorRequirement{{ 387 Key: "runlevel", 388 Values: []string{"1"}, 389 Operator: metav1.LabelSelectorOpIn, 390 }}, 391 }, 392 ObjectSelector: &metav1.LabelSelector{}, 393 AdmissionReviewVersions: []string{"v1beta1"}, 394 }}, 395 396 ExpectAllow: true, 397 }, 398 { 399 Name: "match & disallow & but allowed because namespaceSelector exempt the ns ii", 400 Webhooks: []registrationv1.ValidatingWebhook{{ 401 Name: "disallow", 402 ClientConfig: ccfgSVC("disallow"), 403 Rules: newMatchEverythingRules(), 404 NamespaceSelector: &metav1.LabelSelector{ 405 MatchExpressions: []metav1.LabelSelectorRequirement{{ 406 Key: "runlevel", 407 Values: []string{"0"}, 408 Operator: metav1.LabelSelectorOpNotIn, 409 }}, 410 }, 411 ObjectSelector: &metav1.LabelSelector{}, 412 AdmissionReviewVersions: []string{"v1beta1"}, 413 }}, 414 ExpectAllow: true, 415 }, 416 { 417 Name: "match & fail (but allow because fail open)", 418 Webhooks: []registrationv1.ValidatingWebhook{{ 419 Name: "internalErr A", 420 ClientConfig: ccfgSVC("internalErr"), 421 Rules: matchEverythingRules, 422 NamespaceSelector: &metav1.LabelSelector{}, 423 ObjectSelector: &metav1.LabelSelector{}, 424 FailurePolicy: &policyIgnore, 425 AdmissionReviewVersions: []string{"v1beta1"}, 426 }, { 427 Name: "internalErr B", 428 ClientConfig: ccfgSVC("internalErr"), 429 Rules: matchEverythingRules, 430 NamespaceSelector: &metav1.LabelSelector{}, 431 ObjectSelector: &metav1.LabelSelector{}, 432 FailurePolicy: &policyIgnore, 433 AdmissionReviewVersions: []string{"v1beta1"}, 434 }, { 435 Name: "internalErr C", 436 ClientConfig: ccfgSVC("internalErr"), 437 Rules: matchEverythingRules, 438 NamespaceSelector: &metav1.LabelSelector{}, 439 ObjectSelector: &metav1.LabelSelector{}, 440 FailurePolicy: &policyIgnore, 441 AdmissionReviewVersions: []string{"v1beta1"}, 442 }}, 443 444 SkipBenchmark: true, 445 ExpectAllow: true, 446 ExpectAnnotations: map[string]string{ 447 "failed-open.validating.webhook.admission.k8s.io/round_0_index_0": "internalErr A", 448 "failed-open.validating.webhook.admission.k8s.io/round_0_index_1": "internalErr B", 449 "failed-open.validating.webhook.admission.k8s.io/round_0_index_2": "internalErr C", 450 }, 451 }, 452 { 453 Name: "match & fail (but disallow because fail close on nil FailurePolicy)", 454 Webhooks: []registrationv1.ValidatingWebhook{{ 455 Name: "internalErr A", 456 ClientConfig: ccfgSVC("internalErr"), 457 NamespaceSelector: &metav1.LabelSelector{}, 458 ObjectSelector: &metav1.LabelSelector{}, 459 Rules: matchEverythingRules, 460 AdmissionReviewVersions: []string{"v1beta1"}, 461 }, { 462 Name: "internalErr B", 463 ClientConfig: ccfgSVC("internalErr"), 464 NamespaceSelector: &metav1.LabelSelector{}, 465 ObjectSelector: &metav1.LabelSelector{}, 466 Rules: matchEverythingRules, 467 AdmissionReviewVersions: []string{"v1beta1"}, 468 }, { 469 Name: "internalErr C", 470 ClientConfig: ccfgSVC("internalErr"), 471 NamespaceSelector: &metav1.LabelSelector{}, 472 ObjectSelector: &metav1.LabelSelector{}, 473 Rules: matchEverythingRules, 474 AdmissionReviewVersions: []string{"v1beta1"}, 475 }}, 476 ExpectStatusCode: http.StatusInternalServerError, 477 ExpectAllow: false, 478 }, 479 { 480 Name: "match & fail (but fail because fail closed)", 481 Webhooks: []registrationv1.ValidatingWebhook{{ 482 Name: "internalErr A", 483 ClientConfig: ccfgSVC("internalErr"), 484 Rules: matchEverythingRules, 485 NamespaceSelector: &metav1.LabelSelector{}, 486 ObjectSelector: &metav1.LabelSelector{}, 487 FailurePolicy: &policyFail, 488 AdmissionReviewVersions: []string{"v1beta1"}, 489 }, { 490 Name: "internalErr B", 491 ClientConfig: ccfgSVC("internalErr"), 492 Rules: matchEverythingRules, 493 NamespaceSelector: &metav1.LabelSelector{}, 494 ObjectSelector: &metav1.LabelSelector{}, 495 FailurePolicy: &policyFail, 496 AdmissionReviewVersions: []string{"v1beta1"}, 497 }, { 498 Name: "internalErr C", 499 ClientConfig: ccfgSVC("internalErr"), 500 Rules: matchEverythingRules, 501 NamespaceSelector: &metav1.LabelSelector{}, 502 ObjectSelector: &metav1.LabelSelector{}, 503 FailurePolicy: &policyFail, 504 AdmissionReviewVersions: []string{"v1beta1"}, 505 }}, 506 ExpectStatusCode: http.StatusInternalServerError, 507 ExpectAllow: false, 508 }, 509 { 510 Name: "match & allow (url)", 511 Webhooks: []registrationv1.ValidatingWebhook{{ 512 Name: "allow.example.com", 513 ClientConfig: ccfgURL("allow"), 514 Rules: matchEverythingRules, 515 NamespaceSelector: &metav1.LabelSelector{}, 516 ObjectSelector: &metav1.LabelSelector{}, 517 AdmissionReviewVersions: []string{"v1beta1"}, 518 }}, 519 ExpectAllow: true, 520 ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, 521 }, 522 { 523 Name: "match & disallow (url)", 524 Webhooks: []registrationv1.ValidatingWebhook{{ 525 Name: "disallow", 526 ClientConfig: ccfgURL("disallow"), 527 Rules: matchEverythingRules, 528 NamespaceSelector: &metav1.LabelSelector{}, 529 ObjectSelector: &metav1.LabelSelector{}, 530 AdmissionReviewVersions: []string{"v1beta1"}, 531 }}, 532 ExpectStatusCode: http.StatusForbidden, 533 ErrorContains: "without explanation", 534 }, { 535 Name: "absent response and fail open", 536 Webhooks: []registrationv1.ValidatingWebhook{{ 537 Name: "nilResponse", 538 ClientConfig: ccfgURL("nilResponse"), 539 FailurePolicy: &policyIgnore, 540 Rules: matchEverythingRules, 541 NamespaceSelector: &metav1.LabelSelector{}, 542 ObjectSelector: &metav1.LabelSelector{}, 543 AdmissionReviewVersions: []string{"v1beta1"}, 544 }}, 545 SkipBenchmark: true, 546 ExpectAllow: true, 547 ExpectAnnotations: map[string]string{"failed-open.validating.webhook.admission.k8s.io/round_0_index_0": "nilResponse"}, 548 }, 549 { 550 Name: "absent response and fail closed", 551 Webhooks: []registrationv1.ValidatingWebhook{{ 552 Name: "nilResponse", 553 ClientConfig: ccfgURL("nilResponse"), 554 FailurePolicy: &policyFail, 555 Rules: matchEverythingRules, 556 NamespaceSelector: &metav1.LabelSelector{}, 557 ObjectSelector: &metav1.LabelSelector{}, 558 AdmissionReviewVersions: []string{"v1beta1"}, 559 }}, 560 ExpectStatusCode: http.StatusInternalServerError, 561 ErrorContains: "webhook response was absent", 562 }, 563 { 564 Name: "no match dry run", 565 Webhooks: []registrationv1.ValidatingWebhook{{ 566 Name: "nomatch", 567 ClientConfig: ccfgSVC("allow"), 568 Rules: []registrationv1.RuleWithOperations{{ 569 Operations: []registrationv1.OperationType{registrationv1.Create}, 570 }}, 571 NamespaceSelector: &metav1.LabelSelector{}, 572 ObjectSelector: &metav1.LabelSelector{}, 573 SideEffects: &sideEffectsSome, 574 AdmissionReviewVersions: []string{"v1beta1"}, 575 }}, 576 IsDryRun: true, 577 ExpectAllow: true, 578 }, 579 { 580 Name: "match dry run side effects Unknown", 581 Webhooks: []registrationv1.ValidatingWebhook{{ 582 Name: "allow", 583 ClientConfig: ccfgSVC("allow"), 584 Rules: matchEverythingRules, 585 NamespaceSelector: &metav1.LabelSelector{}, 586 ObjectSelector: &metav1.LabelSelector{}, 587 SideEffects: &sideEffectsUnknown, 588 AdmissionReviewVersions: []string{"v1beta1"}, 589 }}, 590 IsDryRun: true, 591 ExpectStatusCode: http.StatusBadRequest, 592 ErrorContains: "does not support dry run", 593 }, 594 { 595 Name: "match dry run side effects None", 596 Webhooks: []registrationv1.ValidatingWebhook{{ 597 Name: "allow", 598 ClientConfig: ccfgSVC("allow"), 599 Rules: matchEverythingRules, 600 NamespaceSelector: &metav1.LabelSelector{}, 601 ObjectSelector: &metav1.LabelSelector{}, 602 SideEffects: &sideEffectsNone, 603 AdmissionReviewVersions: []string{"v1beta1"}, 604 }}, 605 IsDryRun: true, 606 ExpectAllow: true, 607 ExpectAnnotations: map[string]string{"allow/key1": "value1"}, 608 }, 609 { 610 Name: "match dry run side effects Some", 611 Webhooks: []registrationv1.ValidatingWebhook{{ 612 Name: "allow", 613 ClientConfig: ccfgSVC("allow"), 614 Rules: matchEverythingRules, 615 NamespaceSelector: &metav1.LabelSelector{}, 616 ObjectSelector: &metav1.LabelSelector{}, 617 SideEffects: &sideEffectsSome, 618 AdmissionReviewVersions: []string{"v1beta1"}, 619 }}, 620 IsDryRun: true, 621 ExpectStatusCode: http.StatusBadRequest, 622 ErrorContains: "does not support dry run", 623 }, 624 { 625 Name: "match dry run side effects NoneOnDryRun", 626 Webhooks: []registrationv1.ValidatingWebhook{{ 627 Name: "allow", 628 ClientConfig: ccfgSVC("allow"), 629 Rules: matchEverythingRules, 630 NamespaceSelector: &metav1.LabelSelector{}, 631 ObjectSelector: &metav1.LabelSelector{}, 632 SideEffects: &sideEffectsNoneOnDryRun, 633 AdmissionReviewVersions: []string{"v1beta1"}, 634 }}, 635 IsDryRun: true, 636 ExpectAllow: true, 637 ExpectAnnotations: map[string]string{"allow/key1": "value1"}, 638 }, 639 { 640 Name: "illegal annotation format", 641 Webhooks: []registrationv1.ValidatingWebhook{{ 642 Name: "invalidAnnotation", 643 ClientConfig: ccfgURL("invalidAnnotation"), 644 Rules: matchEverythingRules, 645 NamespaceSelector: &metav1.LabelSelector{}, 646 ObjectSelector: &metav1.LabelSelector{}, 647 AdmissionReviewVersions: []string{"v1beta1"}, 648 }}, 649 ExpectAllow: true, 650 }, 651 { 652 Name: "skip webhook whose objectSelector does not match", 653 Webhooks: []registrationv1.ValidatingWebhook{{ 654 Name: "allow.example.com", 655 ClientConfig: ccfgSVC("allow"), 656 Rules: matchEverythingRules, 657 NamespaceSelector: &metav1.LabelSelector{}, 658 ObjectSelector: &metav1.LabelSelector{}, 659 AdmissionReviewVersions: []string{"v1beta1"}, 660 }, { 661 Name: "shouldNotBeCalled", 662 ClientConfig: ccfgSVC("shouldNotBeCalled"), 663 NamespaceSelector: &metav1.LabelSelector{}, 664 ObjectSelector: &metav1.LabelSelector{ 665 MatchLabels: map[string]string{ 666 "label": "nonexistent", 667 }, 668 }, 669 Rules: matchEverythingRules, 670 AdmissionReviewVersions: []string{"v1beta1"}, 671 }}, 672 ExpectAllow: true, 673 ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, 674 }, 675 { 676 Name: "skip webhook whose objectSelector does not match CRD's labels", 677 Webhooks: []registrationv1.ValidatingWebhook{{ 678 Name: "allow.example.com", 679 ClientConfig: ccfgSVC("allow"), 680 Rules: matchEverythingRules, 681 NamespaceSelector: &metav1.LabelSelector{}, 682 ObjectSelector: &metav1.LabelSelector{}, 683 AdmissionReviewVersions: []string{"v1beta1"}, 684 }, { 685 Name: "shouldNotBeCalled", 686 ClientConfig: ccfgSVC("shouldNotBeCalled"), 687 NamespaceSelector: &metav1.LabelSelector{}, 688 ObjectSelector: &metav1.LabelSelector{ 689 MatchLabels: map[string]string{ 690 "label": "nonexistent", 691 }, 692 }, 693 Rules: matchEverythingRules, 694 AdmissionReviewVersions: []string{"v1beta1"}, 695 }}, 696 IsCRD: true, 697 ExpectAllow: true, 698 ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, 699 }, 700 // No need to test everything with the url case, since only the 701 // connection is different. 702 } 703 } 704 705 // NewNonMutatingPanicTestCases returns test cases with a given base url. 706 // All test cases in NewNonMutatingTestCases have no Patch set in 707 // AdmissionResponse. The expected responses are set for panic handling. 708 func NewNonMutatingPanicTestCases(url *url.URL) []ValidatingTest { 709 policyIgnore := registrationv1.Ignore 710 policyFail := registrationv1.Fail 711 712 return []ValidatingTest{ 713 { 714 Name: "match & allow, but panic", 715 Webhooks: []registrationv1.ValidatingWebhook{{ 716 Name: "allow.example.com", 717 ClientConfig: ccfgSVC("allow"), 718 Rules: matchEverythingRules, 719 NamespaceSelector: &metav1.LabelSelector{}, 720 ObjectSelector: &metav1.LabelSelector{}, 721 AdmissionReviewVersions: []string{"v1beta1"}, 722 }}, 723 ExpectStatusCode: http.StatusForbidden, 724 ErrorContains: "ValidatingAdmissionWebhook/allow.example.com has panicked: Start panicking!", 725 ExpectAnnotations: map[string]string{}, 726 }, 727 { 728 Name: "match & fail (but allow because fail open)", 729 Webhooks: []registrationv1.ValidatingWebhook{{ 730 Name: "internalErr A", 731 ClientConfig: ccfgSVC("internalErr"), 732 Rules: matchEverythingRules, 733 NamespaceSelector: &metav1.LabelSelector{}, 734 ObjectSelector: &metav1.LabelSelector{}, 735 FailurePolicy: &policyIgnore, 736 AdmissionReviewVersions: []string{"v1beta1"}, 737 }, { 738 Name: "internalErr B", 739 ClientConfig: ccfgSVC("internalErr"), 740 Rules: matchEverythingRules, 741 NamespaceSelector: &metav1.LabelSelector{}, 742 ObjectSelector: &metav1.LabelSelector{}, 743 FailurePolicy: &policyIgnore, 744 AdmissionReviewVersions: []string{"v1beta1"}, 745 }, { 746 Name: "internalErr C", 747 ClientConfig: ccfgSVC("internalErr"), 748 Rules: matchEverythingRules, 749 NamespaceSelector: &metav1.LabelSelector{}, 750 ObjectSelector: &metav1.LabelSelector{}, 751 FailurePolicy: &policyIgnore, 752 AdmissionReviewVersions: []string{"v1beta1"}, 753 }}, 754 755 SkipBenchmark: true, 756 ExpectAllow: true, 757 ExpectAnnotations: map[string]string{ 758 "failed-open.validating.webhook.admission.k8s.io/round_0_index_0": "internalErr A", 759 "failed-open.validating.webhook.admission.k8s.io/round_0_index_1": "internalErr B", 760 "failed-open.validating.webhook.admission.k8s.io/round_0_index_2": "internalErr C", 761 }, 762 }, 763 { 764 Name: "match & fail (but fail because fail closed)", 765 Webhooks: []registrationv1.ValidatingWebhook{{ 766 Name: "internalErr A", 767 ClientConfig: ccfgSVC("internalErr"), 768 Rules: matchEverythingRules, 769 NamespaceSelector: &metav1.LabelSelector{}, 770 ObjectSelector: &metav1.LabelSelector{}, 771 FailurePolicy: &policyFail, 772 AdmissionReviewVersions: []string{"v1beta1"}, 773 }, { 774 Name: "internalErr B", 775 ClientConfig: ccfgSVC("internalErr"), 776 Rules: matchEverythingRules, 777 NamespaceSelector: &metav1.LabelSelector{}, 778 ObjectSelector: &metav1.LabelSelector{}, 779 FailurePolicy: &policyFail, 780 AdmissionReviewVersions: []string{"v1beta1"}, 781 }, { 782 Name: "internalErr C", 783 ClientConfig: ccfgSVC("internalErr"), 784 Rules: matchEverythingRules, 785 NamespaceSelector: &metav1.LabelSelector{}, 786 ObjectSelector: &metav1.LabelSelector{}, 787 FailurePolicy: &policyFail, 788 AdmissionReviewVersions: []string{"v1beta1"}, 789 }}, 790 ExpectStatusCode: http.StatusInternalServerError, 791 ExpectAllow: false, 792 ErrorContains: " has panicked: Start panicking!", 793 }, 794 } 795 } 796 797 func mutationAnnotationValue(configuration, webhook string, mutated bool) string { 798 return fmt.Sprintf(`{"configuration":"%s","webhook":"%s","mutated":%t}`, configuration, webhook, mutated) 799 } 800 801 func patchAnnotationValue(configuration, webhook string, patch string) string { 802 return strings.Replace(fmt.Sprintf(`{"configuration": "%s", "webhook": "%s", "patch": %s, "patchType": "JSONPatch"}`, configuration, webhook, patch), " ", "", -1) 803 } 804 805 // NewMutatingTestCases returns test cases with a given base url. 806 // All test cases in NewMutatingTestCases have Patch set in 807 // AdmissionResponse. The test cases are only used by both MutatingAdmissionWebhook. 808 func NewMutatingTestCases(url *url.URL, configurationName string) []MutatingTest { 809 return []MutatingTest{ 810 { 811 Name: "match & remove label", 812 Webhooks: []registrationv1.MutatingWebhook{{ 813 Name: "removelabel.example.com", 814 ClientConfig: ccfgSVC("removeLabel"), 815 Rules: matchEverythingRules, 816 NamespaceSelector: &metav1.LabelSelector{}, 817 ObjectSelector: &metav1.LabelSelector{}, 818 AdmissionReviewVersions: []string{"v1beta1"}, 819 }}, 820 ExpectAllow: true, 821 AdditionalLabels: map[string]string{"remove": "me"}, 822 ExpectLabels: map[string]string{"pod.name": "my-pod"}, 823 ExpectAnnotations: map[string]string{ 824 "removelabel.example.com/key1": "value1", 825 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "removelabel.example.com", true), 826 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "removelabel.example.com", `[{"op": "remove", "path": "/metadata/labels/remove"}]`), 827 }, 828 }, 829 { 830 Name: "match & add label", 831 Webhooks: []registrationv1.MutatingWebhook{{ 832 Name: "addLabel", 833 ClientConfig: ccfgSVC("addLabel"), 834 Rules: matchEverythingRules, 835 NamespaceSelector: &metav1.LabelSelector{}, 836 ObjectSelector: &metav1.LabelSelector{}, 837 AdmissionReviewVersions: []string{"v1beta1"}, 838 }}, 839 ExpectAllow: true, 840 ExpectLabels: map[string]string{"pod.name": "my-pod", "added": "test"}, 841 ExpectAnnotations: map[string]string{ 842 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "addLabel", true), 843 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "addLabel", `[{"op": "add", "path": "/metadata/labels/added", "value": "test"}]`), 844 }, 845 }, 846 { 847 Name: "match CRD & add label", 848 Webhooks: []registrationv1.MutatingWebhook{{ 849 Name: "addLabel", 850 ClientConfig: ccfgSVC("addLabel"), 851 Rules: matchEverythingRules, 852 NamespaceSelector: &metav1.LabelSelector{}, 853 ObjectSelector: &metav1.LabelSelector{}, 854 AdmissionReviewVersions: []string{"v1beta1"}, 855 }}, 856 IsCRD: true, 857 ExpectAllow: true, 858 ExpectLabels: map[string]string{"crd.name": "my-test-crd", "added": "test"}, 859 ExpectAnnotations: map[string]string{ 860 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "addLabel", true), 861 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "addLabel", `[{"op": "add", "path": "/metadata/labels/added", "value": "test"}]`), 862 }, 863 }, 864 { 865 Name: "match CRD & remove label", 866 Webhooks: []registrationv1.MutatingWebhook{{ 867 Name: "removelabel.example.com", 868 ClientConfig: ccfgSVC("removeLabel"), 869 Rules: matchEverythingRules, 870 NamespaceSelector: &metav1.LabelSelector{}, 871 ObjectSelector: &metav1.LabelSelector{}, 872 AdmissionReviewVersions: []string{"v1beta1"}, 873 }}, 874 IsCRD: true, 875 ExpectAllow: true, 876 AdditionalLabels: map[string]string{"remove": "me"}, 877 ExpectLabels: map[string]string{"crd.name": "my-test-crd"}, 878 ExpectAnnotations: map[string]string{ 879 "removelabel.example.com/key1": "value1", 880 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "removelabel.example.com", true), 881 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "removelabel.example.com", `[{"op": "remove", "path": "/metadata/labels/remove"}]`), 882 }, 883 }, 884 { 885 Name: "match & invalid mutation", 886 Webhooks: []registrationv1.MutatingWebhook{{ 887 Name: "invalidMutation", 888 ClientConfig: ccfgSVC("invalidMutation"), 889 Rules: matchEverythingRules, 890 NamespaceSelector: &metav1.LabelSelector{}, 891 ObjectSelector: &metav1.LabelSelector{}, 892 AdmissionReviewVersions: []string{"v1beta1"}, 893 }}, 894 ExpectStatusCode: http.StatusInternalServerError, 895 ErrorContains: "invalid character", 896 ExpectAnnotations: map[string]string{ 897 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "invalidMutation", false), 898 }, 899 }, 900 { 901 Name: "match & remove label dry run unsupported", 902 Webhooks: []registrationv1.MutatingWebhook{{ 903 Name: "removeLabel", 904 ClientConfig: ccfgSVC("removeLabel"), 905 Rules: matchEverythingRules, 906 NamespaceSelector: &metav1.LabelSelector{}, 907 ObjectSelector: &metav1.LabelSelector{}, 908 SideEffects: &sideEffectsUnknown, 909 AdmissionReviewVersions: []string{"v1beta1"}, 910 }}, 911 IsDryRun: true, 912 ExpectStatusCode: http.StatusBadRequest, 913 ErrorContains: "does not support dry run", 914 ExpectAnnotations: map[string]string{ 915 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "removeLabel", false), 916 }, 917 }, 918 { 919 Name: "first webhook remove labels, second webhook shouldn't be called", 920 Webhooks: []registrationv1.MutatingWebhook{{ 921 Name: "removelabel.example.com", 922 ClientConfig: ccfgSVC("removeLabel"), 923 Rules: matchEverythingRules, 924 NamespaceSelector: &metav1.LabelSelector{}, 925 ObjectSelector: &metav1.LabelSelector{ 926 MatchLabels: map[string]string{ 927 "remove": "me", 928 }, 929 }, 930 AdmissionReviewVersions: []string{"v1beta1"}, 931 }, { 932 Name: "shouldNotBeCalled", 933 ClientConfig: ccfgSVC("shouldNotBeCalled"), 934 NamespaceSelector: &metav1.LabelSelector{}, 935 ObjectSelector: &metav1.LabelSelector{ 936 MatchLabels: map[string]string{ 937 "remove": "me", 938 }, 939 }, 940 Rules: matchEverythingRules, 941 AdmissionReviewVersions: []string{"v1beta1"}, 942 }}, 943 ExpectAllow: true, 944 AdditionalLabels: map[string]string{"remove": "me"}, 945 ExpectLabels: map[string]string{"pod.name": "my-pod"}, 946 ExpectAnnotations: map[string]string{ 947 "removelabel.example.com/key1": "value1", 948 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "removelabel.example.com", true), 949 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "removelabel.example.com", `[{"op": "remove", "path": "/metadata/labels/remove"}]`), 950 }, 951 }, 952 { 953 Name: "first webhook remove labels from CRD, second webhook shouldn't be called", 954 Webhooks: []registrationv1.MutatingWebhook{{ 955 Name: "removelabel.example.com", 956 ClientConfig: ccfgSVC("removeLabel"), 957 Rules: matchEverythingRules, 958 NamespaceSelector: &metav1.LabelSelector{}, 959 ObjectSelector: &metav1.LabelSelector{ 960 MatchLabels: map[string]string{ 961 "remove": "me", 962 }, 963 }, 964 AdmissionReviewVersions: []string{"v1beta1"}, 965 }, { 966 Name: "shouldNotBeCalled", 967 ClientConfig: ccfgSVC("shouldNotBeCalled"), 968 NamespaceSelector: &metav1.LabelSelector{}, 969 ObjectSelector: &metav1.LabelSelector{ 970 MatchLabels: map[string]string{ 971 "remove": "me", 972 }, 973 }, 974 Rules: matchEverythingRules, 975 AdmissionReviewVersions: []string{"v1beta1"}, 976 }}, 977 IsCRD: true, 978 ExpectAllow: true, 979 AdditionalLabels: map[string]string{"remove": "me"}, 980 ExpectLabels: map[string]string{"crd.name": "my-test-crd"}, 981 ExpectAnnotations: map[string]string{ 982 "removelabel.example.com/key1": "value1", 983 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "removelabel.example.com", true), 984 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "removelabel.example.com", `[{"op": "remove", "path": "/metadata/labels/remove"}]`), 985 }, 986 }, 987 // No need to test everything with the url case, since only the 988 // connection is different. 989 { 990 Name: "match & reinvoke if needed policy", 991 Webhooks: []registrationv1.MutatingWebhook{{ 992 Name: "addLabel", 993 ClientConfig: ccfgSVC("addLabel"), 994 Rules: matchEverythingRules, 995 NamespaceSelector: &metav1.LabelSelector{}, 996 ObjectSelector: &metav1.LabelSelector{}, 997 AdmissionReviewVersions: []string{"v1beta1"}, 998 ReinvocationPolicy: &reinvokeIfNeeded, 999 }, { 1000 Name: "removeLabel", 1001 ClientConfig: ccfgSVC("removeLabel"), 1002 Rules: matchEverythingRules, 1003 NamespaceSelector: &metav1.LabelSelector{}, 1004 ObjectSelector: &metav1.LabelSelector{}, 1005 AdmissionReviewVersions: []string{"v1beta1"}, 1006 ReinvocationPolicy: &reinvokeIfNeeded, 1007 }}, 1008 AdditionalLabels: map[string]string{"remove": "me"}, 1009 ExpectAllow: true, 1010 ExpectReinvokeWebhooks: map[string]bool{"addLabel": true}, 1011 ExpectAnnotations: map[string]string{ 1012 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "addLabel", true), 1013 "mutation.webhook.admission.k8s.io/round_0_index_1": mutationAnnotationValue(configurationName, "removeLabel", true), 1014 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "addLabel", `[{"op": "add", "path": "/metadata/labels/added", "value": "test"}]`), 1015 "patch.webhook.admission.k8s.io/round_0_index_1": patchAnnotationValue(configurationName, "removeLabel", `[{"op": "remove", "path": "/metadata/labels/remove"}]`), 1016 }, 1017 }, 1018 { 1019 Name: "match & never reinvoke policy", 1020 Webhooks: []registrationv1.MutatingWebhook{{ 1021 Name: "addLabel", 1022 ClientConfig: ccfgSVC("addLabel"), 1023 Rules: matchEverythingRules, 1024 NamespaceSelector: &metav1.LabelSelector{}, 1025 ObjectSelector: &metav1.LabelSelector{}, 1026 AdmissionReviewVersions: []string{"v1beta1"}, 1027 ReinvocationPolicy: &reinvokeNever, 1028 }}, 1029 ExpectAllow: true, 1030 ExpectReinvokeWebhooks: map[string]bool{"addLabel": false}, 1031 ExpectAnnotations: map[string]string{ 1032 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "addLabel", true), 1033 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "addLabel", `[{"op": "add", "path": "/metadata/labels/added", "value": "test"}]`), 1034 }, 1035 }, 1036 { 1037 Name: "match & never reinvoke policy (by default)", 1038 Webhooks: []registrationv1.MutatingWebhook{{ 1039 Name: "addLabel", 1040 ClientConfig: ccfgSVC("addLabel"), 1041 Rules: matchEverythingRules, 1042 NamespaceSelector: &metav1.LabelSelector{}, 1043 ObjectSelector: &metav1.LabelSelector{}, 1044 AdmissionReviewVersions: []string{"v1beta1"}, 1045 }}, 1046 ExpectAllow: true, 1047 ExpectReinvokeWebhooks: map[string]bool{"addLabel": false}, 1048 ExpectAnnotations: map[string]string{ 1049 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "addLabel", true), 1050 "patch.webhook.admission.k8s.io/round_0_index_0": patchAnnotationValue(configurationName, "addLabel", `[{"op": "add", "path": "/metadata/labels/added", "value": "test"}]`), 1051 }, 1052 }, 1053 { 1054 Name: "match & no reinvoke", 1055 Webhooks: []registrationv1.MutatingWebhook{{ 1056 Name: "noop", 1057 ClientConfig: ccfgSVC("noop"), 1058 Rules: matchEverythingRules, 1059 NamespaceSelector: &metav1.LabelSelector{}, 1060 ObjectSelector: &metav1.LabelSelector{}, 1061 AdmissionReviewVersions: []string{"v1beta1"}, 1062 }}, 1063 ExpectAllow: true, 1064 ExpectAnnotations: map[string]string{ 1065 "mutation.webhook.admission.k8s.io/round_0_index_0": mutationAnnotationValue(configurationName, "noop", false), 1066 }, 1067 }, 1068 } 1069 } 1070 1071 // CachedTest is a test case for the client manager. 1072 type CachedTest struct { 1073 Name string 1074 Webhooks []registrationv1.ValidatingWebhook 1075 ExpectAllow bool 1076 ExpectCacheMiss bool 1077 } 1078 1079 // NewCachedClientTestcases returns a set of client manager test cases. 1080 func NewCachedClientTestcases(url *url.URL) []CachedTest { 1081 policyIgnore := registrationv1.Ignore 1082 ccfgURL := urlConfigGenerator{url}.ccfgURL 1083 1084 return []CachedTest{ 1085 { 1086 Name: "uncached: service webhook, path 'allow'", 1087 Webhooks: []registrationv1.ValidatingWebhook{{ 1088 Name: "cache1", 1089 ClientConfig: ccfgSVC("allow"), 1090 Rules: newMatchEverythingRules(), 1091 NamespaceSelector: &metav1.LabelSelector{}, 1092 ObjectSelector: &metav1.LabelSelector{}, 1093 FailurePolicy: &policyIgnore, 1094 AdmissionReviewVersions: []string{"v1beta1"}, 1095 }}, 1096 ExpectAllow: true, 1097 ExpectCacheMiss: true, 1098 }, 1099 { 1100 Name: "uncached: service webhook, path 'internalErr'", 1101 Webhooks: []registrationv1.ValidatingWebhook{{ 1102 Name: "cache2", 1103 ClientConfig: ccfgSVC("internalErr"), 1104 Rules: newMatchEverythingRules(), 1105 NamespaceSelector: &metav1.LabelSelector{}, 1106 ObjectSelector: &metav1.LabelSelector{}, 1107 FailurePolicy: &policyIgnore, 1108 AdmissionReviewVersions: []string{"v1beta1"}, 1109 }}, 1110 ExpectAllow: true, 1111 ExpectCacheMiss: true, 1112 }, 1113 { 1114 Name: "cached: service webhook, path 'allow'", 1115 Webhooks: []registrationv1.ValidatingWebhook{{ 1116 Name: "cache3", 1117 ClientConfig: ccfgSVC("allow"), 1118 Rules: newMatchEverythingRules(), 1119 NamespaceSelector: &metav1.LabelSelector{}, 1120 ObjectSelector: &metav1.LabelSelector{}, 1121 FailurePolicy: &policyIgnore, 1122 AdmissionReviewVersions: []string{"v1beta1"}, 1123 }}, 1124 ExpectAllow: true, 1125 ExpectCacheMiss: false, 1126 }, 1127 { 1128 Name: "uncached: url webhook, path 'allow'", 1129 Webhooks: []registrationv1.ValidatingWebhook{{ 1130 Name: "cache4", 1131 ClientConfig: ccfgURL("allow"), 1132 Rules: newMatchEverythingRules(), 1133 NamespaceSelector: &metav1.LabelSelector{}, 1134 ObjectSelector: &metav1.LabelSelector{}, 1135 FailurePolicy: &policyIgnore, 1136 AdmissionReviewVersions: []string{"v1beta1"}, 1137 }}, 1138 ExpectAllow: true, 1139 ExpectCacheMiss: true, 1140 }, 1141 { 1142 Name: "cached: url webhook, path 'allow'", 1143 Webhooks: []registrationv1.ValidatingWebhook{{ 1144 Name: "cache5", 1145 ClientConfig: ccfgURL("allow"), 1146 Rules: newMatchEverythingRules(), 1147 NamespaceSelector: &metav1.LabelSelector{}, 1148 ObjectSelector: &metav1.LabelSelector{}, 1149 FailurePolicy: &policyIgnore, 1150 AdmissionReviewVersions: []string{"v1beta1"}, 1151 }}, 1152 ExpectAllow: true, 1153 ExpectCacheMiss: false, 1154 }, 1155 } 1156 } 1157 1158 // ccfgSVC returns a client config using the service reference mechanism. 1159 func ccfgSVC(urlPath string) registrationv1.WebhookClientConfig { 1160 return registrationv1.WebhookClientConfig{ 1161 Service: ®istrationv1.ServiceReference{ 1162 Name: "webhook-test", 1163 Namespace: "default", 1164 Path: &urlPath, 1165 }, 1166 CABundle: testcerts.CACert, 1167 } 1168 } 1169 1170 func newMatchEverythingRules() []registrationv1.RuleWithOperations { 1171 return []registrationv1.RuleWithOperations{{ 1172 Operations: []registrationv1.OperationType{registrationv1.OperationAll}, 1173 Rule: registrationv1.Rule{ 1174 APIGroups: []string{"*"}, 1175 APIVersions: []string{"*"}, 1176 Resources: []string{"*/*"}, 1177 }, 1178 }} 1179 } 1180 1181 // NewObjectInterfacesForTest returns an ObjectInterfaces appropriate for test cases in this file. 1182 func NewObjectInterfacesForTest() admission.ObjectInterfaces { 1183 scheme := runtime.NewScheme() 1184 corev1.AddToScheme(scheme) 1185 return admission.NewObjectInterfacesFromScheme(scheme) 1186 } 1187 1188 // NewValidationDurationTestCases returns test cases for webhook duration test 1189 func NewValidationDurationTestCases(url *url.URL) []DurationTest { 1190 ccfgURL := urlConfigGenerator{url}.ccfgURL 1191 webhooks := []registrationv1.ValidatingWebhook{ 1192 { 1193 Name: "allow match", 1194 ClientConfig: ccfgURL("allow/100"), 1195 Rules: matchEverythingRules, 1196 NamespaceSelector: &metav1.LabelSelector{}, 1197 ObjectSelector: &metav1.LabelSelector{}, 1198 AdmissionReviewVersions: []string{"v1beta1"}, 1199 }, 1200 { 1201 Name: "allow no match", 1202 ClientConfig: ccfgURL("allow/200"), 1203 NamespaceSelector: &metav1.LabelSelector{}, 1204 ObjectSelector: &metav1.LabelSelector{}, 1205 AdmissionReviewVersions: []string{"v1beta1"}, 1206 }, 1207 { 1208 Name: "disallow match", 1209 ClientConfig: ccfgURL("disallow/400"), 1210 Rules: matchEverythingRules, 1211 NamespaceSelector: &metav1.LabelSelector{}, 1212 ObjectSelector: &metav1.LabelSelector{}, 1213 AdmissionReviewVersions: []string{"v1beta1"}, 1214 }, 1215 { 1216 Name: "disallow no match", 1217 ClientConfig: ccfgURL("disallow/800"), 1218 NamespaceSelector: &metav1.LabelSelector{}, 1219 ObjectSelector: &metav1.LabelSelector{}, 1220 AdmissionReviewVersions: []string{"v1beta1"}, 1221 }, 1222 } 1223 1224 return []DurationTest{ 1225 { 1226 Name: "duration test", 1227 IsDryRun: false, 1228 InitContext: true, 1229 Webhooks: webhooks, 1230 ExpectedDurationSum: 500, 1231 ExpectedDurationMax: 400, 1232 }, 1233 { 1234 Name: "duration dry run", 1235 IsDryRun: true, 1236 InitContext: true, 1237 Webhooks: webhooks, 1238 ExpectedDurationSum: 0, 1239 ExpectedDurationMax: 0, 1240 }, 1241 { 1242 Name: "duration no init", 1243 IsDryRun: false, 1244 InitContext: false, 1245 Webhooks: webhooks, 1246 ExpectedDurationSum: 0, 1247 ExpectedDurationMax: 0, 1248 }, 1249 } 1250 }