k8s.io/kubernetes@v1.29.3/test/integration/apiserver/admissionwebhook/broken_webhook_test.go (about)

     1  /*
     2  Copyright 2018 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package admissionwebhook
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"testing"
    23  	"time"
    24  
    25  	admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
    26  	appsv1 "k8s.io/api/apps/v1"
    27  	corev1 "k8s.io/api/core/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/client-go/kubernetes"
    30  	kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
    31  	"k8s.io/kubernetes/test/integration/framework"
    32  )
    33  
    34  var (
    35  	brokenWebhookName    = "integration-broken-webhook-test-webhook-config"
    36  	deploymentNamePrefix = "integration-broken-webhook-test-deployment"
    37  )
    38  
    39  func TestBrokenWebhook(t *testing.T) {
    40  	var tearDownFn kubeapiservertesting.TearDownFunc
    41  	defer func() {
    42  		if tearDownFn != nil {
    43  			tearDownFn()
    44  		}
    45  	}()
    46  
    47  	etcdConfig := framework.SharedEtcd()
    48  	server := kubeapiservertesting.StartTestServerOrDie(t, nil, nil, etcdConfig)
    49  	tearDownFn = server.TearDownFn
    50  
    51  	client, err := kubernetes.NewForConfig(server.ClientConfig)
    52  	if err != nil {
    53  		t.Fatalf("unexpected error: %v", err)
    54  	}
    55  
    56  	t.Logf("Creating Deployment to ensure apiserver is functional")
    57  	_, err = client.AppsV1().Deployments("default").Create(context.TODO(), exampleDeployment(generateDeploymentName(0)), metav1.CreateOptions{})
    58  	if err != nil {
    59  		t.Fatalf("Failed to create deployment: %v", err)
    60  	}
    61  
    62  	t.Logf("Creating Broken Webhook that will block all operations on all objects")
    63  	_, err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), brokenWebhookConfig(brokenWebhookName), metav1.CreateOptions{})
    64  	if err != nil {
    65  		t.Fatalf("Failed to register broken webhook: %v", err)
    66  	}
    67  
    68  	// There is no guarantee on how long it takes the apiserver to honor the configuration and there is
    69  	// no API to determine if the configuration is being honored, so we will just wait 10s, which is long enough
    70  	// in most cases.
    71  	time.Sleep(10 * time.Second)
    72  
    73  	// test whether the webhook blocks requests
    74  	t.Logf("Attempt to create Deployment which should fail due to the webhook")
    75  	_, err = client.AppsV1().Deployments("default").Create(context.TODO(), exampleDeployment(generateDeploymentName(1)), metav1.CreateOptions{})
    76  	if err == nil {
    77  		t.Fatalf("Expected the broken webhook to cause creating a deployment to fail, but it succeeded.")
    78  	}
    79  
    80  	t.Logf("Restarting apiserver")
    81  	tearDownFn = nil
    82  	server.TearDownFn()
    83  	server = kubeapiservertesting.StartTestServerOrDie(t, nil, nil, etcdConfig)
    84  	tearDownFn = server.TearDownFn
    85  
    86  	client, err = kubernetes.NewForConfig(server.ClientConfig)
    87  	if err != nil {
    88  		t.Fatalf("unexpected error: %v", err)
    89  	}
    90  
    91  	// test whether the webhook still blocks requests after restarting
    92  	t.Logf("Attempt again to create Deployment which should fail due to the webhook")
    93  	_, err = client.AppsV1().Deployments("default").Create(context.TODO(), exampleDeployment(generateDeploymentName(2)), metav1.CreateOptions{})
    94  	if err == nil {
    95  		t.Fatalf("Expected the broken webhook to cause creating a deployment to fail, but it succeeded.")
    96  	}
    97  
    98  	t.Logf("Deleting the broken webhook to fix the cluster")
    99  	err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), brokenWebhookName, metav1.DeleteOptions{})
   100  	if err != nil {
   101  		t.Fatalf("Failed to delete broken webhook: %v", err)
   102  	}
   103  
   104  	// The webhook deletion is honored in 10s.
   105  	time.Sleep(10 * time.Second)
   106  
   107  	// test if the deleted webhook no longer blocks requests
   108  	t.Logf("Creating Deployment to ensure webhook is deleted")
   109  	_, err = client.AppsV1().Deployments("default").Create(context.TODO(), exampleDeployment(generateDeploymentName(3)), metav1.CreateOptions{})
   110  	if err != nil {
   111  		t.Fatalf("Failed to create deployment: %v", err)
   112  	}
   113  }
   114  
   115  func generateDeploymentName(suffix int) string {
   116  	return fmt.Sprintf("%v-%v", deploymentNamePrefix, suffix)
   117  }
   118  
   119  func exampleDeployment(name string) *appsv1.Deployment {
   120  	var replicas int32 = 1
   121  	return &appsv1.Deployment{
   122  		TypeMeta: metav1.TypeMeta{
   123  			Kind:       "Deployment",
   124  			APIVersion: "apps/v1",
   125  		},
   126  		ObjectMeta: metav1.ObjectMeta{
   127  			Namespace: "default",
   128  			Name:      name,
   129  		},
   130  		Spec: appsv1.DeploymentSpec{
   131  			Replicas: &replicas,
   132  			Selector: &metav1.LabelSelector{
   133  				MatchLabels: map[string]string{"foo": "bar"},
   134  			},
   135  			Template: corev1.PodTemplateSpec{
   136  				ObjectMeta: metav1.ObjectMeta{
   137  					Labels: map[string]string{"foo": "bar"},
   138  				},
   139  				Spec: corev1.PodSpec{
   140  					Containers: []corev1.Container{
   141  						{
   142  							Name:  "foo",
   143  							Image: "foo",
   144  						},
   145  					},
   146  				},
   147  			},
   148  		},
   149  	}
   150  }
   151  
   152  func brokenWebhookConfig(name string) *admissionregistrationv1.ValidatingWebhookConfiguration {
   153  	var path string
   154  	failurePolicy := admissionregistrationv1.Fail
   155  	return &admissionregistrationv1.ValidatingWebhookConfiguration{
   156  		ObjectMeta: metav1.ObjectMeta{
   157  			Name: name,
   158  		},
   159  		Webhooks: []admissionregistrationv1.ValidatingWebhook{
   160  			{
   161  				Name: "broken-webhook.k8s.io",
   162  				Rules: []admissionregistrationv1.RuleWithOperations{{
   163  					Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
   164  					Rule: admissionregistrationv1.Rule{
   165  						APIGroups:   []string{"*"},
   166  						APIVersions: []string{"*"},
   167  						Resources:   []string{"*/*"},
   168  					},
   169  				}},
   170  				// This client config references a non existent service
   171  				// so it should always fail.
   172  				ClientConfig: admissionregistrationv1.WebhookClientConfig{
   173  					Service: &admissionregistrationv1.ServiceReference{
   174  						Namespace: "default",
   175  						Name:      "invalid-webhook-service",
   176  						Path:      &path,
   177  					},
   178  					CABundle: nil,
   179  				},
   180  				FailurePolicy:           &failurePolicy,
   181  				SideEffects:             &noSideEffects,
   182  				AdmissionReviewVersions: []string{"v1"},
   183  			},
   184  		},
   185  	}
   186  }