1
16
17 package apimachinery
18
19 import (
20 "context"
21 "encoding/json"
22 "fmt"
23 "reflect"
24 "strings"
25 "time"
26
27 "k8s.io/utils/pointer"
28
29 admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
30 appsv1 "k8s.io/api/apps/v1"
31 v1 "k8s.io/api/core/v1"
32 rbacv1 "k8s.io/api/rbac/v1"
33 apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
34 crdclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
35 apierrors "k8s.io/apimachinery/pkg/api/errors"
36 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
37 "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
38 "k8s.io/apimachinery/pkg/types"
39 "k8s.io/apimachinery/pkg/util/intstr"
40 "k8s.io/apimachinery/pkg/util/uuid"
41 "k8s.io/apimachinery/pkg/util/wait"
42 "k8s.io/client-go/dynamic"
43 clientset "k8s.io/client-go/kubernetes"
44 "k8s.io/client-go/util/retry"
45 "k8s.io/kubernetes/test/e2e/framework"
46 e2edeployment "k8s.io/kubernetes/test/e2e/framework/deployment"
47 e2ekubectl "k8s.io/kubernetes/test/e2e/framework/kubectl"
48 e2epod "k8s.io/kubernetes/test/e2e/framework/pod"
49 "k8s.io/kubernetes/test/utils/crd"
50 imageutils "k8s.io/kubernetes/test/utils/image"
51 admissionapi "k8s.io/pod-security-admission/api"
52
53 "github.com/onsi/ginkgo/v2"
54 "github.com/onsi/gomega"
55
56
57 _ "github.com/stretchr/testify/assert"
58 )
59
60 const (
61 secretName = "sample-webhook-secret"
62 deploymentName = "sample-webhook-deployment"
63 serviceName = "e2e-test-webhook"
64 roleBindingName = "webhook-auth-reader"
65
66 skipNamespaceLabelKey = "skip-webhook-admission"
67 skipNamespaceLabelValue = "yes"
68 skipNamespaceBaseName = "exempted-namespace"
69 disallowedPodName = "disallowed-pod"
70 toBeAttachedPodName = "to-be-attached-pod"
71 hangingPodName = "hanging-pod"
72 disallowedConfigMapName = "disallowed-configmap"
73 allowedConfigMapName = "allowed-configmap"
74 failNamespaceLabelKey = "fail-closed-webhook"
75 failNamespaceLabelValue = "yes"
76 failNamespaceBaseName = "fail-closed-namespace"
77 addedLabelKey = "added-label"
78 addedLabelValue = "yes"
79 )
80
81 var _ = SIGDescribe("AdmissionWebhook [Privileged:ClusterAdmin]", func() {
82 var certCtx *certContext
83 f := framework.NewDefaultFramework("webhook")
84 f.NamespacePodSecurityLevel = admissionapi.LevelBaseline
85 servicePort := int32(8443)
86 containerPort := int32(8444)
87
88 var client clientset.Interface
89 var namespaceName string
90 var markersNamespaceName string
91
92 ginkgo.BeforeEach(func(ctx context.Context) {
93 client = f.ClientSet
94 namespaceName = f.Namespace.Name
95
96
97 labelNamespace(ctx, f, f.Namespace.Name)
98 markersNamespaceName = createWebhookConfigurationReadyNamespace(ctx, f)
99
100 ginkgo.By("Setting up server cert")
101 certCtx = setupServerCert(namespaceName, serviceName)
102 createAuthReaderRoleBinding(ctx, f, namespaceName)
103
104 deployWebhookAndService(ctx, f, imageutils.GetE2EImage(imageutils.Agnhost), certCtx, servicePort, containerPort)
105 })
106
107 ginkgo.AfterEach(func(ctx context.Context) {
108 cleanWebhookTest(ctx, client, namespaceName)
109 })
110
111
119 framework.ConformanceIt("should include webhook resources in discovery documents", func(ctx context.Context) {
120 {
121 ginkgo.By("fetching the /apis discovery document")
122 apiGroupList := &metav1.APIGroupList{}
123 err := client.Discovery().RESTClient().Get().AbsPath("/apis").Do(ctx).Into(apiGroupList)
124 framework.ExpectNoError(err, "fetching /apis")
125
126 ginkgo.By("finding the admissionregistration.k8s.io API group in the /apis discovery document")
127 var group *metav1.APIGroup
128 for _, g := range apiGroupList.Groups {
129 if g.Name == admissionregistrationv1.GroupName {
130 group = &g
131 break
132 }
133 }
134 gomega.Expect(group).ToNot(gomega.BeNil(), "admissionregistration.k8s.io API group not found in /apis discovery document")
135
136 ginkgo.By("finding the admissionregistration.k8s.io/v1 API group/version in the /apis discovery document")
137 var version *metav1.GroupVersionForDiscovery
138 for _, v := range group.Versions {
139 if v.Version == admissionregistrationv1.SchemeGroupVersion.Version {
140 version = &v
141 break
142 }
143 }
144 gomega.Expect(version).ToNot(gomega.BeNil(), "admissionregistration.k8s.io/v1 API group version not found in /apis discovery document")
145 }
146
147 {
148 ginkgo.By("fetching the /apis/admissionregistration.k8s.io discovery document")
149 group := &metav1.APIGroup{}
150 err := client.Discovery().RESTClient().Get().AbsPath("/apis/admissionregistration.k8s.io").Do(ctx).Into(group)
151 framework.ExpectNoError(err, "fetching /apis/admissionregistration.k8s.io")
152 gomega.Expect(group.Name).To(gomega.Equal(admissionregistrationv1.GroupName), "verifying API group name in /apis/admissionregistration.k8s.io discovery document")
153
154 ginkgo.By("finding the admissionregistration.k8s.io/v1 API group/version in the /apis/admissionregistration.k8s.io discovery document")
155 var version *metav1.GroupVersionForDiscovery
156 for _, v := range group.Versions {
157 if v.Version == admissionregistrationv1.SchemeGroupVersion.Version {
158 version = &v
159 break
160 }
161 }
162 gomega.Expect(version).ToNot(gomega.BeNil(), "admissionregistration.k8s.io/v1 API group version not found in /apis/admissionregistration.k8s.io discovery document")
163 }
164
165 {
166 ginkgo.By("fetching the /apis/admissionregistration.k8s.io/v1 discovery document")
167 apiResourceList := &metav1.APIResourceList{}
168 err := client.Discovery().RESTClient().Get().AbsPath("/apis/admissionregistration.k8s.io/v1").Do(ctx).Into(apiResourceList)
169 framework.ExpectNoError(err, "fetching /apis/admissionregistration.k8s.io/v1")
170 gomega.Expect(apiResourceList.GroupVersion).To(gomega.Equal(admissionregistrationv1.SchemeGroupVersion.String()), "verifying API group/version in /apis/admissionregistration.k8s.io/v1 discovery document")
171
172 ginkgo.By("finding mutatingwebhookconfigurations and validatingwebhookconfigurations resources in the /apis/admissionregistration.k8s.io/v1 discovery document")
173 var (
174 mutatingWebhookResource *metav1.APIResource
175 validatingWebhookResource *metav1.APIResource
176 )
177 for i := range apiResourceList.APIResources {
178 if apiResourceList.APIResources[i].Name == "mutatingwebhookconfigurations" {
179 mutatingWebhookResource = &apiResourceList.APIResources[i]
180 }
181 if apiResourceList.APIResources[i].Name == "validatingwebhookconfigurations" {
182 validatingWebhookResource = &apiResourceList.APIResources[i]
183 }
184 }
185 gomega.Expect(mutatingWebhookResource).ToNot(gomega.BeNil(), "mutatingwebhookconfigurations resource not found in /apis/admissionregistration.k8s.io/v1 discovery document")
186 gomega.Expect(validatingWebhookResource).ToNot(gomega.BeNil(), "validatingwebhookconfigurations resource not found in /apis/admissionregistration.k8s.io/v1 discovery document")
187 }
188 })
189
190
199 framework.ConformanceIt("should be able to deny pod and configmap creation", func(ctx context.Context) {
200 registerWebhook(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
201 testWebhook(ctx, f)
202 })
203
204
210 framework.ConformanceIt("should be able to deny attaching pod", func(ctx context.Context) {
211 registerWebhookForAttachingPod(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
212 testAttachingPodWebhook(ctx, f)
213 })
214
215
221 framework.ConformanceIt("should be able to deny custom resource creation, update and deletion", func(ctx context.Context) {
222 testcrd, err := crd.CreateTestCRD(f)
223 if err != nil {
224 return
225 }
226 ginkgo.DeferCleanup(testcrd.CleanUp)
227 registerWebhookForCustomResource(ctx, f, markersNamespaceName, f.UniqueName, certCtx, testcrd, servicePort)
228 testCustomResourceWebhook(ctx, f, testcrd.Crd, testcrd.DynamicClients["v1"])
229 testBlockingCustomResourceUpdateDeletion(ctx, f, testcrd.Crd, testcrd.DynamicClients["v1"])
230 })
231
232
238 framework.ConformanceIt("should unconditionally reject operations on fail closed webhook", func(ctx context.Context) {
239 registerFailClosedWebhook(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
240 testFailClosedWebhook(ctx, f)
241 })
242
243
250 framework.ConformanceIt("should mutate configmap", func(ctx context.Context) {
251 registerMutatingWebhookForConfigMap(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
252 testMutatingConfigMapWebhook(ctx, f)
253 })
254
255
261 framework.ConformanceIt("should mutate pod and apply defaults after mutation", func(ctx context.Context) {
262 registerMutatingWebhookForPod(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
263 testMutatingPodWebhook(ctx, f)
264 })
265
266
273 framework.ConformanceIt("should not be able to mutate or prevent deletion of webhook configuration objects", func(ctx context.Context) {
274 registerValidatingWebhookForWebhookConfigurations(ctx, f, markersNamespaceName, f.UniqueName+"blocking", certCtx, servicePort)
275 registerMutatingWebhookForWebhookConfigurations(ctx, f, markersNamespaceName, f.UniqueName+"blocking", certCtx, servicePort)
276 testWebhooksForWebhookConfigurations(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
277 })
278
279
285 framework.ConformanceIt("should mutate custom resource", func(ctx context.Context) {
286 testcrd, err := crd.CreateTestCRD(f)
287 if err != nil {
288 return
289 }
290 ginkgo.DeferCleanup(testcrd.CleanUp)
291 registerMutatingWebhookForCustomResource(ctx, f, markersNamespaceName, f.UniqueName, certCtx, testcrd, servicePort)
292 testMutatingCustomResourceWebhook(ctx, f, testcrd.Crd, testcrd.DynamicClients["v1"], false)
293 })
294
295
301 framework.ConformanceIt("should deny crd creation", func(ctx context.Context) {
302 registerValidatingWebhookForCRD(ctx, f, markersNamespaceName, f.UniqueName, certCtx, servicePort)
303
304 testCRDDenyWebhook(ctx, f)
305 })
306
307
315 framework.ConformanceIt("should mutate custom resource with different stored version", func(ctx context.Context) {
316 testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f)
317 if err != nil {
318 return
319 }
320 ginkgo.DeferCleanup(testcrd.CleanUp)
321 registerMutatingWebhookForCustomResource(ctx, f, markersNamespaceName, f.UniqueName, certCtx, testcrd, servicePort)
322 testMultiVersionCustomResourceWebhook(ctx, f, testcrd)
323 })
324
325
332 framework.ConformanceIt("should mutate custom resource with pruning", func(ctx context.Context) {
333 const prune = true
334 testcrd, err := createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f, func(crd *apiextensionsv1.CustomResourceDefinition) {
335 crd.Spec.PreserveUnknownFields = false
336 for i := range crd.Spec.Versions {
337 crd.Spec.Versions[i].Schema = &apiextensionsv1.CustomResourceValidation{
338 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
339 Type: "object",
340 Properties: map[string]apiextensionsv1.JSONSchemaProps{
341 "data": {
342 Type: "object",
343 Properties: map[string]apiextensionsv1.JSONSchemaProps{
344 "mutation-start": {Type: "string"},
345 "mutation-stage-1": {Type: "string"},
346
347 },
348 },
349 },
350 },
351 }
352 }
353 })
354 if err != nil {
355 return
356 }
357 ginkgo.DeferCleanup(testcrd.CleanUp)
358 registerMutatingWebhookForCustomResource(ctx, f, markersNamespaceName, f.UniqueName, certCtx, testcrd, servicePort)
359 testMutatingCustomResourceWebhook(ctx, f, testcrd.Crd, testcrd.DynamicClients["v1"], prune)
360 })
361
362
371 framework.ConformanceIt("should honor timeout", func(ctx context.Context) {
372 policyFail := admissionregistrationv1.Fail
373 policyIgnore := admissionregistrationv1.Ignore
374
375 ginkgo.By("Setting timeout (1s) shorter than webhook latency (5s)")
376 slowWebhookCleanup := registerSlowWebhook(ctx, f, markersNamespaceName, f.UniqueName, certCtx, &policyFail, pointer.Int32(1), servicePort)
377 testSlowWebhookTimeoutFailEarly(ctx, f)
378 slowWebhookCleanup(ctx)
379
380 ginkgo.By("Having no error when timeout is shorter than webhook latency and failure policy is ignore")
381 slowWebhookCleanup = registerSlowWebhook(ctx, f, markersNamespaceName, f.UniqueName, certCtx, &policyIgnore, pointer.Int32(1), servicePort)
382 testSlowWebhookTimeoutNoError(ctx, f)
383 slowWebhookCleanup(ctx)
384
385 ginkgo.By("Having no error when timeout is longer than webhook latency")
386 slowWebhookCleanup = registerSlowWebhook(ctx, f, markersNamespaceName, f.UniqueName, certCtx, &policyFail, pointer.Int32(10), servicePort)
387 testSlowWebhookTimeoutNoError(ctx, f)
388 slowWebhookCleanup(ctx)
389
390 ginkgo.By("Having no error when timeout is empty (defaulted to 10s in v1)")
391 slowWebhookCleanup = registerSlowWebhook(ctx, f, markersNamespaceName, f.UniqueName, certCtx, &policyFail, nil, servicePort)
392 testSlowWebhookTimeoutNoError(ctx, f)
393 slowWebhookCleanup(ctx)
394 })
395
396
403 framework.ConformanceIt("patching/updating a validating webhook should work", func(ctx context.Context) {
404 client := f.ClientSet
405 admissionClient := client.AdmissionregistrationV1()
406
407 ginkgo.By("Creating a validating webhook configuration")
408 hook, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
409 ObjectMeta: metav1.ObjectMeta{
410 Name: f.UniqueName,
411 },
412 Webhooks: []admissionregistrationv1.ValidatingWebhook{
413 newDenyConfigMapWebhookFixture(f, certCtx, servicePort),
414 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
415 },
416 })
417 framework.ExpectNoError(err, "Creating validating webhook configuration")
418 defer func() {
419 err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, hook.Name, metav1.DeleteOptions{})
420 framework.ExpectNoError(err, "Deleting validating webhook configuration")
421 }()
422
423
424 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
425 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
426
427 ginkgo.By("Creating a configMap that does not comply to the validation webhook rules")
428 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
429 cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f)
430 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
431 if err == nil {
432 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
433 framework.ExpectNoError(err, "Deleting successfully created configMap")
434 return false, nil
435 }
436 if !strings.Contains(err.Error(), "denied") {
437 return false, err
438 }
439 return true, nil
440 })
441
442 ginkgo.By("Updating a validating webhook configuration's rules to not include the create operation")
443 err = retry.RetryOnConflict(retry.DefaultRetry, func() error {
444 h, err := admissionClient.ValidatingWebhookConfigurations().Get(ctx, f.UniqueName, metav1.GetOptions{})
445 framework.ExpectNoError(err, "Getting validating webhook configuration")
446 h.Webhooks[0].Rules[0].Operations = []admissionregistrationv1.OperationType{admissionregistrationv1.Update}
447 _, err = admissionClient.ValidatingWebhookConfigurations().Update(ctx, h, metav1.UpdateOptions{})
448 return err
449 })
450 framework.ExpectNoError(err, "Updating validating webhook configuration")
451
452 ginkgo.By("Creating a configMap that does not comply to the validation webhook rules")
453 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
454 cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f)
455 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
456 if err != nil {
457 if !strings.Contains(err.Error(), "denied") {
458 return false, err
459 }
460 return false, nil
461 }
462 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
463 framework.ExpectNoError(err, "Deleting successfully created configMap")
464 return true, nil
465 })
466 framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be allowed creation since webhook was updated to not validate create", f.Namespace.Name)
467
468 ginkgo.By("Patching a validating webhook configuration's rules to include the create operation")
469 hook, err = admissionClient.ValidatingWebhookConfigurations().Patch(ctx, f.UniqueName,
470 types.JSONPatchType,
471 []byte(`[{"op": "replace", "path": "/webhooks/0/rules/0/operations", "value": ["CREATE"]}]`), metav1.PatchOptions{})
472 framework.ExpectNoError(err, "Patching validating webhook configuration")
473
474 ginkgo.By("Creating a configMap that does not comply to the validation webhook rules")
475 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
476 cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f)
477 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
478 if err == nil {
479 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
480 framework.ExpectNoError(err, "Deleting successfully created configMap")
481 return false, nil
482 }
483 if !strings.Contains(err.Error(), "denied") {
484 return false, err
485 }
486 return true, nil
487 })
488 framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be denied creation by validating webhook", f.Namespace.Name)
489 })
490
491
498 framework.ConformanceIt("patching/updating a mutating webhook should work", func(ctx context.Context) {
499 client := f.ClientSet
500 admissionClient := client.AdmissionregistrationV1()
501
502 ginkgo.By("Creating a mutating webhook configuration")
503 hook, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
504 ObjectMeta: metav1.ObjectMeta{
505 Name: f.UniqueName,
506 },
507 Webhooks: []admissionregistrationv1.MutatingWebhook{
508 newMutateConfigMapWebhookFixture(f, certCtx, 1, servicePort),
509 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
510 },
511 })
512 framework.ExpectNoError(err, "Creating mutating webhook configuration")
513 defer func() {
514 err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(ctx, hook.Name, metav1.DeleteOptions{})
515 framework.ExpectNoError(err, "Deleting mutating webhook configuration")
516 }()
517
518
519 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
520 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
521
522 hook, err = admissionClient.MutatingWebhookConfigurations().Get(ctx, f.UniqueName, metav1.GetOptions{})
523 framework.ExpectNoError(err, "Getting mutating webhook configuration")
524 ginkgo.By("Updating a mutating webhook configuration's rules to not include the create operation")
525 hook.Webhooks[0].Rules[0].Operations = []admissionregistrationv1.OperationType{admissionregistrationv1.Update}
526 hook, err = admissionClient.MutatingWebhookConfigurations().Update(ctx, hook, metav1.UpdateOptions{})
527 framework.ExpectNoError(err, "Updating mutating webhook configuration")
528
529 ginkgo.By("Creating a configMap that should not be mutated")
530 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
531 cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f)
532 created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
533 if err != nil {
534 return false, err
535 }
536 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
537 framework.ExpectNoError(err, "Deleting successfully created configMap")
538 _, ok := created.Data["mutation-stage-1"]
539 return !ok, nil
540 })
541 framework.ExpectNoError(err, "Waiting for configMap in namespace %s this is not mutated", f.Namespace.Name)
542
543 ginkgo.By("Patching a mutating webhook configuration's rules to include the create operation")
544 hook, err = admissionClient.MutatingWebhookConfigurations().Patch(ctx, f.UniqueName,
545 types.JSONPatchType,
546 []byte(`[{"op": "replace", "path": "/webhooks/0/rules/0/operations", "value": ["CREATE"]}]`), metav1.PatchOptions{})
547 framework.ExpectNoError(err, "Patching mutating webhook configuration")
548
549 ginkgo.By("Creating a configMap that should be mutated")
550 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
551 cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f)
552 created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
553 if err != nil {
554 return false, err
555 }
556 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
557 framework.ExpectNoError(err, "Deleting successfully created configMap")
558 _, ok := created.Data["mutation-stage-1"]
559 return ok, nil
560 })
561 framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be mutated", f.Namespace.Name)
562 })
563
564
572 framework.ConformanceIt("listing validating webhooks should work", func(ctx context.Context) {
573 testListSize := 10
574 testUUID := string(uuid.NewUUID())
575
576 for i := 0; i < testListSize; i++ {
577 name := fmt.Sprintf("%s-%d", f.UniqueName, i)
578 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
579 ObjectMeta: metav1.ObjectMeta{
580 Name: name,
581 Labels: map[string]string{"e2e-list-test-uuid": testUUID},
582 },
583 Webhooks: []admissionregistrationv1.ValidatingWebhook{
584 newDenyConfigMapWebhookFixture(f, certCtx, servicePort),
585 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
586 },
587 })
588 framework.ExpectNoError(err, "Creating validating webhook configuration")
589 }
590 selectorListOpts := metav1.ListOptions{LabelSelector: "e2e-list-test-uuid=" + testUUID}
591
592 ginkgo.By("Listing all of the created validation webhooks")
593 list, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().List(ctx, selectorListOpts)
594 framework.ExpectNoError(err, "Listing validating webhook configurations")
595 gomega.Expect(list.Items).To(gomega.HaveLen(testListSize))
596
597
598 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
599 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
600
601 ginkgo.By("Creating a configMap that does not comply to the validation webhook rules")
602 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
603 cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f)
604 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
605 if err == nil {
606 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
607 framework.ExpectNoError(err, "Deleting successfully created configMap")
608 return false, nil
609 }
610 if !strings.Contains(err.Error(), "denied") {
611 return false, err
612 }
613 return true, nil
614 })
615 framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be denied creation by validating webhook", f.Namespace.Name)
616
617 ginkgo.By("Deleting the collection of validation webhooks")
618 err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().DeleteCollection(ctx, metav1.DeleteOptions{}, selectorListOpts)
619 framework.ExpectNoError(err, "Deleting collection of validating webhook configurations")
620
621 ginkgo.By("Creating a configMap that does not comply to the validation webhook rules")
622 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
623 cm := namedNonCompliantConfigMap(string(uuid.NewUUID()), f)
624 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
625 if err != nil {
626 if !strings.Contains(err.Error(), "denied") {
627 return false, err
628 }
629 return false, nil
630 }
631 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
632 framework.ExpectNoError(err, "Deleting successfully created configMap")
633 return true, nil
634 })
635 framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be allowed creation since there are no webhooks", f.Namespace.Name)
636 })
637
638
646 framework.ConformanceIt("listing mutating webhooks should work", func(ctx context.Context) {
647 testListSize := 10
648 testUUID := string(uuid.NewUUID())
649
650 for i := 0; i < testListSize; i++ {
651 name := fmt.Sprintf("%s-%d", f.UniqueName, i)
652 _, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
653 ObjectMeta: metav1.ObjectMeta{
654 Name: name,
655 Labels: map[string]string{"e2e-list-test-uuid": testUUID},
656 },
657 Webhooks: []admissionregistrationv1.MutatingWebhook{
658 newMutateConfigMapWebhookFixture(f, certCtx, 1, servicePort),
659 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
660 },
661 })
662 framework.ExpectNoError(err, "Creating mutating webhook configuration")
663 }
664 selectorListOpts := metav1.ListOptions{LabelSelector: "e2e-list-test-uuid=" + testUUID}
665
666 ginkgo.By("Listing all of the created validation webhooks")
667 list, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().List(ctx, selectorListOpts)
668 framework.ExpectNoError(err, "Listing mutating webhook configurations")
669 gomega.Expect(list.Items).To(gomega.HaveLen(testListSize))
670
671
672 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
673 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
674
675 ginkgo.By("Creating a configMap that should be mutated")
676 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
677 cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f)
678 created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
679 if err != nil {
680 return false, err
681 }
682 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
683 framework.ExpectNoError(err, "Deleting successfully created configMap")
684 _, ok := created.Data["mutation-stage-1"]
685 return ok, nil
686 })
687 framework.ExpectNoError(err, "Waiting for configMap in namespace %s to be mutated", f.Namespace.Name)
688
689 ginkgo.By("Deleting the collection of validation webhooks")
690 err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().DeleteCollection(ctx, metav1.DeleteOptions{}, selectorListOpts)
691 framework.ExpectNoError(err, "Deleting collection of mutating webhook configurations")
692
693 ginkgo.By("Creating a configMap that should not be mutated")
694 err = wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
695 cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f)
696 created, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
697 if err != nil {
698 return false, err
699 }
700 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, cm.Name, metav1.DeleteOptions{})
701 framework.ExpectNoError(err, "Deleting successfully created configMap")
702 _, ok := created.Data["mutation-stage-1"]
703 return !ok, nil
704 })
705 framework.ExpectNoError(err, "Waiting for configMap in namespace %s this is not mutated", f.Namespace.Name)
706 })
707
708
715 framework.ConformanceIt("should be able to create and update validating webhook configurations with match conditions", func(ctx context.Context) {
716 initalMatchConditions := []admissionregistrationv1.MatchCondition{
717 {
718 Name: "expression-1",
719 Expression: "object.metadata.namespace == 'production'",
720 },
721 }
722
723 ginkgo.By("creating a validating webhook with match conditions")
724 validatingWebhookConfiguration := newValidatingWebhookWithMatchConditions(f, servicePort, certCtx, initalMatchConditions)
725
726 _, err := createValidatingWebhookConfiguration(ctx, f, validatingWebhookConfiguration)
727 framework.ExpectNoError(err)
728
729 ginkgo.By("verifying the validating webhook match conditions")
730 validatingWebhookConfiguration, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, f.UniqueName, metav1.GetOptions{})
731 framework.ExpectNoError(err)
732 gomega.Expect(validatingWebhookConfiguration.Webhooks[0].MatchConditions).To(gomega.Equal(initalMatchConditions), "verifying that match conditions are created")
733 defer func() {
734 err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, validatingWebhookConfiguration.Name, metav1.DeleteOptions{})
735 framework.ExpectNoError(err, "deleting mutating webhook configuration")
736 }()
737
738 ginkgo.By("updating the validating webhook match conditions")
739 updatedMatchConditions := []admissionregistrationv1.MatchCondition{
740 {
741 Name: "expression-1",
742 Expression: "object.metadata.namespace == 'production'",
743 },
744 {
745 Name: "expression-2",
746 Expression: "object.metadata.namespace == 'staging'",
747 },
748 }
749 validatingWebhookConfiguration.Webhooks[0].MatchConditions = updatedMatchConditions
750 _, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(ctx, validatingWebhookConfiguration, metav1.UpdateOptions{})
751 framework.ExpectNoError(err)
752
753 ginkgo.By("verifying the validating webhook match conditions")
754 validatingWebhookConfiguration, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(ctx, f.UniqueName, metav1.GetOptions{})
755 framework.ExpectNoError(err)
756 gomega.Expect(validatingWebhookConfiguration.Webhooks[0].MatchConditions).To(gomega.Equal(updatedMatchConditions), "verifying that match conditions are updated")
757 })
758
759
766 framework.ConformanceIt("should be able to create and update mutating webhook configurations with match conditions", func(ctx context.Context) {
767 initalMatchConditions := []admissionregistrationv1.MatchCondition{
768 {
769 Name: "expression-1",
770 Expression: "object.metadata.namespace == 'production'",
771 },
772 }
773
774 ginkgo.By("creating a mutating webhook with match conditions")
775 mutatingWebhookConfiguration := newMutatingWebhookWithMatchConditions(f, servicePort, certCtx, initalMatchConditions)
776
777 _, err := createMutatingWebhookConfiguration(ctx, f, mutatingWebhookConfiguration)
778 framework.ExpectNoError(err)
779
780 ginkgo.By("verifying the mutating webhook match conditions")
781 mutatingWebhookConfiguration, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, f.UniqueName, metav1.GetOptions{})
782 framework.ExpectNoError(err)
783 gomega.Expect(mutatingWebhookConfiguration.Webhooks[0].MatchConditions).To(gomega.Equal(initalMatchConditions), "verifying that match conditions are created")
784 defer func() {
785 err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(ctx, mutatingWebhookConfiguration.Name, metav1.DeleteOptions{})
786 framework.ExpectNoError(err, "deleting mutating webhook configuration")
787 }()
788
789 ginkgo.By("updating the mutating webhook match conditions")
790 updatedMatchConditions := []admissionregistrationv1.MatchCondition{
791 {
792 Name: "expression-1",
793 Expression: "object.metadata.namespace == 'production'",
794 },
795 {
796 Name: "expression-2",
797 Expression: "object.metadata.namespace == 'staging'",
798 },
799 }
800 mutatingWebhookConfiguration.Webhooks[0].MatchConditions = updatedMatchConditions
801 _, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Update(ctx, mutatingWebhookConfiguration, metav1.UpdateOptions{})
802 framework.ExpectNoError(err)
803
804 ginkgo.By("verifying the mutating webhook match conditions")
805 mutatingWebhookConfiguration, err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Get(ctx, f.UniqueName, metav1.GetOptions{})
806 framework.ExpectNoError(err)
807 gomega.Expect(mutatingWebhookConfiguration.Webhooks[0].MatchConditions).To(gomega.Equal(updatedMatchConditions), "verifying that match conditions are updated")
808 })
809
810
817 framework.ConformanceIt("should reject validating webhook configurations with invalid match conditions", func(ctx context.Context) {
818 initalMatchConditions := []admissionregistrationv1.MatchCondition{
819 {
820 Name: "invalid-expression-1",
821 Expression: "... [] bad expression",
822 },
823 }
824
825 ginkgo.By("creating a validating webhook with match conditions")
826 validatingWebhookConfiguration := newValidatingWebhookWithMatchConditions(f, servicePort, certCtx, initalMatchConditions)
827
828 _, err := createValidatingWebhookConfiguration(ctx, f, validatingWebhookConfiguration)
829 gomega.Expect(err).To(gomega.HaveOccurred(), "create validatingwebhookconfiguration should have been denied by the api-server")
830 expectedErrMsg := "compilation failed"
831 gomega.Expect(strings.Contains(err.Error(), expectedErrMsg)).To(gomega.BeTrue())
832 })
833
834
841 framework.ConformanceIt("should reject mutating webhook configurations with invalid match conditions", func(ctx context.Context) {
842 initalMatchConditions := []admissionregistrationv1.MatchCondition{
843 {
844 Name: "invalid-expression-1",
845 Expression: "... [] bad expression",
846 },
847 }
848
849 ginkgo.By("creating a mutating webhook with match conditions")
850 mutatingWebhookConfiguration := newMutatingWebhookWithMatchConditions(f, servicePort, certCtx, initalMatchConditions)
851
852 _, err := createMutatingWebhookConfiguration(ctx, f, mutatingWebhookConfiguration)
853 gomega.Expect(err).To(gomega.HaveOccurred(), "create mutatingwebhookconfiguration should have been denied by the api-server")
854 expectedErrMsg := "compilation failed"
855 gomega.Expect(strings.Contains(err.Error(), expectedErrMsg)).To(gomega.BeTrue())
856 })
857
858
866 framework.ConformanceIt("should mutate everything except 'skip-me' configmaps", func(ctx context.Context) {
867 skipMeMatchConditions := []admissionregistrationv1.MatchCondition{
868 {
869 Name: "skip-me",
870 Expression: "object.metadata.name != 'skip-me'",
871 },
872 }
873
874 ginkgo.By("creating a mutating webhook with match conditions")
875 namespace := f.Namespace.Name
876
877 mutatingWebhook1 := newMutateConfigMapWebhookFixture(f, certCtx, 1, servicePort)
878 mutatingWebhook1.MatchConditions = skipMeMatchConditions
879 created, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
880 ObjectMeta: metav1.ObjectMeta{
881 Name: f.UniqueName,
882 },
883 Webhooks: []admissionregistrationv1.MutatingWebhook{
884 mutatingWebhook1,
885
886 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
887 },
888 })
889 framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", f.UniqueName, namespace)
890 defer func() {
891 err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(ctx, created.Name, metav1.DeleteOptions{})
892 framework.ExpectNoError(err, "deleting mutating webhook configuration")
893 }()
894
895 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
896 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
897 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete), f.UniqueName, metav1.DeleteOptions{})
898
899
900 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
901 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
902
903 ginkgo.By("create the configmap with a random name")
904
905 cm := namedToBeMutatedConfigMap(string(uuid.NewUUID()), f)
906 mutatedCM, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
907 framework.ExpectNoError(err, "creating configMap object")
908
909 ginkgo.By("verify the configmap is mutated")
910 expectedConfigMapData := map[string]string{
911 "mutation-start": "yes",
912 "mutation-stage-1": "yes",
913 }
914 gomega.Expect(reflect.DeepEqual(expectedConfigMapData, mutatedCM.Data)).To(gomega.BeTrue())
915
916 ginkgo.By("create the configmap with 'skip-me' name")
917
918 cm = namedToBeMutatedConfigMap("skip-me", f)
919 skippedCM, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, cm, metav1.CreateOptions{})
920 framework.ExpectNoError(err, "creating configMap object")
921 expectedConfigMapData = map[string]string{
922 "mutation-start": "yes",
923 }
924 gomega.Expect(reflect.DeepEqual(expectedConfigMapData, skippedCM.Data)).To(gomega.BeTrue())
925 })
926 })
927
928 func newValidatingWebhookWithMatchConditions(
929 f *framework.Framework,
930 servicePort int32,
931 certCtx *certContext,
932 matchConditions []admissionregistrationv1.MatchCondition,
933 ) *admissionregistrationv1.ValidatingWebhookConfiguration {
934 sideEffects := admissionregistrationv1.SideEffectClassNone
935 equivalent := admissionregistrationv1.Equivalent
936 return &admissionregistrationv1.ValidatingWebhookConfiguration{
937 ObjectMeta: metav1.ObjectMeta{
938 Name: f.UniqueName,
939 },
940 Webhooks: []admissionregistrationv1.ValidatingWebhook{
941 {
942 Name: "validation-webhook-with-match-conditions.k8s.io",
943 Rules: []admissionregistrationv1.RuleWithOperations{{
944 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
945 Rule: admissionregistrationv1.Rule{
946 APIGroups: []string{""},
947 APIVersions: []string{"v1"},
948 Resources: []string{"configmaps"},
949 },
950 }},
951 ClientConfig: admissionregistrationv1.WebhookClientConfig{
952 Service: &admissionregistrationv1.ServiceReference{
953 Namespace: f.Namespace.Name,
954 Name: serviceName,
955 Path: strPtr("/always-deny"),
956 Port: pointer.Int32(servicePort),
957 },
958 CABundle: certCtx.signingCert,
959 },
960 SideEffects: &sideEffects,
961 MatchPolicy: &equivalent,
962 AdmissionReviewVersions: []string{"v1"},
963
964 NamespaceSelector: &metav1.LabelSelector{
965 MatchLabels: map[string]string{f.UniqueName: "true"},
966 },
967 MatchConditions: matchConditions,
968 },
969 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
970 },
971 }
972 }
973
974 func newMutatingWebhookWithMatchConditions(
975 f *framework.Framework,
976 servicePort int32,
977 certCtx *certContext,
978 matchConditions []admissionregistrationv1.MatchCondition,
979 ) *admissionregistrationv1.MutatingWebhookConfiguration {
980 sideEffects := admissionregistrationv1.SideEffectClassNone
981 return &admissionregistrationv1.MutatingWebhookConfiguration{
982 ObjectMeta: metav1.ObjectMeta{
983 Name: f.UniqueName,
984 },
985 Webhooks: []admissionregistrationv1.MutatingWebhook{
986 {
987 Name: "adding-configmap-data.k8s.io",
988 Rules: []admissionregistrationv1.RuleWithOperations{{
989 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
990 Rule: admissionregistrationv1.Rule{
991 APIGroups: []string{""},
992 APIVersions: []string{"v1"},
993 Resources: []string{"configmaps"},
994 },
995 }},
996 ClientConfig: admissionregistrationv1.WebhookClientConfig{
997 Service: &admissionregistrationv1.ServiceReference{
998 Namespace: f.Namespace.Name,
999 Name: serviceName,
1000 Path: strPtr("/mutating-configmaps"),
1001 Port: pointer.Int32(servicePort),
1002 },
1003 CABundle: certCtx.signingCert,
1004 },
1005 SideEffects: &sideEffects,
1006 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1007
1008 NamespaceSelector: &metav1.LabelSelector{
1009 MatchLabels: map[string]string{f.UniqueName: "true"},
1010 },
1011 MatchConditions: matchConditions,
1012 },
1013 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
1014 },
1015 }
1016 }
1017
1018 func createAuthReaderRoleBinding(ctx context.Context, f *framework.Framework, namespace string) {
1019 ginkgo.By("Create role binding to let webhook read extension-apiserver-authentication")
1020 client := f.ClientSet
1021
1022 _, err := client.RbacV1().RoleBindings("kube-system").Create(ctx, &rbacv1.RoleBinding{
1023 ObjectMeta: metav1.ObjectMeta{
1024 Name: roleBindingName,
1025 Annotations: map[string]string{
1026 rbacv1.AutoUpdateAnnotationKey: "true",
1027 },
1028 },
1029 RoleRef: rbacv1.RoleRef{
1030 APIGroup: "",
1031 Kind: "Role",
1032 Name: "extension-apiserver-authentication-reader",
1033 },
1034
1035 Subjects: []rbacv1.Subject{
1036 {
1037 Kind: "ServiceAccount",
1038 Name: "default",
1039 Namespace: namespace,
1040 },
1041 },
1042 }, metav1.CreateOptions{})
1043 if err != nil && apierrors.IsAlreadyExists(err) {
1044 framework.Logf("role binding %s already exists", roleBindingName)
1045 } else {
1046 framework.ExpectNoError(err, "creating role binding %s:webhook to access configMap", namespace)
1047 }
1048 }
1049
1050 func deployWebhookAndService(ctx context.Context, f *framework.Framework, image string, certCtx *certContext, servicePort int32, containerPort int32) {
1051 ginkgo.By("Deploying the webhook pod")
1052 client := f.ClientSet
1053
1054
1055 secret := &v1.Secret{
1056 ObjectMeta: metav1.ObjectMeta{
1057 Name: secretName,
1058 },
1059 Type: v1.SecretTypeOpaque,
1060 Data: map[string][]byte{
1061 "tls.crt": certCtx.cert,
1062 "tls.key": certCtx.key,
1063 },
1064 }
1065 namespace := f.Namespace.Name
1066 _, err := client.CoreV1().Secrets(namespace).Create(ctx, secret, metav1.CreateOptions{})
1067 framework.ExpectNoError(err, "creating secret %q in namespace %q", secretName, namespace)
1068
1069
1070 podLabels := map[string]string{"app": "sample-webhook", "webhook": "true"}
1071 replicas := int32(1)
1072 mounts := []v1.VolumeMount{
1073 {
1074 Name: "webhook-certs",
1075 ReadOnly: true,
1076 MountPath: "/webhook.local.config/certificates",
1077 },
1078 }
1079 volumes := []v1.Volume{
1080 {
1081 Name: "webhook-certs",
1082 VolumeSource: v1.VolumeSource{
1083 Secret: &v1.SecretVolumeSource{SecretName: secretName},
1084 },
1085 },
1086 }
1087 containers := []v1.Container{
1088 {
1089 Name: "sample-webhook",
1090 VolumeMounts: mounts,
1091 Args: []string{
1092 "webhook",
1093 "--tls-cert-file=/webhook.local.config/certificates/tls.crt",
1094 "--tls-private-key-file=/webhook.local.config/certificates/tls.key",
1095 "-v=4",
1096
1097 fmt.Sprintf("--port=%d", containerPort),
1098 },
1099 ReadinessProbe: &v1.Probe{
1100 ProbeHandler: v1.ProbeHandler{
1101 HTTPGet: &v1.HTTPGetAction{
1102 Scheme: v1.URISchemeHTTPS,
1103 Port: intstr.FromInt32(containerPort),
1104 Path: "/readyz",
1105 },
1106 },
1107 PeriodSeconds: 1,
1108 SuccessThreshold: 1,
1109 FailureThreshold: 30,
1110 },
1111 Image: image,
1112 Ports: []v1.ContainerPort{{ContainerPort: containerPort}},
1113 },
1114 }
1115 d := e2edeployment.NewDeployment(deploymentName, replicas, podLabels, "", "", appsv1.RollingUpdateDeploymentStrategyType)
1116 d.Spec.Template.Spec.Containers = containers
1117 d.Spec.Template.Spec.Volumes = volumes
1118
1119 deployment, err := client.AppsV1().Deployments(namespace).Create(ctx, d, metav1.CreateOptions{})
1120 framework.ExpectNoError(err, "creating deployment %s in namespace %s", deploymentName, namespace)
1121 ginkgo.By("Wait for the deployment to be ready")
1122 err = e2edeployment.WaitForDeploymentRevisionAndImage(client, namespace, deploymentName, "1", image)
1123 framework.ExpectNoError(err, "waiting for the deployment of image %s in %s in %s to complete", image, deploymentName, namespace)
1124 err = e2edeployment.WaitForDeploymentComplete(client, deployment)
1125 framework.ExpectNoError(err, "waiting for the deployment status valid", image, deploymentName, namespace)
1126
1127 ginkgo.By("Deploying the webhook service")
1128
1129 serviceLabels := map[string]string{"webhook": "true"}
1130 service := &v1.Service{
1131 ObjectMeta: metav1.ObjectMeta{
1132 Namespace: namespace,
1133 Name: serviceName,
1134 Labels: map[string]string{"test": "webhook"},
1135 },
1136 Spec: v1.ServiceSpec{
1137 Selector: serviceLabels,
1138 Ports: []v1.ServicePort{
1139 {
1140 Protocol: v1.ProtocolTCP,
1141 Port: servicePort,
1142 TargetPort: intstr.FromInt32(containerPort),
1143 },
1144 },
1145 },
1146 }
1147 _, err = client.CoreV1().Services(namespace).Create(ctx, service, metav1.CreateOptions{})
1148 framework.ExpectNoError(err, "creating service %s in namespace %s", serviceName, namespace)
1149
1150 ginkgo.By("Verifying the service has paired with the endpoint")
1151 err = framework.WaitForServiceEndpointsNum(ctx, client, namespace, serviceName, 1, 1*time.Second, 30*time.Second)
1152 framework.ExpectNoError(err, "waiting for service %s/%s have %d endpoint", namespace, serviceName, 1)
1153 }
1154
1155 func strPtr(s string) *string { return &s }
1156
1157 func registerWebhook(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1158 client := f.ClientSet
1159 ginkgo.By("Registering the webhook via the AdmissionRegistration API")
1160
1161 namespace := f.Namespace.Name
1162
1163 failOpenHook := failingWebhook(namespace, "fail-open.k8s.io", servicePort)
1164 policyIgnore := admissionregistrationv1.Ignore
1165 failOpenHook.FailurePolicy = &policyIgnore
1166 failOpenHook.NamespaceSelector = &metav1.LabelSelector{
1167 MatchLabels: map[string]string{f.UniqueName: "true"},
1168 }
1169
1170 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
1171 ObjectMeta: metav1.ObjectMeta{
1172 Name: configName,
1173 },
1174 Webhooks: []admissionregistrationv1.ValidatingWebhook{
1175 newDenyPodWebhookFixture(f, certCtx, servicePort),
1176 newDenyConfigMapWebhookFixture(f, certCtx, servicePort),
1177
1178
1179 failOpenHook,
1180
1181
1182 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
1183 },
1184 })
1185 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1186
1187 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1188 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1189
1190 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1191 }
1192
1193 func registerWebhookForAttachingPod(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1194 client := f.ClientSet
1195 ginkgo.By("Registering the webhook via the AdmissionRegistration API")
1196
1197 namespace := f.Namespace.Name
1198 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1199
1200 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
1201 ObjectMeta: metav1.ObjectMeta{
1202 Name: configName,
1203 },
1204 Webhooks: []admissionregistrationv1.ValidatingWebhook{
1205 {
1206 Name: "deny-attaching-pod.k8s.io",
1207 Rules: []admissionregistrationv1.RuleWithOperations{{
1208 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Connect},
1209 Rule: admissionregistrationv1.Rule{
1210 APIGroups: []string{""},
1211 APIVersions: []string{"v1"},
1212 Resources: []string{"pods/attach"},
1213 },
1214 }},
1215 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1216 Service: &admissionregistrationv1.ServiceReference{
1217 Namespace: namespace,
1218 Name: serviceName,
1219 Path: strPtr("/pods/attach"),
1220 Port: pointer.Int32(servicePort),
1221 },
1222 CABundle: certCtx.signingCert,
1223 },
1224 SideEffects: &sideEffectsNone,
1225 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1226
1227 NamespaceSelector: &metav1.LabelSelector{
1228 MatchLabels: map[string]string{f.UniqueName: "true"},
1229 },
1230 },
1231
1232 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
1233 },
1234 })
1235 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1236
1237 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1238 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1239
1240 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1241 }
1242
1243 func registerMutatingWebhookForConfigMap(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1244 client := f.ClientSet
1245 ginkgo.By("Registering the mutating configmap webhook via the AdmissionRegistration API")
1246
1247 namespace := f.Namespace.Name
1248
1249 _, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
1250 ObjectMeta: metav1.ObjectMeta{
1251 Name: configName,
1252 },
1253 Webhooks: []admissionregistrationv1.MutatingWebhook{
1254 newMutateConfigMapWebhookFixture(f, certCtx, 1, servicePort),
1255 newMutateConfigMapWebhookFixture(f, certCtx, 2, servicePort),
1256
1257 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
1258 },
1259 })
1260 framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", configName, namespace)
1261
1262 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1263 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1264 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1265 }
1266
1267 func testMutatingConfigMapWebhook(ctx context.Context, f *framework.Framework) {
1268 ginkgo.By("create a configmap that should be updated by the webhook")
1269 client := f.ClientSet
1270 configMap := toBeMutatedConfigMap(f)
1271 mutatedConfigMap, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configMap, metav1.CreateOptions{})
1272 framework.ExpectNoError(err)
1273 expectedConfigMapData := map[string]string{
1274 "mutation-start": "yes",
1275 "mutation-stage-1": "yes",
1276 "mutation-stage-2": "yes",
1277 }
1278 if !reflect.DeepEqual(expectedConfigMapData, mutatedConfigMap.Data) {
1279 framework.Failf("\nexpected %#v\n, got %#v\n", expectedConfigMapData, mutatedConfigMap.Data)
1280 }
1281 }
1282
1283 func registerMutatingWebhookForPod(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1284 client := f.ClientSet
1285 ginkgo.By("Registering the mutating pod webhook via the AdmissionRegistration API")
1286
1287 namespace := f.Namespace.Name
1288 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1289
1290 _, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
1291 ObjectMeta: metav1.ObjectMeta{
1292 Name: configName,
1293 },
1294 Webhooks: []admissionregistrationv1.MutatingWebhook{
1295 {
1296 Name: "adding-init-container.k8s.io",
1297 Rules: []admissionregistrationv1.RuleWithOperations{{
1298 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
1299 Rule: admissionregistrationv1.Rule{
1300 APIGroups: []string{""},
1301 APIVersions: []string{"v1"},
1302 Resources: []string{"pods"},
1303 },
1304 }},
1305 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1306 Service: &admissionregistrationv1.ServiceReference{
1307 Namespace: namespace,
1308 Name: serviceName,
1309 Path: strPtr("/mutating-pods"),
1310 Port: pointer.Int32(servicePort),
1311 },
1312 CABundle: certCtx.signingCert,
1313 },
1314 SideEffects: &sideEffectsNone,
1315 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1316
1317 NamespaceSelector: &metav1.LabelSelector{
1318 MatchLabels: map[string]string{f.UniqueName: "true"},
1319 },
1320 },
1321
1322 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
1323 },
1324 })
1325 framework.ExpectNoError(err, "registering mutating webhook config %s with namespace %s", configName, namespace)
1326
1327 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1328 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1329
1330 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1331 }
1332
1333 func testMutatingPodWebhook(ctx context.Context, f *framework.Framework) {
1334 ginkgo.By("create a pod that should be updated by the webhook")
1335 client := f.ClientSet
1336 pod := toBeMutatedPod(f)
1337 mutatedPod, err := client.CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
1338 framework.ExpectNoError(err)
1339 if len(mutatedPod.Spec.InitContainers) != 1 {
1340 framework.Failf("expect pod to have 1 init container, got %#v", mutatedPod.Spec.InitContainers)
1341 }
1342 if got, expected := mutatedPod.Spec.InitContainers[0].Name, "webhook-added-init-container"; got != expected {
1343 framework.Failf("expect the init container name to be %q, got %q", expected, got)
1344 }
1345 if got, expected := mutatedPod.Spec.InitContainers[0].TerminationMessagePolicy, v1.TerminationMessageReadFile; got != expected {
1346 framework.Failf("expect the init terminationMessagePolicy to be default to %q, got %q", expected, got)
1347 }
1348 }
1349
1350 func toBeMutatedPod(f *framework.Framework) *v1.Pod {
1351 return &v1.Pod{
1352 ObjectMeta: metav1.ObjectMeta{
1353 Name: "webhook-to-be-mutated",
1354 },
1355 Spec: v1.PodSpec{
1356 Containers: []v1.Container{
1357 {
1358 Name: "example",
1359 Image: imageutils.GetPauseImageName(),
1360 },
1361 },
1362 },
1363 }
1364 }
1365
1366 func testWebhook(ctx context.Context, f *framework.Framework) {
1367 ginkgo.By("create a pod that should be denied by the webhook")
1368 client := f.ClientSet
1369
1370 pod := nonCompliantPod(f)
1371 _, err := client.CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
1372 gomega.Expect(err).To(gomega.HaveOccurred(), "create pod %s in namespace %s should have been denied by webhook", pod.Name, f.Namespace.Name)
1373 expectedErrMsg1 := "the pod contains unwanted container name"
1374 if !strings.Contains(err.Error(), expectedErrMsg1) {
1375 framework.Failf("expect error contains %q, got %q", expectedErrMsg1, err.Error())
1376 }
1377 expectedErrMsg2 := "the pod contains unwanted label"
1378 if !strings.Contains(err.Error(), expectedErrMsg2) {
1379 framework.Failf("expect error contains %q, got %q", expectedErrMsg2, err.Error())
1380 }
1381
1382 ginkgo.By("create a pod that causes the webhook to hang")
1383 client = f.ClientSet
1384
1385 pod = hangingPod(f)
1386 _, err = client.CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
1387 gomega.Expect(err).To(gomega.HaveOccurred(), "create pod %s in namespace %s should have caused webhook to hang", pod.Name, f.Namespace.Name)
1388
1389 if !strings.Contains(err.Error(), "webhook") {
1390 framework.Failf("expect error %q, got %q", "webhook", err.Error())
1391 }
1392
1393 if !strings.Contains(err.Error(), "deadline") {
1394 framework.Failf("expect error %q, got %q", "deadline", err.Error())
1395 }
1396
1397 if _, err := client.CoreV1().Pods(f.Namespace.Name).Get(ctx, pod.Name, metav1.GetOptions{}); !apierrors.IsNotFound(err) {
1398 framework.Failf("expect notfound error looking for rejected pod, got %v", err)
1399 }
1400
1401 ginkgo.By("create a configmap that should be denied by the webhook")
1402
1403 configmap := nonCompliantConfigMap(f)
1404 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configmap, metav1.CreateOptions{})
1405 gomega.Expect(err).To(gomega.HaveOccurred(), "create configmap %s in namespace %s should have been denied by the webhook", configmap.Name, f.Namespace.Name)
1406 expectedErrMsg := "the configmap contains unwanted key and value"
1407 if !strings.Contains(err.Error(), expectedErrMsg) {
1408 framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
1409 }
1410
1411 ginkgo.By("create a configmap that should be admitted by the webhook")
1412
1413 configmap = &v1.ConfigMap{
1414 ObjectMeta: metav1.ObjectMeta{
1415 Name: allowedConfigMapName,
1416 },
1417 Data: map[string]string{
1418 "admit": "this",
1419 },
1420 }
1421 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, configmap, metav1.CreateOptions{})
1422 framework.ExpectNoError(err, "failed to create configmap %s in namespace: %s", configmap.Name, f.Namespace.Name)
1423
1424 ginkgo.By("update (PUT) the admitted configmap to a non-compliant one should be rejected by the webhook")
1425 toNonCompliantFn := func(cm *v1.ConfigMap) {
1426 if cm.Data == nil {
1427 cm.Data = map[string]string{}
1428 }
1429 cm.Data["webhook-e2e-test"] = "webhook-disallow"
1430 }
1431 _, err = updateConfigMap(ctx, client, f.Namespace.Name, allowedConfigMapName, toNonCompliantFn)
1432 gomega.Expect(err).To(gomega.HaveOccurred(), "update (PUT) admitted configmap %s in namespace %s to a non-compliant one should be rejected by webhook", allowedConfigMapName, f.Namespace.Name)
1433 if !strings.Contains(err.Error(), expectedErrMsg) {
1434 framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
1435 }
1436
1437 ginkgo.By("update (PATCH) the admitted configmap to a non-compliant one should be rejected by the webhook")
1438 patch := nonCompliantConfigMapPatch()
1439 _, err = client.CoreV1().ConfigMaps(f.Namespace.Name).Patch(ctx, allowedConfigMapName, types.StrategicMergePatchType, []byte(patch), metav1.PatchOptions{})
1440 gomega.Expect(err).To(gomega.HaveOccurred(), "update admitted configmap %s in namespace %s by strategic merge patch to a non-compliant one should be rejected by webhook. Patch: %+v", allowedConfigMapName, f.Namespace.Name, patch)
1441 if !strings.Contains(err.Error(), expectedErrMsg) {
1442 framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
1443 }
1444
1445 ginkgo.By("create a namespace that bypass the webhook")
1446
1447 skipNamespace, err := f.CreateNamespace(ctx, skipNamespaceBaseName, map[string]string{
1448 skipNamespaceLabelKey: skipNamespaceLabelValue,
1449 f.UniqueName: "true",
1450 })
1451 framework.ExpectNoError(err, "creating namespace %q", skipNamespaceBaseName)
1452 skipNamespaceName := skipNamespace.Name
1453
1454 ginkgo.By("create a configmap that violates the webhook policy but is in a whitelisted namespace")
1455 configmap = nonCompliantConfigMap(f)
1456 _, err = client.CoreV1().ConfigMaps(skipNamespaceName).Create(ctx, configmap, metav1.CreateOptions{})
1457 framework.ExpectNoError(err, "failed to create configmap %s in namespace: %s", configmap.Name, skipNamespaceName)
1458 }
1459
1460 func testAttachingPodWebhook(ctx context.Context, f *framework.Framework) {
1461 ginkgo.By("create a pod")
1462 client := f.ClientSet
1463 pod := toBeAttachedPod(f)
1464 _, err := client.CoreV1().Pods(f.Namespace.Name).Create(ctx, pod, metav1.CreateOptions{})
1465 framework.ExpectNoError(err, "failed to create pod %s in namespace: %s", pod.Name, f.Namespace.Name)
1466 err = e2epod.WaitForPodNameRunningInNamespace(ctx, client, pod.Name, f.Namespace.Name)
1467 framework.ExpectNoError(err, "error while waiting for pod %s to go to Running phase in namespace: %s", pod.Name, f.Namespace.Name)
1468
1469 ginkgo.By("'kubectl attach' the pod, should be denied by the webhook")
1470 timer := time.NewTimer(30 * time.Second)
1471 defer timer.Stop()
1472 _, err = e2ekubectl.NewKubectlCommand(f.Namespace.Name, "attach", fmt.Sprintf("--namespace=%v", f.Namespace.Name), pod.Name, "-i", "-c=container1").WithTimeout(timer.C).Exec()
1473 gomega.Expect(err).To(gomega.HaveOccurred(), "'kubectl attach' the pod, should be denied by the webhook")
1474 if e, a := "attaching to pod 'to-be-attached-pod' is not allowed", err.Error(); !strings.Contains(a, e) {
1475 framework.Failf("unexpected 'kubectl attach' error message. expected to contain %q, got %q", e, a)
1476 }
1477 }
1478
1479
1480
1481 func failingWebhook(namespace, name string, servicePort int32) admissionregistrationv1.ValidatingWebhook {
1482 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1483
1484 return admissionregistrationv1.ValidatingWebhook{
1485 Name: name,
1486 Rules: []admissionregistrationv1.RuleWithOperations{{
1487 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
1488 Rule: admissionregistrationv1.Rule{
1489 APIGroups: []string{""},
1490 APIVersions: []string{"v1"},
1491 Resources: []string{"configmaps"},
1492 },
1493 }},
1494 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1495 Service: &admissionregistrationv1.ServiceReference{
1496 Namespace: namespace,
1497 Name: serviceName,
1498 Path: strPtr("/configmaps"),
1499 Port: pointer.Int32(servicePort),
1500 },
1501
1502 CABundle: nil,
1503 },
1504 SideEffects: &sideEffectsNone,
1505 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1506 }
1507 }
1508
1509 func registerFailClosedWebhook(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1510 ginkgo.By("Registering a webhook that server cannot talk to, with fail closed policy, via the AdmissionRegistration API")
1511
1512 namespace := f.Namespace.Name
1513
1514 policyFail := admissionregistrationv1.Fail
1515 hook := failingWebhook(namespace, "fail-closed.k8s.io", servicePort)
1516 hook.FailurePolicy = &policyFail
1517 hook.NamespaceSelector = &metav1.LabelSelector{
1518 MatchLabels: map[string]string{f.UniqueName: "true"},
1519 MatchExpressions: []metav1.LabelSelectorRequirement{
1520 {
1521 Key: failNamespaceLabelKey,
1522 Operator: metav1.LabelSelectorOpIn,
1523 Values: []string{failNamespaceLabelValue},
1524 },
1525 },
1526 }
1527
1528 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
1529 ObjectMeta: metav1.ObjectMeta{
1530 Name: configName,
1531 },
1532 Webhooks: []admissionregistrationv1.ValidatingWebhook{
1533
1534
1535 hook,
1536
1537 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
1538 },
1539 })
1540 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1541
1542 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1543 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1544 ginkgo.DeferCleanup(framework.IgnoreNotFound(f.ClientSet.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1545 }
1546
1547 func testFailClosedWebhook(ctx context.Context, f *framework.Framework) {
1548 client := f.ClientSet
1549 ginkgo.By("create a namespace for the webhook")
1550
1551 failNamespace, err := f.CreateNamespace(ctx, failNamespaceBaseName, map[string]string{
1552 failNamespaceLabelKey: failNamespaceLabelValue,
1553 f.UniqueName: "true",
1554 })
1555 framework.ExpectNoError(err, "creating namespace %q", failNamespaceBaseName)
1556 failNamespaceName := failNamespace.Name
1557
1558 ginkgo.By("create a configmap should be unconditionally rejected by the webhook")
1559 configmap := &v1.ConfigMap{
1560 ObjectMeta: metav1.ObjectMeta{
1561 Name: "foo",
1562 },
1563 }
1564 _, err = client.CoreV1().ConfigMaps(failNamespaceName).Create(ctx, configmap, metav1.CreateOptions{})
1565 gomega.Expect(err).To(gomega.HaveOccurred(), "create configmap in namespace: %s should be unconditionally rejected by the webhook", failNamespaceName)
1566 if !apierrors.IsInternalError(err) {
1567 framework.Failf("expect an internal error, got %#v", err)
1568 }
1569 }
1570
1571 func registerValidatingWebhookForWebhookConfigurations(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1572 var err error
1573 client := f.ClientSet
1574 ginkgo.By("Registering a validating webhook on ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects, via the AdmissionRegistration API")
1575
1576 namespace := f.Namespace.Name
1577 failurePolicy := admissionregistrationv1.Fail
1578 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1579
1580
1581
1582
1583 _, err = createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
1584 ObjectMeta: metav1.ObjectMeta{
1585 Name: configName,
1586 },
1587 Webhooks: []admissionregistrationv1.ValidatingWebhook{
1588 {
1589 Name: "deny-webhook-configuration-deletions.k8s.io",
1590 Rules: []admissionregistrationv1.RuleWithOperations{{
1591 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Delete},
1592 Rule: admissionregistrationv1.Rule{
1593 APIGroups: []string{"admissionregistration.k8s.io"},
1594 APIVersions: []string{"*"},
1595 Resources: []string{
1596 "validatingwebhookconfigurations",
1597 "mutatingwebhookconfigurations",
1598 },
1599 },
1600 }},
1601 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1602 Service: &admissionregistrationv1.ServiceReference{
1603 Namespace: namespace,
1604 Name: serviceName,
1605 Path: strPtr("/always-deny"),
1606 Port: pointer.Int32(servicePort),
1607 },
1608 CABundle: certCtx.signingCert,
1609 },
1610 SideEffects: &sideEffectsNone,
1611 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1612 FailurePolicy: &failurePolicy,
1613
1614 NamespaceSelector: &metav1.LabelSelector{
1615 MatchLabels: map[string]string{f.UniqueName: "true"},
1616 },
1617 },
1618
1619 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
1620 },
1621 })
1622 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1623
1624 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1625 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1626 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1627 }
1628
1629 func registerMutatingWebhookForWebhookConfigurations(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1630 var err error
1631 client := f.ClientSet
1632 ginkgo.By("Registering a mutating webhook on ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects, via the AdmissionRegistration API")
1633
1634 namespace := f.Namespace.Name
1635 failurePolicy := admissionregistrationv1.Fail
1636 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1637
1638
1639
1640
1641 _, err = createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
1642 ObjectMeta: metav1.ObjectMeta{
1643 Name: configName,
1644 },
1645 Webhooks: []admissionregistrationv1.MutatingWebhook{
1646 {
1647 Name: "add-label-to-webhook-configurations.k8s.io",
1648 Rules: []admissionregistrationv1.RuleWithOperations{{
1649 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
1650 Rule: admissionregistrationv1.Rule{
1651 APIGroups: []string{"admissionregistration.k8s.io"},
1652 APIVersions: []string{"*"},
1653 Resources: []string{
1654 "validatingwebhookconfigurations",
1655 "mutatingwebhookconfigurations",
1656 },
1657 },
1658 }},
1659 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1660 Service: &admissionregistrationv1.ServiceReference{
1661 Namespace: namespace,
1662 Name: serviceName,
1663 Path: strPtr("/add-label"),
1664 Port: pointer.Int32(servicePort),
1665 },
1666 CABundle: certCtx.signingCert,
1667 },
1668 SideEffects: &sideEffectsNone,
1669 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1670 FailurePolicy: &failurePolicy,
1671
1672 NamespaceSelector: &metav1.LabelSelector{
1673 MatchLabels: map[string]string{f.UniqueName: "true"},
1674 },
1675 },
1676
1677 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
1678 },
1679 })
1680 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1681
1682 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1683 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1684 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1685 }
1686
1687
1688
1689
1690 func testWebhooksForWebhookConfigurations(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
1691 var err error
1692 client := f.ClientSet
1693 ginkgo.By("Creating a dummy validating-webhook-configuration object")
1694
1695 namespace := f.Namespace.Name
1696 failurePolicy := admissionregistrationv1.Ignore
1697 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1698
1699 mutatedValidatingWebhookConfiguration, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
1700 ObjectMeta: metav1.ObjectMeta{
1701 Name: configName,
1702 },
1703 Webhooks: []admissionregistrationv1.ValidatingWebhook{
1704 {
1705 Name: "dummy-validating-webhook.k8s.io",
1706 Rules: []admissionregistrationv1.RuleWithOperations{{
1707 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
1708
1709 Rule: admissionregistrationv1.Rule{
1710 APIGroups: []string{""},
1711 APIVersions: []string{"v1"},
1712 Resources: []string{"invalid"},
1713 },
1714 }},
1715 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1716 Service: &admissionregistrationv1.ServiceReference{
1717 Namespace: namespace,
1718 Name: serviceName,
1719
1720
1721
1722
1723 Path: strPtr(""),
1724 Port: pointer.Int32(servicePort),
1725 },
1726 CABundle: nil,
1727 },
1728 SideEffects: &sideEffectsNone,
1729 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1730 FailurePolicy: &failurePolicy,
1731
1732 NamespaceSelector: &metav1.LabelSelector{
1733 MatchLabels: map[string]string{f.UniqueName: "true"},
1734 },
1735 },
1736
1737 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
1738 },
1739 })
1740 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1741 if mutatedValidatingWebhookConfiguration.ObjectMeta.Labels != nil && mutatedValidatingWebhookConfiguration.ObjectMeta.Labels[addedLabelKey] == addedLabelValue {
1742 framework.Failf("expected %s not to be mutated by mutating webhooks but it was", configName)
1743 }
1744
1745 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1746 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1747
1748 ginkgo.By("Deleting the validating-webhook-configuration, which should be possible to remove")
1749
1750 err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, configName, metav1.DeleteOptions{})
1751 framework.ExpectNoError(err, "deleting webhook config %s with namespace %s", configName, namespace)
1752
1753 ginkgo.By("Creating a dummy mutating-webhook-configuration object")
1754
1755 mutatedMutatingWebhookConfiguration, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
1756 ObjectMeta: metav1.ObjectMeta{
1757 Name: configName,
1758 },
1759 Webhooks: []admissionregistrationv1.MutatingWebhook{
1760 {
1761 Name: "dummy-mutating-webhook.k8s.io",
1762 Rules: []admissionregistrationv1.RuleWithOperations{{
1763 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
1764
1765 Rule: admissionregistrationv1.Rule{
1766 APIGroups: []string{""},
1767 APIVersions: []string{"v1"},
1768 Resources: []string{"invalid"},
1769 },
1770 }},
1771 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1772 Service: &admissionregistrationv1.ServiceReference{
1773 Namespace: namespace,
1774 Name: serviceName,
1775
1776
1777
1778
1779 Path: strPtr(""),
1780 Port: pointer.Int32(servicePort),
1781 },
1782 CABundle: nil,
1783 },
1784 SideEffects: &sideEffectsNone,
1785 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1786 FailurePolicy: &failurePolicy,
1787
1788 NamespaceSelector: &metav1.LabelSelector{
1789 MatchLabels: map[string]string{f.UniqueName: "true"},
1790 },
1791 },
1792
1793 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
1794 },
1795 })
1796 framework.ExpectNoError(err, "registering webhook config %s with namespace %s", configName, namespace)
1797 if mutatedMutatingWebhookConfiguration.ObjectMeta.Labels != nil && mutatedMutatingWebhookConfiguration.ObjectMeta.Labels[addedLabelKey] == addedLabelValue {
1798 framework.Failf("expected %s not to be mutated by mutating webhooks but it was", configName)
1799 }
1800
1801 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1802 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1803
1804 ginkgo.By("Deleting the mutating-webhook-configuration, which should be possible to remove")
1805
1806 err = client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(ctx, configName, metav1.DeleteOptions{})
1807 framework.ExpectNoError(err, "deleting webhook config %s with namespace %s", configName, namespace)
1808 }
1809
1810 func nonCompliantPod(f *framework.Framework) *v1.Pod {
1811 return &v1.Pod{
1812 ObjectMeta: metav1.ObjectMeta{
1813 Name: disallowedPodName,
1814 Labels: map[string]string{
1815 "webhook-e2e-test": "webhook-disallow",
1816 },
1817 },
1818 Spec: v1.PodSpec{
1819 Containers: []v1.Container{
1820 {
1821 Name: "webhook-disallow",
1822 Image: imageutils.GetPauseImageName(),
1823 },
1824 },
1825 },
1826 }
1827 }
1828
1829 func hangingPod(f *framework.Framework) *v1.Pod {
1830 return &v1.Pod{
1831 ObjectMeta: metav1.ObjectMeta{
1832 Name: hangingPodName,
1833 Labels: map[string]string{
1834 "webhook-e2e-test": "wait-forever",
1835 },
1836 },
1837 Spec: v1.PodSpec{
1838 Containers: []v1.Container{
1839 {
1840 Name: "wait-forever",
1841 Image: imageutils.GetPauseImageName(),
1842 },
1843 },
1844 },
1845 }
1846 }
1847
1848 func toBeAttachedPod(f *framework.Framework) *v1.Pod {
1849 return &v1.Pod{
1850 ObjectMeta: metav1.ObjectMeta{
1851 Name: toBeAttachedPodName,
1852 },
1853 Spec: v1.PodSpec{
1854 Containers: []v1.Container{
1855 {
1856 Name: "container1",
1857 Image: imageutils.GetPauseImageName(),
1858 },
1859 },
1860 },
1861 }
1862 }
1863
1864 func nonCompliantConfigMap(f *framework.Framework) *v1.ConfigMap {
1865 return namedNonCompliantConfigMap(disallowedConfigMapName, f)
1866 }
1867
1868 func namedNonCompliantConfigMap(name string, f *framework.Framework) *v1.ConfigMap {
1869 return &v1.ConfigMap{
1870 ObjectMeta: metav1.ObjectMeta{
1871 Name: name,
1872 },
1873 Data: map[string]string{
1874 "webhook-e2e-test": "webhook-disallow",
1875 },
1876 }
1877 }
1878
1879 func toBeMutatedConfigMap(f *framework.Framework) *v1.ConfigMap {
1880 return namedToBeMutatedConfigMap("to-be-mutated", f)
1881 }
1882
1883 func namedToBeMutatedConfigMap(name string, f *framework.Framework) *v1.ConfigMap {
1884 return &v1.ConfigMap{
1885 ObjectMeta: metav1.ObjectMeta{
1886 Name: name,
1887 },
1888 Data: map[string]string{
1889 "mutation-start": "yes",
1890 },
1891 }
1892 }
1893
1894 func nonCompliantConfigMapPatch() string {
1895 return fmt.Sprint(`{"data":{"webhook-e2e-test":"webhook-disallow"}}`)
1896 }
1897
1898 type updateConfigMapFn func(cm *v1.ConfigMap)
1899
1900 func updateConfigMap(ctx context.Context, c clientset.Interface, ns, name string, update updateConfigMapFn) (*v1.ConfigMap, error) {
1901 var cm *v1.ConfigMap
1902 pollErr := wait.PollImmediate(2*time.Second, 1*time.Minute, func() (bool, error) {
1903 var err error
1904 if cm, err = c.CoreV1().ConfigMaps(ns).Get(ctx, name, metav1.GetOptions{}); err != nil {
1905 return false, err
1906 }
1907 update(cm)
1908 if cm, err = c.CoreV1().ConfigMaps(ns).Update(ctx, cm, metav1.UpdateOptions{}); err == nil {
1909 return true, nil
1910 }
1911
1912 if !apierrors.IsConflict(err) {
1913 return false, err
1914 }
1915 return false, nil
1916 })
1917 return cm, pollErr
1918 }
1919
1920 type updateCustomResourceFn func(cm *unstructured.Unstructured)
1921
1922 func updateCustomResource(ctx context.Context, c dynamic.ResourceInterface, ns, name string, update updateCustomResourceFn) (*unstructured.Unstructured, error) {
1923 var cr *unstructured.Unstructured
1924 pollErr := wait.PollImmediate(2*time.Second, 1*time.Minute, func() (bool, error) {
1925 var err error
1926 if cr, err = c.Get(ctx, name, metav1.GetOptions{}); err != nil {
1927 return false, err
1928 }
1929 update(cr)
1930 if cr, err = c.Update(ctx, cr, metav1.UpdateOptions{}); err == nil {
1931 return true, nil
1932 }
1933
1934 if !apierrors.IsConflict(err) {
1935 return false, err
1936 }
1937 return false, nil
1938 })
1939 return cr, pollErr
1940 }
1941
1942 func cleanWebhookTest(ctx context.Context, client clientset.Interface, namespaceName string) {
1943 _ = client.CoreV1().Services(namespaceName).Delete(ctx, serviceName, metav1.DeleteOptions{})
1944 _ = client.AppsV1().Deployments(namespaceName).Delete(ctx, deploymentName, metav1.DeleteOptions{})
1945 _ = client.CoreV1().Secrets(namespaceName).Delete(ctx, secretName, metav1.DeleteOptions{})
1946 _ = client.RbacV1().RoleBindings("kube-system").Delete(ctx, roleBindingName, metav1.DeleteOptions{})
1947 }
1948
1949 func registerWebhookForCustomResource(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, testcrd *crd.TestCrd, servicePort int32) {
1950 client := f.ClientSet
1951 ginkgo.By("Registering the custom resource webhook via the AdmissionRegistration API")
1952
1953 namespace := f.Namespace.Name
1954 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
1955
1956 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
1957 ObjectMeta: metav1.ObjectMeta{
1958 Name: configName,
1959 },
1960 Webhooks: []admissionregistrationv1.ValidatingWebhook{
1961 {
1962 Name: "deny-unwanted-custom-resource-data.k8s.io",
1963 Rules: []admissionregistrationv1.RuleWithOperations{{
1964 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete},
1965 Rule: admissionregistrationv1.Rule{
1966 APIGroups: []string{testcrd.Crd.Spec.Group},
1967 APIVersions: servedAPIVersions(testcrd.Crd),
1968 Resources: []string{testcrd.Crd.Spec.Names.Plural},
1969 },
1970 }},
1971 ClientConfig: admissionregistrationv1.WebhookClientConfig{
1972 Service: &admissionregistrationv1.ServiceReference{
1973 Namespace: namespace,
1974 Name: serviceName,
1975 Path: strPtr("/custom-resource"),
1976 Port: pointer.Int32(servicePort),
1977 },
1978 CABundle: certCtx.signingCert,
1979 },
1980 SideEffects: &sideEffectsNone,
1981 AdmissionReviewVersions: []string{"v1", "v1beta1"},
1982
1983 NamespaceSelector: &metav1.LabelSelector{
1984 MatchLabels: map[string]string{f.UniqueName: "true"},
1985 },
1986 },
1987
1988 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
1989 },
1990 })
1991 framework.ExpectNoError(err, "registering custom resource webhook config %s with namespace %s", configName, namespace)
1992
1993 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
1994 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
1995 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
1996 }
1997
1998 func registerMutatingWebhookForCustomResource(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, testcrd *crd.TestCrd, servicePort int32) {
1999 client := f.ClientSet
2000 ginkgo.By(fmt.Sprintf("Registering the mutating webhook for custom resource %s via the AdmissionRegistration API", testcrd.Crd.Name))
2001
2002 namespace := f.Namespace.Name
2003 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2004
2005 _, err := createMutatingWebhookConfiguration(ctx, f, &admissionregistrationv1.MutatingWebhookConfiguration{
2006 ObjectMeta: metav1.ObjectMeta{
2007 Name: configName,
2008 },
2009 Webhooks: []admissionregistrationv1.MutatingWebhook{
2010 {
2011 Name: "mutate-custom-resource-data-stage-1.k8s.io",
2012 Rules: []admissionregistrationv1.RuleWithOperations{{
2013 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update},
2014 Rule: admissionregistrationv1.Rule{
2015 APIGroups: []string{testcrd.Crd.Spec.Group},
2016 APIVersions: servedAPIVersions(testcrd.Crd),
2017 Resources: []string{testcrd.Crd.Spec.Names.Plural},
2018 },
2019 }},
2020 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2021 Service: &admissionregistrationv1.ServiceReference{
2022 Namespace: namespace,
2023 Name: serviceName,
2024 Path: strPtr("/mutating-custom-resource"),
2025 Port: pointer.Int32(servicePort),
2026 },
2027 CABundle: certCtx.signingCert,
2028 },
2029 SideEffects: &sideEffectsNone,
2030 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2031
2032 NamespaceSelector: &metav1.LabelSelector{
2033 MatchLabels: map[string]string{f.UniqueName: "true"},
2034 },
2035 },
2036 {
2037 Name: "mutate-custom-resource-data-stage-2.k8s.io",
2038 Rules: []admissionregistrationv1.RuleWithOperations{{
2039 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2040 Rule: admissionregistrationv1.Rule{
2041 APIGroups: []string{testcrd.Crd.Spec.Group},
2042 APIVersions: servedAPIVersions(testcrd.Crd),
2043 Resources: []string{testcrd.Crd.Spec.Names.Plural},
2044 },
2045 }},
2046 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2047 Service: &admissionregistrationv1.ServiceReference{
2048 Namespace: namespace,
2049 Name: serviceName,
2050 Path: strPtr("/mutating-custom-resource"),
2051 Port: pointer.Int32(servicePort),
2052 },
2053 CABundle: certCtx.signingCert,
2054 },
2055 SideEffects: &sideEffectsNone,
2056 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2057
2058 NamespaceSelector: &metav1.LabelSelector{
2059 MatchLabels: map[string]string{f.UniqueName: "true"},
2060 },
2061 },
2062
2063 newMutatingIsReadyWebhookFixture(f, certCtx, servicePort),
2064 },
2065 })
2066 framework.ExpectNoError(err, "registering custom resource webhook config %s with namespace %s", configName, namespace)
2067
2068 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
2069 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
2070
2071 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
2072 }
2073
2074 func testCustomResourceWebhook(ctx context.Context, f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
2075 ginkgo.By("Creating a custom resource that should be denied by the webhook")
2076 crInstanceName := "cr-instance-1"
2077 crInstance := &unstructured.Unstructured{
2078 Object: map[string]interface{}{
2079 "kind": crd.Spec.Names.Kind,
2080 "apiVersion": crd.Spec.Group + "/" + crd.Spec.Versions[0].Name,
2081 "metadata": map[string]interface{}{
2082 "name": crInstanceName,
2083 "namespace": f.Namespace.Name,
2084 },
2085 "data": map[string]interface{}{
2086 "webhook-e2e-test": "webhook-disallow",
2087 },
2088 },
2089 }
2090 _, err := customResourceClient.Create(ctx, crInstance, metav1.CreateOptions{})
2091 gomega.Expect(err).To(gomega.HaveOccurred(), "create custom resource %s in namespace %s should be denied by webhook", crInstanceName, f.Namespace.Name)
2092 expectedErrMsg := "the custom resource contains unwanted data"
2093 if !strings.Contains(err.Error(), expectedErrMsg) {
2094 framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
2095 }
2096 }
2097
2098 func testBlockingCustomResourceUpdateDeletion(ctx context.Context, f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface) {
2099 ginkgo.By("Creating a custom resource whose deletion would be denied by the webhook")
2100 crInstanceName := "cr-instance-2"
2101 crInstance := &unstructured.Unstructured{
2102 Object: map[string]interface{}{
2103 "kind": crd.Spec.Names.Kind,
2104 "apiVersion": crd.Spec.Group + "/" + crd.Spec.Versions[0].Name,
2105 "metadata": map[string]interface{}{
2106 "name": crInstanceName,
2107 "namespace": f.Namespace.Name,
2108 },
2109 "data": map[string]interface{}{
2110 "webhook-e2e-test": "webhook-nondeletable",
2111 },
2112 },
2113 }
2114 _, err := customResourceClient.Create(ctx, crInstance, metav1.CreateOptions{})
2115 framework.ExpectNoError(err, "failed to create custom resource %s in namespace: %s", crInstanceName, f.Namespace.Name)
2116
2117 ginkgo.By("Updating the custom resource with disallowed data should be denied")
2118 toNonCompliantFn := func(cr *unstructured.Unstructured) {
2119 if _, ok := cr.Object["data"]; !ok {
2120 cr.Object["data"] = map[string]interface{}{}
2121 }
2122 data := cr.Object["data"].(map[string]interface{})
2123 data["webhook-e2e-test"] = "webhook-disallow"
2124 }
2125 _, err = updateCustomResource(ctx, customResourceClient, f.Namespace.Name, crInstanceName, toNonCompliantFn)
2126 gomega.Expect(err).To(gomega.HaveOccurred(), "updating custom resource %s in namespace: %s should be denied", crInstanceName, f.Namespace.Name)
2127
2128 expectedErrMsg := "the custom resource contains unwanted data"
2129 if !strings.Contains(err.Error(), expectedErrMsg) {
2130 framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
2131 }
2132
2133 ginkgo.By("Deleting the custom resource should be denied")
2134 err = customResourceClient.Delete(ctx, crInstanceName, metav1.DeleteOptions{})
2135 gomega.Expect(err).To(gomega.HaveOccurred(), "deleting custom resource %s in namespace: %s should be denied", crInstanceName, f.Namespace.Name)
2136 expectedErrMsg1 := "the custom resource cannot be deleted because it contains unwanted key and value"
2137 if !strings.Contains(err.Error(), expectedErrMsg1) {
2138 framework.Failf("expect error contains %q, got %q", expectedErrMsg1, err.Error())
2139 }
2140
2141 ginkgo.By("Remove the offending key and value from the custom resource data")
2142 toCompliantFn := func(cr *unstructured.Unstructured) {
2143 if _, ok := cr.Object["data"]; !ok {
2144 cr.Object["data"] = map[string]interface{}{}
2145 }
2146 data := cr.Object["data"].(map[string]interface{})
2147 data["webhook-e2e-test"] = "webhook-allow"
2148 }
2149 _, err = updateCustomResource(ctx, customResourceClient, f.Namespace.Name, crInstanceName, toCompliantFn)
2150 framework.ExpectNoError(err, "failed to update custom resource %s in namespace: %s", crInstanceName, f.Namespace.Name)
2151
2152 ginkgo.By("Deleting the updated custom resource should be successful")
2153 err = customResourceClient.Delete(ctx, crInstanceName, metav1.DeleteOptions{})
2154 framework.ExpectNoError(err, "failed to delete custom resource %s in namespace: %s", crInstanceName, f.Namespace.Name)
2155
2156 }
2157
2158 func testMutatingCustomResourceWebhook(ctx context.Context, f *framework.Framework, crd *apiextensionsv1.CustomResourceDefinition, customResourceClient dynamic.ResourceInterface, prune bool) {
2159 ginkgo.By("Creating a custom resource that should be mutated by the webhook")
2160 crName := "cr-instance-1"
2161 cr := &unstructured.Unstructured{
2162 Object: map[string]interface{}{
2163 "kind": crd.Spec.Names.Kind,
2164 "apiVersion": crd.Spec.Group + "/" + crd.Spec.Versions[0].Name,
2165 "metadata": map[string]interface{}{
2166 "name": crName,
2167 "namespace": f.Namespace.Name,
2168 },
2169 "data": map[string]interface{}{
2170 "mutation-start": "yes",
2171 },
2172 },
2173 }
2174 mutatedCR, err := customResourceClient.Create(ctx, cr, metav1.CreateOptions{})
2175 framework.ExpectNoError(err, "failed to create custom resource %s in namespace: %s", crName, f.Namespace.Name)
2176 expectedCRData := map[string]interface{}{
2177 "mutation-start": "yes",
2178 "mutation-stage-1": "yes",
2179 }
2180 if !prune {
2181 expectedCRData["mutation-stage-2"] = "yes"
2182 }
2183 if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
2184 framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
2185 }
2186 }
2187
2188 func testMultiVersionCustomResourceWebhook(ctx context.Context, f *framework.Framework, testcrd *crd.TestCrd) {
2189 customResourceClient := testcrd.DynamicClients["v1"]
2190 ginkgo.By("Creating a custom resource while v1 is storage version")
2191 crName := "cr-instance-1"
2192 cr := &unstructured.Unstructured{
2193 Object: map[string]interface{}{
2194 "kind": testcrd.Crd.Spec.Names.Kind,
2195 "apiVersion": testcrd.Crd.Spec.Group + "/" + testcrd.Crd.Spec.Versions[0].Name,
2196 "metadata": map[string]interface{}{
2197 "name": crName,
2198 "namespace": f.Namespace.Name,
2199 },
2200 "data": map[string]interface{}{
2201 "mutation-start": "yes",
2202 },
2203 },
2204 }
2205 _, err := customResourceClient.Create(ctx, cr, metav1.CreateOptions{})
2206 framework.ExpectNoError(err, "failed to create custom resource %s in namespace: %s", crName, f.Namespace.Name)
2207
2208 ginkgo.By("Patching Custom Resource Definition to set v2 as storage")
2209 apiVersionWithV2StoragePatch := `{
2210 "spec": {
2211 "versions": [
2212 {
2213 "name": "v1",
2214 "storage": false,
2215 "served": true,
2216 "schema": {
2217 "openAPIV3Schema": {"x-kubernetes-preserve-unknown-fields": true, "type": "object"}
2218 }
2219 },
2220 {
2221 "name": "v2",
2222 "storage": true,
2223 "served": true,
2224 "schema": {
2225 "openAPIV3Schema": {"x-kubernetes-preserve-unknown-fields": true, "type": "object"}
2226 }
2227 }
2228 ]
2229 }
2230 }`
2231 _, err = testcrd.APIExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Patch(ctx, testcrd.Crd.Name, types.StrategicMergePatchType, []byte(apiVersionWithV2StoragePatch), metav1.PatchOptions{})
2232 framework.ExpectNoError(err, "failed to patch custom resource definition %s in namespace: %s", testcrd.Crd.Name, f.Namespace.Name)
2233
2234 ginkgo.By("Patching the custom resource while v2 is storage version")
2235 crDummyPatch := fmt.Sprint(`[{ "op": "add", "path": "/dummy", "value": "test" }]`)
2236 mutatedCR, err := testcrd.DynamicClients["v2"].Patch(ctx, crName, types.JSONPatchType, []byte(crDummyPatch), metav1.PatchOptions{})
2237 framework.ExpectNoError(err, "failed to patch custom resource %s in namespace: %s", crName, f.Namespace.Name)
2238 expectedCRData := map[string]interface{}{
2239 "mutation-start": "yes",
2240 "mutation-stage-1": "yes",
2241 "mutation-stage-2": "yes",
2242 }
2243 if !reflect.DeepEqual(expectedCRData, mutatedCR.Object["data"]) {
2244 framework.Failf("\nexpected %#v\n, got %#v\n", expectedCRData, mutatedCR.Object["data"])
2245 }
2246 if !reflect.DeepEqual("test", mutatedCR.Object["dummy"]) {
2247 framework.Failf("\nexpected %#v\n, got %#v\n", "test", mutatedCR.Object["dummy"])
2248 }
2249 }
2250
2251 func registerValidatingWebhookForCRD(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, servicePort int32) {
2252 client := f.ClientSet
2253 ginkgo.By("Registering the crd webhook via the AdmissionRegistration API")
2254
2255 namespace := f.Namespace.Name
2256 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2257
2258
2259
2260
2261
2262 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
2263 ObjectMeta: metav1.ObjectMeta{
2264 Name: configName,
2265 },
2266 Webhooks: []admissionregistrationv1.ValidatingWebhook{
2267 {
2268 Name: "deny-crd-with-unwanted-label.k8s.io",
2269 Rules: []admissionregistrationv1.RuleWithOperations{{
2270 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2271 Rule: admissionregistrationv1.Rule{
2272 APIGroups: []string{"apiextensions.k8s.io"},
2273 APIVersions: []string{"*"},
2274 Resources: []string{"customresourcedefinitions"},
2275 },
2276 }},
2277 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2278 Service: &admissionregistrationv1.ServiceReference{
2279 Namespace: namespace,
2280 Name: serviceName,
2281 Path: strPtr("/crd"),
2282 Port: pointer.Int32(servicePort),
2283 },
2284 CABundle: certCtx.signingCert,
2285 },
2286 SideEffects: &sideEffectsNone,
2287 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2288
2289 ObjectSelector: &metav1.LabelSelector{
2290 MatchLabels: map[string]string{f.UniqueName: "true"},
2291 },
2292 },
2293
2294 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
2295 },
2296 })
2297 framework.ExpectNoError(err, "registering crd webhook config %s with namespace %s", configName, namespace)
2298
2299 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
2300 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
2301 ginkgo.DeferCleanup(framework.IgnoreNotFound(client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete), configName, metav1.DeleteOptions{})
2302 }
2303
2304 func testCRDDenyWebhook(ctx context.Context, f *framework.Framework) {
2305 ginkgo.By("Creating a custom resource definition that should be denied by the webhook")
2306 name := fmt.Sprintf("e2e-test-%s-%s-crd", f.BaseName, "deny")
2307 kind := fmt.Sprintf("E2e-test-%s-%s-crd", f.BaseName, "deny")
2308 group := fmt.Sprintf("%s.example.com", f.BaseName)
2309 apiVersions := []apiextensionsv1.CustomResourceDefinitionVersion{
2310 {
2311 Name: "v1",
2312 Served: true,
2313 Storage: true,
2314 Schema: &apiextensionsv1.CustomResourceValidation{
2315 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
2316 XPreserveUnknownFields: pointer.BoolPtr(true),
2317 Type: "object",
2318 },
2319 },
2320 },
2321 }
2322
2323
2324 config, err := framework.LoadConfig()
2325 if err != nil {
2326 framework.Failf("failed to load config: %v", err)
2327 return
2328 }
2329 apiExtensionClient, err := crdclientset.NewForConfig(config)
2330 if err != nil {
2331 framework.Failf("failed to initialize apiExtensionClient: %v", err)
2332 return
2333 }
2334 crd := &apiextensionsv1.CustomResourceDefinition{
2335 ObjectMeta: metav1.ObjectMeta{
2336 Name: name + "s." + group,
2337 Labels: map[string]string{
2338
2339 f.UniqueName: "true",
2340
2341 "webhook-e2e-test": "webhook-disallow",
2342 },
2343 },
2344 Spec: apiextensionsv1.CustomResourceDefinitionSpec{
2345 Group: group,
2346 Versions: apiVersions,
2347 Names: apiextensionsv1.CustomResourceDefinitionNames{
2348 Singular: name,
2349 Kind: kind,
2350 ListKind: kind + "List",
2351 Plural: name + "s",
2352 },
2353 Scope: apiextensionsv1.NamespaceScoped,
2354 },
2355 }
2356
2357
2358 _, err = apiExtensionClient.ApiextensionsV1().CustomResourceDefinitions().Create(ctx, crd, metav1.CreateOptions{})
2359 gomega.Expect(err).To(gomega.HaveOccurred(), "create custom resource definition %s should be denied by webhook", crd.Name)
2360 expectedErrMsg := "the crd contains unwanted label"
2361 if !strings.Contains(err.Error(), expectedErrMsg) {
2362 framework.Failf("expect error contains %q, got %q", expectedErrMsg, err.Error())
2363 }
2364 }
2365
2366 func labelNamespace(ctx context.Context, f *framework.Framework, namespace string) {
2367 client := f.ClientSet
2368
2369
2370 nsPatch, err := json.Marshal(map[string]interface{}{
2371 "metadata": map[string]interface{}{
2372 "labels": map[string]string{f.UniqueName: "true"},
2373 },
2374 })
2375 framework.ExpectNoError(err, "error marshaling namespace %s", namespace)
2376 _, err = client.CoreV1().Namespaces().Patch(ctx, namespace, types.StrategicMergePatchType, nsPatch, metav1.PatchOptions{})
2377 framework.ExpectNoError(err, "error labeling namespace %s", namespace)
2378 }
2379
2380 func registerSlowWebhook(ctx context.Context, f *framework.Framework, markersNamespaceName string, configName string, certCtx *certContext, policy *admissionregistrationv1.FailurePolicyType, timeout *int32, servicePort int32) func(ctx context.Context) {
2381 client := f.ClientSet
2382 ginkgo.By("Registering slow webhook via the AdmissionRegistration API")
2383
2384 namespace := f.Namespace.Name
2385 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2386
2387 _, err := createValidatingWebhookConfiguration(ctx, f, &admissionregistrationv1.ValidatingWebhookConfiguration{
2388 ObjectMeta: metav1.ObjectMeta{
2389 Name: configName,
2390 },
2391 Webhooks: []admissionregistrationv1.ValidatingWebhook{
2392 {
2393 Name: "allow-configmap-with-delay-webhook.k8s.io",
2394 Rules: []admissionregistrationv1.RuleWithOperations{{
2395 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2396 Rule: admissionregistrationv1.Rule{
2397 APIGroups: []string{""},
2398 APIVersions: []string{"v1"},
2399 Resources: []string{"configmaps"},
2400 },
2401 }},
2402 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2403 Service: &admissionregistrationv1.ServiceReference{
2404 Namespace: namespace,
2405 Name: serviceName,
2406 Path: strPtr("/always-allow-delay-5s"),
2407 Port: pointer.Int32(servicePort),
2408 },
2409 CABundle: certCtx.signingCert,
2410 },
2411
2412 NamespaceSelector: &metav1.LabelSelector{
2413 MatchLabels: map[string]string{f.UniqueName: "true"},
2414 },
2415 FailurePolicy: policy,
2416 TimeoutSeconds: timeout,
2417 SideEffects: &sideEffectsNone,
2418 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2419 },
2420
2421 newValidatingIsReadyWebhookFixture(f, certCtx, servicePort),
2422 },
2423 })
2424 framework.ExpectNoError(err, "registering slow webhook config %s with namespace %s", configName, namespace)
2425
2426 err = waitWebhookConfigurationReady(ctx, f, markersNamespaceName)
2427 framework.ExpectNoError(err, "waiting for webhook configuration to be ready")
2428
2429 cleanup := func(ctx context.Context) {
2430 err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(ctx, configName, metav1.DeleteOptions{})
2431 if !apierrors.IsNotFound(err) {
2432 framework.ExpectNoError(err)
2433 }
2434 }
2435
2436
2437
2438 ginkgo.DeferCleanup(cleanup)
2439 return cleanup
2440 }
2441
2442 func testSlowWebhookTimeoutFailEarly(ctx context.Context, f *framework.Framework) {
2443 ginkgo.By("Request fails when timeout (1s) is shorter than slow webhook latency (5s)")
2444 client := f.ClientSet
2445 name := "e2e-test-slow-webhook-configmap"
2446 _, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: name}}, metav1.CreateOptions{})
2447 gomega.Expect(err).To(gomega.HaveOccurred(), "create configmap in namespace %s should have timed-out reaching slow webhook", f.Namespace.Name)
2448
2449
2450 isTimeoutError := strings.Contains(err.Error(), `context deadline exceeded`) || strings.Contains(err.Error(), `timeout`)
2451 isErrorQueryingWebhook := strings.Contains(err.Error(), `/always-allow-delay-5s?timeout=1s`)
2452 if !isTimeoutError || !isErrorQueryingWebhook {
2453 framework.Failf("expect an HTTP/dial timeout error querying the slow webhook, got: %q", err.Error())
2454 }
2455 }
2456
2457 func testSlowWebhookTimeoutNoError(ctx context.Context, f *framework.Framework) {
2458 client := f.ClientSet
2459 name := "e2e-test-slow-webhook-configmap"
2460 _, err := client.CoreV1().ConfigMaps(f.Namespace.Name).Create(ctx, &v1.ConfigMap{ObjectMeta: metav1.ObjectMeta{Name: name}}, metav1.CreateOptions{})
2461 framework.ExpectNoError(err)
2462 err = client.CoreV1().ConfigMaps(f.Namespace.Name).Delete(ctx, name, metav1.DeleteOptions{})
2463 framework.ExpectNoError(err)
2464 }
2465
2466
2467
2468 func createAdmissionWebhookMultiVersionTestCRDWithV1Storage(f *framework.Framework, opts ...crd.Option) (*crd.TestCrd, error) {
2469 group := fmt.Sprintf("%s.example.com", f.BaseName)
2470 return crd.CreateMultiVersionTestCRD(f, group, append([]crd.Option{func(crd *apiextensionsv1.CustomResourceDefinition) {
2471 crd.Spec.Versions = []apiextensionsv1.CustomResourceDefinitionVersion{
2472 {
2473 Name: "v1",
2474 Served: true,
2475 Storage: true,
2476 Schema: &apiextensionsv1.CustomResourceValidation{
2477 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
2478 XPreserveUnknownFields: pointer.BoolPtr(true),
2479 Type: "object",
2480 },
2481 },
2482 },
2483 {
2484 Name: "v2",
2485 Served: true,
2486 Storage: false,
2487 Schema: &apiextensionsv1.CustomResourceValidation{
2488 OpenAPIV3Schema: &apiextensionsv1.JSONSchemaProps{
2489 XPreserveUnknownFields: pointer.BoolPtr(true),
2490 Type: "object",
2491 },
2492 },
2493 },
2494 }
2495 }}, opts...)...)
2496 }
2497
2498
2499 func servedAPIVersions(crd *apiextensionsv1.CustomResourceDefinition) []string {
2500 ret := []string{}
2501 for _, v := range crd.Spec.Versions {
2502 if v.Served {
2503 ret = append(ret, v.Name)
2504 }
2505 }
2506 return ret
2507 }
2508
2509
2510
2511 func createValidatingWebhookConfiguration(ctx context.Context, f *framework.Framework, config *admissionregistrationv1.ValidatingWebhookConfiguration) (*admissionregistrationv1.ValidatingWebhookConfiguration, error) {
2512 for _, webhook := range config.Webhooks {
2513 if webhook.NamespaceSelector != nil && webhook.NamespaceSelector.MatchLabels[f.UniqueName] == "true" {
2514 continue
2515 }
2516 if webhook.ObjectSelector != nil && webhook.ObjectSelector.MatchLabels[f.UniqueName] == "true" {
2517 continue
2518 }
2519 framework.Failf(`webhook %s in config %s has no namespace or object selector with %s="true", and can interfere with other tests`, webhook.Name, config.Name, f.UniqueName)
2520 }
2521 return f.ClientSet.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(ctx, config, metav1.CreateOptions{})
2522 }
2523
2524
2525
2526 func createMutatingWebhookConfiguration(ctx context.Context, f *framework.Framework, config *admissionregistrationv1.MutatingWebhookConfiguration) (*admissionregistrationv1.MutatingWebhookConfiguration, error) {
2527 for _, webhook := range config.Webhooks {
2528 if webhook.NamespaceSelector != nil && webhook.NamespaceSelector.MatchLabels[f.UniqueName] == "true" {
2529 continue
2530 }
2531 if webhook.ObjectSelector != nil && webhook.ObjectSelector.MatchLabels[f.UniqueName] == "true" {
2532 continue
2533 }
2534 framework.Failf(`webhook %s in config %s has no namespace or object selector with %s="true", and can interfere with other tests`, webhook.Name, config.Name, f.UniqueName)
2535 }
2536 return f.ClientSet.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(ctx, config, metav1.CreateOptions{})
2537 }
2538
2539 func newDenyPodWebhookFixture(f *framework.Framework, certCtx *certContext, servicePort int32) admissionregistrationv1.ValidatingWebhook {
2540 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2541 return admissionregistrationv1.ValidatingWebhook{
2542 Name: "deny-unwanted-pod-container-name-and-label.k8s.io",
2543 Rules: []admissionregistrationv1.RuleWithOperations{{
2544 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2545 Rule: admissionregistrationv1.Rule{
2546 APIGroups: []string{""},
2547 APIVersions: []string{"v1"},
2548 Resources: []string{"pods"},
2549 },
2550 }},
2551 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2552 Service: &admissionregistrationv1.ServiceReference{
2553 Namespace: f.Namespace.Name,
2554 Name: serviceName,
2555 Path: strPtr("/pods"),
2556 Port: pointer.Int32(servicePort),
2557 },
2558 CABundle: certCtx.signingCert,
2559 },
2560 SideEffects: &sideEffectsNone,
2561 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2562
2563 NamespaceSelector: &metav1.LabelSelector{
2564 MatchLabels: map[string]string{f.UniqueName: "true"},
2565 },
2566 }
2567 }
2568
2569 func newDenyConfigMapWebhookFixture(f *framework.Framework, certCtx *certContext, servicePort int32) admissionregistrationv1.ValidatingWebhook {
2570 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2571 return admissionregistrationv1.ValidatingWebhook{
2572 Name: "deny-unwanted-configmap-data.k8s.io",
2573 Rules: []admissionregistrationv1.RuleWithOperations{{
2574 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create, admissionregistrationv1.Update, admissionregistrationv1.Delete},
2575 Rule: admissionregistrationv1.Rule{
2576 APIGroups: []string{""},
2577 APIVersions: []string{"v1"},
2578 Resources: []string{"configmaps"},
2579 },
2580 }},
2581
2582 NamespaceSelector: &metav1.LabelSelector{
2583 MatchLabels: map[string]string{f.UniqueName: "true"},
2584 MatchExpressions: []metav1.LabelSelectorRequirement{
2585 {
2586 Key: skipNamespaceLabelKey,
2587 Operator: metav1.LabelSelectorOpNotIn,
2588 Values: []string{skipNamespaceLabelValue},
2589 },
2590 },
2591 },
2592 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2593 Service: &admissionregistrationv1.ServiceReference{
2594 Namespace: f.Namespace.Name,
2595 Name: serviceName,
2596 Path: strPtr("/configmaps"),
2597 Port: pointer.Int32(servicePort),
2598 },
2599 CABundle: certCtx.signingCert,
2600 },
2601 SideEffects: &sideEffectsNone,
2602 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2603 }
2604 }
2605
2606 func newMutateConfigMapWebhookFixture(f *framework.Framework, certCtx *certContext, stage int, servicePort int32) admissionregistrationv1.MutatingWebhook {
2607 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2608 return admissionregistrationv1.MutatingWebhook{
2609 Name: fmt.Sprintf("adding-configmap-data-stage-%d.k8s.io", stage),
2610 Rules: []admissionregistrationv1.RuleWithOperations{{
2611 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2612 Rule: admissionregistrationv1.Rule{
2613 APIGroups: []string{""},
2614 APIVersions: []string{"v1"},
2615 Resources: []string{"configmaps"},
2616 },
2617 }},
2618 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2619 Service: &admissionregistrationv1.ServiceReference{
2620 Namespace: f.Namespace.Name,
2621 Name: serviceName,
2622 Path: strPtr("/mutating-configmaps"),
2623 Port: pointer.Int32(servicePort),
2624 },
2625 CABundle: certCtx.signingCert,
2626 },
2627 SideEffects: &sideEffectsNone,
2628 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2629
2630 NamespaceSelector: &metav1.LabelSelector{
2631 MatchLabels: map[string]string{f.UniqueName: "true"},
2632 },
2633 }
2634 }
2635
2636
2637
2638 func createWebhookConfigurationReadyNamespace(ctx context.Context, f *framework.Framework) string {
2639 baseName := f.BaseName + "-markers"
2640
2641 ns, err := f.CreateNamespace(ctx, baseName, map[string]string{
2642 f.UniqueName + "-markers": "true",
2643 })
2644 framework.ExpectNoError(err, "creating namespace for webhook configuration ready markers")
2645 return ns.Name
2646 }
2647
2648
2649
2650
2651 func waitWebhookConfigurationReady(ctx context.Context, f *framework.Framework, markersNamespaceName string) error {
2652 cmClient := f.ClientSet.CoreV1().ConfigMaps(markersNamespaceName)
2653 return wait.PollImmediate(100*time.Millisecond, 30*time.Second, func() (bool, error) {
2654 marker := &v1.ConfigMap{
2655 ObjectMeta: metav1.ObjectMeta{
2656 Name: string(uuid.NewUUID()),
2657 Labels: map[string]string{
2658 f.UniqueName: "true",
2659 },
2660 },
2661 }
2662 _, err := cmClient.Create(ctx, marker, metav1.CreateOptions{})
2663 if err != nil {
2664
2665 if strings.Contains(err.Error(), "denied") {
2666 return true, nil
2667 }
2668 return false, err
2669 }
2670
2671 _ = cmClient.Delete(ctx, marker.GetName(), metav1.DeleteOptions{})
2672 framework.Logf("Waiting for webhook configuration to be ready...")
2673 return false, nil
2674 })
2675 }
2676
2677
2678
2679 func newValidatingIsReadyWebhookFixture(f *framework.Framework, certCtx *certContext, servicePort int32) admissionregistrationv1.ValidatingWebhook {
2680 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2681 failOpen := admissionregistrationv1.Ignore
2682 return admissionregistrationv1.ValidatingWebhook{
2683 Name: "validating-is-webhook-configuration-ready.k8s.io",
2684 Rules: []admissionregistrationv1.RuleWithOperations{{
2685 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2686 Rule: admissionregistrationv1.Rule{
2687 APIGroups: []string{""},
2688 APIVersions: []string{"v1"},
2689 Resources: []string{"configmaps"},
2690 },
2691 }},
2692 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2693 Service: &admissionregistrationv1.ServiceReference{
2694 Namespace: f.Namespace.Name,
2695 Name: serviceName,
2696 Path: strPtr("/always-deny"),
2697 Port: pointer.Int32(servicePort),
2698 },
2699 CABundle: certCtx.signingCert,
2700 },
2701
2702 FailurePolicy: &failOpen,
2703 SideEffects: &sideEffectsNone,
2704 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2705
2706 NamespaceSelector: &metav1.LabelSelector{
2707 MatchLabels: map[string]string{f.UniqueName + "-markers": "true"},
2708 },
2709
2710 ObjectSelector: &metav1.LabelSelector{
2711 MatchLabels: map[string]string{f.UniqueName: "true"},
2712 },
2713 }
2714 }
2715
2716
2717
2718 func newMutatingIsReadyWebhookFixture(f *framework.Framework, certCtx *certContext, servicePort int32) admissionregistrationv1.MutatingWebhook {
2719 sideEffectsNone := admissionregistrationv1.SideEffectClassNone
2720 failOpen := admissionregistrationv1.Ignore
2721 return admissionregistrationv1.MutatingWebhook{
2722 Name: "mutating-is-webhook-configuration-ready.k8s.io",
2723 Rules: []admissionregistrationv1.RuleWithOperations{{
2724 Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
2725 Rule: admissionregistrationv1.Rule{
2726 APIGroups: []string{""},
2727 APIVersions: []string{"v1"},
2728 Resources: []string{"configmaps"},
2729 },
2730 }},
2731 ClientConfig: admissionregistrationv1.WebhookClientConfig{
2732 Service: &admissionregistrationv1.ServiceReference{
2733 Namespace: f.Namespace.Name,
2734 Name: serviceName,
2735 Path: strPtr("/always-deny"),
2736 Port: pointer.Int32(servicePort),
2737 },
2738 CABundle: certCtx.signingCert,
2739 },
2740
2741 FailurePolicy: &failOpen,
2742 SideEffects: &sideEffectsNone,
2743 AdmissionReviewVersions: []string{"v1", "v1beta1"},
2744
2745 NamespaceSelector: &metav1.LabelSelector{
2746 MatchLabels: map[string]string{f.UniqueName + "-markers": "true"},
2747 },
2748
2749 ObjectSelector: &metav1.LabelSelector{
2750 MatchLabels: map[string]string{f.UniqueName: "true"},
2751 },
2752 }
2753 }
2754
View as plain text