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  }