istio.io/istio@v0.0.0-20240520182934-d79c90f27776/tests/integration/security/sds_ingress/util/util.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 util
    19  
    20  import (
    21  	"context"
    22  	"fmt"
    23  	"net/http"
    24  	"strings"
    25  	"time"
    26  
    27  	"github.com/hashicorp/go-multierror"
    28  	v1 "k8s.io/api/core/v1"
    29  	"k8s.io/apimachinery/pkg/api/errors"
    30  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    31  	"sigs.k8s.io/yaml"
    32  
    33  	"istio.io/istio/pkg/config/protocol"
    34  	"istio.io/istio/pkg/http/headers"
    35  	"istio.io/istio/pkg/test/framework"
    36  	"istio.io/istio/pkg/test/framework/components/cluster"
    37  	"istio.io/istio/pkg/test/framework/components/echo"
    38  	"istio.io/istio/pkg/test/framework/components/echo/check"
    39  	"istio.io/istio/pkg/test/framework/components/echo/common/deployment"
    40  	"istio.io/istio/pkg/test/framework/components/echo/echotest"
    41  	"istio.io/istio/pkg/test/framework/components/istio"
    42  	"istio.io/istio/pkg/test/framework/components/istio/ingress"
    43  	"istio.io/istio/pkg/test/framework/components/namespace"
    44  	"istio.io/istio/pkg/test/framework/resource"
    45  	"istio.io/istio/pkg/test/util/retry"
    46  )
    47  
    48  const (
    49  	// The ID/name for the certificate chain in kubernetes tls secret.
    50  	tlsScrtCert = "tls.crt"
    51  	// The ID/name for the k8sKey in kubernetes tls secret.
    52  	tlsScrtKey = "tls.key"
    53  	// The ID/name for the CA certificate in kubernetes tls secret
    54  	tlsScrtCaCert = "ca.crt"
    55  	// The ID/name for the CRL in kubernetes tls secret
    56  	tlsScrtCaCrl = "ca.crl"
    57  	// The ID/name for the certificate chain in kubernetes generic secret.
    58  	genericScrtCert = "cert"
    59  	// The ID/name for the private key in kubernetes generic secret.
    60  	genericScrtKey = "key"
    61  	// The ID/name for the CA certificate in kubernetes generic secret.
    62  	genericScrtCaCert = "cacert"
    63  	// The ID/name for the CRL in kubernetes generic secret.
    64  	genericScrtCaCrl = "crl"
    65  	ASvc             = "a"
    66  	VMSvc            = "vm"
    67  )
    68  
    69  type EchoDeployments struct {
    70  	ServerNs namespace.Instance
    71  	All      echo.Instances
    72  }
    73  
    74  type IngressCredential struct {
    75  	PrivateKey  string
    76  	Certificate string
    77  	CaCert      string
    78  	Crl         string
    79  }
    80  
    81  var IngressCredentialA = IngressCredential{
    82  	PrivateKey:  TLSServerKeyA,
    83  	Certificate: TLSServerCertA,
    84  	CaCert:      CaCertA,
    85  }
    86  
    87  var IngressCredentialServerKeyCertA = IngressCredential{
    88  	PrivateKey:  TLSServerKeyA,
    89  	Certificate: TLSServerCertA,
    90  }
    91  
    92  var IngressCredentialCaCertA = IngressCredential{
    93  	CaCert: CaCertA,
    94  }
    95  
    96  var IngressCredentialB = IngressCredential{
    97  	PrivateKey:  TLSServerKeyB,
    98  	Certificate: TLSServerCertB,
    99  	CaCert:      CaCertB,
   100  }
   101  
   102  var IngressCredentialServerKeyCertB = IngressCredential{
   103  	PrivateKey:  TLSServerKeyB,
   104  	Certificate: TLSServerCertB,
   105  }
   106  
   107  var (
   108  	A  echo.Instances
   109  	VM echo.Instances
   110  )
   111  
   112  // IngressKubeSecretYAML will generate a credential for a gateway
   113  func IngressKubeSecretYAML(name, namespace string, ingressType CallType, ingressCred IngressCredential) string {
   114  	// Create Kubernetes secret for ingress gateway
   115  	secret := createSecret(ingressType, name, namespace, ingressCred, true)
   116  	by, err := yaml.Marshal(secret)
   117  	if err != nil {
   118  		panic(err)
   119  	}
   120  	return string(by) + "\n---\n"
   121  }
   122  
   123  // CreateIngressKubeSecret reads credential names from credNames and key/cert from ingressCred,
   124  // and creates K8s secrets for ingress gateway.
   125  // nolint: interfacer
   126  func CreateIngressKubeSecret(t framework.TestContext, credName string,
   127  	ingressType CallType, ingressCred IngressCredential, isCompoundAndNotGeneric bool, clusters ...cluster.Cluster,
   128  ) {
   129  	t.Helper()
   130  
   131  	// Get namespace for ingress gateway pod.
   132  	istioCfg := istio.DefaultConfigOrFail(t, t)
   133  	systemNS := namespace.ClaimOrFail(t, t, istioCfg.SystemNamespace)
   134  	CreateIngressKubeSecretInNamespace(t, credName, ingressType, ingressCred, isCompoundAndNotGeneric, systemNS.Name(), clusters...)
   135  }
   136  
   137  // CreateIngressKubeSecretInNamespace  reads credential names from credNames and key/cert from ingressCred,
   138  // and creates K8s secrets for ingress gateway in the given namespace.
   139  func CreateIngressKubeSecretInNamespace(t framework.TestContext, credName string,
   140  	ingressType CallType, ingressCred IngressCredential, isCompoundAndNotGeneric bool, ns string, clusters ...cluster.Cluster,
   141  ) {
   142  	t.Helper()
   143  
   144  	t.CleanupConditionally(func() {
   145  		deleteKubeSecret(t, credName)
   146  	})
   147  
   148  	// Create Kubernetes secret for ingress gateway
   149  	wg := multierror.Group{}
   150  	if len(clusters) == 0 {
   151  		clusters = t.Clusters()
   152  	}
   153  	for _, c := range clusters {
   154  		c := c
   155  		wg.Go(func() error {
   156  			secret := createSecret(ingressType, credName, ns, ingressCred, isCompoundAndNotGeneric)
   157  			_, err := c.Kube().CoreV1().Secrets(ns).Create(context.TODO(), secret, metav1.CreateOptions{})
   158  			if err != nil {
   159  				if errors.IsAlreadyExists(err) {
   160  					if _, err := c.Kube().CoreV1().Secrets(ns).Update(context.TODO(), secret, metav1.UpdateOptions{}); err != nil {
   161  						return fmt.Errorf("failed to update secret (error: %s)", err)
   162  					}
   163  				} else {
   164  					return fmt.Errorf("failed to update secret (error: %s)", err)
   165  				}
   166  			}
   167  			// Check if Kubernetes secret is ready
   168  			return retry.UntilSuccess(func() error {
   169  				_, err := c.Kube().CoreV1().Secrets(ns).Get(context.TODO(), credName, metav1.GetOptions{})
   170  				if err != nil {
   171  					return fmt.Errorf("secret %v not found: %v", credName, err)
   172  				}
   173  				return nil
   174  			}, retry.Timeout(time.Second*5))
   175  		})
   176  	}
   177  	if err := wg.Wait().ErrorOrNil(); err != nil {
   178  		t.Fatal(err)
   179  	}
   180  }
   181  
   182  // deleteKubeSecret deletes a secret
   183  // nolint: interfacer
   184  func deleteKubeSecret(t framework.TestContext, credName string) {
   185  	// Get namespace for ingress gateway pod.
   186  	istioCfg := istio.DefaultConfigOrFail(t, t)
   187  	systemNS := namespace.ClaimOrFail(t, t, istioCfg.SystemNamespace)
   188  
   189  	// Delete Kubernetes secret for ingress gateway
   190  	c := t.Clusters().Default()
   191  	var immediate int64
   192  	err := c.Kube().CoreV1().Secrets(systemNS.Name()).Delete(context.TODO(), credName,
   193  		metav1.DeleteOptions{GracePeriodSeconds: &immediate})
   194  	if err != nil && !errors.IsNotFound(err) {
   195  		t.Fatalf("Failed to delete secret (error: %s)", err)
   196  	}
   197  }
   198  
   199  // createSecret creates a kubernetes secret which stores private key, server certificate for TLS ingress gateway.
   200  // For mTLS ingress gateway, createSecret adds ca certificate into the secret object.
   201  
   202  func createSecret(ingressType CallType, cn, ns string, ic IngressCredential, isCompoundAndNotGeneric bool) *v1.Secret {
   203  	if ingressType == Mtls {
   204  		if isCompoundAndNotGeneric {
   205  			return &v1.Secret{
   206  				TypeMeta: metav1.TypeMeta{
   207  					Kind:       "Secret",
   208  					APIVersion: "v1",
   209  				},
   210  				ObjectMeta: metav1.ObjectMeta{
   211  					Name:      cn,
   212  					Namespace: ns,
   213  				},
   214  				Data: map[string][]byte{
   215  					tlsScrtCert:   []byte(ic.Certificate),
   216  					tlsScrtKey:    []byte(ic.PrivateKey),
   217  					tlsScrtCaCert: []byte(ic.CaCert),
   218  					tlsScrtCaCrl:  []byte(ic.Crl),
   219  				},
   220  			}
   221  		}
   222  		return &v1.Secret{
   223  			TypeMeta: metav1.TypeMeta{
   224  				Kind:       "Secret",
   225  				APIVersion: "v1",
   226  			},
   227  			ObjectMeta: metav1.ObjectMeta{
   228  				Name:      cn,
   229  				Namespace: ns,
   230  			},
   231  			Data: map[string][]byte{
   232  				genericScrtCert:   []byte(ic.Certificate),
   233  				genericScrtKey:    []byte(ic.PrivateKey),
   234  				genericScrtCaCert: []byte(ic.CaCert),
   235  				genericScrtCaCrl:  []byte(ic.Crl),
   236  			},
   237  		}
   238  	}
   239  	data := map[string][]byte{}
   240  	if ic.Certificate != "" {
   241  		data[tlsScrtCert] = []byte(ic.Certificate)
   242  	}
   243  	if ic.PrivateKey != "" {
   244  		data[tlsScrtKey] = []byte(ic.PrivateKey)
   245  	}
   246  	if ic.CaCert != "" {
   247  		data[tlsScrtCaCert] = []byte(ic.CaCert)
   248  	}
   249  	return &v1.Secret{
   250  		TypeMeta: metav1.TypeMeta{
   251  			Kind:       "Secret",
   252  			APIVersion: "v1",
   253  		},
   254  		ObjectMeta: metav1.ObjectMeta{
   255  			Name:      cn,
   256  			Namespace: ns,
   257  		},
   258  		Data: data,
   259  	}
   260  }
   261  
   262  // CallType defines ingress gateway type
   263  type CallType int
   264  
   265  const (
   266  	TLS CallType = iota
   267  	Mtls
   268  )
   269  
   270  type ExpectedResponse struct {
   271  	StatusCode                   int
   272  	SkipErrorMessageVerification bool
   273  	ErrorMessage                 string
   274  }
   275  
   276  type TLSContext struct {
   277  	// CaCert is inline base64 encoded root certificate that authenticates server certificate provided
   278  	// by ingress gateway.
   279  	CaCert string
   280  	// PrivateKey is inline base64 encoded private key for test client.
   281  	PrivateKey string
   282  	// Cert is inline base64 encoded certificate for test client.
   283  	Cert string
   284  }
   285  
   286  // SendRequestOrFail makes HTTPS request to ingress gateway to visit product page
   287  func SendRequestOrFail(ctx framework.TestContext, ing ingress.Instance, host string, path string,
   288  	callType CallType, tlsCtx TLSContext, exRsp ExpectedResponse,
   289  ) {
   290  	doSendRequestsOrFail(ctx, ing, host, path, callType, tlsCtx, exRsp, false /* useHTTP3 */)
   291  }
   292  
   293  func SendQUICRequestsOrFail(ctx framework.TestContext, ing ingress.Instance, host string, path string,
   294  	callType CallType, tlsCtx TLSContext, exRsp ExpectedResponse,
   295  ) {
   296  	doSendRequestsOrFail(ctx, ing, host, path, callType, tlsCtx, exRsp, true /* useHTTP3 */)
   297  }
   298  
   299  func doSendRequestsOrFail(ctx framework.TestContext, ing ingress.Instance, host string, path string,
   300  	callType CallType, tlsCtx TLSContext, exRsp ExpectedResponse, useHTTP3 bool,
   301  ) {
   302  	ctx.Helper()
   303  	opts := echo.CallOptions{
   304  		Timeout: time.Second,
   305  		Retry: echo.Retry{
   306  			Options: []retry.Option{retry.Timeout(time.Minute * 2)},
   307  		},
   308  		Port: echo.Port{
   309  			Protocol: protocol.HTTPS,
   310  		},
   311  		HTTP: echo.HTTP{
   312  			HTTP3:   useHTTP3,
   313  			Path:    fmt.Sprintf("/%s", path),
   314  			Headers: headers.New().WithHost(host).Build(),
   315  		},
   316  		TLS: echo.TLS{
   317  			CaCert: tlsCtx.CaCert,
   318  		},
   319  		Check: func(result echo.CallResult, err error) error {
   320  			// Check that the error message is expected.
   321  			if err != nil {
   322  				// If expected error message is empty, but we got some error
   323  				// message then it should be treated as error when error message
   324  				// verification is not skipped. Error message verification is skipped
   325  				// when the error message is non-deterministic.
   326  				if !exRsp.SkipErrorMessageVerification && len(exRsp.ErrorMessage) == 0 {
   327  					return fmt.Errorf("unexpected error: %w", err)
   328  				}
   329  				if !exRsp.SkipErrorMessageVerification && !strings.Contains(err.Error(), exRsp.ErrorMessage) {
   330  					return fmt.Errorf("expected response error message %s but got %w",
   331  						exRsp.ErrorMessage, err)
   332  				}
   333  				return nil
   334  			}
   335  			if callType == Mtls {
   336  				return check.And(check.Status(exRsp.StatusCode), check.MTLSForHTTP()).Check(result, nil)
   337  			}
   338  
   339  			return check.Status(exRsp.StatusCode).Check(result, nil)
   340  		},
   341  	}
   342  
   343  	if callType == Mtls {
   344  		opts.TLS.Key = tlsCtx.PrivateKey
   345  		opts.TLS.Cert = tlsCtx.Cert
   346  	}
   347  
   348  	// Certs occasionally take quite a while to become active in Envoy, so retry for a long time (2min)
   349  	ing.CallOrFail(ctx, opts)
   350  }
   351  
   352  // RotateSecrets deletes kubernetes secrets by name in credNames and creates same secrets using key/cert
   353  // from ingressCred.
   354  func RotateSecrets(ctx framework.TestContext, credName string, // nolint:interfacer
   355  	ingressType CallType, ingressCred IngressCredential, isCompoundAndNotGeneric bool,
   356  ) {
   357  	ctx.Helper()
   358  	c := ctx.Clusters().Default()
   359  	ist := istio.GetOrFail(ctx, ctx)
   360  	systemNS := namespace.ClaimOrFail(ctx, ctx, ist.Settings().SystemNamespace)
   361  	scrt, err := c.Kube().CoreV1().Secrets(systemNS.Name()).Get(context.TODO(), credName, metav1.GetOptions{})
   362  	if err != nil {
   363  		ctx.Errorf("Failed to get secret %s:%s (error: %s)", systemNS.Name(), credName, err)
   364  	}
   365  	scrt = updateSecret(ingressType, scrt, ingressCred, isCompoundAndNotGeneric)
   366  	if _, err = c.Kube().CoreV1().Secrets(systemNS.Name()).Update(context.TODO(), scrt, metav1.UpdateOptions{}); err != nil {
   367  		ctx.Errorf("Failed to update secret %s:%s (error: %s)", scrt.Namespace, scrt.Name, err)
   368  	}
   369  	// Check if Kubernetes secret is ready
   370  	retry.UntilSuccessOrFail(ctx, func() error {
   371  		_, err := c.Kube().CoreV1().Secrets(systemNS.Name()).Get(context.TODO(), credName, metav1.GetOptions{})
   372  		if err != nil {
   373  			return fmt.Errorf("secret %v not found: %v", credName, err)
   374  		}
   375  		return nil
   376  	}, retry.Timeout(time.Second*5))
   377  }
   378  
   379  // createSecret creates a kubernetes secret which stores private key, server certificate for TLS ingress gateway.
   380  // For mTLS ingress gateway, createSecret adds ca certificate into the secret object.
   381  func updateSecret(ingressType CallType, scrt *v1.Secret, ic IngressCredential, isCompoundAndNotGeneric bool) *v1.Secret {
   382  	if ingressType == Mtls {
   383  		if isCompoundAndNotGeneric {
   384  			scrt.Data[tlsScrtCert] = []byte(ic.Certificate)
   385  			scrt.Data[tlsScrtKey] = []byte(ic.PrivateKey)
   386  			scrt.Data[tlsScrtCaCert] = []byte(ic.CaCert)
   387  			scrt.Data[tlsScrtCaCrl] = []byte(ic.Crl)
   388  		} else {
   389  			scrt.Data[genericScrtCert] = []byte(ic.Certificate)
   390  			scrt.Data[genericScrtKey] = []byte(ic.PrivateKey)
   391  			scrt.Data[genericScrtCaCert] = []byte(ic.CaCert)
   392  			scrt.Data[genericScrtCaCrl] = []byte(ic.Crl)
   393  		}
   394  	} else {
   395  		scrt.Data[tlsScrtCert] = []byte(ic.Certificate)
   396  		scrt.Data[tlsScrtKey] = []byte(ic.PrivateKey)
   397  	}
   398  	return scrt
   399  }
   400  
   401  func EchoConfig(service string, ns namespace.Instance, buildVM bool) echo.Config {
   402  	return echo.Config{
   403  		Service:   service,
   404  		Namespace: ns,
   405  		Ports: []echo.Port{
   406  			{
   407  				Name:     "http",
   408  				Protocol: protocol.HTTP,
   409  				// We use a port > 1024 to not require root
   410  				WorkloadPort: 8090,
   411  			},
   412  		},
   413  		DeployAsVM: buildVM,
   414  	}
   415  }
   416  
   417  func SetupTest(ctx resource.Context, customCfg *[]echo.Config, customNs namespace.Getter) error {
   418  	var customConfig []echo.Config
   419  	buildVM := !ctx.Settings().Skip(echo.VM)
   420  	a := EchoConfig(ASvc, customNs.Get(), false)
   421  	vm := EchoConfig(VMSvc, customNs.Get(), buildVM)
   422  	customConfig = append(customConfig, a, vm)
   423  	*customCfg = customConfig
   424  	return nil
   425  }
   426  
   427  type TestConfig struct {
   428  	Mode           string
   429  	CredentialName string
   430  	Host           string
   431  	ServiceName    string
   432  	GatewayLabel   string
   433  }
   434  
   435  const vsTemplate = `
   436  apiVersion: networking.istio.io/v1alpha3
   437  kind: VirtualService
   438  metadata:
   439    name: {{.CredentialName}}
   440  spec:
   441    hosts:
   442    - "{{.Host}}"
   443    gateways:
   444    - {{.CredentialName}}
   445    http:
   446    - match:
   447      - uri:
   448          exact: /{{.CredentialName}}
   449      route:
   450      - destination:
   451          host: {{.ServiceName}}
   452          port:
   453            number: 80
   454  `
   455  
   456  const gwTemplate = `
   457  apiVersion: networking.istio.io/v1alpha3
   458  kind: Gateway
   459  metadata:
   460    name: {{.CredentialName}}
   461  spec:
   462    selector:
   463      istio: {{.GatewayLabel | default "ingressgateway"}}
   464    servers:
   465    - port:
   466        number: 443
   467        name: https
   468        protocol: HTTPS
   469      tls:
   470        mode: {{.Mode}}
   471        credentialName: "{{.CredentialName}}"
   472      hosts:
   473      - "{{.Host}}"
   474  `
   475  
   476  func SetupConfig(ctx framework.TestContext, ns namespace.Instance, config ...TestConfig) {
   477  	b := ctx.ConfigIstio().New()
   478  	for _, c := range config {
   479  		b.Eval(ns.Name(), c, vsTemplate)
   480  		b.Eval(ns.Name(), c, gwTemplate)
   481  	}
   482  	b.ApplyOrFail(ctx)
   483  }
   484  
   485  // RunTestMultiMtlsGateways deploys multiple mTLS gateways with SDS enabled, and creates kubernetes secret that stores
   486  // private key, server certificate and CA certificate for each mTLS gateway. Verifies that all gateways are able to terminate
   487  // mTLS connections successfully.
   488  func RunTestMultiMtlsGateways(ctx framework.TestContext, inst istio.Instance, ns namespace.Getter) { // nolint:interfacer
   489  	var credNames []string
   490  	var tests []TestConfig
   491  	echotest.New(ctx, A).
   492  		SetupForDestination(func(ctx framework.TestContext, to echo.Target) error {
   493  			for i := 1; i < 6; i++ {
   494  				cred := fmt.Sprintf("runtestmultimtlsgateways-%d", i)
   495  				tests = append(tests, TestConfig{
   496  					Mode:           "MUTUAL",
   497  					CredentialName: cred,
   498  					Host:           fmt.Sprintf("runtestmultimtlsgateways%d.example.com", i),
   499  					ServiceName:    to.Config().Service,
   500  					GatewayLabel:   inst.Settings().IngressGatewayIstioLabel,
   501  				})
   502  				credNames = append(credNames, cred)
   503  			}
   504  			SetupConfig(ctx, ns.Get(), tests...)
   505  			return nil
   506  		}).
   507  		To(echotest.SimplePodServiceAndAllSpecial(1)).
   508  		RunFromClusters(func(ctx framework.TestContext, fromCluster cluster.Cluster, to echo.Target) {
   509  			for _, cn := range credNames {
   510  				CreateIngressKubeSecret(ctx, cn, Mtls, IngressCredentialA, false)
   511  			}
   512  
   513  			ing := inst.IngressFor(fromCluster)
   514  			if ing == nil {
   515  				ctx.Skip()
   516  			}
   517  			tlsContext := TLSContext{
   518  				CaCert:     CaCertA,
   519  				PrivateKey: TLSClientKeyA,
   520  				Cert:       TLSClientCertA,
   521  			}
   522  			callType := Mtls
   523  
   524  			for _, h := range tests {
   525  				ctx.NewSubTest(h.Host).Run(func(t framework.TestContext) {
   526  					SendRequestOrFail(t, ing, h.Host, h.CredentialName, callType, tlsContext,
   527  						ExpectedResponse{StatusCode: http.StatusOK})
   528  				})
   529  			}
   530  		})
   531  }
   532  
   533  // RunTestMultiTLSGateways deploys multiple TLS gateways with SDS enabled, and creates kubernetes secret that stores
   534  // private key and server certificate for each TLS gateway. Verifies that all gateways are able to terminate
   535  // SSL connections successfully.
   536  func RunTestMultiTLSGateways(t framework.TestContext, inst istio.Instance, ns namespace.Getter) { // nolint:interfacer
   537  	var credNames []string
   538  	var tests []TestConfig
   539  	echotest.New(t, A).
   540  		SetupForDestination(func(t framework.TestContext, to echo.Target) error {
   541  			for i := 1; i < 6; i++ {
   542  				cred := fmt.Sprintf("runtestmultitlsgateways-%d", i)
   543  				tests = append(tests, TestConfig{
   544  					Mode:           "SIMPLE",
   545  					CredentialName: cred,
   546  					Host:           fmt.Sprintf("runtestmultitlsgateways%d.example.com", i),
   547  					ServiceName:    to.Config().Service,
   548  					GatewayLabel:   inst.Settings().IngressGatewayIstioLabel,
   549  				})
   550  				credNames = append(credNames, cred)
   551  			}
   552  			SetupConfig(t, ns.Get(), tests...)
   553  			return nil
   554  		}).
   555  		To(echotest.SimplePodServiceAndAllSpecial(1)).
   556  		RunFromClusters(func(t framework.TestContext, fromCluster cluster.Cluster, to echo.Target) {
   557  			for _, cn := range credNames {
   558  				CreateIngressKubeSecret(t, cn, TLS, IngressCredentialA, false)
   559  			}
   560  
   561  			ing := inst.IngressFor(fromCluster)
   562  			if ing == nil {
   563  				t.Skip()
   564  			}
   565  			tlsContext := TLSContext{
   566  				CaCert: CaCertA,
   567  			}
   568  			callType := TLS
   569  
   570  			for _, h := range tests {
   571  				t.NewSubTest(h.Host).Run(func(t framework.TestContext) {
   572  					SendRequestOrFail(t, ing, h.Host, h.CredentialName, callType, tlsContext,
   573  						ExpectedResponse{StatusCode: http.StatusOK})
   574  				})
   575  			}
   576  		})
   577  }
   578  
   579  // RunTestMultiQUICGateways deploys multiple TLS/mTLS gateways with SDS enabled, and creates kubernetes secret that stores
   580  // private key and server certificate for each TLS/mTLS gateway. Verifies that all gateways are able to terminate
   581  // QUIC connections successfully.
   582  func RunTestMultiQUICGateways(t framework.TestContext, inst istio.Instance, callType CallType, ns namespace.Getter) {
   583  	var credNames []string
   584  	var tests []TestConfig
   585  	allInstances := []echo.Instances{A, VM}
   586  	for _, instances := range allInstances {
   587  		echotest.New(t, instances).
   588  			SetupForDestination(func(t framework.TestContext, to echo.Target) error {
   589  				for i := 1; i < 6; i++ {
   590  					cred := fmt.Sprintf("runtestmultitlsgateways-%d", i)
   591  					mode := "SIMPLE"
   592  					if callType == Mtls {
   593  						mode = "MUTUAL"
   594  					}
   595  					tests = append(tests, TestConfig{
   596  						Mode:           mode,
   597  						CredentialName: cred,
   598  						Host:           fmt.Sprintf("runtestmultitlsgateways%d.example.com", i),
   599  						ServiceName:    to.Config().Service,
   600  						GatewayLabel:   inst.Settings().IngressGatewayIstioLabel,
   601  					})
   602  					credNames = append(credNames, cred)
   603  				}
   604  				SetupConfig(t, ns.Get(), tests...)
   605  				return nil
   606  			}).
   607  			To(echotest.SimplePodServiceAndAllSpecial(1)).
   608  			RunFromClusters(func(t framework.TestContext, fromCluster cluster.Cluster, to echo.Target) {
   609  				for _, cn := range credNames {
   610  					CreateIngressKubeSecret(t, cn, TLS, IngressCredentialA, false)
   611  				}
   612  
   613  				ing := inst.IngressFor(fromCluster)
   614  				if ing == nil {
   615  					t.Skip()
   616  				}
   617  				tlsContext := TLSContext{
   618  					CaCert: CaCertA,
   619  				}
   620  				if callType == Mtls {
   621  					tlsContext = TLSContext{
   622  						CaCert:     CaCertA,
   623  						PrivateKey: TLSClientKeyA,
   624  						Cert:       TLSClientCertA,
   625  					}
   626  				}
   627  
   628  				for _, h := range tests {
   629  					t.NewSubTest(h.Host).Run(func(t framework.TestContext) {
   630  						SendQUICRequestsOrFail(t, ing, h.Host, h.CredentialName, callType, tlsContext,
   631  							ExpectedResponse{StatusCode: http.StatusOK})
   632  					})
   633  				}
   634  			})
   635  	}
   636  }
   637  
   638  func CreateCustomInstances(apps *deployment.SingleNamespaceView) error {
   639  	for index, namespacedName := range apps.EchoNamespace.All.NamespacedNames() {
   640  		switch {
   641  		case namespacedName.Name == "a":
   642  			A = apps.EchoNamespace.All[index]
   643  		case namespacedName.Name == "vm":
   644  			VM = apps.EchoNamespace.All[index]
   645  		}
   646  	}
   647  	return nil
   648  }