broken_webhook_test.go 6.0 KB

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