sigs.k8s.io/controller-runtime@v0.18.2/pkg/webhook/webhook_integration_test.go (about) 1 /* 2 Copyright 2021 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package webhook_test 18 19 import ( 20 "context" 21 "crypto/tls" 22 "strings" 23 "time" 24 25 . "github.com/onsi/ginkgo/v2" 26 . "github.com/onsi/gomega" 27 appsv1 "k8s.io/api/apps/v1" 28 corev1 "k8s.io/api/core/v1" 29 apierrors "k8s.io/apimachinery/pkg/api/errors" 30 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 31 metricsserver "sigs.k8s.io/controller-runtime/pkg/metrics/server" 32 33 "sigs.k8s.io/controller-runtime/pkg/client" 34 "sigs.k8s.io/controller-runtime/pkg/manager" 35 "sigs.k8s.io/controller-runtime/pkg/webhook" 36 "sigs.k8s.io/controller-runtime/pkg/webhook/admission" 37 ) 38 39 var _ = Describe("Webhook", func() { 40 var c client.Client 41 var obj *appsv1.Deployment 42 BeforeEach(func() { 43 Expect(cfg).NotTo(BeNil()) 44 var err error 45 c, err = client.New(cfg, client.Options{}) 46 Expect(err).NotTo(HaveOccurred()) 47 48 obj = &appsv1.Deployment{ 49 TypeMeta: metav1.TypeMeta{ 50 APIVersion: "apps/v1", 51 Kind: "Deployment", 52 }, 53 ObjectMeta: metav1.ObjectMeta{ 54 Name: "test-deployment", 55 Namespace: "default", 56 }, 57 Spec: appsv1.DeploymentSpec{ 58 Selector: &metav1.LabelSelector{ 59 MatchLabels: map[string]string{"foo": "bar"}, 60 }, 61 Template: corev1.PodTemplateSpec{ 62 ObjectMeta: metav1.ObjectMeta{Labels: map[string]string{"foo": "bar"}}, 63 Spec: corev1.PodSpec{ 64 Containers: []corev1.Container{ 65 { 66 Name: "nginx", 67 Image: "nginx", 68 }, 69 }, 70 }, 71 }, 72 }, 73 } 74 }) 75 Context("when running a webhook server with a manager", func() { 76 It("should reject create request for webhook that rejects all requests", func() { 77 m, err := manager.New(cfg, manager.Options{ 78 WebhookServer: webhook.NewServer(webhook.Options{ 79 Port: testenv.WebhookInstallOptions.LocalServingPort, 80 Host: testenv.WebhookInstallOptions.LocalServingHost, 81 CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, 82 TLSOpts: []func(*tls.Config){func(config *tls.Config) {}}, 83 }), 84 }) // we need manager here just to leverage manager.SetFields 85 Expect(err).NotTo(HaveOccurred()) 86 server := m.GetWebhookServer() 87 server.Register("/failing", &webhook.Admission{Handler: &rejectingValidator{d: admission.NewDecoder(testenv.Scheme)}}) 88 89 ctx, cancel := context.WithCancel(context.Background()) 90 go func() { 91 err := server.Start(ctx) 92 Expect(err).NotTo(HaveOccurred()) 93 }() 94 95 Eventually(func() bool { 96 err := c.Create(context.TODO(), obj) 97 return err != nil && strings.HasSuffix(err.Error(), "Always denied") && apierrors.ReasonForError(err) == metav1.StatusReasonForbidden 98 }, 1*time.Second).Should(BeTrue()) 99 100 cancel() 101 }) 102 It("should reject create request for multi-webhook that rejects all requests", func() { 103 m, err := manager.New(cfg, manager.Options{ 104 Metrics: metricsserver.Options{BindAddress: "0"}, 105 WebhookServer: webhook.NewServer(webhook.Options{ 106 Port: testenv.WebhookInstallOptions.LocalServingPort, 107 Host: testenv.WebhookInstallOptions.LocalServingHost, 108 CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, 109 TLSOpts: []func(*tls.Config){func(config *tls.Config) {}}, 110 }), 111 }) // we need manager here just to leverage manager.SetFields 112 Expect(err).NotTo(HaveOccurred()) 113 server := m.GetWebhookServer() 114 server.Register("/failing", &webhook.Admission{Handler: admission.MultiValidatingHandler(&rejectingValidator{d: admission.NewDecoder(testenv.Scheme)})}) 115 116 ctx, cancel := context.WithCancel(context.Background()) 117 go func() { 118 err = server.Start(ctx) 119 Expect(err).NotTo(HaveOccurred()) 120 }() 121 122 Eventually(func() bool { 123 err = c.Create(context.TODO(), obj) 124 return err != nil && strings.HasSuffix(err.Error(), "Always denied") && apierrors.ReasonForError(err) == metav1.StatusReasonForbidden 125 }, 1*time.Second).Should(BeTrue()) 126 127 cancel() 128 }) 129 }) 130 Context("when running a webhook server without a manager", func() { 131 It("should reject create request for webhook that rejects all requests", func() { 132 server := webhook.NewServer(webhook.Options{ 133 Port: testenv.WebhookInstallOptions.LocalServingPort, 134 Host: testenv.WebhookInstallOptions.LocalServingHost, 135 CertDir: testenv.WebhookInstallOptions.LocalServingCertDir, 136 }) 137 server.Register("/failing", &webhook.Admission{Handler: &rejectingValidator{d: admission.NewDecoder(testenv.Scheme)}}) 138 139 ctx, cancel := context.WithCancel(context.Background()) 140 go func() { 141 err := server.Start(ctx) 142 Expect(err).NotTo(HaveOccurred()) 143 }() 144 145 Eventually(func() bool { 146 err := c.Create(context.TODO(), obj) 147 return err != nil && strings.HasSuffix(err.Error(), "Always denied") && apierrors.ReasonForError(err) == metav1.StatusReasonForbidden 148 }, 1*time.Second).Should(BeTrue()) 149 150 cancel() 151 }) 152 }) 153 }) 154 155 type rejectingValidator struct { 156 d admission.Decoder 157 } 158 159 func (v *rejectingValidator) Handle(ctx context.Context, req admission.Request) admission.Response { 160 var obj appsv1.Deployment 161 if err := v.d.Decode(req, &obj); err != nil { 162 return admission.Denied(err.Error()) 163 } 164 return admission.Denied("Always denied") 165 }