istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/pilot/webhook_test.go (about) 1 //go:build integ 2 // +build integ 3 4 // Copyright Istio Authors 5 // 6 // Licensed under the Apache License, Version 2.0 (the "License"); 7 // you may not use this file except in compliance with the License. 8 // You may obtain a copy of the License at 9 // 10 // http://www.apache.org/licenses/LICENSE-2.0 11 // 12 // Unless required by applicable law or agreed to in writing, software 13 // distributed under the License is distributed on an "AS IS" BASIS, 14 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 // See the License for the specific language governing permissions and 16 // limitations under the License. 17 18 package pilot 19 20 import ( 21 "context" 22 "errors" 23 "fmt" 24 "testing" 25 26 kubeApiAdmission "k8s.io/api/admissionregistration/v1" 27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 28 "k8s.io/client-go/kubernetes" 29 30 "istio.io/api/label" 31 networking "istio.io/api/networking/v1alpha3" 32 "istio.io/client-go/pkg/apis/networking/v1alpha3" 33 "istio.io/istio/pkg/test/framework" 34 "istio.io/istio/pkg/test/util/retry" 35 ) 36 37 func TestWebhook(t *testing.T) { 38 // nolint: staticcheck 39 framework.NewTest(t). 40 RequiresSingleCluster(). 41 Run(func(t framework.TestContext) { 42 vwcName := "istio-validator" 43 if t.Settings().Revisions.Default() != "" { 44 vwcName = fmt.Sprintf("%s-%s", vwcName, t.Settings().Revisions.Default()) 45 } 46 vwcName += "-istio-system" 47 webhooks := []string{vwcName, "istiod-default-validator"} 48 49 // clear the updated fields and verify istiod updates them 50 cluster := t.Clusters().Default() 51 for _, vwcName := range webhooks { 52 retry.UntilSuccessOrFail(t, func() error { 53 got, err := getValidatingWebhookConfiguration(cluster.Kube(), vwcName) 54 if err != nil { 55 return fmt.Errorf("error getting initial webhook: %v", err) 56 } 57 if err := verifyValidatingWebhookConfiguration(got); err != nil { 58 return err 59 } 60 61 updated := got.DeepCopyObject().(*kubeApiAdmission.ValidatingWebhookConfiguration) 62 updated.Webhooks[0].ClientConfig.CABundle = nil 63 ignore := kubeApiAdmission.Ignore // can't take the address of a constant 64 updated.Webhooks[0].FailurePolicy = &ignore 65 66 if _, err := cluster.Kube().AdmissionregistrationV1().ValidatingWebhookConfigurations().Update(context.TODO(), 67 updated, metav1.UpdateOptions{}); err != nil { 68 return fmt.Errorf("could not update validating webhook config %q: %v", updated.Name, err) 69 } 70 return nil 71 }) 72 73 retry.UntilSuccessOrFail(t, func() error { 74 got, err := getValidatingWebhookConfiguration(cluster.Kube(), vwcName) 75 if err != nil { 76 t.Fatalf("error getting initial webhook: %v", err) 77 } 78 if err := verifyValidatingWebhookConfiguration(got); err != nil { 79 return fmt.Errorf("validatingwebhookconfiguration not updated yet: %v", err) 80 } 81 return nil 82 }) 83 } 84 85 revision := "default" 86 if t.Settings().Revisions.Default() != "" { 87 revision = t.Settings().Revisions.Default() 88 } 89 verifyRejectsInvalidConfig(t, revision, true) 90 verifyRejectsInvalidConfig(t, "", true) 91 }) 92 } 93 94 func getValidatingWebhookConfiguration(client kubernetes.Interface, name string) (*kubeApiAdmission.ValidatingWebhookConfiguration, error) { 95 whc, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Get(context.TODO(), 96 name, metav1.GetOptions{}) 97 if err != nil { 98 return nil, fmt.Errorf("could not get validating webhook config %q: %v", name, err) 99 } 100 return whc, nil 101 } 102 103 func verifyValidatingWebhookConfiguration(c *kubeApiAdmission.ValidatingWebhookConfiguration) error { 104 if len(c.Webhooks) == 0 { 105 return errors.New("no webhook entries found") 106 } 107 for i, wh := range c.Webhooks { 108 if *wh.FailurePolicy != kubeApiAdmission.Fail { 109 return fmt.Errorf("webhook #%v: wrong failure policy. c %v wanted %v", 110 i, *wh.FailurePolicy, kubeApiAdmission.Fail) 111 } 112 if len(wh.ClientConfig.CABundle) == 0 { 113 return fmt.Errorf("webhook #%v: caBundle not patched", i) 114 } 115 } 116 return nil 117 } 118 119 func verifyRejectsInvalidConfig(t framework.TestContext, configRevision string, shouldReject bool) { 120 t.Helper() 121 const istioNamespace = "istio-system" 122 revLabel := map[string]string{} 123 if configRevision != "" { 124 revLabel[label.IoIstioRev.Name] = configRevision 125 } 126 invalidGateway := &v1alpha3.Gateway{ 127 ObjectMeta: metav1.ObjectMeta{ 128 Name: "invalid-istio-gateway", 129 Namespace: istioNamespace, 130 Labels: revLabel, 131 }, 132 Spec: networking.Gateway{}, 133 } 134 135 createOptions := metav1.CreateOptions{DryRun: []string{metav1.DryRunAll}} 136 istioClient := t.Clusters().Default().Istio().NetworkingV1alpha3() 137 _, err := istioClient.Gateways(istioNamespace).Create(context.TODO(), invalidGateway, createOptions) 138 rejected := err != nil 139 if rejected != shouldReject { 140 t.Errorf("Config rejected: %t, expected config rejected: %t", rejected, shouldReject) 141 } 142 }