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  }